Skip to content

Test setup | Passing ActionView::Helpers::FormBuilder to component #311

@phylor

Description

@phylor

I have a component receiving a Rails form builder (e.g. created by form_for) as an argument. When trying to write tests for it, I can't manage to receive the complete output generated by calling methods on the form builder.

The component seems to only capture method calls directly wrapped within another element (e.g. a span { .. }).

Here's a test to reproduce it:

# frozen_string_literal: true

test "Rails form builder" do
  component = Class.new(Phlex::HTML) do
    attr_reader :form

    def initialize(form)
      @form = form
    end

    define_method :view_template do
      span do
        form.text_field :name
      end
      form.button "Submit"

      form.label :age do |label|
        strong { label.translation }
      end

      form.fields_for :a do |fields|
        fields.text_field :b
      end

      form.fields :c do |fields|
        fields.text_field :d
      end
    end
  end

  Cat = Data.define(:name, :age, :a, :c)

  form_builder = ActionView::Helpers::FormBuilder.new(
    :cat,
    Cat.new(name: "Ella", age: 3, a: nil, c: nil),
    controller.view_context,
    {}
  )

  output = render(component.new(form_builder))

  assert_equivalent_html output, <<~HTML
    <input type="text" name="name" value="Ella">
    <button name="button" type="submit">Submit</button>
    <label>
      <strong>Age</strong>
    </label>
    <input type="text" name="[a][b]" id="_a_b">
    <input type="text" name="[c][d]">
  HTML
end

This is the test output:

Actual                                                                Expected
1 <span>                                                              .
2   <input type="text" value="Ella" name="cat[name]" id="cat_name">   1 <input type="text" name="name" value="Ella">
.                                                                     2 <button name="button" type="submit">
.                                                                     3   Submit
3 </span>                                                             4 </button>
.                                                                     5 <label>
4 <strong>                                                            6   <strong>
5   Age                                                               7     Age
6 </strong>                                                           8   </strong>
                                                                      9 </label>
                                                                     10 <input type="text" name="[a][b]" id="_a_b">
                                                                     11 <input type="text" name="[c][d]">

If I remove the span element, the name text field is not rendered as well.

Note that this is only a testing issue - the component works fine in a normal request flow.

I'll add a couple of crazy, desperate things I tried below.

Using `Phlex::Rails::Builder`
# frozen_string_literal: true

test "Rails form builder" do
  component = Class.new(Phlex::HTML) do
    attr_reader :form

    def initialize(form)
      @form = form
    end

    define_method :view_template do
      span do
        form.text_field :name
      end
      form.button "Submit"

      form.label :age do |label|
        strong { label.translation }
      end

      form.fields_for :a do |fields|
        fields.text_field :b
      end

      form.fields :c do |fields|
        fields.text_field :d
      end
    end
  end

  Cat = Data.define(:name, :age, :a, :c)

  form_builder = Phlex::Rails::Builder.new(
    ActionView::Helpers::FormBuilder.new(
      :cat,
      Cat.new(name: "Ella", age: 3, a: nil, c: nil),
      controller.view_context,
      {}
    ),
    component: Class.new(Phlex::HTML).new
  ).tap do |builder|
    state = Phlex::SGML::State.new(
      user_context: {},
      output_buffer: +"",
      fragments: nil
    )

    builder.instance_variable_set(:@_state, state)
  end

  output = render(component.new(form_builder))

  assert_equivalent_html output, <<~HTML
    <input type="text" name="name" value="Ella">
    <button name="button" type="submit">Submit</button>
    <label>
      <strong>Age</strong>
    </label>
    <input type="text" name="[a][b]" id="_a_b">
    <input type="text" name="[c][d]">
  HTML
end
Not using `render`, but `call` with a buffer
# frozen_string_literal: true

test "Rails form builder" do
  component = Class.new(Phlex::HTML) do
    attr_reader :form

    def initialize(form)
      @form = form
    end

    define_method :view_template do
      span do
        form.text_field :name
      end
      form.button "Submit"

      form.label :age do |label|
        strong { label.translation }
      end

      form.fields_for :a do |fields|
        fields.text_field :b
      end

      form.fields :c do |fields|
        fields.text_field :d
      end
    end
  end

  Cat = Data.define(:name, :age, :a, :c)

  form_builder = Phlex::Rails::Builder.new(
    ActionView::Helpers::FormBuilder.new(
      :cat,
      Cat.new(name: "Ella", age: 3, a: nil, c: nil),
      view_context,
      {}
    ),
    component: Class.new(Phlex::HTML).new
  ).tap do |builder|
    state = Phlex::SGML::State.new(
      user_context: {},
      output_buffer: +"",
      fragments: nil
    )

    builder.instance_variable_set(:@_state, state)
  end

  buffer = StringIO.new
  output = component.new(form_builder).call(buffer).string

  assert_equivalent_html output, <<~HTML
    <input type="text" name="name" value="Ella">
    <button name="button" type="submit">Submit</button>
    <label>
      <strong>Age</strong>
    </label>
    <input type="text" name="[a][b]" id="_a_b">
    <input type="text" name="[c][d]">
  HTML
end

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions