Enumerable not implemented when testing with render_component

Hey, been having very fun with LiveView and the new Components!

So we are trying to find a structure where we can generate forms in our liveviews with compile-time variable checks and we found a way that is working well except when writing the unit tests on the render functions with render_component.

These input components basically look like this:

defmodule TextInput do
  use AppWeb, :component

  @enforce_keys [:label, :field]
  defstruct [:label, :field, :f]

  def render(%TextInput{} = assigns) do
      <%= label @f, @label %>
      <%= text_input @f, @field %>
      <%= error_tag @f, @field %>

In the liveview, we assign the fields of a form like this

fields: [
    label: "Name",
    field: :name
    label: "Translation Key",
    field: :translation_key

In the heex we render them using a Fields module like this (inside a .form div)

<%= for field <- @fields do %>
  <%= Fields.render(field, f) %>
<% end %>

Which are pattern-matched like this (so it picks up diferent types of input fields and renders them using the right struct-backed component)

def render(%TextInput{} = assigns, f) do
  |> Map.put(:f, f)
  |> TextInput.render()

This works very well until we want to test these components with render_component
The following test code gives a “Enumerable not implemented for type %TextInput{}”, which can be temporarily fixed by implementing the Enumerable with its functions, but we are looking for a better solution to all this.

test "render a text input on good TextInput params" do
  changeset = Person.changeset(%Person{}, %{name: "name"})

  params = %TextInput{
    label: "test",
    field: :name,
    f: Phoenix.HTML.Form.form_for(changeset, nil)

  result = render_component(&TextInput.render/1, params)
  assert result =~ "test"

Any guidelines are highly appreciated, thanks for taking your time! :slight_smile:

1 Like

Have you tried using a map or keyword list for params (instead of %TextInput{})?

Also check out the good vs bad examples at the bottom of Phoenix.LiveComponent - Cost of stateful components

Yeah, but unfortunately it tries to pattern match render with TextInput still, so that doesn’t work (on purpose cause of struct based design)