Skip to content

Commit

Permalink
feat(role): support {pressed: true} for buttons (#729)
Browse files Browse the repository at this point in the history
Hat tip to Sebastian Silbermann for his prior work on PR #692
  • Loading branch information
jluxenberg committed Aug 6, 2020
1 parent ed87ae9 commit 372ac60
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 0 deletions.
31 changes: 31 additions & 0 deletions src/__tests__/ariaAttributes.js
Expand Up @@ -9,6 +9,15 @@ test('`selected` throws on unsupported roles', () => {
)
})

test('`pressed` throws on unsupported roles', () => {
const {getByRole} = render(`<input aria-pressed="true" type="text" />`)
expect(() =>
getByRole('textbox', {pressed: true}),
).toThrowErrorMatchingInlineSnapshot(
`"\\"aria-pressed\\" is not supported on role \\"textbox\\"."`,
)
})

test('`checked` throws on unsupported roles', () => {
const {getByRole} = render(`<input aria-checked="true" type="text">`)
expect(() =>
Expand Down Expand Up @@ -136,3 +145,25 @@ test('`selected: true` matches `aria-selected="true"` on supported roles', () =>
'selected-tab',
])
})

test('`pressed: true|false` matches `pressed` buttons', () => {
const {getByRole} = renderIntoDocument(
`<div>
<button aria-pressed="true" />
<button aria-pressed="false" />
</div>`,
)
expect(getByRole('button', {pressed: true})).toBeInTheDocument()
expect(getByRole('button', {pressed: false})).toBeInTheDocument()
})

test('`pressed: true|false` matches `pressed` elements with proper role', () => {
const {getByRole} = renderIntoDocument(
`<div>
<span role="button" aria-pressed="true">✔</span>
<span role="button" aria-pressed="false">𝒙</span>
</div>`,
)
expect(getByRole('button', {pressed: true})).toBeInTheDocument()
expect(getByRole('button', {pressed: false})).toBeInTheDocument()
})
12 changes: 12 additions & 0 deletions src/queries/role.js
Expand Up @@ -3,6 +3,7 @@ import {roles as allRoles} from 'aria-query'
import {
computeAriaSelected,
computeAriaChecked,
computeAriaPressed,
getImplicitAriaRoles,
prettyRoles,
isInaccessible,
Expand Down Expand Up @@ -31,6 +32,7 @@ function queryAllByRole(
queryFallbacks = false,
selected,
checked,
pressed,
} = {},
) {
checkContainerType(container)
Expand All @@ -51,6 +53,13 @@ function queryAllByRole(
}
}

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

const subtreeIsInaccessibleCache = new WeakMap()
function cachedIsSubtreeInaccessible(element) {
if (!subtreeIsInaccessibleCache.has(element)) {
Expand Down Expand Up @@ -94,6 +103,9 @@ function queryAllByRole(
if (checked !== undefined) {
return checked === computeAriaChecked(element)
}
if (pressed !== undefined) {
return pressed === computeAriaPressed(element)
}
// don't care if aria attributes are unspecified
return true
})
Expand Down
10 changes: 10 additions & 0 deletions src/role-helpers.js
Expand Up @@ -209,6 +209,15 @@ function computeAriaChecked(element) {
return checkBooleanAttribute(element, 'aria-checked')
}

/**
* @param {Element} element -
* @returns {boolean | undefined} - false/true if (not)pressed, undefined if not press-able
*/
function computeAriaPressed(element) {
// https://www.w3.org/TR/wai-aria-1.1/#aria-pressed
return checkBooleanAttribute(element, 'aria-pressed')
}

function checkBooleanAttribute(element, attribute) {
const attributeValue = element.getAttribute(attribute)
if (attributeValue === 'true') {
Expand All @@ -229,4 +238,5 @@ export {
isInaccessible,
computeAriaSelected,
computeAriaChecked,
computeAriaPressed,
}
5 changes: 5 additions & 0 deletions types/queries.d.ts
Expand Up @@ -83,6 +83,11 @@ export interface ByRoleOptions extends MatcherOptions {
* checked in the accessibility tree, i.e., `aria-checked="true"`
*/
checked?: boolean
/**
* If true only includes elements in the query set that are marked as
* pressed in the accessibility tree, i.e., `aria-pressed="true"`
*/
pressed?: boolean
/**
* Includes every role used in the `role` attribute
* For example *ByRole('progressbar', {queryFallbacks: true})` will find <div role="meter progressbar">`.
Expand Down

0 comments on commit 372ac60

Please sign in to comment.