phoenixframework / phoenix_live_view

Rich, real-time user experiences with server-rendered HTML
https://hex.pm/packages/phoenix_live_view
MIT License
6.12k stars 917 forks source link

render_click/3 does not handle HTML entities #1549

Closed Schultzer closed 8 months ago

Schultzer commented 3 years ago

Environment

Actual behavior

Running the below example will fail on render_click/3:

     assert has_element?(view,
       "a[href='#{
         Routes.my_path(
           conn,
           :show,
           organization,
           some_id: nil,
           another_id: nil
         )
       }']",
       "My link"
     )

     view
     |> element(
       "a[href='#{
         Routes.my_path(
           conn,
           :show,
           organization,
           some_id: nil,
           another_id: nil
         )
       }']",
       "My link"
     )
     |> render_click()

With the following message:

** (exit) exited in: GenServer.call(#PID<0.893.0>, {:render_element, :find_element, {"phx-FpQvwQjxBxje_g2B", "render"}}, 30000)
         ** (EXIT) no process: the process is not alive or there's no process currently associated with the given name, possibly because its application isn't started

The failure is due to how render_click is not handling HTML entities, Routes.my_path(conn, :show, organization, some_id: nil, another_id: nil) will render something like: /orgs?some_id=&another_id=, where if we take a look at the rendered HTML from {:ok, view, html} = live(conn, path) then it's rendered as: /orgs?some_id=&amp;another_id=

Expected behavior

render_click/3 should have the same behavior as has_element? where: & === &amp;

SteffenDE commented 8 months ago

I cannot reproduce this:

Application.put_env(:phoenix, Example.Endpoint,
  http: [ip: {127, 0, 0, 1}, port: 5001],
  server: true,
  live_view: [signing_salt: "aaaaaaaa"],
  secret_key_base: String.duplicate("a", 64)
)

Mix.install([
  {:plug_cowboy, "~> 2.5"},
  {:jason, "~> 1.0"},
  {:phoenix, "~> 1.7.10", override: true},
  {:phoenix_live_view, "~> 0.20.3"},
  {:floki, ">= 0.30.0"}
])

ExUnit.start()

defmodule Example.ErrorView do
  def render(template, _), do: Phoenix.Controller.status_message_from_template(template)
end

defmodule Example.HomeLive do
  use Phoenix.LiveView, layout: {__MODULE__, :live}

  def mount(_params, _session, socket) do
    socket
    |> then(&{:ok, &1})
  end

  def render("live.html", assigns) do
    ~H"""
    <script src="https://cdn.jsdelivr.net/npm/phoenix@1.7.10/priv/static/phoenix.min.js"></script>
    <script src="https://cdn.jsdelivr.net/gh/phoenixframework/phoenix_live_view@0.20.3/priv/static/phoenix_live_view.js"></script>
    <script>
      let liveSocket = new window.LiveView.LiveSocket("/live", window.Phoenix.Socket, {
        hooks: {
          FakeHook: {
            mounted() {}
          }
        }
      })
      liveSocket.connect()
    </script>
    <style>
      * { font-size: 1.1em; }
    </style>
    <%= @inner_content %>
    """
  end

  def render(assigns) do
    ~H"""
    <a href="/orgs?some_id=&amp;another_id=">My link</a>
    """
  end
end

defmodule Example.Router do
  use Phoenix.Router
  import Phoenix.LiveView.Router

  pipeline :browser do
    plug(:accepts, ["html"])
  end

  scope "/", Example do
    pipe_through(:browser)

    live("/", HomeLive, :index)
  end
end

defmodule Example.Endpoint do
  use Phoenix.Endpoint, otp_app: :phoenix
  socket("/live", Phoenix.LiveView.Socket)
  plug(Example.Router)
end

defmodule Example.HomeLiveTest do
  use ExUnit.Case

  import Phoenix.ConnTest
  import Plug.Conn
  import Phoenix.LiveViewTest

  @endpoint Example.Endpoint

  test "works properly" do
    conn = Phoenix.ConnTest.build_conn()

    {:ok, view, html} = live(conn, "/")

    IO.inspect(html)

    assert has_element?(view,
       "a[href='/orgs?some_id=&another_id=']",
       "My link"
     )

    view
    |> element(
    "a[href='/orgs?some_id=&another_id=']",
      "My link"
    )
    |> render_click()
    |> IO.inspect()
  end
end

{:ok, _} = Supervisor.start_link([Example.Endpoint], strategy: :one_for_one)
ExUnit.run()
Process.sleep(:infinity)

Could you check if this still is an issue for you? The code that looks for the selector in render_click seems to be the same as the one has_element uses.

chrismccord commented 8 months ago

Let us know if you can provide more info. Thanks!