From a68d3de3f3e120c95bb9eec2512b2b6c1e53ed05 Mon Sep 17 00:00:00 2001 From: Ben Lesh Date: Thu, 1 Aug 2024 16:57:29 -0400 Subject: [PATCH] feat: Add strictVisibility check option to ByRole queries --- src/__tests__/element-queries.js | 31 +++++++++++++++++++++++++++++++ src/queries/role.ts | 10 +++++++--- src/role-helpers.js | 23 +++++++++++++++++++++++ types/queries.d.ts | 11 +++++++++++ 4 files changed, 72 insertions(+), 3 deletions(-) diff --git a/src/__tests__/element-queries.js b/src/__tests__/element-queries.js index a8e00011..334cb2ff 100644 --- a/src/__tests__/element-queries.js +++ b/src/__tests__/element-queries.js @@ -813,6 +813,37 @@ test('queryAllByRole returns semantic html elements', () => { expect(queryAllByRole('listbox')).toHaveLength(1) }) +test('getAllByRole matchers with strictVisibility enabled and disabled', () => { + const {getAllByRole} = render(` + +
+ +
+
+ +
+
+
+ +
+
+
+ +
+ `) + + const defaultResults = getAllByRole('button') + expect(defaultResults).toHaveLength(1) + + const strictResults = getAllByRole('button', {strictVisibilityCheck: true}) + expect(strictResults).toHaveLength(1) + + const looseResults = getAllByRole('button', {strictVisibilityCheck: false}) + expect(looseResults).toHaveLength(1) +}) + test('getAll* matchers return an array', () => { const { getAllByAltText, diff --git a/src/queries/role.ts b/src/queries/role.ts index e4dd395f..2d462d8b 100644 --- a/src/queries/role.ts +++ b/src/queries/role.ts @@ -24,6 +24,7 @@ import { prettyRoles, isInaccessible, isSubtreeInaccessible, + isInaccessibleLoose, } from '../role-helpers' import {wrapAllByQueryWithSuggestion} from '../query-helpers' import {checkContainerType} from '../helpers' @@ -54,6 +55,7 @@ const queryAllByRole: AllByRole = ( current, level, expanded, + strictVisibilityCheck = true, value: { now: valueNow, min: valueMin, @@ -297,9 +299,11 @@ const queryAllByRole: AllByRole = ( }) .filter(element => { return hidden === false - ? isInaccessible(element, { - isSubtreeInaccessible: cachedIsSubtreeInaccessible, - }) === false + ? strictVisibilityCheck + ? isInaccessible(element, { + isSubtreeInaccessible: cachedIsSubtreeInaccessible, + }) === false + : isInaccessibleLoose(element) === false : true }) } diff --git a/src/role-helpers.js b/src/role-helpers.js index 8d7e7cb0..452f88a6 100644 --- a/src/role-helpers.js +++ b/src/role-helpers.js @@ -65,6 +65,28 @@ function isInaccessible(element, options = {}) { return false } +/** + * A fast check to see if an element is inaccessible. + * @param {Element} element + * @returns {boolean} true if exluded, otherwise false + */ +function isInaccessibleLoose(element) { + do { + const explictlyHidden = + element.style.visibility === 'hidden' || element.style.display === 'none' + if (explictlyHidden) { + return true + } + + const ariaHidden = element.getAttribute('aria-hidden') === 'true' + if (ariaHidden) { + return true + } + } while ((element = element.parentElement)) + + return false +} + function getImplicitAriaRoles(currentNode) { // eslint bug here: // eslint-disable-next-line no-unused-vars @@ -382,6 +404,7 @@ export { isSubtreeInaccessible, prettyRoles, isInaccessible, + isInaccessibleLoose, computeAriaSelected, computeAriaBusy, computeAriaChecked, diff --git a/types/queries.d.ts b/types/queries.d.ts index c6ce9054..549b921e 100644 --- a/types/queries.d.ts +++ b/types/queries.d.ts @@ -110,6 +110,17 @@ export interface ByRoleOptions { * the `aria-level` attribute. */ level?: number + + /** + * Whether or not to strictly check the visibliity of the element. Doing so + * can cause issues with the performance of tests, because it requires the DOM tree + * is traversed, and the `getComputedStyle` function is called on each element. + * + * If you're finding that your tests are slow, you may want to disable this option. + * @default true + */ + strictVisibilityCheck?: boolean + value?: { now?: number min?: number