Skip to content

Commit 26bb662

Browse files
authored
root tracking for macro components (#3882)
* root tracking for macro components Closes #3877. * prune text after empty macro component * remove prune_text_after_slot
1 parent eb161bb commit 26bb662

File tree

2 files changed

+69
-41
lines changed

2 files changed

+69
-41
lines changed

lib/phoenix_live_view/tag_engine.ex

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -363,10 +363,13 @@ defmodule Phoenix.LiveView.TagEngine do
363363
%{state | slots: [[slot | slots] | other_slots]}
364364
end
365365

366-
defp prune_text_after_slot([{:text, text, meta} | tokens]),
366+
defp maybe_prune_text_after_macro_component("", tokens), do: prune_text(tokens)
367+
defp maybe_prune_text_after_macro_component(_ast, tokens), do: tokens
368+
369+
defp prune_text([{:text, text, meta} | tokens]),
367370
do: [{:text, String.trim_leading(text), meta} | tokens]
368371

369-
defp prune_text_after_slot(tokens),
372+
defp prune_text(tokens),
370373
do: tokens
371374

372375
defp validate_slot!(%{tags: [{type, _, _, _} | _]}, _name, _tag_meta)
@@ -424,6 +427,11 @@ defmodule Phoenix.LiveView.TagEngine do
424427
%{state | tags: [token | state.tags]}
425428
end
426429

430+
# special clause for macro components
431+
defp pop_tag!(%{tags: [{:macro_tag, name} | tags]} = state, {:close, :macro_tag, name}) do
432+
%{state | tags: tags}
433+
end
434+
427435
defp pop_tag!(
428436
%{tags: [{type, tag_name, _attrs, _meta} = tag | tags]} = state,
429437
{:close, type, tag_name, _}
@@ -623,7 +631,7 @@ defmodule Phoenix.LiveView.TagEngine do
623631
assigns = wrap_special_slot(special, merge_component_attrs(roots, attrs, line))
624632

625633
add_slot(state, slot_name, assigns, attr_info, tag_meta, special)
626-
|> continue(prune_text_after_slot(tokens))
634+
|> continue(prune_text(tokens))
627635
end
628636

629637
# Slot (with inner content)
@@ -659,7 +667,7 @@ defmodule Phoenix.LiveView.TagEngine do
659667
state
660668
|> add_slot(slot_name, assigns, inner, tag_meta, special)
661669
|> pop_substate_from_stack()
662-
|> continue(prune_text_after_slot(tokens))
670+
|> continue(prune_text(tokens))
663671
end
664672

665673
# Local function component (self close)
@@ -859,11 +867,6 @@ defmodule Phoenix.LiveView.TagEngine do
859867

860868
module = validate_module!(module_string, tag_meta, state)
861869

862-
# we do not perform root tracking on macro components
863-
# but we call set_root_on_not_tag since a macro component could be
864-
# just some text without any tags
865-
state = set_root_on_not_tag(state)
866-
867870
try do
868871
{ast, rest} =
869872
case Phoenix.Component.MacroComponent.build_ast(tokens, state.caller) do
@@ -883,14 +886,14 @@ defmodule Phoenix.LiveView.TagEngine do
883886
{{:ok, new_ast}, rest} ->
884887
state
885888
|> handle_ast(new_ast, tag_meta)
886-
|> continue(rest)
889+
|> continue(maybe_prune_text_after_macro_component(new_ast, rest))
887890

888891
{{:ok, new_ast, data}, rest} ->
889892
Module.put_attribute(state.caller.module, :__macro_components__, {module, data})
890893

891894
state
892895
|> handle_ast(new_ast, tag_meta)
893-
|> continue(rest)
896+
|> continue(maybe_prune_text_after_macro_component(new_ast, rest))
894897

895898
{other, _rest} ->
896899
raise ArgumentError,
@@ -907,22 +910,30 @@ defmodule Phoenix.LiveView.TagEngine do
907910
end
908911

909912
state
913+
|> set_root_on_tag()
910914
|> update_subengine(:handle_text, [[], "<#{tag}"])
911915
|> handle_ast_attrs(attrs, tag_open_meta)
912916
|> update_subengine(:handle_text, [[], suffix])
913917
end
914918

915919
defp handle_ast(state, {tag, attrs, children, _meta}, tag_open_meta) do
916920
state
921+
|> set_root_on_tag()
922+
|> push_tag({:macro_tag, tag})
917923
|> update_subengine(:handle_text, [[], "<#{tag}"])
918924
|> handle_ast_attrs(attrs, tag_open_meta)
919925
|> update_subengine(:handle_text, [[], ">"])
920926
|> handle_ast(children, tag_open_meta)
921927
|> update_subengine(:handle_text, [[], "</#{tag}>"])
928+
|> pop_tag!({:close, :macro_tag, tag})
922929
end
923930

931+
defp handle_ast(state, "", _tag_open_meta), do: state
932+
924933
defp handle_ast(state, text, _tag_open_meta) when is_binary(text) do
925-
update_subengine(state, :handle_text, [[], text])
934+
state
935+
|> set_root_on_not_tag()
936+
|> update_subengine(:handle_text, [[], text])
926937
end
927938

928939
defp handle_ast(state, children, tag_open_meta) when is_list(children) do

test/phoenix_component/macro_component_integration_test.exs

Lines changed: 46 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -311,35 +311,52 @@ defmodule Phoenix.Component.MacroComponentIntegrationTest do
311311
assert Enum.find(data, fn %{opts: opts} -> opts == %{"id" => "2"} end)
312312
end
313313

314-
describe "root tracking" do
315-
@endpoint Phoenix.LiveViewTest.Support.Endpoint
316-
317-
test "does not count as root" do
318-
defmodule TestLVDoesNotCountAsRoot do
319-
use Phoenix.LiveView
320-
321-
defmodule LC do
322-
use Phoenix.LiveComponent
323-
324-
def render(assigns) do
325-
~H"""
326-
<div :type={MyComponent}></div>
327-
"""
328-
end
329-
end
330-
331-
def render(assigns) do
332-
~H"""
333-
<.live_component module={LC} id="my-lc" />
334-
"""
335-
end
336-
end
314+
test "root tracking" do
315+
assert eval_heex("<div :type={MyComponent}>Test</div>").root
337316

338-
assert_raise ArgumentError,
339-
~r/Stateful components must have a single static HTML tag at the root/,
340-
fn ->
341-
live_isolated(Phoenix.ConnTest.build_conn(), TestLVDoesNotCountAsRoot)
342-
end
343-
end
317+
refute eval_heex("""
318+
<div :type={MyComponent}>Test</div>
319+
<span>Another</span>
320+
""").root
321+
322+
Process.put(
323+
:new_ast,
324+
{:div, [{"id", "1"}],
325+
[
326+
{"span", [{"class", "\"foo\""}], ["Test"], %{}},
327+
{"span", [{"class", "'foo'"}], ["Test"], %{}}
328+
], %{}}
329+
)
330+
331+
assert eval_heex("<div :type={MyComponent}>Test</div>").root
332+
333+
Process.put(:new_ast, "")
334+
335+
assert eval_heex("""
336+
<div :type={MyComponent}>Test</div><span>Another</span>
337+
""").root
338+
339+
Process.put(:new_ast, "some text")
340+
341+
refute eval_heex("""
342+
<div :type={MyComponent}>Test</div>
343+
<span>Another</span>
344+
""").root
345+
end
346+
347+
defp eval_heex(source) do
348+
require Phoenix.Component
349+
350+
EEx.compile_string(source,
351+
engine: Phoenix.LiveView.TagEngine,
352+
line: 1,
353+
file: __ENV__.file,
354+
trim: true,
355+
caller: __ENV__,
356+
source: source,
357+
tag_handler: Phoenix.LiveView.HTMLEngine
358+
)
359+
|> Code.eval_quoted(assigns: %{})
360+
|> elem(0)
344361
end
345362
end

0 commit comments

Comments
 (0)