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.js b/src/queries/role.ts similarity index 76% rename from src/queries/role.js rename to src/queries/role.ts index a46d8a2b..b1066467 100644 --- a/src/queries/role.js +++ b/src/queries/role.ts @@ -2,7 +2,11 @@ import { computeAccessibleDescription, computeAccessibleName, } from 'dom-accessibility-api' -import {roles as allRoles, roleElements} from 'aria-query' +import { + roles as allRoles, + roleElements, + ARIARoleDefinitionKey, +} from 'aria-query' import { computeAriaSelected, computeAriaChecked, @@ -17,6 +21,17 @@ import { } from '../role-helpers' import {wrapAllByQueryWithSuggestion} from '../query-helpers' import {checkContainerType} from '../helpers' +import { + AllByRole, + ByRoleMatcher, + ByRoleOptions, + GetErrorFunction, + Matcher, + MatcherFunction, + MatcherOptions, + NormalizerFn, +} from '../../types' + import { buildQueries, fuzzyMatches, @@ -25,7 +40,7 @@ import { matches, } from './all-utils' -function queryAllByRole( +const queryAllByRole: AllByRole = ( container, role, { @@ -44,28 +59,37 @@ 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 ( + allRoles.get(role as ARIARoleDefinitionKey)?.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)?.props['aria-checked'] === undefined) { + if ( + allRoles.get(role as ARIARoleDefinitionKey)?.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)?.props['aria-pressed'] === undefined) { + if ( + allRoles.get(role as ARIARoleDefinitionKey)?.props['aria-pressed'] === + undefined + ) { throw new Error(`"aria-pressed" is not supported on role "${role}".`) } } @@ -75,7 +99,10 @@ 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 ( + allRoles.get(role as ARIARoleDefinitionKey)?.props['aria-current'] === + undefined + ) { throw new Error(`"aria-current" is not supported on role "${role}".`) } } @@ -89,22 +116,25 @@ function queryAllByRole( if (expanded !== undefined) { // guard against unknown roles - if (allRoles.get(role)?.props['aria-expanded'] === undefined) { + if ( + allRoles.get(role as ARIARoleDefinitionKey)?.props['aria-expanded'] === + undefined + ) { throw new Error(`"aria-expanded" is not supported on role "${role}".`) } } - const subtreeIsInaccessibleCache = new WeakMap() - function cachedIsSubtreeInaccessible(element) { + const subtreeIsInaccessibleCache = new WeakMap() + function cachedIsSubtreeInaccessible(element: Element) { if (!subtreeIsInaccessibleCache.has(element)) { subtreeIsInaccessibleCache.set(element, isSubtreeInaccessible(element)) } - return subtreeIsInaccessibleCache.get(element) + return subtreeIsInaccessibleCache.get(element) as boolean } 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,26 +143,26 @@ function queryAllByRole( const isRoleSpecifiedExplicitly = node.hasAttribute('role') if (isRoleSpecifiedExplicitly) { - const roleValue = node.getAttribute('role') + const roleValue = node.getAttribute('role') as string 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) + const implicitRoles = getImplicitAriaRoles(node) as string[] return implicitRoles.some(implicitRole => - matcher(implicitRole, node, role, matchNormalizer), + matcher(implicitRole, node, role as Matcher, matchNormalizer), ) }) .filter(element => { @@ -169,7 +199,7 @@ function queryAllByRole( getConfig().computedStyleSupportsPseudoElements, }), element, - name, + name as MatcherFunction, text => text, ) }) @@ -185,7 +215,7 @@ function queryAllByRole( getConfig().computedStyleSupportsPseudoElements, }), element, - description, + description as Matcher, text => text, ) }) @@ -198,7 +228,11 @@ 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 '*' @@ -207,7 +241,8 @@ function makeRoleSelector(role, exact, customNormalizer) { 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), ) @@ -220,7 +255,7 @@ function makeRoleSelector(role, exact, customNormalizer) { .join(',') } -const getNameHint = name => { +const getNameHint = (name: ByRoleOptions['name']): string => { let nameHint = '' if (name === undefined) { nameHint = '' @@ -233,11 +268,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 +286,7 @@ const getMissingError = ( } let roles = '' - Array.from(container.children).forEach(childElement => { + Array.from((container as Element).children).forEach(childElement => { roles += prettyRoles(childElement, { hidden, includeDescription: description !== undefined, @@ -297,11 +336,10 @@ Unable to find an ${ ${roleMessage}`.trim() } -const queryAllByRoleWithSuggestions = wrapAllByQueryWithSuggestion( - queryAllByRole, - queryAllByRole.name, - 'queryAll', -) +const queryAllByRoleWithSuggestions = wrapAllByQueryWithSuggestion< + // @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/src/query-helpers.ts b/src/query-helpers.ts index 8de75a23..44328e69 100644 --- a/src/query-helpers.ts +++ b/src/query-helpers.ts @@ -117,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, ) => { @@ -209,16 +209,16 @@ 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( +function buildQueries( queryAllBy: QueryMethod< - [matcher: Matcher, options: MatcherOptions], + [matcher: QueryMatcher, options: MatcherOptions], HTMLElement[] >, getMultipleError: GetErrorFunction< - [matcher: Matcher, options: MatcherOptions] + [matcher: QueryMatcher, options: MatcherOptions] >, getMissingError: GetErrorFunction< - [matcher: Matcher, options: MatcherOptions] + [matcher: QueryMatcher, options: MatcherOptions] >, ) { const queryBy = wrapSingleQueryWithSuggestion( diff --git a/types/__tests__/type-tests.ts b/types/__tests__/type-tests.ts index 7a3212cf..b0240e6a 100644 --- a/types/__tests__/type-tests.ts +++ b/types/__tests__/type-tests.ts @@ -182,7 +182,6 @@ export async function testByRole() { }) === 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)