Skip to content

Commit

Permalink
Merge pull request #163 from ufirstgroup/add-assert_encode-macro
Browse files Browse the repository at this point in the history
add assert_encode macro to simplify tests
  • Loading branch information
jlgeering authored Oct 18, 2023
2 parents 09b86fd + fbec1af commit 5383911
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 84 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
### Fixed

- handle "\n" strings - [#157](https://github.com/ufirstgroup/ymlr/issues/157),[#159](https://github.com/ufirstgroup/ymlr/pull/159)
- handle empty list - [#163](https://github.com/ufirstgroup/ymlr/pull/163)

<!--------------------- Don't add new entries after this line --------------------->

Expand Down
4 changes: 3 additions & 1 deletion lib/ymlr/encode.ex
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ defmodule Ymlr.Encode do
end

@spec list(data :: list(), indent_level :: integer, opts :: Encoder.opts()) :: iodata()
def list([], _, _), do: "[]"

def list(data, indent_level, opts) do
indentation = indent(indent_level)

Expand Down Expand Up @@ -202,7 +204,7 @@ defmodule Ymlr.Encode do
indentation = indent(level)

block =
data |> String.trim_trailing("\n") |> String.replace("\n", IO.iodata_to_binary(indentation))
data |> String.replace("\n", IO.iodata_to_binary(indentation))

[block_chomping_indicator(data) | [indentation | block]]
end
Expand Down
180 changes: 97 additions & 83 deletions test/ymlr/encode_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -4,96 +4,111 @@ defmodule Ymlr.EncodeTest do

alias Ymlr.Encode, as: MUT

defmacro assert_encode(input) do
quote do
assert unquote(input) |> MUT.to_s!() |> YamlElixir.read_from_string!() == unquote(input)
end
end

defmacro assert_encode(input, exptected_output, opts \\ []) do
quote do
assert unquote(input) |> MUT.to_s!() |> YamlElixir.read_from_string!() == unquote(input)
assert MUT.to_s!(unquote(input), unquote(opts)) == unquote(exptected_output)
end
end

describe "to_s!/1" do
test "atoms" do
assert MUT.to_s!(:a) == "a"
end

test "empty string" do
assert MUT.to_s!("") == "''"
assert_encode("", "''")
end

test "simple string" do
assert MUT.to_s!("hello world") == "hello world"
assert_encode("hello world", "hello world")
end

# see http://blogs.perl.org/users/tinita/2018/03/strings-in-yaml---to-quote-or-not-to-quote.html

test "quoted strings - avoid type confusion" do
assert MUT.to_s!("yes") == ~S('yes')
assert MUT.to_s!("no") == ~S('no')
assert MUT.to_s!("true") == ~S('true')
assert MUT.to_s!("false") == ~S('false')
assert MUT.to_s!("True") == ~S('True')
assert MUT.to_s!("False") == ~S('False')
assert MUT.to_s!("1") == ~S('1')
assert MUT.to_s!("1.2") == ~S('1.2')
assert MUT.to_s!("-1") == ~S('-1')
assert MUT.to_s!("1e3") == ~S('1e3')
assert MUT.to_s!("0b1010") == ~S('0b1010')
assert MUT.to_s!("0o777") == ~S('0o777')
assert MUT.to_s!("0x1F") == ~S('0x1F')
assert MUT.to_s!("null") == ~S('null')
assert_encode("yes", ~S('yes'))
assert_encode("no", ~S('no'))
assert_encode("true", ~S('true'))
assert_encode("false", ~S('false'))
assert_encode("True", ~S('True'))
assert_encode("False", ~S('False'))
assert_encode("1", ~S('1'))
assert_encode("1.2", ~S('1.2'))
assert_encode("-1", ~S('-1'))
assert_encode("1e3", ~S('1e3'))
assert_encode("0b1010", ~S('0b1010'))
assert_encode("0o777", ~S('0o777'))
assert_encode("0x1F", ~S('0x1F'))
assert_encode("null", ~S('null'))
end

test "quoted strings - avoid mapping confusion" do
assert MUT.to_s!("hello: world") == ~S('hello: world')
assert_encode("hello: world", ~S('hello: world'))
end

test "quoted strings - avoid comment confusion" do
assert MUT.to_s!("hello #world") == ~S('hello #world')
assert_encode("hello #world", ~S('hello #world'))
end

test "quoted strings - starts with special char" do
assert MUT.to_s!("!tag") == ~S('!tag')
assert MUT.to_s!("&anchor") == ~S('&anchor')
assert MUT.to_s!("*alias") == ~S('*alias')
assert MUT.to_s!("- block sequence enty") == ~S('- block sequence enty')
assert MUT.to_s!(": block mapping entry") == ~S(': block mapping entry')
assert MUT.to_s!("? explicit mapping key") == ~S('? explicit mapping key')
assert MUT.to_s!("{flow_mapping") == ~S('{flow_mapping')
assert MUT.to_s!("}flow_mapping") == ~S('}flow_mapping')
assert MUT.to_s!(":{ block flow") == ~S(':{ block flow')
assert MUT.to_s!("[sequence_mapping") == ~S('[sequence_mapping')
assert MUT.to_s!("]sequence_mapping") == ~S(']sequence_mapping')
assert_encode("!tag", ~S('!tag'))
assert_encode("&anchor", ~S('&anchor'))
assert_encode("*alias", ~S('*alias'))
assert_encode("- block sequence enty", ~S('- block sequence enty'))
assert_encode(": block mapping entry", ~S(': block mapping entry'))
assert_encode("? explicit mapping key", ~S('? explicit mapping key'))
assert_encode("{flow_mapping", ~S('{flow_mapping'))
assert_encode("}flow_mapping", ~S('}flow_mapping'))
assert_encode(":{ block flow", ~S(':{ block flow'))
assert_encode("[sequence_mapping", ~S('[sequence_mapping'))
assert_encode("]sequence_mapping", ~S(']sequence_mapping'))

assert MUT.to_s!(",flow_collection_entry_separator") ==
~S(',flow_collection_entry_separator')

assert MUT.to_s!("#comment") == ~S('#comment')
assert MUT.to_s!("|block_scalar") == ~S('|block_scalar')
assert MUT.to_s!(">block_scalar") == ~S('>block_scalar')
assert MUT.to_s!(~S("double_quote)) == ~S('"double_quote')
assert MUT.to_s!(~S('single_quote)) == ~S("'single_quote")
assert_encode("#comment", ~S('#comment'))
assert_encode("|block_scalar", ~S('|block_scalar'))
assert_encode(">block_scalar", ~S('>block_scalar'))
assert_encode(~S("double_quote), ~S('"double_quote'))
assert_encode(~S('single_quote), ~S("'single_quote"))
# see https://yaml.org/spec/1.2.2/#rule-c-reserved
assert MUT.to_s!("@reserved") == ~S('@reserved')
assert MUT.to_s!("`reserved") == ~S('`reserved')
assert_encode("@reserved", ~S('@reserved'))
assert_encode("`reserved", ~S('`reserved'))
end

test "quoted strings - ends with colon" do
assert MUT.to_s!("some:entry:") == ~S('some:entry:')
assert_encode("some:entry:", ~S('some:entry:'))
end

test "quoted strings - escape seq forces double quotes (tab char)" do
assert MUT.to_s!("a\tb") == ~s("a\tb")
assert MUT.to_s!("!a\tb") == ~s("!a\tb")
assert_encode("a\tb", ~s("a\tb"))
assert_encode("!a\tb", ~s("!a\tb"))
# Not for explicit backslash:
assert_encode(~S(!a\tb), ~S('!a\tb'))
end

test "quoted strings - listy and mappy things" do
# ... (prefer single quotes)
assert MUT.to_s!("[]") == ~S('[]')
assert MUT.to_s!(~S(["hello"])) == ~S('["hello"]')
assert MUT.to_s!(~S(["he|\o"])) == ~S('["he|\o"]')
assert MUT.to_s!("{}") == ~S('{}')
assert MUT.to_s!("[{}]") == ~S('[{}]')
assert_encode("[]", ~S('[]'))
assert_encode(~S(["hello"]), ~S('["hello"]'))
assert_encode(~S(["he|\o"]), ~S('["he|\o"]'))
assert_encode("{}", ~S('{}'))
assert_encode("[{}]", ~S('[{}]'))
# ... (use double quotes if string contains single quotes)
assert MUT.to_s!(~S(["I don't know!\nRea|\y?"])) == ~S("[\"I don't know!\\nRea|\\y?\"]")
assert_encode(~S(["I don't know!\nRea|\y?"]), ~S("[\"I don't know!\\nRea|\\y?\"]"))
end

test "quoted strings - in map key" do
assert MUT.to_s!(%{"!key" => "value"}) == ~S('!key': value)
assert MUT.to_s!(%{"@key" => "value"}) == ~S('@key': value)
assert MUT.to_s!(%{"true" => "value"}) == ~S('true': value)
assert_encode(%{"!key" => "value"}, ~S('!key': value))
assert_encode(%{"@key" => "value"}, ~S('@key': value))
assert_encode(%{"true" => "value"}, ~S('true': value))
end

test "bitstrings" do
Expand All @@ -105,69 +120,71 @@ defmodule Ymlr.EncodeTest do
@tag skip: "not sure about those => to be reviewed"
# https://yaml.org/spec/1.2.2/#example-escaped-characters
test "quoted strings - example-escaped-characters from 1.2.2 spec" do
assert MUT.to_s!("Fun with \\") == ~S("Fun with \\")
assert MUT.to_s!("\" \u0007 \b \u001b \f") == ~S("\" \a \b \e \f")
# assert MUT.to_s!("\n \r \t \u000b \u0000") == ~S("\n \r \t \v \0")
assert_encode("Fun with \\", ~S("Fun with \\"))
assert_encode("\" \u0007 \b \u001b \f", ~S("\" \a \b \e \f"))
# assert_encode("\n \r \t \u000b \u0000", ~S("\n \r \t \v \0"))
# or we use | when string contains newlines => rewrite the example to:
assert MUT.to_s!("\r \t \u000b \u0000") == ~S("\r \t \v \0")
assert MUT.to_s!("\u0020 \u00a0 \u0085 \u2028 \u2029") == ~S("\ \_ \N \L \P")
assert_encode("\r \t \u000b \u0000", ~S("\r \t \v \0"))
assert_encode("\u0020 \u00a0 \u0085 \u2028 \u2029", ~S("\ \_ \N \L \P"))
end

@tag skip: "not sure about those => review the spec"
test "quoted strings - in map key (requires escape char)" do
assert MUT.to_s!(%{"a\tb" => "value"}) == ~s("a\tb": value)
assert MUT.to_s!(%{"a\rb" => "value"}) == ~s("a\rb": value)
assert_encode(%{"a\tb" => "value"}, ~s("a\tb": value))
assert_encode(%{"a\rb" => "value"}, ~s("a\rb": value))
end

test "newline in map key" do
assert MUT.to_s!(%{"a\nb" => "value"}) == ~S("a\nb": value)
assert_encode(%{"a\nb" => "value"}, ~S("a\nb": value))
end

test "integers" do
assert MUT.to_s!(1) == "1"
assert_encode(1, "1")
end

test "floats" do
assert MUT.to_s!(1.2) == "1.2"
assert_encode(1.2, "1.2")
end

test "decimals" do
assert MUT.to_s!(Decimal.new("1.2")) == "1.2"
end

test "hex and oversize float" do
assert MUT.to_s!("7e0981ff4c0daa3a47db5542ad5c167176145ef65f597a7f94ba2f5b41d35718") ==
"7e0981ff4c0daa3a47db5542ad5c167176145ef65f597a7f94ba2f5b41d35718"
assert_encode(
"7e0981ff4c0daa3a47db5542ad5c167176145ef65f597a7f94ba2f5b41d35718",
"7e0981ff4c0daa3a47db5542ad5c167176145ef65f597a7f94ba2f5b41d35718"
)

assert MUT.to_s!("1.7976931348623157e+309") == "1.7976931348623157e+309"
assert_encode("1.7976931348623157e+309", "1.7976931348623157e+309")
end

test "lists" do
assert MUT.to_s!([]) == ""
assert MUT.to_s!([1]) == "- 1"
assert MUT.to_s!([""]) == ~s(- "")
assert MUT.to_s!([1, nil, 2]) == "- 1\n-\n- 2"
assert_encode([], "[]")
assert_encode([1], "- 1")
assert_encode([""], ~s(- ""))
assert_encode([1, nil, 2], "- 1\n-\n- 2")
end

test "empty map" do
assert MUT.to_s!(%{}) == "{}"
assert_encode(%{}, "{}")
end

test "maps" do
assert MUT.to_s!(%{a: 1}) == "a: 1"
assert MUT.to_s!(%{a: 1, b: 2}) == "a: 1\nb: 2"
assert MUT.to_s!(%{"a" => 1, "b" => 2}) == "a: 1\nb: 2"
assert MUT.to_s!(%{"a b" => 1, "c d" => 2}) == "a b: 1\nc d: 2"
assert MUT.to_s!(%{1 => 1, 2 => 2}) == "1: 1\n2: 2"
assert_encode(%{"a" => 1, "b" => 2}, "a: 1\nb: 2")
assert_encode(%{"a b" => 1, "c d" => 2}, "a b: 1\nc d: 2")
assert_encode(%{1 => 1, 2 => 2}, "1: 1\n2: 2")
assert MUT.to_s!(%{a: nil}) == "a:"
end

test "maps with atoms: true" do
assert MUT.to_s!(%{a: 1}, atoms: true) == ":a: 1"
assert MUT.to_s!(%{a: 1, b: 2}, atoms: true) == ":a: 1\n:b: 2"
assert MUT.to_s!(%{"a" => 1, "b" => 2}, atoms: true) == "a: 1\nb: 2"
assert MUT.to_s!(%{"a b" => 1, "c d" => 2}, atoms: true) == "a b: 1\nc d: 2"
assert MUT.to_s!(%{1 => 1, 2 => 2}, atoms: true) == "1: 1\n2: 2"
assert_encode(%{"a" => 1, "b" => 2}, "a: 1\nb: 2", atoms: true)
assert_encode(%{"a b" => 1, "c d" => 2}, "a b: 1\nc d: 2", atoms: true)
assert_encode(%{1 => 1, 2 => 2}, "1: 1\n2: 2", atoms: true)
assert MUT.to_s!(%{a: nil}, atoms: true) == ":a:"
end

Expand Down Expand Up @@ -221,7 +238,7 @@ defmodule Ymlr.EncodeTest do
end

test "nested: list / list" do
assert MUT.to_s!([[1, 2], [3, 4]]) == "- - 1\n - 2\n- - 3\n - 4"
assert_encode([[1, 2], [3, 4]], "- - 1\n - 2\n- - 3\n - 4")
end

test "nested: list / map" do
Expand Down Expand Up @@ -266,7 +283,8 @@ defmodule Ymlr.EncodeTest do

test "nested: map / map" do
result =
MUT.to_s!(%{a: %{b: 1, c: %{d: 2}}, e: %{f: 3, g: 4}}) |> YamlElixir.read_from_string!()
MUT.to_s!(%{a: %{b: 1, c: %{d: 2}}, e: %{f: 3, g: 4}})
|> YamlElixir.read_from_string!()

assert 2 == result["a"]["c"]["d"]
assert 1 == result["a"]["b"]
Expand All @@ -276,8 +294,8 @@ defmodule Ymlr.EncodeTest do

# see https://yaml-multiline.info/
test "multiline strings" do
assert MUT.to_s!("hello\nworld") == "|-\nhello\nworld"
assert MUT.to_s!("hello\nworld\n") == "|\nhello\nworld"
assert_encode("hello\nworld", "|-\nhello\nworld")
assert_encode("hello\nworld\n", "|\nhello\nworld\n")
end

test "newline only string - encoding" do
Expand Down Expand Up @@ -327,17 +345,13 @@ defmodule Ymlr.EncodeTest do
}
"""

# not working yet => TODO better handling of terminal newlines
# assert YamlElixir.read_from_string!(encoded) == given
assert YamlElixir.read_from_string!(encoded) == given
# assert encoded == expected
assert encoded == String.trim(expected)
assert encoded == expected <> " "
end

test "nested: list / multiline string" do
given = ["a\nb\n", "c"]
encoded = MUT.to_s!(given)

assert encoded == "- |\n a\n b\n- c"
assert_encode(["a\nb\n", "c"], "- |\n a\n b\n \n- c")
end

test "nested: map / multiline string" do
Expand Down

0 comments on commit 5383911

Please sign in to comment.