diff --git a/src/__tests__/ariaAttributes.js b/src/__tests__/ariaAttributes.js
index 498f21b3..7984d5e0 100644
--- a/src/__tests__/ariaAttributes.js
+++ b/src/__tests__/ariaAttributes.js
@@ -259,6 +259,105 @@ test('`level` throws on unsupported roles', () => {
)
})
+test('`value.now` throws on unsupported roles', () => {
+ const {getByRole} = render(``)
+ 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(
+ `
diff --git a/src/queries/role.ts b/src/queries/role.ts
index 5655e2ba..98b08848 100644
--- a/src/queries/role.ts
+++ b/src/queries/role.ts
@@ -1,3 +1,4 @@
+/* eslint-disable complexity */
import {
computeAccessibleDescription,
computeAccessibleName,
@@ -14,6 +15,10 @@ import {
computeAriaPressed,
computeAriaCurrent,
computeAriaExpanded,
+ computeAriaValueNow,
+ computeAriaValueMax,
+ computeAriaValueMin,
+ computeAriaValueText,
computeHeadingLevel,
getImplicitAriaRoles,
prettyRoles,
@@ -49,6 +54,12 @@ const queryAllByRole: AllByRole = (
current,
level,
expanded,
+ value: {
+ now: valueNow,
+ min: valueMin,
+ max: valueMax,
+ text: valueText,
+ } = {} as NonNullable
,
} = {},
) => {
checkContainerType(container)
@@ -113,6 +124,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 (
@@ -182,6 +233,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
})
diff --git a/src/role-helpers.js b/src/role-helpers.js
index 7a8f3c82..bc134f27 100644
--- a/src/role-helpers.js
+++ b/src/role-helpers.js
@@ -334,6 +334,42 @@ function computeHeadingLevel(element) {
return ariaLevelAttribute || implicitHeadingLevels[element.tagName]
}
+/**
+ * @param {Element} element -
+ * @returns {number | undefined} -
+ */
+function computeAriaValueNow(element) {
+ const valueNow = element.getAttribute('aria-valuenow')
+ return valueNow === null ? undefined : +valueNow
+}
+
+/**
+ * @param {Element} element -
+ * @returns {number | undefined} -
+ */
+function computeAriaValueMax(element) {
+ const valueMax = element.getAttribute('aria-valuemax')
+ return valueMax === null ? undefined : +valueMax
+}
+
+/**
+ * @param {Element} element -
+ * @returns {number | undefined} -
+ */
+function computeAriaValueMin(element) {
+ const valueMin = element.getAttribute('aria-valuemin')
+ return valueMin === null ? undefined : +valueMin
+}
+
+/**
+ * @param {Element} element -
+ * @returns {string | undefined} -
+ */
+function computeAriaValueText(element) {
+ const valueText = element.getAttribute('aria-valuetext')
+ return valueText === null ? undefined : valueText
+}
+
export {
getRoles,
logRoles,
@@ -347,5 +383,9 @@ export {
computeAriaPressed,
computeAriaCurrent,
computeAriaExpanded,
+ computeAriaValueNow,
+ computeAriaValueMax,
+ computeAriaValueMin,
+ computeAriaValueText,
computeHeadingLevel,
}
diff --git a/types/queries.d.ts b/types/queries.d.ts
index c6568abf..c6ce9054 100644
--- a/types/queries.d.ts
+++ b/types/queries.d.ts
@@ -110,6 +110,12 @@ export interface ByRoleOptions {
* the `aria-level` attribute.
*/
level?: number
+ value?: {
+ now?: number
+ min?: number
+ max?: number
+ text?: Matcher
+ }
/**
* Includes every role used in the `role` attribute
* For example *ByRole('progressbar', {queryFallbacks: true})` will find `.