Skip to content

Commit 0118c45

Browse files
feat: upgrade html-aria package (#121)
1 parent 3fc1af0 commit 0118c45

File tree

7 files changed

+497
-568
lines changed

7 files changed

+497
-568
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ The current status of the WPT coverage is:
5757

5858
| Passing | Failing | Skipped |
5959
| :-----: | :-----: | :-----: |
60-
| 410 | 117 | 338 |
60+
| 410 | 107 | 338 |
6161

6262
The included tests, skipped tests, and expected failures can be found in the [WPT configuration file](./test/wpt-jsdom/to-run.yaml) with reasons as to skips and expected failures.
6363

package.json

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -67,26 +67,26 @@
6767
"@testing-library/user-event": "^14.6.1",
6868
"aria-query": "^5.3.2",
6969
"dom-accessibility-api": "^0.7.0",
70-
"html-aria": "^0.1.9"
70+
"html-aria": "^0.2.0"
7171
},
7272
"devDependencies": {
73-
"@arethetypeswrong/cli": "^0.17.3",
73+
"@arethetypeswrong/cli": "^0.17.4",
7474
"@testing-library/jest-dom": "^6.6.3",
7575
"@testing-library/react": "^16.2.0",
7676
"@types/expect": "^24.3.2",
7777
"@types/jest": "^29.5.14",
7878
"@types/json-schema": "^7.0.15",
7979
"@types/mocha": "^10.0.10",
80-
"@types/node": "^22.10.10",
80+
"@types/node": "^22.13.10",
8181
"@types/prop-types": "^15.7.14",
82-
"@types/react": "^19.0.8",
83-
"@types/react-dom": "^19.0.3",
82+
"@types/react": "^19.0.10",
83+
"@types/react-dom": "^19.0.4",
8484
"@types/scheduler": "^0.23.0",
8585
"@types/semver": "^7.5.8",
8686
"chalk": "^4.0.0",
8787
"css.escape": "^1.5.1",
88-
"eslint": "^9.19.0",
89-
"eslint-config-prettier": "^10.0.1",
88+
"eslint": "^9.22.0",
89+
"eslint-config-prettier": "^10.1.1",
9090
"globals": "^15.14.0",
9191
"jest": "^29.7.0",
9292
"jest-environment-jsdom": "^29.7.0",
@@ -98,12 +98,12 @@
9898
"react": "^19.0.0",
9999
"react-dom": "^19.0.0",
100100
"rimraf": "^6.0.1",
101-
"ts-jest": "^29.2.3",
101+
"ts-jest": "^29.2.6",
102102
"ts-jest-resolver": "^2.0.1",
103103
"ts-mocha": "^10.0.0",
104104
"ts-node": "^10.9.2",
105-
"tsup": "^8.3.6",
106-
"typescript": "^5.7.3",
107-
"typescript-eslint": "^8.22.0"
105+
"tsup": "^8.4.0",
106+
"typescript": "^5.8.2",
107+
"typescript-eslint": "^8.26.1"
108108
}
109109
}

src/getNodeAccessibilityData/getRole.ts

Lines changed: 4 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
1-
import {
2-
type AncestorList,
3-
getRole as getImplicitRole,
4-
roles,
5-
type TagName,
6-
type VirtualElement,
7-
} from "html-aria";
1+
import { getRole as getHtmlAriaRole, roles } from "html-aria";
82
import { roles as backupRoles } from "aria-query";
93
import { getLocalName } from "../getLocalName";
104
import { isElement } from "../isElement";
@@ -181,74 +175,6 @@ function getExplicitRole({
181175
return filteredRoles?.[0] ?? "";
182176
}
183177

184-
// TODO: upstream update to `html-aria` to support supplying a jsdom element in
185-
// a Node environment. Appears their check for `element instanceof HTMLElement`
186-
// fails the `test/int/nodeEnvironment.int.test.ts` suite.
187-
function virtualizeElement(element: HTMLElement): VirtualElement {
188-
const tagName = getLocalName(element) as TagName;
189-
const attributes: Record<string, string | null> = {};
190-
191-
for (let i = 0; i < element.attributes.length; i++) {
192-
const { name } = element.attributes[i]!;
193-
194-
attributes[name] = element.getAttribute(name);
195-
}
196-
197-
return { tagName, attributes };
198-
}
199-
200-
const rolesDependentOnHierarchy = new Set([
201-
"aside",
202-
"footer",
203-
"header",
204-
"li",
205-
"td",
206-
"th",
207-
"tr",
208-
]);
209-
const ignoredAncestors = new Set(["body", "document"]);
210-
211-
// TODO: Thought needed if the `getAncestors()` can limit the number of parents
212-
// it enumerates? Presumably as ancestors only matter for a limited number of
213-
// roles, there might be a ceiling to the amount of nesting that is even valid,
214-
// and therefore put an upper bound on how far to backtrack without having to
215-
// stop at the document level for every single element.
216-
//
217-
// Another thought is that we special case each element so the backtracking can
218-
// exit early if an ancestor with a relevant role has already been found.
219-
//
220-
// Alternatively see if providing an element that is part of a DOM can be
221-
// traversed by the `html-aria` library itself so these concerns are
222-
// centralised.
223-
function getAncestors(node: HTMLElement): AncestorList | undefined {
224-
if (!rolesDependentOnHierarchy.has(getLocalName(node))) {
225-
return undefined;
226-
}
227-
228-
const ancestors: AncestorList = [];
229-
230-
let target: HTMLElement | null = node;
231-
let targetLocalName: string;
232-
233-
while (true) {
234-
target = target.parentElement;
235-
236-
if (!target) {
237-
break;
238-
}
239-
240-
targetLocalName = getLocalName(target);
241-
242-
if (ignoredAncestors.has(targetLocalName)) {
243-
break;
244-
}
245-
246-
ancestors.push({ tagName: targetLocalName as TagName });
247-
}
248-
249-
return ancestors;
250-
}
251-
252178
export function getRole({
253179
accessibleName,
254180
allowedAccessibilityRoles,
@@ -264,12 +190,11 @@ export function getRole({
264190
return { explicitRole: "", implicitRole: "", role: "" };
265191
}
266192

267-
const target = node.cloneNode() as HTMLElement;
268193
const baseExplicitRole = getExplicitRole({
269194
accessibleName,
270195
allowedAccessibilityRoles,
271196
inheritedImplicitPresentational,
272-
node: target,
197+
node,
273198
});
274199
const explicitRole = mapAliasedRoles(baseExplicitRole);
275200

@@ -283,16 +208,12 @@ export function getRole({
283208
return { explicitRole, implicitRole: role, role };
284209
}
285210

286-
target.removeAttribute("role");
287-
288211
// Backwards compatibility
289-
const isBodyElement = getLocalName(target) === "body";
212+
const isBodyElement = getLocalName(node) === "body";
290213

291214
const baseImplicitRole = isBodyElement
292215
? "document"
293-
: getImplicitRole(virtualizeElement(target), {
294-
ancestors: getAncestors(node),
295-
}) ?? "";
216+
: getHtmlAriaRole(node, { ignoreRoleAttribute: true })?.name ?? "";
296217

297218
const implicitRole = mapAliasedRoles(baseImplicitRole);
298219

src/getNodeAccessibilityData/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ import { getAccessibleName } from "./getAccessibleName";
55
import { getAccessibleValue } from "./getAccessibleValue";
66
import { isElement } from "../isElement";
77

8-
// TODO: swap out with the html-aria package if this property becomes supported.
8+
// TODO: swap out with the html-aria package once it supports `dpub-aam` /
9+
// `dpub-aria` specifications.
910
const childrenPresentationalRoles = new Set([
1011
...(roles
1112
.entries()

test/wpt-jsdom/run-single-wpt.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ const { resolveReason } = require("./utils.js");
99
const {
1010
createAccessibilityTree,
1111
} = require("../../src/createAccessibilityTree");
12+
const {
13+
END_OF_ROLE_PREFIX,
14+
END_OF_NO_ROLE_PREFIX,
15+
} = require("../../src/flattenTree");
1216
const { observeDOM } = require("../../src/observeDOM");
1317
const {
1418
getAccessibleAttributeLabels,
@@ -138,6 +142,7 @@ function createJSDOM(urlPrefix, testPath, expectFail) {
138142
storageQuota: 100000, // Filling the default quota takes about a minute between two WPTs
139143
}).then((dom) => {
140144
const { window } = dom;
145+
globalThis.Element = window.Element;
141146

142147
return new Promise((resolve, reject) => {
143148
const errors = [];
@@ -177,7 +182,9 @@ function createJSDOM(urlPrefix, testPath, expectFail) {
177182
),
178183
{
179184
...treeNodeWithAttributeLabels,
180-
spokenRole: `end of ${treeNodeWithAttributeLabels.spokenRole}`,
185+
spokenRole: treeNodeWithAttributeLabels.spokenRole
186+
? `${END_OF_ROLE_PREFIX} ${treeNodeWithAttributeLabels.spokenRole}`
187+
: END_OF_NO_ROLE_PREFIX,
181188
},
182189
];
183190
};

test/wpt-jsdom/to-run.yaml

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -369,21 +369,9 @@ roles-dynamic-switch.tentative.window.html:
369369
[fail, upstream issue with html-aria package]
370370
"Connected <input type=checkbox switch>: adding type attribute":
371371
[fail, upstream issue with html-aria package]
372-
roles.html:
373-
"el-dd":
374-
[
375-
fail,
376-
"specification issue with mismatches between html-aria (REF: https://www.w3.org/TR/html-aria/#el-dd), html-aam (REF: https://www.w3.org/TR/html-aam-1.0/#el-dd), and wpt (REF: https://github.com/web-platform-tests/wpt/blob/master/html-aam/roles.html#L49). See also https://github.com/w3c/aria/issues/1662.",
377-
]
378-
"el-dt":
379-
[
380-
fail,
381-
"specification issue with mismatches between html-aria (REF: https://www.w3.org/TR/html-aria/#el-dt), html-aam (REF: https://www.w3.org/TR/html-aam-1.0/#el-dt), and wpt (REF: https://github.com/web-platform-tests/wpt/blob/master/html-aam/roles.html#L62). See also https://github.com/w3c/aria/issues/1662.",
382-
]
383372
roles.tentative.html:
384373
"el-input-checkbox-switch": [fail, upstream issue with html-aria package]
385374
table-roles.html:
386-
"el-th": [fail, upstream issue with html-aria package]
387375
"el-th-in-row": [fail, upstream issue with html-aria package]
388376

389377
---
@@ -398,10 +386,6 @@ role/role-img.tentative.html:
398386
[fail, upstream issue with html-aria package]
399387
"Role: el-image (label from image>title element)":
400388
[fail, upstream issue with html-aria package]
401-
role/roles.html:
402-
"el-a[xlink:href]": [fail, upstream issue with html-aria package]
403-
"el-g": [fail, upstream issue with html-aria package]
404-
"el-image": [fail, upstream issue with html-aria package]
405389

406390
---
407391
DIR: wai-aria
@@ -412,11 +396,6 @@ manual/*:
412396
timeout,
413397
targets accessibility APIs whose mappings have not been implemented in this test suite yet,
414398
]
415-
role/contextual-roles.tentative.html:
416-
"role is sectionfooter (in main)":
417-
[fail, upstream issue with html-aria package]
418-
"role is sectionheader (in main)":
419-
[fail, upstream issue with html-aria package]
420399
role/grid-roles.tentative.html:
421400
"orphaned button with gridcell role outside the context of row":
422401
[fail, upstream issue with html-aria package]
@@ -454,9 +433,6 @@ role/menu-roles.tentative.html:
454433
[fail, upstream issue with html-aria package]
455434
"orphan button with menuitemradio role":
456435
[fail, upstream issue with html-aria package]
457-
role/roles.tentative.html:
458-
"role: sectionheader": [fail, upstream issue with html-aria package]
459-
"role: sectionfooter": [fail, upstream issue with html-aria package]
460436
role/tab-roles.tentative.html:
461437
"orphan button with tab role": [fail, upstream issue with html-aria package]
462438
"orphan span with tab role": [fail, upstream issue with html-aria package]

0 commit comments

Comments
 (0)