diff --git a/src/__tests__/role.js b/src/__tests__/role.js
index a87bc000..9c766d0a 100644
--- a/src/__tests__/role.js
+++ b/src/__tests__/role.js
@@ -572,3 +572,177 @@ test('should find the input using type property instead of attribute', () => {
const {getByRole} = render('')
expect(getByRole('textbox')).not.toBeNull()
})
+
+test('can be filtered by accessible description', () => {
+ const targetedNotificationMessage = 'Your session is about to expire!'
+ const {getByRole} = renderIntoDocument(
+ `
+
+
+
+
You have unread emails
+
+
+
+
${targetedNotificationMessage}
+
+
`,
+ )
+
+ const notification = getByRole('alertdialog', {
+ description: targetedNotificationMessage,
+ })
+
+ expect(notification).not.toBeNull()
+ expect(notification).toHaveTextContent(targetedNotificationMessage)
+
+ expect(
+ getQueriesForElement(notification).getByRole('button', {name: 'Close'}),
+ ).not.toBeNull()
+})
+
+test('error should include description when filtering and no results are found', () => {
+ const targetedNotificationMessage = 'Your session is about to expire!'
+ const {getByRole} = renderIntoDocument(
+ `
${targetedNotificationMessage}
`,
+ )
+
+ expect(() =>
+ getByRole('alertdialog', {description: targetedNotificationMessage}),
+ ).toThrowErrorMatchingInlineSnapshot(`
+ Unable to find an accessible element with the role "alertdialog" and description "Your session is about to expire!"
+
+ Here are the accessible roles:
+
+ dialog:
+
+ Name "":
+ Description "Your session is about to expire!":
+
+
+ --------------------------------------------------
+ button:
+
+ Name "Close":
+ Description "":
+
+
+ --------------------------------------------------
+
+ Ignored nodes: comments, ,
+
+
`,
+ )
+
+ expect(() => getByRole('alertdialog', {description: /unknown description/}))
+ .toThrowErrorMatchingInlineSnapshot(`
+ Unable to find an accessible element with the role "alertdialog" and description \`/unknown description/\`
+
+ Here are the accessible roles:
+
+ alertdialog:
+
+ Name "":
+ Description "Your session is about to expire!":
+
+
+ --------------------------------------------------
+ button:
+
+ Name "Close":
+ Description "":
+
+
+ --------------------------------------------------
+
+ Ignored nodes: comments, ,
+
+
+
+
+
+
+ Your session is about to expire!
+
+
+
+ `)
+
+ expect(() => getByRole('alertdialog', {description: () => false}))
+ .toThrowErrorMatchingInlineSnapshot(`
+ Unable to find an accessible element with the role "alertdialog" and description \`() => false\`
+
+ Here are the accessible roles:
+
+ alertdialog:
+
+ Name "":
+ Description "Your session is about to expire!":
+
+
+ --------------------------------------------------
+ button:
+
+ Name "Close":
+ Description "":
+
+
+ --------------------------------------------------
+
+ Ignored nodes: comments, ,
+
+
+
+
+
+
+ Your session is about to expire!
+
+
+
+ `)
+})
diff --git a/src/queries/role.js b/src/queries/role.js
index 339647a6..9b27f527 100644
--- a/src/queries/role.js
+++ b/src/queries/role.js
@@ -1,4 +1,7 @@
-import {computeAccessibleName} from 'dom-accessibility-api'
+import {
+ computeAccessibleDescription,
+ computeAccessibleName,
+} from 'dom-accessibility-api'
import {roles as allRoles, roleElements} from 'aria-query'
import {
computeAriaSelected,
@@ -30,6 +33,7 @@ function queryAllByRole(
collapseWhitespace,
hidden = getConfig().defaultHidden,
name,
+ description,
trim,
normalizer,
queryFallbacks = false,
@@ -169,6 +173,22 @@ function queryAllByRole(
text => text,
)
})
+ .filter(element => {
+ if (description === undefined) {
+ // Don't care
+ return true
+ }
+
+ return matches(
+ computeAccessibleDescription(element, {
+ computedStyleSupportsPseudoElements:
+ getConfig().computedStyleSupportsPseudoElements,
+ }),
+ element,
+ description,
+ text => text,
+ )
+ })
.filter(element => {
return hidden === false
? isInaccessible(element, {
@@ -216,7 +236,7 @@ const getMultipleError = (c, role, {name} = {}) => {
const getMissingError = (
container,
role,
- {hidden = getConfig().defaultHidden, name} = {},
+ {hidden = getConfig().defaultHidden, name, description} = {},
) => {
if (getConfig()._disableExpensiveErrorDiagnostics) {
return `Unable to find role="${role}"`
@@ -227,6 +247,7 @@ const getMissingError = (
roles += prettyRoles(childElement, {
hidden,
includeName: name !== undefined,
+ includeDescription: description !== undefined,
})
})
let roleMessage
@@ -257,10 +278,19 @@ Here are the ${hidden === false ? 'accessible' : 'available'} roles:
nameHint = ` and name \`${name}\``
}
+ let descriptionHint = ''
+ if (description === undefined) {
+ descriptionHint = ''
+ } else if (typeof description === 'string') {
+ descriptionHint = ` and description "${description}"`
+ } else {
+ descriptionHint = ` and description \`${description}\``
+ }
+
return `
Unable to find an ${
hidden === false ? 'accessible ' : ''
- }element with the role "${role}"${nameHint}
+ }element with the role "${role}"${nameHint}${descriptionHint}
${roleMessage}`.trim()
}
diff --git a/src/role-helpers.js b/src/role-helpers.js
index 7de55932..0b494da5 100644
--- a/src/role-helpers.js
+++ b/src/role-helpers.js
@@ -1,5 +1,8 @@
import {elementRoles} from 'aria-query'
-import {computeAccessibleName} from 'dom-accessibility-api'
+import {
+ computeAccessibleDescription,
+ computeAccessibleName,
+} from 'dom-accessibility-api'
import {prettyDOM} from './pretty-dom'
import {getConfig} from './config'
@@ -178,7 +181,7 @@ function getRoles(container, {hidden = false} = {}) {
}, {})
}
-function prettyRoles(dom, {hidden}) {
+function prettyRoles(dom, {hidden, includeDescription}) {
const roles = getRoles(dom, {hidden})
// We prefer to skip generic role, we don't recommend it
return Object.entries(roles)
@@ -191,7 +194,20 @@ function prettyRoles(dom, {hidden}) {
computedStyleSupportsPseudoElements:
getConfig().computedStyleSupportsPseudoElements,
})}":\n`
+
const domString = prettyDOM(el.cloneNode(false))
+
+ if (includeDescription) {
+ const descriptionString = `Description "${computeAccessibleDescription(
+ el,
+ {
+ computedStyleSupportsPseudoElements:
+ getConfig().computedStyleSupportsPseudoElements,
+ },
+ )}":\n`
+ return `${nameString}${descriptionString}${domString}`
+ }
+
return `${nameString}${domString}`
})
.join('\n\n')
diff --git a/types/queries.d.ts b/types/queries.d.ts
index 1ed64151..1fb66b7d 100644
--- a/types/queries.d.ts
+++ b/types/queries.d.ts
@@ -115,6 +115,13 @@ export interface ByRoleOptions extends MatcherOptions {
| RegExp
| string
| ((accessibleName: string, element: Element) => boolean)
+ /**
+ * Only considers elements with the specified accessible description.
+ */
+ description?:
+ | RegExp
+ | string
+ | ((accessibleDescription: string, element: Element) => boolean)
}
export type AllByRole = (