Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test duplicate id / component errors
Browse files Browse the repository at this point in the history
SteffenDE committed Jan 26, 2025

Verified

This commit was signed with the committer’s verified signature.
SteffenDE Steffen Deusch
1 parent ee3218e commit b138418
Showing 6 changed files with 334 additions and 12 deletions.
14 changes: 7 additions & 7 deletions lib/phoenix_live_view/test/client_proxy.ex
Original file line number Diff line number Diff line change
@@ -23,7 +23,7 @@ defmodule Phoenix.LiveViewTest.ClientProxy do
uri: nil,
connect_params: %{},
connect_info: %{},
handle_errors: :raise
on_error: :raise

alias Plug.Conn.Query
alias Phoenix.LiveViewTest.{ClientProxy, DOM, Element, View, Upload}
@@ -84,7 +84,7 @@ defmodule Phoenix.LiveViewTest.ClientProxy do
session: session,
url: url,
test_supervisor: test_supervisor,
handle_errors: handle_errors
on_error: on_error
} = opts

# We can assume there is at least one LiveView
@@ -114,9 +114,9 @@ defmodule Phoenix.LiveViewTest.ClientProxy do
uri: URI.parse(url),
child_statics: Map.delete(DOM.find_static_views(root_html), id),
topic: "lv:#{id}",
# we store handle_errors in the view ClientProxy struct as well
# we store on_error in the view ClientProxy struct as well
# to pass it when live_redirecting
handle_errors: handle_errors
on_error: on_error
}

# We build an absolute path to any relative
@@ -149,7 +149,7 @@ defmodule Phoenix.LiveViewTest.ClientProxy do
test_supervisor: test_supervisor,
url: url,
page_title: :unset,
handle_errors: handle_errors
on_error: on_error
}

try do
@@ -481,12 +481,12 @@ defmodule Phoenix.LiveViewTest.ClientProxy do
end

def handle_info({:test_error, error}, state) do
case state.handle_errors do
case state.on_error do
:raise ->
raise """
#{String.trim(error)}
You can prevent this from raising by passing `handle_errors: :warn` to
You can prevent this from raising by passing `on_error: :warn` to
`Phoenix.LiveViewTest.live/3` or `Phoenix.LiveViewTest.live_isolated/3`.
"""

10 changes: 5 additions & 5 deletions lib/phoenix_live_view/test/live_view_test.ex
Original file line number Diff line number Diff line change
@@ -205,7 +205,7 @@ defmodule Phoenix.LiveViewTest do
## Options
* `:handle_errors` - Can be either `:raise` or `:warn` to control whether
* `:on_error` - Can be either `:raise` or `:warn` to control whether
detected errors like duplicate IDs or live components fail the test or just log
a warning. Defaults to `:raise`.
@@ -244,7 +244,7 @@ defmodule Phoenix.LiveViewTest do
## Options
* `:session` - the session to be given to the LiveView
* `:handle_errors` - Can be either `:raise` or `:warn` to control whether
* `:on_error` - Can be either `:raise` or `:warn` to control whether
detected errors like duplicate IDs or live components fail the test or just log
a warning. Defaults to `:raise`.
@@ -347,7 +347,7 @@ defmodule Phoenix.LiveViewTest do
endpoint: Phoenix.Controller.endpoint_module(conn),
session: maybe_get_session(conn),
url: Plug.Conn.request_url(conn),
handle_errors: opts[:handle_errors] || :raise
on_error: opts[:on_error] || :raise
})
end

@@ -394,7 +394,7 @@ defmodule Phoenix.LiveViewTest do
session: opts.session,
url: opts.url,
test_supervisor: fetch_test_supervisor!(),
handle_errors: opts.handle_errors
on_error: opts.on_error
})

case ClientProxy.start_link(opts) do
@@ -1815,7 +1815,7 @@ defmodule Phoenix.LiveViewTest do
router: root.router,
session: session,
url: url,
handle_errors: root.handle_errors
on_error: root.on_error
})
end

115 changes: 115 additions & 0 deletions test/phoenix_live_view/integrations/live_view_test.exs
Original file line number Diff line number Diff line change
@@ -151,6 +151,56 @@ defmodule Phoenix.LiveView.LiveViewTest do
{:ok, _view, html} = live(conn, "/classlist")
assert html =~ ~s|class="foo bar"|
end

test "raises for duplicate ids by default", %{conn: conn} do
Process.flag(:trap_exit, true)

{:ok, view, _html} = live(conn, "/duplicate-id")
{{exception, _}, _} = catch_exit(render(view))
assert Exception.message(exception) =~ "Duplicate id found while testing LiveView: a"
assert_receive {:EXIT, _, _}
end

test "raises for duplicate ids when on_error: :raise", %{conn: conn} do
Process.flag(:trap_exit, true)

{:ok, view, _html} = live(conn, "/duplicate-id", on_error: :raise)
{{exception, _}, _} = catch_exit(render(view))
assert Exception.message(exception) =~ "Duplicate id found while testing LiveView: a"
assert_receive {:EXIT, _, _}
end

test "raises for duplicate components by default", %{conn: conn} do
Process.flag(:trap_exit, true)

{:ok, view, _html} = live(conn, "/dynamic-duplicate-component", on_error: :raise)
view |> element("button", "Toggle duplicate LC") |> render_click() =~ "I am LiveComponent2"

{{exception, _}, _} = catch_exit(render(view))

message = Exception.message(exception)
assert message =~ "Duplicate live component found while testing LiveView:"
assert message =~ "I am LiveComponent2"
refute message =~ "I am a LC inside nested LV"

assert_receive {:EXIT, _, _}
end

test "raises for duplicate components when on_error: :raise", %{conn: conn} do
Process.flag(:trap_exit, true)

{:ok, view, _html} = live(conn, "/dynamic-duplicate-component", on_error: :raise)
view |> element("button", "Toggle duplicate LC") |> render_click() =~ "I am LiveComponent2"

{{exception, _}, _} = catch_exit(render(view))

message = Exception.message(exception)
assert message =~ "Duplicate live component found while testing LiveView:"
assert message =~ "I am LiveComponent2"
refute message =~ "I am a LC inside nested LV"

assert_receive {:EXIT, _, _}
end
end

describe "render_*" do
@@ -388,6 +438,71 @@ defmodule Phoenix.LiveView.LiveViewTest do
)
end
end

test "raises for duplicate ids by default" do
Process.flag(:trap_exit, true)

{:ok, view, _html} =
live_isolated(Phoenix.ConnTest.build_conn(), Phoenix.LiveViewTest.Support.DuplicateIdLive)

# errors are detected asynchronously, so we need to render again for the message to be processed
{{exception, _}, _} = catch_exit(render(view))
assert Exception.message(exception) =~ "Duplicate id found while testing LiveView: a"
assert_receive {:EXIT, _, _}
end

test "raises for duplicate ids when on_error: raise" do
Process.flag(:trap_exit, true)

{:ok, view, _html} =
live_isolated(Phoenix.ConnTest.build_conn(), Phoenix.LiveViewTest.Support.DuplicateIdLive,
on_error: :raise
)

{{exception, _}, _} = catch_exit(render(view))
assert Exception.message(exception) =~ "Duplicate id found while testing LiveView: a"
assert_receive {:EXIT, _, _}
end

test "raises for duplicate components by default" do
Process.flag(:trap_exit, true)

{:ok, view, _html} =
live_isolated(
Phoenix.ConnTest.build_conn(),
Phoenix.LiveViewTest.Support.DynamicDuplicateComponentLive
)

view |> element("button", "Toggle duplicate LC") |> render_click() =~ "I am LiveComponent2"

# errors are detected asynchronously, so we need to render again for the message to be processed
{{exception, _}, _} = catch_exit(render(view))

assert Exception.message(exception) =~
"Duplicate live component found while testing LiveView:"

assert_receive {:EXIT, _, _}
end

test "raises for duplicate components when on_error: raise" do
Process.flag(:trap_exit, true)

{:ok, view, _html} =
live_isolated(
Phoenix.ConnTest.build_conn(),
Phoenix.LiveViewTest.Support.DynamicDuplicateComponentLive,
on_error: :raise
)

view |> element("button", "Toggle duplicate LC") |> render_click() =~ "I am LiveComponent2"

{{exception, _}, _} = catch_exit(render(view))

assert Exception.message(exception) =~
"Duplicate live component found while testing LiveView:"

assert_receive {:EXIT, _, _}
end
end

describe "format_status/2" do
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
defmodule Phoenix.LiveView.LiveViewTestWarningsTest do
use ExUnit.Case, async: false

import ExUnit.CaptureIO

import Phoenix.ConnTest
import Phoenix.LiveViewTest

alias Phoenix.LiveViewTest.Support.Endpoint

@endpoint Endpoint

describe "live" do
test "warns for duplicate ids when on_error: warn" do
conn = Plug.Test.init_test_session(Phoenix.ConnTest.build_conn(), %{})
conn = get(conn, "/duplicate-id")

Process.flag(:trap_exit, true)

assert capture_io(:stderr, fn ->
{:ok, view, _html} = live(conn, nil, on_error: :warn)
render(view)
end) =~
"Duplicate id found while testing LiveView: a"

refute_receive {:EXIT, _, _}
end

test "warns for duplicate component when on_error: warn" do
conn = Plug.Test.init_test_session(Phoenix.ConnTest.build_conn(), %{})
conn = get(conn, "/dynamic-duplicate-component")

Process.flag(:trap_exit, true)

warning =
capture_io(:stderr, fn ->
{:ok, view, _html} = live(conn, nil, on_error: :warn)

view |> element("button", "Toggle duplicate LC") |> render_click() =~
"I am LiveComponent2"

render(view)
end)

assert warning =~ "Duplicate live component found while testing LiveView:"
assert warning =~ "I am LiveComponent2"
refute warning =~ "I am a LC inside nested LV"

refute_receive {:EXIT, _, _}
end
end

describe "live_isolated" do
test "warns for duplicate ids when on_error: warn" do
Process.flag(:trap_exit, true)

assert capture_io(:stderr, fn ->
{:ok, view, _html} =
live_isolated(
Phoenix.ConnTest.build_conn(),
Phoenix.LiveViewTest.Support.DuplicateIdLive,
on_error: :warn
)

render(view)
end) =~
"Duplicate id found while testing LiveView: a"

refute_receive {:EXIT, _, _}
end

test "warns for duplicate component when on_error: warn" do
Process.flag(:trap_exit, true)

warning =
capture_io(:stderr, fn ->
{:ok, view, _html} =
live_isolated(
Phoenix.ConnTest.build_conn(),
Phoenix.LiveViewTest.Support.DynamicDuplicateComponentLive,
on_error: :warn
)

view |> element("button", "Toggle duplicate LC") |> render_click() =~
"I am LiveComponent2"

render(view)
end)

assert warning =~ "Duplicate live component found while testing LiveView:"
assert warning =~ "I am LiveComponent2"
refute warning =~ "I am a LC inside nested LV"

refute_receive {:EXIT, _, _}
end
end
end
107 changes: 107 additions & 0 deletions test/support/live_views/duplicates.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
defmodule Phoenix.LiveViewTest.Support.DuplicateIdLive do
use Phoenix.LiveView

def render(assigns) do
~H"""
<div id="a">
<div id="b">
<div id="a" />
</div>
</div>
"""
end
end

defmodule Phoenix.LiveViewTest.Support.DynamicDuplicateComponentLive do
use Phoenix.LiveView

defmodule LiveComponent do
use Phoenix.LiveComponent

def mount(socket) do
{:ok, socket}
end

def render(assigns) do
~H"""
<div>
<.live_component
:if={@render_child}
module={Phoenix.LiveViewTest.Support.DynamicDuplicateComponentLive.LiveComponent2}
id="duplicate"
/> Other content of LiveComponent {@id}
</div>
"""
end
end

defmodule LiveComponent2 do
use Phoenix.LiveComponent

def render(assigns) do
~H"""
<div>I am LiveComponent2</div>
"""
end
end

defmodule NestedLive do
use Phoenix.LiveView

def render(assigns) do
~H"""
<.live_component
module={Phoenix.LiveViewTest.Support.DynamicDuplicateComponentLive.LiveComponent3}
id="inside-nested"
/>
"""
end
end

defmodule LiveComponent3 do
use Phoenix.LiveComponent

def render(assigns) do
~H"""
<div>I am a LC inside nested LV</div>
"""
end
end

def mount(_params, _session, socket) do
{:ok, assign(socket, render_first: true, render_second: true, render_duplicate: false)}
end

def render(assigns) do
~H"""
<.live_component
id="First"
module={Phoenix.LiveViewTest.Support.DynamicDuplicateComponentLive.LiveComponent}
render_child={true}
/>
<.live_component
id="Second"
module={Phoenix.LiveViewTest.Support.DynamicDuplicateComponentLive.LiveComponent}
render_child={@render_duplicate}
/>
{live_render(@socket, Phoenix.LiveViewTest.Support.DynamicDuplicateComponentLive.NestedLive,
id: "nested"
)}
<button phx-click="toggle_duplicate">Toggle duplicate LC</button>
"""
end

def handle_event("toggle_duplicate", _, socket) do
{:noreply, assign(socket, :render_duplicate, !socket.assigns.render_duplicate)}
end

def handle_event("toggle_first", _, socket) do
{:noreply, assign(socket, :render_first, !socket.assigns.render_first)}
end

def handle_event("toggle_second", _, socket) do
{:noreply, assign(socket, :render_second, !socket.assigns.render_second)}
end
end
3 changes: 3 additions & 0 deletions test/support/router.ex
Original file line number Diff line number Diff line change
@@ -49,6 +49,9 @@ defmodule Phoenix.LiveViewTest.Support.Router do

live "/expensive-runtime-checks", ExpensiveRuntimeChecksLive

live "/duplicate-id", DuplicateIdLive
live "/dynamic-duplicate-component", DynamicDuplicateComponentLive

# controller test
get "/controller/:type", Controller, :incoming
get "/widget", Controller, :widget

0 comments on commit b138418

Please sign in to comment.