Skip to content

Commit a42f42e

Browse files
Merge branch 'main' into feat/byrole-disabled
2 parents 876fef0 + d09b3c2 commit a42f42e

File tree

8 files changed

+312
-30
lines changed

8 files changed

+312
-30
lines changed

.github/workflows/validate.yml

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,17 @@ on:
1111
- 'beta'
1212
- 'alpha'
1313
- '!all-contributors/**'
14-
pull_request: {}
14+
pull_request:
15+
16+
concurrency:
17+
group: ${{ github.workflow }}-${{ github.ref }}
18+
cancel-in-progress: true
1519

1620
permissions: {}
1721

1822
jobs:
1923
main:
2024
permissions:
21-
actions: write # to cancel/stop running workflows (styfle/cancel-workflow-action)
2225
contents: read # to fetch code (actions/checkout)
2326
# ignore all-contributors PRs
2427
if: ${{ !contains(github.head_ref, 'all-contributors') }}
@@ -29,9 +32,6 @@ jobs:
2932
node: [14, 16, 18]
3033
runs-on: ubuntu-latest
3134
steps:
32-
- name: 🛑 Cancel Previous Runs
33-
uses: styfle/[email protected]
34-
3535
- name: ⬇️ Checkout repo
3636
uses: actions/checkout@v3
3737
with:
@@ -63,7 +63,6 @@ jobs:
6363

6464
release:
6565
permissions:
66-
actions: write # to cancel/stop running workflows (styfle/cancel-workflow-action)
6766
contents: write # to create release tags (cycjimmy/semantic-release-action)
6867
issues: write # to post release that resolves an issue
6968

@@ -73,9 +72,6 @@ jobs:
7372
${{ github.repository == 'testing-library/dom-testing-library' &&
7473
github.event_name == 'push' }}
7574
steps:
76-
- name: 🛑 Cancel Previous Runs
77-
uses: styfle/[email protected]
78-
7975
- name: ⬇️ Checkout repo
8076
uses: actions/checkout@v3
8177

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,11 @@
6565
"aria-query": "^5.0.0",
6666
"chalk": "^4.1.0",
6767
"dom-accessibility-api": "^0.5.9",
68-
"lz-string": "^1.4.4",
68+
"lz-string": "^1.5.0",
6969
"pretty-format": "^27.0.2"
7070
},
7171
"devDependencies": {
7272
"@testing-library/jest-dom": "^5.11.6",
73-
"@types/lz-string": "^1.3.34",
7473
"jest-in-case": "^1.0.2",
7574
"jest-snapshot-serializer-ansi": "^1.0.0",
7675
"jest-watch-select-projects": "^2.0.0",

src/__tests__/ariaAttributes.js

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,28 @@ test('`expanded` throws on unsupported roles', () => {
3636
)
3737
})
3838

39+
test('`busy` throws on unsupported roles', () => {
40+
const {getByRole} = render(
41+
`<div aria-busy="true" role="none">Hello, Dave!</div>`,
42+
)
43+
expect(() =>
44+
getByRole('none', {busy: true}),
45+
).toThrowErrorMatchingInlineSnapshot(
46+
`"aria-busy" is not supported on role "none".`,
47+
)
48+
})
49+
50+
test('`busy: true|false` matches `busy` regions', () => {
51+
const {getByRole} = renderIntoDocument(
52+
`<div>
53+
<div role="log" aria-busy="true" />
54+
<div role="log" aria-busy="false" />
55+
</div>`,
56+
)
57+
expect(getByRole('log', {busy: true})).toBeInTheDocument()
58+
expect(getByRole('log', {busy: false})).toBeInTheDocument()
59+
})
60+
3961
test('`checked: true|false` matches `checked` checkboxes', () => {
4062
const {getByRole} = renderIntoDocument(
4163
`<div>
@@ -237,6 +259,105 @@ test('`level` throws on unsupported roles', () => {
237259
)
238260
})
239261

262+
test('`value.now` throws on unsupported roles', () => {
263+
const {getByRole} = render(`<button aria-valuenow="1">Button</button>`)
264+
expect(() =>
265+
getByRole('button', {value: {now: 1}}),
266+
).toThrowErrorMatchingInlineSnapshot(
267+
`"aria-valuenow" is not supported on role "button".`,
268+
)
269+
})
270+
271+
test('`value.now: number` matches `aria-valuenow` on widgets', () => {
272+
const {getByRole} = renderIntoDocument(
273+
`<div>
274+
<button role="spinbutton" />
275+
<button role="spinbutton" aria-valuenow="5" />
276+
<button role="spinbutton" aria-valuenow="10" />
277+
</div>`,
278+
)
279+
expect(getByRole('spinbutton', {value: {now: 5}})).toBeInTheDocument()
280+
expect(getByRole('spinbutton', {value: {now: 10}})).toBeInTheDocument()
281+
})
282+
283+
test('`value.max` throws on unsupported roles', () => {
284+
const {getByRole} = render(`<button aria-valuemax="1">Button</button>`)
285+
expect(() =>
286+
getByRole('button', {value: {max: 1}}),
287+
).toThrowErrorMatchingInlineSnapshot(
288+
`"aria-valuemax" is not supported on role "button".`,
289+
)
290+
})
291+
292+
test('`value.max: number` matches `aria-valuemax` on widgets', () => {
293+
const {getByRole} = renderIntoDocument(
294+
`<div>
295+
<button role="spinbutton" />
296+
<button role="spinbutton" aria-valuemax="5" />
297+
<button role="spinbutton" aria-valuemax="10" />
298+
</div>`,
299+
)
300+
expect(getByRole('spinbutton', {value: {max: 5}})).toBeInTheDocument()
301+
expect(getByRole('spinbutton', {value: {max: 10}})).toBeInTheDocument()
302+
})
303+
304+
test('`value.min` throws on unsupported roles', () => {
305+
const {getByRole} = render(`<button aria-valuemin="1">Button</button>`)
306+
expect(() =>
307+
getByRole('button', {value: {min: 1}}),
308+
).toThrowErrorMatchingInlineSnapshot(
309+
`"aria-valuemin" is not supported on role "button".`,
310+
)
311+
})
312+
313+
test('`value.min: number` matches `aria-valuemin` on widgets', () => {
314+
const {getByRole} = renderIntoDocument(
315+
`<div>
316+
<button role="spinbutton" />
317+
<button role="spinbutton" aria-valuemin="5" />
318+
<button role="spinbutton" aria-valuemin="10" />
319+
</div>`,
320+
)
321+
expect(getByRole('spinbutton', {value: {min: 5}})).toBeInTheDocument()
322+
expect(getByRole('spinbutton', {value: {min: 10}})).toBeInTheDocument()
323+
})
324+
325+
test('`value.text` throws on unsupported roles', () => {
326+
const {getByRole} = render(`<button aria-valuetext="one">Button</button>`)
327+
expect(() =>
328+
getByRole('button', {value: {text: 'one'}}),
329+
).toThrowErrorMatchingInlineSnapshot(
330+
`"aria-valuetext" is not supported on role "button".`,
331+
)
332+
})
333+
334+
test('`value.text: Matcher` matches `aria-valuetext` on widgets', () => {
335+
const {getAllByRole, getByRole} = renderIntoDocument(
336+
`<div>
337+
<button role="spinbutton" />
338+
<button role="spinbutton" aria-valuetext="zero" />
339+
<button role="spinbutton" aria-valuetext="few" />
340+
<button role="spinbutton" aria-valuetext="many" />
341+
</div>`,
342+
)
343+
expect(getByRole('spinbutton', {value: {text: 'zero'}})).toBeInTheDocument()
344+
expect(getAllByRole('spinbutton', {value: {text: /few|many/}})).toHaveLength(
345+
2,
346+
)
347+
})
348+
349+
test('`value.*` must all match if specified', () => {
350+
const {getByRole} = renderIntoDocument(
351+
`<div>
352+
<button role="spinbutton" aria-valuemin="0" aria-valuenow="1" aria-valuemax="10" aria-valuetext="eins" />
353+
<button role="spinbutton" aria-valuemin="0" aria-valuenow="1" aria-valuemax="10" aria-valuetext="one" />
354+
</div>`,
355+
)
356+
expect(
357+
getByRole('spinbutton', {value: {now: 1, text: 'one'}}),
358+
).toBeInTheDocument()
359+
})
360+
240361
test('`expanded: true|false` matches `expanded` buttons', () => {
241362
const {getByRole} = renderIntoDocument(
242363
`<div>

src/__tests__/suggestions.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,15 @@ beforeAll(() => {
66
configure({throwSuggestions: true})
77
})
88

9+
beforeEach(() => {
10+
// We're testing suggestions of find* queries but we're not interested in their time-related behavior.
11+
// Real timers would make the test suite slower for no reason.
12+
jest.useFakeTimers()
13+
})
14+
915
afterEach(() => {
10-
configure({testIdAttribute: 'data-testid'})
16+
jest.useRealTimers()
17+
configure({testIdAttribute: 'data-testid', throwSuggestions: true})
1118
console.warn.mockClear()
1219
})
1320

@@ -103,6 +110,17 @@ test('should not suggest when suggest is turned off for a query', () => {
103110
).not.toThrowError()
104111
})
105112

113+
test('should suggest when suggest is turned on for a specific query but disabled in config', () => {
114+
configure({throwSuggestions: false})
115+
renderIntoDocument(`
116+
<button data-testid="foo">submit</button>
117+
<button data-testid="foot">another</button>`)
118+
119+
expect(() => screen.getByTestId('foo', {suggest: true})).toThrowError(
120+
"try this:\ngetByRole('button', { name: /submit/i })",
121+
)
122+
})
123+
106124
test('should suggest getByRole when used with getBy', () => {
107125
renderIntoDocument(`<button data-testid="foo">submit</button>`)
108126

src/queries/role.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable complexity */
12
import {
23
computeAccessibleDescription,
34
computeAccessibleName,
@@ -9,11 +10,16 @@ import {
910
} from 'aria-query'
1011
import {
1112
computeAriaSelected,
13+
computeAriaBusy,
1214
computeAriaChecked,
1315
computeAriaPressed,
1416
computeAriaCurrent,
1517
computeAriaDisabled,
1618
computeAriaExpanded,
19+
computeAriaValueNow,
20+
computeAriaValueMax,
21+
computeAriaValueMin,
22+
computeAriaValueText,
1723
computeHeadingLevel,
1824
getImplicitAriaRoles,
1925
prettyRoles,
@@ -43,12 +49,19 @@ const queryAllByRole: AllByRole = (
4349
description,
4450
queryFallbacks = false,
4551
selected,
52+
busy,
4653
checked,
4754
pressed,
4855
current,
4956
disabled,
5057
level,
5158
expanded,
59+
value: {
60+
now: valueNow,
61+
min: valueMin,
62+
max: valueMax,
63+
text: valueText,
64+
} = {} as NonNullable<ByRoleOptions['value']>,
5265
} = {},
5366
) => {
5467
checkContainerType(container)
@@ -63,6 +76,16 @@ const queryAllByRole: AllByRole = (
6376
}
6477
}
6578

79+
if (busy !== undefined) {
80+
// guard against unknown roles
81+
if (
82+
allRoles.get(role as ARIARoleDefinitionKey)?.props['aria-busy'] ===
83+
undefined
84+
) {
85+
throw new Error(`"aria-busy" is not supported on role "${role}".`)
86+
}
87+
}
88+
6689
if (checked !== undefined) {
6790
// guard against unknown roles
6891
if (
@@ -103,6 +126,46 @@ const queryAllByRole: AllByRole = (
103126
}
104127
}
105128

129+
if (valueNow !== undefined) {
130+
// guard against unknown roles
131+
if (
132+
allRoles.get(role as ARIARoleDefinitionKey)?.props['aria-valuenow'] ===
133+
undefined
134+
) {
135+
throw new Error(`"aria-valuenow" is not supported on role "${role}".`)
136+
}
137+
}
138+
139+
if (valueMax !== undefined) {
140+
// guard against unknown roles
141+
if (
142+
allRoles.get(role as ARIARoleDefinitionKey)?.props['aria-valuemax'] ===
143+
undefined
144+
) {
145+
throw new Error(`"aria-valuemax" is not supported on role "${role}".`)
146+
}
147+
}
148+
149+
if (valueMin !== undefined) {
150+
// guard against unknown roles
151+
if (
152+
allRoles.get(role as ARIARoleDefinitionKey)?.props['aria-valuemin'] ===
153+
undefined
154+
) {
155+
throw new Error(`"aria-valuemin" is not supported on role "${role}".`)
156+
}
157+
}
158+
159+
if (valueText !== undefined) {
160+
// guard against unknown roles
161+
if (
162+
allRoles.get(role as ARIARoleDefinitionKey)?.props['aria-valuetext'] ===
163+
undefined
164+
) {
165+
throw new Error(`"aria-valuetext" is not supported on role "${role}".`)
166+
}
167+
}
168+
106169
if (expanded !== undefined) {
107170
// guard against unknown roles
108171
if (
@@ -164,6 +227,9 @@ const queryAllByRole: AllByRole = (
164227
if (selected !== undefined) {
165228
return selected === computeAriaSelected(element)
166229
}
230+
if (busy !== undefined) {
231+
return busy === computeAriaBusy(element)
232+
}
167233
if (checked !== undefined) {
168234
return checked === computeAriaChecked(element)
169235
}
@@ -182,6 +248,33 @@ const queryAllByRole: AllByRole = (
182248
if (level !== undefined) {
183249
return level === computeHeadingLevel(element)
184250
}
251+
if (
252+
valueNow !== undefined ||
253+
valueMax !== undefined ||
254+
valueMin !== undefined ||
255+
valueText !== undefined
256+
) {
257+
let valueMatches = true
258+
if (valueNow !== undefined) {
259+
valueMatches &&= valueNow === computeAriaValueNow(element)
260+
}
261+
if (valueMax !== undefined) {
262+
valueMatches &&= valueMax === computeAriaValueMax(element)
263+
}
264+
if (valueMin !== undefined) {
265+
valueMatches &&= valueMin === computeAriaValueMin(element)
266+
}
267+
if (valueText !== undefined) {
268+
valueMatches &&= matches(
269+
computeAriaValueText(element) ?? null,
270+
element,
271+
valueText,
272+
text => text,
273+
)
274+
}
275+
276+
return valueMatches
277+
}
185278
// don't care if aria attributes are unspecified
186279
return true
187280
})

0 commit comments

Comments
 (0)