diff --git a/lib/govuk_elements_form_builder/form_builder.rb b/lib/govuk_elements_form_builder/form_builder.rb index 751bfaf..865f74b 100644 --- a/lib/govuk_elements_form_builder/form_builder.rb +++ b/lib/govuk_elements_form_builder/form_builder.rb @@ -192,9 +192,8 @@ def form_group_id attribute end def form_group_classes attributes - attributes = [attributes] if !attributes.respond_to? :count classes = 'form-group' - classes += ' error' if attributes.find { |a| error_for? a } + classes += ' error' if Array(attributes).find { |a| error_for? a } classes end diff --git a/spec/lib/govuk_elements_form_builder/form_builder_spec.rb b/spec/lib/govuk_elements_form_builder/form_builder_spec.rb index 6a254bd..f03d43f 100644 --- a/spec/lib/govuk_elements_form_builder/form_builder_spec.rb +++ b/spec/lib/govuk_elements_form_builder/form_builder_spec.rb @@ -13,7 +13,7 @@ class TestHelper < ActionView::Base; end let(:helper) { TestHelper.new } let(:resource) { Person.new } - let(:builder) { described_class.new :person, resource, helper, {} } + subject(:builder) { described_class.new :person, resource, helper, {} } def expect_equal output, expected split_output = output.gsub(">\n", ' />').split("<").join("\n<").split(">").join(">\n").squeeze("\n").strip + '>' @@ -30,76 +30,42 @@ def type_for(method, type) end shared_examples_for 'input field' do |method, type| - - def size(method, size) - method == :text_area ? '' : %'size="#{size}" ' - end + let(:default_builder_options) { { resource: 'person' } } it 'outputs label and input wrapped in div' do output = builder.send method, :name - expect_equal output, [ - '
', - '', - %'<#{element_for(method)} class="form-control" #{type_for(method, type)}name="person[name]" id="person_name" />', - '
' - ] + expect(output).to match_form_group(default_builder_options.merge(field: :name, input_type: method)) + end + + it 'supports attributes defined as a string' do + output = builder.send method, 'name' + + expect(output).to match_form_group(default_builder_options.merge(field: :name, input_type: method)) end it 'adds custom class to input when passed class: "custom-class"' do output = builder.send method, :name, class: 'custom-class' - expect_equal output, [ - '
', - '', - %'<#{element_for(method)} class="form-control custom-class" #{type_for(method, type)}name="person[name]" id="person_name" />', - '
' - ] + expect(output).to match_form_group(default_builder_options.merge(field: :name, input_type: method, class: 'custom-class')) end it 'adds custom classes to input when passed class: ["custom-class", "another-class"]' do output = builder.send method, :name, class: ['custom-class', 'another-class'] - expect_equal output, [ - '
', - '', - %'<#{element_for(method)} class="form-control custom-class another-class" #{type_for(method, type)}name="person[name]" id="person_name" />', - '
' - ] + expect(output).to match_form_group(default_builder_options.merge(field: :name, input_type: method, class: 'custom-class another-class')) end it 'passes options passed to text_field onto super text_field implementation' do output = builder.send method, :name, size: 100 - expect_equal output, [ - '
', - '', - %'<#{element_for(method)} #{size(method, 100)}class="form-control" #{type_for(method, type)}name="person[name]" id="person_name" />', - '
' - ] + + expect(output).to match_form_group(default_builder_options.merge(field: :name, input_type: method, size: 100)) end context 'when hint text provided' do it 'outputs hint text in span inside label' do output = builder.send method, :ni_number - expect_equal output, [ - '
', - '', - %'<#{element_for(method)} class="form-control" #{type_for(method, type)}name="person[ni_number]" id="person_ni_number" />', - '
' - ] + expect(output).to match_form_group(default_builder_options.merge(field: :ni_number, input_type: method)) end end @@ -108,14 +74,7 @@ def size(method, size) output = builder.fields_for(:address, Address.new) do |f| f.send method, :postcode end - expect_equal output, [ - '
', - '', - %'<#{element_for(method)} class="form-control" #{type_for(method, type)}name="person[address_attributes][postcode]" id="person_address_attributes_postcode" />', - '
' - ] + expect(output).to match_form_group(default_builder_options.merge(field: :postcode, input_type: method, fields_for: :address)) end end @@ -224,7 +183,7 @@ def expected_error_html method, type, attribute, name_value, label, error end describe '#text_area' do - include_examples 'input field', :text_area, nil + include_examples 'input field', :text_area end describe '#email_field' do diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 241a969..f271a2b 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -21,6 +21,7 @@ # # Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f } require_relative 'support/translation_helper.rb' +require_relative 'support/matcher_helpers.rb' # Checks for pending migrations before tests are run. # If you are not using ActiveRecord, you can remove this line. @@ -49,4 +50,5 @@ # The different available types are documented in the features, such as in # https://relishapp.com/rspec/rspec-rails/docs config.infer_spec_type_from_file_location! + config.include MatcherHelpers end diff --git a/spec/support/matcher_helpers.rb b/spec/support/matcher_helpers.rb new file mode 100644 index 0000000..ba561d8 --- /dev/null +++ b/spec/support/matcher_helpers.rb @@ -0,0 +1,7 @@ +require_relative 'matchers/form_group_matcher_helpers' + +module MatcherHelpers + def self.included(base) + base.send(:include, FormGroupMatcherHelpers) + end +end diff --git a/spec/support/matchers/form_group_matcher_helpers.rb b/spec/support/matchers/form_group_matcher_helpers.rb new file mode 100644 index 0000000..405a6a9 --- /dev/null +++ b/spec/support/matchers/form_group_matcher_helpers.rb @@ -0,0 +1,115 @@ +require 'rspec/expectations' + +module FormGroupMatcherHelpers + extend RSpec::Matchers::DSL + + class FormGroupObject + attr_reader :options, :resource, :field, + :label, :input_type, :input_class, :input_size, + :fields_for + + def initialize(options) + @options = options + @resource = options[:resource] + @field = options.fetch(:field) + @label = options.fetch(:label) { @field.to_s.humanize } + @input_type = options.fetch(:input_type) + @input_class = options[:class] + @input_size = options[:size] + @fields_for = options[:fields_for] + end + + def field_id + [resource, fields_for_attributes, field].compact.join('_') + end + + def field_name + return "#{resource}[#{fields_for_attributes}][#{field}]" if resource && fields_for_attributes + return "#{resource}[#{field}]" if resource + field + end + + def to_s + ['
', label_tag, field_tag, '
'].join("\n") + end + + private + + def fields_for_attributes + return unless fields_for + "#{fields_for}_attributes" + end + + def text_area_input? + input_type.to_s == 'text_area' + end + + def input_tag + return '', localized_hint, ''].join("\n") + end + + def label_tag + localized_path = ['helpers', 'label', resource, field].compact.join('.') + localized_label = I18n.t(localized_path, default: field.to_s.humanize) + [%['].compact.join("\n") + end + + def field_tag + [input_tag, input_tag_size, input_tag_class, input_tag_type, %[name="#{field_name}"], %[id="#{field_id}"], '/>'].compact.join(' ') + end + end + + matcher :match_form_group do |expected| + define_method :formatted_expected do + FormGroupObject.new(expected).to_s + end + + define_method :formatted_actual do |actual| + actual.gsub(">\n", ' />').split("<").join("\n<").split(">").join(">\n").squeeze("\n").strip + '>' + end + + match do |actual| + formatted_actual(actual) == formatted_expected + end + + failure_message do |actual| + "expected\n#{formatted_actual(actual)}\nto match\n#{formatted_expected}" + end + end +end