accented 0.0.1-dev.4 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +14 -0
- package/README.md +71 -0
- package/dist/accented.d.ts +28 -7
- package/dist/accented.d.ts.map +1 -1
- package/dist/accented.js +107 -42
- package/dist/accented.js.map +1 -1
- package/dist/common/tokens.d.ts +7 -0
- package/dist/common/tokens.d.ts.map +1 -0
- package/dist/common/tokens.js +8 -0
- package/dist/common/tokens.js.map +1 -0
- package/dist/constants.d.ts +4 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +4 -0
- package/dist/constants.js.map +1 -0
- package/dist/dom-updater.d.ts +1 -6
- package/dist/dom-updater.d.ts.map +1 -1
- package/dist/dom-updater.js +136 -20
- package/dist/dom-updater.js.map +1 -1
- package/dist/elements/accented-dialog.d.ts +359 -0
- package/dist/elements/accented-dialog.d.ts.map +1 -0
- package/dist/elements/accented-dialog.js +377 -0
- package/dist/elements/accented-dialog.js.map +1 -0
- package/dist/elements/accented-trigger.d.ts +364 -0
- package/dist/elements/accented-trigger.d.ts.map +1 -0
- package/dist/elements/accented-trigger.js +214 -0
- package/dist/elements/accented-trigger.js.map +1 -0
- package/dist/fullscreen-listener.d.ts +2 -0
- package/dist/fullscreen-listener.d.ts.map +1 -0
- package/dist/fullscreen-listener.js +17 -0
- package/dist/fullscreen-listener.js.map +1 -0
- package/dist/intersection-observer.d.ts +5 -0
- package/dist/intersection-observer.d.ts.map +1 -0
- package/dist/intersection-observer.js +34 -0
- package/dist/intersection-observer.js.map +1 -0
- package/dist/log-and-rethrow.d.ts +2 -0
- package/dist/log-and-rethrow.d.ts.map +1 -0
- package/dist/log-and-rethrow.js +6 -0
- package/dist/log-and-rethrow.js.map +1 -0
- package/dist/logger.d.ts +2 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +28 -0
- package/dist/logger.js.map +1 -0
- package/dist/register-elements.d.ts +2 -0
- package/dist/register-elements.d.ts.map +1 -0
- package/dist/register-elements.js +20 -0
- package/dist/register-elements.js.map +1 -0
- package/dist/resize-listener.d.ts +2 -0
- package/dist/resize-listener.d.ts.map +1 -0
- package/dist/resize-listener.js +17 -0
- package/dist/resize-listener.js.map +1 -0
- package/dist/scanner.d.ts +3 -0
- package/dist/scanner.d.ts.map +1 -0
- package/dist/scanner.js +153 -0
- package/dist/scanner.js.map +1 -0
- package/dist/scroll-listeners.d.ts +2 -0
- package/dist/scroll-listeners.d.ts.map +1 -0
- package/dist/scroll-listeners.js +37 -0
- package/dist/scroll-listeners.js.map +1 -0
- package/dist/state.d.ts +7 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +16 -0
- package/dist/state.js.map +1 -0
- package/dist/task-queue.d.ts +5 -6
- package/dist/task-queue.d.ts.map +1 -1
- package/dist/task-queue.js +30 -25
- package/dist/task-queue.js.map +1 -1
- package/dist/types.d.ts +227 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/are-elements-with-issues-equal.d.ts +3 -0
- package/dist/utils/are-elements-with-issues-equal.d.ts.map +1 -0
- package/dist/utils/are-elements-with-issues-equal.js +5 -0
- package/dist/utils/are-elements-with-issues-equal.js.map +1 -0
- package/dist/utils/are-issue-sets-equal.d.ts +3 -0
- package/dist/utils/are-issue-sets-equal.d.ts.map +1 -0
- package/dist/utils/are-issue-sets-equal.js +6 -0
- package/dist/utils/are-issue-sets-equal.js.map +1 -0
- package/dist/utils/containing-blocks.d.ts +3 -0
- package/dist/utils/containing-blocks.d.ts.map +1 -0
- package/dist/utils/containing-blocks.js +46 -0
- package/dist/utils/containing-blocks.js.map +1 -0
- package/dist/utils/contains.d.ts +2 -0
- package/dist/utils/contains.d.ts.map +1 -0
- package/dist/utils/contains.js +19 -0
- package/dist/utils/contains.js.map +1 -0
- package/dist/utils/deduplicate-nodes.d.ts +2 -0
- package/dist/utils/deduplicate-nodes.d.ts.map +1 -0
- package/dist/utils/deduplicate-nodes.js +4 -0
- package/dist/utils/deduplicate-nodes.js.map +1 -0
- package/dist/utils/deep-merge.d.ts +4 -0
- package/dist/utils/deep-merge.d.ts.map +1 -0
- package/dist/utils/deep-merge.js +21 -0
- package/dist/utils/deep-merge.js.map +1 -0
- package/dist/utils/dom-helpers.d.ts +9 -0
- package/dist/utils/dom-helpers.d.ts.map +1 -0
- package/dist/utils/dom-helpers.js +34 -0
- package/dist/utils/dom-helpers.js.map +1 -0
- package/dist/utils/ensure-non-empty.d.ts +2 -0
- package/dist/utils/ensure-non-empty.d.ts.map +1 -0
- package/dist/utils/ensure-non-empty.js +7 -0
- package/dist/utils/ensure-non-empty.js.map +1 -0
- package/dist/utils/get-element-html.d.ts +2 -0
- package/dist/utils/get-element-html.d.ts.map +1 -0
- package/dist/utils/get-element-html.js +16 -0
- package/dist/utils/get-element-html.js.map +1 -0
- package/dist/utils/get-element-position.d.ts +11 -0
- package/dist/utils/get-element-position.d.ts.map +1 -0
- package/dist/utils/get-element-position.js +70 -0
- package/dist/utils/get-element-position.js.map +1 -0
- package/dist/utils/get-parent.d.ts +2 -0
- package/dist/utils/get-parent.d.ts.map +1 -0
- package/dist/utils/get-parent.js +12 -0
- package/dist/utils/get-parent.js.map +1 -0
- package/dist/utils/get-scan-context.d.ts +3 -0
- package/dist/utils/get-scan-context.d.ts.map +1 -0
- package/dist/utils/get-scan-context.js +28 -0
- package/dist/utils/get-scan-context.js.map +1 -0
- package/dist/utils/get-scrollable-ancestors.d.ts +2 -0
- package/dist/utils/get-scrollable-ancestors.d.ts.map +1 -0
- package/dist/utils/get-scrollable-ancestors.js +19 -0
- package/dist/utils/get-scrollable-ancestors.js.map +1 -0
- package/dist/utils/is-node-in-scan-context.d.ts +3 -0
- package/dist/utils/is-node-in-scan-context.d.ts.map +1 -0
- package/dist/utils/is-node-in-scan-context.js +26 -0
- package/dist/utils/is-node-in-scan-context.js.map +1 -0
- package/dist/utils/is-non-empty.d.ts +2 -0
- package/dist/utils/is-non-empty.d.ts.map +1 -0
- package/dist/utils/is-non-empty.js +4 -0
- package/dist/utils/is-non-empty.js.map +1 -0
- package/dist/utils/normalize-context.d.ts +3 -0
- package/dist/utils/normalize-context.d.ts.map +1 -0
- package/dist/utils/normalize-context.js +59 -0
- package/dist/utils/normalize-context.js.map +1 -0
- package/dist/utils/recalculate-positions.d.ts +2 -0
- package/dist/utils/recalculate-positions.d.ts.map +1 -0
- package/dist/utils/recalculate-positions.js +27 -0
- package/dist/utils/recalculate-positions.js.map +1 -0
- package/dist/utils/recalculate-scrollable-ancestors.d.ts +2 -0
- package/dist/utils/recalculate-scrollable-ancestors.d.ts.map +1 -0
- package/dist/utils/recalculate-scrollable-ancestors.js +13 -0
- package/dist/utils/recalculate-scrollable-ancestors.js.map +1 -0
- package/dist/utils/shadow-dom-aware-mutation-observer.d.ts +10 -0
- package/dist/utils/shadow-dom-aware-mutation-observer.d.ts.map +1 -0
- package/dist/utils/shadow-dom-aware-mutation-observer.js +61 -0
- package/dist/utils/shadow-dom-aware-mutation-observer.js.map +1 -0
- package/dist/utils/supports-anchor-positioning.d.ts +6 -0
- package/dist/utils/supports-anchor-positioning.d.ts.map +1 -0
- package/dist/utils/supports-anchor-positioning.js +4 -0
- package/dist/utils/supports-anchor-positioning.js.map +1 -0
- package/dist/utils/transform-violations.d.ts +4 -0
- package/dist/utils/transform-violations.d.ts.map +1 -0
- package/dist/utils/transform-violations.js +61 -0
- package/dist/utils/transform-violations.js.map +1 -0
- package/dist/utils/update-elements-with-issues.d.ts +13 -0
- package/dist/utils/update-elements-with-issues.d.ts.map +1 -0
- package/dist/utils/update-elements-with-issues.js +96 -0
- package/dist/utils/update-elements-with-issues.js.map +1 -0
- package/dist/validate-options.d.ts +3 -0
- package/dist/validate-options.d.ts.map +1 -0
- package/dist/validate-options.js +129 -0
- package/dist/validate-options.js.map +1 -0
- package/package.json +21 -8
- package/src/accented.test.ts +24 -0
- package/src/accented.ts +130 -0
- package/src/common/tokens.ts +10 -0
- package/src/constants.ts +3 -0
- package/src/dom-updater.ts +165 -0
- package/src/elements/accented-dialog.ts +419 -0
- package/src/elements/accented-trigger.ts +251 -0
- package/src/fullscreen-listener.ts +21 -0
- package/src/intersection-observer.ts +39 -0
- package/src/log-and-rethrow.ts +8 -0
- package/src/logger.ts +36 -0
- package/src/register-elements.ts +21 -0
- package/src/resize-listener.ts +21 -0
- package/src/scanner.ts +195 -0
- package/src/scroll-listeners.ts +45 -0
- package/src/state.ts +35 -0
- package/src/task-queue.test.ts +136 -0
- package/src/task-queue.ts +61 -0
- package/src/types.ts +258 -0
- package/src/utils/are-elements-with-issues-equal.ts +11 -0
- package/src/utils/are-issue-sets-equal.test.ts +53 -0
- package/src/utils/are-issue-sets-equal.ts +12 -0
- package/src/utils/containing-blocks.ts +60 -0
- package/src/utils/contains.test.ts +54 -0
- package/src/utils/contains.ts +19 -0
- package/src/utils/deduplicate-nodes.ts +3 -0
- package/src/utils/deep-merge.test.ts +41 -0
- package/src/utils/deep-merge.ts +24 -0
- package/src/utils/dom-helpers.ts +42 -0
- package/src/utils/ensure-non-empty.ts +6 -0
- package/src/utils/get-element-html.ts +15 -0
- package/src/utils/get-element-position.ts +89 -0
- package/src/utils/get-parent.ts +14 -0
- package/src/utils/get-scan-context.test.ts +85 -0
- package/src/utils/get-scan-context.ts +36 -0
- package/src/utils/get-scrollable-ancestors.ts +22 -0
- package/src/utils/is-node-in-scan-context.test.ts +70 -0
- package/src/utils/is-node-in-scan-context.ts +29 -0
- package/src/utils/is-non-empty.ts +3 -0
- package/src/utils/normalize-context.test.ts +105 -0
- package/src/utils/normalize-context.ts +65 -0
- package/src/utils/recalculate-positions.ts +27 -0
- package/src/utils/recalculate-scrollable-ancestors.ts +13 -0
- package/src/utils/shadow-dom-aware-mutation-observer.ts +75 -0
- package/src/utils/supports-anchor-positioning.ts +7 -0
- package/src/utils/transform-violations.test.ts +128 -0
- package/src/utils/transform-violations.ts +74 -0
- package/src/utils/update-elements-with-issues.test.ts +371 -0
- package/src/utils/update-elements-with-issues.ts +144 -0
- package/src/validate-options.ts +184 -0
- package/dist/utils/issuesToElements.d.ts +0 -3
- package/dist/utils/issuesToElements.d.ts.map +0 -1
- package/dist/utils/issuesToElements.js +0 -16
- package/dist/utils/issuesToElements.js.map +0 -1
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { suite, test } from 'node:test';
|
|
3
|
+
import { transformViolations } from './transform-violations';
|
|
4
|
+
|
|
5
|
+
import type { AxeResults } from 'axe-core';
|
|
6
|
+
type Violation = AxeResults['violations'][number];
|
|
7
|
+
type Node = Violation['nodes'][number];
|
|
8
|
+
|
|
9
|
+
const commonViolationProps1: Omit<Violation, 'nodes'> = {
|
|
10
|
+
id: 'id1',
|
|
11
|
+
help: 'help1',
|
|
12
|
+
helpUrl: 'http://example.com',
|
|
13
|
+
description: 'description1',
|
|
14
|
+
tags: [],
|
|
15
|
+
impact: 'serious',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const commonViolationProps2: Omit<Violation, 'nodes'> = {
|
|
19
|
+
id: 'id2',
|
|
20
|
+
help: 'help2',
|
|
21
|
+
helpUrl: 'http://example.com',
|
|
22
|
+
description: 'description2',
|
|
23
|
+
tags: [],
|
|
24
|
+
impact: 'serious',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const getRootNode = (): Node => ({}) as Node;
|
|
28
|
+
|
|
29
|
+
// @ts-expect-error element is not HTMLElement
|
|
30
|
+
const element1: HTMLElement = { getRootNode };
|
|
31
|
+
// @ts-expect-error element is not HTMLElement
|
|
32
|
+
const element2: HTMLElement = { getRootNode };
|
|
33
|
+
// @ts-expect-error element is not HTMLElement
|
|
34
|
+
const element3: HTMLElement = { getRootNode };
|
|
35
|
+
|
|
36
|
+
const commonNodeProps = {
|
|
37
|
+
html: '<div></div>',
|
|
38
|
+
any: [],
|
|
39
|
+
all: [],
|
|
40
|
+
none: [],
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const node1: Node = {
|
|
44
|
+
...commonNodeProps,
|
|
45
|
+
element: element1,
|
|
46
|
+
target: ['div'],
|
|
47
|
+
failureSummary: 'summary1',
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const node2: Node = {
|
|
51
|
+
...commonNodeProps,
|
|
52
|
+
element: element2,
|
|
53
|
+
target: ['div'],
|
|
54
|
+
failureSummary: 'summary2',
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const node3: Node = {
|
|
58
|
+
...commonNodeProps,
|
|
59
|
+
element: element3,
|
|
60
|
+
target: ['div'],
|
|
61
|
+
failureSummary: 'summary3',
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
suite('transformViolations', () => {
|
|
65
|
+
test('one violation, one element', () => {
|
|
66
|
+
const violation: Violation = {
|
|
67
|
+
...commonViolationProps1,
|
|
68
|
+
nodes: [node1],
|
|
69
|
+
};
|
|
70
|
+
const elementsWithIssues = transformViolations([violation], 'accented');
|
|
71
|
+
assert.equal(elementsWithIssues.length, 1);
|
|
72
|
+
assert.equal(elementsWithIssues[0]?.element, element1);
|
|
73
|
+
assert.equal(elementsWithIssues[0].issues.length, 1);
|
|
74
|
+
assert.equal(elementsWithIssues[0].issues[0]?.id, 'id1');
|
|
75
|
+
assert.equal(elementsWithIssues[0].issues[0]?.description, 'summary1');
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('two violations, two elements each', () => {
|
|
79
|
+
const violation1: Violation = {
|
|
80
|
+
...commonViolationProps1,
|
|
81
|
+
nodes: [node1, node2],
|
|
82
|
+
};
|
|
83
|
+
const violation2: Violation = {
|
|
84
|
+
...commonViolationProps2,
|
|
85
|
+
nodes: [node1, node3],
|
|
86
|
+
};
|
|
87
|
+
const elementsWithIssues = transformViolations([violation1, violation2], 'accented');
|
|
88
|
+
assert.equal(elementsWithIssues.length, 3);
|
|
89
|
+
const elementWithTwoIssues = elementsWithIssues.find(
|
|
90
|
+
(elementWithIssues) => elementWithIssues.element === element1,
|
|
91
|
+
);
|
|
92
|
+
assert.equal(elementWithTwoIssues?.issues.length, 2);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test('a violation in an iframe', () => {
|
|
96
|
+
const node: Node = {
|
|
97
|
+
...commonNodeProps,
|
|
98
|
+
element: element1,
|
|
99
|
+
// A target array whose length is > 1 signifies an element in an iframe
|
|
100
|
+
target: ['iframe', 'div'],
|
|
101
|
+
failureSummary: 'summary1',
|
|
102
|
+
};
|
|
103
|
+
const violation: Violation = {
|
|
104
|
+
...commonViolationProps1,
|
|
105
|
+
nodes: [node],
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const elementsWithIssues = transformViolations([violation], 'accented');
|
|
109
|
+
assert.equal(elementsWithIssues.length, 0);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('a violation in shadow DOM', () => {
|
|
113
|
+
const node: Node = {
|
|
114
|
+
...commonNodeProps,
|
|
115
|
+
element: element1,
|
|
116
|
+
// A target that contains an array within the outer array signifies an element in shadow DOM
|
|
117
|
+
target: [['div', 'div']],
|
|
118
|
+
failureSummary: 'summary1',
|
|
119
|
+
};
|
|
120
|
+
const violation: Violation = {
|
|
121
|
+
...commonViolationProps1,
|
|
122
|
+
nodes: [node],
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
const elementsWithIssues = transformViolations([violation], 'accented');
|
|
126
|
+
assert.equal(elementsWithIssues.length, 1);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { AxeResults, ImpactValue } from 'axe-core';
|
|
2
|
+
import type { ElementWithIssues, Issue } from '../types.ts';
|
|
3
|
+
|
|
4
|
+
// This is a list of axe-core violations (their ids) that may be flagged by axe-core
|
|
5
|
+
// as false positives if an Accented trigger is a descendant of the element with the issue.
|
|
6
|
+
const violationsAffectedByAccentedTriggers = [
|
|
7
|
+
'aria-hidden-focus',
|
|
8
|
+
'aria-text',
|
|
9
|
+
'definition-list',
|
|
10
|
+
'label-content-name-mismatch',
|
|
11
|
+
'list',
|
|
12
|
+
'nested-interactive',
|
|
13
|
+
'scrollable-region-focusable', // The Accented trigger might make the content grow such that scrolling is required.
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
function maybeCausedByAccented(violationId: string, element: HTMLElement, name: string) {
|
|
17
|
+
return (
|
|
18
|
+
violationsAffectedByAccentedTriggers.includes(violationId) &&
|
|
19
|
+
Boolean(element.querySelector(`${name}-trigger`))
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function impactCompare(a: ImpactValue, b: ImpactValue) {
|
|
24
|
+
const impactOrder = [null, 'minor', 'moderate', 'serious', 'critical'];
|
|
25
|
+
return impactOrder.indexOf(a) - impactOrder.indexOf(b);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function transformViolations(violations: typeof AxeResults.violations, name: string) {
|
|
29
|
+
const elementsWithIssues: Array<ElementWithIssues> = [];
|
|
30
|
+
|
|
31
|
+
for (const violation of violations) {
|
|
32
|
+
for (const node of violation.nodes) {
|
|
33
|
+
const { element, target } = node;
|
|
34
|
+
|
|
35
|
+
// Although axe-core can perform iframe scanning, I haven't succeeded in it,
|
|
36
|
+
// and the docs suggest that the axe-core script should be explicitly included
|
|
37
|
+
// in each of the iframed documents anyway.
|
|
38
|
+
// It seems preferable to disallow iframe scanning and not report issues in elements within iframes
|
|
39
|
+
// in the case that such issues are for some reason reported by axe-core.
|
|
40
|
+
// A consumer of Accented can instead scan the iframed document by calling Accented initialization from that document.
|
|
41
|
+
const isInIframe = target.length > 1;
|
|
42
|
+
|
|
43
|
+
if (element && !isInIframe && !maybeCausedByAccented(violation.id, element, name)) {
|
|
44
|
+
const issue: Issue = {
|
|
45
|
+
id: violation.id,
|
|
46
|
+
title: violation.help,
|
|
47
|
+
description: node.failureSummary ?? violation.description,
|
|
48
|
+
url: violation.helpUrl,
|
|
49
|
+
impact: violation.impact ?? null,
|
|
50
|
+
};
|
|
51
|
+
const existingElement = elementsWithIssues.find(
|
|
52
|
+
(elementWithIssues) => elementWithIssues.element === element,
|
|
53
|
+
);
|
|
54
|
+
if (existingElement === undefined) {
|
|
55
|
+
elementsWithIssues.push({
|
|
56
|
+
element,
|
|
57
|
+
rootNode: element.getRootNode(),
|
|
58
|
+
issues: [issue],
|
|
59
|
+
});
|
|
60
|
+
} else {
|
|
61
|
+
existingElement.issues.push(issue);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
for (const elementWithIssues of elementsWithIssues) {
|
|
68
|
+
elementWithIssues.issues.sort((a, b) => {
|
|
69
|
+
return -impactCompare(a.impact, b.impact) || a.id.localeCompare(b.id);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return elementsWithIssues;
|
|
74
|
+
}
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { suite, test } from 'node:test';
|
|
3
|
+
import type { Signal } from '@preact/signals-core';
|
|
4
|
+
import { signal } from '@preact/signals-core';
|
|
5
|
+
import type { ExtendedElementWithIssues, Issue } from '../types';
|
|
6
|
+
import { updateElementsWithIssues } from './update-elements-with-issues';
|
|
7
|
+
|
|
8
|
+
import type { AxeResults, ImpactValue } from 'axe-core';
|
|
9
|
+
import type { AccentedTrigger } from '../elements/accented-trigger';
|
|
10
|
+
type Violation = AxeResults['violations'][number];
|
|
11
|
+
type AxeNode = Violation['nodes'][number];
|
|
12
|
+
|
|
13
|
+
const win: Window & { CSS: typeof CSS } = {
|
|
14
|
+
document: {
|
|
15
|
+
// @ts-expect-error the return value is of incorrect type.
|
|
16
|
+
createElement: () => ({
|
|
17
|
+
style: {
|
|
18
|
+
setProperty: () => {},
|
|
19
|
+
},
|
|
20
|
+
dataset: {},
|
|
21
|
+
}),
|
|
22
|
+
contains: () => true,
|
|
23
|
+
},
|
|
24
|
+
// @ts-expect-error we're missing a lot of properties
|
|
25
|
+
getComputedStyle: () => ({
|
|
26
|
+
zIndex: '',
|
|
27
|
+
direction: 'ltr',
|
|
28
|
+
getPropertyValue: () => 'none',
|
|
29
|
+
}),
|
|
30
|
+
// @ts-expect-error we're missing a lot of properties
|
|
31
|
+
CSS: {
|
|
32
|
+
supports: () => true,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const getBoundingClientRect = () => ({});
|
|
37
|
+
|
|
38
|
+
const getRootNode = (): Node => ({}) as Node;
|
|
39
|
+
|
|
40
|
+
const baseElement = {
|
|
41
|
+
getBoundingClientRect,
|
|
42
|
+
getRootNode,
|
|
43
|
+
style: {
|
|
44
|
+
getPropertyValue: () => '',
|
|
45
|
+
},
|
|
46
|
+
closest: () => null,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// @ts-expect-error element is not HTMLElement
|
|
50
|
+
const element1: HTMLElement = { ...baseElement, isConnected: true };
|
|
51
|
+
// @ts-expect-error element is not HTMLElement
|
|
52
|
+
const element2: HTMLElement = { ...baseElement, isConnected: true };
|
|
53
|
+
// @ts-expect-error element is not HTMLElement
|
|
54
|
+
const element3: HTMLElement = { ...baseElement, isConnected: false };
|
|
55
|
+
|
|
56
|
+
// @ts-expect-error rootNode is not Node
|
|
57
|
+
const rootNode: Node = {};
|
|
58
|
+
|
|
59
|
+
const trigger = win.document.createElement('accented-trigger') as AccentedTrigger;
|
|
60
|
+
|
|
61
|
+
const position = signal({
|
|
62
|
+
left: 0,
|
|
63
|
+
width: 100,
|
|
64
|
+
top: 0,
|
|
65
|
+
height: 100,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const visible = signal(true);
|
|
69
|
+
|
|
70
|
+
const scrollableAncestors = signal(new Set<HTMLElement>());
|
|
71
|
+
|
|
72
|
+
const commonNodeProps = {
|
|
73
|
+
html: '<div></div>',
|
|
74
|
+
any: [],
|
|
75
|
+
all: [],
|
|
76
|
+
none: [],
|
|
77
|
+
target: ['div'],
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const node1: AxeNode = {
|
|
81
|
+
...commonNodeProps,
|
|
82
|
+
element: element1,
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const node2: AxeNode = {
|
|
86
|
+
...commonNodeProps,
|
|
87
|
+
element: element2,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const node3: AxeNode = {
|
|
91
|
+
...commonNodeProps,
|
|
92
|
+
element: element3,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const commonViolationProps = {
|
|
96
|
+
help: 'help',
|
|
97
|
+
helpUrl: 'http://example.com',
|
|
98
|
+
description: 'description',
|
|
99
|
+
tags: [],
|
|
100
|
+
impact: 'serious' as ImpactValue,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const violation1: Violation = {
|
|
104
|
+
...commonViolationProps,
|
|
105
|
+
id: 'id1',
|
|
106
|
+
nodes: [node1],
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const violation2: Violation = {
|
|
110
|
+
...commonViolationProps,
|
|
111
|
+
id: 'id2',
|
|
112
|
+
nodes: [node2],
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const violation3: Violation = {
|
|
116
|
+
...commonViolationProps,
|
|
117
|
+
id: 'id3',
|
|
118
|
+
nodes: [node2],
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const violation4: Violation = {
|
|
122
|
+
...commonViolationProps,
|
|
123
|
+
id: 'id4',
|
|
124
|
+
nodes: [node3],
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const commonIssueProps = {
|
|
128
|
+
title: 'help',
|
|
129
|
+
description: 'description',
|
|
130
|
+
url: 'http://example.com',
|
|
131
|
+
impact: 'serious',
|
|
132
|
+
} as const;
|
|
133
|
+
|
|
134
|
+
const issue1: Issue = {
|
|
135
|
+
id: 'id1',
|
|
136
|
+
...commonIssueProps,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const issue2: Issue = {
|
|
140
|
+
id: 'id2',
|
|
141
|
+
...commonIssueProps,
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const issue3: Issue = {
|
|
145
|
+
id: 'id3',
|
|
146
|
+
...commonIssueProps,
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const scanContext = {
|
|
150
|
+
include: [win.document],
|
|
151
|
+
exclude: [],
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
suite('updateElementsWithIssues', () => {
|
|
155
|
+
test('no changes', () => {
|
|
156
|
+
const extendedElementsWithIssues: Signal<Array<ExtendedElementWithIssues>> = signal([
|
|
157
|
+
{
|
|
158
|
+
id: 1,
|
|
159
|
+
element: element1,
|
|
160
|
+
rootNode,
|
|
161
|
+
skipRender: false,
|
|
162
|
+
position,
|
|
163
|
+
visible,
|
|
164
|
+
trigger,
|
|
165
|
+
anchorNameValue: 'none',
|
|
166
|
+
scrollableAncestors,
|
|
167
|
+
issues: signal([issue1]),
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: 2,
|
|
171
|
+
element: element2,
|
|
172
|
+
rootNode,
|
|
173
|
+
skipRender: false,
|
|
174
|
+
position,
|
|
175
|
+
visible,
|
|
176
|
+
trigger,
|
|
177
|
+
anchorNameValue: 'none',
|
|
178
|
+
scrollableAncestors,
|
|
179
|
+
issues: signal([issue2]),
|
|
180
|
+
},
|
|
181
|
+
]);
|
|
182
|
+
updateElementsWithIssues({
|
|
183
|
+
extendedElementsWithIssues,
|
|
184
|
+
scanContext,
|
|
185
|
+
violations: [violation1, violation2],
|
|
186
|
+
win,
|
|
187
|
+
name: 'accented',
|
|
188
|
+
});
|
|
189
|
+
assert.equal(extendedElementsWithIssues.value.length, 2);
|
|
190
|
+
assert.equal(extendedElementsWithIssues.value[0]?.element, element1);
|
|
191
|
+
assert.equal(extendedElementsWithIssues.value[0]?.issues.value.length, 1);
|
|
192
|
+
assert.equal(extendedElementsWithIssues.value[1]?.element, element2);
|
|
193
|
+
assert.equal(extendedElementsWithIssues.value[1]?.issues.value.length, 1);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test('one issue added', () => {
|
|
197
|
+
const extendedElementsWithIssues: Signal<Array<ExtendedElementWithIssues>> = signal([
|
|
198
|
+
{
|
|
199
|
+
id: 1,
|
|
200
|
+
element: element1,
|
|
201
|
+
rootNode,
|
|
202
|
+
skipRender: false,
|
|
203
|
+
position,
|
|
204
|
+
visible,
|
|
205
|
+
trigger,
|
|
206
|
+
anchorNameValue: 'none',
|
|
207
|
+
scrollableAncestors,
|
|
208
|
+
issues: signal([issue1]),
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
id: 2,
|
|
212
|
+
element: element2,
|
|
213
|
+
rootNode,
|
|
214
|
+
skipRender: false,
|
|
215
|
+
position,
|
|
216
|
+
visible,
|
|
217
|
+
trigger,
|
|
218
|
+
anchorNameValue: 'none',
|
|
219
|
+
scrollableAncestors,
|
|
220
|
+
issues: signal([issue2]),
|
|
221
|
+
},
|
|
222
|
+
]);
|
|
223
|
+
updateElementsWithIssues({
|
|
224
|
+
extendedElementsWithIssues,
|
|
225
|
+
scanContext,
|
|
226
|
+
violations: [violation1, violation2, violation3],
|
|
227
|
+
win,
|
|
228
|
+
name: 'accented',
|
|
229
|
+
});
|
|
230
|
+
assert.equal(extendedElementsWithIssues.value.length, 2);
|
|
231
|
+
assert.equal(extendedElementsWithIssues.value[0]?.element, element1);
|
|
232
|
+
assert.equal(extendedElementsWithIssues.value[0]?.issues.value.length, 1);
|
|
233
|
+
assert.equal(extendedElementsWithIssues.value[1]?.element, element2);
|
|
234
|
+
assert.equal(extendedElementsWithIssues.value[1]?.issues.value.length, 2);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test('one issue removed', () => {
|
|
238
|
+
const extendedElementsWithIssues: Signal<Array<ExtendedElementWithIssues>> = signal([
|
|
239
|
+
{
|
|
240
|
+
id: 1,
|
|
241
|
+
element: element1,
|
|
242
|
+
rootNode,
|
|
243
|
+
skipRender: false,
|
|
244
|
+
position,
|
|
245
|
+
visible,
|
|
246
|
+
trigger,
|
|
247
|
+
anchorNameValue: 'none',
|
|
248
|
+
scrollableAncestors,
|
|
249
|
+
issues: signal([issue1]),
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
id: 2,
|
|
253
|
+
element: element2,
|
|
254
|
+
rootNode,
|
|
255
|
+
skipRender: false,
|
|
256
|
+
position,
|
|
257
|
+
visible,
|
|
258
|
+
trigger,
|
|
259
|
+
anchorNameValue: 'none',
|
|
260
|
+
scrollableAncestors,
|
|
261
|
+
issues: signal([issue2, issue3]),
|
|
262
|
+
},
|
|
263
|
+
]);
|
|
264
|
+
updateElementsWithIssues({
|
|
265
|
+
extendedElementsWithIssues,
|
|
266
|
+
scanContext,
|
|
267
|
+
violations: [violation1, violation2],
|
|
268
|
+
win,
|
|
269
|
+
name: 'accented',
|
|
270
|
+
});
|
|
271
|
+
assert.equal(extendedElementsWithIssues.value.length, 2);
|
|
272
|
+
assert.equal(extendedElementsWithIssues.value[0]?.element, element1);
|
|
273
|
+
assert.equal(extendedElementsWithIssues.value[0]?.issues.value.length, 1);
|
|
274
|
+
assert.equal(extendedElementsWithIssues.value[1]?.element, element2);
|
|
275
|
+
assert.equal(extendedElementsWithIssues.value[1]?.issues.value.length, 1);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test('one element added', () => {
|
|
279
|
+
const extendedElementsWithIssues: Signal<Array<ExtendedElementWithIssues>> = signal([
|
|
280
|
+
{
|
|
281
|
+
id: 1,
|
|
282
|
+
element: element1,
|
|
283
|
+
rootNode,
|
|
284
|
+
skipRender: false,
|
|
285
|
+
position,
|
|
286
|
+
visible,
|
|
287
|
+
trigger,
|
|
288
|
+
anchorNameValue: 'none',
|
|
289
|
+
scrollableAncestors,
|
|
290
|
+
issues: signal([issue1]),
|
|
291
|
+
},
|
|
292
|
+
]);
|
|
293
|
+
updateElementsWithIssues({
|
|
294
|
+
extendedElementsWithIssues,
|
|
295
|
+
scanContext,
|
|
296
|
+
violations: [violation1, violation2],
|
|
297
|
+
win,
|
|
298
|
+
name: 'accented',
|
|
299
|
+
});
|
|
300
|
+
assert.equal(extendedElementsWithIssues.value.length, 2);
|
|
301
|
+
assert.equal(extendedElementsWithIssues.value[0]?.element, element1);
|
|
302
|
+
assert.equal(extendedElementsWithIssues.value[0]?.issues.value.length, 1);
|
|
303
|
+
assert.equal(extendedElementsWithIssues.value[1]?.element, element2);
|
|
304
|
+
assert.equal(extendedElementsWithIssues.value[1]?.issues.value.length, 1);
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
test('one disconnected element added', () => {
|
|
308
|
+
const extendedElementsWithIssues: Signal<Array<ExtendedElementWithIssues>> = signal([
|
|
309
|
+
{
|
|
310
|
+
id: 1,
|
|
311
|
+
element: element1,
|
|
312
|
+
rootNode,
|
|
313
|
+
skipRender: false,
|
|
314
|
+
position,
|
|
315
|
+
visible,
|
|
316
|
+
trigger,
|
|
317
|
+
anchorNameValue: 'none',
|
|
318
|
+
scrollableAncestors,
|
|
319
|
+
issues: signal([issue1]),
|
|
320
|
+
},
|
|
321
|
+
]);
|
|
322
|
+
updateElementsWithIssues({
|
|
323
|
+
extendedElementsWithIssues,
|
|
324
|
+
scanContext,
|
|
325
|
+
violations: [violation1, violation4],
|
|
326
|
+
win,
|
|
327
|
+
name: 'accented',
|
|
328
|
+
});
|
|
329
|
+
assert.equal(extendedElementsWithIssues.value.length, 1);
|
|
330
|
+
assert.equal(extendedElementsWithIssues.value[0]?.element, element1);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
test('one element removed', () => {
|
|
334
|
+
const extendedElementsWithIssues: Signal<Array<ExtendedElementWithIssues>> = signal([
|
|
335
|
+
{
|
|
336
|
+
id: 1,
|
|
337
|
+
element: element1,
|
|
338
|
+
rootNode,
|
|
339
|
+
skipRender: false,
|
|
340
|
+
position,
|
|
341
|
+
visible,
|
|
342
|
+
trigger,
|
|
343
|
+
anchorNameValue: 'none',
|
|
344
|
+
scrollableAncestors,
|
|
345
|
+
issues: signal([issue1]),
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
id: 2,
|
|
349
|
+
element: element2,
|
|
350
|
+
rootNode,
|
|
351
|
+
skipRender: false,
|
|
352
|
+
position,
|
|
353
|
+
visible,
|
|
354
|
+
trigger,
|
|
355
|
+
anchorNameValue: 'none',
|
|
356
|
+
scrollableAncestors,
|
|
357
|
+
issues: signal([issue2]),
|
|
358
|
+
},
|
|
359
|
+
]);
|
|
360
|
+
updateElementsWithIssues({
|
|
361
|
+
extendedElementsWithIssues,
|
|
362
|
+
scanContext,
|
|
363
|
+
violations: [violation1],
|
|
364
|
+
win,
|
|
365
|
+
name: 'accented',
|
|
366
|
+
});
|
|
367
|
+
assert.equal(extendedElementsWithIssues.value.length, 1);
|
|
368
|
+
assert.equal(extendedElementsWithIssues.value[0]?.element, element1);
|
|
369
|
+
assert.equal(extendedElementsWithIssues.value[0]?.issues.value.length, 1);
|
|
370
|
+
});
|
|
371
|
+
});
|