Skip to content

Commit

Permalink
Refactor character count to inject new element
Browse files Browse the repository at this point in the history
Changes the character count to inject a new element that is
live updated, instead of overwriting the content of the initial
HTML hint.

This is intended to resolve an issue where screen readers are
reading out all elements referenced in aria-describedby for
each keystroke, and not just the updated counter element.

#2539
  • Loading branch information
querkmachine committed Mar 31, 2022
1 parent 651ad62 commit d358a05
Show file tree
Hide file tree
Showing 5 changed files with 39 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ exports[`Character count when it includes a hint renders with hint 1`] = `
</div>
<div id="with-hint-info"
class="govuk-hint govuk-character-count__message"
aria-live="polite"
>
You can enter up to 10 characters
</div>
Expand Down
22 changes: 17 additions & 5 deletions src/govuk/components/character-count/character-count.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ function CharacterCount ($module) {
this.$module = $module
this.$textarea = $module.querySelector('.govuk-js-character-count')
if (this.$textarea) {
this.$countMessage = document.getElementById(this.$textarea.id + '-info')
this.$fallbackLimitMessage = document.getElementById(this.$textarea.id + '-info')
}
}

Expand All @@ -20,15 +20,27 @@ CharacterCount.prototype.init = function () {
// Check for module
var $module = this.$module
var $textarea = this.$textarea
var $countMessage = this.$countMessage
var $fallbackLimitMessage = this.$fallbackLimitMessage

if (!$textarea || !$countMessage) {
if (!$textarea) {
return
}

// We move count message right after the field
// We move fallback count message right after the field
// Kept for backwards compatibility
$textarea.insertAdjacentElement('afterend', $countMessage)
$textarea.insertAdjacentElement('afterend', $fallbackLimitMessage)

// Create our live-updating counter element, copying the classes from the
// fallback element for backwards compatibility as these may have been configured
var $countMessage = document.createElement('div')
$countMessage.className = $fallbackLimitMessage.className
$countMessage.classList.add('govuk-character-count__status')
$countMessage.setAttribute('aria-live', 'polite')
this.$countMessage = $countMessage
$fallbackLimitMessage.insertAdjacentElement('afterend', $countMessage)

// Hide the fallback limit message
$fallbackLimitMessage.classList.add('govuk-visually-hidden')

// Read options set using dataset ('data-' values)
this.options = this.getDataset($module)
Expand Down
42 changes: 21 additions & 21 deletions src/govuk/components/character-count/character-count.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@ describe('Character count', () => {
it('shows the dynamic message', async () => {
await goToExample()

const message = await page.$eval('.govuk-character-count__message', el => el.innerHTML.trim())
const message = await page.$eval('.govuk-character-count__status', el => el.innerHTML.trim())

expect(message).toEqual('You have 10 characters remaining')
})

it('shows the characters remaining if the field is pre-filled', async () => {
await goToExample('with-default-value')

const message = await page.$eval('.govuk-character-count__message', el => el.innerHTML.trim())
const message = await page.$eval('.govuk-character-count__status', el => el.innerHTML.trim())

expect(message).toEqual('You have 67 characters remaining')
})
Expand All @@ -52,7 +52,7 @@ describe('Character count', () => {
await goToExample()
await page.type('.govuk-js-character-count', 'A')

const message = await page.$eval('.govuk-character-count__message', el => el.innerHTML.trim())
const message = await page.$eval('.govuk-character-count__status', el => el.innerHTML.trim())

expect(message).toEqual('You have 9 characters remaining')
})
Expand All @@ -61,7 +61,7 @@ describe('Character count', () => {
await goToExample()
await page.type('.govuk-js-character-count', 'A'.repeat(9))

const message = await page.$eval('.govuk-character-count__message', el => el.innerHTML.trim())
const message = await page.$eval('.govuk-character-count__status', el => el.innerHTML.trim())

expect(message).toEqual('You have 1 character remaining')
})
Expand All @@ -73,14 +73,14 @@ describe('Character count', () => {
})

it('shows the number of characters over the limit', async () => {
const message = await page.$eval('.govuk-character-count__message', el => el.innerHTML.trim())
const message = await page.$eval('.govuk-character-count__status', el => el.innerHTML.trim())
expect(message).toEqual('You have 1 character too many')
})

it('uses the plural when the limit is exceeded by 2 or more', async () => {
await page.type('.govuk-js-character-count', 'A')

const message = await page.$eval('.govuk-character-count__message', el => el.innerHTML.trim())
const message = await page.$eval('.govuk-character-count__status', el => el.innerHTML.trim())
expect(message).toEqual('You have 2 characters too many')
})

Expand All @@ -90,7 +90,7 @@ describe('Character count', () => {
})

it('adds error styles to the count message', async () => {
const messageClasses = await page.$eval('.govuk-character-count__message', el => el.className)
const messageClasses = await page.$eval('.govuk-character-count__status', el => el.className)
expect(messageClasses).toContain('govuk-error-message')
})
})
Expand All @@ -101,7 +101,7 @@ describe('Character count', () => {
})

it('shows the number of characters over the limit', async () => {
const message = await page.$eval('.govuk-character-count__message', el => el.innerHTML.trim())
const message = await page.$eval('.govuk-character-count__status', el => el.innerHTML.trim())
expect(message).toEqual('You have 23 characters too many')
})

Expand All @@ -111,7 +111,7 @@ describe('Character count', () => {
})

it('adds error styles to the count message', async () => {
const messageClasses = await page.$eval('.govuk-character-count__message', el => el.className)
const messageClasses = await page.$eval('.govuk-character-count__status', el => el.className)
expect(messageClasses).toContain('govuk-error-message')
})
})
Expand All @@ -122,22 +122,22 @@ describe('Character count', () => {
})

it('does not show the limit until the threshold is reached', async () => {
const visibility = await page.$eval('.govuk-character-count__message', el => window.getComputedStyle(el).visibility)
const visibility = await page.$eval('.govuk-character-count__status', el => window.getComputedStyle(el).visibility)
expect(visibility).toEqual('hidden')

// Ensure threshold is hidden for users of assistive technologies
const ariaHidden = await page.$eval('.govuk-character-count__message', el => el.getAttribute('aria-hidden'))
const ariaHidden = await page.$eval('.govuk-character-count__status', el => el.getAttribute('aria-hidden'))
expect(ariaHidden).toEqual('true')
})

it('becomes visible once the threshold is reached', async () => {
await page.type('.govuk-js-character-count', 'A'.repeat(8))

const visibility = await page.$eval('.govuk-character-count__message', el => window.getComputedStyle(el).visibility)
const visibility = await page.$eval('.govuk-character-count__status', el => window.getComputedStyle(el).visibility)
expect(visibility).toEqual('visible')

// Ensure threshold is visible for users of assistive technologies
const ariaHidden = await page.$eval('.govuk-character-count__message', el => el.getAttribute('aria-hidden'))
const ariaHidden = await page.$eval('.govuk-character-count__status', el => el.getAttribute('aria-hidden'))
expect(ariaHidden).toBeFalsy()
})
})
Expand All @@ -147,7 +147,7 @@ describe('Character count', () => {
it('still works correctly', async () => {
await goToExample('with-id-starting-with-number')

const message = await page.$eval('.govuk-character-count__message', el => el.innerHTML.trim())
const message = await page.$eval('.govuk-character-count__status', el => el.innerHTML.trim())

expect(message).toEqual('You have 10 characters remaining')
})
Expand All @@ -157,7 +157,7 @@ describe('Character count', () => {
it('still works correctly', async () => {
await goToExample('with-id-with-special-characters')

const message = await page.$eval('.govuk-character-count__message', el => el.innerHTML.trim())
const message = await page.$eval('.govuk-character-count__status', el => el.innerHTML.trim())

expect(message).toEqual('You have 10 characters remaining')
})
Expand All @@ -168,7 +168,7 @@ describe('Character count', () => {
it('shows the dynamic message', async () => {
await goToExample('with-word-count')

const message = await page.$eval('.govuk-character-count__message', el => el.innerHTML.trim())
const message = await page.$eval('.govuk-character-count__status', el => el.innerHTML.trim())

expect(message).toEqual('You have 10 words remaining')
})
Expand All @@ -177,7 +177,7 @@ describe('Character count', () => {
await goToExample('with-word-count')
await page.type('.govuk-js-character-count', 'Hello world')

const message = await page.$eval('.govuk-character-count__message', el => el.innerHTML.trim())
const message = await page.$eval('.govuk-character-count__status', el => el.innerHTML.trim())

expect(message).toEqual('You have 8 words remaining')
})
Expand All @@ -186,7 +186,7 @@ describe('Character count', () => {
await goToExample('with-word-count')
await page.type('.govuk-js-character-count', 'Hello '.repeat(9))

const message = await page.$eval('.govuk-character-count__message', el => el.innerHTML.trim())
const message = await page.$eval('.govuk-character-count__status', el => el.innerHTML.trim())

expect(message).toEqual('You have 1 word remaining')
})
Expand All @@ -198,14 +198,14 @@ describe('Character count', () => {
})

it('shows the number of words over the limit', async () => {
const message = await page.$eval('.govuk-character-count__message', el => el.innerHTML.trim())
const message = await page.$eval('.govuk-character-count__status', el => el.innerHTML.trim())
expect(message).toEqual('You have 1 word too many')
})

it('uses the plural when the limit is exceeded by 2 or more', async () => {
await page.type('.govuk-js-character-count', 'World')

const message = await page.$eval('.govuk-character-count__message', el => el.innerHTML.trim())
const message = await page.$eval('.govuk-character-count__status', el => el.innerHTML.trim())
expect(message).toEqual('You have 2 words too many')
})

Expand All @@ -215,7 +215,7 @@ describe('Character count', () => {
})

it('adds error styles to the count message', async () => {
const messageClasses = await page.$eval('.govuk-character-count__message', el => el.className)
const messageClasses = await page.$eval('.govuk-character-count__status', el => el.className)
expect(messageClasses).toContain('govuk-error-message')
})
})
Expand Down
5 changes: 1 addition & 4 deletions src/govuk/components/character-count/template.njk
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,6 @@
{{ govukHint({
text: 'You can enter up to ' + (params.maxlength or params.maxwords) + (' words' if params.maxwords else ' characters'),
id: params.id + '-info',
classes: 'govuk-character-count__message' + (' ' + params.countMessage.classes if params.countMessage.classes),
attributes: {
'aria-live': 'polite'
}
classes: 'govuk-character-count__message' + (' ' + params.countMessage.classes if params.countMessage.classes)
}) }}
</div>
7 changes: 0 additions & 7 deletions src/govuk/components/character-count/template.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,13 +114,6 @@ describe('Character count', () => {
const $countMessage = $('.govuk-character-count__message')
expect($countMessage.hasClass('app-custom-count-message')).toBeTruthy()
})

it('renders with aria live set to polite', () => {
const $ = render('character-count', examples.default)

const $countMessage = $('.govuk-character-count__message')
expect($countMessage.attr('aria-live')).toEqual('polite')
})
})

describe('when it has the spellcheck attribute', () => {
Expand Down

0 comments on commit d358a05

Please sign in to comment.