Back to Repositories

Testing Form Wrapper Components in SimpleForm

This test suite validates the wrapper functionality in Simple Form, ensuring proper HTML class handling, error states, and custom wrapper configurations. It covers essential form builder wrapper behaviors and customization options.

Test Coverage Overview

The test suite provides comprehensive coverage of Simple Form’s wrapper functionality, including:

  • Error class handling for attributes with and without errors
  • Attribute name class assignment for regular and nested forms
  • Association name class handling
  • Input validation states and CSS classes
  • Hint class management
  • Disabled state handling

Implementation Analysis

The testing approach focuses on ActionView integration, utilizing a structured pattern for wrapper validation:

Tests verify wrapper behavior through HTML output assertion, covering standard and custom wrapper configurations. The implementation leverages Minitest’s assertion methods to validate DOM structure and CSS classes.

Technical Details

Key technical components include:

  • ActionView::TestCase as the base test framework
  • Custom helper methods for form generation
  • HTML selector assertions for validation
  • Wrapper configuration swapping capabilities
  • Integration with SimpleForm’s core configuration

Best Practices Demonstrated

The test suite exemplifies strong testing practices:

  • Comprehensive edge case coverage
  • Isolated test scenarios
  • Clear test naming conventions
  • Thorough validation of HTML structure
  • Effective use of test helpers and setup methods

heartcombo/simple_form

test/form_builder/wrapper_test.rb

            
# frozen_string_literal: true
require 'test_helper'

class WrapperTest < ActionView::TestCase
  test 'wrapper does not have error class for attribute without errors' do
    with_form_for @user, :active
    assert_no_select 'div.field_with_errors'
  end

  test 'wrapper does not have error class when object is not present' do
    with_form_for :project, :name
    assert_no_select 'div.field_with_errors'
  end

  test 'wrapper adds the attribute name class' do
    with_form_for @user, :name
    assert_select 'div.user_name'
  end

  test 'wrapper adds the attribute name class for nested forms' do
    @user.company = Company.new(1, 'Empresa')
    with_concat_form_for @user do |f|
      concat(f.simple_fields_for(:company) do |company_form|
        concat(company_form.input :name)
      end)
    end

    assert_select 'div.user_company_name'
  end

  test 'wrapper adds the association name class' do
    with_form_for @user, :company
    assert_select 'div.user_company'
  end

  test 'wrapper adds error class for attribute with errors' do
    with_form_for @user, :name
    assert_select 'div.field_with_errors'
  end

  test 'wrapper adds error class to input for attribute with errors' do
    with_form_for @user, :name, wrapper: custom_wrapper_with_input_error_class
    assert_select 'div.field_with_errors'
    assert_select 'input.is-invalid'
  end

  test 'wrapper does not add error class to input when the attribute is valid' do
    with_form_for @user, :phone_number, wrapper: custom_wrapper_with_input_error_class
    assert_no_select 'div.field_with_errors'
    assert_no_select 'input.is-invalid'
  end

  test 'wrapper adds valid class for present attribute without errors' do
    @user.instance_eval { undef errors }
    with_form_for @user, :name, wrapper: custom_wrapper_with_input_valid_class
    assert_select 'div.field_without_errors'
    assert_select 'input.is-valid'
    assert_no_select 'div.field_with_errors'
    assert_no_select 'input.is-invalid'
  end

  test 'wrapper does not determine if valid class is needed when it is set to nil' do
    @user.instance_eval { undef errors }
    with_form_for @user, :name, wrapper: custom_wrapper_with_input_valid_class(valid_class: nil)

    assert_no_select 'div.field_without_errors'
  end

  test 'wrapper adds hint class for attribute with a hint' do
    with_form_for @user, :name, hint: 'hint'
    assert_select 'div.field_with_hint'
  end

  test 'wrapper does not have disabled class by default' do
    with_form_for @user, :active
    assert_no_select 'div.disabled'
  end

  test 'wrapper has disabled class when input is disabled' do
    with_form_for @user, :active, disabled: true
    assert_select 'div.disabled'
  end

  test 'wrapper supports no wrapping when wrapper is false' do
    with_form_for @user, :name, wrapper: false
    assert_select 'form > label[for=user_name]'
    assert_select 'form > input#user_name.string'
  end

  test 'wrapper supports no wrapping when wrapper tag is false' do
    with_form_for @user, :name, wrapper: custom_wrapper_without_top_level
    assert_select 'form > label[for=user_name]'
    assert_select 'form > input#user_name.string'
  end

  test 'wrapper wraps tag adds required/optional css classes' do
    with_form_for @user, :name
    assert_select 'form div.input.required.string'

    with_form_for @user, :age, required: false
    assert_select 'form div.input.optional.integer'
  end

  test 'wrapper allows custom options to be given' do
    with_form_for @user, :name, wrapper_html: { id: "super_cool", class: 'yay' }
    assert_select 'form #super_cool.required.string.yay'
  end

  test 'wrapper allows tag to be given on demand' do
    with_form_for @user, :name, wrapper_tag: :b
    assert_select 'form b.required.string'
  end

  test 'wrapper allows wrapper class to be given on demand' do
    with_form_for @user, :name, wrapper_class: :wrapper
    assert_select 'form div.wrapper.required.string'
  end

  test 'wrapper skips additional classes when configured' do
    swap SimpleForm, generate_additional_classes_for: %i[input label] do
      with_form_for @user, :name, wrapper_class: :wrapper
      assert_select 'form div.wrapper'
      assert_no_select 'div.required'
      assert_no_select 'div.string'
      assert_no_select 'div.user_name'
    end
  end

  test 'wrapper does not generate empty css class' do
    swap SimpleForm, generate_additional_classes_for: %i[input label] do
      swap_wrapper :default, custom_wrapper_without_class do
        with_form_for @user, :name
        assert_no_select 'div#custom_wrapper_without_class[class]'
      end
    end
  end

  # Custom wrapper test

  test 'custom wrappers works' do
    swap_wrapper do
      with_form_for @user, :name, hint: "cool"
      assert_select "section.custom_wrapper div.another_wrapper label"
      assert_select "section.custom_wrapper div.another_wrapper input.string"
      assert_no_select "section.custom_wrapper div.another_wrapper span.omg_error"
      assert_select "section.custom_wrapper div.error_wrapper span.omg_error"
      assert_select "section.custom_wrapper > div.omg_hint", "cool"
    end
  end

  test 'custom wrappers can be turned off' do
    swap_wrapper do
      with_form_for @user, :name, another: false
      assert_no_select "section.custom_wrapper div.another_wrapper label"
      assert_no_select "section.custom_wrapper div.another_wrapper input.string"
      assert_select "section.custom_wrapper div.error_wrapper span.omg_error"
    end
  end

  test 'custom wrappers can have additional attributes' do
    swap_wrapper :default, custom_wrapper_with_additional_attributes do
      with_form_for @user, :name

      assert_select "div.custom_wrapper[title='some title'][data-wrapper='test']"
    end
  end

  test 'custom wrappers can have full error message on attributes' do
    swap_wrapper :default, custom_wrapper_with_full_error do
      with_form_for @user, :name
      assert_select 'span.error', "Super User Name! cannot be blank"
    end
  end

  test 'custom wrappers on a form basis' do
    swap_wrapper :another do
      with_concat_form_for(@user) do |f|
        f.input :name
      end

      assert_no_select "section.custom_wrapper div.another_wrapper label"
      assert_no_select "section.custom_wrapper div.another_wrapper input.string"

      with_concat_form_for(@user, wrapper: :another) do |f|
        f.input :name
      end

      assert_select "section.custom_wrapper div.another_wrapper label"
      assert_select "section.custom_wrapper div.another_wrapper input.string"
    end
  end

  test 'custom wrappers on input basis' do
    swap_wrapper :another do
      with_form_for @user, :name
      assert_no_select "section.custom_wrapper div.another_wrapper label"
      assert_no_select "section.custom_wrapper div.another_wrapper input.string"
      output_buffer.to_s.replace ""

      with_form_for @user, :name, wrapper: :another
      assert_select "section.custom_wrapper div.another_wrapper label"
      assert_select "section.custom_wrapper div.another_wrapper input.string"
      output_buffer.to_s.replace ""
    end

    with_form_for @user, :name, wrapper: custom_wrapper
    assert_select "section.custom_wrapper div.another_wrapper label"
    assert_select "section.custom_wrapper div.another_wrapper input.string"
  end

  test 'access wrappers with indifferent access' do
    swap_wrapper :another do
      with_form_for @user, :name, wrapper: "another"
      assert_select "section.custom_wrapper div.another_wrapper label"
      assert_select "section.custom_wrapper div.another_wrapper input.string"
    end
  end

  test 'does not duplicate label classes for different inputs' do
    swap_wrapper :default, custom_wrapper_with_label_html_option do
      with_concat_form_for(@user) do |f|
        concat f.input :name, required: false
        concat f.input :email, as: :email, required: true
      end

      assert_select "label.string.optional.extra-label-class[for='user_name']"
      assert_select "label.email.required.extra-label-class[for='user_email']"
      assert_no_select "label.string.optional.extra-label-class[for='user_email']"
    end
  end

  test 'raise error when wrapper not found' do
    assert_raise SimpleForm::WrapperNotFound do
      with_form_for @user, :name, wrapper: :not_found
    end
  end

  test 'uses wrapper for specified in config mapping' do
    swap_wrapper :another do
      swap SimpleForm, wrapper_mappings: { string: :another } do
        with_form_for @user, :name
        assert_select "section.custom_wrapper div.another_wrapper label"
        assert_select "section.custom_wrapper div.another_wrapper input.string"
      end
    end
  end

  test 'uses custom wrapper mapping per form basis' do
    swap_wrapper :another do
      with_concat_form_for @user, wrapper_mappings: { string: :another } do |f|
        concat f.input :name
      end
    end

    assert_select "section.custom_wrapper div.another_wrapper label"
    assert_select "section.custom_wrapper div.another_wrapper input.string"
  end

  test 'simple_fields_form reuses custom wrapper mapping per form basis' do
    @user.company = Company.new(1, 'Empresa')

    swap_wrapper :another do
      with_concat_form_for @user, wrapper_mappings: { string: :another } do |f|
        concat(f.simple_fields_for(:company) do |company_form|
          concat(company_form.input(:name))
        end)
      end
    end

    assert_select "section.custom_wrapper div.another_wrapper label"
    assert_select "section.custom_wrapper div.another_wrapper input.string"
  end

  test "input attributes class will merge with wrapper_options' classes" do
    swap_wrapper :default, custom_wrapper_with_input_class do
      with_concat_form_for @user do |f|
        concat f.input :name, input_html: { class: 'another-class' }
      end
    end

    assert_select "div.custom_wrapper input.string.inline-class.another-class"
  end

  test "input with data attributes will merge with wrapper_options' data" do
    swap_wrapper :default, custom_wrapper_with_input_data_modal do
      with_concat_form_for @user do |f|
        concat f.input :name, input_html: { data: { modal: 'another-data', target: 'merge-data' } }
      end
    end

    assert_select "input[data-wrapper='data-wrapper'][data-modal='another-data'][data-target='merge-data']"
  end

  test "input with aria attributes will merge with wrapper_options' aria" do
    swap_wrapper :default, custom_wrapper_with_input_aria_modal do
      with_concat_form_for @user do |f|
        concat f.input :name, input_html: { aria: { modal: 'another-aria', target: 'merge-aria' } }
      end
    end

    assert_select "input[aria-wrapper='aria-wrapper'][aria-modal='another-aria'][aria-target='merge-aria']"
  end

  test 'input accepts attributes in the DSL' do
    swap_wrapper :default, custom_wrapper_with_input_class do
      with_concat_form_for @user do |f|
        concat f.input :name
      end
    end

    assert_select "div.custom_wrapper input.string.inline-class"
  end

  test 'label accepts attributes in the DSL' do
    swap_wrapper :default, custom_wrapper_with_label_class do
      with_concat_form_for @user do |f|
        concat f.input :name
      end
    end

    assert_select "div.custom_wrapper label.string.inline-class"
  end

  test 'label_input accepts attributes in the DSL' do
    swap_wrapper :default, custom_wrapper_with_label_input_class do
      with_concat_form_for @user do |f|
        concat f.input :name
      end
    end

    assert_select "div.custom_wrapper label.string.inline-class"
    assert_select "div.custom_wrapper input.string.inline-class"
  end

  test 'input accepts data attributes in the DSL' do
    swap_wrapper :default, custom_wrapper_with_input_attributes do
      with_concat_form_for @user do |f|
        concat f.input :name
      end
    end

    assert_select "div.custom_wrapper input.string[data-modal=true]"
  end

  test 'inline wrapper displays when there is content' do
    swap_wrapper :default, custom_wrapper_with_wrapped_optional_component do
      with_form_for @user, :name, hint: "cannot be blank"
      assert_select 'section.custom_wrapper div.no_output_wrapper p.omg_hint', "cannot be blank"
      assert_select 'p.omg_hint'
    end
  end

  test 'inline wrapper does not display when there is no content' do
    swap_wrapper :default, custom_wrapper_with_wrapped_optional_component do
      with_form_for @user, :name
      assert_select 'section.custom_wrapper div.no_output_wrapper'
      assert_no_select 'p.omg_hint'
    end
  end

  test 'optional wrapper does not display when there is content' do
    swap_wrapper :default, custom_wrapper_with_unless_blank do
      with_form_for @user, :name, hint: "can't be blank"
      assert_select 'section.custom_wrapper div.no_output_wrapper'
      assert_select 'div.no_output_wrapper'
      assert_select 'p.omg_hint'
    end
  end

  test 'optional wrapper does not display when there is no content' do
    swap_wrapper :default, custom_wrapper_with_unless_blank do
      with_form_for @user, :name
      assert_no_select 'section.custom_wrapper div.no_output_wrapper'
      assert_no_select 'div.no_output_wrapper'
      assert_no_select 'p.omg_hint'
    end
  end
end