Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add assert_encode macro to simplify tests #163

Merged
merged 5 commits into from
Oct 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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