How do I pass an inner_block to Phoenix.LiveViewTest.render_component in a test?

Hello everyone!

Does anyone know the right way to pass an inner_block to Phoenix.LiveViewTest.render_component in a test?

Example:

assigns = []
render_component(ModalComponent, id: :modal, inner_block: ~H"""
      <button>Close modal</button>
      """)

In my ModalComponent I render the block with:

<div class="modal-content">
     <%= render_slot(@inner_block) %>
</div>

When I run the above, I get a:

     ** (KeyError) key :inner_block not found in: %Phoenix.LiveView.Rendered{dynamic: #Function<1.53563550/1 in DataIngestionWeb.ModalComponentTest."test the modal renders its inner block and default buttons"/1>, fingerprint: 264391584146384125242153930287832393521, root: true, static: ["<button>Close modal</button>"]}

Note that this is a stateful component, so using rendered_to_string isn’t an option.

I tried out a few workarounds:

  1. Using ~H"""<.live_component module={ModalComponent}>...</.live_component>""" . That gives me another error.
  2. Doing:
inner_block = fn _, _ ->
    assigns = []
      ~H"""
      <button>Close modal</button>
      """
    end
render_component(ModalComponent, id: :modal, inner_block: inner_block)

This works as long as I’m using the deprecated render_block (instead of render_slot) in my component. Not ideal.

I hope that somebody who’s more accustomed to LiveVIew’s internals than me can shed some light on this issue.

Thanks!

2 Likes

I had the same question as you and found your post Googling for solutions. Here’s what I came up with. I hope this helps. :slight_smile:

defmodule MyTest do
  use ExUnit.Case, async: true

  use Phoenix.Component
  import Phoenix.LiveViewTest

  defp my_component(assigns) do
    ~H"""
    <span><%= render_slot(@inner_block) %></span>
    """
  end

  test "foo" do
    assert "<span>hi there</span>" ==
             render_component(
               fn assigns -> ~H"<.my_component>hi <%= @some_key %></.my_component>" end,
               %{some_key: "there"}
             )
  end
end
3 Likes

Thanks for answering! I finally tried out your suggestion. My component is stateful, so I have to use live_component to render it, but your ideas is still applicable:

render_component(fn assigns ->
        ~H"""
        <.live_component module={MyComponent} id={:id}>static inner block</.live_component>
        """
end)

Or if I want to pass the inner block in a variable:

setup do
    assigns = []

    inner_block = ~H"""
     <button>Close modal</button>
    """

    %{inner_block: inner_block}
  end

  test "inner block as variable", %{inner_block: inner_block} do
    html =
      render_component(
        fn assigns ->
          ~H"""
          <.live_component module={MyComponent} id={:modal}><%= @inner_block %></.live_component>
          """
        end,
        %{inner_block: inner_block}
      )
  end

:+1: both work! Thanks again :slight_smile: