diff --git a/src/__tests__/ariaAttributes.js b/src/__tests__/ariaAttributes.js index 3f504c87..e207721a 100644 --- a/src/__tests__/ariaAttributes.js +++ b/src/__tests__/ariaAttributes.js @@ -9,6 +9,15 @@ test('`selected` throws on unsupported roles', () => { ) }) +test('`pressed` throws on unsupported roles', () => { + const {getByRole} = render(``) + expect(() => + getByRole('textbox', {pressed: true}), + ).toThrowErrorMatchingInlineSnapshot( + `"\\"aria-pressed\\" is not supported on role \\"textbox\\"."`, + ) +}) + test('`checked` throws on unsupported roles', () => { const {getByRole} = render(``) expect(() => @@ -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( + `
+
`, + ) + 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( + `
+ + 𝒙 +
`, + ) + expect(getByRole('button', {pressed: true})).toBeInTheDocument() + expect(getByRole('button', {pressed: false})).toBeInTheDocument() +}) diff --git a/src/queries/role.js b/src/queries/role.js index f9be4564..8ecd230f 100644 --- a/src/queries/role.js +++ b/src/queries/role.js @@ -3,6 +3,7 @@ import {roles as allRoles} from 'aria-query' import { computeAriaSelected, computeAriaChecked, + computeAriaPressed, getImplicitAriaRoles, prettyRoles, isInaccessible, @@ -31,6 +32,7 @@ function queryAllByRole( queryFallbacks = false, selected, checked, + pressed, } = {}, ) { checkContainerType(container) @@ -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)) { @@ -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 }) diff --git a/src/role-helpers.js b/src/role-helpers.js index 10112b0e..47dc05e5 100644 --- a/src/role-helpers.js +++ b/src/role-helpers.js @@ -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') { @@ -229,4 +238,5 @@ export { isInaccessible, computeAriaSelected, computeAriaChecked, + computeAriaPressed, } diff --git a/types/queries.d.ts b/types/queries.d.ts index e84b6700..13056da7 100644 --- a/types/queries.d.ts +++ b/types/queries.d.ts @@ -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
`.