Skip to content
This repository has been archived by the owner on May 5, 2020. It is now read-only.

Commit

Permalink
Create custom RSpec matcher to validate form group HTML output
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Sergio Marques committed Nov 24, 2016
1 parent 107fe37 commit c0849df
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 64 deletions.
74 changes: 10 additions & 64 deletions spec/lib/govuk_elements_form_builder/form_builder_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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, [
'<div class="form-group">',
'<label class="form-label" for="person_name">',
'Full name',
'</label>',
%'<#{element_for(method)} class="form-control" #{type_for(method, type)}name="person[name]" id="person_name" />',
'</div>'
]
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, [
'<div class="form-group">',
'<label class="form-label" for="person_name">',
'Full name',
'</label>',
%'<#{element_for(method)} class="form-control custom-class" #{type_for(method, type)}name="person[name]" id="person_name" />',
'</div>'
]
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, [
'<div class="form-group">',
'<label class="form-label" for="person_name">',
'Full name',
'</label>',
%'<#{element_for(method)} class="form-control custom-class" #{type_for(method, type)}name="person[name]" id="person_name" />',
'</div>'
]
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, [
'<div class="form-group">',
'<label class="form-label" for="person_name">',
'Full name',
'</label>',
%'<#{element_for(method)} class="form-control custom-class another-class" #{type_for(method, type)}name="person[name]" id="person_name" />',
'</div>'
]
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, [
'<div class="form-group">',
'<label class="form-label" for="person_name">',
'Full name',
'</label>',
%'<#{element_for(method)} #{size(method, 100)}class="form-control" #{type_for(method, type)}name="person[name]" id="person_name" />',
'</div>'
]

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, [
'<div class="form-group">',
'<label class="form-label" for="person_ni_number">',
'National Insurance number',
'<span class="form-hint">',
'It’ll be on your last payslip. For example, JH 21 90 0A.',
'</span>',
'</label>',
%'<#{element_for(method)} class="form-control" #{type_for(method, type)}name="person[ni_number]" id="person_ni_number" />',
'</div>'
]
expect(output).to match_form_group(default_builder_options.merge(field: :ni_number, input_type: method))
end
end

Expand All @@ -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, [
'<div class="form-group">',
'<label class="form-label" for="person_address_attributes_postcode">',
'Postcode',
'</label>',
%'<#{element_for(method)} class="form-control" #{type_for(method, type)}name="person[address_attributes][postcode]" id="person_address_attributes_postcode" />',
'</div>'
]
expect(output).to match_form_group(default_builder_options.merge(field: :postcode, input_type: method, fields_for: :address))
end
end

Expand Down
2 changes: 2 additions & 0 deletions spec/rails_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
7 changes: 7 additions & 0 deletions spec/support/matcher_helpers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
require_relative 'matchers/form_group_matcher_helpers'

module MatcherHelpers
def self.included(base)
base.send(:include, FormGroupMatcherHelpers)
end
end
115 changes: 115 additions & 0 deletions spec/support/matchers/form_group_matcher_helpers.rb
Original file line number Diff line number Diff line change
@@ -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
[ '<div class="form-group">', label_tag, field_tag, '</div>' ].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 '<textarea' if text_area_input?
'<input'
end

def input_tag_type
return if text_area_input?
type = {
text_field: 'text',
email_field: 'email',
number_field: 'number',
password_field: 'password',
phone_field: 'tel',
range_field: 'range',
search_field: 'search',
telephone_field: 'tel',
url_field: 'url'
}[input_type.to_sym]
%[type="#{type}"]
end

def input_tag_class
classes = 'form-control'
classes << ' ' << input_class if input_class
%[class="#{classes}"]
end

def input_tag_size
return if text_area_input?
return unless input_size
%[size="#{input_size}"]
end

def hint_tag
localized_path = ['helpers', 'hint', resource, field].compact.join('.')
localized_hint = I18n.t(localized_path, default: '')
return unless localized_hint.present?
['<span class="form-hint">', localized_hint, '</span>'].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)
[ %[<label class="form-label" for="#{field_id}">], localized_label, hint_tag, '</label>' ].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</textarea>", ' />').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

0 comments on commit c0849df

Please sign in to comment.