accented 0.0.2 → 1.0.1
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 +44 -187
- package/dist/accented.d.ts +8 -8
- package/dist/accented.d.ts.map +1 -1
- package/dist/accented.js +37 -30
- package/dist/accented.js.map +1 -1
- package/dist/common/strings.d.ts +2 -0
- package/dist/common/strings.d.ts.map +1 -0
- package/dist/common/strings.js +2 -0
- package/dist/common/strings.js.map +1 -0
- 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 +2 -1
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +2 -1
- package/dist/constants.js.map +1 -1
- package/dist/dom-updater.d.ts +1 -1
- package/dist/dom-updater.d.ts.map +1 -1
- package/dist/dom-updater.js +73 -31
- package/dist/dom-updater.js.map +1 -1
- package/dist/elements/accented-dialog.d.ts +18 -10
- package/dist/elements/accented-dialog.d.ts.map +1 -1
- package/dist/elements/accented-dialog.js +116 -95
- package/dist/elements/accented-dialog.js.map +1 -1
- package/dist/elements/accented-trigger.d.ts +14 -9
- package/dist/elements/accented-trigger.d.ts.map +1 -1
- package/dist/elements/accented-trigger.js +83 -24
- package/dist/elements/accented-trigger.js.map +1 -1
- 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 +1 -1
- package/dist/intersection-observer.d.ts.map +1 -1
- package/dist/intersection-observer.js +12 -6
- package/dist/intersection-observer.js.map +1 -1
- package/dist/log-and-rethrow.d.ts +1 -1
- package/dist/log-and-rethrow.d.ts.map +1 -1
- package/dist/log-and-rethrow.js +2 -3
- package/dist/log-and-rethrow.js.map +1 -1
- package/dist/logger.d.ts +4 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +6 -3
- package/dist/logger.js.map +1 -1
- package/dist/register-elements.d.ts +1 -1
- package/dist/register-elements.d.ts.map +1 -1
- package/dist/register-elements.js +6 -7
- package/dist/register-elements.js.map +1 -1
- package/dist/resize-listener.d.ts +1 -1
- package/dist/resize-listener.d.ts.map +1 -1
- package/dist/resize-listener.js +3 -4
- package/dist/resize-listener.js.map +1 -1
- package/dist/scanner.d.ts +2 -2
- package/dist/scanner.d.ts.map +1 -1
- package/dist/scanner.js +76 -43
- package/dist/scanner.js.map +1 -1
- package/dist/scroll-listeners.d.ts +1 -1
- package/dist/scroll-listeners.d.ts.map +1 -1
- package/dist/scroll-listeners.js +3 -4
- package/dist/scroll-listeners.js.map +1 -1
- package/dist/state.d.ts +3 -2
- package/dist/state.d.ts.map +1 -1
- package/dist/state.js +5 -3
- package/dist/state.js.map +1 -1
- package/dist/task-queue.d.ts +4 -4
- package/dist/task-queue.d.ts.map +1 -1
- package/dist/task-queue.js +3 -2
- package/dist/task-queue.js.map +1 -1
- package/dist/types.d.ts +140 -49
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- 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 +2 -2
- package/dist/utils/are-issue-sets-equal.d.ts.map +1 -1
- package/dist/utils/are-issue-sets-equal.js +3 -3
- package/dist/utils/are-issue-sets-equal.js.map +1 -1
- 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 +1 -1
- package/dist/utils/deep-merge.d.ts.map +1 -1
- package/dist/utils/deep-merge.js +8 -5
- package/dist/utils/deep-merge.js.map +1 -1
- 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 +1 -1
- package/dist/utils/get-element-html.d.ts.map +1 -1
- package/dist/utils/get-element-html.js +4 -2
- package/dist/utils/get-element-html.js.map +1 -1
- package/dist/utils/get-element-position.d.ts +10 -2
- package/dist/utils/get-element-position.d.ts.map +1 -1
- package/dist/utils/get-element-position.js +64 -16
- package/dist/utils/get-element-position.js.map +1 -1
- 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 +1 -1
- package/dist/utils/get-scrollable-ancestors.d.ts.map +1 -1
- package/dist/utils/get-scrollable-ancestors.js +10 -6
- package/dist/utils/get-scrollable-ancestors.js.map +1 -1
- 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 +1 -1
- package/dist/utils/recalculate-positions.d.ts.map +1 -1
- package/dist/utils/recalculate-positions.js +5 -5
- package/dist/utils/recalculate-positions.js.map +1 -1
- package/dist/utils/recalculate-scrollable-ancestors.d.ts +1 -1
- package/dist/utils/recalculate-scrollable-ancestors.d.ts.map +1 -1
- package/dist/utils/recalculate-scrollable-ancestors.js +4 -4
- package/dist/utils/recalculate-scrollable-ancestors.js.map +1 -1
- 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 +1 -1
- package/dist/utils/supports-anchor-positioning.d.ts.map +1 -1
- package/dist/utils/supports-anchor-positioning.js +15 -2
- package/dist/utils/supports-anchor-positioning.js.map +1 -1
- package/dist/utils/transform-violations.d.ts +2 -2
- package/dist/utils/transform-violations.d.ts.map +1 -1
- package/dist/utils/transform-violations.js +25 -10
- package/dist/utils/transform-violations.js.map +1 -1
- package/dist/utils/update-elements-with-issues.d.ts +11 -5
- package/dist/utils/update-elements-with-issues.d.ts.map +1 -1
- package/dist/utils/update-elements-with-issues.js +56 -24
- package/dist/utils/update-elements-with-issues.js.map +1 -1
- package/dist/validate-options.d.ts +2 -2
- package/dist/validate-options.d.ts.map +1 -1
- package/dist/validate-options.js +91 -4
- package/dist/validate-options.js.map +1 -1
- package/package.json +16 -8
- package/src/accented.test.ts +2 -2
- package/src/accented.ts +45 -34
- package/src/common/strings.ts +2 -0
- package/src/common/tokens.ts +10 -0
- package/src/constants.ts +2 -1
- package/src/dom-updater.ts +87 -34
- package/src/elements/accented-dialog.ts +163 -123
- package/src/elements/accented-trigger.ts +128 -50
- package/src/fullscreen-listener.ts +21 -0
- package/src/intersection-observer.ts +27 -16
- package/src/log-and-rethrow.ts +2 -3
- package/src/logger.ts +14 -4
- package/src/register-elements.ts +7 -7
- package/src/resize-listener.ts +15 -11
- package/src/scanner.ts +113 -57
- package/src/scroll-listeners.ts +27 -19
- package/src/state.ts +27 -16
- package/src/task-queue.test.ts +5 -4
- package/src/task-queue.ts +8 -6
- package/src/types.ts +179 -76
- package/src/utils/are-elements-with-issues-equal.ts +11 -0
- package/src/utils/are-issue-sets-equal.test.ts +6 -7
- package/src/utils/are-issue-sets-equal.ts +8 -6
- 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 +8 -1
- package/src/utils/deep-merge.ts +14 -8
- package/src/utils/dom-helpers.ts +42 -0
- package/src/utils/ensure-non-empty.ts +6 -0
- package/src/utils/get-element-html.ts +4 -2
- package/src/utils/get-element-position.ts +84 -16
- 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 +15 -7
- 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 +5 -5
- package/src/utils/recalculate-scrollable-ancestors.ts +4 -4
- package/src/utils/shadow-dom-aware-mutation-observer.ts +75 -0
- package/src/utils/supports-anchor-positioning.ts +19 -3
- package/src/utils/transform-violations.test.ts +29 -25
- package/src/utils/transform-violations.ts +32 -12
- package/src/utils/update-elements-with-issues.test.ts +145 -53
- package/src/utils/update-elements-with-issues.ts +123 -54
- package/src/validate-options.ts +154 -14
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
type WindowWithCSS = Window & {
|
|
2
|
-
CSS: typeof CSS
|
|
2
|
+
CSS: typeof CSS;
|
|
3
|
+
};
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* We have to do browser sniffing now and explicitly turn off Anchor positioning in Safari
|
|
7
|
+
* since anchor positioning is not working correctly in Safari 26 Technology Preview.
|
|
8
|
+
*/
|
|
9
|
+
function isWebKit(win: Window) {
|
|
10
|
+
const ua = win.navigator.userAgent;
|
|
11
|
+
return (/AppleWebKit/.test(ua) && !/Chrome/.test(ua)) || /\b(iPad|iPhone|iPod)\b/.test(ua);
|
|
3
12
|
}
|
|
4
13
|
|
|
5
|
-
|
|
6
|
-
|
|
14
|
+
// ATTENTION: sync with the implementation in end-to-end tests.
|
|
15
|
+
// I didn't find a way to sync this with automatically with the implementation of supportsAnchorPositioning
|
|
16
|
+
// in end-to-end tests, so it has to be synced manually.
|
|
17
|
+
export function supportsAnchorPositioning(win: WindowWithCSS) {
|
|
18
|
+
return (
|
|
19
|
+
win.CSS.supports('anchor-name: --foo') &&
|
|
20
|
+
win.CSS.supports('position-anchor: --foo') &&
|
|
21
|
+
!isWebKit(win)
|
|
22
|
+
);
|
|
7
23
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import assert from 'node:assert/strict';
|
|
2
|
-
import {suite, test} from 'node:test';
|
|
3
|
-
import transformViolations from './transform-violations';
|
|
4
|
-
|
|
2
|
+
import { suite, test } from 'node:test';
|
|
5
3
|
import type { AxeResults } from 'axe-core';
|
|
4
|
+
import { transformViolations } from './transform-violations';
|
|
5
|
+
|
|
6
6
|
type Violation = AxeResults['violations'][number];
|
|
7
7
|
type Node = Violation['nodes'][number];
|
|
8
8
|
|
|
@@ -12,7 +12,7 @@ const commonViolationProps1: Omit<Violation, 'nodes'> = {
|
|
|
12
12
|
helpUrl: 'http://example.com',
|
|
13
13
|
description: 'description1',
|
|
14
14
|
tags: [],
|
|
15
|
-
impact: 'serious'
|
|
15
|
+
impact: 'serious',
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
const commonViolationProps2: Omit<Violation, 'nodes'> = {
|
|
@@ -21,51 +21,53 @@ const commonViolationProps2: Omit<Violation, 'nodes'> = {
|
|
|
21
21
|
helpUrl: 'http://example.com',
|
|
22
22
|
description: 'description2',
|
|
23
23
|
tags: [],
|
|
24
|
-
impact: 'serious'
|
|
24
|
+
impact: 'serious',
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
+
const getRootNode = (): Node => ({}) as Node;
|
|
28
|
+
|
|
27
29
|
// @ts-expect-error element is not HTMLElement
|
|
28
|
-
const element1: HTMLElement = {};
|
|
30
|
+
const element1: HTMLElement = { getRootNode };
|
|
29
31
|
// @ts-expect-error element is not HTMLElement
|
|
30
|
-
const element2: HTMLElement = {};
|
|
32
|
+
const element2: HTMLElement = { getRootNode };
|
|
31
33
|
// @ts-expect-error element is not HTMLElement
|
|
32
|
-
const element3: HTMLElement = {};
|
|
34
|
+
const element3: HTMLElement = { getRootNode };
|
|
33
35
|
|
|
34
36
|
const commonNodeProps = {
|
|
35
37
|
html: '<div></div>',
|
|
36
38
|
any: [],
|
|
37
39
|
all: [],
|
|
38
|
-
none: []
|
|
40
|
+
none: [],
|
|
39
41
|
};
|
|
40
42
|
|
|
41
43
|
const node1: Node = {
|
|
42
44
|
...commonNodeProps,
|
|
43
45
|
element: element1,
|
|
44
46
|
target: ['div'],
|
|
45
|
-
failureSummary: 'summary1'
|
|
47
|
+
failureSummary: 'summary1',
|
|
46
48
|
};
|
|
47
49
|
|
|
48
50
|
const node2: Node = {
|
|
49
51
|
...commonNodeProps,
|
|
50
52
|
element: element2,
|
|
51
53
|
target: ['div'],
|
|
52
|
-
failureSummary: 'summary2'
|
|
54
|
+
failureSummary: 'summary2',
|
|
53
55
|
};
|
|
54
56
|
|
|
55
57
|
const node3: Node = {
|
|
56
58
|
...commonNodeProps,
|
|
57
59
|
element: element3,
|
|
58
60
|
target: ['div'],
|
|
59
|
-
failureSummary: 'summary3'
|
|
61
|
+
failureSummary: 'summary3',
|
|
60
62
|
};
|
|
61
63
|
|
|
62
64
|
suite('transformViolations', () => {
|
|
63
65
|
test('one violation, one element', () => {
|
|
64
66
|
const violation: Violation = {
|
|
65
67
|
...commonViolationProps1,
|
|
66
|
-
nodes: [node1]
|
|
68
|
+
nodes: [node1],
|
|
67
69
|
};
|
|
68
|
-
const elementsWithIssues = transformViolations([violation]);
|
|
70
|
+
const elementsWithIssues = transformViolations([violation], 'accented');
|
|
69
71
|
assert.equal(elementsWithIssues.length, 1);
|
|
70
72
|
assert.equal(elementsWithIssues[0]?.element, element1);
|
|
71
73
|
assert.equal(elementsWithIssues[0].issues.length, 1);
|
|
@@ -76,15 +78,17 @@ suite('transformViolations', () => {
|
|
|
76
78
|
test('two violations, two elements each', () => {
|
|
77
79
|
const violation1: Violation = {
|
|
78
80
|
...commonViolationProps1,
|
|
79
|
-
nodes: [node1, node2]
|
|
81
|
+
nodes: [node1, node2],
|
|
80
82
|
};
|
|
81
83
|
const violation2: Violation = {
|
|
82
84
|
...commonViolationProps2,
|
|
83
|
-
nodes: [node1, node3]
|
|
85
|
+
nodes: [node1, node3],
|
|
84
86
|
};
|
|
85
|
-
const elementsWithIssues = transformViolations([violation1, violation2]);
|
|
87
|
+
const elementsWithIssues = transformViolations([violation1, violation2], 'accented');
|
|
86
88
|
assert.equal(elementsWithIssues.length, 3);
|
|
87
|
-
const elementWithTwoIssues = elementsWithIssues.find(
|
|
89
|
+
const elementWithTwoIssues = elementsWithIssues.find(
|
|
90
|
+
(elementWithIssues) => elementWithIssues.element === element1,
|
|
91
|
+
);
|
|
88
92
|
assert.equal(elementWithTwoIssues?.issues.length, 2);
|
|
89
93
|
});
|
|
90
94
|
|
|
@@ -94,14 +98,14 @@ suite('transformViolations', () => {
|
|
|
94
98
|
element: element1,
|
|
95
99
|
// A target array whose length is > 1 signifies an element in an iframe
|
|
96
100
|
target: ['iframe', 'div'],
|
|
97
|
-
failureSummary: 'summary1'
|
|
101
|
+
failureSummary: 'summary1',
|
|
98
102
|
};
|
|
99
103
|
const violation: Violation = {
|
|
100
104
|
...commonViolationProps1,
|
|
101
|
-
nodes: [node]
|
|
105
|
+
nodes: [node],
|
|
102
106
|
};
|
|
103
107
|
|
|
104
|
-
const elementsWithIssues = transformViolations([violation]);
|
|
108
|
+
const elementsWithIssues = transformViolations([violation], 'accented');
|
|
105
109
|
assert.equal(elementsWithIssues.length, 0);
|
|
106
110
|
});
|
|
107
111
|
|
|
@@ -111,14 +115,14 @@ suite('transformViolations', () => {
|
|
|
111
115
|
element: element1,
|
|
112
116
|
// A target that contains an array within the outer array signifies an element in shadow DOM
|
|
113
117
|
target: [['div', 'div']],
|
|
114
|
-
failureSummary: 'summary1'
|
|
118
|
+
failureSummary: 'summary1',
|
|
115
119
|
};
|
|
116
120
|
const violation: Violation = {
|
|
117
121
|
...commonViolationProps1,
|
|
118
|
-
nodes: [node]
|
|
122
|
+
nodes: [node],
|
|
119
123
|
};
|
|
120
124
|
|
|
121
|
-
const elementsWithIssues = transformViolations([violation]);
|
|
122
|
-
assert.equal(elementsWithIssues.length,
|
|
125
|
+
const elementsWithIssues = transformViolations([violation], 'accented');
|
|
126
|
+
assert.equal(elementsWithIssues.length, 1);
|
|
123
127
|
});
|
|
124
128
|
});
|
|
@@ -1,12 +1,31 @@
|
|
|
1
1
|
import type { AxeResults, ImpactValue } from 'axe-core';
|
|
2
|
-
import type {
|
|
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
|
+
}
|
|
3
22
|
|
|
4
23
|
function impactCompare(a: ImpactValue, b: ImpactValue) {
|
|
5
24
|
const impactOrder = [null, 'minor', 'moderate', 'serious', 'critical'];
|
|
6
25
|
return impactOrder.indexOf(a) - impactOrder.indexOf(b);
|
|
7
26
|
}
|
|
8
27
|
|
|
9
|
-
export
|
|
28
|
+
export function transformViolations(violations: typeof AxeResults.violations, name: string) {
|
|
10
29
|
const elementsWithIssues: Array<ElementWithIssues> = [];
|
|
11
30
|
|
|
12
31
|
for (const violation of violations) {
|
|
@@ -21,26 +40,27 @@ export default function transformViolations(violations: typeof AxeResults.violat
|
|
|
21
40
|
// A consumer of Accented can instead scan the iframed document by calling Accented initialization from that document.
|
|
22
41
|
const isInIframe = target.length > 1;
|
|
23
42
|
|
|
24
|
-
|
|
25
|
-
// Until then, we don’t want such elements to be added to the set.
|
|
26
|
-
const isInShadowDOM = Array.isArray(target[0]);
|
|
27
|
-
|
|
28
|
-
if (element && !isInIframe && !isInShadowDOM) {
|
|
43
|
+
if (element && !isInIframe && !maybeCausedByAccented(violation.id, element, name)) {
|
|
29
44
|
const issue: Issue = {
|
|
30
45
|
id: violation.id,
|
|
31
46
|
title: violation.help,
|
|
47
|
+
// See https://github.com/pomerantsev/accented/issues/203
|
|
32
48
|
description: node.failureSummary ?? violation.description,
|
|
33
49
|
url: violation.helpUrl,
|
|
34
|
-
|
|
50
|
+
// See https://github.com/pomerantsev/accented/issues/203
|
|
51
|
+
impact: violation.impact ?? null,
|
|
35
52
|
};
|
|
36
|
-
const
|
|
37
|
-
|
|
53
|
+
const existingElement = elementsWithIssues.find(
|
|
54
|
+
(elementWithIssues) => elementWithIssues.element === element,
|
|
55
|
+
);
|
|
56
|
+
if (existingElement === undefined) {
|
|
38
57
|
elementsWithIssues.push({
|
|
39
58
|
element,
|
|
40
|
-
|
|
59
|
+
rootNode: element.getRootNode(),
|
|
60
|
+
issues: [issue],
|
|
41
61
|
});
|
|
42
62
|
} else {
|
|
43
|
-
|
|
63
|
+
existingElement.issues.push(issue);
|
|
44
64
|
}
|
|
45
65
|
}
|
|
46
66
|
}
|
|
@@ -1,51 +1,72 @@
|
|
|
1
|
-
import {suite, test} from 'node:test';
|
|
2
1
|
import assert from 'node:assert/strict';
|
|
2
|
+
import { suite, test } from 'node:test';
|
|
3
3
|
import type { Signal } from '@preact/signals-core';
|
|
4
4
|
import { signal } from '@preact/signals-core';
|
|
5
|
-
import type { ExtendedElementWithIssues, Issue } from '../types';
|
|
6
|
-
import updateElementsWithIssues from './update-elements-with-issues';
|
|
7
|
-
|
|
8
5
|
import type { AxeResults, ImpactValue } from 'axe-core';
|
|
9
6
|
import type { AccentedTrigger } from '../elements/accented-trigger';
|
|
7
|
+
import type { ExtendedElementWithIssues, Issue } from '../types';
|
|
8
|
+
import { updateElementsWithIssues } from './update-elements-with-issues';
|
|
9
|
+
|
|
10
10
|
type Violation = AxeResults['violations'][number];
|
|
11
|
-
type
|
|
11
|
+
type AxeNode = Violation['nodes'][number];
|
|
12
12
|
|
|
13
13
|
const win: Window & { CSS: typeof CSS } = {
|
|
14
14
|
document: {
|
|
15
15
|
// @ts-expect-error the return value is of incorrect type.
|
|
16
16
|
createElement: () => ({
|
|
17
17
|
style: {
|
|
18
|
-
setProperty: () => {}
|
|
18
|
+
setProperty: () => {},
|
|
19
19
|
},
|
|
20
|
-
dataset: {}
|
|
21
|
-
})
|
|
20
|
+
dataset: {},
|
|
21
|
+
}),
|
|
22
|
+
contains: () => true,
|
|
22
23
|
},
|
|
23
24
|
// @ts-expect-error we're missing a lot of properties
|
|
24
25
|
getComputedStyle: () => ({
|
|
25
26
|
zIndex: '',
|
|
26
|
-
direction: 'ltr'
|
|
27
|
+
direction: 'ltr',
|
|
28
|
+
getPropertyValue: () => 'none',
|
|
27
29
|
}),
|
|
28
30
|
// @ts-expect-error we're missing a lot of properties
|
|
29
31
|
CSS: {
|
|
30
|
-
supports: () => true
|
|
31
|
-
}
|
|
32
|
-
|
|
32
|
+
supports: () => true,
|
|
33
|
+
},
|
|
34
|
+
// @ts-expect-error we're missing a lot of properties
|
|
35
|
+
navigator: {
|
|
36
|
+
userAgent: '',
|
|
37
|
+
},
|
|
38
|
+
};
|
|
33
39
|
|
|
34
40
|
const getBoundingClientRect = () => ({});
|
|
35
41
|
|
|
42
|
+
const getRootNode = (): Node => ({}) as Node;
|
|
43
|
+
|
|
44
|
+
const baseElement = {
|
|
45
|
+
getBoundingClientRect,
|
|
46
|
+
getRootNode,
|
|
47
|
+
style: {
|
|
48
|
+
getPropertyValue: () => '',
|
|
49
|
+
},
|
|
50
|
+
closest: () => null,
|
|
51
|
+
};
|
|
52
|
+
|
|
36
53
|
// @ts-expect-error element is not HTMLElement
|
|
37
|
-
const element1: HTMLElement = {
|
|
54
|
+
const element1: HTMLElement = { ...baseElement, isConnected: true };
|
|
38
55
|
// @ts-expect-error element is not HTMLElement
|
|
39
|
-
const element2: HTMLElement = {
|
|
56
|
+
const element2: HTMLElement = { ...baseElement, isConnected: true };
|
|
40
57
|
// @ts-expect-error element is not HTMLElement
|
|
41
|
-
const element3: HTMLElement = {
|
|
58
|
+
const element3: HTMLElement = { ...baseElement, isConnected: false };
|
|
59
|
+
|
|
60
|
+
// @ts-expect-error rootNode is not Node
|
|
61
|
+
const rootNode: Node = {};
|
|
42
62
|
|
|
43
63
|
const trigger = win.document.createElement('accented-trigger') as AccentedTrigger;
|
|
44
64
|
|
|
45
65
|
const position = signal({
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
66
|
+
left: 0,
|
|
67
|
+
width: 100,
|
|
68
|
+
top: 0,
|
|
69
|
+
height: 100,
|
|
49
70
|
});
|
|
50
71
|
|
|
51
72
|
const visible = signal(true);
|
|
@@ -57,20 +78,20 @@ const commonNodeProps = {
|
|
|
57
78
|
any: [],
|
|
58
79
|
all: [],
|
|
59
80
|
none: [],
|
|
60
|
-
target: ['div']
|
|
81
|
+
target: ['div'],
|
|
61
82
|
};
|
|
62
83
|
|
|
63
|
-
const node1:
|
|
84
|
+
const node1: AxeNode = {
|
|
64
85
|
...commonNodeProps,
|
|
65
86
|
element: element1,
|
|
66
87
|
};
|
|
67
88
|
|
|
68
|
-
const node2:
|
|
89
|
+
const node2: AxeNode = {
|
|
69
90
|
...commonNodeProps,
|
|
70
91
|
element: element2,
|
|
71
92
|
};
|
|
72
93
|
|
|
73
|
-
const node3:
|
|
94
|
+
const node3: AxeNode = {
|
|
74
95
|
...commonNodeProps,
|
|
75
96
|
element: element3,
|
|
76
97
|
};
|
|
@@ -80,53 +101,58 @@ const commonViolationProps = {
|
|
|
80
101
|
helpUrl: 'http://example.com',
|
|
81
102
|
description: 'description',
|
|
82
103
|
tags: [],
|
|
83
|
-
impact: 'serious' as ImpactValue
|
|
104
|
+
impact: 'serious' as ImpactValue,
|
|
84
105
|
};
|
|
85
106
|
|
|
86
107
|
const violation1: Violation = {
|
|
87
108
|
...commonViolationProps,
|
|
88
109
|
id: 'id1',
|
|
89
|
-
nodes: [node1]
|
|
110
|
+
nodes: [node1],
|
|
90
111
|
};
|
|
91
112
|
|
|
92
113
|
const violation2: Violation = {
|
|
93
114
|
...commonViolationProps,
|
|
94
115
|
id: 'id2',
|
|
95
|
-
nodes: [node2]
|
|
116
|
+
nodes: [node2],
|
|
96
117
|
};
|
|
97
118
|
|
|
98
119
|
const violation3: Violation = {
|
|
99
120
|
...commonViolationProps,
|
|
100
121
|
id: 'id3',
|
|
101
|
-
nodes: [node2]
|
|
122
|
+
nodes: [node2],
|
|
102
123
|
};
|
|
103
124
|
|
|
104
125
|
const violation4: Violation = {
|
|
105
126
|
...commonViolationProps,
|
|
106
127
|
id: 'id4',
|
|
107
|
-
nodes: [node3]
|
|
128
|
+
nodes: [node3],
|
|
108
129
|
};
|
|
109
130
|
|
|
110
131
|
const commonIssueProps = {
|
|
111
132
|
title: 'help',
|
|
112
133
|
description: 'description',
|
|
113
134
|
url: 'http://example.com',
|
|
114
|
-
impact: 'serious'
|
|
135
|
+
impact: 'serious',
|
|
115
136
|
} as const;
|
|
116
137
|
|
|
117
138
|
const issue1: Issue = {
|
|
118
139
|
id: 'id1',
|
|
119
|
-
...commonIssueProps
|
|
140
|
+
...commonIssueProps,
|
|
120
141
|
};
|
|
121
142
|
|
|
122
143
|
const issue2: Issue = {
|
|
123
144
|
id: 'id2',
|
|
124
|
-
...commonIssueProps
|
|
145
|
+
...commonIssueProps,
|
|
125
146
|
};
|
|
126
147
|
|
|
127
148
|
const issue3: Issue = {
|
|
128
149
|
id: 'id3',
|
|
129
|
-
...commonIssueProps
|
|
150
|
+
...commonIssueProps,
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
const scanContext = {
|
|
154
|
+
include: [win.document],
|
|
155
|
+
exclude: [],
|
|
130
156
|
};
|
|
131
157
|
|
|
132
158
|
suite('updateElementsWithIssues', () => {
|
|
@@ -135,23 +161,35 @@ suite('updateElementsWithIssues', () => {
|
|
|
135
161
|
{
|
|
136
162
|
id: 1,
|
|
137
163
|
element: element1,
|
|
164
|
+
rootNode,
|
|
165
|
+
skipRender: false,
|
|
138
166
|
position,
|
|
139
167
|
visible,
|
|
140
168
|
trigger,
|
|
169
|
+
anchorNameValue: 'none',
|
|
141
170
|
scrollableAncestors,
|
|
142
|
-
issues: signal([issue1])
|
|
171
|
+
issues: signal([issue1]),
|
|
143
172
|
},
|
|
144
173
|
{
|
|
145
174
|
id: 2,
|
|
146
175
|
element: element2,
|
|
176
|
+
rootNode,
|
|
177
|
+
skipRender: false,
|
|
147
178
|
position,
|
|
148
179
|
visible,
|
|
149
180
|
trigger,
|
|
181
|
+
anchorNameValue: 'none',
|
|
150
182
|
scrollableAncestors,
|
|
151
|
-
issues: signal([issue2])
|
|
152
|
-
}
|
|
183
|
+
issues: signal([issue2]),
|
|
184
|
+
},
|
|
153
185
|
]);
|
|
154
|
-
updateElementsWithIssues(
|
|
186
|
+
updateElementsWithIssues({
|
|
187
|
+
extendedElementsWithIssues,
|
|
188
|
+
scanContext,
|
|
189
|
+
violations: [violation1, violation2],
|
|
190
|
+
win,
|
|
191
|
+
name: 'accented',
|
|
192
|
+
});
|
|
155
193
|
assert.equal(extendedElementsWithIssues.value.length, 2);
|
|
156
194
|
assert.equal(extendedElementsWithIssues.value[0]?.element, element1);
|
|
157
195
|
assert.equal(extendedElementsWithIssues.value[0]?.issues.value.length, 1);
|
|
@@ -164,23 +202,35 @@ suite('updateElementsWithIssues', () => {
|
|
|
164
202
|
{
|
|
165
203
|
id: 1,
|
|
166
204
|
element: element1,
|
|
205
|
+
rootNode,
|
|
206
|
+
skipRender: false,
|
|
167
207
|
position,
|
|
168
208
|
visible,
|
|
169
209
|
trigger,
|
|
210
|
+
anchorNameValue: 'none',
|
|
170
211
|
scrollableAncestors,
|
|
171
|
-
issues: signal([issue1])
|
|
212
|
+
issues: signal([issue1]),
|
|
172
213
|
},
|
|
173
214
|
{
|
|
174
215
|
id: 2,
|
|
175
216
|
element: element2,
|
|
217
|
+
rootNode,
|
|
218
|
+
skipRender: false,
|
|
176
219
|
position,
|
|
177
220
|
visible,
|
|
178
221
|
trigger,
|
|
222
|
+
anchorNameValue: 'none',
|
|
179
223
|
scrollableAncestors,
|
|
180
|
-
issues: signal([issue2])
|
|
181
|
-
}
|
|
224
|
+
issues: signal([issue2]),
|
|
225
|
+
},
|
|
182
226
|
]);
|
|
183
|
-
updateElementsWithIssues(
|
|
227
|
+
updateElementsWithIssues({
|
|
228
|
+
extendedElementsWithIssues,
|
|
229
|
+
scanContext,
|
|
230
|
+
violations: [violation1, violation2, violation3],
|
|
231
|
+
win,
|
|
232
|
+
name: 'accented',
|
|
233
|
+
});
|
|
184
234
|
assert.equal(extendedElementsWithIssues.value.length, 2);
|
|
185
235
|
assert.equal(extendedElementsWithIssues.value[0]?.element, element1);
|
|
186
236
|
assert.equal(extendedElementsWithIssues.value[0]?.issues.value.length, 1);
|
|
@@ -193,23 +243,35 @@ suite('updateElementsWithIssues', () => {
|
|
|
193
243
|
{
|
|
194
244
|
id: 1,
|
|
195
245
|
element: element1,
|
|
246
|
+
rootNode,
|
|
247
|
+
skipRender: false,
|
|
196
248
|
position,
|
|
197
249
|
visible,
|
|
198
250
|
trigger,
|
|
251
|
+
anchorNameValue: 'none',
|
|
199
252
|
scrollableAncestors,
|
|
200
|
-
issues: signal([issue1])
|
|
253
|
+
issues: signal([issue1]),
|
|
201
254
|
},
|
|
202
255
|
{
|
|
203
256
|
id: 2,
|
|
204
257
|
element: element2,
|
|
258
|
+
rootNode,
|
|
259
|
+
skipRender: false,
|
|
205
260
|
position,
|
|
206
261
|
visible,
|
|
207
262
|
trigger,
|
|
263
|
+
anchorNameValue: 'none',
|
|
208
264
|
scrollableAncestors,
|
|
209
|
-
issues: signal([issue2, issue3])
|
|
210
|
-
}
|
|
265
|
+
issues: signal([issue2, issue3]),
|
|
266
|
+
},
|
|
211
267
|
]);
|
|
212
|
-
updateElementsWithIssues(
|
|
268
|
+
updateElementsWithIssues({
|
|
269
|
+
extendedElementsWithIssues,
|
|
270
|
+
scanContext,
|
|
271
|
+
violations: [violation1, violation2],
|
|
272
|
+
win,
|
|
273
|
+
name: 'accented',
|
|
274
|
+
});
|
|
213
275
|
assert.equal(extendedElementsWithIssues.value.length, 2);
|
|
214
276
|
assert.equal(extendedElementsWithIssues.value[0]?.element, element1);
|
|
215
277
|
assert.equal(extendedElementsWithIssues.value[0]?.issues.value.length, 1);
|
|
@@ -222,14 +284,23 @@ suite('updateElementsWithIssues', () => {
|
|
|
222
284
|
{
|
|
223
285
|
id: 1,
|
|
224
286
|
element: element1,
|
|
287
|
+
rootNode,
|
|
288
|
+
skipRender: false,
|
|
225
289
|
position,
|
|
226
290
|
visible,
|
|
227
291
|
trigger,
|
|
292
|
+
anchorNameValue: 'none',
|
|
228
293
|
scrollableAncestors,
|
|
229
|
-
issues: signal([issue1])
|
|
230
|
-
}
|
|
294
|
+
issues: signal([issue1]),
|
|
295
|
+
},
|
|
231
296
|
]);
|
|
232
|
-
updateElementsWithIssues(
|
|
297
|
+
updateElementsWithIssues({
|
|
298
|
+
extendedElementsWithIssues,
|
|
299
|
+
scanContext,
|
|
300
|
+
violations: [violation1, violation2],
|
|
301
|
+
win,
|
|
302
|
+
name: 'accented',
|
|
303
|
+
});
|
|
233
304
|
assert.equal(extendedElementsWithIssues.value.length, 2);
|
|
234
305
|
assert.equal(extendedElementsWithIssues.value[0]?.element, element1);
|
|
235
306
|
assert.equal(extendedElementsWithIssues.value[0]?.issues.value.length, 1);
|
|
@@ -242,14 +313,23 @@ suite('updateElementsWithIssues', () => {
|
|
|
242
313
|
{
|
|
243
314
|
id: 1,
|
|
244
315
|
element: element1,
|
|
316
|
+
rootNode,
|
|
317
|
+
skipRender: false,
|
|
245
318
|
position,
|
|
246
319
|
visible,
|
|
247
320
|
trigger,
|
|
321
|
+
anchorNameValue: 'none',
|
|
248
322
|
scrollableAncestors,
|
|
249
|
-
issues: signal([issue1])
|
|
250
|
-
}
|
|
323
|
+
issues: signal([issue1]),
|
|
324
|
+
},
|
|
251
325
|
]);
|
|
252
|
-
updateElementsWithIssues(
|
|
326
|
+
updateElementsWithIssues({
|
|
327
|
+
extendedElementsWithIssues,
|
|
328
|
+
scanContext,
|
|
329
|
+
violations: [violation1, violation4],
|
|
330
|
+
win,
|
|
331
|
+
name: 'accented',
|
|
332
|
+
});
|
|
253
333
|
assert.equal(extendedElementsWithIssues.value.length, 1);
|
|
254
334
|
assert.equal(extendedElementsWithIssues.value[0]?.element, element1);
|
|
255
335
|
});
|
|
@@ -259,23 +339,35 @@ suite('updateElementsWithIssues', () => {
|
|
|
259
339
|
{
|
|
260
340
|
id: 1,
|
|
261
341
|
element: element1,
|
|
342
|
+
rootNode,
|
|
343
|
+
skipRender: false,
|
|
262
344
|
position,
|
|
263
345
|
visible,
|
|
264
346
|
trigger,
|
|
347
|
+
anchorNameValue: 'none',
|
|
265
348
|
scrollableAncestors,
|
|
266
|
-
issues: signal([issue1])
|
|
349
|
+
issues: signal([issue1]),
|
|
267
350
|
},
|
|
268
351
|
{
|
|
269
352
|
id: 2,
|
|
270
353
|
element: element2,
|
|
354
|
+
rootNode,
|
|
355
|
+
skipRender: false,
|
|
271
356
|
position,
|
|
272
357
|
visible,
|
|
273
358
|
trigger,
|
|
359
|
+
anchorNameValue: 'none',
|
|
274
360
|
scrollableAncestors,
|
|
275
|
-
issues: signal([issue2])
|
|
276
|
-
}
|
|
361
|
+
issues: signal([issue2]),
|
|
362
|
+
},
|
|
277
363
|
]);
|
|
278
|
-
updateElementsWithIssues(
|
|
364
|
+
updateElementsWithIssues({
|
|
365
|
+
extendedElementsWithIssues,
|
|
366
|
+
scanContext,
|
|
367
|
+
violations: [violation1],
|
|
368
|
+
win,
|
|
369
|
+
name: 'accented',
|
|
370
|
+
});
|
|
279
371
|
assert.equal(extendedElementsWithIssues.value.length, 1);
|
|
280
372
|
assert.equal(extendedElementsWithIssues.value[0]?.element, element1);
|
|
281
373
|
assert.equal(extendedElementsWithIssues.value[0]?.issues.value.length, 1);
|