Skip to content

Commit 42809fe

Browse files
DaniAcueps1lon
andauthored
chore: Migrate ByRole to TypeScript (#1186)
* fix: migrate role to ts * fix: remove role model abstraction * fix: prettier * fix: force have role that are included in type * fix: type * fix: error and add commet ts * fix: any type * fix: any type * fix: type assertion * fix: ignore branch coverage * fix: typo * fix: add hints in all by role * fix: types and comments * fix: restore types * fix: restore types * fix: lint * fix: ts error * fix: ts error * fix: type tests * Fix lint * Don't couple query-helpers with ByRole * Revert change to ByRoleMatcher type --------- Co-authored-by: eps1lon <[email protected]>
1 parent 25dc8a9 commit 42809fe

File tree

4 files changed

+77
-39
lines changed

4 files changed

+77
-39
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
"@typescript-eslint/prefer-optional-chain": "off",
9191
"@typescript-eslint/no-explicit-any": "off",
9292
"@typescript-eslint/no-unsafe-member-access": "off",
93+
"@typescript-eslint/no-unnecessary-boolean-literal-compare": "off",
9394
"@typescript-eslint/prefer-includes": "off",
9495
"import/prefer-default-export": "off",
9596
"import/no-unassigned-import": "off",

src/queries/role.js renamed to src/queries/role.ts

+69-31
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import {
22
computeAccessibleDescription,
33
computeAccessibleName,
44
} from 'dom-accessibility-api'
5-
import {roles as allRoles, roleElements} from 'aria-query'
5+
import {
6+
roles as allRoles,
7+
roleElements,
8+
ARIARoleDefinitionKey,
9+
} from 'aria-query'
610
import {
711
computeAriaSelected,
812
computeAriaChecked,
@@ -17,6 +21,17 @@ import {
1721
} from '../role-helpers'
1822
import {wrapAllByQueryWithSuggestion} from '../query-helpers'
1923
import {checkContainerType} from '../helpers'
24+
import {
25+
AllByRole,
26+
ByRoleMatcher,
27+
ByRoleOptions,
28+
GetErrorFunction,
29+
Matcher,
30+
MatcherFunction,
31+
MatcherOptions,
32+
NormalizerFn,
33+
} from '../../types'
34+
2035
import {
2136
buildQueries,
2237
fuzzyMatches,
@@ -25,7 +40,7 @@ import {
2540
matches,
2641
} from './all-utils'
2742

28-
function queryAllByRole(
43+
const queryAllByRole: AllByRole = (
2944
container,
3045
role,
3146
{
@@ -44,28 +59,37 @@ function queryAllByRole(
4459
level,
4560
expanded,
4661
} = {},
47-
) {
62+
) => {
4863
checkContainerType(container)
4964
const matcher = exact ? matches : fuzzyMatches
5065
const matchNormalizer = makeNormalizer({collapseWhitespace, trim, normalizer})
5166

5267
if (selected !== undefined) {
5368
// guard against unknown roles
54-
if (allRoles.get(role)?.props['aria-selected'] === undefined) {
69+
if (
70+
allRoles.get(role as ARIARoleDefinitionKey)?.props['aria-selected'] ===
71+
undefined
72+
) {
5573
throw new Error(`"aria-selected" is not supported on role "${role}".`)
5674
}
5775
}
5876

5977
if (checked !== undefined) {
6078
// guard against unknown roles
61-
if (allRoles.get(role)?.props['aria-checked'] === undefined) {
79+
if (
80+
allRoles.get(role as ARIARoleDefinitionKey)?.props['aria-checked'] ===
81+
undefined
82+
) {
6283
throw new Error(`"aria-checked" is not supported on role "${role}".`)
6384
}
6485
}
6586

6687
if (pressed !== undefined) {
6788
// guard against unknown roles
68-
if (allRoles.get(role)?.props['aria-pressed'] === undefined) {
89+
if (
90+
allRoles.get(role as ARIARoleDefinitionKey)?.props['aria-pressed'] ===
91+
undefined
92+
) {
6993
throw new Error(`"aria-pressed" is not supported on role "${role}".`)
7094
}
7195
}
@@ -75,7 +99,10 @@ function queryAllByRole(
7599
// guard against unknown roles
76100
// All currently released ARIA versions support `aria-current` on all roles.
77101
// Leaving this for symetry and forward compatibility
78-
if (allRoles.get(role)?.props['aria-current'] === undefined) {
102+
if (
103+
allRoles.get(role as ARIARoleDefinitionKey)?.props['aria-current'] ===
104+
undefined
105+
) {
79106
throw new Error(`"aria-current" is not supported on role "${role}".`)
80107
}
81108
}
@@ -89,22 +116,25 @@ function queryAllByRole(
89116

90117
if (expanded !== undefined) {
91118
// guard against unknown roles
92-
if (allRoles.get(role)?.props['aria-expanded'] === undefined) {
119+
if (
120+
allRoles.get(role as ARIARoleDefinitionKey)?.props['aria-expanded'] ===
121+
undefined
122+
) {
93123
throw new Error(`"aria-expanded" is not supported on role "${role}".`)
94124
}
95125
}
96126

97-
const subtreeIsInaccessibleCache = new WeakMap()
98-
function cachedIsSubtreeInaccessible(element) {
127+
const subtreeIsInaccessibleCache = new WeakMap<Element, Boolean>()
128+
function cachedIsSubtreeInaccessible(element: Element) {
99129
if (!subtreeIsInaccessibleCache.has(element)) {
100130
subtreeIsInaccessibleCache.set(element, isSubtreeInaccessible(element))
101131
}
102132

103-
return subtreeIsInaccessibleCache.get(element)
133+
return subtreeIsInaccessibleCache.get(element) as boolean
104134
}
105135

106136
return Array.from(
107-
container.querySelectorAll(
137+
container.querySelectorAll<HTMLElement>(
108138
// Only query elements that can be matched by the following filters
109139
makeRoleSelector(role, exact, normalizer ? matchNormalizer : undefined),
110140
),
@@ -113,26 +143,26 @@ function queryAllByRole(
113143
const isRoleSpecifiedExplicitly = node.hasAttribute('role')
114144

115145
if (isRoleSpecifiedExplicitly) {
116-
const roleValue = node.getAttribute('role')
146+
const roleValue = node.getAttribute('role') as string
117147
if (queryFallbacks) {
118148
return roleValue
119149
.split(' ')
120150
.filter(Boolean)
121-
.some(text => matcher(text, node, role, matchNormalizer))
151+
.some(text => matcher(text, node, role as Matcher, matchNormalizer))
122152
}
123153
// if a custom normalizer is passed then let normalizer handle the role value
124154
if (normalizer) {
125-
return matcher(roleValue, node, role, matchNormalizer)
155+
return matcher(roleValue, node, role as Matcher, matchNormalizer)
126156
}
127157
// other wise only send the first word to match
128158
const [firstWord] = roleValue.split(' ')
129-
return matcher(firstWord, node, role, matchNormalizer)
159+
return matcher(firstWord, node, role as Matcher, matchNormalizer)
130160
}
131161

132-
const implicitRoles = getImplicitAriaRoles(node)
162+
const implicitRoles = getImplicitAriaRoles(node) as string[]
133163

134164
return implicitRoles.some(implicitRole =>
135-
matcher(implicitRole, node, role, matchNormalizer),
165+
matcher(implicitRole, node, role as Matcher, matchNormalizer),
136166
)
137167
})
138168
.filter(element => {
@@ -169,7 +199,7 @@ function queryAllByRole(
169199
getConfig().computedStyleSupportsPseudoElements,
170200
}),
171201
element,
172-
name,
202+
name as MatcherFunction,
173203
text => text,
174204
)
175205
})
@@ -185,7 +215,7 @@ function queryAllByRole(
185215
getConfig().computedStyleSupportsPseudoElements,
186216
}),
187217
element,
188-
description,
218+
description as Matcher,
189219
text => text,
190220
)
191221
})
@@ -198,7 +228,11 @@ function queryAllByRole(
198228
})
199229
}
200230

201-
function makeRoleSelector(role, exact, customNormalizer) {
231+
function makeRoleSelector(
232+
role: ByRoleMatcher,
233+
exact: boolean,
234+
customNormalizer?: NormalizerFn,
235+
) {
202236
if (typeof role !== 'string') {
203237
// For non-string role parameters we can not determine the implicitRoleSelectors.
204238
return '*'
@@ -207,7 +241,8 @@ function makeRoleSelector(role, exact, customNormalizer) {
207241
const explicitRoleSelector =
208242
exact && !customNormalizer ? `*[role~="${role}"]` : '*[role]'
209243

210-
const roleRelations = roleElements.get(role) ?? new Set()
244+
const roleRelations =
245+
roleElements.get(role as ARIARoleDefinitionKey) ?? new Set()
211246
const implicitRoleSelectors = new Set(
212247
Array.from(roleRelations).map(({name}) => name),
213248
)
@@ -220,7 +255,7 @@ function makeRoleSelector(role, exact, customNormalizer) {
220255
.join(',')
221256
}
222257

223-
const getNameHint = name => {
258+
const getNameHint = (name: ByRoleOptions['name']): string => {
224259
let nameHint = ''
225260
if (name === undefined) {
226261
nameHint = ''
@@ -233,11 +268,15 @@ const getNameHint = name => {
233268
return nameHint
234269
}
235270

236-
const getMultipleError = (c, role, {name} = {}) => {
271+
const getMultipleError: GetErrorFunction<
272+
[matcher: ByRoleMatcher, options: ByRoleOptions]
273+
> = (c, role, {name} = {}) => {
237274
return `Found multiple elements with the role "${role}"${getNameHint(name)}`
238275
}
239276

240-
const getMissingError = (
277+
const getMissingError: GetErrorFunction<
278+
[matcher: ByRoleMatcher, options: ByRoleOptions]
279+
> = (
241280
container,
242281
role,
243282
{hidden = getConfig().defaultHidden, name, description} = {},
@@ -247,7 +286,7 @@ const getMissingError = (
247286
}
248287

249288
let roles = ''
250-
Array.from(container.children).forEach(childElement => {
289+
Array.from((container as Element).children).forEach(childElement => {
251290
roles += prettyRoles(childElement, {
252291
hidden,
253292
includeDescription: description !== undefined,
@@ -297,11 +336,10 @@ Unable to find an ${
297336
298337
${roleMessage}`.trim()
299338
}
300-
const queryAllByRoleWithSuggestions = wrapAllByQueryWithSuggestion(
301-
queryAllByRole,
302-
queryAllByRole.name,
303-
'queryAll',
304-
)
339+
const queryAllByRoleWithSuggestions = wrapAllByQueryWithSuggestion<
340+
// @ts-expect-error -- See `wrapAllByQueryWithSuggestion` Argument constraint comment
341+
[labelText: Matcher, options?: MatcherOptions]
342+
>(queryAllByRole, queryAllByRole.name, 'queryAll')
305343
const [queryByRole, getAllByRole, getByRole, findAllByRole, findByRole] =
306344
buildQueries(queryAllByRole, getMultipleError, getMissingError)
307345

src/query-helpers.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -117,16 +117,16 @@ function makeGetAllQuery<Arguments extends unknown[]>(
117117

118118
// this accepts a getter query function and returns a function which calls
119119
// waitFor and passing a function which invokes the getter.
120-
function makeFindQuery<QueryFor>(
120+
function makeFindQuery<QueryFor, QueryMatcher>(
121121
getter: (
122122
container: HTMLElement,
123-
text: Matcher,
123+
text: QueryMatcher,
124124
options: MatcherOptions,
125125
) => QueryFor,
126126
) {
127127
return (
128128
container: HTMLElement,
129-
text: Matcher,
129+
text: QueryMatcher,
130130
options: MatcherOptions,
131131
waitForOptions: WaitForOptions,
132132
) => {
@@ -209,16 +209,16 @@ const wrapAllByQueryWithSuggestion =
209209
// TODO: This deviates from the published declarations
210210
// However, the implementation always required a dyadic (after `container`) not variadic `queryAllBy` considering the implementation of `makeFindQuery`
211211
// This is at least statically true and can be verified by accepting `QueryMethod<Arguments, HTMLElement[]>`
212-
function buildQueries(
212+
function buildQueries<QueryMatcher>(
213213
queryAllBy: QueryMethod<
214-
[matcher: Matcher, options: MatcherOptions],
214+
[matcher: QueryMatcher, options: MatcherOptions],
215215
HTMLElement[]
216216
>,
217217
getMultipleError: GetErrorFunction<
218-
[matcher: Matcher, options: MatcherOptions]
218+
[matcher: QueryMatcher, options: MatcherOptions]
219219
>,
220220
getMissingError: GetErrorFunction<
221-
[matcher: Matcher, options: MatcherOptions]
221+
[matcher: QueryMatcher, options: MatcherOptions]
222222
>,
223223
) {
224224
const queryBy = wrapSingleQueryWithSuggestion(

types/__tests__/type-tests.ts

-1
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,6 @@ export async function testByRole() {
182182
}) === null,
183183
)
184184

185-
// allow to query for a role that isn't included in the types
186185
console.assert(queryByRole(element, 'foo') === null)
187186
console.assert(queryByRole(element, /foo/) === null)
188187
console.assert(screen.queryByRole('foo') === null)

0 commit comments

Comments
 (0)