-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Checkboxes and radios with revealed option accessibility (#478)
* Added accessibity helpers for radios and checkboxes * Added unit tests for radios and checkboxes with other input accessibility
- Loading branch information
Showing
9 changed files
with
294 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import domready from 'js/domready'; | ||
import Checkboxes from './checkboxes'; | ||
|
||
domready(() => new Checkboxes('js-checkbox')); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
export default class Checkboxes { | ||
constructor(inputCls) { | ||
this.inputs = [...document.querySelectorAll(`.${inputCls}`)]; | ||
this.inputs.forEach(input => input.addEventListener('change', this.setExpandedAttributes.bind(this))); | ||
this.setExpandedAttributes(); | ||
} | ||
|
||
setExpandedAttributes() { | ||
this.inputs.filter(input => input.hasAttribute('aria-haspopup')).forEach(input => input.setAttribute('aria-expanded', input.checked)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{% from "components/radios/_macro.njk" import onsRadios %} | ||
|
||
{{ onsRadios(params) }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import domready from 'js/domready'; | ||
import Radios from 'components/checkboxes/checkboxes'; | ||
|
||
domready(() => new Radios('js-radio')); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
import { awaitPolyfills } from 'js/polyfills/await-polyfills'; | ||
import template from 'components/checkboxes/_test-template.njk'; | ||
import Checkboxes from 'components/checkboxes/checkboxes'; | ||
|
||
const params = { | ||
legend: 'What are your favourite pizza toppings?', | ||
dontVisuallyHideLegend: true, | ||
checkboxesLabel: 'Select all that apply', | ||
name: 'food-other', | ||
checkboxes: [ | ||
{ | ||
id: 'bacon-other', | ||
label: { | ||
text: 'Bacon' | ||
}, | ||
value: 'bacon' | ||
}, | ||
{ | ||
id: 'olives-other', | ||
label: { | ||
text: 'Olives' | ||
}, | ||
value: 'olives' | ||
}, | ||
{ | ||
id: 'other-checkbox', | ||
label: { | ||
text: 'Other', | ||
description: 'An answer is required' | ||
}, | ||
value: 'other', | ||
other: { | ||
id: 'other-textbox', | ||
name: 'other-answer', | ||
label: { | ||
text: 'Please specify other' | ||
} | ||
} | ||
} | ||
] | ||
}; | ||
|
||
describe('Component: Checkboxes', function() { | ||
before(() => awaitPolyfills); | ||
|
||
beforeEach(function() { | ||
const component = renderComponent(params); | ||
|
||
Object.keys(component).forEach(key => { | ||
this[key] = component[key]; | ||
}); | ||
}); | ||
|
||
describe('Before the component initialises', function() { | ||
it('if a checkbox has an other option, it should be given the correct aria-attributes', function() { | ||
expect(this.checkboxWithOther.hasAttribute('aria-haspopup')).to.equal(true); | ||
expect(this.checkboxWithOther.getAttribute('aria-haspopup')).to.equal('true'); | ||
expect(this.checkboxWithOther.hasAttribute('aria-controls')).to.equal(true); | ||
expect(this.checkboxWithOther.getAttribute('aria-controls')).to.equal( | ||
`${params.checkboxes[params.checkboxes.length - 1].id}-other-wrap` | ||
); | ||
}); | ||
}); | ||
|
||
describe('When the component initialises', function() { | ||
beforeEach(function() { | ||
new Checkboxes('js-checkbox'); | ||
}); | ||
|
||
it('checkboxes with other options should be given aria-expanded attributes', function() { | ||
expect(this.checkboxWithOther.hasAttribute('aria-expanded')).to.equal(true); | ||
expect(this.checkboxWithOther.getAttribute('aria-expanded')).to.equal('false'); | ||
}); | ||
|
||
describe('and a checkbox with an other input is checked', function() { | ||
beforeEach(function() { | ||
this.checkboxWithOther.click(); | ||
}); | ||
|
||
// eslint-disable-next-line prettier/prettier | ||
it('it\'s aria-expanded attribute should be set to true', function() { | ||
expect(this.checkboxWithOther.getAttribute('aria-expanded')).to.equal('true'); | ||
}); | ||
|
||
describe('and any other checkbox is changed', function() { | ||
beforeEach(function() { | ||
this.checkboxes[0].click(); | ||
}); | ||
|
||
// eslint-disable-next-line prettier/prettier | ||
it('the checkbox with an other input\'s aria-expanded attribute not change', function() { | ||
expect(this.checkboxWithOther.getAttribute('aria-expanded')).to.equal('true'); | ||
}); | ||
}); | ||
|
||
describe('and a checkbox with an other input is unchecked', function() { | ||
beforeEach(function() { | ||
this.checkboxWithOther.click(); | ||
}); | ||
|
||
// eslint-disable-next-line prettier/prettier | ||
it('it\'s aria-expanded attribute should be set to false', function() { | ||
expect(this.checkboxWithOther.getAttribute('aria-expanded')).to.equal('false'); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
|
||
function renderComponent(params) { | ||
const html = template.render({ params }); | ||
|
||
const wrapper = document.createElement('div'); | ||
wrapper.innerHTML = html; | ||
document.body.appendChild(wrapper); | ||
|
||
const checkboxes = [...wrapper.querySelectorAll('.js-checkbox')]; | ||
const checkboxWithOther = checkboxes[checkboxes.length - 1]; | ||
|
||
return { | ||
wrapper, | ||
checkboxes, | ||
checkboxWithOther | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
import { awaitPolyfills } from 'js/polyfills/await-polyfills'; | ||
import template from 'components/radios/_test-template.njk'; | ||
import Radios from 'components/checkboxes/checkboxes'; | ||
|
||
const params = { | ||
name: 'contact-preference', | ||
radios: [ | ||
{ | ||
id: 'email', | ||
value: 'email', | ||
label: { | ||
text: 'Email' | ||
}, | ||
other: { | ||
type: 'email', | ||
label: { | ||
text: 'Enter your email address' | ||
} | ||
} | ||
}, | ||
{ | ||
id: 'phone', | ||
value: 'phone', | ||
label: { | ||
text: 'Phone' | ||
}, | ||
other: { | ||
type: 'tel', | ||
label: { | ||
text: 'Enter your phone number' | ||
} | ||
} | ||
}, | ||
{ | ||
id: 'text', | ||
value: 'text', | ||
label: { | ||
text: 'Text' | ||
}, | ||
other: { | ||
type: 'tel', | ||
label: { | ||
text: 'Enter your phone number' | ||
} | ||
} | ||
} | ||
] | ||
}; | ||
|
||
describe('Component: Radios', function() { | ||
before(() => awaitPolyfills); | ||
|
||
beforeEach(function() { | ||
const component = renderComponent(params); | ||
|
||
Object.keys(component).forEach(key => { | ||
this[key] = component[key]; | ||
}); | ||
}); | ||
|
||
describe('Before the component initialises', function() { | ||
it('if a checkbox has an other option, it should be given the correct aria-attributes', function() { | ||
this.radios.forEach(radio => { | ||
expect(radio.hasAttribute('aria-haspopup')).to.equal(true); | ||
expect(radio.getAttribute('aria-haspopup')).to.equal('true'); | ||
expect(radio.hasAttribute('aria-controls')).to.equal(true); | ||
expect(radio.getAttribute('aria-controls')).to.equal(`${radio.id}-other-wrap`); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('When the component initialises', function() { | ||
beforeEach(function() { | ||
new Radios('js-radio'); | ||
}); | ||
|
||
it('checkboxes with other options should be given aria-expanded attributes', function() { | ||
this.radios.forEach(radio => { | ||
expect(radio.hasAttribute('aria-expanded')).to.equal(true); | ||
expect(radio.getAttribute('aria-expanded')).to.equal('false'); | ||
}); | ||
}); | ||
|
||
describe('and a radio checked', function() { | ||
beforeEach(function() { | ||
this.radios[0].click(); | ||
}); | ||
|
||
// eslint-disable-next-line prettier/prettier | ||
it('then the checked radio\'s aria-expanded attribute should be set to true', function() { | ||
expect(this.radios[0].getAttribute('aria-expanded')).to.equal('true'); | ||
}); | ||
|
||
// eslint-disable-next-line prettier/prettier | ||
it('then the unchecked radios\' aria-expanded attribute should be set to false', function() { | ||
this.radios | ||
.filter(radio => !radio.checked) | ||
.forEach(radio => { | ||
expect(radio.getAttribute('aria-expanded')).to.equal('false'); | ||
}); | ||
}); | ||
|
||
describe('and the radio selection is changed', function() { | ||
beforeEach(function() { | ||
this.radios[1].click(); | ||
}); | ||
|
||
// eslint-disable-next-line prettier/prettier | ||
it('then the checked radio\'s aria-expanded attribute should be set to true', function() { | ||
expect(this.radios[1].getAttribute('aria-expanded')).to.equal('true'); | ||
}); | ||
|
||
// eslint-disable-next-line prettier/prettier | ||
it('then the unchecked radios\' aria-expanded attribute should be set to false', function() { | ||
this.radios | ||
.filter(radio => !radio.checked) | ||
.forEach(radio => { | ||
expect(radio.getAttribute('aria-expanded')).to.equal('false'); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
|
||
function renderComponent(params) { | ||
const html = template.render({ params }); | ||
|
||
const wrapper = document.createElement('div'); | ||
wrapper.innerHTML = html; | ||
document.body.appendChild(wrapper); | ||
|
||
const radios = [...wrapper.querySelectorAll('.js-radio')]; | ||
|
||
return { | ||
wrapper, | ||
radios | ||
}; | ||
} |