diff --git a/.formatter.exs b/.formatter.exs index eb039f1..6d31ace 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -121,6 +121,6 @@ locals_without_parens = Enum.map(temple ++ html ++ svg, &{&1, :*}) [ import_deps: [:typed_struct], inputs: ["*.{ex,exs}", "{config,lib,test}/**/*.{ex,exs}"], - locals_without_parens: locals_without_parens, + locals_without_parens: locals_without_parens ++ [assert_html: 2], export: [locals_without_parens: locals_without_parens] ] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 126b9e0..660a35b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,7 @@ on: jobs: tests: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 name: Test (${{matrix.elixir}}/${{matrix.otp}}) strategy: diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f579fe..32583f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,7 @@ - Rendering slots is now done by passing the assign with the slot name to the `slot` keyword instead of name as an atom. If this slot has multiple definitions, you can loop through them and render each one individually, or render them all at once. Please see the migration guide for more information. - The `:default` slot has been renamed to `:inner_block`. This is to be easily compatible with HEEx/Surface. Please see the migration guide for more information. -- Capturing the data being passed into a slot is now defined using the `:let` attribute. Please see the migration guide for more information. +- Capturing the data being passed into a slot is now defined using the `:let!` attribute. Please see the migration guide for more information. ### Enhancements diff --git a/guides/components.md b/guides/components.md index 9e90b0c..17f49df 100644 --- a/guides/components.md +++ b/guides/components.md @@ -26,7 +26,7 @@ end To use a component, you will use the special `c` keyword. This is called a "keyword" because it is not a function or macro, but only exists inside of the `Temple.temple/1` block. -The first argument will be the function reference to your component function, followed by any assigns. +The first argument will be the function reference to your component function, followed by any assigns. You can pass dynamic assigns using the `:rest!` keyword the same way you would with a normal tag. ```elixir defmodule MyApp.ConfirmDialog do @@ -156,7 +156,7 @@ end ## Passing data to and through Slots -Sometimes it is necessary to pass data _into_ a slot (hereby known as *slot attributes*) from the call site and _from_ a component definition (hereby known as *slot arguments*) back to the call site. +Sometimes it is necessary to pass data _into_ a slot (hereby known as *slot attributes*) from the call site and _from_ a component definition (hereby known as *slot arguments*) back to the call site. Dynamic slot attributes can be passed using the `:rest!` attribute in the same way you can with tag attributes. Let's look at what a `table` component could look like. Here we observe we access an attribute in the slot in the header with `col.label`. @@ -214,11 +214,11 @@ def MyApp.TableExample do c &table/1, rows: @users do # 👇 defining the parameter for the slot argument - slot :col, let: user, label: "Name" do # 👈 passing a slot attribute + slot :col, let!: user, label: "Name" do # 👈 passing a slot attribute user.name end - slot :col, let: user, label: "Address" do + slot :col, let!: user, label: "Address" do user.address end end diff --git a/guides/migrating/0.10-to-0.11.md b/guides/migrating/0.10-to-0.11.md index 9f84ceb..62b272a 100644 --- a/guides/migrating/0.10-to-0.11.md +++ b/guides/migrating/0.10-to-0.11.md @@ -64,7 +64,7 @@ end The syntax for capturing data being passed from the call site of a slot to the definition of a slot (or put another way, from the definition of a component to the call site of the component) has changed. -You now capture it as the value of the `:let` attribute on the slot definition. +You now capture it as the value of the `:let!` attribute on the slot definition. ### Before @@ -86,7 +86,7 @@ end def my_component(assign) do temple do c &my_component/1 do - slot :a_slot, let: %{some: value} do + slot :a_slot, let!: %{some: value} do "I'm using some #{value}" end end diff --git a/guides/your-first-template.md b/guides/your-first-template.md index 035d035..4ce6b85 100644 --- a/guides/your-first-template.md +++ b/guides/your-first-template.md @@ -121,54 +121,34 @@ end ## Attributes -Attributes are declared as a keyword list. +Temple leverages `Phoenix.HTML.attributes_escape/1` internally, so you can refer to it's documentation for all of the details. -- Keys with underscores are converted to the kebab syntax. -- Values can be Elixir expressions. -- Values that evaluate to `true` will be emitted as a boolean attribute. `disabled` and `checked` are examples of boolean attributes. -- Values that evaluate `false` will not be emitted into the document at all. -- The class attribute has a special "object syntax" that allows you to specify classes as a keyword list, only emitting classes that evaluate to true into the final class. +### Dynamic Attributes -Let's look at an example. +To render dynamic attributes into a tag, you can pass them with the reserved attribute `:rest!`. ```elixir -assigns = %{highlight?: false, user_name: "Mitch"} +assigns = % + data: [data_foo: "hi"] +} temple do - div id: "hero" do - h2 class: "font-bold", do: "Profile" - - section data_controller: "hero" do - p class: ["border": @highlight?] do - "Name: #{@user_name}" - end - - video autoplay: true, src: "https://example.com/rick-rolled.mp4" - end + div id: "foo", rest!: @data do + "Hello, world!" end end ``` -...will emit markup that looks like... +will render to ```html -
-

Profile

- -
-

- Name: Mitch -

-
- - +
+ Hello, world!
``` ## Elixir Expressions -### They Just Work - Any Elixir expression can be used anywhere inside of a Temple template. Here are a few examples. ```elixir diff --git a/lib/temple.ex b/lib/temple.ex index cae4248..d7a2b40 100644 --- a/lib/temple.ex +++ b/lib/temple.ex @@ -1,6 +1,4 @@ defmodule Temple do - @engine Application.compile_env(:temple, :engine, EEx.SmartEngine) - @moduledoc """ Temple syntax is available inside the `temple`, and is compiled into efficient Elixir code at compile time using the configured `EEx.Engine`. @@ -93,82 +91,21 @@ defmodule Temple do ``` """ - @doc false - def engine(), do: @engine - defmacro temple(block) do - opts = [engine: engine()] - quote do require Temple.Renderer - Temple.Renderer.compile(unquote(opts), unquote(block)) + + Temple.Renderer.compile(unquote(block)) + |> then(fn + {:safe, template} -> + template + + template -> + template + end) end end @doc false - def component(func, assigns, _) do - apply(func, [assigns]) - end - - defmacro inner_block(_name, do: do_block) do - __inner_block__(do_block) - end - - @doc false - def __inner_block__([{:->, meta, _} | _] = do_block) do - inner_fun = {:fn, meta, do_block} - - quote do - fn arg -> - _ = var!(assigns) - unquote(inner_fun).(arg) - end - end - end - - def __inner_block__(do_block) do - quote do - fn arg -> - _ = var!(assigns) - unquote(do_block) - end - end - end - - defmacro render_slot(slot, arg) do - quote do - unquote(__MODULE__).__render_slot__(unquote(slot), unquote(arg)) - end - end - - @doc false - def __render_slot__([], _), do: nil - - def __render_slot__([entry], argument) do - call_inner_block!(entry, argument) - end - - def __render_slot__(entries, argument) when is_list(entries) do - assigns = %{} - _ = assigns - - temple do - for entry <- entries do - call_inner_block!(entry, argument) - end - end - end - - def __render_slot__(entry, argument) when is_map(entry) do - entry.inner_block.(argument) - end - - defp call_inner_block!(entry, argument) do - if !entry.inner_block do - message = "attempted to render slot #{entry.__slot__} but the slot has no inner content" - raise RuntimeError, message - end - - entry.inner_block.(argument) - end + defdelegate engine, to: Temple.Renderer end diff --git a/lib/temple/ast/components.ex b/lib/temple/ast/components.ex index 8ed4341..5e5b682 100644 --- a/lib/temple/ast/components.ex +++ b/lib/temple/ast/components.ex @@ -40,7 +40,7 @@ defmodule Temple.Ast.Components do if is_nil(slot) do {node, {component_function, named_slots}} else - {parameter, attributes} = Keyword.pop(arguments || [], :let) + {parameter, attributes} = Keyword.pop(arguments || [], :let!) new_slot = {name, %{parameter: parameter, slot: slot, attributes: attributes}} {nil, {component_function, named_slots ++ [new_slot]}} end diff --git a/lib/temple/ast/utils.ex b/lib/temple/ast/utils.ex index fad2f14..f9163e5 100644 --- a/lib/temple/ast/utils.ex +++ b/lib/temple/ast/utils.ex @@ -34,7 +34,7 @@ defmodule Temple.Ast.Utils do [ {:expr, quote do - unquote(List.first(attrs)) + Phoenix.HTML.attributes_escape(unquote(List.first(attrs))) end} ] end @@ -48,14 +48,25 @@ defmodule Temple.Ast.Utils do [] end + def build_attr("rest!", values) when is_list(values) do + Enum.flat_map(values, fn {name, value} -> + build_attr(snake_to_kebab(name), value) + end) + end + + def build_attr("rest!", {_, _, _} = value) do + expr = + quote do + Phoenix.HTML.attributes_escape(unquote(value)) + end + + [{:expr, expr}] + end + def build_attr(name, {_, _, _} = value) do expr = quote do - case unquote(value) do - true -> " " <> unquote(name) - false -> "" - _ -> ~s' #{unquote(name)}="#{unquote(value)}"' - end + Phoenix.HTML.attributes_escape([{unquote(name), unquote(value)}]) end [{:expr, expr}] diff --git a/lib/temple/component.ex b/lib/temple/component.ex new file mode 100644 index 0000000..5b1c358 --- /dev/null +++ b/lib/temple/component.ex @@ -0,0 +1,124 @@ +defmodule Temple.Component do + @moduledoc """ + Use this module to create your own component implementation. + + This is only required if you are not using a component implementation from another framework, + like Phoenix LiveView. + + At it's core, a component implmentation includes the following functions + + - `component/2` + - `inner_block/2` + - `render_slot/2` + + These functions are used by the template compiler, so you won't be calling them directly. + + ## Usage + + Invoke the `__using__/1` macro to create your own module, and then import that module where you + need to define define or use components (usually everywhere). + + We'll use an example that is similar to what Temple uses in its own test suite.. + + ```elixir + defmodule MyAppWeb.Component do + use Temple.Component + + defmacro __using__(_) do + quote do + import Temple + import unquote(__MODULE__) + end + end + end + ``` + + Then you can `use` your module when you want to define or use a component. + + ```elixir + defmodule MyAppWeb.Components do + use MyAppWeb.Component + + def basic_component(_assigns) do + temple do + div do + "I am a basic component" + end + end + end + end + ``` + + """ + defmacro __using__(_) do + quote do + import Temple + @doc false + def component(func, assigns, _) do + {:safe, apply(func, [assigns])} + end + + defmacro inner_block(_name, do: do_block) do + __inner_block__(do_block) + end + + @doc false + def __inner_block__([{:->, meta, _} | _] = do_block) do + inner_fun = {:fn, meta, do_block} + + quote do + fn arg -> + _ = var!(assigns) + unquote(inner_fun).(arg) + end + end + end + + def __inner_block__(do_block) do + quote do + fn arg -> + _ = var!(assigns) + unquote(do_block) + end + end + end + + defmacro render_slot(slot, arg) do + quote do + unquote(__MODULE__).__render_slot__(unquote(slot), unquote(arg)) + end + end + + @doc false + def __render_slot__([], _), do: nil + + def __render_slot__([entry], argument) do + call_inner_block!(entry, argument) + end + + def __render_slot__(entries, argument) when is_list(entries) do + assigns = %{} + _ = assigns + + temple do + for entry <- entries do + call_inner_block!(entry, argument) + end + end + end + + def __render_slot__(entry, argument) when is_map(entry) do + entry.inner_block.(argument) + end + + defp call_inner_block!(entry, argument) do + if !entry.inner_block do + message = "attempted to render slot #{entry.__slot__} but the slot has no inner content" + raise RuntimeError, message + end + + entry.inner_block.(argument) + end + end + end +end diff --git a/lib/temple/renderer.ex b/lib/temple/renderer.ex index 94dbf04..270315f 100644 --- a/lib/temple/renderer.ex +++ b/lib/temple/renderer.ex @@ -17,12 +17,14 @@ defmodule Temple.Renderer do alias Temple.Ast.Utils - @default_engine EEx.SmartEngine + @engine Application.compile_env(:temple, :engine, Phoenix.HTML.Engine) + @doc false + def engine(), do: @engine - defmacro compile(opts \\ [], do: block) do + defmacro compile(do: block) do block |> Temple.Parser.parse() - |> Temple.Renderer.render(opts) + |> Temple.Renderer.render(engine: @engine) # |> Temple.Ast.Utils.inspect_ast() end @@ -30,7 +32,7 @@ defmodule Temple.Renderer do def render(asts, opts \\ []) def render(asts, opts) when is_list(asts) and is_list(opts) do - engine = Keyword.get(opts, :engine, @default_engine) + engine = Keyword.get(opts, :engine, Phoenix.HTML.Engine) state = %{ engine: engine, @@ -97,13 +99,22 @@ defmodule Temple.Renderer do end end - {:%{}, [], - [ - __slot__: slot.name, - inner_block: inner_block - ] ++ slot.attributes} + {rest, attributes} = Keyword.pop(slot.attributes, :rest!, []) + + slot = + {:%{}, [], + [ + __slot__: slot.name, + inner_block: inner_block + ] ++ attributes} + + quote do + Map.merge(unquote(slot), Map.new(unquote(rest))) + end end) + {rest, arguments} = Keyword.pop(arguments, :rest!, []) + component_arguments = {:%{}, [], arguments @@ -111,6 +122,11 @@ defmodule Temple.Renderer do |> Map.merge(slot_quotes) |> Enum.to_list()} + component_arguments = + quote do + Map.merge(unquote(component_arguments), Map.new(unquote(rest))) + end + expr = quote do component( @@ -126,7 +142,9 @@ defmodule Temple.Renderer do def render(buffer, state, %Slot{} = ast) do render_slot_func = quote do - render_slot(unquote(ast.name), unquote(ast.args)) + {rest, args} = Map.pop(Map.new(unquote(ast.args)), :rest!, []) + args = Map.merge(args, Map.new(rest)) + render_slot(unquote(ast.name), args) end state.engine.handle_expr(buffer, "=", render_slot_func) diff --git a/mix.exs b/mix.exs index 0b598c5..7514e09 100644 --- a/mix.exs +++ b/mix.exs @@ -18,7 +18,17 @@ defmodule Temple.MixProject do end # Specifies which paths to compile per environment. - defp elixirc_paths(:test), do: ["lib", "test/support"] + defp elixirc_paths(:test) do + # hack to get the right compiler options used on the non-script files in + # test/support + Code.put_compiler_option( + :parser_options, + Keyword.put(Code.get_compiler_option(:parser_options), :token_metadata, true) + ) + + ["lib", "test/support"] + end + defp elixirc_paths(_), do: ["lib"] # Run "mix help compile.app" to learn about applications. @@ -61,8 +71,9 @@ defmodule Temple.MixProject do defp deps do [ - {:typed_struct, "~> 0.3"}, {:floki, ">= 0.0.0"}, + {:phoenix_html, "~> 3.2"}, + {:typed_struct, "~> 0.3"}, {:ex_doc, "~> 0.29.0", only: :dev, runtime: false} ] end diff --git a/mix.lock b/mix.lock index b46c396..adc287a 100644 --- a/mix.lock +++ b/mix.lock @@ -7,5 +7,6 @@ "makeup_elixir": {:hex, :makeup_elixir, "0.16.0", "f8c570a0d33f8039513fbccaf7108c5d750f47d8defd44088371191b76492b0b", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "28b2cbdc13960a46ae9a8858c4bebdec3c9a6d7b4b9e7f4ed1502f8159f338e7"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, "nimble_parsec": {:hex, :nimble_parsec, "1.2.3", "244836e6e3f1200c7f30cb56733fd808744eca61fd182f731eac4af635cc6d0b", [:mix], [], "hexpm", "c8d789e39b9131acf7b99291e93dae60ab48ef14a7ee9d58c6964f59efb570b0"}, + "phoenix_html": {:hex, :phoenix_html, "3.2.0", "1c1219d4b6cb22ac72f12f73dc5fad6c7563104d083f711c3fcd8551a1f4ae11", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "36ec97ba56d25c0136ef1992c37957e4246b649d620958a1f9fa86165f8bc54f"}, "typed_struct": {:hex, :typed_struct, "0.3.0", "939789e3c1dca39d7170c87f729127469d1315dcf99fee8e152bb774b17e7ff7", [:mix], [], "hexpm", "c50bd5c3a61fe4e198a8504f939be3d3c85903b382bde4865579bc23111d1b6d"}, } diff --git a/test/support/component.ex b/test/support/component.ex new file mode 100644 index 0000000..191affa --- /dev/null +++ b/test/support/component.ex @@ -0,0 +1,10 @@ +defmodule Temple.Support.Component do + use Temple.Component + + defmacro __using__(_) do + quote do + import Temple + import unquote(__MODULE__) + end + end +end diff --git a/test/support/components.ex b/test/support/components.ex new file mode 100644 index 0000000..adc210f --- /dev/null +++ b/test/support/components.ex @@ -0,0 +1,54 @@ +defmodule Temple.Support.Components do + use Temple.Support.Component + + def basic_component(_assigns) do + temple do + div do + "I am a basic component" + end + end + end + + def default_slot(assigns) do + temple do + div do + "I am above the slot" + slot @inner_block + end + end + end + + def named_slot(assigns) do + temple do + div do + "#{@name} is above the slot" + slot @inner_block + end + + footer do + for f <- @footer do + span do: f[:label] + slot f, %{name: @name} + end + end + end + end + + def rest_component(assigns) do + temple do + div do + "I am a basic #{@id} with #{@class}" + end + end + end + + def rest_slot(assigns) do + temple do + div do + for foo <- @foo do + slot foo, slot_id: foo.id, rest!: [slot_class: foo.class] + end + end + end + end +end diff --git a/test/support/helpers.ex b/test/support/helpers.ex new file mode 100644 index 0000000..7b7d5fb --- /dev/null +++ b/test/support/helpers.ex @@ -0,0 +1,13 @@ +defmodule Temple.Support.Helpers do + defmacro assert_html(expected, actual) do + quote do + assert unquote(expected) == Phoenix.HTML.safe_to_string(unquote(actual)), """ + --- Expected --- + #{unquote(expected)}---------------- + + --- Actual --- + #{Phoenix.HTML.safe_to_string(unquote(actual))}-------------- + """ + end + end +end diff --git a/test/parser/anonymous_functions_test.exs b/test/temple/ast/anonymous_functions_test.exs similarity index 100% rename from test/parser/anonymous_functions_test.exs rename to test/temple/ast/anonymous_functions_test.exs diff --git a/test/parser/components_test.exs b/test/temple/ast/components_test.exs similarity index 96% rename from test/parser/components_test.exs rename to test/temple/ast/components_test.exs index 89d200a..09e4109 100644 --- a/test/parser/components_test.exs +++ b/test/temple/ast/components_test.exs @@ -111,7 +111,7 @@ defmodule Temple.Ast.ComponentsTest do raw_ast = quote do c unquote(func), foo: :bar do - slot :foo, let: %{form: form} do + slot :foo, let!: %{form: form} do "in the slot" end end @@ -136,7 +136,7 @@ defmodule Temple.Ast.ComponentsTest do raw_ast = quote do c unquote(func), foo: :bar do - slot :foo, let: %{form: form}, label: the_label do + slot :foo, let!: %{form: form}, label: the_label do "in the slot" end end @@ -170,7 +170,7 @@ defmodule Temple.Ast.ComponentsTest do c unquote(list), socials: @user.socials do "hello" - slot :foo, let: %{text: text, url: url} do + slot :foo, let!: %{text: text, url: url} do a class: "text-blue-500 hover:underline", href: url do text end diff --git a/test/parser/default_test.exs b/test/temple/ast/default_test.exs similarity index 100% rename from test/parser/default_test.exs rename to test/temple/ast/default_test.exs diff --git a/test/parser/do_expressions_test.exs b/test/temple/ast/do_expressions_test.exs similarity index 100% rename from test/parser/do_expressions_test.exs rename to test/temple/ast/do_expressions_test.exs diff --git a/test/parser/empty_test.exs b/test/temple/ast/empty_test.exs similarity index 100% rename from test/parser/empty_test.exs rename to test/temple/ast/empty_test.exs diff --git a/test/parser/match_test.exs b/test/temple/ast/match_test.exs similarity index 100% rename from test/parser/match_test.exs rename to test/temple/ast/match_test.exs diff --git a/test/parser/nonvoid_elements_aliases_test.exs b/test/temple/ast/nonvoid_elements_aliases_test.exs similarity index 100% rename from test/parser/nonvoid_elements_aliases_test.exs rename to test/temple/ast/nonvoid_elements_aliases_test.exs diff --git a/test/parser/right_arrow_test.exs b/test/temple/ast/right_arrow_test.exs similarity index 100% rename from test/parser/right_arrow_test.exs rename to test/temple/ast/right_arrow_test.exs diff --git a/test/parser/slot_test.exs b/test/temple/ast/slot_test.exs similarity index 100% rename from test/parser/slot_test.exs rename to test/temple/ast/slot_test.exs diff --git a/test/parser/temple_namespace_nonvoid_test.exs b/test/temple/ast/temple_namespace_nonvoid_test.exs similarity index 100% rename from test/parser/temple_namespace_nonvoid_test.exs rename to test/temple/ast/temple_namespace_nonvoid_test.exs diff --git a/test/parser/temple_namespace_void_test.exs b/test/temple/ast/temple_namespace_void_test.exs similarity index 100% rename from test/parser/temple_namespace_void_test.exs rename to test/temple/ast/temple_namespace_void_test.exs diff --git a/test/parser/text_test.exs b/test/temple/ast/text_test.exs similarity index 100% rename from test/parser/text_test.exs rename to test/temple/ast/text_test.exs diff --git a/test/parser/utils_test.exs b/test/temple/ast/utils_test.exs similarity index 72% rename from test/parser/utils_test.exs rename to test/temple/ast/utils_test.exs index 31d6343..f1715a6 100644 --- a/test/parser/utils_test.exs +++ b/test/temple/ast/utils_test.exs @@ -25,11 +25,7 @@ defmodule Temple.Ast.UtilsTest do assert Macro.to_string( quote do - case @class do - true -> " " <> "class" - false -> "" - _ -> ~s' #{"class"}="#{@class}"' - end + Phoenix.HTML.attributes_escape([{"class", unquote(class_ast)}]) end ) == Macro.to_string(actual) end @@ -60,5 +56,27 @@ defmodule Temple.Ast.UtilsTest do # the ast metadata is different, let's just compare stringified versions assert Macro.to_string(result_expr) == Macro.to_string(expr) end + + test "the rest! attribute will mix in the values at runtime" do + rest_ast = + quote do + rest + end + + attrs = [class: "text-red", rest!: rest_ast] + + actual = Utils.compile_attrs(attrs) + + assert [ + {:text, ~s' class="text-red"'}, + {:expr, rest_actual} + ] = actual + + assert Macro.to_string( + quote do + Phoenix.HTML.attributes_escape(unquote(rest_ast)) + end + ) == Macro.to_string(rest_actual) + end end end diff --git a/test/parser/void_elements_aliases_test.exs b/test/temple/ast/void_elements_aliases_test.exs similarity index 100% rename from test/parser/void_elements_aliases_test.exs rename to test/temple/ast/void_elements_aliases_test.exs diff --git a/test/temple/renderer_test.exs b/test/temple/renderer_test.exs index 4e9cfe6..4c0670d 100644 --- a/test/temple/renderer_test.exs +++ b/test/temple/renderer_test.exs @@ -1,11 +1,14 @@ defmodule Temple.RendererTest do use ExUnit.Case, async: true - import Temple + use Temple.Support.Component + import Temple.Support.Components require Temple.Renderer alias Temple.Renderer + import Temple.Support.Helpers + describe "compile/1" do test "produces renders a text node" do result = @@ -13,7 +16,7 @@ defmodule Temple.RendererTest do "hello world" end - assert "hello world\n" == result + assert_html "hello world\n", result end test "produces renders a div" do @@ -36,7 +39,7 @@ defmodule Temple.RendererTest do """ - assert expected == result + assert_html expected, result end test "produces renders a void elements" do @@ -61,7 +64,7 @@ defmodule Temple.RendererTest do """ - assert expected == result + assert_html expected, result end test "a match does not emit" do @@ -83,7 +86,7 @@ defmodule Temple.RendererTest do """ - assert expected == result + assert_html expected, result end test "handles simple expression inside attributes" do @@ -104,29 +107,29 @@ defmodule Temple.RendererTest do """ - assert expected == result + assert_html expected, result end - # test "handles simple expression are the entire attributes" do - # assigns = %{statement: "hello world", attributes: [class: "green"]} + test "handles simple expression are the entire attributes" do + assigns = %{statement: "hello world", attributes: [class: "green"]} - # result = - # Renderer.compile do - # div @attributes do - # @statement - # end - # end + result = + Renderer.compile do + div @attributes do + @statement + end + end - # # html - # expected = """ - #
- # hello world - #
+ # html + expected = """ +
+ hello world +
- # """ + """ - # assert expected == result - # end + assert_html expected, result + end test "handles simple expression with @ assign" do assigns = %{statement: "hello world"} @@ -146,7 +149,7 @@ defmodule Temple.RendererTest do """ - assert expected == result + assert_html expected, result end test "handles multi line expression" do @@ -172,7 +175,7 @@ defmodule Temple.RendererTest do """ - assert expected == result + assert_html expected, result end test "if expression" do @@ -199,7 +202,7 @@ defmodule Temple.RendererTest do """ - assert expected == result + assert_html expected, result end end @@ -232,7 +235,7 @@ defmodule Temple.RendererTest do """ - assert expected == result + assert_html expected, result end end @@ -264,7 +267,7 @@ defmodule Temple.RendererTest do end end - assert expected == result + assert_html expected, result end test "handles anonymous functions" do @@ -293,7 +296,7 @@ defmodule Temple.RendererTest do """ - assert expected == result + assert_html expected, result end def super_map(enumerable, func, _extra_args) do @@ -330,15 +333,7 @@ defmodule Temple.RendererTest do """ - assert expected == result - end - - def basic_component(_assigns) do - temple do - div do - "I am a basic component" - end - end + assert_html expected, result end test "basic component" do @@ -361,16 +356,7 @@ defmodule Temple.RendererTest do """ - assert expected == result - end - - def default_slot(assigns) do - temple do - div do - "I am above the slot" - slot @inner_block - end - end + assert_html expected, result end test "component with default slot" do @@ -399,23 +385,7 @@ defmodule Temple.RendererTest do """ - assert expected == result - end - - def named_slot(assigns) do - temple do - div do - "#{@name} is above the slot" - slot @inner_block - end - - footer do - for f <- @footer do - span do: f[:label] - slot f, %{name: @name} - end - end - end + assert_html expected, result end test "component with a named slot" do @@ -427,7 +397,7 @@ defmodule Temple.RendererTest do c &named_slot/1, name: "motchy boi" do span do: "i'm a slot" - slot :footer, let: %{name: name}, label: @label, expr: 1 + 1 do + slot :footer, let!: %{name: name}, label: @label, expr: 1 + 1 do p do "#{name}'s in the footer!" end @@ -446,9 +416,9 @@ defmodule Temple.RendererTest do
@@ -458,7 +428,7 @@ defmodule Temple.RendererTest do """ - assert expected == result + assert_html expected, result end end @@ -479,7 +449,7 @@ defmodule Temple.RendererTest do """ - assert expected == result + assert_html expected, result end test "boolean attributes only emit correctly with truthy values" do @@ -493,7 +463,7 @@ defmodule Temple.RendererTest do """ - assert expected == result + assert_html expected, result end test "boolean attributes don't emit with falsy values" do @@ -507,7 +477,7 @@ defmodule Temple.RendererTest do """ - assert expected == result + assert_html expected, result end test "runtime boolean attributes emit the right values" do @@ -524,7 +494,7 @@ defmodule Temple.RendererTest do """ - assert expected == result + assert_html expected, result end test "multiple slots" do @@ -536,13 +506,13 @@ defmodule Temple.RendererTest do c &named_slot/1, name: "motchy boi" do span do: "i'm a slot" - slot :footer, let: %{name: name} do + slot :footer, let!: %{name: name} do p do "#{name}'s in the footer!" end end - slot :footer, let: %{name: name} do + slot :footer, let!: %{name: name} do p do "#{name} is the second footer!" end @@ -563,7 +533,7 @@ defmodule Temple.RendererTest do