Skip to content

Commit

Permalink
Support initial aria-describedby on all form fields
Browse files Browse the repository at this point in the history
  • Loading branch information
colinrotherham committed May 17, 2019
1 parent 840d019 commit 7f28b36
Show file tree
Hide file tree
Showing 24 changed files with 678 additions and 39 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@

🆕 New features:

- Support aria-describedby on all form fields

All form fields now support an initial `aria-describedby` value, populated before the optional hint and error message IDs are appended.

Useful when fields are described by errors or hints on parent fieldsets.

([PR #1347](https://github.com/alphagov/govuk-frontend/pull/1347))

- Pull Request Title goes here

Description goes here (optional)
Expand Down
16 changes: 16 additions & 0 deletions src/components/checkboxes/checkboxes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,22 @@ examples:
hint:
text: Go on, you know you want to!

- name: with fieldset and error message
data:
name: colours
errorMessage:
text: Please accept the terms and conditions
fieldset:
legend:
text: What is your nationality?
items:
- value: british
text: British
- value: irish
text: Irish
- value: other
text: Citizen of another country

- name: with all fieldset attributes
data:
idPrefix: example
Expand Down
5 changes: 4 additions & 1 deletion src/components/checkboxes/template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@

{#- a record of other elements that we need to associate with the input using
aria-describedby – for example hints or error messages -#}
{% set describedBy = "" %}
{% set describedBy = params.describedBy if params.describedBy else "" %}
{% if params.fieldset.describedBy %}
{% set describedBy = params.fieldset.describedBy %}
{% endif %}

{% set isConditional = false %}
{% for item in params.items %}
Expand Down
93 changes: 89 additions & 4 deletions src/components/checkboxes/template.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,30 @@ describe('Checkboxes', () => {
expect($component.hasClass('app-checkboxes--custom-modifier')).toBeTruthy()
})

it('renders initial aria-describedby on fieldset', () => {
const describedById = 'parent-fieldset-hint-id'

const $ = render('checkboxes', {
name: 'example-name',
fieldset: {
describedBy: describedById
},
items: [
{
value: '1',
text: 'Option 1'
},
{
value: '2',
text: 'Option 2'
}
]
})

const $fieldset = $('.govuk-fieldset')
expect($fieldset.attr('aria-describedby')).toMatch(describedById)
})

it('render attributes', () => {
const $ = render('checkboxes', {
name: 'example-name',
Expand Down Expand Up @@ -521,7 +545,7 @@ describe('Checkboxes', () => {
})

it('associates the fieldset as "described by" the error message', () => {
const $ = render('checkboxes', examples['with all fieldset attributes'])
const $ = render('checkboxes', examples['with fieldset and error message'])

const $fieldset = $('.govuk-fieldset')
const $errorMessage = $('.govuk-error-message')
Expand All @@ -534,6 +558,27 @@ describe('Checkboxes', () => {
.toMatch(errorMessageId)
})

it('associates the fieldset as "described by" the error message and parent fieldset', () => {
const describedById = 'parent-fieldset-hint-id'
const params = examples['with fieldset and error message']

params.fieldset.describedBy = describedById

const $ = render('checkboxes', params)

const $fieldset = $('.govuk-fieldset')
const $errorMessage = $('.govuk-error-message')

const errorMessageId = new RegExp(
WORD_BOUNDARY + describedById + WHITESPACE + $errorMessage.attr('id') + WORD_BOUNDARY
)

expect($fieldset.attr('aria-describedby'))
.toMatch(errorMessageId)

delete params.fieldset.describedBy
})

it('does not associate each input as "described by" the error message', () => {
const $ = render('checkboxes', examples['with error message and hints on items'])

Expand Down Expand Up @@ -576,22 +621,62 @@ describe('Checkboxes', () => {

expect($fieldset.attr('aria-describedby')).toMatch(hintId)
})

it('associates the fieldset as "described by" the hint and parent fieldset', () => {
const describedById = 'parent-fieldset-hint-id'
const params = examples['with all fieldset attributes']

params.fieldset.describedBy = describedById

const $ = render('checkboxes', params)
const $fieldset = $('.govuk-fieldset')
const $hint = $('.govuk-hint')

const hintId = new RegExp(
WORD_BOUNDARY + describedById + WHITESPACE + $hint.attr('id') + WORD_BOUNDARY
)

expect($fieldset.attr('aria-describedby')).toMatch(hintId)
delete params.fieldset.describedBy
})
})

describe('when they include both a hint and an error message', () => {
it('associates the fieldset as described by both the hint and the error message', () => {
const $ = render('checkboxes', examples['with all fieldset attributes'])

const $fieldset = $('.govuk-fieldset')
const $errorMessageId = $('.govuk-error-message').attr('id')
const $hintId = $('.govuk-hint').attr('id')
const errorMessageId = $('.govuk-error-message').attr('id')
const hintId = $('.govuk-hint').attr('id')

const combinedIds = new RegExp(
WORD_BOUNDARY + hintId + WHITESPACE + errorMessageId + WORD_BOUNDARY
)

expect($fieldset.attr('aria-describedby'))
.toMatch(combinedIds)
})

it('associates the fieldset as described by the hint, error message and parent fieldset', () => {
const describedById = 'parent-fieldset-hint-id'
const params = examples['with all fieldset attributes']

params.fieldset.describedBy = describedById

const $ = render('checkboxes', params)

const $fieldset = $('.govuk-fieldset')
const errorMessageId = $('.govuk-error-message').attr('id')
const hintId = $('.govuk-hint').attr('id')

const combinedIds = new RegExp(
WORD_BOUNDARY + $hintId + WHITESPACE + $errorMessageId + WORD_BOUNDARY
WORD_BOUNDARY + describedById + WHITESPACE + hintId + WHITESPACE + errorMessageId + WORD_BOUNDARY
)

expect($fieldset.attr('aria-describedby'))
.toMatch(combinedIds)

delete params.fieldset.describedBy
})
})

Expand Down
20 changes: 19 additions & 1 deletion src/components/date-input/date-input.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,25 @@ examples:
-
name: year
classes: govuk-input--width-4
- name: with errors
- name: with errors only
data:
id: dob-errors
fieldset:
legend:
text: What is your date of birth?
errorMessage:
text: Error message goes here
items:
-
name: day
classes: govuk-input--width-2 govuk-input--error
-
name: month
classes: govuk-input--width-2 govuk-input--error
-
name: year
classes: govuk-input--width-4 govuk-input--error
- name: with errors and hint
data:
id: dob-errors
fieldset:
Expand Down
2 changes: 1 addition & 1 deletion src/components/date-input/template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

{#- a record of other elements that we need to associate with the input using
aria-describedby – for example hints or error messages -#}
{% set describedBy = "" %}
{% set describedBy = params.fieldset.describedBy if params.fieldset.describedBy else "" %}

{% if params.items %}
{% set dateInputItems = params.items %}
Expand Down
82 changes: 72 additions & 10 deletions src/components/date-input/template.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ describe('Date input', () => {
})

it('sets the `group` role on the fieldset to force JAWS18 to announce the hint and error message', () => {
const $ = render('date-input', examples['with errors'])
const $ = render('date-input', examples['with errors and hint'])

const $fieldset = $('.govuk-fieldset')

Expand Down Expand Up @@ -336,12 +336,12 @@ describe('Date input', () => {

describe('when it includes a hint', () => {
it('renders the hint', () => {
const $ = render('date-input', examples['with errors'])
const $ = render('date-input', examples['with errors and hint'])
expect(htmlWithClassName($, '.govuk-hint')).toMatchSnapshot()
})

it('associates the fieldset as "described by" the hint', () => {
const $ = render('date-input', examples['with errors'])
const $ = render('date-input', examples['with errors and hint'])

const $fieldset = $('.govuk-fieldset')
const $hint = $('.govuk-hint')
Expand All @@ -353,24 +353,45 @@ describe('Date input', () => {
expect($fieldset.attr('aria-describedby'))
.toMatch(hintId)
})

it('associates the fieldset as "described by" the hint and parent fieldset', () => {
const describedById = 'parent-fieldset-hint-id'
const params = examples['with errors and hint']

params.fieldset.describedBy = describedById

const $ = render('date-input', params)

const $fieldset = $('.govuk-fieldset')
const $hint = $('.govuk-hint')

const hintId = new RegExp(
WORD_BOUNDARY + describedById + WHITESPACE + $hint.attr('id') + WORD_BOUNDARY
)

expect($fieldset.attr('aria-describedby'))
.toMatch(hintId)

delete params.fieldset.describedBy
})
})

describe('when it includes an error message', () => {
it('renders the error message', () => {
const $ = render('date-input', examples['with errors'])
const $ = render('date-input', examples['with errors only'])
expect(htmlWithClassName($, '.govuk-error-message')).toMatchSnapshot()
})

it('uses the id as a prefix for the error message id', () => {
const $ = render('date-input', examples['with errors'])
const $ = render('date-input', examples['with errors only'])

const $errorMessage = $('.govuk-error-message')

expect($errorMessage.attr('id')).toEqual('dob-errors-error')
})

it('associates the fieldset as "described by" the error message', () => {
const $ = render('date-input', examples['with errors'])
const $ = render('date-input', examples['with errors only'])

const $fieldset = $('.govuk-fieldset')
const $errorMessage = $('.govuk-error-message')
Expand All @@ -383,6 +404,27 @@ describe('Date input', () => {
.toMatch(errorMessageId)
})

it('associates the fieldset as "described by" the error message and parent fieldset', () => {
const describedById = 'parent-fieldset-hint-id'
const params = examples['with errors only']

params.fieldset.describedBy = describedById

const $ = render('date-input', params)

const $fieldset = $('.govuk-fieldset')
const $errorMessage = $('.govuk-error-message')

const errorMessageId = new RegExp(
WORD_BOUNDARY + describedById + WHITESPACE + $errorMessage.attr('id') + WORD_BOUNDARY
)

expect($fieldset.attr('aria-describedby'))
.toMatch(errorMessageId)

delete params.fieldset.describedBy
})

it('renders with a form group wrapper that has an error state', () => {
const $ = render('date-input', {
errorMessage: {
Expand All @@ -397,14 +439,34 @@ describe('Date input', () => {

describe('when they include both a hint and an error message', () => {
it('associates the fieldset as described by both the hint and the error message', () => {
const $ = render('date-input', examples['with errors'])
const $ = render('date-input', examples['with errors and hint'])

const $fieldset = $('.govuk-fieldset')
const errorMessageId = $('.govuk-error-message').attr('id')
const hintId = $('.govuk-hint').attr('id')

const combinedIds = new RegExp(
WORD_BOUNDARY + hintId + WHITESPACE + errorMessageId + WORD_BOUNDARY
)

expect($fieldset.attr('aria-describedby'))
.toMatch(combinedIds)
})

it('associates the fieldset as described by the hint, error message and parent fieldset', () => {
const describedById = 'parent-fieldset-hint-id'
const params = examples['with errors and hint']

params.fieldset.describedBy = describedById

const $ = render('date-input', params)

const $fieldset = $('.govuk-fieldset')
const $errorMessageId = $('.govuk-error-message').attr('id')
const $hintId = $('.govuk-hint').attr('id')
const errorMessageId = $('.govuk-error-message').attr('id')
const hintId = $('.govuk-hint').attr('id')

const combinedIds = new RegExp(
WORD_BOUNDARY + $hintId + WHITESPACE + $errorMessageId + WORD_BOUNDARY
WORD_BOUNDARY + describedById + WHITESPACE + hintId + WHITESPACE + errorMessageId + WORD_BOUNDARY
)

expect($fieldset.attr('aria-describedby'))
Expand Down
2 changes: 1 addition & 1 deletion src/components/fieldset/fieldset.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ params:
- name: describedBy
type: string
required: false
description: Text or element id to add to the `aria-describedby` attribute to provide description of the group of fields for screenreader users.
description: One or more element IDs to add to the `aria-describedby` attribute, used to provide additional descriptive information for screenreader users.
- name: legend
type: object
required: false
Expand Down
9 changes: 9 additions & 0 deletions src/components/fieldset/template.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,15 @@ describe('fieldset', () => {
expect($legend.text().trim()).toEqual('What is your address?')
})

it('allows you to set the initial aria-describedby attribute', () => {
const $ = render('fieldset', {
describedBy: 'parent-fieldset-hint-id'
})

const $component = $('.govuk-fieldset')
expect($component.attr('aria-describedby')).toEqual('parent-fieldset-hint-id')
})

it('escapes HTML in the text argument', () => {
const $ = render('fieldset', {
legend: {
Expand Down
4 changes: 4 additions & 0 deletions src/components/file-upload/file-upload.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ params:
type: string
required: false
description: Optional initial value of the input
- name: describedBy
type: string
required: false
description: One or more element IDs to add to the `aria-describedby` attribute, used to provide additional descriptive information for screenreader users.
- name: label
type: object
required: true
Expand Down
Loading

0 comments on commit 7f28b36

Please sign in to comment.