x-govuk/govuk-form-builder

View on GitHub
spec/govuk_design_system_formbuilder/builder/date_spec.rb

Summary

Maintainability
A
2 hrs
Test Coverage
describe GOVUKDesignSystemFormBuilder::FormBuilder do
  include_context 'setup builder'

  describe '#govuk_date_field' do
    let(:method) { :govuk_date_field }
    let(:attribute) { :born_on }

    let(:fieldset_heading) { 'Date of birth' }
    let(:legend_text) { 'When were you born?' }
    let(:hint_text) { 'It says it on your birth certificate' }

    let(:day_multiparam_attribute) { '3i' }
    let(:month_multiparam_attribute) { '2i' }
    let(:year_multiparam_attribute) { '1i' }
    let(:multiparam_attributes) { [day_multiparam_attribute, month_multiparam_attribute, year_multiparam_attribute] }

    let(:day_identifier) { "person_born_on_#{day_multiparam_attribute}" }
    let(:month_identifier) { "person_born_on_#{month_multiparam_attribute}" }
    let(:year_identifier) { "person_born_on_#{year_multiparam_attribute}" }

    let(:args) { [method, attribute] }
    subject { builder.send(*args) }

    let(:field_type) { 'input' }
    let(:aria_described_by_target) { 'fieldset' }

    include_examples 'HTML formatting checks'

    it_behaves_like 'a field that supports hints'

    it_behaves_like 'a field that supports errors' do
      let(:object) { Person.new(born_on: Date.today.next_year(5)) }

      let(:error_message) { /Your date of birth must be in the past/ }
      let(:error_class) { 'govuk-input--error' }
      let(:error_identifier) { 'person-born-on-error' }
    end

    it_behaves_like 'a field that accepts arbitrary blocks of HTML' do
      let(:described_element) { 'fieldset' }

      # the block content should be before the hint and date inputs
      context 'ordering' do
        let(:hint_div_selector) { 'div.govuk-hint' }
        let(:block_paragraph_selector) { 'p.block-content' }
        let(:govuk_date_selector) { 'div.govuk-date-input' }

        let(:paragraph) { 'A descriptive paragraph all about dates' }

        subject do
          builder.send(*args, legend: { text: legend_text }, hint: { text: hint_text }) do
            builder.tag.p(paragraph, class: 'block-content')
          end
        end

        specify 'the block content should be before the hint and the date inputs' do
          actual = parsed_subject.css([hint_div_selector, block_paragraph_selector, govuk_date_selector].join(",")).flat_map(&:classes)
          expected = %w(block-content govuk-hint govuk-date-input)

          expect(actual).to eql(expected)
        end
      end
    end

    it_behaves_like 'a field that supports setting the legend via localisation'
    it_behaves_like 'a field that supports setting the legend caption via localisation'
    it_behaves_like 'a field that supports setting the hint via localisation'

    it_behaves_like 'a field that supports custom branding'
    it_behaves_like 'a field that contains a customisable form group'

    it_behaves_like 'a field that supports a fieldset with legend'
    it_behaves_like 'a field that supports captions on the legend'

    it_behaves_like 'a date field that accepts a plain ruby object' do
      let(:described_element) { ['input', { count: 3 }] }
    end

    specify 'should output a form group with fieldset, date group and 3 inputs and labels' do
      expect(subject).to have_tag('div', with: { class: 'govuk-form-group' }) do
        with_tag('fieldset', with: { class: 'govuk-fieldset' }) do
          with_tag('div', with: { class: 'govuk-date-input' }) do
            with_tag('input', with: { type: 'text' }, count: 3)
            with_tag('label', count: 3)
          end
        end
      end
    end

    context 'separate date part inputs' do
      specify 'inputs should have the correct labels' do
        expect(subject).to have_tag('div', with: { class: 'govuk-date-input' }) do
          %w(Day Month Year).each do |label_text|
            with_tag('label', text: label_text)
          end
        end
      end

      specify 'inputs should have the correct name' do
        multiparam_attributes.each do |mpa|
          expect(subject).to have_tag('input', with: { name: "#{object_name}[#{attribute}(#{mpa})]" })
        end
      end

      specify 'inputs should have an inputmode of numeric' do
        expect(subject).to have_tag('input', with: { inputmode: 'numeric' })
      end

      specify 'labels should be associated with inputs' do
        [day_identifier, month_identifier, year_identifier].each do |identifier|
          expect(subject).to have_tag('label', with: { for: identifier }, count: 1)
          expect(subject).to have_tag('input', with: { id: identifier }, count: 1)
        end
      end

      specify 'inputs should have the correct classes' do
        expect(subject).to have_tag(
          'input',
          count: 3,
          with: { class: %w(govuk-input govuk-date-input__input) }
        )
      end

      specify 'inputs should have the width class' do
        expect(subject).to have_tag(
          'input',
          count: 2,
          with: { class: %w(govuk-input--width-2) }
        )

        expect(subject).to have_tag(
          'input',
          count: 1,
          with: { class: %w(govuk-input--width-4) }
        )
      end
    end

    context 'when segments are overridden' do
      subject { builder.send(*args, segments: { day: "day", month: "month", year: "year" }) }

      specify "sets the day field correctly" do
        expect(subject).to have_tag('input', with: { id: "person_born_on_day", name: "person[born_on(day)]" })
      end

      specify "sets the month field correctly" do
        expect(subject).to have_tag('input', with: { id: "person_born_on_month", name: "person[born_on(month)]" })
      end

      specify "sets the year field correctly" do
        expect(subject).to have_tag('input', with: { id: "person_born_on_year", name: "person[born_on(year)]" })
      end
    end

    context 'showing only month and year inputs' do
      subject { builder.send(*args, omit_day: true) }

      specify 'there should only be month and year text inputs' do
        expect(subject).to have_tag('input', count: 3)

        [month_multiparam_attribute, year_multiparam_attribute].each do |mpa|
          expect(subject).to have_tag('input', with: { name: "#{object_name}[#{attribute}(#{mpa})]", type: 'text' })
        end

        expect(subject).to have_tag('input', with: { name: "#{object_name}[#{attribute}(#{day_multiparam_attribute})]", type: 'hidden' })
      end

      specify 'there be no day label' do
        expect(subject).not_to have_tag('label', text: 'Day')
      end

      specify 'there should only be month and year labels' do
        expect(subject).to have_tag('label', text: 'Month')
        expect(subject).to have_tag('label', text: 'Year')
      end
    end

    context 'not restricting chars with maxlength' do
      subject { builder.send(*args, maxlength_enabled: false) }

      specify 'there should be a day maxlength attribute' do
        expect(subject).not_to have_tag('input', with: { name: "#{object_name}[#{attribute}(#{day_multiparam_attribute})]", maxlength: '2' })
      end

      specify 'there should be a month maxlength attribute' do
        expect(subject).not_to have_tag('input', with: { name: "#{object_name}[#{attribute}(#{month_multiparam_attribute})]", maxlength: '2' })
      end

      specify 'there should be a year maxlength attribute' do
        expect(subject).not_to have_tag('input', with: { name: "#{object_name}[#{attribute}(#{year_multiparam_attribute})]", maxlength: '4' })
      end
    end

    context 'restricting chars with maxlength' do
      subject { builder.send(*args, maxlength_enabled: true) }

      specify 'there should be a day maxlength attribute' do
        expect(subject).to have_tag('input', with: { name: "#{object_name}[#{attribute}(#{day_multiparam_attribute})]", maxlength: '2' })
      end

      specify 'there should be a month maxlength attribute' do
        expect(subject).to have_tag('input', with: { name: "#{object_name}[#{attribute}(#{month_multiparam_attribute})]", maxlength: '2' })
      end

      specify 'there should be a year maxlength attribute' do
        expect(subject).to have_tag('input', with: { name: "#{object_name}[#{attribute}(#{year_multiparam_attribute})]", maxlength: '4' })
      end
    end

    context 'default values' do
      let(:birth_day) { 3 }
      let(:birth_month) { 2 }
      let(:birth_year) { 1970 }

      context "when the attribute is a `Date` object" do
        let(:object) do
          Person.new(
            name: 'Joey',
            born_on: Date.new(birth_year, birth_month, birth_day)
          )
        end

        specify 'should set the day value correctly' do
          expect(subject).to have_tag('input', with: {
            id: day_identifier,
            value: birth_day
          })
        end

        specify 'should set the month value correctly' do
          expect(subject).to have_tag('input', with: {
            id: month_identifier,
            value: birth_month
          })
        end

        specify 'should set the year value correctly' do
          expect(subject).to have_tag('input', with: {
            id: year_identifier,
            value: birth_year
          })
        end
      end

      context "when the attribute is a multiparameter hash object" do
        let(:object) do
          Person.new(
            name: 'Joey',
            born_on: { 3 => birth_day, 2 => birth_month, 1 => birth_year }
          )
        end

        specify 'should set the day value correctly' do
          expect(subject).to have_tag('input', with: {
            id: day_identifier,
            value: birth_day
          })
        end

        specify 'should set the month value correctly' do
          expect(subject).to have_tag('input', with: {
            id: month_identifier,
            value: birth_month
          })
        end

        specify 'should set the year value correctly' do
          expect(subject).to have_tag('input', with: {
            id: year_identifier,
            value: birth_year
          })
        end
      end
    end

    context 'auto-completion' do
      context 'date of birth' do
        subject { builder.send(*args, date_of_birth: true) }
        specify "day field should have autocomplete attribute with value 'bday-day'" do
          expect(subject).to have_tag('input', with: { autocomplete: 'bday-day' })
        end

        specify "month field should have autocomplete attribute with value 'bday-month'" do
          expect(subject).to have_tag('input', with: { autocomplete: 'bday-month' })
        end

        specify "year field should have autocomplete attribute with value 'bday-year'" do
          expect(subject).to have_tag('input', with: { autocomplete: 'bday-year' })
        end
      end
    end

    describe "additional attributes" do
      subject { builder.send(*args, data: { test: "abc" }) }

      specify "should have additional attributes" do
        expect(subject).to have_tag('div', with: { 'data-test': 'abc' })
      end
    end

    describe "invalid date-like objects" do
      let(:wrong_date) { WrongDate.new(nil, nil, nil) }
      before { object.born_on = wrong_date }

      specify "fails with an appropriate error message" do
        expect { subject }.to raise_error(ArgumentError, /invalid Date-like object/)
      end
    end

    describe "hashes without the right keys" do
      let(:wrong_hash) { { d: 20, m: 3, y: 2009 } }
      before { object.born_on = wrong_hash }
      before { allow(Rails).to receive_message_chain(:logger, :warn) }
      before { subject }

      specify "logs an appropriate warning" do
        expect(Rails.logger).to have_received(:warn).with(/No key '.*' found in MULTIPARAMETER_KEY hash/).exactly(wrong_hash.length).times
      end

      specify "doesn't generate inputs with values" do
        parsed_subject.css('input').each do |element|
          expect(element.attributes.keys).not_to include('value')
        end
      end
    end
  end
end