From 6c37f472221a99d78144bcbcf9f6287c494e84ab Mon Sep 17 00:00:00 2001 From: katarzynatobis <60467496+katarzynatobis@users.noreply.github.com> Date: Tue, 3 Jan 2023 12:24:38 +0100 Subject: [PATCH] Add label to Accordion (#2614) * add 'aria-label' and 'aria-labelledby' to Accordion * cleanup a11y tests * add mutationobserver-shim * update Accordion a11y failing test * improve Accordion expand/collapse tests --- package.json | 1 + scripts/testsPolyfill.js | 2 + .../accordion/Accordion.a11y.spec.jsx | 75 +++++++++++++------ src/components/accordion/Accordion.jsx | 6 ++ .../accordion/stories/rules.a11y.js | 6 +- yarn.lock | 70 +++++++++-------- 6 files changed, 107 insertions(+), 53 deletions(-) diff --git a/package.json b/package.json index d87550cb4..a5a92a10d 100644 --- a/package.json +++ b/package.json @@ -110,6 +110,7 @@ "jest": "^24.0.0", "jest-axe": "^5.0.1", "mini-css-extract-plugin": "1.6.2", + "mutationobserver-shim": "^0.3.7", "polished": "^4.0.3", "postcss": "^8.4.5", "postcss-loader": "^4.1.0", diff --git a/scripts/testsPolyfill.js b/scripts/testsPolyfill.js index b84a13532..383b29cee 100644 --- a/scripts/testsPolyfill.js +++ b/scripts/testsPolyfill.js @@ -1,3 +1,5 @@ +import 'mutationobserver-shim'; + global.requestAnimationFrame = callback => { setTimeout(callback, 0); }; diff --git a/src/components/accordion/Accordion.a11y.spec.jsx b/src/components/accordion/Accordion.a11y.spec.jsx index 82582643a..9b27e0b9a 100644 --- a/src/components/accordion/Accordion.a11y.spec.jsx +++ b/src/components/accordion/Accordion.a11y.spec.jsx @@ -1,25 +1,11 @@ import * as React from 'react'; -import {render, waitForElementToBeRemoved} from '@testing-library/react'; +import {render, waitFor, fireEvent} from '@testing-library/react'; import Accordion from './Accordion'; import AccordionItem from './AccordionItem'; import {testA11y} from '../../axe'; import userEvent from '@testing-library/user-event'; -describe('Accordion a11y', () => { - it('renders accordion with expanded and collapsed items', async () => { - const accordionIds = ['id-1', 'id-2']; - - await testA11y( - - {accordionIds.map(id => ( - - Accordion Item Description - - ))} - - ); - }); - +describe('Accordion', () => { it('renders with named items', () => { const title = 'Item_1'; const accordion = render( @@ -40,7 +26,7 @@ describe('Accordion a11y', () => { ); }); - it('expands and collapses item on Enter/Space keydown', async () => { + it('expands and collapses item on click', async () => { const accordionId = 'id-1'; const accordion = render( @@ -52,17 +38,64 @@ describe('Accordion a11y', () => { const item = accordion.getByRole('button'); expect(item.getAttribute('aria-expanded')).toEqual('false'); - expect(accordion.queryByRole('region')).toBeFalsy(); + expect(accordion.queryByRole('region')).toBeNull(); + accordion.getByRole('button').click(); + + expect(item.getAttribute('aria-expanded')).toEqual('true'); + await waitFor(() => expect(accordion.getByRole('region')).toBeTruthy()); + + accordion.getByRole('button').click(); + + expect(item.getAttribute('aria-expanded')).toEqual('false'); + fireEvent(accordion.queryByRole('region'), new Event('transitionend')); + await waitFor(() => expect(accordion.queryByRole('region')).toBeNull()); + }); + + it('expands and collapses item on Enter/Space keydown when motion is reduced', async () => { + const accordionId = 'id-1'; + const accordion = render( + + + Accordion Item Description + + + ); + const item = accordion.getByRole('button'); + + expect(item.getAttribute('aria-expanded')).toEqual('false'); + expect(accordion.queryByRole('region')).toBeNull(); accordion.getByRole('button').focus(); expect(item).toEqual(document.activeElement); - userEvent.keyboard('{enter}'); + userEvent.keyboard('{enter}'); expect(item.getAttribute('aria-expanded')).toEqual('true'); expect(accordion.getByRole('region')).toBeTruthy(); userEvent.keyboard('{space}'); - expect(item.getAttribute('aria-expanded')).toEqual('false'); - waitForElementToBeRemoved(accordion.queryByRole('region')); + expect(accordion.queryByRole('region')).toBeNull(); + }); + + it('has an accessible name', () => { + const label = 'Accordion name'; + const accordion = render(); + + expect(accordion.getByLabelText(label)).toBeTruthy(); + }); +}); + +describe('Accordion a11y', () => { + it('should have no a11y violations when renders Accordion with expanded and collapsed items', async () => { + const accordionIds = ['id-1', 'id-2']; + + await testA11y( + + {accordionIds.map(id => ( + + Accordion Item Description + + ))} + + ); }); }); diff --git a/src/components/accordion/Accordion.jsx b/src/components/accordion/Accordion.jsx index a2aaf3bee..6049bc4dc 100644 --- a/src/components/accordion/Accordion.jsx +++ b/src/components/accordion/Accordion.jsx @@ -66,6 +66,8 @@ export type AccordionPropsType = $ReadOnly<{ expanded?: string | Array, defaultExpanded?: string | Array, onChange?: string => void, + 'aria-label'?: string, + 'aria-labelledby'?: string, }>; type ContextType = { @@ -89,6 +91,8 @@ const Accordion = ({ defaultExpanded, expanded, onChange, + 'aria-label': ariaLabel, + 'aria-labelledby': ariaLabelledby, }: AccordionPropsType) => { const wrapperRef = useRef(null); const isControlled = expanded !== undefined; @@ -305,6 +309,8 @@ const Accordion = ({ ), className )} + aria-label={ariaLabel} + aria-labelledby={ariaLabelledby} > {children} diff --git a/src/components/accordion/stories/rules.a11y.js b/src/components/accordion/stories/rules.a11y.js index f4c91920f..fd6505610 100644 --- a/src/components/accordion/stories/rules.a11y.js +++ b/src/components/accordion/stories/rules.a11y.js @@ -1,8 +1,10 @@ const rules = [ { pattern: 'Can have an accessible name.', - status: 'TO DO', - tests: 'TO DO', + comment: `Can be named by a label specified by aria-label prop or a value + (IDREF) set for the aria-labelledby prop that refers to an element.`, + status: 'DONE', + tests: 'DONE', }, ]; diff --git a/yarn.lock b/yarn.lock index 14c7ad301..81c5d5370 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@adobe/css-tools@^4.0.1": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.0.1.tgz#b38b444ad3aa5fedbb15f2f746dcd934226a12dd" + integrity sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g== + "@ampproject/remapping@^2.0.0": version "2.0.2" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.0.2.tgz#f3d9760bf30588c51408dbe7c05ff2bb13069307" @@ -4773,9 +4778,9 @@ resolve-from "^5.0.0" "@testing-library/dom@^8.0.0", "@testing-library/dom@^8.11.1": - version "8.11.1" - resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.11.1.tgz#03fa2684aa09ade589b460db46b4c7be9fc69753" - integrity sha512-3KQDyx9r0RKYailW2MiYrSSKEfH0GTkI51UGEvJenvcoDoeRYs0PZpi2SXqtnMClQvCqdtTTpOfFETDTVADpAg== + version "8.19.0" + resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.19.0.tgz#bd3f83c217ebac16694329e413d9ad5fdcfd785f" + integrity sha512-6YWYPPpxG3e/xOo6HIWwB/58HukkwIVTOaZ0VwdMVjhRUX/01E4FtQbck9GazOOj7MXHc5RBzMrU86iBJHbI+A== dependencies: "@babel/code-frame" "^7.10.4" "@babel/runtime" "^7.12.5" @@ -4801,35 +4806,36 @@ pretty-format "^27.0.2" "@testing-library/jest-dom@^5.16.4": - version "5.16.4" - resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.4.tgz#938302d7b8b483963a3ae821f1c0808f872245cd" - integrity sha512-Gy+IoFutbMQcky0k+bqqumXZ1cTGswLsFqmNLzNdSKkU9KGV2u9oXhukCbbJ9/LRPKiqwxEE8VpV/+YZlfkPUA== + version "5.16.5" + resolved "https://registry.yarnpkg.com/@testing-library/jest-dom/-/jest-dom-5.16.5.tgz#3912846af19a29b2dbf32a6ae9c31ef52580074e" + integrity sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA== dependencies: + "@adobe/css-tools" "^4.0.1" "@babel/runtime" "^7.9.2" "@types/testing-library__jest-dom" "^5.9.1" aria-query "^5.0.0" chalk "^3.0.0" - css "^3.0.0" css.escape "^1.5.1" dom-accessibility-api "^0.5.6" lodash "^4.17.15" redent "^3.0.0" "@testing-library/react-hooks@^8.0.0": - version "8.0.0" - resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-8.0.0.tgz#7d0164bffce4647f506039de0a97f6fcbd20f4bf" - integrity sha512-uZqcgtcUUtw7Z9N32W13qQhVAD+Xki2hxbTR461MKax8T6Jr8nsUvZB+vcBTkzY2nFvsUet434CsgF0ncW2yFw== + version "8.0.1" + resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-8.0.1.tgz#0924bbd5b55e0c0c0502d1754657ada66947ca12" + integrity sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g== dependencies: "@babel/runtime" "^7.12.5" react-error-boundary "^3.1.0" "@testing-library/react@^12.1.2": - version "12.1.2" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-12.1.2.tgz#f1bc9a45943461fa2a598bb4597df1ae044cfc76" - integrity sha512-ihQiEOklNyHIpo2Y8FREkyD1QAea054U0MVbwH1m8N9TxeFz+KoJ9LkqoKqJlzx2JDm56DVwaJ1r36JYxZM05g== + version "12.1.5" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-12.1.5.tgz#bb248f72f02a5ac9d949dea07279095fa577963b" + integrity sha512-OfTXCJUFgjd/digLUuPxa0+/3ZxsQmE7ub9kcbW/wi96Bh3o/p5vrETcBGfP17NWPGqeYYl5LTRpwyGoMC4ysg== dependencies: "@babel/runtime" "^7.12.5" "@testing-library/dom" "^8.0.0" + "@types/react-dom" "<18.0.0" "@testing-library/user-event@^13.2.1", "@testing-library/user-event@^13.5.0": version "13.5.0" @@ -5079,6 +5085,13 @@ resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== +"@types/react-dom@<18.0.0": + version "17.0.18" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.18.tgz#8f7af38f5d9b42f79162eea7492e5a1caff70dc2" + integrity sha512-rLVtIfbwyur2iFKykP2w0pl/1unw26b5td16d5xMgp7/yjTHomkyxPYChFoCr/FtEX1lN9wY6lFj1qvKdS5kDw== + dependencies: + "@types/react" "^17" + "@types/react-dom@^16.2.0": version "16.9.17" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.17.tgz#29100cbcc422d7b7dba7de24bb906de56680dd34" @@ -5095,6 +5108,15 @@ "@types/scheduler" "*" csstype "^3.0.2" +"@types/react@^17": + version "17.0.52" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.52.tgz#10d8b907b5c563ac014a541f289ae8eaa9bf2e9b" + integrity sha512-vwk8QqVODi0VaZZpDXQCmEmiOuyjEFPY7Ttaw5vjM112LOq37yz1CDJGrRJwA1fYEq4Iitd5rnjd1yWAc/bT+A== + dependencies: + "@types/prop-types" "*" + "@types/scheduler" "*" + csstype "^3.0.2" + "@types/scheduler@*": version "0.16.2" resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" @@ -8258,15 +8280,6 @@ css@2.X, css@^2.2.1: source-map-resolve "^0.5.2" urix "^0.1.0" -css@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/css/-/css-3.0.0.tgz#4447a4d58fdd03367c516ca9f64ae365cee4aa5d" - integrity sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ== - dependencies: - inherits "^2.0.4" - source-map "^0.6.1" - source-map-resolve "^0.6.0" - cssesc@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" @@ -14517,6 +14530,11 @@ mustache@^2.3.0: resolved "https://registry.yarnpkg.com/mustache/-/mustache-2.3.2.tgz#a6d4d9c3f91d13359ab889a812954f9230a3d0c5" integrity sha512-KpMNwdQsYz3O/SBS1qJ/o3sqUJ5wSb8gb0pul8CO0S56b9Y2ALm8zCfsjPXsqGFfoNBkDwZuZIAjhsZI03gYVQ== +mutationobserver-shim@^0.3.7: + version "0.3.7" + resolved "https://registry.yarnpkg.com/mutationobserver-shim/-/mutationobserver-shim-0.3.7.tgz#8bf633b0c0b0291a1107255ed32c13088a8c5bf3" + integrity sha512-oRIDTyZQU96nAiz2AQyngwx1e89iApl2hN5AOYwyxLUB47UYsU3Wv9lJWqH5y/QdiYkc5HQLi23ZNB3fELdHcQ== + mute-stdout@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/mute-stdout/-/mute-stdout-1.0.1.tgz#acb0300eb4de23a7ddeec014e3e96044b3472331" @@ -17909,14 +17927,6 @@ source-map-resolve@^0.5.0, source-map-resolve@^0.5.2: source-map-url "^0.4.0" urix "^0.1.0" -source-map-resolve@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.6.0.tgz#3d9df87e236b53f16d01e58150fc7711138e5ed2" - integrity sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w== - dependencies: - atob "^2.1.2" - decode-uri-component "^0.2.0" - source-map-support@^0.4.15: version "0.4.18" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f"