Skip to content

Commit f1299ba

Browse files
Merge branch 'main' into main
2 parents 711960d + 11fc773 commit f1299ba

File tree

5 files changed

+236
-9
lines changed

5 files changed

+236
-9
lines changed

.github/workflows/validate.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ name: validate
22
on:
33
push:
44
branches:
5-
- '+([0-9])?(.{+([0-9]),x}).x'
5+
# Match SemVer major release branches
6+
# e.g. "12.x" or "8.x"
7+
- '[0-9]+.x'
68
- 'main'
79
- 'next'
810
- 'next-major'
@@ -58,8 +60,7 @@ jobs:
5860
runs-on: ubuntu-latest
5961
if:
6062
${{ github.repository == 'testing-library/dom-testing-library' &&
61-
contains('refs/heads/main,refs/heads/beta,refs/heads/next,refs/heads/alpha',
62-
github.ref) && github.event_name == 'push' }}
63+
github.event_name == 'push' }}
6364
steps:
6465
- name: 🛑 Cancel Previous Runs
6566
uses: styfle/[email protected]

src/__tests__/role.js

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,3 +572,177 @@ test('should find the input using type property instead of attribute', () => {
572572
const {getByRole} = render('<input type="124">')
573573
expect(getByRole('textbox')).not.toBeNull()
574574
})
575+
576+
test('can be filtered by accessible description', () => {
577+
const targetedNotificationMessage = 'Your session is about to expire!'
578+
const {getByRole} = renderIntoDocument(
579+
`
580+
<ul>
581+
<li role="alertdialog" aria-describedby="notification-id-1">
582+
<div><button>Close</button></div>
583+
<div id="notification-id-1">You have unread emails</div>
584+
</li>
585+
<li role="alertdialog" aria-describedby="notification-id-2">
586+
<div><button>Close</button></div>
587+
<div id="notification-id-2">${targetedNotificationMessage}</div>
588+
</li>
589+
</ul>`,
590+
)
591+
592+
const notification = getByRole('alertdialog', {
593+
description: targetedNotificationMessage,
594+
})
595+
596+
expect(notification).not.toBeNull()
597+
expect(notification).toHaveTextContent(targetedNotificationMessage)
598+
599+
expect(
600+
getQueriesForElement(notification).getByRole('button', {name: 'Close'}),
601+
).not.toBeNull()
602+
})
603+
604+
test('error should include description when filtering and no results are found', () => {
605+
const targetedNotificationMessage = 'Your session is about to expire!'
606+
const {getByRole} = renderIntoDocument(
607+
`<div role="dialog" aria-describedby="some-id"><div><button>Close</button></div><div id="some-id">${targetedNotificationMessage}</div></div>`,
608+
)
609+
610+
expect(() =>
611+
getByRole('alertdialog', {description: targetedNotificationMessage}),
612+
).toThrowErrorMatchingInlineSnapshot(`
613+
Unable to find an accessible element with the role "alertdialog" and description "Your session is about to expire!"
614+
615+
Here are the accessible roles:
616+
617+
dialog:
618+
619+
Name "":
620+
Description "Your session is about to expire!":
621+
<div
622+
aria-describedby="some-id"
623+
role="dialog"
624+
/>
625+
626+
--------------------------------------------------
627+
button:
628+
629+
Name "Close":
630+
Description "":
631+
<button />
632+
633+
--------------------------------------------------
634+
635+
Ignored nodes: comments, <script />, <style />
636+
<body>
637+
<div
638+
aria-describedby="some-id"
639+
role="dialog"
640+
>
641+
<div>
642+
<button>
643+
Close
644+
</button>
645+
</div>
646+
<div
647+
id="some-id"
648+
>
649+
Your session is about to expire!
650+
</div>
651+
</div>
652+
</body>
653+
`)
654+
})
655+
656+
test('TextMatch serialization for description filter in error message', () => {
657+
const {getByRole} = renderIntoDocument(
658+
`<div role="alertdialog" aria-describedby="some-id"><div><button>Close</button></div><div id="some-id">Your session is about to expire!</div></div>`,
659+
)
660+
661+
expect(() => getByRole('alertdialog', {description: /unknown description/}))
662+
.toThrowErrorMatchingInlineSnapshot(`
663+
Unable to find an accessible element with the role "alertdialog" and description \`/unknown description/\`
664+
665+
Here are the accessible roles:
666+
667+
alertdialog:
668+
669+
Name "":
670+
Description "Your session is about to expire!":
671+
<div
672+
aria-describedby="some-id"
673+
role="alertdialog"
674+
/>
675+
676+
--------------------------------------------------
677+
button:
678+
679+
Name "Close":
680+
Description "":
681+
<button />
682+
683+
--------------------------------------------------
684+
685+
Ignored nodes: comments, <script />, <style />
686+
<body>
687+
<div
688+
aria-describedby="some-id"
689+
role="alertdialog"
690+
>
691+
<div>
692+
<button>
693+
Close
694+
</button>
695+
</div>
696+
<div
697+
id="some-id"
698+
>
699+
Your session is about to expire!
700+
</div>
701+
</div>
702+
</body>
703+
`)
704+
705+
expect(() => getByRole('alertdialog', {description: () => false}))
706+
.toThrowErrorMatchingInlineSnapshot(`
707+
Unable to find an accessible element with the role "alertdialog" and description \`() => false\`
708+
709+
Here are the accessible roles:
710+
711+
alertdialog:
712+
713+
Name "":
714+
Description "Your session is about to expire!":
715+
<div
716+
aria-describedby="some-id"
717+
role="alertdialog"
718+
/>
719+
720+
--------------------------------------------------
721+
button:
722+
723+
Name "Close":
724+
Description "":
725+
<button />
726+
727+
--------------------------------------------------
728+
729+
Ignored nodes: comments, <script />, <style />
730+
<body>
731+
<div
732+
aria-describedby="some-id"
733+
role="alertdialog"
734+
>
735+
<div>
736+
<button>
737+
Close
738+
</button>
739+
</div>
740+
<div
741+
id="some-id"
742+
>
743+
Your session is about to expire!
744+
</div>
745+
</div>
746+
</body>
747+
`)
748+
})

src/queries/role.js

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import {computeAccessibleName} from 'dom-accessibility-api'
1+
import {
2+
computeAccessibleDescription,
3+
computeAccessibleName,
4+
} from 'dom-accessibility-api'
25
import {roles as allRoles, roleElements} from 'aria-query'
36
import {
47
computeAriaSelected,
@@ -30,6 +33,7 @@ function queryAllByRole(
3033
collapseWhitespace,
3134
hidden = getConfig().defaultHidden,
3235
name,
36+
description,
3337
trim,
3438
normalizer,
3539
queryFallbacks = false,
@@ -170,6 +174,22 @@ function queryAllByRole(
170174
text => text,
171175
)
172176
})
177+
.filter(element => {
178+
if (description === undefined) {
179+
// Don't care
180+
return true
181+
}
182+
183+
return matches(
184+
computeAccessibleDescription(element, {
185+
computedStyleSupportsPseudoElements:
186+
getConfig().computedStyleSupportsPseudoElements,
187+
}),
188+
element,
189+
description,
190+
text => text,
191+
)
192+
})
173193
.filter(element => {
174194
return hidden === false
175195
? isInaccessible(element, {
@@ -217,7 +237,7 @@ const getMultipleError = (c, role, {name} = {}) => {
217237
const getMissingError = (
218238
container,
219239
role,
220-
{hidden = getConfig().defaultHidden, name} = {},
240+
{hidden = getConfig().defaultHidden, name, description} = {},
221241
) => {
222242
if (getConfig()._disableExpensiveErrorDiagnostics) {
223243
return `Unable to find role="${role}"`
@@ -227,7 +247,7 @@ const getMissingError = (
227247
Array.from(container.children).forEach(childElement => {
228248
roles += prettyRoles(childElement, {
229249
hidden,
230-
includeName: name !== undefined,
250+
includeDescription: description !== undefined,
231251
})
232252
})
233253
let roleMessage
@@ -258,10 +278,19 @@ Here are the ${hidden === false ? 'accessible' : 'available'} roles:
258278
nameHint = ` and name \`${name}\``
259279
}
260280

281+
let descriptionHint = ''
282+
if (description === undefined) {
283+
descriptionHint = ''
284+
} else if (typeof description === 'string') {
285+
descriptionHint = ` and description "${description}"`
286+
} else {
287+
descriptionHint = ` and description \`${description}\``
288+
}
289+
261290
return `
262291
Unable to find an ${
263292
hidden === false ? 'accessible ' : ''
264-
}element with the role "${role}"${nameHint}
293+
}element with the role "${role}"${nameHint}${descriptionHint}
265294
266295
${roleMessage}`.trim()
267296
}

src/role-helpers.js

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import {elementRoles} from 'aria-query'
2-
import {computeAccessibleName} from 'dom-accessibility-api'
2+
import {
3+
computeAccessibleDescription,
4+
computeAccessibleName,
5+
} from 'dom-accessibility-api'
36
import {prettyDOM} from './pretty-dom'
47
import {getConfig} from './config'
58

@@ -178,7 +181,7 @@ function getRoles(container, {hidden = false} = {}) {
178181
}, {})
179182
}
180183

181-
function prettyRoles(dom, {hidden}) {
184+
function prettyRoles(dom, {hidden, includeDescription}) {
182185
const roles = getRoles(dom, {hidden})
183186
// We prefer to skip generic role, we don't recommend it
184187
return Object.entries(roles)
@@ -191,7 +194,20 @@ function prettyRoles(dom, {hidden}) {
191194
computedStyleSupportsPseudoElements:
192195
getConfig().computedStyleSupportsPseudoElements,
193196
})}":\n`
197+
194198
const domString = prettyDOM(el.cloneNode(false))
199+
200+
if (includeDescription) {
201+
const descriptionString = `Description "${computeAccessibleDescription(
202+
el,
203+
{
204+
computedStyleSupportsPseudoElements:
205+
getConfig().computedStyleSupportsPseudoElements,
206+
},
207+
)}":\n`
208+
return `${nameString}${descriptionString}${domString}`
209+
}
210+
195211
return `${nameString}${domString}`
196212
})
197213
.join('\n\n')

types/queries.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,13 @@ export interface ByRoleOptions extends MatcherOptions {
115115
| RegExp
116116
| string
117117
| ((accessibleName: string, element: Element) => boolean)
118+
/**
119+
* Only considers elements with the specified accessible description.
120+
*/
121+
description?:
122+
| RegExp
123+
| string
124+
| ((accessibleDescription: string, element: Element) => boolean)
118125
}
119126

120127
export type AllByRole<T extends HTMLElement = HTMLElement> = (

0 commit comments

Comments
 (0)