From 4fc28e57e59c127cbe89de3d7ab687d24e4077ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Acu=C3=B1a?= Date: Fri, 28 Oct 2022 10:02:41 -0300 Subject: [PATCH 01/22] fix: migrate role to ts --- src/queries/{role.js => role.ts} | 60 ++++++++++++++++++++------------ src/query-helpers.ts | 22 ++++++------ types/matches.d.ts | 6 ++-- types/queries.d.ts | 17 +++++---- 4 files changed, 62 insertions(+), 43 deletions(-) rename src/queries/{role.js => role.ts} (81%) diff --git a/src/queries/role.js b/src/queries/role.ts similarity index 81% rename from src/queries/role.js rename to src/queries/role.ts index a46d8a2b..b813995e 100644 --- a/src/queries/role.js +++ b/src/queries/role.ts @@ -2,7 +2,7 @@ import { computeAccessibleDescription, computeAccessibleName, } from 'dom-accessibility-api' -import {roles as allRoles, roleElements} from 'aria-query' +import {roles as allRoles, roleElements, ARIAPropertyMap} from 'aria-query' import { computeAriaSelected, computeAriaChecked, @@ -24,8 +24,9 @@ import { makeNormalizer, matches, } from './all-utils' +import { AllByRole, ByRoleMatcher, ByRoleOptions, GetErrorFunction, MatcherOptions, NormalizerFn } from '../../types' -function queryAllByRole( +const queryAllByRole: AllByRole = ( container, role, { @@ -44,28 +45,28 @@ function queryAllByRole( level, expanded, } = {}, -) { +) => { checkContainerType(container) const matcher = exact ? matches : fuzzyMatches const matchNormalizer = makeNormalizer({collapseWhitespace, trim, normalizer}) if (selected !== undefined) { // guard against unknown roles - if (allRoles.get(role)?.props['aria-selected'] === undefined) { + if (getRoleModelFor(role).getProp('aria-selected') === undefined) { throw new Error(`"aria-selected" is not supported on role "${role}".`) } } if (checked !== undefined) { // guard against unknown roles - if (allRoles.get(role)?.props['aria-checked'] === undefined) { + if (getRoleModelFor(role).getProp('aria-checked') === undefined) { throw new Error(`"aria-checked" is not supported on role "${role}".`) } } if (pressed !== undefined) { // guard against unknown roles - if (allRoles.get(role)?.props['aria-pressed'] === undefined) { + if (getRoleModelFor(role).getProp('aria-pressed') === undefined) { throw new Error(`"aria-pressed" is not supported on role "${role}".`) } } @@ -75,27 +76,25 @@ function queryAllByRole( // guard against unknown roles // All currently released ARIA versions support `aria-current` on all roles. // Leaving this for symetry and forward compatibility - if (allRoles.get(role)?.props['aria-current'] === undefined) { + if (getRoleModelFor(role).getProp('aria-current') === undefined) { throw new Error(`"aria-current" is not supported on role "${role}".`) } } - if (level !== undefined) { - // guard against using `level` option with any role other than `heading` - if (role !== 'heading') { - throw new Error(`Role "${role}" cannot have "level" property.`) - } + // guard against using `level` option with any role other than `heading` + if (level !== undefined && role !== 'heading') { + throw new Error(`Role "${role}" cannot have "level" property.`) } if (expanded !== undefined) { // guard against unknown roles - if (allRoles.get(role)?.props['aria-expanded'] === undefined) { + if (getRoleModelFor(role).getProp('aria-expanded') === undefined) { throw new Error(`"aria-expanded" is not supported on role "${role}".`) } } const subtreeIsInaccessibleCache = new WeakMap() - function cachedIsSubtreeInaccessible(element) { + function cachedIsSubtreeInaccessible(element: Element) { if (!subtreeIsInaccessibleCache.has(element)) { subtreeIsInaccessibleCache.set(element, isSubtreeInaccessible(element)) } @@ -104,7 +103,7 @@ function queryAllByRole( } return Array.from( - container.querySelectorAll( + container.querySelectorAll( // Only query elements that can be matched by the following filters makeRoleSelector(role, exact, normalizer ? matchNormalizer : undefined), ), @@ -113,7 +112,7 @@ function queryAllByRole( const isRoleSpecifiedExplicitly = node.hasAttribute('role') if (isRoleSpecifiedExplicitly) { - const roleValue = node.getAttribute('role') + const roleValue = node.getAttribute('role') || "" if (queryFallbacks) { return roleValue .split(' ') @@ -198,7 +197,7 @@ function queryAllByRole( }) } -function makeRoleSelector(role, exact, customNormalizer) { +function makeRoleSelector(role: ByRoleMatcher, exact: boolean, customNormalizer?: NormalizerFn) { if (typeof role !== 'string') { // For non-string role parameters we can not determine the implicitRoleSelectors. return '*' @@ -220,7 +219,17 @@ function makeRoleSelector(role, exact, customNormalizer) { .join(',') } -const getNameHint = name => { +function getRoleModelFor (role: ByRoleMatcher) { + return { + getProp(propName: N) { + if(typeof role !== "string") return; + return allRoles.get(role)?.props?.[propName] as ARIAPropertyMap[N]; + } + } +} + + +const getNameHint = (name: ByRoleOptions["name"]): string => { let nameHint = '' if (name === undefined) { nameHint = '' @@ -233,11 +242,15 @@ const getNameHint = name => { return nameHint } -const getMultipleError = (c, role, {name} = {}) => { +const getMultipleError: GetErrorFunction< +[matcher: ByRoleMatcher, options: ByRoleOptions] +> = (c, role, {name} = {}) => { return `Found multiple elements with the role "${role}"${getNameHint(name)}` } -const getMissingError = ( +const getMissingError: GetErrorFunction< +[matcher: ByRoleMatcher, options: ByRoleOptions] +> = ( container, role, {hidden = getConfig().defaultHidden, name, description} = {}, @@ -247,7 +260,7 @@ const getMissingError = ( } let roles = '' - Array.from(container.children).forEach(childElement => { + Array.from(container?.children || []).forEach(childElement => { roles += prettyRoles(childElement, { hidden, includeDescription: description !== undefined, @@ -297,7 +310,10 @@ Unable to find an ${ ${roleMessage}`.trim() } -const queryAllByRoleWithSuggestions = wrapAllByQueryWithSuggestion( +const queryAllByRoleWithSuggestions = wrapAllByQueryWithSuggestion< +// @ts-expect-error -- See `wrapAllByQueryWithSuggestion` Argument constraint comment +[labelText: Matcher, options?: MatcherOptions] +>( queryAllByRole, queryAllByRole.name, 'queryAll', diff --git a/src/query-helpers.ts b/src/query-helpers.ts index 8de75a23..7722d617 100644 --- a/src/query-helpers.ts +++ b/src/query-helpers.ts @@ -1,4 +1,7 @@ import { + type AllByRole, + type ByRoleMatcher, + type ByRoleOptions, type GetErrorFunction, type Matcher, type MatcherOptions, @@ -210,16 +213,15 @@ const wrapAllByQueryWithSuggestion = // However, the implementation always required a dyadic (after `container`) not variadic `queryAllBy` considering the implementation of `makeFindQuery` // This is at least statically true and can be verified by accepting `QueryMethod` function buildQueries( - queryAllBy: QueryMethod< - [matcher: Matcher, options: MatcherOptions], - HTMLElement[] - >, - getMultipleError: GetErrorFunction< - [matcher: Matcher, options: MatcherOptions] - >, - getMissingError: GetErrorFunction< - [matcher: Matcher, options: MatcherOptions] - >, + queryAllBy: + | AllByRole + | QueryMethod<[matcher: Matcher, options: MatcherOptions], HTMLElement[]>, + getMultipleError: + | GetErrorFunction<[matcher: ByRoleMatcher, options: ByRoleOptions]> + | GetErrorFunction<[matcher: Matcher, options: MatcherOptions]>, + getMissingError: + | GetErrorFunction<[matcher: ByRoleMatcher, options: ByRoleOptions]> + | GetErrorFunction<[matcher: Matcher, options: MatcherOptions]>, ) { const queryBy = wrapSingleQueryWithSuggestion( makeSingleQuery(queryAllBy, getMultipleError), diff --git a/types/matches.d.ts b/types/matches.d.ts index 13fa3692..965646f6 100644 --- a/types/matches.d.ts +++ b/types/matches.d.ts @@ -4,11 +4,13 @@ export type MatcherFunction = ( content: string, element: Element | null, ) => boolean -export type Matcher = MatcherFunction | RegExp | number | string // Get autocomplete for ARIARole union types, while still supporting another string // Ref: https://github.com/microsoft/TypeScript/issues/29729#issuecomment-505826972 -export type ByRoleMatcher = ARIARole | MatcherFunction | {} +export type ByRoleMatcher = MatcherFunction | ARIARole + +export type Matcher = ByRoleMatcher | MatcherFunction | RegExp | number | string + export type NormalizerFn = (text: string) => string diff --git a/types/queries.d.ts b/types/queries.d.ts index 1fb66b7d..06fbc31e 100644 --- a/types/queries.d.ts +++ b/types/queries.d.ts @@ -1,5 +1,5 @@ -import {ByRoleMatcher, Matcher, MatcherOptions} from './matches' -import {SelectorMatcherOptions} from './query-helpers' +import {ByRoleMatcher, Matcher, MatcherFunction, MatcherOptions} from './matches' +import {QueryMethod, SelectorMatcherOptions} from './query-helpers' import {waitForOptions} from './wait-for' export type QueryByBoundAttribute = ( @@ -114,21 +114,20 @@ export interface ByRoleOptions extends MatcherOptions { name?: | RegExp | string - | ((accessibleName: string, element: Element) => boolean) + | MatcherFunction /** * Only considers elements with the specified accessible description. */ description?: | RegExp | string - | ((accessibleDescription: string, element: Element) => boolean) + | MatcherFunction } -export type AllByRole = ( - container: HTMLElement, - role: ByRoleMatcher, - options?: ByRoleOptions, -) => T[] +export type AllByRole = QueryMethod<[ + ByRoleMatcher, + ByRoleOptions | undeined +], T[]>; export type GetByRole = ( container: HTMLElement, From 36d53db039c6790650d0e36b2a71a995058635b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Acu=C3=B1a?= Date: Sun, 30 Oct 2022 17:37:11 -0300 Subject: [PATCH 02/22] fix: remove role model abstraction --- src/queries/role.ts | 22 ++++++---------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/src/queries/role.ts b/src/queries/role.ts index b813995e..aab5208d 100644 --- a/src/queries/role.ts +++ b/src/queries/role.ts @@ -2,7 +2,7 @@ import { computeAccessibleDescription, computeAccessibleName, } from 'dom-accessibility-api' -import {roles as allRoles, roleElements, ARIAPropertyMap} from 'aria-query' +import {roles as allRoles, roleElements, ARIAPropertyMap, ARIARoleDefintionKey} from 'aria-query' import { computeAriaSelected, computeAriaChecked, @@ -52,21 +52,21 @@ const queryAllByRole: AllByRole = ( if (selected !== undefined) { // guard against unknown roles - if (getRoleModelFor(role).getProp('aria-selected') === undefined) { + if (allRoles.get(role as ARIARoleDefintionKey)?.props['aria-selected'] === undefined) { throw new Error(`"aria-selected" is not supported on role "${role}".`) } } if (checked !== undefined) { // guard against unknown roles - if (getRoleModelFor(role).getProp('aria-checked') === undefined) { + if (allRoles.get(role as ARIARoleDefintionKey)?.props['aria-checked'] === undefined) { throw new Error(`"aria-checked" is not supported on role "${role}".`) } } if (pressed !== undefined) { // guard against unknown roles - if (getRoleModelFor(role).getProp('aria-pressed') === undefined) { + if (allRoles.get(role as ARIARoleDefintionKey)?.props['aria-pressed'] === undefined) { throw new Error(`"aria-pressed" is not supported on role "${role}".`) } } @@ -76,7 +76,7 @@ const queryAllByRole: AllByRole = ( // guard against unknown roles // All currently released ARIA versions support `aria-current` on all roles. // Leaving this for symetry and forward compatibility - if (getRoleModelFor(role).getProp('aria-current') === undefined) { + if (allRoles.get(role as ARIARoleDefintionKey)?.props['aria-current'] === undefined) { throw new Error(`"aria-current" is not supported on role "${role}".`) } } @@ -88,7 +88,7 @@ const queryAllByRole: AllByRole = ( if (expanded !== undefined) { // guard against unknown roles - if (getRoleModelFor(role).getProp('aria-expanded') === undefined) { + if (allRoles.get(role as ARIARoleDefintionKey)?.props['aria-expanded'] === undefined) { throw new Error(`"aria-expanded" is not supported on role "${role}".`) } } @@ -219,16 +219,6 @@ function makeRoleSelector(role: ByRoleMatcher, exact: boolean, customNormalizer? .join(',') } -function getRoleModelFor (role: ByRoleMatcher) { - return { - getProp(propName: N) { - if(typeof role !== "string") return; - return allRoles.get(role)?.props?.[propName] as ARIAPropertyMap[N]; - } - } -} - - const getNameHint = (name: ByRoleOptions["name"]): string => { let nameHint = '' if (name === undefined) { From b7e403cc931005624a96440c5fd10b466542aaf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Acu=C3=B1a?= Date: Sun, 30 Oct 2022 17:50:29 -0300 Subject: [PATCH 03/22] fix: prettier --- src/queries/role.ts | 65 ++++++++++++++++++++++++++++++++------------- types/matches.d.ts | 1 - types/queries.d.ts | 25 +++++++++-------- 3 files changed, 58 insertions(+), 33 deletions(-) diff --git a/src/queries/role.ts b/src/queries/role.ts index aab5208d..7f8607e5 100644 --- a/src/queries/role.ts +++ b/src/queries/role.ts @@ -2,7 +2,12 @@ import { computeAccessibleDescription, computeAccessibleName, } from 'dom-accessibility-api' -import {roles as allRoles, roleElements, ARIAPropertyMap, ARIARoleDefintionKey} from 'aria-query' +import { + roles as allRoles, + roleElements, + ARIAPropertyMap, + ARIARoleDefintionKey, +} from 'aria-query' import { computeAriaSelected, computeAriaChecked, @@ -24,7 +29,14 @@ import { makeNormalizer, matches, } from './all-utils' -import { AllByRole, ByRoleMatcher, ByRoleOptions, GetErrorFunction, MatcherOptions, NormalizerFn } from '../../types' +import { + AllByRole, + ByRoleMatcher, + ByRoleOptions, + GetErrorFunction, + MatcherOptions, + NormalizerFn, +} from '../../types' const queryAllByRole: AllByRole = ( container, @@ -52,21 +64,30 @@ const queryAllByRole: AllByRole = ( if (selected !== undefined) { // guard against unknown roles - if (allRoles.get(role as ARIARoleDefintionKey)?.props['aria-selected'] === undefined) { + if ( + allRoles.get(role as ARIARoleDefintionKey)?.props['aria-selected'] === + undefined + ) { throw new Error(`"aria-selected" is not supported on role "${role}".`) } } if (checked !== undefined) { // guard against unknown roles - if (allRoles.get(role as ARIARoleDefintionKey)?.props['aria-checked'] === undefined) { + if ( + allRoles.get(role as ARIARoleDefintionKey)?.props['aria-checked'] === + undefined + ) { throw new Error(`"aria-checked" is not supported on role "${role}".`) } } if (pressed !== undefined) { // guard against unknown roles - if (allRoles.get(role as ARIARoleDefintionKey)?.props['aria-pressed'] === undefined) { + if ( + allRoles.get(role as ARIARoleDefintionKey)?.props['aria-pressed'] === + undefined + ) { throw new Error(`"aria-pressed" is not supported on role "${role}".`) } } @@ -76,7 +97,10 @@ const queryAllByRole: AllByRole = ( // guard against unknown roles // All currently released ARIA versions support `aria-current` on all roles. // Leaving this for symetry and forward compatibility - if (allRoles.get(role as ARIARoleDefintionKey)?.props['aria-current'] === undefined) { + if ( + allRoles.get(role as ARIARoleDefintionKey)?.props['aria-current'] === + undefined + ) { throw new Error(`"aria-current" is not supported on role "${role}".`) } } @@ -88,7 +112,10 @@ const queryAllByRole: AllByRole = ( if (expanded !== undefined) { // guard against unknown roles - if (allRoles.get(role as ARIARoleDefintionKey)?.props['aria-expanded'] === undefined) { + if ( + allRoles.get(role as ARIARoleDefintionKey)?.props['aria-expanded'] === + undefined + ) { throw new Error(`"aria-expanded" is not supported on role "${role}".`) } } @@ -112,7 +139,7 @@ const queryAllByRole: AllByRole = ( const isRoleSpecifiedExplicitly = node.hasAttribute('role') if (isRoleSpecifiedExplicitly) { - const roleValue = node.getAttribute('role') || "" + const roleValue = node.getAttribute('role') || '' if (queryFallbacks) { return roleValue .split(' ') @@ -197,7 +224,11 @@ const queryAllByRole: AllByRole = ( }) } -function makeRoleSelector(role: ByRoleMatcher, exact: boolean, customNormalizer?: NormalizerFn) { +function makeRoleSelector( + role: ByRoleMatcher, + exact: boolean, + customNormalizer?: NormalizerFn, +) { if (typeof role !== 'string') { // For non-string role parameters we can not determine the implicitRoleSelectors. return '*' @@ -219,7 +250,7 @@ function makeRoleSelector(role: ByRoleMatcher, exact: boolean, customNormalizer? .join(',') } -const getNameHint = (name: ByRoleOptions["name"]): string => { +const getNameHint = (name: ByRoleOptions['name']): string => { let nameHint = '' if (name === undefined) { nameHint = '' @@ -233,13 +264,13 @@ const getNameHint = (name: ByRoleOptions["name"]): string => { } const getMultipleError: GetErrorFunction< -[matcher: ByRoleMatcher, options: ByRoleOptions] + [matcher: ByRoleMatcher, options: ByRoleOptions] > = (c, role, {name} = {}) => { return `Found multiple elements with the role "${role}"${getNameHint(name)}` } const getMissingError: GetErrorFunction< -[matcher: ByRoleMatcher, options: ByRoleOptions] + [matcher: ByRoleMatcher, options: ByRoleOptions] > = ( container, role, @@ -301,13 +332,9 @@ Unable to find an ${ ${roleMessage}`.trim() } const queryAllByRoleWithSuggestions = wrapAllByQueryWithSuggestion< -// @ts-expect-error -- See `wrapAllByQueryWithSuggestion` Argument constraint comment -[labelText: Matcher, options?: MatcherOptions] ->( - queryAllByRole, - queryAllByRole.name, - 'queryAll', -) + // @ts-expect-error -- See `wrapAllByQueryWithSuggestion` Argument constraint comment + [labelText: Matcher, options?: MatcherOptions] +>(queryAllByRole, queryAllByRole.name, 'queryAll') const [queryByRole, getAllByRole, getByRole, findAllByRole, findByRole] = buildQueries(queryAllByRole, getMultipleError, getMissingError) diff --git a/types/matches.d.ts b/types/matches.d.ts index 965646f6..0a90975f 100644 --- a/types/matches.d.ts +++ b/types/matches.d.ts @@ -11,7 +11,6 @@ export type ByRoleMatcher = MatcherFunction | ARIARole export type Matcher = ByRoleMatcher | MatcherFunction | RegExp | number | string - export type NormalizerFn = (text: string) => string export interface NormalizerOptions extends DefaultNormalizerOptions { diff --git a/types/queries.d.ts b/types/queries.d.ts index 06fbc31e..52e706c9 100644 --- a/types/queries.d.ts +++ b/types/queries.d.ts @@ -1,4 +1,9 @@ -import {ByRoleMatcher, Matcher, MatcherFunction, MatcherOptions} from './matches' +import { + ByRoleMatcher, + Matcher, + MatcherFunction, + MatcherOptions, +} from './matches' import {QueryMethod, SelectorMatcherOptions} from './query-helpers' import {waitForOptions} from './wait-for' @@ -111,23 +116,17 @@ export interface ByRoleOptions extends MatcherOptions { /** * Only considers elements with the specified accessible name. */ - name?: - | RegExp - | string - | MatcherFunction + name?: RegExp | string | MatcherFunction /** * Only considers elements with the specified accessible description. */ - description?: - | RegExp - | string - | MatcherFunction + description?: RegExp | string | MatcherFunction } -export type AllByRole = QueryMethod<[ - ByRoleMatcher, - ByRoleOptions | undeined -], T[]>; +export type AllByRole = QueryMethod< + [ByRoleMatcher, ByRoleOptions | undeined], + T[] +> export type GetByRole = ( container: HTMLElement, From 3b24e00849cc6d19d727c681e45a914af758b7fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Acu=C3=B1a?= Date: Sun, 30 Oct 2022 18:32:43 -0300 Subject: [PATCH 04/22] fix: force have role that are included in type --- types/__tests__/type-tests.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/types/__tests__/type-tests.ts b/types/__tests__/type-tests.ts index 7a3212cf..7399f366 100644 --- a/types/__tests__/type-tests.ts +++ b/types/__tests__/type-tests.ts @@ -178,15 +178,9 @@ export async function testByRole() { console.assert(queryByRole(element, 'button', {name: /^Log/}) === null) console.assert( queryByRole(element, 'button', { - name: (name, el) => name === 'Login' && el.hasAttribute('disabled'), + name: (name, el) => name === 'Login' && el!.hasAttribute('disabled'), }) === null, ) - - // allow to query for a role that isn't included in the types - console.assert(queryByRole(element, 'foo') === null) - console.assert(queryByRole(element, /foo/) === null) - console.assert(screen.queryByRole('foo') === null) - console.assert(screen.queryByRole(/foo/) === null) } export function testA11yHelper() { From 037c07366e33fc3b915dc1303539c695bf0dac1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Acu=C3=B1a?= Date: Sun, 30 Oct 2022 18:45:03 -0300 Subject: [PATCH 05/22] fix: type --- src/query-helpers.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/query-helpers.ts b/src/query-helpers.ts index 7722d617..d43d1c73 100644 --- a/src/query-helpers.ts +++ b/src/query-helpers.ts @@ -4,6 +4,7 @@ import { type ByRoleOptions, type GetErrorFunction, type Matcher, + type MatcherFunction, type MatcherOptions, type QueryMethod, type Variant, @@ -228,9 +229,9 @@ function buildQueries( queryAllBy.name, 'query', ) - const getAllBy = makeGetAllQuery(queryAllBy, getMissingError) + const getAllBy = makeGetAllQuery(queryAllBy, getMissingError) as any - const getBy = makeSingleQuery(getAllBy, getMultipleError) + const getBy = makeSingleQuery(getAllBy, getMultipleError) as any const getByWithSuggestions = wrapSingleQueryWithSuggestion( getBy, queryAllBy.name, From 98568cd674421e301c7dcb8933f41797691e4ca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Acu=C3=B1a?= Date: Mon, 31 Oct 2022 20:05:05 -0300 Subject: [PATCH 06/22] fix: error and add commet ts --- src/query-helpers.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/query-helpers.ts b/src/query-helpers.ts index d43d1c73..c72eb1c9 100644 --- a/src/query-helpers.ts +++ b/src/query-helpers.ts @@ -229,9 +229,9 @@ function buildQueries( queryAllBy.name, 'query', ) - const getAllBy = makeGetAllQuery(queryAllBy, getMissingError) as any + const getAllBy = makeGetAllQuery(queryAllBy, getMissingError) - const getBy = makeSingleQuery(getAllBy, getMultipleError) as any + const getBy = makeSingleQuery(getAllBy, getMultipleError) const getByWithSuggestions = wrapSingleQueryWithSuggestion( getBy, queryAllBy.name, @@ -244,9 +244,11 @@ function buildQueries( ) const findAllBy = makeFindQuery( + // @ts-expect-error -- See `wrapAllByQueryWithSuggestion` Argument constraint comment wrapAllByQueryWithSuggestion(getAllBy, queryAllBy.name, 'findAll'), ) const findBy = makeFindQuery( + // @ts-expect-error -- See `wrapAllByQueryWithSuggestion` Argument constraint comment wrapSingleQueryWithSuggestion(getBy, queryAllBy.name, 'find'), ) From 984acb9603e8a841f10113460a2546b7d1cd7b0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Acu=C3=B1a?= Date: Mon, 31 Oct 2022 20:17:19 -0300 Subject: [PATCH 07/22] fix: any type --- src/queries/role.ts | 24 ++++++++++-------------- types/queries.d.ts | 2 +- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/queries/role.ts b/src/queries/role.ts index 7f8607e5..3417b4ae 100644 --- a/src/queries/role.ts +++ b/src/queries/role.ts @@ -2,12 +2,7 @@ import { computeAccessibleDescription, computeAccessibleName, } from 'dom-accessibility-api' -import { - roles as allRoles, - roleElements, - ARIAPropertyMap, - ARIARoleDefintionKey, -} from 'aria-query' +import {roles as allRoles, roleElements, ARIARoleDefintionKey} from 'aria-query' import { computeAriaSelected, computeAriaChecked, @@ -22,13 +17,6 @@ import { } from '../role-helpers' import {wrapAllByQueryWithSuggestion} from '../query-helpers' import {checkContainerType} from '../helpers' -import { - buildQueries, - fuzzyMatches, - getConfig, - makeNormalizer, - matches, -} from './all-utils' import { AllByRole, ByRoleMatcher, @@ -38,6 +26,14 @@ import { NormalizerFn, } from '../../types' +import { + buildQueries, + fuzzyMatches, + getConfig, + makeNormalizer, + matches, +} from './all-utils' + const queryAllByRole: AllByRole = ( container, role, @@ -139,7 +135,7 @@ const queryAllByRole: AllByRole = ( const isRoleSpecifiedExplicitly = node.hasAttribute('role') if (isRoleSpecifiedExplicitly) { - const roleValue = node.getAttribute('role') || '' + const roleValue = node.getAttribute('role') ?? '' if (queryFallbacks) { return roleValue .split(' ') diff --git a/types/queries.d.ts b/types/queries.d.ts index 52e706c9..8d9d17c0 100644 --- a/types/queries.d.ts +++ b/types/queries.d.ts @@ -124,7 +124,7 @@ export interface ByRoleOptions extends MatcherOptions { } export type AllByRole = QueryMethod< - [ByRoleMatcher, ByRoleOptions | undeined], + [ByRoleMatcher, ByRoleOptions | undefined], T[] > From 9fab0b87028f2b2eccce0aa2dc728d2ef8d71147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Acu=C3=B1a?= Date: Mon, 31 Oct 2022 20:26:38 -0300 Subject: [PATCH 08/22] fix: any type --- src/queries/role.ts | 4 ++-- src/query-helpers.ts | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/queries/role.ts b/src/queries/role.ts index 3417b4ae..bf599c19 100644 --- a/src/queries/role.ts +++ b/src/queries/role.ts @@ -151,7 +151,7 @@ const queryAllByRole: AllByRole = ( return matcher(firstWord, node, role, matchNormalizer) } - const implicitRoles = getImplicitAriaRoles(node) + const implicitRoles = getImplicitAriaRoles(node) as string[] return implicitRoles.some(implicitRole => matcher(implicitRole, node, role, matchNormalizer), @@ -277,7 +277,7 @@ const getMissingError: GetErrorFunction< } let roles = '' - Array.from(container?.children || []).forEach(childElement => { + Array.from(container?.children ?? []).forEach(childElement => { roles += prettyRoles(childElement, { hidden, includeDescription: description !== undefined, diff --git a/src/query-helpers.ts b/src/query-helpers.ts index c72eb1c9..5a84b776 100644 --- a/src/query-helpers.ts +++ b/src/query-helpers.ts @@ -1,10 +1,9 @@ -import { +import { type AllByRole, type ByRoleMatcher, type ByRoleOptions, type GetErrorFunction, type Matcher, - type MatcherFunction, type MatcherOptions, type QueryMethod, type Variant, From 82503fbf2761142891df650c8097bf90a23dfad8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Acu=C3=B1a?= Date: Mon, 31 Oct 2022 20:43:14 -0300 Subject: [PATCH 09/22] fix: type assertion --- types/__tests__/type-tests.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/__tests__/type-tests.ts b/types/__tests__/type-tests.ts index 7399f366..5e14b390 100644 --- a/types/__tests__/type-tests.ts +++ b/types/__tests__/type-tests.ts @@ -178,7 +178,7 @@ export async function testByRole() { console.assert(queryByRole(element, 'button', {name: /^Log/}) === null) console.assert( queryByRole(element, 'button', { - name: (name, el) => name === 'Login' && el!.hasAttribute('disabled'), + name: (name, el) => name === 'Login' && !!el?.hasAttribute('disabled'), }) === null, ) } From 3de4aa93a6c69d4aeb22b2e37faea88bc415fb25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Acu=C3=B1a?= Date: Mon, 31 Oct 2022 20:53:36 -0300 Subject: [PATCH 10/22] fix: ignore branch coverage --- src/queries/role.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/queries/role.ts b/src/queries/role.ts index bf599c19..2ad1cd53 100644 --- a/src/queries/role.ts +++ b/src/queries/role.ts @@ -135,6 +135,7 @@ const queryAllByRole: AllByRole = ( const isRoleSpecifiedExplicitly = node.hasAttribute('role') if (isRoleSpecifiedExplicitly) { + /* istanbul ignore next */ const roleValue = node.getAttribute('role') ?? '' if (queryFallbacks) { return roleValue @@ -277,7 +278,10 @@ const getMissingError: GetErrorFunction< } let roles = '' - Array.from(container?.children ?? []).forEach(childElement => { + Array.from( + /* istanbul ignore next */ + container?.children ?? [], + ).forEach(childElement => { roles += prettyRoles(childElement, { hidden, includeDescription: description !== undefined, From a1fb8c13a80e43c2610bd483b75a7e1160776f11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Acu=C3=B1a?= Date: Mon, 9 Jan 2023 23:54:25 -0300 Subject: [PATCH 11/22] fix: typo --- src/queries/role.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/queries/role.ts b/src/queries/role.ts index 2ad1cd53..cec79218 100644 --- a/src/queries/role.ts +++ b/src/queries/role.ts @@ -2,7 +2,11 @@ import { computeAccessibleDescription, computeAccessibleName, } from 'dom-accessibility-api' -import {roles as allRoles, roleElements, ARIARoleDefintionKey} from 'aria-query' +import { + roles as allRoles, + roleElements, + ARIARoleDefinitionKey, +} from 'aria-query' import { computeAriaSelected, computeAriaChecked, @@ -61,7 +65,7 @@ const queryAllByRole: AllByRole = ( if (selected !== undefined) { // guard against unknown roles if ( - allRoles.get(role as ARIARoleDefintionKey)?.props['aria-selected'] === + allRoles.get(role as ARIARoleDefinitionKey)?.props['aria-selected'] === undefined ) { throw new Error(`"aria-selected" is not supported on role "${role}".`) @@ -71,7 +75,7 @@ const queryAllByRole: AllByRole = ( if (checked !== undefined) { // guard against unknown roles if ( - allRoles.get(role as ARIARoleDefintionKey)?.props['aria-checked'] === + allRoles.get(role as ARIARoleDefinitionKey)?.props['aria-checked'] === undefined ) { throw new Error(`"aria-checked" is not supported on role "${role}".`) @@ -81,7 +85,7 @@ const queryAllByRole: AllByRole = ( if (pressed !== undefined) { // guard against unknown roles if ( - allRoles.get(role as ARIARoleDefintionKey)?.props['aria-pressed'] === + allRoles.get(role as ARIARoleDefinitionKey)?.props['aria-pressed'] === undefined ) { throw new Error(`"aria-pressed" is not supported on role "${role}".`) @@ -94,7 +98,7 @@ const queryAllByRole: AllByRole = ( // All currently released ARIA versions support `aria-current` on all roles. // Leaving this for symetry and forward compatibility if ( - allRoles.get(role as ARIARoleDefintionKey)?.props['aria-current'] === + allRoles.get(role as ARIARoleDefinitionKey)?.props['aria-current'] === undefined ) { throw new Error(`"aria-current" is not supported on role "${role}".`) @@ -109,7 +113,7 @@ const queryAllByRole: AllByRole = ( if (expanded !== undefined) { // guard against unknown roles if ( - allRoles.get(role as ARIARoleDefintionKey)?.props['aria-expanded'] === + allRoles.get(role as ARIARoleDefinitionKey)?.props['aria-expanded'] === undefined ) { throw new Error(`"aria-expanded" is not supported on role "${role}".`) From 874e7699ac5d052b1866e323df7e84a66369e1be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Acu=C3=B1a?= Date: Tue, 10 Jan 2023 21:14:28 -0300 Subject: [PATCH 12/22] fix: add hints in all by role --- types/queries.d.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/types/queries.d.ts b/types/queries.d.ts index 8d9d17c0..8940f373 100644 --- a/types/queries.d.ts +++ b/types/queries.d.ts @@ -4,7 +4,7 @@ import { MatcherFunction, MatcherOptions, } from './matches' -import {QueryMethod, SelectorMatcherOptions} from './query-helpers' +import {SelectorMatcherOptions} from './query-helpers' import {waitForOptions} from './wait-for' export type QueryByBoundAttribute = ( @@ -123,10 +123,11 @@ export interface ByRoleOptions extends MatcherOptions { description?: RegExp | string | MatcherFunction } -export type AllByRole = QueryMethod< - [ByRoleMatcher, ByRoleOptions | undefined], - T[] -> +export type AllByRole = ( + container: HTMLElement, + role: ByRoleMatcher, + options?: ByRoleOptions, +) => T[] export type GetByRole = ( container: HTMLElement, From 7431f752b37dc345cd2be5ee64cf32cccaac0dec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Acu=C3=B1a?= Date: Sat, 14 Jan 2023 20:51:05 -0300 Subject: [PATCH 13/22] fix: types and comments --- src/queries/role.ts | 20 ++++++++++---------- types/matches.d.ts | 5 ++--- types/queries.d.ts | 10 ++++++++-- 3 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/queries/role.ts b/src/queries/role.ts index cec79218..da527bc8 100644 --- a/src/queries/role.ts +++ b/src/queries/role.ts @@ -26,6 +26,7 @@ import { ByRoleMatcher, ByRoleOptions, GetErrorFunction, + MatcherFunction, MatcherOptions, NormalizerFn, } from '../../types' @@ -105,9 +106,11 @@ const queryAllByRole: AllByRole = ( } } - // guard against using `level` option with any role other than `heading` - if (level !== undefined && role !== 'heading') { - throw new Error(`Role "${role}" cannot have "level" property.`) + if (level !== undefined) { + // guard against using `level` option with any role other than `heading` + if (role !== 'heading') { + throw new Error(`Role "${role}" cannot have "level" property.`) + } } if (expanded !== undefined) { @@ -139,8 +142,8 @@ const queryAllByRole: AllByRole = ( const isRoleSpecifiedExplicitly = node.hasAttribute('role') if (isRoleSpecifiedExplicitly) { + const roleValue = node.getAttribute('role')! /* istanbul ignore next */ - const roleValue = node.getAttribute('role') ?? '' if (queryFallbacks) { return roleValue .split(' ') @@ -156,7 +159,7 @@ const queryAllByRole: AllByRole = ( return matcher(firstWord, node, role, matchNormalizer) } - const implicitRoles = getImplicitAriaRoles(node) as string[] + const implicitRoles = getImplicitAriaRoles(node) return implicitRoles.some(implicitRole => matcher(implicitRole, node, role, matchNormalizer), @@ -196,7 +199,7 @@ const queryAllByRole: AllByRole = ( getConfig().computedStyleSupportsPseudoElements, }), element, - name, + name as MatcherFunction, text => text, ) }) @@ -282,10 +285,7 @@ const getMissingError: GetErrorFunction< } let roles = '' - Array.from( - /* istanbul ignore next */ - container?.children ?? [], - ).forEach(childElement => { + Array.from(container!.children).forEach(childElement => { roles += prettyRoles(childElement, { hidden, includeDescription: description !== undefined, diff --git a/types/matches.d.ts b/types/matches.d.ts index 0a90975f..13fa3692 100644 --- a/types/matches.d.ts +++ b/types/matches.d.ts @@ -4,12 +4,11 @@ export type MatcherFunction = ( content: string, element: Element | null, ) => boolean +export type Matcher = MatcherFunction | RegExp | number | string // Get autocomplete for ARIARole union types, while still supporting another string // Ref: https://github.com/microsoft/TypeScript/issues/29729#issuecomment-505826972 -export type ByRoleMatcher = MatcherFunction | ARIARole - -export type Matcher = ByRoleMatcher | MatcherFunction | RegExp | number | string +export type ByRoleMatcher = ARIARole | MatcherFunction | {} export type NormalizerFn = (text: string) => string diff --git a/types/queries.d.ts b/types/queries.d.ts index 8940f373..b764dac8 100644 --- a/types/queries.d.ts +++ b/types/queries.d.ts @@ -116,11 +116,17 @@ export interface ByRoleOptions extends MatcherOptions { /** * Only considers elements with the specified accessible name. */ - name?: RegExp | string | MatcherFunction + name?: + | RegExp + | string + | ((accessibleName: string, element: Element) => boolean) /** * Only considers elements with the specified accessible description. */ - description?: RegExp | string | MatcherFunction + description?: + | RegExp + | string + | ((accessibleName: string, element: Element) => boolean) } export type AllByRole = ( From 8a3c1a0b0c54a53b6aadca46d3fb476550c17352 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Acu=C3=B1a?= Date: Sat, 14 Jan 2023 21:04:23 -0300 Subject: [PATCH 14/22] fix: restore types --- src/queries/role.ts | 14 +++++++------- src/query-helpers.ts | 21 ++++++++++----------- types/queries.d.ts | 2 +- 3 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/queries/role.ts b/src/queries/role.ts index da527bc8..9ba8f49a 100644 --- a/src/queries/role.ts +++ b/src/queries/role.ts @@ -26,6 +26,7 @@ import { ByRoleMatcher, ByRoleOptions, GetErrorFunction, + Matcher, MatcherFunction, MatcherOptions, NormalizerFn, @@ -143,26 +144,25 @@ const queryAllByRole: AllByRole = ( if (isRoleSpecifiedExplicitly) { const roleValue = node.getAttribute('role')! - /* istanbul ignore next */ if (queryFallbacks) { return roleValue .split(' ') .filter(Boolean) - .some(text => matcher(text, node, role, matchNormalizer)) + .some(text => matcher(text, node, role as Matcher, matchNormalizer)) } // if a custom normalizer is passed then let normalizer handle the role value if (normalizer) { - return matcher(roleValue, node, role, matchNormalizer) + return matcher(roleValue, node, role as Matcher, matchNormalizer) } // other wise only send the first word to match const [firstWord] = roleValue.split(' ') - return matcher(firstWord, node, role, matchNormalizer) + return matcher(firstWord, node, role as Matcher, matchNormalizer) } const implicitRoles = getImplicitAriaRoles(node) return implicitRoles.some(implicitRole => - matcher(implicitRole, node, role, matchNormalizer), + matcher(implicitRole, node, role as Matcher, matchNormalizer), ) }) .filter(element => { @@ -215,7 +215,7 @@ const queryAllByRole: AllByRole = ( getConfig().computedStyleSupportsPseudoElements, }), element, - description, + description as Matcher, text => text, ) }) @@ -241,7 +241,7 @@ function makeRoleSelector( const explicitRoleSelector = exact && !customNormalizer ? `*[role~="${role}"]` : '*[role]' - const roleRelations = roleElements.get(role) ?? new Set() + const roleRelations = roleElements.get(role as any) ?? new Set() const implicitRoleSelectors = new Set( Array.from(roleRelations).map(({name}) => name), ) diff --git a/src/query-helpers.ts b/src/query-helpers.ts index 5a84b776..f3b1ff52 100644 --- a/src/query-helpers.ts +++ b/src/query-helpers.ts @@ -213,15 +213,16 @@ const wrapAllByQueryWithSuggestion = // However, the implementation always required a dyadic (after `container`) not variadic `queryAllBy` considering the implementation of `makeFindQuery` // This is at least statically true and can be verified by accepting `QueryMethod` function buildQueries( - queryAllBy: - | AllByRole - | QueryMethod<[matcher: Matcher, options: MatcherOptions], HTMLElement[]>, - getMultipleError: - | GetErrorFunction<[matcher: ByRoleMatcher, options: ByRoleOptions]> - | GetErrorFunction<[matcher: Matcher, options: MatcherOptions]>, - getMissingError: - | GetErrorFunction<[matcher: ByRoleMatcher, options: ByRoleOptions]> - | GetErrorFunction<[matcher: Matcher, options: MatcherOptions]>, + queryAllBy: QueryMethod< + [matcher: Matcher, options: MatcherOptions], + HTMLElement[] + >, + getMultipleError: GetErrorFunction< + [matcher: Matcher, options: MatcherOptions] + >, + getMissingError: GetErrorFunction< + [matcher: Matcher, options: MatcherOptions] + >, ) { const queryBy = wrapSingleQueryWithSuggestion( makeSingleQuery(queryAllBy, getMultipleError), @@ -243,11 +244,9 @@ function buildQueries( ) const findAllBy = makeFindQuery( - // @ts-expect-error -- See `wrapAllByQueryWithSuggestion` Argument constraint comment wrapAllByQueryWithSuggestion(getAllBy, queryAllBy.name, 'findAll'), ) const findBy = makeFindQuery( - // @ts-expect-error -- See `wrapAllByQueryWithSuggestion` Argument constraint comment wrapSingleQueryWithSuggestion(getBy, queryAllBy.name, 'find'), ) diff --git a/types/queries.d.ts b/types/queries.d.ts index b764dac8..c6815987 100644 --- a/types/queries.d.ts +++ b/types/queries.d.ts @@ -126,7 +126,7 @@ export interface ByRoleOptions extends MatcherOptions { description?: | RegExp | string - | ((accessibleName: string, element: Element) => boolean) + | ((accessibleDescription: string, element: Element) => boolean) } export type AllByRole = ( From df141225ced77034e83e484eb87bd160714edbab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Acu=C3=B1a?= Date: Sat, 14 Jan 2023 21:05:55 -0300 Subject: [PATCH 15/22] fix: restore types --- types/queries.d.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/types/queries.d.ts b/types/queries.d.ts index c6815987..1fb66b7d 100644 --- a/types/queries.d.ts +++ b/types/queries.d.ts @@ -1,9 +1,4 @@ -import { - ByRoleMatcher, - Matcher, - MatcherFunction, - MatcherOptions, -} from './matches' +import {ByRoleMatcher, Matcher, MatcherOptions} from './matches' import {SelectorMatcherOptions} from './query-helpers' import {waitForOptions} from './wait-for' From 4423a51b894a86f24ca087d474e69c59846c80f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Acu=C3=B1a?= Date: Sun, 15 Jan 2023 11:37:43 -0300 Subject: [PATCH 16/22] fix: lint --- src/queries/role.ts | 14 +++++++------- src/query-helpers.ts | 21 +++++++++++---------- types/matches.d.ts | 2 +- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/queries/role.ts b/src/queries/role.ts index 9ba8f49a..8be0aeee 100644 --- a/src/queries/role.ts +++ b/src/queries/role.ts @@ -124,13 +124,13 @@ const queryAllByRole: AllByRole = ( } } - const subtreeIsInaccessibleCache = new WeakMap() - function cachedIsSubtreeInaccessible(element: Element) { + const subtreeIsInaccessibleCache = new WeakMap() + function cachedIsSubtreeInaccessible(element: HTMLElement): Boolean { if (!subtreeIsInaccessibleCache.has(element)) { subtreeIsInaccessibleCache.set(element, isSubtreeInaccessible(element)) } - return subtreeIsInaccessibleCache.get(element) + return subtreeIsInaccessibleCache.get(element) as Boolean } return Array.from( @@ -143,7 +143,7 @@ const queryAllByRole: AllByRole = ( const isRoleSpecifiedExplicitly = node.hasAttribute('role') if (isRoleSpecifiedExplicitly) { - const roleValue = node.getAttribute('role')! + const roleValue = node.getAttribute('role') as string if (queryFallbacks) { return roleValue .split(' ') @@ -222,7 +222,7 @@ const queryAllByRole: AllByRole = ( .filter(element => { return hidden === false ? isInaccessible(element, { - isSubtreeInaccessible: cachedIsSubtreeInaccessible, + isSubtreeInaccessible: cachedIsSubtreeInaccessible as any, }) === false : true }) @@ -241,7 +241,7 @@ function makeRoleSelector( const explicitRoleSelector = exact && !customNormalizer ? `*[role~="${role}"]` : '*[role]' - const roleRelations = roleElements.get(role as any) ?? new Set() + const roleRelations = roleElements.get(role) ?? new Set() const implicitRoleSelectors = new Set( Array.from(roleRelations).map(({name}) => name), ) @@ -285,7 +285,7 @@ const getMissingError: GetErrorFunction< } let roles = '' - Array.from(container!.children).forEach(childElement => { + Array.from((container as Element).children).forEach(childElement => { roles += prettyRoles(childElement, { hidden, includeDescription: description !== undefined, diff --git a/src/query-helpers.ts b/src/query-helpers.ts index f3b1ff52..b18c16df 100644 --- a/src/query-helpers.ts +++ b/src/query-helpers.ts @@ -213,16 +213,15 @@ const wrapAllByQueryWithSuggestion = // However, the implementation always required a dyadic (after `container`) not variadic `queryAllBy` considering the implementation of `makeFindQuery` // This is at least statically true and can be verified by accepting `QueryMethod` function buildQueries( - queryAllBy: QueryMethod< - [matcher: Matcher, options: MatcherOptions], - HTMLElement[] - >, - getMultipleError: GetErrorFunction< - [matcher: Matcher, options: MatcherOptions] - >, - getMissingError: GetErrorFunction< - [matcher: Matcher, options: MatcherOptions] - >, + queryAllBy: + | AllByRole + | QueryMethod<[matcher: Matcher, options: MatcherOptions], HTMLElement[]>, + getMultipleError: + | GetErrorFunction<[matcher: ByRoleMatcher, options: ByRoleOptions]> + | GetErrorFunction<[matcher: Matcher, options: MatcherOptions]>, + getMissingError: + | GetErrorFunction<[matcher: ByRoleMatcher, options: ByRoleOptions]> + | GetErrorFunction<[matcher: Matcher, options: MatcherOptions]>, ) { const queryBy = wrapSingleQueryWithSuggestion( makeSingleQuery(queryAllBy, getMultipleError), @@ -244,9 +243,11 @@ function buildQueries( ) const findAllBy = makeFindQuery( + /* @ts-ignore */ wrapAllByQueryWithSuggestion(getAllBy, queryAllBy.name, 'findAll'), ) const findBy = makeFindQuery( + /* @ts-ignore */ wrapSingleQueryWithSuggestion(getBy, queryAllBy.name, 'find'), ) diff --git a/types/matches.d.ts b/types/matches.d.ts index 13fa3692..05ee443a 100644 --- a/types/matches.d.ts +++ b/types/matches.d.ts @@ -8,7 +8,7 @@ export type Matcher = MatcherFunction | RegExp | number | string // Get autocomplete for ARIARole union types, while still supporting another string // Ref: https://github.com/microsoft/TypeScript/issues/29729#issuecomment-505826972 -export type ByRoleMatcher = ARIARole | MatcherFunction | {} +export type ByRoleMatcher = ARIARole | MatcherFunction export type NormalizerFn = (text: string) => string From 4de62b3b91a6da8b0874c6ada173741f0f05dc0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Acu=C3=B1a?= Date: Sun, 15 Jan 2023 23:34:35 -0300 Subject: [PATCH 17/22] fix: ts error --- src/query-helpers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/query-helpers.ts b/src/query-helpers.ts index b18c16df..a0132bac 100644 --- a/src/query-helpers.ts +++ b/src/query-helpers.ts @@ -243,11 +243,11 @@ function buildQueries( ) const findAllBy = makeFindQuery( - /* @ts-ignore */ + // @ts-expect-error wrapAllByQueryWithSuggestion(getAllBy, queryAllBy.name, 'findAll'), ) const findBy = makeFindQuery( - /* @ts-ignore */ + // @ts-expect-error wrapSingleQueryWithSuggestion(getBy, queryAllBy.name, 'find'), ) From 1ea74bd7552827c49c72fdd7790fdf53e553c3b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Acu=C3=B1a?= Date: Sun, 15 Jan 2023 23:43:32 -0300 Subject: [PATCH 18/22] fix: ts error --- src/queries/role.ts | 2 +- src/query-helpers.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/queries/role.ts b/src/queries/role.ts index 8be0aeee..ac977c0b 100644 --- a/src/queries/role.ts +++ b/src/queries/role.ts @@ -159,7 +159,7 @@ const queryAllByRole: AllByRole = ( return matcher(firstWord, node, role as Matcher, matchNormalizer) } - const implicitRoles = getImplicitAriaRoles(node) + const implicitRoles = getImplicitAriaRoles(node) as string[] return implicitRoles.some(implicitRole => matcher(implicitRole, node, role as Matcher, matchNormalizer), diff --git a/src/query-helpers.ts b/src/query-helpers.ts index a0132bac..ad4fbc82 100644 --- a/src/query-helpers.ts +++ b/src/query-helpers.ts @@ -243,11 +243,11 @@ function buildQueries( ) const findAllBy = makeFindQuery( - // @ts-expect-error + // @ts-expect-error: ByRoleMatcher and ByRoleOptions are not compatible with Matcher and MatcherOptions respectible wrapAllByQueryWithSuggestion(getAllBy, queryAllBy.name, 'findAll'), ) const findBy = makeFindQuery( - // @ts-expect-error + // @ts-expect-error: ByRoleMatcher and ByRoleOptions are not compatible with Matcher and MatcherOptions respectible wrapSingleQueryWithSuggestion(getBy, queryAllBy.name, 'find'), ) From 8ae5a9c41d82113885f44fc60c7eb64b74094145 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Acu=C3=B1a?= Date: Sun, 15 Jan 2023 23:49:51 -0300 Subject: [PATCH 19/22] fix: type tests --- types/__tests__/type-tests.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/types/__tests__/type-tests.ts b/types/__tests__/type-tests.ts index 5e14b390..904c84e2 100644 --- a/types/__tests__/type-tests.ts +++ b/types/__tests__/type-tests.ts @@ -178,9 +178,18 @@ export async function testByRole() { console.assert(queryByRole(element, 'button', {name: /^Log/}) === null) console.assert( queryByRole(element, 'button', { - name: (name, el) => name === 'Login' && !!el?.hasAttribute('disabled'), + name: (name, el) => name === 'Login' && el.hasAttribute('disabled'), }) === null, ) + + // @ts-expect-error: allow to query for a role that isn't included in the types + console.assert(queryByRole(element, 'foo') === null) + // @ts-expect-error: allow to query for a role that isn't included in the types + console.assert(queryByRole(element, /foo/) === null) + // @ts-expect-error: allow to query for a role that isn't included in the types + console.assert(screen.queryByRole('foo') === null) + // @ts-expect-error: allow to query for a role that isn't included in the types + console.assert(screen.queryByRole(/foo/) === null) } export function testA11yHelper() { From 94163c1cd31a44f38e5c99a8a1ca86f62cbc9cf6 Mon Sep 17 00:00:00 2001 From: eps1lon Date: Mon, 6 Feb 2023 19:03:26 +0100 Subject: [PATCH 20/22] Fix lint --- package.json | 1 + src/queries/role.ts | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 11ad8217..fd4a11d1 100644 --- a/package.json +++ b/package.json @@ -90,6 +90,7 @@ "@typescript-eslint/prefer-optional-chain": "off", "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-unsafe-member-access": "off", + "@typescript-eslint/no-unnecessary-boolean-literal-compare": "off", "@typescript-eslint/prefer-includes": "off", "import/prefer-default-export": "off", "import/no-unassigned-import": "off", diff --git a/src/queries/role.ts b/src/queries/role.ts index ac977c0b..414cd080 100644 --- a/src/queries/role.ts +++ b/src/queries/role.ts @@ -124,13 +124,13 @@ const queryAllByRole: AllByRole = ( } } - const subtreeIsInaccessibleCache = new WeakMap() - function cachedIsSubtreeInaccessible(element: HTMLElement): Boolean { + const subtreeIsInaccessibleCache = new WeakMap() + function cachedIsSubtreeInaccessible(element: Element) { if (!subtreeIsInaccessibleCache.has(element)) { subtreeIsInaccessibleCache.set(element, isSubtreeInaccessible(element)) } - return subtreeIsInaccessibleCache.get(element) as Boolean + return subtreeIsInaccessibleCache.get(element) as boolean } return Array.from( @@ -222,7 +222,7 @@ const queryAllByRole: AllByRole = ( .filter(element => { return hidden === false ? isInaccessible(element, { - isSubtreeInaccessible: cachedIsSubtreeInaccessible as any, + isSubtreeInaccessible: cachedIsSubtreeInaccessible, }) === false : true }) From 30d44d0ee66cf36905f54d0bdf8ea00623ac75dd Mon Sep 17 00:00:00 2001 From: eps1lon Date: Mon, 6 Feb 2023 19:10:11 +0100 Subject: [PATCH 21/22] Don't couple query-helpers with ByRole --- src/query-helpers.ts | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/src/query-helpers.ts b/src/query-helpers.ts index ad4fbc82..44328e69 100644 --- a/src/query-helpers.ts +++ b/src/query-helpers.ts @@ -1,7 +1,4 @@ -import { - type AllByRole, - type ByRoleMatcher, - type ByRoleOptions, +import { type GetErrorFunction, type Matcher, type MatcherOptions, @@ -120,16 +117,16 @@ function makeGetAllQuery( // this accepts a getter query function and returns a function which calls // waitFor and passing a function which invokes the getter. -function makeFindQuery( +function makeFindQuery( getter: ( container: HTMLElement, - text: Matcher, + text: QueryMatcher, options: MatcherOptions, ) => QueryFor, ) { return ( container: HTMLElement, - text: Matcher, + text: QueryMatcher, options: MatcherOptions, waitForOptions: WaitForOptions, ) => { @@ -212,16 +209,17 @@ const wrapAllByQueryWithSuggestion = // TODO: This deviates from the published declarations // However, the implementation always required a dyadic (after `container`) not variadic `queryAllBy` considering the implementation of `makeFindQuery` // This is at least statically true and can be verified by accepting `QueryMethod` -function buildQueries( - queryAllBy: - | AllByRole - | QueryMethod<[matcher: Matcher, options: MatcherOptions], HTMLElement[]>, - getMultipleError: - | GetErrorFunction<[matcher: ByRoleMatcher, options: ByRoleOptions]> - | GetErrorFunction<[matcher: Matcher, options: MatcherOptions]>, - getMissingError: - | GetErrorFunction<[matcher: ByRoleMatcher, options: ByRoleOptions]> - | GetErrorFunction<[matcher: Matcher, options: MatcherOptions]>, +function buildQueries( + queryAllBy: QueryMethod< + [matcher: QueryMatcher, options: MatcherOptions], + HTMLElement[] + >, + getMultipleError: GetErrorFunction< + [matcher: QueryMatcher, options: MatcherOptions] + >, + getMissingError: GetErrorFunction< + [matcher: QueryMatcher, options: MatcherOptions] + >, ) { const queryBy = wrapSingleQueryWithSuggestion( makeSingleQuery(queryAllBy, getMultipleError), @@ -243,11 +241,9 @@ function buildQueries( ) const findAllBy = makeFindQuery( - // @ts-expect-error: ByRoleMatcher and ByRoleOptions are not compatible with Matcher and MatcherOptions respectible wrapAllByQueryWithSuggestion(getAllBy, queryAllBy.name, 'findAll'), ) const findBy = makeFindQuery( - // @ts-expect-error: ByRoleMatcher and ByRoleOptions are not compatible with Matcher and MatcherOptions respectible wrapSingleQueryWithSuggestion(getBy, queryAllBy.name, 'find'), ) From 9e5d4b4b5634823abda07c912be5d2596d4dacb2 Mon Sep 17 00:00:00 2001 From: eps1lon Date: Mon, 6 Feb 2023 19:12:33 +0100 Subject: [PATCH 22/22] Revert change to ByRoleMatcher type --- src/queries/role.ts | 3 ++- types/__tests__/type-tests.ts | 4 ---- types/matches.d.ts | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/queries/role.ts b/src/queries/role.ts index 414cd080..b1066467 100644 --- a/src/queries/role.ts +++ b/src/queries/role.ts @@ -241,7 +241,8 @@ function makeRoleSelector( const explicitRoleSelector = exact && !customNormalizer ? `*[role~="${role}"]` : '*[role]' - const roleRelations = roleElements.get(role) ?? new Set() + const roleRelations = + roleElements.get(role as ARIARoleDefinitionKey) ?? new Set() const implicitRoleSelectors = new Set( Array.from(roleRelations).map(({name}) => name), ) diff --git a/types/__tests__/type-tests.ts b/types/__tests__/type-tests.ts index 904c84e2..b0240e6a 100644 --- a/types/__tests__/type-tests.ts +++ b/types/__tests__/type-tests.ts @@ -182,13 +182,9 @@ export async function testByRole() { }) === null, ) - // @ts-expect-error: allow to query for a role that isn't included in the types console.assert(queryByRole(element, 'foo') === null) - // @ts-expect-error: allow to query for a role that isn't included in the types console.assert(queryByRole(element, /foo/) === null) - // @ts-expect-error: allow to query for a role that isn't included in the types console.assert(screen.queryByRole('foo') === null) - // @ts-expect-error: allow to query for a role that isn't included in the types console.assert(screen.queryByRole(/foo/) === null) } diff --git a/types/matches.d.ts b/types/matches.d.ts index 05ee443a..13fa3692 100644 --- a/types/matches.d.ts +++ b/types/matches.d.ts @@ -8,7 +8,7 @@ export type Matcher = MatcherFunction | RegExp | number | string // Get autocomplete for ARIARole union types, while still supporting another string // Ref: https://github.com/microsoft/TypeScript/issues/29729#issuecomment-505826972 -export type ByRoleMatcher = ARIARole | MatcherFunction +export type ByRoleMatcher = ARIARole | MatcherFunction | {} export type NormalizerFn = (text: string) => string