2019-07-02 02:48:51 +00:00
|
|
|
defmodule TempleTest do
|
2019-05-12 03:28:26 +00:00
|
|
|
use ExUnit.Case, async: true
|
2019-07-02 02:48:51 +00:00
|
|
|
use Temple
|
2020-04-09 02:54:09 +00:00
|
|
|
use Temple.Support.Utils
|
2019-05-12 03:28:26 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
test "renders an attribute on a div passed as a variable" do
|
|
|
|
result =
|
|
|
|
temple do
|
|
|
|
div class: "hello" do
|
|
|
|
div class: "hi"
|
2019-08-10 05:09:24 +00:00
|
|
|
end
|
2020-06-16 19:28:21 +00:00
|
|
|
end
|
2019-08-10 05:09:24 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
assert result == ~s{<div class="hello"><div class="hi"></div></div>}
|
|
|
|
end
|
2019-08-10 05:09:24 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
test "renders void element" do
|
|
|
|
result =
|
|
|
|
temple do
|
|
|
|
input name: "password"
|
|
|
|
end
|
2019-05-12 03:28:26 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
assert result == ~s{<input name="password">}
|
|
|
|
end
|
2019-05-12 03:28:26 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
test "renders a text node from the text keyword with siblings" do
|
|
|
|
result =
|
|
|
|
temple do
|
|
|
|
div class: "hello" do
|
|
|
|
"hi"
|
|
|
|
"foo"
|
|
|
|
end
|
|
|
|
end
|
2019-05-12 03:28:26 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
assert result == ~s{<div class="hello">hifoo</div>}
|
|
|
|
end
|
2019-05-12 03:28:26 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
test "renders a variable text node as eex" do
|
|
|
|
result =
|
|
|
|
temple do
|
|
|
|
div class: "hello" do
|
|
|
|
foo
|
2019-05-12 03:28:26 +00:00
|
|
|
end
|
2020-06-16 19:28:21 +00:00
|
|
|
end
|
2019-05-12 03:28:26 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
assert result == ~s{<div class="hello"><%= foo %></div>}
|
|
|
|
end
|
2019-05-12 03:28:26 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
test "renders an assign text node as eex" do
|
|
|
|
result =
|
|
|
|
temple do
|
|
|
|
div class: "hello" do
|
|
|
|
@foo
|
2019-05-12 03:28:26 +00:00
|
|
|
end
|
2020-06-16 19:28:21 +00:00
|
|
|
end
|
2019-05-12 03:28:26 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
assert result == ~s{<div class="hello"><%= @foo %></div>}
|
|
|
|
end
|
2019-05-12 03:28:26 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
test "renders a match expression" do
|
|
|
|
result =
|
|
|
|
temple do
|
|
|
|
x = 420
|
2019-05-12 03:28:26 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
div do
|
|
|
|
"blaze it"
|
2019-05-12 03:28:26 +00:00
|
|
|
end
|
2020-06-16 19:28:21 +00:00
|
|
|
end
|
2019-05-12 03:28:26 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
assert result == ~s{<% x = 420 %><div>blaze it</div>}
|
|
|
|
end
|
2020-04-09 02:18:22 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
test "renders a non-match expression" do
|
|
|
|
result =
|
|
|
|
temple do
|
|
|
|
IO.inspect(:foo)
|
2020-04-09 02:18:22 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
div do
|
|
|
|
"bar"
|
2020-04-09 02:18:22 +00:00
|
|
|
end
|
2020-06-16 19:28:21 +00:00
|
|
|
end
|
2020-04-09 02:18:22 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
assert result == ~s{<%= IO.inspect(:foo) %><div>bar</div>}
|
|
|
|
end
|
2020-04-09 02:18:22 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
test "renders an expression in attr as eex" do
|
|
|
|
result =
|
|
|
|
temple do
|
|
|
|
div class: foo <> " bar"
|
|
|
|
end
|
2020-04-09 02:18:22 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
assert result == ~s{<div class="<%= foo <> " bar" %>"></div>}
|
|
|
|
end
|
2020-04-09 02:18:22 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
test "renders an attribute on a div passed as a variable as eex" do
|
|
|
|
result =
|
|
|
|
temple do
|
|
|
|
div class: Enum.map([:one, :two], fn x -> x end) do
|
|
|
|
div class: "hi"
|
2020-04-09 02:18:22 +00:00
|
|
|
end
|
2020-06-16 19:28:21 +00:00
|
|
|
end
|
2020-04-09 02:18:22 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
assert result ==
|
|
|
|
~s{<div class="<%= Enum.map([:one, :two], fn x -> x end) %>"><div class="hi"></div></div>}
|
|
|
|
end
|
2019-05-12 03:28:26 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
test "renders a for comprehension as eex" do
|
|
|
|
result =
|
|
|
|
temple do
|
|
|
|
for x <- 1..5 do
|
|
|
|
div class: "hi"
|
2019-05-12 03:28:26 +00:00
|
|
|
end
|
2020-06-16 19:28:21 +00:00
|
|
|
end
|
2019-05-12 03:28:26 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
assert result == ~s{<%= for(x <- 1..5) do %><div class="hi"></div><% end %>}
|
|
|
|
end
|
2019-05-12 03:28:26 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
test "renders an if expression as eex" do
|
|
|
|
result =
|
|
|
|
temple do
|
|
|
|
if true == false do
|
|
|
|
div class: "hi"
|
2019-05-12 03:28:26 +00:00
|
|
|
end
|
2020-06-16 19:28:21 +00:00
|
|
|
end
|
2019-05-12 03:28:26 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
assert result == ~s{<%= if(true == false) do %><div class="hi"></div><% end %>}
|
|
|
|
end
|
2019-05-12 03:28:26 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
test "renders an if/else expression as eex" do
|
|
|
|
result =
|
|
|
|
temple do
|
|
|
|
if true == false do
|
|
|
|
div class: "hi"
|
|
|
|
else
|
|
|
|
div class: "haha"
|
2019-05-12 03:28:26 +00:00
|
|
|
end
|
2020-06-16 19:28:21 +00:00
|
|
|
end
|
2019-05-12 03:28:26 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
assert result ==
|
|
|
|
~s{<%= if(true == false) do %><div class="hi"></div><% else %><div class="haha"></div><% end %>}
|
|
|
|
end
|
2019-05-12 03:28:26 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
test "renders an unless expression as eex" do
|
|
|
|
result =
|
|
|
|
temple do
|
|
|
|
unless true == false do
|
|
|
|
div class: "hi"
|
|
|
|
end
|
|
|
|
end
|
2019-05-12 03:28:26 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
assert result == ~s{<%= unless(true == false) do %><div class="hi"></div><% end %>}
|
|
|
|
end
|
2019-05-12 03:28:26 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
test "renders multiline anonymous function with 1 arg before the function" do
|
|
|
|
result =
|
|
|
|
temple do
|
|
|
|
form_for Routes.user_path(@conn, :create), fn f ->
|
|
|
|
"Name: "
|
|
|
|
text_input f, :name
|
2019-05-12 03:28:26 +00:00
|
|
|
end
|
2020-06-16 19:28:21 +00:00
|
|
|
end
|
2019-05-12 03:28:26 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
assert result ==
|
|
|
|
~s{<%= form_for Routes.user_path(@conn, :create), fn f -> %>Name: <%= text_input(f, :name) %><% end %>}
|
|
|
|
end
|
2019-05-12 03:28:26 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
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 ->
|
|
|
|
"Name: "
|
|
|
|
text_input f, :name
|
|
|
|
end
|
|
|
|
end
|
2019-05-12 03:28:26 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
assert result ==
|
|
|
|
~s{<%= form_for @changeset, Routes.user_path(@conn, :create), fn f -> %>Name: <%= text_input(f, :name) %><% end %>}
|
|
|
|
end
|
2019-05-12 03:28:26 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
test "renders multiline anonymous functions with complex nested children" do
|
|
|
|
result =
|
|
|
|
temple do
|
|
|
|
form_for @changeset, Routes.user_path(@conn, :create), fn f ->
|
|
|
|
div do
|
|
|
|
"Name: "
|
|
|
|
text_input f, :name
|
2019-05-12 03:28:26 +00:00
|
|
|
end
|
|
|
|
end
|
2020-06-16 19:28:21 +00:00
|
|
|
end
|
2019-05-12 03:28:26 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
assert result ==
|
|
|
|
~s{<%= form_for @changeset, Routes.user_path(@conn, :create), fn f -> %><div>Name: <%= text_input(f, :name) %></div><% end %>}
|
|
|
|
end
|
2019-07-04 04:16:29 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
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 ->
|
|
|
|
"Name: "
|
|
|
|
text_input f, :name
|
2019-07-04 04:16:29 +00:00
|
|
|
end
|
2020-06-16 19:28:21 +00:00
|
|
|
end
|
2019-07-04 04:16:29 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
assert result ==
|
|
|
|
~s{<%= form_for @changeset, Routes.user_path(@conn, :create), [foo: :bar], fn f -> %>Name: <%= text_input(f, :name) %><% end %>}
|
|
|
|
end
|
2019-07-04 04:16:29 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
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
|
|
|
|
end
|
|
|
|
|
|
|
|
assert result ==
|
|
|
|
~s{<%= form_for @changeset, fn f -> %>Name: <%= text_input(f, :name) %><% end, [foo: :bar] %>}
|
|
|
|
end
|
2019-07-04 04:16:29 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
test "tags prefixed with Temple. should be interpreted as temple tags" do
|
|
|
|
result =
|
|
|
|
temple do
|
|
|
|
div do
|
|
|
|
Temple.span do
|
|
|
|
"bob"
|
2019-07-04 04:16:29 +00:00
|
|
|
end
|
|
|
|
end
|
2020-06-16 19:28:21 +00:00
|
|
|
end
|
2019-07-04 04:16:29 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
assert result == ~s{<div><span>bob</span></div>}
|
|
|
|
end
|
2019-07-04 04:16:29 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
test "can pass do as an arg instead of a block" do
|
|
|
|
result =
|
|
|
|
temple do
|
|
|
|
div class: "font-bold" do
|
|
|
|
"Hello, world"
|
|
|
|
end
|
2019-07-04 04:16:29 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
div class: "font-bold", do: "Hello, world"
|
|
|
|
div do: "Hello, world"
|
|
|
|
end
|
2019-07-04 04:16:29 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
assert result ==
|
|
|
|
~s{<div class="font-bold">Hello, world</div><div class="font-bold">Hello, world</div><div>Hello, world</div>}
|
|
|
|
end
|
2019-07-08 02:26:32 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
test "passing 'compact: true' will not insert new lines" do
|
|
|
|
import Temple.Support.Utils, only: []
|
|
|
|
import Kernel
|
|
|
|
|
|
|
|
result =
|
|
|
|
temple do
|
|
|
|
p compact: true do
|
|
|
|
"Bob"
|
|
|
|
end
|
|
|
|
|
|
|
|
p compact: true do
|
|
|
|
foo
|
2019-07-04 04:16:29 +00:00
|
|
|
end
|
2020-06-16 19:28:21 +00:00
|
|
|
end
|
2019-07-04 04:16:29 +00:00
|
|
|
|
2020-06-16 19:28:21 +00:00
|
|
|
assert result == ~s{<p>Bob</p>\n<p><%= foo %></p>}
|
2019-05-12 03:28:26 +00:00
|
|
|
end
|
Components API
Components work very similarly to how they worked before, but with a few
differences.
To define a component, you can create a file in your configured temple
components directory, which defaults to `lib/components`. You would
probably want ot change that to be `lib/my_app_web/components` if you
are building a phoenix app.
This file should be of the `.exs` extension, and contain any temple
compatible code.
You can then use this component in any other temple template.
For example, if I were to define a `flex` component, I would create a
file called `lib/my_app_web/components/flex.exs`, with the following
contents.
```elixir
div class: "flex #{@temple[:class]}", id: @id do
@children
end
```
And we could use the component like so
```elixir
flex class: "justify-between items-center", id: "arnold" do
div do: "Hi"
div do: "I'm"
div do: "Arnold"
div do: "Schwarzenegger"
end
```
We've demonstated several features to components in this example.
We can pass assigns to our component, and access them just like we would
in a normal phoenix template. If they don't match up with any assigns we
passed to our component, they will be rendered as-is, and will become a
normal Phoenix assign.
You can also access a special `@temple` assign. This allows you do
optionally pass an assign, and not have the `@my_assign` pass through.
If you didn't pass it to your component, it will evaluate to nil.
The block passed to your component can be accessed as `@children`. This
allows your components to wrap a body of markup from the call site.
In order for components to trigger a recompile when they are changed,
you can call `use Temple.Recompiler` in your `lib/my_app_web.ex` file,
in the `view`, `live_view`, and `live_component` functions
```elixir
def view do
quote do
# ...
use Temple.Recompiler
# ...
end
end
```
2020-07-16 02:11:35 +00:00
|
|
|
|
|
|
|
test "inlines function components" do
|
|
|
|
result =
|
|
|
|
temple do
|
|
|
|
div class: "font-bold" do
|
|
|
|
"Hello, world"
|
|
|
|
end
|
|
|
|
|
|
|
|
component do
|
|
|
|
"I'm a component!"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
assert result ==
|
|
|
|
~s{<div class="font-bold">Hello, world</div><div class="<%= @assign %>">I'm a component!</div>}
|
|
|
|
end
|
|
|
|
|
|
|
|
test "function components can accept local assigns" do
|
|
|
|
result =
|
|
|
|
temple do
|
|
|
|
div class: "font-bold" do
|
|
|
|
"Hello, world"
|
|
|
|
end
|
|
|
|
|
|
|
|
component2 class: "bg-red" do
|
|
|
|
"I'm a component!"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
assert result ==
|
|
|
|
~s{<div class="font-bold">Hello, world</div><div class="bg-red">I'm a component!</div>}
|
|
|
|
end
|
|
|
|
|
|
|
|
test "function components can accept local assigns that are variables" do
|
|
|
|
result =
|
|
|
|
temple do
|
|
|
|
div class: "font-bold" do
|
|
|
|
"Hello, world"
|
|
|
|
end
|
|
|
|
|
|
|
|
class = "bg-red"
|
|
|
|
|
|
|
|
component2 class: class do
|
|
|
|
"I'm a component!"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
assert result ==
|
|
|
|
~s{<div class="font-bold">Hello, world</div><% class = "bg-red" %><div class="<%= class %>">I'm a component!</div>}
|
|
|
|
end
|
|
|
|
|
|
|
|
test "function components can use other components" do
|
|
|
|
result =
|
|
|
|
temple do
|
|
|
|
outer do
|
|
|
|
"outer!"
|
|
|
|
end
|
|
|
|
|
|
|
|
inner do
|
|
|
|
"inner!"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
assert result ==
|
|
|
|
~s{<div id="inner" outer-id="from-outer">outer!</div><div id="inner" outer-id="<%= @outer_id %>">inner!</div>}
|
|
|
|
end
|
|
|
|
|
|
|
|
test "@temple should be available in any component" do
|
|
|
|
result =
|
|
|
|
temple do
|
|
|
|
has_temple class: "boom" do
|
|
|
|
"yay!"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
assert result == ~s{<div class="<%= [class: "boom"][:class] %>">yay!</div>}
|
|
|
|
end
|
2020-07-16 03:23:12 +00:00
|
|
|
|
|
|
|
test "normal functions with blocks should be treated like if expressions" do
|
|
|
|
result =
|
|
|
|
temple do
|
|
|
|
leenk to: "/route", class: "foo" do
|
|
|
|
div class: "hi"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2020-07-23 01:30:39 +00:00
|
|
|
assert result ==
|
|
|
|
~s{<%= leenk(to: "/route", class: "foo") do %><div class="hi"></div><% end %>}
|
|
|
|
end
|
|
|
|
|
|
|
|
test "for with 2 generators" do
|
|
|
|
result =
|
|
|
|
temple do
|
|
|
|
for x <- 1..5, y <- 6..10 do
|
|
|
|
div do: x
|
|
|
|
div do: y
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
assert result ==
|
|
|
|
~s{<%= for(x <- 1..5, y <- 6..10) do %><div><%= x %></div><div><%= y %></div><% end %>}
|
2020-07-16 03:23:12 +00:00
|
|
|
end
|
2020-08-09 14:07:27 +00:00
|
|
|
|
|
|
|
test "can pass an expression as assigns" do
|
|
|
|
result =
|
|
|
|
temple do
|
|
|
|
fieldset if true == false, do: [disabled: true], else: [] do
|
|
|
|
input type: "text"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
assert result ==
|
|
|
|
~s{<fieldset<%= Temple.Parser.Private.runtime_attrs(if(true == false) do [disabled: true]else []end) %>><input type="text"></fieldset>}
|
|
|
|
end
|
|
|
|
|
|
|
|
test "can pass a variable as assigns" do
|
|
|
|
result =
|
|
|
|
temple do
|
|
|
|
fieldset foo_bar do
|
|
|
|
input type: "text"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
assert result ==
|
|
|
|
~s{<fieldset<%= Temple.Parser.Private.runtime_attrs(foo_bar) %>><input type="text"></fieldset>}
|
|
|
|
end
|
|
|
|
|
|
|
|
test "can pass a function as assigns" do
|
|
|
|
result =
|
|
|
|
temple do
|
|
|
|
fieldset Foo.foo_bar() do
|
|
|
|
input type: "text"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
assert result ==
|
|
|
|
~s{<fieldset<%= Temple.Parser.Private.runtime_attrs(Foo.foo_bar()) %>><input type="text"></fieldset>}
|
|
|
|
end
|
|
|
|
|
|
|
|
test "can pass a function as assigns that has @temple" do
|
|
|
|
result =
|
|
|
|
temple do
|
|
|
|
has_temple_function_assign class: "justify-end", style: "color: pink" do
|
|
|
|
input type: "text"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
expected =
|
|
|
|
~S"""
|
|
|
|
<div<%= Temple.Parser.Private.runtime_attrs(Keyword.put([class: "justify-end", style: "color: pink"], :class, "flex #{[class: "justify-end", style: "color: pink"][:class]}")) %>>
|
|
|
|
<input type="text">
|
|
|
|
</div>
|
|
|
|
"""
|
|
|
|
|> String.trim()
|
|
|
|
|
|
|
|
assert result == expected
|
|
|
|
|
|
|
|
assert evaluate_template(result) == evaluate_template(expected)
|
|
|
|
end
|
|
|
|
|
|
|
|
defp evaluate_template(template) do
|
|
|
|
template
|
|
|
|
|> EEx.compile_string(engine: Phoenix.HTML.Engine)
|
|
|
|
|> Code.eval_quoted()
|
|
|
|
|> elem(0)
|
|
|
|
|> Phoenix.HTML.safe_to_string()
|
|
|
|
end
|
2019-04-15 01:44:39 +00:00
|
|
|
end
|