-
Notifications
You must be signed in to change notification settings - Fork 347
[SPIKE] Fix VoiceOver access to hidden Service Navigation menu button, initial HTML with data-aria-controls
#6342
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b5db129
394bb70
95a9e9a
f714395
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -109,7 +109,9 @@ describe('/components/service-navigation', () => { | |
| await expect( | ||
| render(page, 'service-navigation', examples.default, { | ||
| beforeInitialisation($root, { selector }) { | ||
| $root.querySelector(selector).removeAttribute('aria-controls') | ||
| $root | ||
| .querySelector(selector) | ||
| .removeAttribute('data-aria-controls') | ||
| }, | ||
| context: { | ||
| selector: toggleButtonSelector | ||
|
|
@@ -119,7 +121,7 @@ describe('/components/service-navigation', () => { | |
| cause: { | ||
| name: 'ElementError', | ||
| message: | ||
| 'govuk-service-navigation: Navigation button (`<button class="govuk-js-service-navigation-toggle">`) attribute (`aria-controls`) not found' | ||
| 'govuk-service-navigation: Navigation button (`<button class="govuk-js-service-navigation-toggle">`) attribute (`data-aria-controls`) not found' | ||
| } | ||
| }) | ||
| }) | ||
|
|
@@ -167,6 +169,15 @@ describe('/components/service-navigation', () => { | |
| expect(buttonHiddenAttribute).toBeFalsy() | ||
| }) | ||
|
|
||
| it('does not add an `aria-hidden` attribute to the button for Voice Over', async () => { | ||
| const buttonHiddenAttribute = await page.$eval( | ||
| toggleButtonSelector, | ||
| (el) => el.hasAttribute('aria-hidden') | ||
| ) | ||
|
|
||
| expect(buttonHiddenAttribute).toBeFalsy() | ||
| }) | ||
|
|
||
| it('renders the toggle button with `aria-expanded` set to false', async () => { | ||
| const toggleExpandedAttribute = await page.$eval( | ||
| toggleButtonSelector, | ||
|
|
@@ -175,6 +186,15 @@ describe('/components/service-navigation', () => { | |
|
|
||
| expect(toggleExpandedAttribute).toBe('false') | ||
| }) | ||
|
|
||
| it('adds the `aria-controls` attribute to the toggle', async () => { | ||
| const toggleExpandedAttribute = await page.$eval( | ||
| toggleButtonSelector, | ||
| (el) => el.getAttribute('aria-controls') | ||
| ) | ||
|
|
||
| expect(toggleExpandedAttribute).toBe('navigation') | ||
| }) | ||
| }) | ||
|
|
||
| describe('when toggle button is clicked once', () => { | ||
|
|
@@ -229,5 +249,97 @@ describe('/components/service-navigation', () => { | |
| expect(toggleExpandedAttribute).toBe('false') | ||
| }) | ||
| }) | ||
|
|
||
| describe('on wide viewports', () => { | ||
| beforeAll(async () => { | ||
| // First let's reset the viewport to a desktop one | ||
| await page.setViewport({ | ||
| width: 1280, | ||
| height: 720 | ||
| }) | ||
| }) | ||
|
|
||
| afterAll(async () => { | ||
| // After tests have run reset teh viewport to a phone | ||
| await page.emulate(iPhone) | ||
| }) | ||
|
|
||
| describe('on page load', () => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Depending on the outcome of the other thread, we'd also want a test checking for
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good spot! |
||
| beforeAll(async () => { | ||
| await render(page, 'service-navigation', examples.default) | ||
| }) | ||
|
|
||
| it('shows the navigation', async () => { | ||
| const navigationHiddenAttribute = await page.$eval( | ||
| navigationSelector, | ||
| (el) => el.hasAttribute('hidden') | ||
| ) | ||
|
|
||
| expect(navigationHiddenAttribute).toBeFalsy() | ||
| }) | ||
|
|
||
| it('keeps the toggle button hidden', async () => { | ||
| const buttonHiddenAttribute = await page.$eval( | ||
| toggleButtonSelector, | ||
| (el) => el.hasAttribute('hidden') | ||
| ) | ||
|
|
||
| expect(buttonHiddenAttribute).toBeTruthy() | ||
| }) | ||
|
|
||
| it('adds an `aria-hidden` attribute for Voice Over', async () => { | ||
| const buttonHiddenAttribute = await page.$eval( | ||
| toggleButtonSelector, | ||
| (el) => el.getAttribute('aria-hidden') | ||
| ) | ||
|
|
||
| expect(buttonHiddenAttribute).toBe('true') | ||
| }) | ||
| }) | ||
|
|
||
| describe('when page is resized to a narrow viewport', () => { | ||
| beforeAll(async () => { | ||
| await render(page, 'service-navigation', examples.default) | ||
|
|
||
| // Once the page is loaded resize the viewport to a narrow one | ||
| // Only set the width and height to avoid the page getting wiped | ||
| await page.setViewport({ | ||
| width: iPhone.viewport.width, | ||
| height: iPhone.viewport.height | ||
| }) | ||
|
|
||
| // Wait that the page got updated after the resize, | ||
| // as sometimes the tests run too early | ||
| await page.waitForSelector(`${navigationSelector}[hidden]`) | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. note This effectively doubles up with the first test within the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it worth expanding the comment to note that that's why there are 2 instances of
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Whoops, that's actually a copy paste in that first test. The selector in the first test should just be |
||
| }) | ||
|
|
||
| it('hides the navigation', async () => { | ||
| const navigationHiddenAttribute = await page.$eval( | ||
| `${navigationSelector}`, | ||
| (el) => el.hasAttribute('hidden') | ||
| ) | ||
|
|
||
| expect(navigationHiddenAttribute).toBeTruthy() | ||
| }) | ||
|
|
||
| it('reveals the toggle button', async () => { | ||
| const buttonHiddenAttribute = await page.$eval( | ||
| toggleButtonSelector, | ||
| (el) => el.hasAttribute('hidden') | ||
| ) | ||
|
|
||
| expect(buttonHiddenAttribute).toBeFalsy() | ||
| }) | ||
|
|
||
| it('removes the `aria-hidden` attribute on the toggle', async () => { | ||
| const buttonHiddenAttribute = await page.$eval( | ||
| toggleButtonSelector, | ||
| (el) => el.hasAttribute('aria-hidden') | ||
| ) | ||
|
|
||
| expect(buttonHiddenAttribute).toBeFalsy() | ||
| }) | ||
| }) | ||
| }) | ||
| }) | ||
| }) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When testing locally, if you remove
data-aria-controlsthen the class aborts early as it can't find the button, meaning the button doesn't show and the show/hide functionality doesn't get applied.Could we also check for
aria-controlsstill to avoid this being a breaking change?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
aria-controlsis unfortunately the source of the issue, as it's what makes VoiceOver still maintain access to the button even when it has thehiddenattribute. This means the button cannot have thearia-controlsattribute in the HTML.I'd like to propose a different approach where the button is not in the HTML at all and injected by JavaScript, using the ID of the menu to fill
aria-controls. However, that's a whole separate piece of work.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortuantely unless we're willing to release that breakage and stop the mobile menu working then we'll have to delay this being resolved 😕 It also means we can't backport this, but it sounds like we can't anyway since this either will break the design on mobile or won't actually solve the problem.
Sounds like this isn't backportable but we should make a call now on if we release it in v6 or not.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the JavaScript change remains backportable if it uses
aria-controlsinstead of the newdata-aria-controlsfor v5. It's not as thorough a fix as for v6 as if JavaScript does not load, thehiddenmenu button is still accessible through Voice Over, but still an improvement on the situation that it gets hidden properly once JavaScript kicks in 🤔There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding v6, I guess our choices are:
data-aria-controlswould get a fix when JavaScript is loaded, but not if it doesn't load. Then in v7, we'd remove the support foraria-controls.aria-controlsI'll bring this up at dev catch-up as we're once more in the blurry land of deprecations at the time of a breaking release.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trying to write a CHANGELOG for this to guide users' updates, I'm wondering if it'd be easier to remove the button than the attribute 😓
We could still ease the update path by checking if a button already exists, and backport to v5 with the same caveat that the fix is only when JavaScript has loaded.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The thing I'm still not 100% is if it's an incrimental improvement or if the fact that
aria-controlswill still be there in v5 means that it's almost not worth doing. Thinking about your comment above:For the purpose of having a way forward, my proposal is: