Skip to content

Commit

Permalink
Merge branch 'main' into feat/byrole-disabled
Browse files Browse the repository at this point in the history
  • Loading branch information
timdeschryver authored May 25, 2023
2 parents 876fef0 + d09b3c2 commit a42f42e
Show file tree
Hide file tree
Showing 8 changed files with 312 additions and 30 deletions.
14 changes: 5 additions & 9 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,17 @@ on:
- 'beta'
- 'alpha'
- '!all-contributors/**'
pull_request: {}
pull_request:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions: {}

jobs:
main:
permissions:
actions: write # to cancel/stop running workflows (styfle/cancel-workflow-action)
contents: read # to fetch code (actions/checkout)
# ignore all-contributors PRs
if: ${{ !contains(github.head_ref, 'all-contributors') }}
Expand All @@ -29,9 +32,6 @@ jobs:
node: [14, 16, 18]
runs-on: ubuntu-latest
steps:
- name: 🛑 Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.9.0

- name: ⬇️ Checkout repo
uses: actions/checkout@v3
with:
Expand Down Expand Up @@ -63,7 +63,6 @@ jobs:

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

Expand All @@ -73,9 +72,6 @@ jobs:
${{ github.repository == 'testing-library/dom-testing-library' &&
github.event_name == 'push' }}
steps:
- name: 🛑 Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.9.0

- name: ⬇️ Checkout repo
uses: actions/checkout@v3

Expand Down
3 changes: 1 addition & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,11 @@
"aria-query": "^5.0.0",
"chalk": "^4.1.0",
"dom-accessibility-api": "^0.5.9",
"lz-string": "^1.4.4",
"lz-string": "^1.5.0",
"pretty-format": "^27.0.2"
},
"devDependencies": {
"@testing-library/jest-dom": "^5.11.6",
"@types/lz-string": "^1.3.34",
"jest-in-case": "^1.0.2",
"jest-snapshot-serializer-ansi": "^1.0.0",
"jest-watch-select-projects": "^2.0.0",
Expand Down
121 changes: 121 additions & 0 deletions src/__tests__/ariaAttributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,28 @@ test('`expanded` throws on unsupported roles', () => {
)
})

test('`busy` throws on unsupported roles', () => {
const {getByRole} = render(
`<div aria-busy="true" role="none">Hello, Dave!</div>`,
)
expect(() =>
getByRole('none', {busy: true}),
).toThrowErrorMatchingInlineSnapshot(
`"aria-busy" is not supported on role "none".`,
)
})

test('`busy: true|false` matches `busy` regions', () => {
const {getByRole} = renderIntoDocument(
`<div>
<div role="log" aria-busy="true" />
<div role="log" aria-busy="false" />
</div>`,
)
expect(getByRole('log', {busy: true})).toBeInTheDocument()
expect(getByRole('log', {busy: false})).toBeInTheDocument()
})

test('`checked: true|false` matches `checked` checkboxes', () => {
const {getByRole} = renderIntoDocument(
`<div>
Expand Down Expand Up @@ -237,6 +259,105 @@ test('`level` throws on unsupported roles', () => {
)
})

test('`value.now` throws on unsupported roles', () => {
const {getByRole} = render(`<button aria-valuenow="1">Button</button>`)
expect(() =>
getByRole('button', {value: {now: 1}}),
).toThrowErrorMatchingInlineSnapshot(
`"aria-valuenow" is not supported on role "button".`,
)
})

test('`value.now: number` matches `aria-valuenow` on widgets', () => {
const {getByRole} = renderIntoDocument(
`<div>
<button role="spinbutton" />
<button role="spinbutton" aria-valuenow="5" />
<button role="spinbutton" aria-valuenow="10" />
</div>`,
)
expect(getByRole('spinbutton', {value: {now: 5}})).toBeInTheDocument()
expect(getByRole('spinbutton', {value: {now: 10}})).toBeInTheDocument()
})

test('`value.max` throws on unsupported roles', () => {
const {getByRole} = render(`<button aria-valuemax="1">Button</button>`)
expect(() =>
getByRole('button', {value: {max: 1}}),
).toThrowErrorMatchingInlineSnapshot(
`"aria-valuemax" is not supported on role "button".`,
)
})

test('`value.max: number` matches `aria-valuemax` on widgets', () => {
const {getByRole} = renderIntoDocument(
`<div>
<button role="spinbutton" />
<button role="spinbutton" aria-valuemax="5" />
<button role="spinbutton" aria-valuemax="10" />
</div>`,
)
expect(getByRole('spinbutton', {value: {max: 5}})).toBeInTheDocument()
expect(getByRole('spinbutton', {value: {max: 10}})).toBeInTheDocument()
})

test('`value.min` throws on unsupported roles', () => {
const {getByRole} = render(`<button aria-valuemin="1">Button</button>`)
expect(() =>
getByRole('button', {value: {min: 1}}),
).toThrowErrorMatchingInlineSnapshot(
`"aria-valuemin" is not supported on role "button".`,
)
})

test('`value.min: number` matches `aria-valuemin` on widgets', () => {
const {getByRole} = renderIntoDocument(
`<div>
<button role="spinbutton" />
<button role="spinbutton" aria-valuemin="5" />
<button role="spinbutton" aria-valuemin="10" />
</div>`,
)
expect(getByRole('spinbutton', {value: {min: 5}})).toBeInTheDocument()
expect(getByRole('spinbutton', {value: {min: 10}})).toBeInTheDocument()
})

test('`value.text` throws on unsupported roles', () => {
const {getByRole} = render(`<button aria-valuetext="one">Button</button>`)
expect(() =>
getByRole('button', {value: {text: 'one'}}),
).toThrowErrorMatchingInlineSnapshot(
`"aria-valuetext" is not supported on role "button".`,
)
})

test('`value.text: Matcher` matches `aria-valuetext` on widgets', () => {
const {getAllByRole, getByRole} = renderIntoDocument(
`<div>
<button role="spinbutton" />
<button role="spinbutton" aria-valuetext="zero" />
<button role="spinbutton" aria-valuetext="few" />
<button role="spinbutton" aria-valuetext="many" />
</div>`,
)
expect(getByRole('spinbutton', {value: {text: 'zero'}})).toBeInTheDocument()
expect(getAllByRole('spinbutton', {value: {text: /few|many/}})).toHaveLength(
2,
)
})

test('`value.*` must all match if specified', () => {
const {getByRole} = renderIntoDocument(
`<div>
<button role="spinbutton" aria-valuemin="0" aria-valuenow="1" aria-valuemax="10" aria-valuetext="eins" />
<button role="spinbutton" aria-valuemin="0" aria-valuenow="1" aria-valuemax="10" aria-valuetext="one" />
</div>`,
)
expect(
getByRole('spinbutton', {value: {now: 1, text: 'one'}}),
).toBeInTheDocument()
})

test('`expanded: true|false` matches `expanded` buttons', () => {
const {getByRole} = renderIntoDocument(
`<div>
Expand Down
20 changes: 19 additions & 1 deletion src/__tests__/suggestions.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,15 @@ beforeAll(() => {
configure({throwSuggestions: true})
})

beforeEach(() => {
// We're testing suggestions of find* queries but we're not interested in their time-related behavior.
// Real timers would make the test suite slower for no reason.
jest.useFakeTimers()
})

afterEach(() => {
configure({testIdAttribute: 'data-testid'})
jest.useRealTimers()
configure({testIdAttribute: 'data-testid', throwSuggestions: true})
console.warn.mockClear()
})

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

test('should suggest when suggest is turned on for a specific query but disabled in config', () => {
configure({throwSuggestions: false})
renderIntoDocument(`
<button data-testid="foo">submit</button>
<button data-testid="foot">another</button>`)

expect(() => screen.getByTestId('foo', {suggest: true})).toThrowError(
"try this:\ngetByRole('button', { name: /submit/i })",
)
})

test('should suggest getByRole when used with getBy', () => {
renderIntoDocument(`<button data-testid="foo">submit</button>`)

Expand Down
93 changes: 93 additions & 0 deletions src/queries/role.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable complexity */
import {
computeAccessibleDescription,
computeAccessibleName,
Expand All @@ -9,11 +10,16 @@ import {
} from 'aria-query'
import {
computeAriaSelected,
computeAriaBusy,
computeAriaChecked,
computeAriaPressed,
computeAriaCurrent,
computeAriaDisabled,
computeAriaExpanded,
computeAriaValueNow,
computeAriaValueMax,
computeAriaValueMin,
computeAriaValueText,
computeHeadingLevel,
getImplicitAriaRoles,
prettyRoles,
Expand Down Expand Up @@ -43,12 +49,19 @@ const queryAllByRole: AllByRole = (
description,
queryFallbacks = false,
selected,
busy,
checked,
pressed,
current,
disabled,
level,
expanded,
value: {
now: valueNow,
min: valueMin,
max: valueMax,
text: valueText,
} = {} as NonNullable<ByRoleOptions['value']>,
} = {},
) => {
checkContainerType(container)
Expand All @@ -63,6 +76,16 @@ const queryAllByRole: AllByRole = (
}
}

if (busy !== undefined) {
// guard against unknown roles
if (
allRoles.get(role as ARIARoleDefinitionKey)?.props['aria-busy'] ===
undefined
) {
throw new Error(`"aria-busy" is not supported on role "${role}".`)
}
}

if (checked !== undefined) {
// guard against unknown roles
if (
Expand Down Expand Up @@ -103,6 +126,46 @@ const queryAllByRole: AllByRole = (
}
}

if (valueNow !== undefined) {
// guard against unknown roles
if (
allRoles.get(role as ARIARoleDefinitionKey)?.props['aria-valuenow'] ===
undefined
) {
throw new Error(`"aria-valuenow" is not supported on role "${role}".`)
}
}

if (valueMax !== undefined) {
// guard against unknown roles
if (
allRoles.get(role as ARIARoleDefinitionKey)?.props['aria-valuemax'] ===
undefined
) {
throw new Error(`"aria-valuemax" is not supported on role "${role}".`)
}
}

if (valueMin !== undefined) {
// guard against unknown roles
if (
allRoles.get(role as ARIARoleDefinitionKey)?.props['aria-valuemin'] ===
undefined
) {
throw new Error(`"aria-valuemin" is not supported on role "${role}".`)
}
}

if (valueText !== undefined) {
// guard against unknown roles
if (
allRoles.get(role as ARIARoleDefinitionKey)?.props['aria-valuetext'] ===
undefined
) {
throw new Error(`"aria-valuetext" is not supported on role "${role}".`)
}
}

if (expanded !== undefined) {
// guard against unknown roles
if (
Expand Down Expand Up @@ -164,6 +227,9 @@ const queryAllByRole: AllByRole = (
if (selected !== undefined) {
return selected === computeAriaSelected(element)
}
if (busy !== undefined) {
return busy === computeAriaBusy(element)
}
if (checked !== undefined) {
return checked === computeAriaChecked(element)
}
Expand All @@ -182,6 +248,33 @@ const queryAllByRole: AllByRole = (
if (level !== undefined) {
return level === computeHeadingLevel(element)
}
if (
valueNow !== undefined ||
valueMax !== undefined ||
valueMin !== undefined ||
valueText !== undefined
) {
let valueMatches = true
if (valueNow !== undefined) {
valueMatches &&= valueNow === computeAriaValueNow(element)
}
if (valueMax !== undefined) {
valueMatches &&= valueMax === computeAriaValueMax(element)
}
if (valueMin !== undefined) {
valueMatches &&= valueMin === computeAriaValueMin(element)
}
if (valueText !== undefined) {
valueMatches &&= matches(
computeAriaValueText(element) ?? null,
element,
valueText,
text => text,
)
}

return valueMatches
}
// don't care if aria attributes are unspecified
return true
})
Expand Down
Loading

0 comments on commit a42f42e

Please sign in to comment.