Testing LiveView that uses `send_update`

Hello,

I’m developing an application using LiveView and as a way to update
the status of inner LiveComponents I started using send_update the
same way it was pioneered by surface here:
http://surface-demo.msaraiva.io/state_management but without the
“reactivesque” bits.

Now i want to test the components (together with one of the views that
uses them) but I’m facing some issues because of how the send_update
works. For example, when I delete an item from a list I want to show a
confirmation dialog to the user, which means really to switch the CSS
class of a “dialog” HTML fragment. Here is how the the Dialog
component is used in the parent LiveView:

<%= live_component @socket, Journey.Manage.Components.Dialog,
    id: "delete-confirm" do %>
  <%= gettext("Are you sure you want to delete this item?") %>
<% end %>

then, when the delete button is clicked, the event event handler on
parent view shows the dialog like this:

def handle_event("item-delete-pre", %{"id" => rec_id},
  %{assigns: %{live_action: :index}} = socket) do
  Dialog.show("delete-confirm",
    actions: [yes: gettext("yes"), no: gettext("no")])
  {:noreply, assign(socket, ask_confirm_id: rec_id)}
end

Then the execution of the Dialog.show() functions runs a send_update()
that internally uses a send() to do the perform the update
asynchronously.

And this asynchronicity is the source of the problem… i think. The
test creates the view and sends the event to the main LiveView:

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

assert html =~ ~r/<div class="modal.*hidden"/
assert render_click(view, "item-delete-pre",
  %{"id" => "A"}) =~ ~r/<div class="modal.*shown"/

… but the check fails because the update is not there yet!

Any idea of something (a workaround) to make it testable? Or am I not
seeing a solution that is there already?

1 Like

Calling render after your click should be all you need :slight_smile:

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

assert html =~ ~r/<div class="modal.*hidden"/
render_click(view, "item-delete-pre", %{"id" => "A"})
assert render(view) =~ ~r/<div class="modal.*shown"/
6 Likes

Indeed it works, Thanks Chris

This might be a similar known-issue to Redirect in upload progress callback not working. · Issue #1620 · phoenixframework/phoenix_live_view · GitHub but in my tests, this works perfectly EXCEPT if I’m redirecting. I have to call render so that my update fires, but the render function crashes. I can actually assert all the correct behavior I need by doing this:

      try do
        render(index_live)
      rescue
        _ ->
          "https://github.com/phoenixframework/phoenix_live_view/issues/1620"
      end

The crash is:

** (FunctionClauseError) no function clause matching in Floki.RawHTML.build_raw_html/4

     The following arguments were given to Floki.RawHTML.build_raw_html/4:
     
         # 1
         [error: {:redirect, %{to: "https://example.com"}}]
     
         # 2
         []
     
         # 3
         &Floki.Entities.encode/1
     
         # 4
         :noop
     
     Attempted function clauses (showing 8 out of 8):
     
         defp build_raw_html([], html, _encoder, _padding)
         defp build_raw_html(string, _html, encoder, padding) when is_binary(string)
         defp build_raw_html(tuple, html, encoder, padding) when is_tuple(tuple)
         defp build_raw_html([string | tail], html, encoder, padding) when is_binary(string)
         defp build_raw_html([{:comment, comment} | tail], html, encoder, padding)
         defp build_raw_html([{:pi, tag, attrs} | tail], html, encoder, padding)
         defp build_raw_html([{:doctype, type, public, system} | tail], html, encoder, padding)
         defp build_raw_html([{type, attrs, children} | tail], html, encoder, padding)
     
     code: render(index_live)
     stacktrace:
       (floki 0.34.2) lib/floki/raw_html.ex:53: Floki.RawHTML.build_raw_html/4
       (floki 0.34.2) lib/floki/raw_html.ex:50: Floki.RawHTML.raw_html/2

As for my use-case I don’t have to to interact with the page I’m redirecting to (it’s actually an external site) so not blocked at all, but found this interesting and worth sharing (?).

Thanks, as always, for all you do!