Skip to content

Commit d1250dc

Browse files
raise on duplicate ids in LiveViewTest (#3560)
* raise on duplicate ids in LiveViewTest When there are duplicate IDs in the DOM, morphdom will not be able to correctly patch the DOM. This commit tries to catch those cases early by walking the HTML tree in LiveViewTest and raising if there are any duplicate IDs. * Update lib/phoenix_live_view/test/dom.ex --------- Co-authored-by: Chris McCord <chris@chrismccord.com>
1 parent 97b3d20 commit d1250dc

4 files changed

Lines changed: 36 additions & 5 deletions

File tree

lib/phoenix_live_view/test/dom.ex

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,40 @@ defmodule Phoenix.LiveViewTest.DOM do
2525
]
2626
def parse(html) do
2727
{:ok, parsed} = Floki.parse_document(html)
28+
detect_duplicate_ids(parsed)
29+
2830
parsed
2931
end
3032

33+
defp detect_duplicate_ids(tree), do: detect_duplicate_ids(tree, MapSet.new())
34+
35+
defp detect_duplicate_ids([node | rest], ids) do
36+
ids = detect_duplicate_ids(node, ids)
37+
detect_duplicate_ids(rest, ids)
38+
end
39+
40+
defp detect_duplicate_ids({_tag_name, _attrs, children} = node, ids) do
41+
case Floki.attribute(node, "id") do
42+
[id] ->
43+
if MapSet.member?(ids, id) do
44+
raise """
45+
Duplicate id found: #{id}
46+
47+
LiveView requires that all elements have unique ids, duplicate IDs will cause
48+
undefined behavior at runtime, as DOM patching will not be able to target the correct
49+
elements.
50+
"""
51+
else
52+
detect_duplicate_ids(children, MapSet.put(ids, id))
53+
end
54+
55+
_ ->
56+
detect_duplicate_ids(children, ids)
57+
end
58+
end
59+
60+
defp detect_duplicate_ids(_non_tag, seen_ids), do: seen_ids
61+
3162
def all(html_tree, selector), do: Floki.find(html_tree, selector)
3263

3364
def maybe_one(html_tree, selector, type \\ :selector) do

test/phoenix_live_view/integrations/event_test.exs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ defmodule Phoenix.LiveView.EventTest do
184184
{:ok, view, _html} = live(conn, "/events-multi-js-in-component")
185185

186186
html =
187-
element(view, "#child_1 #push-to-self")
187+
element(view, "#push-to-self-child_1")
188188
|> render_click()
189189

190190
assert html =~ "child_1 count: 11"
@@ -195,7 +195,7 @@ defmodule Phoenix.LiveView.EventTest do
195195
{:ok, view, _html} = live(conn, "/events-multi-js-in-component")
196196

197197
html =
198-
element(view, "#child_1 #push-to-other-targets")
198+
element(view, "#push-to-other-targets-child_1")
199199
|> render_click()
200200

201201
assert html =~ "child_1 count: 1"

test/phoenix_live_view/integrations/nested_test.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ defmodule Phoenix.LiveView.NestedTest do
147147
:ok = GenServer.call(view.pid, {:dynamic_child, :static})
148148

149149
assert Exception.format(:exit, catch_exit(render(view))) =~
150-
"expected selector \"#static\" to return a single element, but got 2"
150+
"Duplicate id found: static"
151151
end
152152

153153
describe "navigation helpers" do

test/support/live_views/events.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ defmodule Phoenix.LiveViewTest.Support.EventsInComponentMultiJSLive do
9494
~H"""
9595
<div id={@id}>
9696
<button
97-
id="push-to-self"
97+
id={"push-to-self-#{@id}"}
9898
phx-click={
9999
JS.push("inc", target: "#child_1", value: %{inc: 1})
100100
|> JS.push("inc", target: "#child_1", value: %{inc: 10})
@@ -104,7 +104,7 @@ defmodule Phoenix.LiveViewTest.Support.EventsInComponentMultiJSLive do
104104
</button>
105105
106106
<button
107-
id="push-to-other-targets"
107+
id={"push-to-other-targets-#{@id}"}
108108
phx-click={
109109
JS.push("inc", target: "#child_2", value: %{inc: 2})
110110
|> JS.push("inc", target: "#child_1", value: %{inc: 1})

0 commit comments

Comments
 (0)