From dda52881d1d1a4b55c2658ba217544e3dfcd1ceb Mon Sep 17 00:00:00 2001 From: Sergio Marques Date: Fri, 18 Nov 2016 10:18:06 +0000 Subject: [PATCH 1/2] Provides support to Form Builder to define attributes as a string When trying to define a html tag for an attribute which is specified as a string, e.g.: builder.text_area 'my_form_field' The following error occurs: Failure/Error: output = builder.send method, 'name' NoMethodError: undefined method `find' for "name":String Shared Example Group: "input field" called from ./spec/lib/govuk_elements_form_builder/form_builder_spec.rb:272 # ./lib/govuk_elements_form_builder/form_builder.rb:197:in `form_group_classes' # ./lib/govuk_elements_form_builder/form_builder.rb:29:in `block (2 levels) in ' # ./spec/lib/govuk_elements_form_builder/form_builder_spec.rb:39:in `block (3 levels) in ' This is raised here as a ruby String also responds to the method :count and the conversion to array is not done appropriately. --- lib/govuk_elements_form_builder/form_builder.rb | 3 +-- .../form_builder_spec.rb | 17 +++++++++++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) 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..5439dbf 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 + '>' @@ -48,6 +48,19 @@ def size(method, size) ] end + it 'supports attributes defined as a string' 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" />', + '
' + ] + end + it 'adds custom class to input when passed class: "custom-class"' do output = builder.send method, :name, class: 'custom-class' @@ -224,7 +237,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 From f6837e2f9f83733bf80660a33efbc0d72a74f7c4 Mon Sep 17 00:00:00 2001 From: Sergio Marques Date: Thu, 24 Nov 2016 17:14:05 +0000 Subject: [PATCH 2/2] Create custom RSpec matcher to validate form group HTML output The specs that validate what kind of content is generated for all the supported HTML tag built through the Form Builder have a lot of repeated code. This commit tackles that issue by supporting a custom RSpec matcher that validates the HTML output generated by the Form Builder. This commit does NOT change any behaviour in the way that the Form Build works. --- .../form_builder_spec.rb | 74 ++--------- spec/rails_helper.rb | 2 + spec/support/matcher_helpers.rb | 7 ++ .../matchers/form_group_matcher_helpers.rb | 115 ++++++++++++++++++ 4 files changed, 134 insertions(+), 64 deletions(-) create mode 100644 spec/support/matcher_helpers.rb create mode 100644 spec/support/matchers/form_group_matcher_helpers.rb 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 5439dbf..f03d43f 100644 --- a/spec/lib/govuk_elements_form_builder/form_builder_spec.rb +++ b/spec/lib/govuk_elements_form_builder/form_builder_spec.rb @@ -30,89 +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', class: 'custom-class' + output = builder.send method, 'name' - 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)) 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 @@ -121,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 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