@surea11y/core 1.1.2 → 1.3.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/CHANGELOG.md +48 -4
- package/LICENSE +373 -21
- package/README.md +70 -1
- package/bin/core.js +240 -11
- package/docs/API_STABILITY.md +61 -0
- package/docs/BASELINE.md +66 -0
- package/docs/BINDING_AUTHORS_GUIDE.md +9 -9
- package/docs/CI_INTEGRATIONS.md +103 -0
- package/docs/CLI.md +80 -1
- package/docs/ENGINE_OPTIONS.md +4 -0
- package/docs/INTEGRATION.md +19 -1
- package/docs/OUTPUT_SCHEMA.md +2 -2
- package/docs/REPORT.md +33 -0
- package/docs/RULE_AUTHORING.md +31 -0
- package/docs/RULE_CATALOG.md +1 -1
- package/docs/SARIF.md +59 -0
- package/package.json +15 -4
- package/src/baseline.js +0 -0
- package/src/catalogs/composites.wcag.js +414 -450
- package/src/checks/automatic/area-alt-present.js +59 -25
- package/src/checks/automatic/aria-allowed-attr.js +193 -33
- package/src/checks/automatic/aria-allowed-role.js +21 -7
- package/src/checks/automatic/aria-braille-equivalent.js +32 -10
- package/src/checks/automatic/aria-conditional-attr.js +24 -7
- package/src/checks/automatic/aria-deprecated-role.js +22 -8
- package/src/checks/automatic/aria-hidden-body.js +50 -19
- package/src/checks/automatic/aria-hidden-focus.js +408 -53
- package/src/checks/automatic/aria-prohibited-attr.js +296 -22
- package/src/checks/automatic/aria-prohibited-children.js +125 -29
- package/src/checks/automatic/aria-required-attr.js +23 -8
- package/src/checks/automatic/aria-required-children.js +37 -14
- package/src/checks/automatic/aria-required-parent.js +48 -14
- package/src/checks/automatic/aria-role-name-present.js +47 -21
- package/src/checks/automatic/aria-roles-valid.js +22 -12
- package/src/checks/automatic/aria-valid-attr-value.js +28 -7
- package/src/checks/automatic/aria-valid-attr.js +17 -5
- package/src/checks/automatic/autocomplete-valid.js +74 -16
- package/src/checks/automatic/avoid-inline-spacing.js +20 -6
- package/src/checks/automatic/binary-control-name-present.js +60 -50
- package/src/checks/automatic/button-name-present.js +48 -18
- package/src/checks/automatic/bypass-blocks-present.js +50 -25
- package/src/checks/automatic/canvas-text-alternative-present.js +57 -26
- package/src/checks/automatic/combobox-name-present.js +38 -45
- package/src/checks/automatic/contrast-computable.js +361 -341
- package/src/checks/automatic/contrast-enhanced.js +487 -466
- package/src/checks/automatic/contrast-minimum.js +486 -465
- package/src/checks/automatic/css-orientation-lock.js +39 -9
- package/src/checks/automatic/definition-list-children-valid.js +40 -19
- package/src/checks/automatic/deprecated-elements-not-used.js +21 -7
- package/src/checks/automatic/dialog-name-present.js +37 -75
- package/src/checks/automatic/dlitem-parent-valid.js +23 -8
- package/src/checks/automatic/duplicate-id-aria.js +24 -6
- package/src/checks/automatic/embed-text-alternative-present.js +86 -35
- package/src/checks/automatic/form-control-programmatic-label-present.js +79 -196
- package/src/checks/automatic/form-control-single-label.js +47 -10
- package/src/checks/automatic/html-xml-lang-mismatch.js +43 -19
- package/src/checks/automatic/iframe-focusable-content.js +26 -11
- package/src/checks/automatic/iframe-name-present.js +31 -9
- package/src/checks/automatic/iframe-title-unique.js +29 -8
- package/src/checks/automatic/img-alt-present.js +47 -43
- package/src/checks/automatic/input-image-alt-present.js +143 -112
- package/src/checks/automatic/label-in-name.js +50 -22
- package/src/checks/automatic/language-page-present.js +117 -109
- package/src/checks/automatic/link-in-text-block.js +59 -19
- package/src/checks/automatic/link-name-present.js +45 -14
- package/src/checks/automatic/list-children-valid.js +26 -9
- package/src/checks/automatic/listbox-name-present.js +39 -19
- package/src/checks/automatic/listitem-parent-valid.js +18 -6
- package/src/checks/automatic/menuitem-name-present.js +39 -61
- package/src/checks/automatic/meta-refresh-no-exceptions.js +37 -8
- package/src/checks/automatic/meta-refresh-timing-absent.js +28 -6
- package/src/checks/automatic/meta-viewport-zoom-enabled.js +32 -7
- package/src/checks/automatic/meter-name-present.js +36 -33
- package/src/checks/automatic/nested-interactive-controls-absent.js +31 -10
- package/src/checks/automatic/object-text-alternative-present.js +91 -39
- package/src/checks/automatic/option-name-present.js +38 -21
- package/src/checks/automatic/page-title-present.js +26 -7
- package/src/checks/automatic/progressbar-name-present.js +41 -34
- package/src/checks/automatic/role-img-alt-present.js +209 -157
- package/src/checks/automatic/searchbox-name-present.js +39 -19
- package/src/checks/automatic/server-side-image-map-absent.js +23 -8
- package/src/checks/automatic/slider-name-present.js +40 -47
- package/src/checks/automatic/spinbutton-name-present.js +39 -19
- package/src/checks/automatic/summary-name-present.js +37 -17
- package/src/checks/automatic/svg-image-text-alternative-present.js +114 -47
- package/src/checks/automatic/svg-text-alternative-present.js +246 -226
- package/src/checks/automatic/tab-name-present.js +37 -60
- package/src/checks/automatic/table-headers-attr-valid.js +24 -8
- package/src/checks/automatic/table-th-has-data-cells.js +22 -8
- package/src/checks/automatic/target-size-minimum.js +118 -48
- package/src/checks/automatic/td-has-header.js +29 -11
- package/src/checks/automatic/textbox-name-present.js +39 -19
- package/src/checks/automatic/tooltip-name-present.js +37 -18
- package/src/checks/automatic/treeitem-name-present.js +38 -21
- package/src/checks/automatic/valid-lang.js +20 -6
- package/src/checks/automatic/video-poster-text-alternative-present.js +79 -36
- package/src/checks/manual/accesskeys-manual.js +14 -5
- package/src/checks/manual/area-alt-decorative-manual.js +192 -193
- package/src/checks/manual/area-alt-quality-manual.js +182 -141
- package/src/checks/manual/aria-checked-state-mismatch-manual.js +34 -11
- package/src/checks/manual/aria-text-manual.js +14 -6
- package/src/checks/manual/canvas-text-alternative-quality-manual.js +149 -114
- package/src/checks/manual/css-hidden-focus.js +196 -165
- package/src/checks/manual/embed-text-alternative-quality-manual.js +171 -160
- package/src/checks/manual/empty-heading-manual.js +24 -12
- package/src/checks/manual/empty-table-header-manual.js +21 -14
- package/src/checks/manual/focus-order-semantics-manual.js +45 -10
- package/src/checks/manual/form-control-programmatic-label-quality-manual.js +207 -246
- package/src/checks/manual/heading-order-manual.js +22 -12
- package/src/checks/manual/identical-links-same-purpose-manual.js +34 -12
- package/src/checks/manual/image-redundant-alt-manual.js +17 -7
- package/src/checks/manual/img-alt-decorative-manual.js +131 -96
- package/src/checks/manual/img-alt-quality-manual.js +176 -127
- package/src/checks/manual/input-image-alt-decorative-manual.js +125 -92
- package/src/checks/manual/input-image-alt-quality-manual.js +125 -92
- package/src/checks/manual/label-title-only-manual.js +14 -5
- package/src/checks/manual/landmark-banner-is-top-level-manual.js +95 -29
- package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +82 -29
- package/src/checks/manual/landmark-main-is-top-level-manual.js +64 -23
- package/src/checks/manual/landmark-no-duplicate-banner-manual.js +54 -23
- package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +54 -23
- package/src/checks/manual/landmark-no-duplicate-main-manual.js +17 -8
- package/src/checks/manual/landmark-one-main-manual.js +35 -21
- package/src/checks/manual/landmark-unique-manual.js +73 -45
- package/src/checks/manual/link-name-quality-manual.js +43 -12
- package/src/checks/manual/media-transcript-present-manual.js +35 -22
- package/src/checks/manual/meta-viewport-large-manual.js +25 -6
- package/src/checks/manual/mouse-only-event-handlers-manual.js +38 -11
- package/src/checks/manual/no-autoplay-audio-manual.js +20 -6
- package/src/checks/manual/object-text-alternative-quality-manual.js +175 -154
- package/src/checks/manual/p-as-heading-manual.js +22 -7
- package/src/checks/manual/page-has-heading-one-manual.js +40 -23
- package/src/checks/manual/page-title-patterns-manual.js +87 -51
- package/src/checks/manual/presentation-role-conflict-manual.js +59 -19
- package/src/checks/manual/region-manual.js +275 -62
- package/src/checks/manual/scope-attr-valid-manual.js +11 -9
- package/src/checks/manual/scrollable-region-focusable-manual.js +37 -11
- package/src/checks/manual/skip-link-manual.js +35 -12
- package/src/checks/manual/svg-text-alternative-quality-manual.js +206 -165
- package/src/checks/manual/tabindex-manual.js +11 -9
- package/src/checks/manual/table-duplicate-name-manual.js +17 -7
- package/src/checks/manual/table-fake-caption-manual.js +24 -7
- package/src/checks/manual/video-caption-manual.js +15 -4
- package/src/checks/manual-review.js +56 -12
- package/src/core/aria-helpers.js +1128 -823
- package/src/core/contrast-helpers.js +1217 -1062
- package/src/core/dom-helpers.js +4176 -3886
- package/src/core/dom-runner.js +720 -596
- package/src/core/frame-messaging.js +189 -138
- package/src/core/frame-scan.js +94 -82
- package/src/core/rollup-composites.js +94 -102
- package/src/core/rule-meta.js +71 -35
- package/src/core.js +37939 -29121
- package/src/i18n/en.js +1194 -887
- package/src/i18n/fr.js +1136 -793
- package/src/policy/contracts.js +13 -13
- package/src/policy/resolvePolicy.js +48 -44
- package/src/report.js +502 -0
- package/src/sarif.js +175 -0
- package/surea11y.browser.js +36042 -0
|
@@ -45,7 +45,13 @@ const meta = {
|
|
|
45
45
|
tags: ['wcag2a', 'wcag131', 'structure', 'atomic', 'automatic'],
|
|
46
46
|
wcagSc: ['1.3.1'],
|
|
47
47
|
normativeMappings: [
|
|
48
|
-
{
|
|
48
|
+
{
|
|
49
|
+
standard: 'WCAG',
|
|
50
|
+
version: '2.2',
|
|
51
|
+
requirement: '1.3.1',
|
|
52
|
+
title: 'Info and Relationships',
|
|
53
|
+
conformanceLevel: 'A'
|
|
54
|
+
}
|
|
49
55
|
],
|
|
50
56
|
defaultSeverity: 'serious',
|
|
51
57
|
category: 'perceivable',
|
|
@@ -55,11 +61,13 @@ const meta = {
|
|
|
55
61
|
};
|
|
56
62
|
|
|
57
63
|
function runInPage(ctx) {
|
|
58
|
-
const {
|
|
64
|
+
const { helpers, rule } = ctx;
|
|
59
65
|
|
|
60
66
|
const MIN_SIZE = 4;
|
|
61
67
|
|
|
62
|
-
function trim(v) {
|
|
68
|
+
function trim(v) {
|
|
69
|
+
return (v == null ? '' : String(v)).trim();
|
|
70
|
+
}
|
|
63
71
|
|
|
64
72
|
const tables = helpers.queryAllSmart ? helpers.queryAllSmart('table') : helpers.queryAll('table');
|
|
65
73
|
|
|
@@ -76,11 +84,13 @@ function runInPage(ctx) {
|
|
|
76
84
|
const maxCols = rowCells.reduce((m, cells) => Math.max(m, cells.length), 0);
|
|
77
85
|
if (maxCols < MIN_SIZE) continue;
|
|
78
86
|
|
|
79
|
-
const hasSpan = rowCells.some((cells) =>
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
87
|
+
const hasSpan = rowCells.some((cells) =>
|
|
88
|
+
cells.some((c) => {
|
|
89
|
+
const cs = Number.parseInt(c.getAttribute('colspan') || '1', 10);
|
|
90
|
+
const rs = Number.parseInt(c.getAttribute('rowspan') || '1', 10);
|
|
91
|
+
return (Number.isFinite(cs) && cs > 1) || (Number.isFinite(rs) && rs > 1);
|
|
92
|
+
})
|
|
93
|
+
);
|
|
84
94
|
if (hasSpan) continue;
|
|
85
95
|
|
|
86
96
|
applicableCount += 1;
|
|
@@ -118,12 +128,15 @@ function runInPage(ctx) {
|
|
|
118
128
|
if (hasRowHeaderBefore(r, c)) continue;
|
|
119
129
|
|
|
120
130
|
const stableSelector = helpers.buildSelector ? helpers.buildSelector(cell) : 'html';
|
|
121
|
-
const html = helpers.getOuterHtmlSnippet
|
|
131
|
+
const html = helpers.getOuterHtmlSnippet
|
|
132
|
+
? helpers.getOuterHtmlSnippet(cell)
|
|
133
|
+
: cell.outerHTML || '';
|
|
122
134
|
|
|
123
135
|
occurrences.push({
|
|
124
136
|
selector: stableSelector,
|
|
125
137
|
html,
|
|
126
|
-
summary:
|
|
138
|
+
summary:
|
|
139
|
+
'This data cell has no associated header (no headers attribute, no column <th> above it, no row <th> to its left).',
|
|
127
140
|
hint: 'Add a headers attribute referencing the relevant <th> id(s), or restructure the table so this cell has an implicit row/column header.',
|
|
128
141
|
i18n: {
|
|
129
142
|
summaryKey: 'tdHasHeader_summary_fail',
|
|
@@ -142,7 +155,12 @@ function runInPage(ctx) {
|
|
|
142
155
|
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
143
156
|
}
|
|
144
157
|
if (occurrences.length) {
|
|
145
|
-
return {
|
|
158
|
+
return {
|
|
159
|
+
ruleId: rule.ruleId,
|
|
160
|
+
outcome: 'fail',
|
|
161
|
+
severity: rule.defaultSeverity || 'serious',
|
|
162
|
+
occurrences
|
|
163
|
+
};
|
|
146
164
|
}
|
|
147
165
|
return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
|
|
148
166
|
}
|
|
@@ -10,10 +10,16 @@ const meta = {
|
|
|
10
10
|
descriptionKey: 'textboxNamePresent_description'
|
|
11
11
|
},
|
|
12
12
|
helpUrl: null,
|
|
13
|
-
tags: [
|
|
13
|
+
tags: ['wcag2a', 'wcag412', 'forms', 'atomic', 'automatic', 'name', 'textbox'],
|
|
14
14
|
wcagSc: ['4.1.2'],
|
|
15
15
|
normativeMappings: [
|
|
16
|
-
{
|
|
16
|
+
{
|
|
17
|
+
standard: 'WCAG',
|
|
18
|
+
version: '2.2',
|
|
19
|
+
requirement: '4.1.2',
|
|
20
|
+
title: 'Name, Role, Value',
|
|
21
|
+
conformanceLevel: 'A'
|
|
22
|
+
}
|
|
17
23
|
],
|
|
18
24
|
defaultSeverity: 'serious',
|
|
19
25
|
category: 'robust',
|
|
@@ -24,20 +30,22 @@ const meta = {
|
|
|
24
30
|
|
|
25
31
|
function runInPage(ctx) {
|
|
26
32
|
const { document, helpers, rule } = ctx;
|
|
27
|
-
const getEligibilityInfo =
|
|
28
|
-
|
|
29
|
-
: null;
|
|
30
|
-
|
|
33
|
+
const getEligibilityInfo =
|
|
34
|
+
helpers && typeof helpers.getEligibilityInfo === 'function' ? helpers.getEligibilityInfo : null;
|
|
31
35
|
|
|
32
36
|
function normalizeWs(s) {
|
|
33
|
-
return String(s || '')
|
|
37
|
+
return String(s || '')
|
|
38
|
+
.replace(/\s+/g, ' ')
|
|
39
|
+
.trim();
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
function getAttr(el, name) {
|
|
37
43
|
try {
|
|
38
44
|
if (!el || !el.getAttribute) return '';
|
|
39
45
|
return normalizeWs(el.getAttribute(name));
|
|
40
|
-
} catch {
|
|
46
|
+
} catch {
|
|
47
|
+
return '';
|
|
48
|
+
}
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
function getConservativeSubtreeText(document, container) {
|
|
@@ -52,7 +60,7 @@ function runInPage(ctx) {
|
|
|
52
60
|
const info = helpers.getContentNameInfo(container, ctx);
|
|
53
61
|
return info && info.present ? info.value : '';
|
|
54
62
|
}
|
|
55
|
-
const t =
|
|
63
|
+
const t = container && container.textContent ? String(container.textContent) : '';
|
|
56
64
|
return t.replace(/\s+/g, ' ').trim();
|
|
57
65
|
}
|
|
58
66
|
|
|
@@ -100,7 +108,8 @@ function runInPage(ctx) {
|
|
|
100
108
|
}
|
|
101
109
|
|
|
102
110
|
function isEligibleAcc(helpers, el, ctx) {
|
|
103
|
-
const fn =
|
|
111
|
+
const fn =
|
|
112
|
+
helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
|
|
104
113
|
if (!fn) return true;
|
|
105
114
|
try {
|
|
106
115
|
const r = fn(el, ctx);
|
|
@@ -126,12 +135,13 @@ function runInPage(ctx) {
|
|
|
126
135
|
return map;
|
|
127
136
|
}
|
|
128
137
|
|
|
129
|
-
|
|
130
138
|
const occurrences = [];
|
|
131
139
|
let applicableCount = 0;
|
|
132
140
|
|
|
133
|
-
const selector =
|
|
134
|
-
const nodes = helpers.queryAllSmart
|
|
141
|
+
const selector = '[role="textbox"]';
|
|
142
|
+
const nodes = helpers.queryAllSmart
|
|
143
|
+
? helpers.queryAllSmart(selector)
|
|
144
|
+
: helpers.queryAll(selector);
|
|
135
145
|
|
|
136
146
|
// Precompute label[for] map for textbox elements that are labelable
|
|
137
147
|
// native form controls (e.g. <input role="textbox">).
|
|
@@ -200,16 +210,22 @@ function runInPage(ctx) {
|
|
|
200
210
|
if (res.ok) continue;
|
|
201
211
|
|
|
202
212
|
const eligInfo = getEligibilityInfo
|
|
203
|
-
|
|
204
|
-
|
|
213
|
+
? (() => {
|
|
214
|
+
try {
|
|
215
|
+
return getEligibilityInfo(el, ctx, { targetSet: 'acc' });
|
|
216
|
+
} catch {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
})()
|
|
220
|
+
: null;
|
|
205
221
|
const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
|
|
206
|
-
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) :
|
|
222
|
+
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
|
|
207
223
|
|
|
208
224
|
occurrences.push({
|
|
209
225
|
selector: stableSelector,
|
|
210
226
|
html,
|
|
211
227
|
summary: 'This element has no accessible name.',
|
|
212
|
-
hint:
|
|
228
|
+
hint: "Provide aria-label, aria-labelledby, or a title attribute — visible text content is not exposed as this textbox's accessible name.",
|
|
213
229
|
i18n: {
|
|
214
230
|
summaryKey: 'textboxNamePresent_summary_fail',
|
|
215
231
|
hintKey: 'textboxNamePresent_hint_fail',
|
|
@@ -222,12 +238,16 @@ function runInPage(ctx) {
|
|
|
222
238
|
});
|
|
223
239
|
}
|
|
224
240
|
|
|
225
|
-
|
|
226
241
|
if (applicableCount === 0) {
|
|
227
242
|
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
228
243
|
}
|
|
229
244
|
if (occurrences.length) {
|
|
230
|
-
return {
|
|
245
|
+
return {
|
|
246
|
+
ruleId: rule.ruleId,
|
|
247
|
+
outcome: 'fail',
|
|
248
|
+
severity: rule.defaultSeverity || 'minor',
|
|
249
|
+
occurrences
|
|
250
|
+
};
|
|
231
251
|
}
|
|
232
252
|
return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
|
|
233
253
|
}
|
|
@@ -13,7 +13,13 @@ const meta = {
|
|
|
13
13
|
tags: ['wcag2a', 'wcag412', 'navigation', 'atomic', 'automatic', 'name'],
|
|
14
14
|
wcagSc: ['4.1.2'],
|
|
15
15
|
normativeMappings: [
|
|
16
|
-
{
|
|
16
|
+
{
|
|
17
|
+
standard: 'WCAG',
|
|
18
|
+
version: '2.2',
|
|
19
|
+
requirement: '4.1.2',
|
|
20
|
+
title: 'Name, Role, Value',
|
|
21
|
+
conformanceLevel: 'A'
|
|
22
|
+
}
|
|
17
23
|
],
|
|
18
24
|
defaultSeverity: 'serious',
|
|
19
25
|
category: 'robust',
|
|
@@ -24,20 +30,22 @@ const meta = {
|
|
|
24
30
|
|
|
25
31
|
function runInPage(ctx) {
|
|
26
32
|
const { document, helpers, rule } = ctx;
|
|
27
|
-
const getEligibilityInfo =
|
|
28
|
-
|
|
29
|
-
: null;
|
|
30
|
-
|
|
33
|
+
const getEligibilityInfo =
|
|
34
|
+
helpers && typeof helpers.getEligibilityInfo === 'function' ? helpers.getEligibilityInfo : null;
|
|
31
35
|
|
|
32
36
|
function normalizeWs(s) {
|
|
33
|
-
return String(s || '')
|
|
37
|
+
return String(s || '')
|
|
38
|
+
.replace(/\s+/g, ' ')
|
|
39
|
+
.trim();
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
function getAttr(el, name) {
|
|
37
43
|
try {
|
|
38
44
|
if (!el || !el.getAttribute) return '';
|
|
39
45
|
return normalizeWs(el.getAttribute(name));
|
|
40
|
-
} catch {
|
|
46
|
+
} catch {
|
|
47
|
+
return '';
|
|
48
|
+
}
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
function getConservativeSubtreeText(document, container) {
|
|
@@ -52,7 +60,7 @@ function runInPage(ctx) {
|
|
|
52
60
|
const info = helpers.getContentNameInfo(container, ctx);
|
|
53
61
|
return info && info.present ? info.value : '';
|
|
54
62
|
}
|
|
55
|
-
const t =
|
|
63
|
+
const t = container && container.textContent ? String(container.textContent) : '';
|
|
56
64
|
return t.replace(/\s+/g, ' ').trim();
|
|
57
65
|
}
|
|
58
66
|
|
|
@@ -74,7 +82,8 @@ function runInPage(ctx) {
|
|
|
74
82
|
}
|
|
75
83
|
|
|
76
84
|
function isEligibleAcc(helpers, el, ctx) {
|
|
77
|
-
const fn =
|
|
85
|
+
const fn =
|
|
86
|
+
helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
|
|
78
87
|
if (!fn) return true;
|
|
79
88
|
try {
|
|
80
89
|
const r = fn(el, ctx);
|
|
@@ -85,13 +94,13 @@ function runInPage(ctx) {
|
|
|
85
94
|
}
|
|
86
95
|
}
|
|
87
96
|
|
|
88
|
-
|
|
89
97
|
const occurrences = [];
|
|
90
98
|
let applicableCount = 0;
|
|
91
99
|
|
|
92
100
|
const selector = '[role="tooltip"]';
|
|
93
|
-
const nodes = helpers.queryAllSmart
|
|
94
|
-
|
|
101
|
+
const nodes = helpers.queryAllSmart
|
|
102
|
+
? helpers.queryAllSmart(selector)
|
|
103
|
+
: helpers.queryAll(selector);
|
|
95
104
|
|
|
96
105
|
function evaluate(el) {
|
|
97
106
|
const ariaLabel = getAttr(el, 'aria-label');
|
|
@@ -122,10 +131,16 @@ function runInPage(ctx) {
|
|
|
122
131
|
if (res.ok) continue;
|
|
123
132
|
|
|
124
133
|
const eligInfo = getEligibilityInfo
|
|
125
|
-
|
|
126
|
-
|
|
134
|
+
? (() => {
|
|
135
|
+
try {
|
|
136
|
+
return getEligibilityInfo(el, ctx, { targetSet: 'acc' });
|
|
137
|
+
} catch {
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
})()
|
|
141
|
+
: null;
|
|
127
142
|
const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
|
|
128
|
-
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) :
|
|
143
|
+
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
|
|
129
144
|
|
|
130
145
|
occurrences.push({
|
|
131
146
|
selector: stableSelector,
|
|
@@ -144,14 +159,18 @@ function runInPage(ctx) {
|
|
|
144
159
|
});
|
|
145
160
|
}
|
|
146
161
|
|
|
147
|
-
|
|
148
162
|
if (applicableCount === 0) {
|
|
149
163
|
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
150
164
|
}
|
|
151
165
|
if (occurrences.length) {
|
|
152
|
-
return {
|
|
166
|
+
return {
|
|
167
|
+
ruleId: rule.ruleId,
|
|
168
|
+
outcome: 'fail',
|
|
169
|
+
severity: rule.defaultSeverity || 'minor',
|
|
170
|
+
occurrences
|
|
171
|
+
};
|
|
153
172
|
}
|
|
154
173
|
return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
|
|
155
174
|
}
|
|
156
175
|
|
|
157
|
-
module.exports = { id, meta, runInPage };
|
|
176
|
+
module.exports = { id, meta, runInPage };
|
|
@@ -10,10 +10,16 @@ const meta = {
|
|
|
10
10
|
descriptionKey: 'treeitemNamePresent_description'
|
|
11
11
|
},
|
|
12
12
|
helpUrl: null,
|
|
13
|
-
tags: [
|
|
13
|
+
tags: ['wcag2a', 'wcag412', 'navigation', 'atomic', 'automatic', 'name', 'treeitem'],
|
|
14
14
|
wcagSc: ['4.1.2'],
|
|
15
15
|
normativeMappings: [
|
|
16
|
-
{
|
|
16
|
+
{
|
|
17
|
+
standard: 'WCAG',
|
|
18
|
+
version: '2.2',
|
|
19
|
+
requirement: '4.1.2',
|
|
20
|
+
title: 'Name, Role, Value',
|
|
21
|
+
conformanceLevel: 'A'
|
|
22
|
+
}
|
|
17
23
|
],
|
|
18
24
|
defaultSeverity: 'serious',
|
|
19
25
|
category: 'robust',
|
|
@@ -24,20 +30,22 @@ const meta = {
|
|
|
24
30
|
|
|
25
31
|
function runInPage(ctx) {
|
|
26
32
|
const { document, helpers, rule } = ctx;
|
|
27
|
-
const getEligibilityInfo =
|
|
28
|
-
|
|
29
|
-
: null;
|
|
30
|
-
|
|
33
|
+
const getEligibilityInfo =
|
|
34
|
+
helpers && typeof helpers.getEligibilityInfo === 'function' ? helpers.getEligibilityInfo : null;
|
|
31
35
|
|
|
32
36
|
function normalizeWs(s) {
|
|
33
|
-
return String(s || '')
|
|
37
|
+
return String(s || '')
|
|
38
|
+
.replace(/\s+/g, ' ')
|
|
39
|
+
.trim();
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
function getAttr(el, name) {
|
|
37
43
|
try {
|
|
38
44
|
if (!el || !el.getAttribute) return '';
|
|
39
45
|
return normalizeWs(el.getAttribute(name));
|
|
40
|
-
} catch {
|
|
46
|
+
} catch {
|
|
47
|
+
return '';
|
|
48
|
+
}
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
function getConservativeSubtreeText(document, container) {
|
|
@@ -52,7 +60,7 @@ function runInPage(ctx) {
|
|
|
52
60
|
const info = helpers.getContentNameInfo(container, ctx);
|
|
53
61
|
return info && info.present ? info.value : '';
|
|
54
62
|
}
|
|
55
|
-
const t =
|
|
63
|
+
const t = container && container.textContent ? String(container.textContent) : '';
|
|
56
64
|
return t.replace(/\s+/g, ' ').trim();
|
|
57
65
|
}
|
|
58
66
|
|
|
@@ -74,7 +82,8 @@ function runInPage(ctx) {
|
|
|
74
82
|
}
|
|
75
83
|
|
|
76
84
|
function isEligibleAcc(helpers, el, ctx) {
|
|
77
|
-
const fn =
|
|
85
|
+
const fn =
|
|
86
|
+
helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
|
|
78
87
|
if (!fn) return true;
|
|
79
88
|
try {
|
|
80
89
|
const r = fn(el, ctx);
|
|
@@ -85,13 +94,13 @@ function runInPage(ctx) {
|
|
|
85
94
|
}
|
|
86
95
|
}
|
|
87
96
|
|
|
88
|
-
|
|
89
97
|
const occurrences = [];
|
|
90
98
|
let applicableCount = 0;
|
|
91
99
|
|
|
92
|
-
const selector =
|
|
93
|
-
const nodes = helpers.queryAllSmart
|
|
94
|
-
|
|
100
|
+
const selector = '[role="treeitem"]';
|
|
101
|
+
const nodes = helpers.queryAllSmart
|
|
102
|
+
? helpers.queryAllSmart(selector)
|
|
103
|
+
: helpers.queryAll(selector);
|
|
95
104
|
|
|
96
105
|
function hasName(el) {
|
|
97
106
|
const ariaLabel = getAttr(el, 'aria-label');
|
|
@@ -103,11 +112,9 @@ function runInPage(ctx) {
|
|
|
103
112
|
const title = getAttr(el, 'title');
|
|
104
113
|
if (title) return { ok: true, method: 'title' };
|
|
105
114
|
|
|
106
|
-
|
|
107
115
|
const t = getConservativeSubtreeText(document, el);
|
|
108
116
|
if (t) return { ok: true, method: 'content' };
|
|
109
117
|
|
|
110
|
-
|
|
111
118
|
return { ok: false, method: 'none' };
|
|
112
119
|
}
|
|
113
120
|
|
|
@@ -121,10 +128,16 @@ function runInPage(ctx) {
|
|
|
121
128
|
if (res.ok) continue;
|
|
122
129
|
|
|
123
130
|
const eligInfo = getEligibilityInfo
|
|
124
|
-
|
|
125
|
-
|
|
131
|
+
? (() => {
|
|
132
|
+
try {
|
|
133
|
+
return getEligibilityInfo(el, ctx, { targetSet: 'acc' });
|
|
134
|
+
} catch {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
})()
|
|
138
|
+
: null;
|
|
126
139
|
const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
|
|
127
|
-
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) :
|
|
140
|
+
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
|
|
128
141
|
|
|
129
142
|
occurrences.push({
|
|
130
143
|
selector: stableSelector,
|
|
@@ -143,12 +156,16 @@ function runInPage(ctx) {
|
|
|
143
156
|
});
|
|
144
157
|
}
|
|
145
158
|
|
|
146
|
-
|
|
147
159
|
if (applicableCount === 0) {
|
|
148
160
|
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
149
161
|
}
|
|
150
162
|
if (occurrences.length) {
|
|
151
|
-
return {
|
|
163
|
+
return {
|
|
164
|
+
ruleId: rule.ruleId,
|
|
165
|
+
outcome: 'fail',
|
|
166
|
+
severity: rule.defaultSeverity || 'minor',
|
|
167
|
+
occurrences
|
|
168
|
+
};
|
|
152
169
|
}
|
|
153
170
|
return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
|
|
154
171
|
}
|
|
@@ -28,7 +28,8 @@ const id = 'valid-lang';
|
|
|
28
28
|
|
|
29
29
|
const meta = {
|
|
30
30
|
title: 'Element lang attribute must be syntactically valid',
|
|
31
|
-
description:
|
|
31
|
+
description:
|
|
32
|
+
'Checks that any element (other than the root <html>) with a non-empty lang attribute uses a syntactically valid language tag.',
|
|
32
33
|
i18n: {
|
|
33
34
|
titleKey: 'validLang_title',
|
|
34
35
|
descriptionKey: 'validLang_description'
|
|
@@ -37,7 +38,13 @@ const meta = {
|
|
|
37
38
|
tags: ['wcag2aa', 'wcag312', 'structure', 'language', 'atomic', 'automatic'],
|
|
38
39
|
wcagSc: ['3.1.2'],
|
|
39
40
|
normativeMappings: [
|
|
40
|
-
{
|
|
41
|
+
{
|
|
42
|
+
standard: 'WCAG',
|
|
43
|
+
version: '2.2',
|
|
44
|
+
requirement: '3.1.2',
|
|
45
|
+
title: 'Language of Parts',
|
|
46
|
+
conformanceLevel: 'AA'
|
|
47
|
+
}
|
|
41
48
|
],
|
|
42
49
|
defaultSeverity: 'moderate',
|
|
43
50
|
category: 'understandable',
|
|
@@ -47,11 +54,13 @@ const meta = {
|
|
|
47
54
|
};
|
|
48
55
|
|
|
49
56
|
function runInPage(ctx) {
|
|
50
|
-
const {
|
|
57
|
+
const { helpers, rule } = ctx;
|
|
51
58
|
|
|
52
59
|
const BCP47_RE = /^[a-zA-Z]{2,3}(-[a-zA-Z0-9]{2,8})*$/;
|
|
53
60
|
|
|
54
|
-
const nodes = helpers.queryAllSmart
|
|
61
|
+
const nodes = helpers.queryAllSmart
|
|
62
|
+
? helpers.queryAllSmart('[lang]')
|
|
63
|
+
: helpers.queryAll('[lang]');
|
|
55
64
|
|
|
56
65
|
const occurrences = [];
|
|
57
66
|
let applicableCount = 0;
|
|
@@ -69,7 +78,7 @@ function runInPage(ctx) {
|
|
|
69
78
|
|
|
70
79
|
const tag = el.tagName.toLowerCase();
|
|
71
80
|
const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
|
|
72
|
-
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) :
|
|
81
|
+
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
|
|
73
82
|
|
|
74
83
|
occurrences.push({
|
|
75
84
|
selector: stableSelector,
|
|
@@ -91,7 +100,12 @@ function runInPage(ctx) {
|
|
|
91
100
|
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
92
101
|
}
|
|
93
102
|
if (occurrences.length) {
|
|
94
|
-
return {
|
|
103
|
+
return {
|
|
104
|
+
ruleId: rule.ruleId,
|
|
105
|
+
outcome: 'fail',
|
|
106
|
+
severity: rule.defaultSeverity || 'moderate',
|
|
107
|
+
occurrences
|
|
108
|
+
};
|
|
95
109
|
}
|
|
96
110
|
return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
|
|
97
111
|
}
|