From c965048f40f4bc43794c45a941841d8ed7a82aba Mon Sep 17 00:00:00 2001 From: Mitchell Hanberg Date: Sun, 29 Aug 2021 17:45:07 -0400 Subject: [PATCH] Better whitespace handling and control (#145) * Fine tune whitespace The EEx outut now emits more human-readable and predictable formatting. This includes proper indenting, at least for each "root" template. * Internal whitespace control You can now use a bang version of any nonvoid tag to emit the markup witout the internal whitespace. This means that there will not be a newline emitted after the opening tag and before the closing tag. --- .formatter.exs | 9 +- lib/temple.ex | 39 ++- lib/temple/generator.ex | 2 +- lib/temple/parser.ex | 2 +- lib/temple/parser/anonymous_functions.ex | 10 +- lib/temple/parser/components.ex | 17 +- lib/temple/parser/default.ex | 4 +- lib/temple/parser/do_expressions.ex | 15 +- lib/temple/parser/element_list.ex | 33 +++ lib/temple/parser/empty.ex | 2 +- lib/temple/parser/match.ex | 4 +- lib/temple/parser/nonvoid_elements_aliases.ex | 30 ++- lib/temple/parser/right_arrow.ex | 6 +- lib/temple/parser/slot.ex | 7 +- lib/temple/parser/text.ex | 4 +- lib/temple/parser/utils.ex | 4 + lib/temple/parser/void_elements_aliases.ex | 8 +- test/component_test.exs | 75 +++++- test/parser/anonymous_functions_test.exs | 12 +- test/parser/components_test.exs | 29 ++- test/parser/default_test.exs | 6 +- test/parser/do_expressions_test.exs | 32 ++- test/parser/nonvoid_elements_aliases_test.exs | 67 ++++- test/parser/right_arrow_test.exs | 9 +- test/parser/slot_test.exs | 4 +- test/parser/temple_namespace_nonvoid_test.exs | 15 +- test/parser/temple_namespace_void_test.exs | 4 +- test/parser/text_test.exs | 4 +- test/parser/void_elements_aliases_test.exs | 4 +- test/support/utils.ex | 16 ++ test/temple_test.exs | 242 ++++++++++++++---- test/whitespace_test.exs | 46 ++++ 32 files changed, 613 insertions(+), 148 deletions(-) create mode 100644 lib/temple/parser/element_list.ex create mode 100644 test/whitespace_test.exs diff --git a/.formatter.exs b/.formatter.exs index 71b9052..ac394c7 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -16,7 +16,6 @@ locals_without_parens = ~w[ details summary menuitem menu meta link base area br col embed hr img input keygen param source track wbr - txt partial animate animateMotion animateTransform circle clipPath color-profile defs desc discard ellipse feBlend @@ -26,13 +25,7 @@ locals_without_parens = ~w[ marker mask mesh meshgradient meshpatch meshrow metadata mpath path pattern polygon polyline radialGradient rect set solidcolor stop svg switch symbol text textPath tspan unknown use view - - form_for inputs_for - checkbox color_input checkbox color_input date_input date_select datetime_local_input - datetime_select email_input file_input hidden_input number_input password_input range_input - search_input telephone_input textarea text_input time_input time_select url_input - reset submit phx_label radio_button multiple_select select phx_link phx_button -]a |> Enum.map(fn e -> {e, :*} end) +]a |> Enum.flat_map(fn e -> [{e, :*}, {:"#{e}!", :*}] end) [ inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"], diff --git a/lib/temple.ex b/lib/temple.ex index 9d6a253..d6b3399 100644 --- a/lib/temple.ex +++ b/lib/temple.ex @@ -29,7 +29,7 @@ defmodule Temple do # The class attribute also can take a keyword list of classes to conditionally render, based on the boolean result of the value. - div class: ["text-red-500": false, "text-green-500": true ] do + div class: ["text-red-500": false, "text-green-500": true] do "Alert!" end @@ -71,6 +71,34 @@ defmodule Temple do end ``` + ## Whitespace Control + + By default, Temple will emit internal whitespace into tags, something like this. + + ```elixir + span do + "Hello, world!" + end + ``` + + ```html + + Hello, world! + + ``` + + If you need to create a "tight" tag, you can call the "bang" version of the desired tag. + + ```elixir + span! do + "Hello, world!" + end + ``` + + ```html + Hello, world! + ``` + ## Configuration ### Mode @@ -151,7 +179,8 @@ defmodule Temple do markup = block |> Parser.parse() - |> Enum.map(&Temple.Generator.to_eex/1) + |> Enum.map(fn parsed -> Temple.Generator.to_eex(parsed, 0) end) + |> Enum.intersperse("\n") |> :erlang.iolist_to_binary() quote location: :keep do @@ -163,7 +192,8 @@ defmodule Temple do quote location: :keep do unquote(block) |> Parser.parse() - |> Enum.map(&Temple.Generator.to_eex/1) + |> Enum.map(fn parsed -> Temple.Generator.to_eex(parsed, 0) end) + |> Enum.intersperse("\n") |> :erlang.iolist_to_binary() end end @@ -190,7 +220,8 @@ defmodule Temple do markup = block |> Parser.parse() - |> Enum.map(&Temple.Generator.to_eex/1) + |> Enum.map(fn parsed -> Temple.Generator.to_eex(parsed, 0) end) + |> Enum.intersperse("\n") |> :erlang.iolist_to_binary() EEx.compile_string(markup, engine: engine, line: __CALLER__.line, file: __CALLER__.file) diff --git a/lib/temple/generator.ex b/lib/temple/generator.ex index c346035..c0f7d46 100644 --- a/lib/temple/generator.ex +++ b/lib/temple/generator.ex @@ -1,5 +1,5 @@ defprotocol Temple.Generator do @moduledoc false - def to_eex(ast) + def to_eex(ast, indent \\ 0) end diff --git a/lib/temple/parser.ex b/lib/temple/parser.ex index eb82503..1ddbf05 100644 --- a/lib/temple/parser.ex +++ b/lib/temple/parser.ex @@ -58,7 +58,7 @@ defmodule Temple.Parser do option textarea output progress meter details summary menuitem menu html - ]a + ]a |> Enum.flat_map(fn el -> [el, :"#{el}!"] end) @nonvoid_elements_aliases Enum.map(@nonvoid_elements, fn el -> Keyword.get(@aliases, el, el) diff --git a/lib/temple/parser/anonymous_functions.ex b/lib/temple/parser/anonymous_functions.ex index 8b2065b..dd11ff7 100644 --- a/lib/temple/parser/anonymous_functions.ex +++ b/lib/temple/parser/anonymous_functions.ex @@ -32,14 +32,14 @@ defmodule Temple.Parser.AnonymousFunctions do end defimpl Temple.Generator do - def to_eex(%{elixir_ast: {name, _, args}, children: children}) do + def to_eex(%{elixir_ast: {name, _, args}, children: children}, indent \\ 0) do {_do_and_else, args} = Temple.Parser.Utils.split_args(args) {args, {func, _, [{arrow, _, [[{arg, _, _}], _block]}]}, args2} = Temple.Parser.Utils.split_on_fn(args, {[], nil, []}) [ - "<%= ", + "#{Parser.Utils.indent(indent)}<%= ", to_string(name), " ", Enum.map(args, &Macro.to_string(&1)) |> Enum.join(", "), @@ -51,16 +51,16 @@ defmodule Temple.Parser.AnonymousFunctions do to_string(arrow), " %>", "\n", - for(child <- children, do: Temple.Generator.to_eex(child)), + for(child <- children, do: Temple.Generator.to_eex(child, indent + 1)), if Enum.any?(args2) do [ - "<% end, ", + "#{Parser.Utils.indent(indent)}<% end, ", Enum.map(args2, fn arg -> Macro.to_string(arg) end) |> Enum.join(", "), " %>" ] else - ["<% end %>", "\n"] + ["#{Parser.Utils.indent(indent)}<% end %>", "\n"] end ] end diff --git a/lib/temple/parser/components.ex b/lib/temple/parser/components.ex index d5f1906..b9913bb 100644 --- a/lib/temple/parser/components.ex +++ b/lib/temple/parser/components.ex @@ -2,6 +2,8 @@ defmodule Temple.Parser.Components do @moduledoc false @behaviour Temple.Parser + alias Temple.Parser + defstruct module: nil, assigns: [], children: [], slots: [] @impl Temple.Parser @@ -88,12 +90,12 @@ defmodule Temple.Parser.Components do end defimpl Temple.Generator do - def to_eex(%{module: module, assigns: assigns, children: children, slots: slots}) do + def to_eex(%{module: module, assigns: assigns, children: children, slots: slots}, indent \\ 0) do component_function = Temple.Config.mode().component_function renderer = Temple.Config.mode().renderer.(module) [ - "<%= #{component_function} ", + "#{Parser.Utils.indent(indent)}<%= #{component_function} ", renderer, ", ", Macro.to_string(assigns), @@ -102,23 +104,22 @@ defmodule Temple.Parser.Components do " do %>\n", if not Enum.empty?(children) do [ - "<% {:default, _} -> %>\n", - for(child <- children, do: Temple.Generator.to_eex(child)), - "\n" + "#{Parser.Utils.indent(indent + 1)}<% {:default, _} -> %>\n", + for(child <- children, do: Temple.Generator.to_eex(child, indent + 2)) ] else "" end, for slot <- slots do [ - "<% {:", + "#{Parser.Utils.indent(indent + 1)}<% {:", to_string(slot.name), ", ", "#{Macro.to_string(slot.assigns)}} -> %>\n", - for(child <- slot.content, do: Temple.Generator.to_eex(child)) + for(child <- slot.content, do: Temple.Generator.to_eex(child, indent + 2)) ] end, - "<% end %>" + "\n#{Parser.Utils.indent(indent)}<% end %>" ] else " %>" diff --git a/lib/temple/parser/default.ex b/lib/temple/parser/default.ex index b9b4a2d..dc6e30a 100644 --- a/lib/temple/parser/default.ex +++ b/lib/temple/parser/default.ex @@ -15,8 +15,8 @@ defmodule Temple.Parser.Default do end defimpl Temple.Generator do - def to_eex(%{elixir_ast: expression}) do - ["<%= ", Macro.to_string(expression), " %>\n"] + def to_eex(%{elixir_ast: expression}, indent \\ 0) do + ["#{Parser.Utils.indent(indent)}<%= ", Macro.to_string(expression), " %>"] end end end diff --git a/lib/temple/parser/do_expressions.ex b/lib/temple/parser/do_expressions.ex index 1c31e0c..915f79d 100644 --- a/lib/temple/parser/do_expressions.ex +++ b/lib/temple/parser/do_expressions.ex @@ -30,18 +30,23 @@ defmodule Temple.Parser.DoExpressions do end defimpl Temple.Generator do - def to_eex(%{elixir_ast: expression, children: [do_body, else_body]}) do + def to_eex(%{elixir_ast: expression, children: [do_body, else_body]}, indent \\ 0) do [ - "<%= ", + "#{Parser.Utils.indent(indent)}<%= ", Macro.to_string(expression), " do %>", "\n", - for(child <- do_body, do: Temple.Generator.to_eex(child)), + for(child <- do_body, do: Temple.Generator.to_eex(child, indent + 1)) + |> Enum.intersperse("\n"), if(else_body != nil, - do: ["<% else %>\n", for(child <- else_body, do: Temple.Generator.to_eex(child))], + do: [ + "#{Parser.Utils.indent(indent)}\n<% else %>\n", + for(child <- else_body, do: Temple.Generator.to_eex(child, indent + 1)) + |> Enum.intersperse("\n") + ], else: "" ), - "<% end %>" + "\n#{Parser.Utils.indent(indent)}<% end %>" ] end end diff --git a/lib/temple/parser/element_list.ex b/lib/temple/parser/element_list.ex new file mode 100644 index 0000000..1b1260b --- /dev/null +++ b/lib/temple/parser/element_list.ex @@ -0,0 +1,33 @@ +defmodule Temple.Parser.ElementList do + @moduledoc false + + @behaviour Temple.Parser + + defstruct children: [], whitespace: :loose + + @impl Temple.Parser + def applicable?(asts), do: is_list(asts) + + @impl Temple.Parser + def run(asts) do + children = Enum.flat_map(asts, &Temple.Parser.parse/1) + + Temple.Ast.new(__MODULE__, children: children) + end + + defimpl Temple.Generator do + def to_eex(%{children: children, whitespace: whitespace}, indent \\ 0) do + child_indent = if whitespace == :loose, do: indent + 1, else: 0 + self_indent = if whitespace == :loose, do: indent, else: 0 + whitespace = if whitespace == :tight, do: [], else: ["\n"] + + [ + whitespace, + for(child <- children, do: Temple.Generator.to_eex(child, child_indent)) + |> Enum.intersperse("\n"), + whitespace, + Temple.Parser.Utils.indent(self_indent) + ] + end + end +end diff --git a/lib/temple/parser/empty.ex b/lib/temple/parser/empty.ex index 7872fdb..9a76156 100644 --- a/lib/temple/parser/empty.ex +++ b/lib/temple/parser/empty.ex @@ -16,7 +16,7 @@ defmodule Temple.Parser.Empty do end defimpl Temple.Generator do - def to_eex(_) do + def to_eex(_, _ \\ 0) do [] end end diff --git a/lib/temple/parser/match.ex b/lib/temple/parser/match.ex index 53b5973..15a65f4 100644 --- a/lib/temple/parser/match.ex +++ b/lib/temple/parser/match.ex @@ -19,8 +19,8 @@ defmodule Temple.Parser.Match do end defimpl Temple.Generator do - def to_eex(%{elixir_ast: elixir_ast}) do - ["<% ", Macro.to_string(elixir_ast), " %>"] + def to_eex(%{elixir_ast: elixir_ast}, indent \\ 0) do + ["#{Parser.Utils.indent(indent)}<% ", Macro.to_string(elixir_ast), " %>"] end end end diff --git a/lib/temple/parser/nonvoid_elements_aliases.ex b/lib/temple/parser/nonvoid_elements_aliases.ex index 89cb09d..28bca16 100644 --- a/lib/temple/parser/nonvoid_elements_aliases.ex +++ b/lib/temple/parser/nonvoid_elements_aliases.ex @@ -2,7 +2,7 @@ defmodule Temple.Parser.NonvoidElementsAliases do @moduledoc false @behaviour Temple.Parser - defstruct name: nil, attrs: [], children: [] + defstruct name: nil, attrs: [], children: [], meta: %{} alias Temple.Parser @@ -23,18 +23,34 @@ defmodule Temple.Parser.NonvoidElementsAliases do children = Temple.Parser.parse(do_and_else[:do]) - Temple.Ast.new(__MODULE__, name: to_string(name), attrs: args, children: children) + Temple.Ast.new(__MODULE__, + name: to_string(name) |> String.replace_suffix("!", ""), + attrs: args, + children: + Temple.Ast.new(Temple.Parser.ElementList, + children: children, + whitespace: whitespace(to_string(name)) + ) + ) + end + + defp whitespace(name) do + if String.ends_with?(name, "!") do + :tight + else + :loose + end end defimpl Temple.Generator do - def to_eex(%{name: name, attrs: attrs, children: children}) do + def to_eex(%{name: name, attrs: attrs, children: children}, indent \\ 0) do [ - "<", + "#{Parser.Utils.indent(indent)}<", name, Temple.Parser.Utils.compile_attrs(attrs), - ">\n", - for(child <- children, do: Temple.Generator.to_eex(child)), - "\n", + Temple.Generator.to_eex(children, indent), + "" ] diff --git a/lib/temple/parser/right_arrow.ex b/lib/temple/parser/right_arrow.ex index ef01f72..3be2015 100644 --- a/lib/temple/parser/right_arrow.ex +++ b/lib/temple/parser/right_arrow.ex @@ -18,12 +18,12 @@ defmodule Temple.Parser.RightArrow do end defimpl Temple.Generator do - def to_eex(%{elixir_ast: elixir_ast, children: children}) do + def to_eex(%{elixir_ast: elixir_ast, children: children}, indent \\ 0) do [ - "<% ", + "#{Parser.Utils.indent(indent)}<% ", Macro.to_string(elixir_ast), " -> %>\n", - for(child <- children, do: Temple.Generator.to_eex(child)) + for(child <- children, do: Temple.Generator.to_eex(child, indent + 1)) ] end end diff --git a/lib/temple/parser/slot.ex b/lib/temple/parser/slot.ex index 57484ad..287992d 100644 --- a/lib/temple/parser/slot.ex +++ b/lib/temple/parser/slot.ex @@ -1,6 +1,7 @@ defmodule Temple.Parser.Slot do @moduledoc false @behaviour Temple.Parser + alias Temple.Parser.Utils defstruct name: nil, args: [] @@ -26,15 +27,15 @@ defmodule Temple.Parser.Slot do end defimpl Temple.Generator do - def to_eex(%{name: name, args: args}) do + def to_eex(%{name: name, args: args}, indent \\ 0) do render_block_function = Temple.Config.mode().render_block_function [ - "<%= #{render_block_function}(@inner_block, {:", + "#{Utils.indent(indent)}<%= #{render_block_function}(@inner_block, {:", to_string(name), ", ", Macro.to_string(quote(do: Enum.into(unquote(args), %{}))), - "}) %>" + "}) %>\n" ] end end diff --git a/lib/temple/parser/text.ex b/lib/temple/parser/text.ex index 4bab474..3642d5c 100644 --- a/lib/temple/parser/text.ex +++ b/lib/temple/parser/text.ex @@ -16,8 +16,8 @@ defmodule Temple.Parser.Text do end defimpl Temple.Generator do - def to_eex(%{text: text}) do - [text, "\n"] + def to_eex(%{text: text}, indent \\ 0) do + [Parser.Utils.indent(indent), text] end end end diff --git a/lib/temple/parser/utils.ex b/lib/temple/parser/utils.ex index 9d8837e..bbe05e3 100644 --- a/lib/temple/parser/utils.ex +++ b/lib/temple/parser/utils.ex @@ -113,4 +113,8 @@ defmodule Temple.Parser.Utils do def pop_compact?(args) do Keyword.pop(args, :compact, false) end + + def indent(level) do + String.duplicate(" ", level * 2) + end end diff --git a/lib/temple/parser/void_elements_aliases.ex b/lib/temple/parser/void_elements_aliases.ex index 7abd6b9..71ae88d 100644 --- a/lib/temple/parser/void_elements_aliases.ex +++ b/lib/temple/parser/void_elements_aliases.ex @@ -2,6 +2,8 @@ defmodule Temple.Parser.VoidElementsAliases do @moduledoc false @behaviour Temple.Parser + alias Temple.Parser.Utils + defstruct name: nil, attrs: [] @impl Temple.Parser @@ -28,12 +30,12 @@ defmodule Temple.Parser.VoidElementsAliases do end defimpl Temple.Generator do - def to_eex(%{name: name, attrs: attrs}) do + def to_eex(%{name: name, attrs: attrs}, indent \\ 0) do [ - "<", + "#{Utils.indent(indent)}<", to_string(name), Temple.Parser.Utils.compile_attrs(attrs), - ">\n" + ">" ] end end diff --git a/test/component_test.exs b/test/component_test.exs index bf0926d..9750f31 100644 --- a/test/component_test.exs +++ b/test/component_test.exs @@ -18,7 +18,19 @@ defmodule Temple.ComponentTest do end assert evaluate_template(result) == - ~s{
Hello, world
} + ~s""" +
+ Hello, world +
+
+ + + +
+ + """ end test "function components can accept local assigns" do @@ -34,7 +46,16 @@ defmodule Temple.ComponentTest do end assert evaluate_template(result) == - ~s{
Hello, world
I'm a component!
} + ~s""" +
+ Hello, world +
+
+ + I'm a component! + +
+ """ end test "function components can use other components" do @@ -50,8 +71,21 @@ defmodule Temple.ComponentTest do end assert evaluate_template(result) == ~s""" -
outer!
-
inner!
+
+ + + outer! + + +
+ + +
+ + inner! + +
+ """ end @@ -63,7 +97,14 @@ defmodule Temple.ComponentTest do end end - assert evaluate_template(result) == ~s{
doo doo
} + assert evaluate_template(result) == ~s""" +
+ + doo doo + +
+ + """ end test "components can be void elements" do @@ -72,7 +113,11 @@ defmodule Temple.ComponentTest do c Temple.Components.VoidComponent, foo: :bar end - assert evaluate_template(result) == ~s{
bar
} + assert evaluate_template(result) == ~s""" +
+ bar +
+ """ end test "components can have named slots" do @@ -94,6 +139,22 @@ defmodule Temple.ComponentTest do end assert evaluate_template(result, assigns) == - ~s{
the value is Header
} + ~s""" +
+ +
+ the value is Header +
+ +
+ + + +
+
+ + """ end end diff --git a/test/parser/anonymous_functions_test.exs b/test/parser/anonymous_functions_test.exs index d5dfa06..6564304 100644 --- a/test/parser/anonymous_functions_test.exs +++ b/test/parser/anonymous_functions_test.exs @@ -7,9 +7,9 @@ defmodule Temple.Parser.AnonymousFunctionsTest do test "returns true when the node contains an anonymous function as an argument to a function" do raw_asts = [ quote do - form_for changeset, Routes.foo_path(conn, :create), fn form -> + form_for(changeset, Routes.foo_path(conn, :create), fn form -> Does.something!(form) - end + end) end ] @@ -47,9 +47,9 @@ defmodule Temple.Parser.AnonymousFunctionsTest do raw_ast = quote do - form_for changeset, Routes.foo_path(conn, :create), fn form -> + form_for(changeset, Routes.foo_path(conn, :create), fn form -> unquote(expected_child) - end + end) end ast = AnonymousFunctions.run(raw_ast) @@ -69,9 +69,9 @@ defmodule Temple.Parser.AnonymousFunctionsTest do test "emits eex" do raw_ast = quote do - form_for changeset, Routes.foo_path(conn, :create), fn form -> + form_for(changeset, Routes.foo_path(conn, :create), fn form -> Does.something!(form) - end + end) end result = diff --git a/test/parser/components_test.exs b/test/parser/components_test.exs index c7c66fc..224f87e 100644 --- a/test/parser/components_test.exs +++ b/test/parser/components_test.exs @@ -187,7 +187,12 @@ defmodule Temple.Parser.ComponentsTest do |> Temple.Generator.to_eex() assert result |> :erlang.iolist_to_binary() == - ~s|<%= Temple.Component.__component__ SomeModule, [foo: :bar] do %>\n<% {:default, _} -> %>\nI'm a component!\n<% end %>| + ~s""" + <%= Temple.Component.__component__ SomeModule, [foo: :bar] do %> + <% {:default, _} -> %> + I'm a component! + <% end %> + """ end test "emits eex for void component with slots" do @@ -208,7 +213,14 @@ defmodule Temple.Parser.ComponentsTest do |> Temple.Generator.to_eex() assert result |> :erlang.iolist_to_binary() == - ~s|<%= Temple.Component.__component__ SomeModule, [foo: :bar] do %>\n<% {:foo, %{form: form}} -> %>\n
\nin the slot\n\n
\n<% end %>| + ~s""" + <%= Temple.Component.__component__ SomeModule, [foo: :bar] do %> + <% {:foo, %{form: form}} -> %> +
+ in the slot +
+ <% end %> + """ end test "emits eex for nonvoid component with slots" do @@ -233,7 +245,18 @@ defmodule Temple.Parser.ComponentsTest do |> Temple.Generator.to_eex() assert result |> :erlang.iolist_to_binary() == - ~s|<%= Temple.Component.__component__ SomeModule, [foo: :bar] do %>\n<% {:default, _} -> %>\n
\ninner content\n\n
\n<% {:foo, %{form: form}} -> %>
\nin the slot\n\n
<% end %>| + ~s""" + <%= Temple.Component.__component__ SomeModule, [foo: :bar] do %> + <% {:default, _} -> %> +
+ inner content +
+ <% {:foo, %{form: form}} -> %> +
+ in the slot +
+ <% end %> + """ end test "emits eex for void component" do diff --git a/test/parser/default_test.exs b/test/parser/default_test.exs index 80359a5..7c2ffd8 100644 --- a/test/parser/default_test.exs +++ b/test/parser/default_test.exs @@ -2,6 +2,7 @@ defmodule Temple.Parser.DefaultTest do use ExUnit.Case, async: true alias Temple.Parser.Default + alias Temple.Support.Utils describe "applicable?/1" do test "returns true when the node is an elixir expression" do @@ -35,8 +36,11 @@ defmodule Temple.Parser.DefaultTest do end |> Default.run() |> Temple.Generator.to_eex() + |> Utils.iolist_to_binary() - assert result |> :erlang.iolist_to_binary() == ~s|<%= Foo.bar!(baz) %>\n| + assert result == ~s""" + <%= Foo.bar!(baz) %> + """ end end end diff --git a/test/parser/do_expressions_test.exs b/test/parser/do_expressions_test.exs index ae86538..6592fc5 100644 --- a/test/parser/do_expressions_test.exs +++ b/test/parser/do_expressions_test.exs @@ -2,6 +2,7 @@ defmodule Temple.Parser.DoExpressionsTest do use ExUnit.Case, async: true alias Temple.Parser.DoExpressions + alias Temple.Support.Utils describe "applicable?/1" do test "returns true when the node contains a do expression" do @@ -47,9 +48,14 @@ defmodule Temple.Parser.DoExpressionsTest do end |> DoExpressions.run() |> Temple.Generator.to_eex() + |> Utils.iolist_to_binary() - assert result |> :erlang.iolist_to_binary() == - ~s|<%= for(big <- boys) do %>\nbob\n<% end %>| + assert result == + ~s""" + <%= for(big <- boys) do %> + bob + <% end %> + """ end test "emits eex for that includes in else clause" do @@ -65,9 +71,17 @@ defmodule Temple.Parser.DoExpressionsTest do end |> DoExpressions.run() |> Temple.Generator.to_eex() + |> Utils.iolist_to_binary() - assert result |> :erlang.iolist_to_binary() == - ~s|<%= if(foo?) do %>\nbob\nbobby\n<% else %>\ncarol\n<% end %>| + assert result == + ~s""" + <%= if(foo?) do %> + bob + bobby + <% else %> + carol + <% end %> + """ end test "emits eex for a case expression" do @@ -80,9 +94,15 @@ defmodule Temple.Parser.DoExpressionsTest do end |> DoExpressions.run() |> Temple.Generator.to_eex() + |> Utils.iolist_to_binary() - assert result |> :erlang.iolist_to_binary() == - ~s|<%= case(foo?) do %>\n<% :bing -> %>\n<%= :bong %>\n<% end %>| + assert result == + ~s""" + <%= case(foo?) do %> + <% :bing -> %> + <%= :bong %> + <% end %> + """ end end end diff --git a/test/parser/nonvoid_elements_aliases_test.exs b/test/parser/nonvoid_elements_aliases_test.exs index da0f236..22411a2 100644 --- a/test/parser/nonvoid_elements_aliases_test.exs +++ b/test/parser/nonvoid_elements_aliases_test.exs @@ -2,6 +2,8 @@ defmodule Temple.Parser.NonvoidElementsAliasesTest do use ExUnit.Case, async: true alias Temple.Parser.NonvoidElementsAliases + alias Temple.Parser.ElementList + alias Temple.Support.Utils describe "applicable?/1" do test "returns true when the node is a nonvoid element or alias" do @@ -63,19 +65,25 @@ defmodule Temple.Parser.NonvoidElementsAliasesTest do assert %NonvoidElementsAliases{ name: "div", attrs: [class: "foo", id: {:var, [], _}], - children: [ - %NonvoidElementsAliases{ - name: "select", - children: [ - %NonvoidElementsAliases{ - name: "option", + children: %ElementList{ + children: [ + %NonvoidElementsAliases{ + name: "select", + children: %ElementList{ children: [ - %Temple.Parser.Text{text: "foo"} + %NonvoidElementsAliases{ + name: "option", + children: %ElementList{ + children: [ + %Temple.Parser.Text{text: "foo"} + ] + } + } ] } - ] - } - ] + } + ] + } } = ast end end @@ -94,9 +102,44 @@ defmodule Temple.Parser.NonvoidElementsAliasesTest do end |> NonvoidElementsAliases.run() |> Temple.Generator.to_eex() + |> Utils.iolist_to_binary() - assert result |> :erlang.iolist_to_binary() == - ~s|
>\n\n
| + assert result == + ~s""" +
> + +
+ """ + end + + test "produce 'tight' markup" do + result = + quote do + div class: "foo", id: var do + select__ do + option! do + "foo" + end + end + end + end + |> NonvoidElementsAliases.run() + |> Temple.Generator.to_eex() + |> :erlang.iolist_to_binary() + |> Kernel.<>("\n") + + assert result == + ~s""" +
> + +
+ """ end end end diff --git a/test/parser/right_arrow_test.exs b/test/parser/right_arrow_test.exs index 4031ecb..7ee66ef 100644 --- a/test/parser/right_arrow_test.exs +++ b/test/parser/right_arrow_test.exs @@ -2,6 +2,7 @@ defmodule Temple.Parser.RightArrowTest do use ExUnit.Case, async: true alias Temple.Parser.RightArrow + alias Temple.Support.Utils describe "applicable?/1" do test "returns true when the node contains a right arrow" do @@ -70,9 +71,13 @@ defmodule Temple.Parser.RightArrowTest do |> List.first() |> RightArrow.run() |> Temple.Generator.to_eex() + |> Utils.iolist_to_binary() - assert result |> :erlang.iolist_to_binary() == - ~s|<% :bing -> %>\n<%= :bong %>\n| + assert result == + ~s""" + <% :bing -> %> + <%= :bong %> + """ end end end diff --git a/test/parser/slot_test.exs b/test/parser/slot_test.exs index 9b01e1e..78b015c 100644 --- a/test/parser/slot_test.exs +++ b/test/parser/slot_test.exs @@ -42,7 +42,9 @@ defmodule Temple.Parser.SlotTest do |> Temple.Generator.to_eex() assert result |> :erlang.iolist_to_binary() == - ~s|<%= Temple.Component.__render_block__(@inner_block, {:header, Enum.into([value: Form.form_for(changeset, action)], %{})}) %>| + ~s""" + <%= Temple.Component.__render_block__(@inner_block, {:header, Enum.into([value: Form.form_for(changeset, action)], %{})}) %> + """ end end end diff --git a/test/parser/temple_namespace_nonvoid_test.exs b/test/parser/temple_namespace_nonvoid_test.exs index de508ae..81df4ab 100644 --- a/test/parser/temple_namespace_nonvoid_test.exs +++ b/test/parser/temple_namespace_nonvoid_test.exs @@ -3,6 +3,7 @@ defmodule Temple.Parser.TempleNamespaceNonvoidTest do alias Temple.Parser.NonvoidElementsAliases alias Temple.Parser.TempleNamespaceNonvoid + alias Temple.Support.Utils describe "applicable?/1" do test "returns true when the node is a Temple aliased nonvoid element" do @@ -50,7 +51,10 @@ defmodule Temple.Parser.TempleNamespaceNonvoidTest do assert %NonvoidElementsAliases{ name: "div", attrs: [class: "foo", id: {:var, [], _}], - children: [%Temple.Parser.Text{text: "foo"}] + children: %Temple.Parser.ElementList{ + children: [%Temple.Parser.Text{text: "foo"}], + whitespace: :loose + } } = ast end end @@ -65,9 +69,14 @@ defmodule Temple.Parser.TempleNamespaceNonvoidTest do end |> TempleNamespaceNonvoid.run() |> Temple.Generator.to_eex() + |> Utils.iolist_to_binary() - assert result |> :erlang.iolist_to_binary() == - ~s|
>\nfoo\n\n
| + assert result == + ~s""" +
> + foo +
+ """ end end end diff --git a/test/parser/temple_namespace_void_test.exs b/test/parser/temple_namespace_void_test.exs index 1ca3c2d..19e8d2d 100644 --- a/test/parser/temple_namespace_void_test.exs +++ b/test/parser/temple_namespace_void_test.exs @@ -3,6 +3,7 @@ defmodule Temple.Parser.TempleNamespaceVoidTest do alias Temple.Parser.TempleNamespaceVoid alias Temple.Parser.VoidElementsAliases + alias Temple.Support.Utils describe "applicable?/1" do test "returns true when the node is a Temple aliased nonvoid element" do @@ -58,8 +59,9 @@ defmodule Temple.Parser.TempleNamespaceVoidTest do end |> TempleNamespaceVoid.run() |> Temple.Generator.to_eex() + |> Utils.iolist_to_binary() - assert result |> :erlang.iolist_to_binary() == ~s|\n| + assert result == ~s|\n| end end end diff --git a/test/parser/text_test.exs b/test/parser/text_test.exs index 16be3b3..432df1c 100644 --- a/test/parser/text_test.exs +++ b/test/parser/text_test.exs @@ -2,6 +2,7 @@ defmodule Temple.Parser.TextTest do use ExUnit.Case, async: true alias Temple.Parser.Text + alias Temple.Support.Utils describe "applicable?/1" do test "returns true when the node is a string literal" do @@ -35,8 +36,9 @@ defmodule Temple.Parser.TextTest do "string literal" |> Text.run() |> Temple.Generator.to_eex() + |> Utils.iolist_to_binary() - assert result |> :erlang.iolist_to_binary() == ~s|string literal\n| + assert result == ~s|string literal\n| end end end diff --git a/test/parser/void_elements_aliases_test.exs b/test/parser/void_elements_aliases_test.exs index a71d1e8..a2a898a 100644 --- a/test/parser/void_elements_aliases_test.exs +++ b/test/parser/void_elements_aliases_test.exs @@ -2,6 +2,7 @@ defmodule Temple.Parser.VoidElementsAliasesTest do use ExUnit.Case, async: true alias Temple.Parser.VoidElementsAliases + alias Temple.Support.Utils describe "applicable?/1" do test "returns true when the node is a nonvoid element or alias" do @@ -63,8 +64,9 @@ defmodule Temple.Parser.VoidElementsAliasesTest do end |> VoidElementsAliases.run() |> Temple.Generator.to_eex() + |> Utils.iolist_to_binary() - assert result |> :erlang.iolist_to_binary() == ~s|\n| + assert result == ~s|\n| end end end diff --git a/test/support/utils.ex b/test/support/utils.ex index 3a8c028..e47adf0 100644 --- a/test/support/utils.ex +++ b/test/support/utils.ex @@ -33,4 +33,20 @@ defmodule Temple.Support.Utils do |> elem(0) |> Phoenix.HTML.safe_to_string() end + + @doc """ + Converts an iolist to a binary and appends a new line. + """ + def iolist_to_binary(iolist) do + iolist + |> :erlang.iolist_to_binary() + |> append_new_line() + end + + @doc """ + Appends a new line to a string. + """ + def append_new_line(string) do + string <> "\n" + end end diff --git a/test/temple_test.exs b/test/temple_test.exs index 4ca8cc9..fecbd14 100644 --- a/test/temple_test.exs +++ b/test/temple_test.exs @@ -11,7 +11,12 @@ defmodule TempleTest do end end - assert result == ~s{
} + assert result == ~s""" +
+
+
+
+ """ end test "renders void element" do @@ -32,7 +37,12 @@ defmodule TempleTest do end end - assert result == ~s{
hifoo
} + assert result == ~s""" +
+ hi + foo +
+ """ end test "renders a variable text node as eex" do @@ -43,7 +53,11 @@ defmodule TempleTest do end end - assert result == ~s{
<%= foo %>
} + assert result == ~s""" +
+ <%= foo %> +
+ """ end test "renders an assign text node as eex" do @@ -54,7 +68,11 @@ defmodule TempleTest do end end - assert result == ~s{
<%= @foo %>
} + assert result == ~s""" +
+ <%= @foo %> +
+ """ end test "renders a match expression" do @@ -67,7 +85,12 @@ defmodule TempleTest do end end - assert result == ~s{<% x = 420 %>
blaze it
} + assert result == ~s""" + <% x = 420 %> +
+ blaze it +
+ """ end test "renders a non-match expression" do @@ -80,7 +103,12 @@ defmodule TempleTest do end end - assert result == ~s{<%= IO.inspect(:foo) %>
bar
} + assert result == ~s""" + <%= IO.inspect(:foo) %> +
+ bar +
+ """ end test "renders an expression in attr as eex" do @@ -102,7 +130,12 @@ defmodule TempleTest do end assert result == - ~s| x end))} %>>
| + ~s""" + x end))} %>> +
+
+ + """ end test "renders a for comprehension as eex" do @@ -113,7 +146,12 @@ defmodule TempleTest do end end - assert result == ~s{<%= for(x <- 1..5) do %>
<% end %>} + assert result == ~s""" + <%= for(x <- 1..5) do %> +
+
+ <% end %> + """ end test "renders an if expression as eex" do @@ -124,7 +162,12 @@ defmodule TempleTest do end end - assert result == ~s{<%= if(true == false) do %>
<% end %>} + assert result == ~s""" + <%= if(true == false) do %> +
+
+ <% end %> + """ end test "renders an if/else expression as eex" do @@ -138,7 +181,15 @@ defmodule TempleTest do end assert result == - ~s{<%= if(true == false) do %>
<% else %>
<% end %>} + ~s""" + <%= if(true == false) do %> +
+
+ <% else %> +
+
+ <% end %> + """ end test "renders an unless expression as eex" do @@ -149,7 +200,12 @@ defmodule TempleTest do end end - assert result == ~s{<%= unless(true == false) do %>
<% end %>} + assert result == ~s""" + <%= unless(true == false) do %> +
+
+ <% end %> + """ end test "renders a case expression as eex" do @@ -161,14 +217,12 @@ defmodule TempleTest do end end - expected = - ~S""" - <%= case(@foo) do %> + expected = ~S""" + <%= case(@foo) do %> <% :baz -> %> - <%= some_component(form: @form) %> - <% end %> - """ - |> String.trim() + <%= some_component(form: @form) %> + <% end %> + """ assert result == expected end @@ -176,70 +230,99 @@ defmodule TempleTest do test "renders multiline anonymous function with 1 arg before the function" do result = temple do - form_for Routes.user_path(@conn, :create), fn f -> + form_for(Routes.user_path(@conn, :create), fn f -> "Name: " - text_input f, :name - end + text_input(f, :name) + end) end assert result == - ~s{<%= form_for Routes.user_path(@conn, :create), fn f -> %>Name: <%= text_input(f, :name) %><% end %>} + ~s""" + <%= form_for Routes.user_path(@conn, :create), fn f -> %> + Name: + <%= text_input(f, :name) %> + <% end %> + """ end test "renders multiline anonymous functions with 2 args before the function" do result = temple do - form_for @changeset, Routes.user_path(@conn, :create), fn f -> + form_for(@changeset, Routes.user_path(@conn, :create), fn f -> "Name: " - text_input f, :name - end + text_input(f, :name) + end) end assert result == - ~s{<%= form_for @changeset, Routes.user_path(@conn, :create), fn f -> %>Name: <%= text_input(f, :name) %><% end %>} + ~s""" + <%= form_for @changeset, Routes.user_path(@conn, :create), fn f -> %> + Name: + <%= text_input(f, :name) %> + <% end %> + """ end test "renders multiline anonymous functions with complex nested children" do result = temple do - form_for @changeset, Routes.user_path(@conn, :create), fn f -> + form_for(@changeset, Routes.user_path(@conn, :create), fn f -> div do "Name: " - text_input f, :name + text_input(f, :name) end - end + end) end assert result == - ~s{<%= form_for @changeset, Routes.user_path(@conn, :create), fn f -> %>
Name: <%= text_input(f, :name) %>
<% end %>} + ~s""" + <%= form_for @changeset, Routes.user_path(@conn, :create), fn f -> %> +
+ Name: + <%= text_input(f, :name) %> +
+ <% end %> + """ end test "renders multiline anonymous function with 3 arg before the function" do result = temple do - form_for @changeset, Routes.user_path(@conn, :create), [foo: :bar], fn f -> + form_for(@changeset, Routes.user_path(@conn, :create), [foo: :bar], fn f -> "Name: " - text_input f, :name - end + text_input(f, :name) + end) end assert result == - ~s{<%= form_for @changeset, Routes.user_path(@conn, :create), [foo: :bar], fn f -> %>Name: <%= text_input(f, :name) %><% end %>} + ~s""" + <%= form_for @changeset, Routes.user_path(@conn, :create), [foo: :bar], fn f -> %> + Name: + <%= text_input(f, :name) %> + <% end %> + """ end test "renders multiline anonymous function with 1 arg before the function and 1 arg after" do result = temple do - form_for @changeset, - fn f -> - "Name: " - text_input f, :name - end, - foo: :bar + form_for( + @changeset, + fn f -> + "Name: " + text_input(f, :name) + end, + foo: :bar + ) end assert result == - ~s{<%= form_for @changeset, fn f -> %>Name: <%= text_input(f, :name) %><% end, [foo: :bar] %>} + ~s""" + <%= form_for @changeset, fn f -> %> + Name: + <%= text_input(f, :name) %> + <% end, [foo: :bar] %> + """ end test "tags prefixed with Temple. should be interpreted as temple tags" do @@ -252,7 +335,13 @@ defmodule TempleTest do end end - assert result == ~s{
bob
} + assert result == ~s""" +
+ + bob + +
+ """ end test "can pass do as an arg instead of a block" do @@ -267,7 +356,17 @@ defmodule TempleTest do end assert result == - ~s{
Hello, world
Hello, world
Hello, world
} + ~s""" +
+ Hello, world +
+
+ Hello, world +
+
+ Hello, world +
+ """ end test "for with 2 generators" do @@ -280,7 +379,16 @@ defmodule TempleTest do end assert result == - ~s{<%= for(x <- 1..5, y <- 6..10) do %>
<%= x %>
<%= y %>
<% end %>} + ~s""" + <%= for(x <- 1..5, y <- 6..10) do %> +
+ <%= x %> +
+
+ <%= y %> +
+ <% end %> + """ end test "can pass an expression as assigns" do @@ -292,7 +400,15 @@ defmodule TempleTest do end assert result == - ~s{>} + ~s""" + > + + + """ end test "can pass a variable as assigns" do @@ -304,7 +420,11 @@ defmodule TempleTest do end assert result == - ~s{>} + ~s""" + > + + + """ end test "can pass a function as assigns" do @@ -316,7 +436,11 @@ defmodule TempleTest do end assert result == - ~s{>} + ~s""" + > + + + """ end test "hr tag works" do @@ -334,7 +458,23 @@ defmodule TempleTest do end assert evaluate_template(result, assigns) == - ~s{
foo

foo

bar

bar
} + ~s""" +
+ foo +
+
+
+ foo +
+
+
+ bar +
+
+
+ bar +
+ """ end test "boolean attributes" do @@ -363,6 +503,10 @@ defmodule TempleTest do end end - assert evaluate_template(result, assigns) == ~s{
\nfoo\n\n
} + assert evaluate_template(result, assigns) == ~s""" +
+ foo +
+ """ end end diff --git a/test/whitespace_test.exs b/test/whitespace_test.exs new file mode 100644 index 0000000..c663026 --- /dev/null +++ b/test/whitespace_test.exs @@ -0,0 +1,46 @@ +defmodule Temple.WhitespaceTest do + use ExUnit.Case, async: true + + import Temple + + alias Temple.Support.Utils + + test "only emits a single new line" do + result = + temple do + div class: "hello" do + span id: "foo" do + "Howdy, " + end + + div class: "hi" do + "Jim Bob" + end + + c WhoaNelly, foo: "bar" do + slot :silver do + "esketit" + end + end + end + end + |> Utils.append_new_line() + + expected = ~s""" +
+ + Howdy, + +
+ Jim Bob +
+ <%= Temple.Component.__component__ WhoaNelly, [foo: "bar"] do %> + <% {:silver, %{}} -> %> + esketit + <% end %> +
+ """ + + assert result == expected + end +end