@surea11y/core 1.2.0 → 1.4.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 +81 -7
- package/LICENSE +373 -21
- package/README.md +175 -35
- package/bin/surea11y-core.js +20 -0
- package/docs/API_STABILITY.md +27 -1
- package/docs/BINDING_AUTHORS_GUIDE.md +9 -9
- package/docs/CI_INTEGRATIONS.md +103 -0
- package/docs/ENGINE_OPTIONS.md +2 -0
- package/docs/I18N.md +12 -9
- package/docs/INTEGRATION.md +19 -1
- package/docs/LIMITATIONS.md +1 -1
- package/docs/OUTPUT_SCHEMA.md +1 -1
- package/docs/REPORT.md +1 -1
- package/docs/RULE_CATALOG.md +1 -1
- package/docs/SARIF.md +59 -0
- package/package.json +63 -18
- package/src/baseline.js +0 -0
- package/src/checks/automatic/area-alt-present.js +63 -31
- package/src/checks/automatic/aria-allowed-attr.js +204 -80
- package/src/checks/automatic/aria-allowed-role.js +23 -7
- package/src/checks/automatic/aria-braille-equivalent.js +34 -10
- package/src/checks/automatic/aria-conditional-attr.js +32 -14
- package/src/checks/automatic/aria-deprecated-role.js +26 -11
- package/src/checks/automatic/aria-hidden-body.js +48 -23
- package/src/checks/automatic/aria-hidden-focus.js +420 -66
- package/src/checks/automatic/aria-prohibited-attr.js +327 -60
- package/src/checks/automatic/aria-prohibited-children.js +111 -103
- package/src/checks/automatic/aria-required-attr.js +29 -15
- package/src/checks/automatic/aria-required-children.js +44 -24
- package/src/checks/automatic/aria-required-parent.js +64 -35
- package/src/checks/automatic/aria-role-name-present.js +49 -21
- package/src/checks/automatic/aria-roles-valid.js +24 -12
- package/src/checks/automatic/aria-valid-attr-value.js +46 -22
- package/src/checks/automatic/aria-valid-attr.js +19 -5
- package/src/checks/automatic/autocomplete-valid.js +76 -16
- package/src/checks/automatic/avoid-inline-spacing.js +23 -8
- package/src/checks/automatic/binary-control-name-present.js +62 -50
- package/src/checks/automatic/button-name-present.js +54 -24
- package/src/checks/automatic/bypass-blocks-present.js +51 -32
- package/src/checks/automatic/canvas-text-alternative-present.js +59 -26
- package/src/checks/automatic/combobox-name-present.js +40 -45
- package/src/checks/automatic/contrast-computable.js +363 -341
- package/src/checks/automatic/contrast-enhanced.js +489 -466
- package/src/checks/automatic/contrast-minimum.js +488 -465
- package/src/checks/automatic/css-orientation-lock.js +51 -35
- package/src/checks/automatic/definition-list-children-valid.js +46 -25
- package/src/checks/automatic/deprecated-elements-not-used.js +25 -9
- package/src/checks/automatic/dialog-name-present.js +47 -85
- package/src/checks/automatic/dlitem-parent-valid.js +25 -8
- package/src/checks/automatic/duplicate-id-aria.js +28 -9
- package/src/checks/automatic/embed-text-alternative-present.js +88 -35
- package/src/checks/automatic/form-control-programmatic-label-present.js +81 -196
- package/src/checks/automatic/form-control-single-label.js +50 -14
- package/src/checks/automatic/html-xml-lang-mismatch.js +36 -18
- package/src/checks/automatic/iframe-focusable-content.js +265 -22
- package/src/checks/automatic/iframe-name-present.js +33 -9
- package/src/checks/automatic/iframe-title-unique.js +32 -9
- package/src/checks/automatic/img-alt-present.js +54 -52
- package/src/checks/automatic/input-image-alt-present.js +141 -112
- package/src/checks/automatic/label-in-name.js +65 -41
- package/src/checks/automatic/language-page-present.js +111 -109
- package/src/checks/automatic/link-in-text-block.js +61 -19
- package/src/checks/automatic/link-name-present.js +47 -14
- package/src/checks/automatic/list-children-valid.js +40 -33
- package/src/checks/automatic/listbox-name-present.js +41 -19
- package/src/checks/automatic/listitem-parent-valid.js +48 -13
- package/src/checks/automatic/menuitem-name-present.js +41 -61
- package/src/checks/automatic/meta-refresh-no-exceptions.js +32 -11
- package/src/checks/automatic/meta-refresh-timing-absent.js +22 -6
- package/src/checks/automatic/meta-viewport-zoom-enabled.js +26 -7
- package/src/checks/automatic/meter-name-present.js +40 -36
- package/src/checks/automatic/nested-interactive-controls-absent.js +58 -15
- package/src/checks/automatic/object-text-alternative-present.js +93 -39
- package/src/checks/automatic/option-name-present.js +40 -21
- package/src/checks/automatic/page-title-present.js +19 -6
- package/src/checks/automatic/progressbar-name-present.js +49 -44
- package/src/checks/automatic/role-img-alt-present.js +211 -159
- package/src/checks/automatic/searchbox-name-present.js +41 -19
- package/src/checks/automatic/server-side-image-map-absent.js +27 -11
- package/src/checks/automatic/slider-name-present.js +42 -47
- package/src/checks/automatic/spinbutton-name-present.js +41 -19
- package/src/checks/automatic/summary-name-present.js +39 -17
- package/src/checks/automatic/svg-image-text-alternative-present.js +116 -47
- package/src/checks/automatic/svg-text-alternative-present.js +262 -230
- package/src/checks/automatic/tab-name-present.js +39 -60
- package/src/checks/automatic/table-headers-attr-valid.js +27 -10
- package/src/checks/automatic/table-th-has-data-cells.js +24 -8
- package/src/checks/automatic/target-size-minimum.js +123 -48
- package/src/checks/automatic/td-has-header.js +53 -12
- package/src/checks/automatic/textbox-name-present.js +41 -19
- package/src/checks/automatic/tooltip-name-present.js +39 -18
- package/src/checks/automatic/treeitem-name-present.js +40 -21
- package/src/checks/automatic/valid-lang.js +22 -6
- package/src/checks/automatic/video-poster-text-alternative-present.js +81 -36
- package/src/checks/manual/accesskeys-manual.js +17 -6
- package/src/checks/manual/area-alt-decorative-manual.js +194 -193
- package/src/checks/manual/area-alt-quality-manual.js +184 -141
- package/src/checks/manual/aria-checked-state-mismatch-manual.js +48 -34
- package/src/checks/manual/aria-text-manual.js +20 -11
- package/src/checks/manual/canvas-text-alternative-quality-manual.js +151 -114
- package/src/checks/manual/css-hidden-focus.js +375 -169
- package/src/checks/manual/embed-text-alternative-quality-manual.js +178 -162
- package/src/checks/manual/empty-heading-manual.js +41 -24
- package/src/checks/manual/empty-table-header-manual.js +69 -31
- package/src/checks/manual/focus-order-semantics-manual.js +60 -13
- package/src/checks/manual/form-control-programmatic-label-quality-manual.js +209 -246
- package/src/checks/manual/heading-order-manual.js +50 -8
- package/src/checks/manual/identical-links-same-purpose-manual.js +36 -12
- package/src/checks/manual/image-redundant-alt-manual.js +38 -8
- package/src/checks/manual/img-alt-decorative-manual.js +133 -96
- package/src/checks/manual/img-alt-quality-manual.js +178 -127
- package/src/checks/manual/input-image-alt-decorative-manual.js +127 -92
- package/src/checks/manual/input-image-alt-quality-manual.js +127 -92
- package/src/checks/manual/label-title-only-manual.js +44 -28
- package/src/checks/manual/landmark-banner-is-top-level-manual.js +95 -38
- package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +85 -32
- package/src/checks/manual/landmark-main-is-top-level-manual.js +69 -27
- package/src/checks/manual/landmark-no-duplicate-banner-manual.js +45 -33
- package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +43 -31
- package/src/checks/manual/landmark-no-duplicate-main-manual.js +27 -21
- package/src/checks/manual/landmark-one-main-manual.js +38 -43
- package/src/checks/manual/landmark-unique-manual.js +78 -67
- package/src/checks/manual/link-name-quality-manual.js +45 -12
- package/src/checks/manual/media-transcript-present-manual.js +37 -22
- package/src/checks/manual/meta-viewport-large-manual.js +19 -6
- package/src/checks/manual/mouse-only-event-handlers-manual.js +40 -11
- package/src/checks/manual/no-autoplay-audio-manual.js +22 -6
- package/src/checks/manual/object-text-alternative-quality-manual.js +177 -154
- package/src/checks/manual/p-as-heading-manual.js +24 -7
- package/src/checks/manual/page-has-heading-one-manual.js +42 -32
- package/src/checks/manual/page-title-patterns-manual.js +80 -50
- package/src/checks/manual/presentation-role-conflict-manual.js +101 -47
- package/src/checks/manual/region-manual.js +244 -60
- package/src/checks/manual/scope-attr-valid-manual.js +13 -4
- package/src/checks/manual/scrollable-region-focusable-manual.js +39 -11
- package/src/checks/manual/skip-link-manual.js +42 -18
- package/src/checks/manual/svg-text-alternative-quality-manual.js +208 -165
- package/src/checks/manual/tabindex-manual.js +13 -4
- package/src/checks/manual/table-duplicate-name-manual.js +22 -11
- package/src/checks/manual/table-fake-caption-manual.js +48 -10
- package/src/checks/manual/video-caption-manual.js +17 -4
- package/src/checks/manual-review.js +58 -12
- package/src/core.js +41705 -29650
- package/src/index.js +2 -0
- package/src/report.js +109 -47
- package/src/sarif.js +190 -0
- package/surea11y.browser.js +37774 -0
- package/bin/core.js +0 -348
- package/docs/CLI.md +0 -75
- package/src/catalogs/composites.wcag.js +0 -490
- package/src/checks/rules-and-tags.full.csv +0 -19
- package/src/checks/rules-and-tags.full.json +0 -259
- package/src/core/aria-helpers.js +0 -970
- package/src/core/contrast-helpers.js +0 -1147
- package/src/core/dom-helpers.js +0 -4235
- package/src/core/dom-runner.js +0 -671
- package/src/core/frame-messaging.js +0 -210
- package/src/core/frame-scan.js +0 -178
- package/src/core/rollup-composites.js +0 -135
- package/src/core/rule-meta.js +0 -159
- package/src/coverage/wcag-facets.js +0 -1079
- package/src/coverage/wcag-version-map.js +0 -84
- package/src/i18n/en.js +0 -923
- package/src/i18n/fr.js +0 -844
- package/src/policy/contracts.js +0 -18
- package/src/policy/resolvePolicy.js +0 -55
- package/src/policy/schemas/engine-options.schema.json +0 -103
- package/src/policy/schemas/policy-contract.schema.json +0 -40
|
@@ -1,36 +1,30 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MPL-2.0 */
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* @check empty-table-header
|
|
5
7
|
* @atomic true
|
|
6
8
|
* @summary Table header cells must not be empty
|
|
7
|
-
* @standard Best Practices (
|
|
9
|
+
* @standard Best Practices (no formal WCAG Success Criterion — see ROADMAP.md Tier 1b)
|
|
8
10
|
* @applicability
|
|
9
11
|
* Applies to <th> elements that don't carry a conflicting explicit role,
|
|
10
12
|
* plus any element (native <th> or not) with role="columnheader" or
|
|
11
|
-
* role="rowheader"
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* role="columnheader"> in a role="grid"/role="table" widget) is caught too
|
|
18
|
-
* — found missing entirely via the cross-engine diff tool 2026-07-23.
|
|
13
|
+
* role="rowheader" (`th:not([role]), [role="columnheader"], [role="rowheader"]`):
|
|
14
|
+
* a <th> that explicitly restates role="columnheader"/"rowheader" is still
|
|
15
|
+
* covered via the second clause, but a <th role="presentation"> (no longer
|
|
16
|
+
* meaningfully a header) is correctly excluded, and an ARIA-role-only header
|
|
17
|
+
* (e.g. a <div role="columnheader"> in a role="grid"/role="table" widget) is
|
|
18
|
+
* caught too.
|
|
19
19
|
* @expectation
|
|
20
20
|
* The header cell has visible text content. A <th> named only via
|
|
21
21
|
* aria-label/aria-labelledby (no visible text) is ALSO flagged, not
|
|
22
|
-
* treated as equivalent —
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* (only visible text is announced), JAWS+Chrome/IE11 also only announce
|
|
29
|
-
* visible text in the header cell itself. Visible text is the one
|
|
30
|
-
* mechanism confirmed to work across every tested combination. A widely-used
|
|
31
|
-
* reference engine's own equivalent check (`has-visible-text` only, no aria-label/
|
|
32
|
-
* aria-labelledby alternative — confirmed directly against its rule
|
|
33
|
-
* descriptor) reaches the same conclusion.
|
|
22
|
+
* treated as equivalent — aria-label support on <th> is genuinely
|
|
23
|
+
* inconsistent in practice: NVDA+Firefox and iOS VoiceOver+Safari ignore
|
|
24
|
+
* it entirely (only visible text is announced), JAWS+Chrome/IE11 also only
|
|
25
|
+
* announce visible text in the header cell itself. Visible text is the one
|
|
26
|
+
* mechanism confirmed to work across every tested combination. See
|
|
27
|
+
* https://html5accessibility.com/stuff/2024/05/22/not-so-short-note-on-aria-label-usage-big-table-edition/.
|
|
34
28
|
* @implementation-notes
|
|
35
29
|
* - Not WCAG-normative — authored as an advisory, cantTell-capped
|
|
36
30
|
* `type: 'manual'` rule; see landmark-banner-is-top-level's
|
|
@@ -46,7 +40,8 @@ const id = 'empty-table-header';
|
|
|
46
40
|
|
|
47
41
|
const meta = {
|
|
48
42
|
title: 'Table header cells must not be empty',
|
|
49
|
-
description:
|
|
43
|
+
description:
|
|
44
|
+
'Checks that table header cells (<th>, or any element with role="columnheader"/"rowheader") have visible text content — a header named only via aria-label/aria-labelledby is also flagged, since real screen-reader/browser support for that is inconsistent.',
|
|
50
45
|
i18n: {
|
|
51
46
|
titleKey: 'emptyTableHeader_title',
|
|
52
47
|
descriptionKey: 'emptyTableHeader_description'
|
|
@@ -66,11 +61,41 @@ function runInPage(ctx) {
|
|
|
66
61
|
const { document, helpers, rule } = ctx;
|
|
67
62
|
|
|
68
63
|
function normalizeWs(s) {
|
|
69
|
-
return String(s || '')
|
|
64
|
+
return String(s || '')
|
|
65
|
+
.replace(/\s+/g, ' ')
|
|
66
|
+
.trim();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const isAccTreeEligible =
|
|
70
|
+
helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
|
|
71
|
+
|
|
72
|
+
function isEligible(node) {
|
|
73
|
+
if (!isAccTreeEligible) return true;
|
|
74
|
+
try {
|
|
75
|
+
const r = isAccTreeEligible(node, ctx);
|
|
76
|
+
return !!(r && r.eligible);
|
|
77
|
+
} catch {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
70
80
|
}
|
|
71
81
|
|
|
82
|
+
// Plain el.textContent includes text from aria-hidden descendants, which
|
|
83
|
+
// a real screen reader never announces. A <th> whose only text comes from
|
|
84
|
+
// an aria-hidden descendant (e.g. <th><span aria-hidden="true">Name</span
|
|
85
|
+
// ></th>) would otherwise be treated as having visible text, even though
|
|
86
|
+
// AT announces nothing for it.
|
|
72
87
|
function getVisibleText(el) {
|
|
73
|
-
|
|
88
|
+
function walk(node) {
|
|
89
|
+
if (node.nodeType === 3) return node.nodeValue || '';
|
|
90
|
+
if (node.nodeType !== 1) return '';
|
|
91
|
+
if (!isEligible(node)) return '';
|
|
92
|
+
let text = '';
|
|
93
|
+
for (const child of node.childNodes || []) text += walk(child);
|
|
94
|
+
return text;
|
|
95
|
+
}
|
|
96
|
+
let text = '';
|
|
97
|
+
for (const child of el.childNodes || []) text += walk(child);
|
|
98
|
+
return normalizeWs(text);
|
|
74
99
|
}
|
|
75
100
|
|
|
76
101
|
function getAriaOnlyName(el) {
|
|
@@ -94,30 +119,38 @@ function runInPage(ctx) {
|
|
|
94
119
|
return '';
|
|
95
120
|
}
|
|
96
121
|
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
// columnheader/rowheader role (native or not).
|
|
122
|
+
// A <th> with no conflicting explicit role, plus any element carrying an
|
|
123
|
+
// explicit columnheader/rowheader role (native or not).
|
|
100
124
|
const selector = 'th:not([role]), [role="columnheader"], [role="rowheader"]';
|
|
101
|
-
const nodes = helpers.queryAllSmart
|
|
125
|
+
const nodes = helpers.queryAllSmart
|
|
126
|
+
? helpers.queryAllSmart(selector)
|
|
127
|
+
: helpers.queryAll(selector);
|
|
102
128
|
|
|
103
129
|
const occurrences = [];
|
|
104
130
|
let applicableCount = 0;
|
|
105
131
|
|
|
106
132
|
for (const el of nodes) {
|
|
107
133
|
if (!el) continue;
|
|
134
|
+
|
|
135
|
+
// A fully aria-hidden header cell isn't part of the AT-perceived
|
|
136
|
+
// table structure at all -- queryAllSmart's default hidden-content
|
|
137
|
+
// policy only excludes "hard" CSS-based hiding, not aria-hidden.
|
|
138
|
+
if (!isEligible(el)) continue;
|
|
139
|
+
|
|
108
140
|
applicableCount += 1;
|
|
109
141
|
|
|
110
142
|
if (getVisibleText(el)) continue;
|
|
111
143
|
|
|
112
144
|
const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
|
|
113
|
-
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) :
|
|
145
|
+
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
|
|
114
146
|
|
|
115
147
|
const ariaName = getAriaOnlyName(el);
|
|
116
148
|
if (ariaName) {
|
|
117
149
|
occurrences.push({
|
|
118
150
|
selector: stableSelector,
|
|
119
151
|
html,
|
|
120
|
-
summary:
|
|
152
|
+
summary:
|
|
153
|
+
'This table header cell has no visible text — its only accessible name comes from aria-label/aria-labelledby, which real screen-reader/browser combinations (e.g. NVDA+Firefox, iOS VoiceOver+Safari) are known to ignore on <th> elements.',
|
|
121
154
|
hint: 'Add visible text content to this header cell (in addition to, or instead of, aria-label/aria-labelledby) — visible text is the only naming mechanism confirmed to work across tested screen readers.',
|
|
122
155
|
i18n: {
|
|
123
156
|
summaryKey: 'emptyTableHeader_summary_cantTell_ariaOnly',
|
|
@@ -151,7 +184,12 @@ function runInPage(ctx) {
|
|
|
151
184
|
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
152
185
|
}
|
|
153
186
|
if (occurrences.length) {
|
|
154
|
-
return {
|
|
187
|
+
return {
|
|
188
|
+
ruleId: rule.ruleId,
|
|
189
|
+
outcome: 'cantTell',
|
|
190
|
+
severity: rule.defaultSeverity || 'minor',
|
|
191
|
+
occurrences
|
|
192
|
+
};
|
|
155
193
|
}
|
|
156
194
|
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
157
195
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MPL-2.0 */
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* @check focus-order-semantics
|
|
5
7
|
* @atomic true
|
|
6
8
|
* @summary Elements added to the tab order should have interactive semantics
|
|
7
|
-
* @standard Best Practices (
|
|
9
|
+
* @standard Best Practices (no formal WCAG Success Criterion — see ROADMAP.md Tier 1b)
|
|
8
10
|
* @applicability
|
|
9
11
|
* Elements with an explicit `tabindex` of `0` or greater (in the tab
|
|
10
12
|
* order) AND an explicit `role` attribute that is one of a curated set
|
|
@@ -16,8 +18,8 @@
|
|
|
16
18
|
* announce, which is confusing for keyboard users who land on it and
|
|
17
19
|
* get no indication of what activating it (if anything) would do.
|
|
18
20
|
* @implementation-notes
|
|
19
|
-
* - Not WCAG-normative
|
|
20
|
-
*
|
|
21
|
+
* - Not WCAG-normative — authored as an advisory, cantTell-capped
|
|
22
|
+
* `type: 'manual'` rule.
|
|
21
23
|
* - The non-interactive role list is deliberately curated and
|
|
22
24
|
* conservative (structural/document roles only) — legitimate custom
|
|
23
25
|
* widget patterns using `tabindex` with a genuinely interactive role
|
|
@@ -25,13 +27,25 @@
|
|
|
25
27
|
* `tabindex` and NO role at all are also not flagged: native semantics
|
|
26
28
|
* or an intentionally generic custom-interactive pattern cannot be
|
|
27
29
|
* distinguished from markup alone with the same confidence.
|
|
30
|
+
* - `region` is deliberately NOT in the non-interactive role list: a
|
|
31
|
+
* tabbable `role="region"` is a real, WCAG 2.1.1/2.1.3-grounded pattern
|
|
32
|
+
* this engine's own `scrollable-region-focusable` check exists to
|
|
33
|
+
* RECOMMEND (a scrollable landmark with no other focusable content
|
|
34
|
+
* needs `tabindex="0"` to be keyboard-reachable at all) — flagging it
|
|
35
|
+
* here would be internally inconsistent with that sibling check. A
|
|
36
|
+
* `role="region"` is also commonly made tabbable on its own merits
|
|
37
|
+
* (e.g. a cookie-consent banner or notification/toast region a keyboard
|
|
38
|
+
* user should be able to reach directly, as in
|
|
39
|
+
* `<div role="region" tabindex="0">`). Scoped to `region` only;
|
|
40
|
+
* navigation/status/tabpanel remain flagged pending their own evidence.
|
|
28
41
|
*/
|
|
29
42
|
|
|
30
43
|
const id = 'focus-order-semantics';
|
|
31
44
|
|
|
32
45
|
const meta = {
|
|
33
46
|
title: 'Elements added to the tab order should have interactive semantics',
|
|
34
|
-
description:
|
|
47
|
+
description:
|
|
48
|
+
'Flags elements with tabindex >= 0 whose explicit role is a non-interactive structural/document role (e.g. heading, list, region, presentation), for manual review.',
|
|
35
49
|
i18n: {
|
|
36
50
|
titleKey: 'focusOrderSemantics_title',
|
|
37
51
|
descriptionKey: 'focusOrderSemantics_description'
|
|
@@ -48,17 +62,45 @@ const meta = {
|
|
|
48
62
|
};
|
|
49
63
|
|
|
50
64
|
function runInPage(ctx) {
|
|
51
|
-
const {
|
|
65
|
+
const { helpers, rule } = ctx;
|
|
52
66
|
|
|
53
67
|
const NON_INTERACTIVE_ROLES = new Set([
|
|
54
|
-
'generic',
|
|
55
|
-
'
|
|
56
|
-
'
|
|
57
|
-
'
|
|
58
|
-
'
|
|
68
|
+
'generic',
|
|
69
|
+
'group',
|
|
70
|
+
'text',
|
|
71
|
+
'presentation',
|
|
72
|
+
'none',
|
|
73
|
+
'img',
|
|
74
|
+
'heading',
|
|
75
|
+
'article',
|
|
76
|
+
'banner',
|
|
77
|
+
'contentinfo',
|
|
78
|
+
'main',
|
|
79
|
+
'navigation',
|
|
80
|
+
'complementary',
|
|
81
|
+
'document',
|
|
82
|
+
'note',
|
|
83
|
+
'list',
|
|
84
|
+
'listitem',
|
|
85
|
+
'table',
|
|
86
|
+
'row',
|
|
87
|
+
'cell',
|
|
88
|
+
'columnheader',
|
|
89
|
+
'rowheader',
|
|
90
|
+
'figure',
|
|
91
|
+
'term',
|
|
92
|
+
'definition',
|
|
93
|
+
'paragraph',
|
|
94
|
+
'caption',
|
|
95
|
+
'status',
|
|
96
|
+
'alert',
|
|
97
|
+
'log',
|
|
98
|
+
'tooltip'
|
|
59
99
|
]);
|
|
60
100
|
|
|
61
|
-
const nodes = helpers.queryAllSmart
|
|
101
|
+
const nodes = helpers.queryAllSmart
|
|
102
|
+
? helpers.queryAllSmart('[tabindex][role]')
|
|
103
|
+
: helpers.queryAll('[tabindex][role]');
|
|
62
104
|
|
|
63
105
|
const occurrences = [];
|
|
64
106
|
let applicableCount = 0;
|
|
@@ -78,7 +120,7 @@ function runInPage(ctx) {
|
|
|
78
120
|
if (!NON_INTERACTIVE_ROLES.has(role)) continue;
|
|
79
121
|
|
|
80
122
|
const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
|
|
81
|
-
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) :
|
|
123
|
+
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
|
|
82
124
|
|
|
83
125
|
const baseOccurrence = {
|
|
84
126
|
selector: stableSelector,
|
|
@@ -107,7 +149,12 @@ function runInPage(ctx) {
|
|
|
107
149
|
}
|
|
108
150
|
|
|
109
151
|
if (occurrences.length) {
|
|
110
|
-
return {
|
|
152
|
+
return {
|
|
153
|
+
ruleId: rule.ruleId,
|
|
154
|
+
outcome: 'cantTell',
|
|
155
|
+
severity: rule.defaultSeverity || 'minor',
|
|
156
|
+
occurrences
|
|
157
|
+
};
|
|
111
158
|
}
|
|
112
159
|
|
|
113
160
|
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|