Slots!
Integration test for slots Format integration test project Hide slots assign in temple prefixed key Won't compile temple related assigns when calling Utils.runtime_attrs Update component docs with slots usage
This commit is contained in:
parent
f7197ede4a
commit
851f6415fe
26 changed files with 457 additions and 80 deletions
|
@ -1,5 +1,5 @@
|
|||
locals_without_parens = ~w[
|
||||
temple c
|
||||
temple c slot
|
||||
html head title style script
|
||||
noscript template
|
||||
body section nav article aside h1 h2 h3 h4 h5 h6
|
||||
|
|
|
@ -191,3 +191,8 @@ To include Temple's formatter configuration, add `:temple` to your `.formatter.e
|
|||
inputs: ["*.{ex,exs}", "priv/*/seeds.exs", "{config,lib,test}/**/*.{ex,exs,lexs}"],
|
||||
]
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [Introducing Temple: An elegant HTML library for Elixir and Phoenix](https://www.mitchellhanberg.com/introducing-temple-an-elegant-html-library-for-elixir-and-phoenix/)
|
||||
- [Temple, AST, and Protocols](https://www.mitchellhanberg.com/temple-ast-and-protocols/)
|
||||
|
|
|
@ -29,6 +29,5 @@ config :wallaby,
|
|||
otp_app: :temple_demo,
|
||||
screenshot_on_failure: true
|
||||
|
||||
|
||||
# Print only warnings and errors during test
|
||||
config :logger, level: :warn
|
||||
|
|
|
@ -38,6 +38,7 @@ defmodule TempleDemoWeb do
|
|||
|
||||
alias TempleDemoWeb.Component.Outer
|
||||
alias TempleDemoWeb.Component.Flash
|
||||
alias TempleDemoWeb.Component.Form
|
||||
|
||||
# Include shared imports and aliases for views
|
||||
unquote(view_helpers())
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
defmodule TempleDemoWeb.Component.Form do
|
||||
use Temple.Component
|
||||
|
||||
render do
|
||||
f = Phoenix.HTML.Form.form_for(@changeset, @action)
|
||||
|
||||
f
|
||||
|
||||
slot(:f, f: f)
|
||||
|
||||
"</form>"
|
||||
end
|
||||
end
|
|
@ -5,7 +5,6 @@ defmodule TempleDemoWeb.Endpoint do
|
|||
plug Phoenix.Ecto.SQL.Sandbox
|
||||
end
|
||||
|
||||
|
||||
# The session will be stored in the cookie and signed,
|
||||
# this means its contents can be read but not tampered with.
|
||||
# Set :encryption_salt if you would also like to encrypt it.
|
||||
|
|
|
@ -12,6 +12,7 @@ section class: "phx-hero" do
|
|||
p do
|
||||
"Peace-of-mind from prototype to staging"
|
||||
end
|
||||
|
||||
_ ->
|
||||
p do
|
||||
"Peace-of-mind from prototype to production"
|
||||
|
|
|
@ -1,27 +1,29 @@
|
|||
form_for @changeset, @action, fn f ->
|
||||
if @changeset.action do
|
||||
c Flash, type: :info do
|
||||
p do: "Oops, something went wrong! Please check the errors below."
|
||||
c Form, changeset: @changeset, action: @action do
|
||||
slot :f, %{f: f} do
|
||||
if @changeset.action do
|
||||
c Flash, type: :info do
|
||||
p do: "Oops, something went wrong! Please check the errors below."
|
||||
end
|
||||
end
|
||||
|
||||
label f, :title
|
||||
text_input f, :title
|
||||
error_tag(f, :title)
|
||||
|
||||
label f, :body
|
||||
textarea f, :body
|
||||
error_tag(f, :body)
|
||||
|
||||
label f, :published_at
|
||||
datetime_select f, :published_at
|
||||
error_tag(f, :published_at)
|
||||
|
||||
label f, :author
|
||||
text_input f, :author
|
||||
error_tag(f, :author)
|
||||
|
||||
div do
|
||||
submit "Save"
|
||||
end
|
||||
end
|
||||
|
||||
label f, :title
|
||||
text_input f, :title
|
||||
error_tag(f, :title)
|
||||
|
||||
label f, :body
|
||||
textarea f, :body
|
||||
error_tag(f, :body)
|
||||
|
||||
label f, :published_at
|
||||
datetime_select f, :published_at
|
||||
error_tag(f, :published_at)
|
||||
|
||||
label f, :author
|
||||
text_input f, :author
|
||||
error_tag(f, :author)
|
||||
|
||||
div do
|
||||
submit "Save"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -11,16 +11,20 @@ table do
|
|||
|
||||
tbody do
|
||||
for post <- @posts do
|
||||
tr do
|
||||
td do: post.title
|
||||
td do: post.body
|
||||
td do: post.published_at
|
||||
td do: post.author
|
||||
tr do
|
||||
td do: post.title
|
||||
td do: post.body
|
||||
td do: post.published_at
|
||||
td do: post.author
|
||||
|
||||
td do
|
||||
link "Show", to: Routes.post_path(@conn, :show, post)
|
||||
link "Edit", to: Routes.post_path(@conn, :edit, post)
|
||||
link "Delete", to: Routes.post_path(@conn, :delete, post),
|
||||
method: :delete, data: [confirm: "Are you sure?"]
|
||||
|
||||
link "Delete",
|
||||
to: Routes.post_path(@conn, :delete, post),
|
||||
method: :delete,
|
||||
data: [confirm: "Are you sure?"]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
h1 do: "Show Post"
|
||||
|
||||
ul do
|
||||
ul do
|
||||
li do: [strong(do: "Title"), @post.title]
|
||||
|
||||
li do
|
||||
strong do: "Body"
|
||||
Phoenix.HTML.Format.text_to_html @post.body, attributes: [class: "whitespace-pre"]
|
||||
end
|
||||
Phoenix.HTML.Format.text_to_html(@post.body, attributes: [class: "whitespace-pre"])
|
||||
end
|
||||
|
||||
li do
|
||||
strong do: "Published at"
|
||||
@post.published_at
|
||||
end
|
||||
end
|
||||
|
||||
li do
|
||||
strong do: "Author"
|
||||
@post.author
|
||||
end
|
||||
end
|
||||
|
||||
span do
|
||||
link "Edit", to: Routes.post_path(@conn, :edit, @post)
|
||||
|
|
|
@ -6,7 +6,7 @@ defmodule TempleDemoWeb.PostView do
|
|||
|
||||
defcomp Headers do
|
||||
thead id: PostView.thing() do
|
||||
tr do
|
||||
tr do
|
||||
@inner_content
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,6 +10,5 @@ defmodule TempleDemo.Repo.Migrations.CreatePosts do
|
|||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,8 +6,18 @@ defmodule TempleDemo.BlogTest do
|
|||
describe "posts" do
|
||||
alias TempleDemo.Blog.Post
|
||||
|
||||
@valid_attrs %{author: "some author", body: "some body", published_at: ~N[2010-04-17 14:00:00], title: "some title"}
|
||||
@update_attrs %{author: "some updated author", body: "some updated body", published_at: ~N[2011-05-18 15:01:01], title: "some updated title"}
|
||||
@valid_attrs %{
|
||||
author: "some author",
|
||||
body: "some body",
|
||||
published_at: ~N[2010-04-17 14:00:00],
|
||||
title: "some title"
|
||||
}
|
||||
@update_attrs %{
|
||||
author: "some updated author",
|
||||
body: "some updated body",
|
||||
published_at: ~N[2011-05-18 15:01:01],
|
||||
title: "some updated title"
|
||||
}
|
||||
@invalid_attrs %{author: nil, body: nil, published_at: nil, title: nil}
|
||||
|
||||
def post_fixture(attrs \\ %{}) do
|
||||
|
|
|
@ -3,8 +3,18 @@ defmodule TempleDemoWeb.PostControllerTest do
|
|||
|
||||
alias TempleDemo.Blog
|
||||
|
||||
@create_attrs %{author: "some author", body: "some body", published_at: ~N[2010-04-17 14:00:00], title: "some title"}
|
||||
@update_attrs %{author: "some updated author", body: "some updated body", published_at: ~N[2011-05-18 15:01:01], title: "some updated title"}
|
||||
@create_attrs %{
|
||||
author: "some author",
|
||||
body: "some body",
|
||||
published_at: ~N[2010-04-17 14:00:00],
|
||||
title: "some title"
|
||||
}
|
||||
@update_attrs %{
|
||||
author: "some updated author",
|
||||
body: "some updated body",
|
||||
published_at: ~N[2011-05-18 15:01:01],
|
||||
title: "some updated title"
|
||||
}
|
||||
@invalid_attrs %{author: nil, body: nil, published_at: nil, title: nil}
|
||||
|
||||
def fixture(:post) do
|
||||
|
@ -75,6 +85,7 @@ defmodule TempleDemoWeb.PostControllerTest do
|
|||
test "deletes chosen post", %{conn: conn, post: post} do
|
||||
conn = delete(conn, Routes.post_path(conn, :delete, post))
|
||||
assert redirected_to(conn) == Routes.post_path(conn, :index)
|
||||
|
||||
assert_error_sent 404, fn ->
|
||||
get(conn, Routes.post_path(conn, :show, post))
|
||||
end
|
||||
|
|
|
@ -56,6 +56,46 @@ defmodule Temple.Component do
|
|||
end
|
||||
end
|
||||
```
|
||||
|
||||
## Slots
|
||||
|
||||
Components can use slots, which are named placeholders that can be called like functions to be able to pass them data. This is very useful
|
||||
when a component needs to pass data from the inside of the component back to the caller, like when rendering a form in LiveView.
|
||||
|
||||
The definition of a slot happens at the call site of the component and you utilize that slot from inside of the component module.
|
||||
|
||||
```elixir
|
||||
defmodule Form do
|
||||
use Temple.Component
|
||||
|
||||
render do
|
||||
form = form_for(@changeset, @action, assigns)
|
||||
|
||||
form
|
||||
|
||||
slot(:f, form: form)
|
||||
|
||||
"</form>"
|
||||
end
|
||||
end
|
||||
|
||||
# lib/my_app_web/templates/post/new.html.lexs
|
||||
|
||||
c Form, changeset: @changeset,
|
||||
action: @action,
|
||||
class: "form-control",
|
||||
phx_submit: :save,
|
||||
phx_change: :validate do
|
||||
slot :f, %{form: f} do
|
||||
label f do
|
||||
"Widget Name"
|
||||
text_input f, :name, class: "text-input"
|
||||
end
|
||||
|
||||
submit "Save!"
|
||||
end
|
||||
end
|
||||
```
|
||||
"""
|
||||
|
||||
defmacro __using__(_) do
|
||||
|
|
|
@ -6,6 +6,7 @@ defmodule Temple.Parser do
|
|||
alias Temple.Parser.TempleNamespaceNonvoid
|
||||
alias Temple.Parser.TempleNamespaceVoid
|
||||
alias Temple.Parser.Components
|
||||
alias Temple.Parser.Slot
|
||||
alias Temple.Parser.NonvoidElementsAliases
|
||||
alias Temple.Parser.VoidElementsAliases
|
||||
alias Temple.Parser.AnonymousFunctions
|
||||
|
@ -18,6 +19,7 @@ defmodule Temple.Parser do
|
|||
%Empty{}
|
||||
| %Text{}
|
||||
| %Components{}
|
||||
| %Slot{}
|
||||
| %NonvoidElementsAliases{}
|
||||
| %VoidElementsAliases{}
|
||||
| %AnonymousFunctions{}
|
||||
|
@ -83,21 +85,23 @@ defmodule Temple.Parser do
|
|||
def void_elements_aliases, do: @void_elements_aliases
|
||||
def void_elements_lookup, do: @void_elements_lookup
|
||||
|
||||
def parsers(),
|
||||
do: [
|
||||
Temple.Parser.Empty,
|
||||
Temple.Parser.Text,
|
||||
Temple.Parser.TempleNamespaceNonvoid,
|
||||
Temple.Parser.TempleNamespaceVoid,
|
||||
Temple.Parser.Components,
|
||||
Temple.Parser.NonvoidElementsAliases,
|
||||
Temple.Parser.VoidElementsAliases,
|
||||
Temple.Parser.AnonymousFunctions,
|
||||
Temple.Parser.RightArrow,
|
||||
Temple.Parser.DoExpressions,
|
||||
Temple.Parser.Match,
|
||||
Temple.Parser.Default
|
||||
def parsers() do
|
||||
[
|
||||
Empty,
|
||||
Text,
|
||||
TempleNamespaceNonvoid,
|
||||
TempleNamespaceVoid,
|
||||
Components,
|
||||
Slot,
|
||||
NonvoidElementsAliases,
|
||||
VoidElementsAliases,
|
||||
AnonymousFunctions,
|
||||
RightArrow,
|
||||
DoExpressions,
|
||||
Match,
|
||||
Default
|
||||
]
|
||||
end
|
||||
|
||||
def parse({:__block__, _, asts}) do
|
||||
parse(asts)
|
||||
|
@ -113,6 +117,7 @@ defmodule Temple.Parser do
|
|||
{_, false} <- {TempleNamespaceNonvoid, TempleNamespaceNonvoid.applicable?(ast)},
|
||||
{_, false} <- {TempleNamespaceVoid, TempleNamespaceVoid.applicable?(ast)},
|
||||
{_, false} <- {Components, Components.applicable?(ast)},
|
||||
{_, false} <- {Slot, Slot.applicable?(ast)},
|
||||
{_, false} <- {NonvoidElementsAliases, NonvoidElementsAliases.applicable?(ast)},
|
||||
{_, false} <- {VoidElementsAliases, VoidElementsAliases.applicable?(ast)},
|
||||
{_, false} <- {AnonymousFunctions, AnonymousFunctions.applicable?(ast)},
|
||||
|
|
|
@ -2,7 +2,7 @@ defmodule Temple.Parser.Components do
|
|||
@moduledoc false
|
||||
@behaviour Temple.Parser
|
||||
|
||||
defstruct module: nil, assigns: [], children: []
|
||||
defstruct module: nil, assigns: [], children: [], slots: []
|
||||
|
||||
@impl Temple.Parser
|
||||
def applicable?({:c, _, _}) do
|
||||
|
@ -19,22 +19,68 @@ defmodule Temple.Parser.Components do
|
|||
|
||||
{do_and_else, assigns} = Temple.Parser.Utils.consolidate_blocks(do_and_else, args)
|
||||
|
||||
{default_slot, named_slots} =
|
||||
if children = do_and_else[:do] do
|
||||
Macro.postwalk(
|
||||
children,
|
||||
%{},
|
||||
fn
|
||||
{:slot, _, [name | args]}, named_slots ->
|
||||
{assigns, slot} = split_assigns_and_children(args, Macro.escape(%{}))
|
||||
|
||||
{nil, Map.put(named_slots, name, %{assigns: assigns, slot: slot})}
|
||||
|
||||
node, named_slots ->
|
||||
{node, named_slots}
|
||||
end
|
||||
)
|
||||
else
|
||||
{nil, %{}}
|
||||
end
|
||||
|
||||
children =
|
||||
if do_and_else[:do] == nil do
|
||||
if default_slot == nil do
|
||||
[]
|
||||
else
|
||||
Temple.Parser.parse(do_and_else[:do])
|
||||
Temple.Parser.parse(default_slot)
|
||||
end
|
||||
|
||||
slots =
|
||||
for {name, %{slot: slot, assigns: assigns}} <- named_slots do
|
||||
Temple.Ast.new(
|
||||
Temple.Parser.Slottable,
|
||||
name: name,
|
||||
content: Temple.Parser.parse(slot),
|
||||
assigns: assigns
|
||||
)
|
||||
end
|
||||
|
||||
Temple.Ast.new(__MODULE__,
|
||||
module: Macro.expand_once(component_module, __ENV__),
|
||||
assigns: assigns,
|
||||
slots: slots,
|
||||
children: children
|
||||
)
|
||||
end
|
||||
|
||||
defp split_assigns_and_children(args, empty) do
|
||||
case args do
|
||||
[assigns, [do: block]] ->
|
||||
{assigns, block}
|
||||
|
||||
[[do: block]] ->
|
||||
{empty, block}
|
||||
|
||||
[assigns] ->
|
||||
{assigns, nil}
|
||||
|
||||
_ ->
|
||||
{empty, nil}
|
||||
end
|
||||
end
|
||||
|
||||
defimpl Temple.Generator do
|
||||
def to_eex(%{module: module, assigns: assigns, children: []}) do
|
||||
def to_eex(%{module: module, assigns: assigns, children: [], slots: slots}) do
|
||||
[
|
||||
"<%= Phoenix.View.render",
|
||||
" ",
|
||||
|
@ -42,22 +88,45 @@ defmodule Temple.Parser.Components do
|
|||
", ",
|
||||
":self,",
|
||||
" ",
|
||||
"[{:__temple_slots__, %{",
|
||||
for slot <- slots do
|
||||
[
|
||||
to_string(slot.name),
|
||||
": ",
|
||||
"fn #{Macro.to_string(slot.assigns)} -> %>",
|
||||
for(child <- slot.content, do: Temple.Generator.to_eex(child)),
|
||||
"<% end, "
|
||||
]
|
||||
end,
|
||||
"}} | ",
|
||||
Macro.to_string(assigns),
|
||||
"]",
|
||||
" ",
|
||||
"%>"
|
||||
]
|
||||
end
|
||||
|
||||
def to_eex(%{module: module, assigns: assigns, children: children}) do
|
||||
def to_eex(%{module: module, assigns: assigns, children: children, slots: slots}) do
|
||||
[
|
||||
"<%= Phoenix.View.render_layout ",
|
||||
Macro.to_string(module),
|
||||
", ",
|
||||
":self",
|
||||
", ",
|
||||
Macro.to_string(assigns),
|
||||
":self,",
|
||||
" ",
|
||||
"do %>",
|
||||
"[{:__temple_slots__, %{",
|
||||
for slot <- slots do
|
||||
[
|
||||
to_string(slot.name),
|
||||
": ",
|
||||
"fn #{Macro.to_string(slot.assigns)} -> %>",
|
||||
for(child <- slot.content, do: Temple.Generator.to_eex(child)),
|
||||
"<% end, "
|
||||
]
|
||||
end,
|
||||
"}} | ",
|
||||
Macro.to_string(assigns),
|
||||
"]",
|
||||
" do %>",
|
||||
"\n",
|
||||
for(child <- children, do: Temple.Generator.to_eex(child)),
|
||||
"\n",
|
||||
|
|
30
lib/temple/parser/slot.ex
Normal file
30
lib/temple/parser/slot.ex
Normal file
|
@ -0,0 +1,30 @@
|
|||
defmodule Temple.Parser.Slot do
|
||||
@moduledoc false
|
||||
@behaviour Temple.Parser
|
||||
|
||||
defstruct name: nil, args: []
|
||||
|
||||
@impl true
|
||||
def applicable?({:slot, _, _}) do
|
||||
true
|
||||
end
|
||||
|
||||
def applicable?(_), do: false
|
||||
|
||||
@impl true
|
||||
def run({:slot, _, [slot_name | [args]]}) do
|
||||
Temple.Ast.new(__MODULE__, name: slot_name, args: args)
|
||||
end
|
||||
|
||||
defimpl Temple.Generator do
|
||||
def to_eex(%{name: name, args: args}) do
|
||||
[
|
||||
"<%= @__temple_slots__.",
|
||||
to_string(name),
|
||||
".(",
|
||||
Macro.to_string(quote(do: Enum.into(unquote(args), %{}))),
|
||||
") %>"
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
3
lib/temple/parser/slottable.ex
Normal file
3
lib/temple/parser/slottable.ex
Normal file
|
@ -0,0 +1,3 @@
|
|||
defmodule Temple.Parser.Slottable do
|
||||
defstruct content: nil, assigns: Macro.escape(%{}), name: nil
|
||||
end
|
|
@ -34,7 +34,7 @@ defmodule Temple.Parser.Utils do
|
|||
|
||||
def runtime_attrs(attrs) do
|
||||
{:safe,
|
||||
for {name, value} <- attrs, into: "" do
|
||||
for {name, value} <- attrs, name != :__temple_slots__, into: "" do
|
||||
name = snake_to_kebab(name)
|
||||
|
||||
" " <> name <> "=\"" <> to_string(value) <> "\""
|
||||
|
|
|
@ -3,7 +3,7 @@ defmodule Temple.ComponentTest do
|
|||
use Temple
|
||||
use Temple.Support.Utils
|
||||
|
||||
# `Phoenix.View.render_layout/4` is a phoenix function used for rendering partials that contain inner_content.
|
||||
# `Phoenix.View.render_layout/4` is a phoenix function used for rendering partials that contain inner_content.
|
||||
# These are usually layouts, but components that contain children are basically the same thing
|
||||
test "renders components using Phoenix.View.render_layout" do
|
||||
result =
|
||||
|
@ -20,7 +20,7 @@ defmodule Temple.ComponentTest do
|
|||
end
|
||||
|
||||
assert result ==
|
||||
~s{<div class="font-bold">Hello, world</div><%= Phoenix.View.render_layout Temple.Components.Component, :self, [] do %><aside class="foobar">I'm a component!</aside><% end %>}
|
||||
~s|<div class="font-bold">Hello, world</div><%= Phoenix.View.render_layout Temple.Components.Component, :self, [{:__temple_slots__, %{}} \| []] do %><aside class="foobar">I'm a component!</aside><% end %>|
|
||||
|
||||
assert evaluate_template(result) ==
|
||||
~s{<div class="font-bold">Hello, world</div><div><aside class="foobar">I'm a component!</aside></div>}
|
||||
|
@ -39,7 +39,7 @@ defmodule Temple.ComponentTest do
|
|||
end
|
||||
|
||||
assert result ==
|
||||
~s{<div class="font-bold">Hello, world</div><%= Phoenix.View.render_layout Temple.Components.Component2, :self, [class: "bg-red"] do %>I'm a component!<% end %>}
|
||||
~s|<div class="font-bold">Hello, world</div><%= Phoenix.View.render_layout Temple.Components.Component2, :self, [{:__temple_slots__, %{}} \| [class: "bg-red"]] do %>I'm a component!<% end %>|
|
||||
|
||||
assert evaluate_template(result) ==
|
||||
~s{<div class="font-bold">Hello, world</div><div class="bg-red">I'm a component!</div>}
|
||||
|
@ -60,7 +60,7 @@ defmodule Temple.ComponentTest do
|
|||
end
|
||||
|
||||
assert result ==
|
||||
~s{<div class="font-bold">Hello, world</div><% class = "bg-red" %><%= Phoenix.View.render_layout Temple.Components.Component2, :self, [class: class] do %>I'm a component!<% end %>}
|
||||
~s|<div class="font-bold">Hello, world</div><% class = "bg-red" %><%= Phoenix.View.render_layout Temple.Components.Component2, :self, [{:__temple_slots__, %{}} \| [class: class]] do %>I'm a component!<% end %>|
|
||||
end
|
||||
|
||||
test "function components can use other components" do
|
||||
|
@ -76,7 +76,7 @@ defmodule Temple.ComponentTest do
|
|||
end
|
||||
|
||||
assert result ==
|
||||
~s{<%= Phoenix.View.render_layout Temple.Components.Outer, :self, [] do %>outer!\n<% end %><%= Phoenix.View.render_layout Temple.Components.Inner, :self, [outer_id: "set by root inner"] do %>inner!\n<% end %>}
|
||||
~s|<%= Phoenix.View.render_layout Temple.Components.Outer, :self, [{:__temple_slots__, %{}} \| []] do %>outer!\n<% end %><%= Phoenix.View.render_layout Temple.Components.Inner, :self, [{:__temple_slots__, %{}} \| [outer_id: "set by root inner"]] do %>inner!\n<% end %>|
|
||||
|
||||
assert evaluate_template(result) == ~s"""
|
||||
<div id="inner" outer-id="from-outer">outer!</div>
|
||||
|
@ -105,7 +105,7 @@ defmodule Temple.ComponentTest do
|
|||
end
|
||||
|
||||
assert result ==
|
||||
~s{<%= Phoenix.View.render_layout Temple.Components.WithFuncs, :self, [foo: :bar] do %>doo doo<% end %>}
|
||||
~s|<%= Phoenix.View.render_layout Temple.Components.WithFuncs, :self, [{:__temple_slots__, %{}} \| [foo: :bar]] do %>doo doo<% end %>|
|
||||
|
||||
assert evaluate_template(result) == ~s{<div class="barbarbar">doo doo</div>}
|
||||
end
|
||||
|
@ -117,8 +117,33 @@ defmodule Temple.ComponentTest do
|
|||
end
|
||||
|
||||
assert result ==
|
||||
~s{<%= Phoenix.View.render Temple.Components.VoidComponent, :self, [foo: :bar] %>}
|
||||
~s|<%= Phoenix.View.render Temple.Components.VoidComponent, :self, [{:__temple_slots__, %{}} \| [foo: :bar]] %>|
|
||||
|
||||
assert evaluate_template(result) == ~s{<div class="void!!">bar</div>}
|
||||
end
|
||||
|
||||
test "components can have named slots" do
|
||||
assigns = %{name: "bob"}
|
||||
|
||||
result =
|
||||
temple do
|
||||
c Temple.Components.WithSlot do
|
||||
slot :header, %{value: val} do
|
||||
div do
|
||||
"the value is #{val}"
|
||||
end
|
||||
end
|
||||
|
||||
button class: "btn", phx_click: :toggle do
|
||||
@name
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
assert result ==
|
||||
~s|<%= Phoenix.View.render_layout Temple.Components.WithSlot, :self, [{:__temple_slots__, %{header: fn %{value: val} -> %>\n<div>\n<%= "the value is \#{val}" %>\n</div><% end, }} \| []] do %>\n<button class="btn" phx-click="toggle">\n<%= @name %>\n\n</button>\n<% end %>|
|
||||
|
||||
assert evaluate_template(result, assigns) ==
|
||||
~s{<div><div>the value is Header</div><div class="wrapped"><button class="btn" phx-click="toggle">bob</button></div></div>}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
defmodule Temple.Parser.ComponentsTest do
|
||||
use ExUnit.Case, async: false
|
||||
alias Temple.Parser.Components
|
||||
alias Temple.Parser.Slottable
|
||||
use Temple.Support.Utils
|
||||
|
||||
describe "applicable?/1" do
|
||||
|
@ -104,6 +105,32 @@ defmodule Temple.Parser.ComponentsTest do
|
|||
children: []
|
||||
} = ast
|
||||
end
|
||||
|
||||
test "gathers all slots" do
|
||||
raw_ast =
|
||||
quote do
|
||||
c SomeModule, foo: :bar do
|
||||
slot :foo, %{form: form} do
|
||||
"in the slot"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ast = Components.run(raw_ast)
|
||||
|
||||
assert %Components{
|
||||
module: SomeModule,
|
||||
assigns: [foo: :bar],
|
||||
slots: [
|
||||
%Slottable{
|
||||
name: :foo,
|
||||
content: [%Temple.Parser.Text{}],
|
||||
assigns: {:%{}, _, [form: _]}
|
||||
}
|
||||
],
|
||||
children: []
|
||||
} = ast
|
||||
end
|
||||
end
|
||||
|
||||
describe "Temple.Generator.to_eex/1" do
|
||||
|
@ -121,7 +148,53 @@ defmodule Temple.Parser.ComponentsTest do
|
|||
|> Temple.Generator.to_eex()
|
||||
|
||||
assert result |> :erlang.iolist_to_binary() ==
|
||||
~s|<%= Phoenix.View.render_layout SomeModule, :self, [foo: :bar] do %>\nI'm a component!\n<% end %>|
|
||||
~s|<%= Phoenix.View.render_layout SomeModule, :self, [{:__temple_slots__, %{}} \| [foo: :bar]] do %>\nI'm a component!\n<% end %>|
|
||||
end
|
||||
|
||||
test "emits eex for void component with slots" do
|
||||
raw_ast =
|
||||
quote do
|
||||
c SomeModule, foo: :bar do
|
||||
slot :foo, %{form: form} do
|
||||
div do
|
||||
"in the slot"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
result =
|
||||
raw_ast
|
||||
|> Components.run()
|
||||
|> Temple.Generator.to_eex()
|
||||
|
||||
assert result |> :erlang.iolist_to_binary() ==
|
||||
~s|<%= Phoenix.View.render SomeModule, :self, [{:__temple_slots__, %{foo: fn %{form: form} -> %><div>\nin the slot\n\n</div><% end, }} \| [foo: :bar]] %>|
|
||||
end
|
||||
|
||||
test "emits eex for nonvoid component with slots" do
|
||||
raw_ast =
|
||||
quote do
|
||||
c SomeModule, foo: :bar do
|
||||
slot :foo, %{form: form} do
|
||||
div do
|
||||
"in the slot"
|
||||
end
|
||||
end
|
||||
|
||||
div do
|
||||
"inner content"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
result =
|
||||
raw_ast
|
||||
|> Components.run()
|
||||
|> Temple.Generator.to_eex()
|
||||
|
||||
assert result |> :erlang.iolist_to_binary() ==
|
||||
~s|<%= Phoenix.View.render_layout SomeModule, :self, [{:__temple_slots__, %{foo: fn %{form: form} -> %><div>\nin the slot\n\n</div><% end, }} \| [foo: :bar]] do %>\n<div>\ninner content</div>\n<% end %>|
|
||||
end
|
||||
|
||||
test "emits eex for void component" do
|
||||
|
@ -136,7 +209,7 @@ defmodule Temple.Parser.ComponentsTest do
|
|||
|> Temple.Generator.to_eex()
|
||||
|
||||
assert result |> :erlang.iolist_to_binary() ==
|
||||
~s|<%= Phoenix.View.render SomeModule, :self, [foo: :bar] %>|
|
||||
~s|<%= Phoenix.View.render SomeModule, :self, [{:__temple_slots__, %{}} \| [foo: :bar]] %>|
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
48
test/parser/slot_test.exs
Normal file
48
test/parser/slot_test.exs
Normal file
|
@ -0,0 +1,48 @@
|
|||
defmodule Temple.Parser.SlotTest do
|
||||
use ExUnit.Case, async: false
|
||||
alias Temple.Parser.Slot
|
||||
|
||||
describe "applicable?/1" do
|
||||
test "runs when using the `c` ast with a block" do
|
||||
ast =
|
||||
quote do
|
||||
slot :header, value: "yolo"
|
||||
end
|
||||
|
||||
assert Slot.applicable?(ast)
|
||||
end
|
||||
end
|
||||
|
||||
describe "run/2" do
|
||||
test "adds a node to the buffer" do
|
||||
raw_ast =
|
||||
quote do
|
||||
slot :header, value: "yolo"
|
||||
end
|
||||
|
||||
ast = Slot.run(raw_ast)
|
||||
|
||||
assert %Slot{
|
||||
name: :header,
|
||||
args: [value: "yolo"]
|
||||
} == ast
|
||||
end
|
||||
end
|
||||
|
||||
describe "Temple.Generator.to_eex/1" do
|
||||
test "emits eex for a slot" do
|
||||
raw_ast =
|
||||
quote do
|
||||
slot :header, value: Form.form_for(changeset, action)
|
||||
end
|
||||
|
||||
result =
|
||||
raw_ast
|
||||
|> Slot.run()
|
||||
|> Temple.Generator.to_eex()
|
||||
|
||||
assert result |> :erlang.iolist_to_binary() ==
|
||||
~s|<%= @__temple_slots__.header.(Enum.into([value: Form.form_for(changeset, action)], %{})) %>|
|
||||
end
|
||||
end
|
||||
end
|
24
test/parser/utils_test.exs
Normal file
24
test/parser/utils_test.exs
Normal file
|
@ -0,0 +1,24 @@
|
|||
defmodule Temple.Parser.UtilsTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
alias Temple.Parser.Utils
|
||||
|
||||
describe "runtime_attrs/1" do
|
||||
test "compiles keyword lists and maps into html attributes" do
|
||||
attrs_map = %{
|
||||
class: "text-red",
|
||||
id: "form1",
|
||||
__temple_slots__: %{}
|
||||
}
|
||||
|
||||
attrs_kw = [
|
||||
class: "text-red",
|
||||
id: "form1",
|
||||
__temple_slots__: %{}
|
||||
]
|
||||
|
||||
assert {:safe, ~s| class="text-red" id="form1"|} == Utils.runtime_attrs(attrs_map)
|
||||
assert {:safe, ~s| class="text-red" id="form1"|} == Utils.runtime_attrs(attrs_kw)
|
||||
end
|
||||
end
|
||||
end
|
13
test/support/components/with_slot.ex
Normal file
13
test/support/components/with_slot.ex
Normal file
|
@ -0,0 +1,13 @@
|
|||
defmodule Temple.Components.WithSlot do
|
||||
use Temple.Component
|
||||
|
||||
render do
|
||||
div do
|
||||
slot :header, value: "Header"
|
||||
|
||||
div class: "wrapped" do
|
||||
@inner_content
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -20,10 +20,10 @@ defmodule Temple.Support.Utils do
|
|||
Kernel.=~(a, b)
|
||||
end
|
||||
|
||||
def evaluate_template(template) do
|
||||
def evaluate_template(template, assigns \\ %{}) do
|
||||
template
|
||||
|> EEx.compile_string(engine: Phoenix.HTML.Engine)
|
||||
|> Code.eval_quoted([])
|
||||
|> Code.eval_quoted(assigns: assigns)
|
||||
|> elem(0)
|
||||
|> Phoenix.HTML.safe_to_string()
|
||||
end
|
||||
|
|
Reference in a new issue