@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,3 +1,5 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MPL-2.0 */
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
/**
|
|
@@ -22,7 +24,8 @@ const id = 'svg-image-text-alternative-present';
|
|
|
22
24
|
|
|
23
25
|
const meta = {
|
|
24
26
|
title: 'SVG <image> must have a text alternative',
|
|
25
|
-
description:
|
|
27
|
+
description:
|
|
28
|
+
'Checks that SVG <image> elements provide a text alternative via <title>/<desc> or an ARIA accessible name.',
|
|
26
29
|
i18n: {
|
|
27
30
|
titleKey: 'svgImage_textAltPresent_title',
|
|
28
31
|
descriptionKey: 'svgImage_textAltPresent_description'
|
|
@@ -31,7 +34,13 @@ const meta = {
|
|
|
31
34
|
tags: ['wcag2a', 'wcag111', 'nontext', 'svg', 'image', 'atomic', 'automatic'],
|
|
32
35
|
wcagSc: ['1.1.1'],
|
|
33
36
|
normativeMappings: [
|
|
34
|
-
{
|
|
37
|
+
{
|
|
38
|
+
standard: 'WCAG',
|
|
39
|
+
version: '2.2',
|
|
40
|
+
requirement: '1.1.1',
|
|
41
|
+
title: 'Non-text Content',
|
|
42
|
+
conformanceLevel: 'A'
|
|
43
|
+
}
|
|
35
44
|
],
|
|
36
45
|
defaultSeverity: 'serious',
|
|
37
46
|
category: 'perceivable',
|
|
@@ -48,31 +57,38 @@ function runInPage(ctx) {
|
|
|
48
57
|
const { document, root, helpers, rule } = ctx;
|
|
49
58
|
const safeRoot = root || document;
|
|
50
59
|
|
|
51
|
-
const queryAllSmart =
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
: null;
|
|
74
|
-
|
|
75
|
-
|
|
60
|
+
const queryAllSmart =
|
|
61
|
+
helpers && typeof helpers.queryAllSmart === 'function' ? helpers.queryAllSmart : null;
|
|
62
|
+
const queryAll =
|
|
63
|
+
helpers && typeof helpers.queryAll === 'function'
|
|
64
|
+
? helpers.queryAll
|
|
65
|
+
: (sel) => {
|
|
66
|
+
try {
|
|
67
|
+
return safeRoot && safeRoot.querySelectorAll
|
|
68
|
+
? Array.from(safeRoot.querySelectorAll(sel))
|
|
69
|
+
: [];
|
|
70
|
+
} catch {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const getEligibilityInfo =
|
|
76
|
+
helpers && typeof helpers.getEligibilityInfo === 'function' ? helpers.getEligibilityInfo : null;
|
|
77
|
+
|
|
78
|
+
const isAccTreeEligible =
|
|
79
|
+
helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
|
|
80
|
+
|
|
81
|
+
const isFocusableInfo =
|
|
82
|
+
helpers && typeof helpers.getFocusableInfo === 'function' ? helpers.getFocusableInfo : null;
|
|
83
|
+
|
|
84
|
+
const getAccessibleNameInfo =
|
|
85
|
+
helpers && typeof helpers.getAccessibleNameInfo === 'function'
|
|
86
|
+
? helpers.getAccessibleNameInfo
|
|
87
|
+
: null;
|
|
88
|
+
|
|
89
|
+
function trim(v) {
|
|
90
|
+
return (v == null ? '' : String(v)).trim();
|
|
91
|
+
}
|
|
76
92
|
|
|
77
93
|
// Per SVG accessible-name conventions, only a <title> that is literally
|
|
78
94
|
// the first child element is used by assistive technologies as the
|
|
@@ -116,16 +132,27 @@ function runInPage(ctx) {
|
|
|
116
132
|
}
|
|
117
133
|
|
|
118
134
|
const rawImages = (() => {
|
|
119
|
-
try {
|
|
120
|
-
|
|
135
|
+
try {
|
|
136
|
+
return Array.from((queryAllSmart ? queryAllSmart('image') : queryAll('image')) || []);
|
|
137
|
+
} catch {
|
|
138
|
+
return queryAll('svg image');
|
|
139
|
+
}
|
|
121
140
|
})();
|
|
122
141
|
|
|
123
142
|
const images = rawImages.filter((el) => {
|
|
124
|
-
try {
|
|
125
|
-
|
|
143
|
+
try {
|
|
144
|
+
return (
|
|
145
|
+
el &&
|
|
146
|
+
el.namespaceURI === 'http://www.w3.org/2000/svg' &&
|
|
147
|
+
String(el.localName).toLowerCase() === 'image'
|
|
148
|
+
);
|
|
149
|
+
} catch {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
126
152
|
});
|
|
127
153
|
|
|
128
|
-
if (!images.length)
|
|
154
|
+
if (!images.length)
|
|
155
|
+
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
129
156
|
|
|
130
157
|
const occurrences = [];
|
|
131
158
|
let applicableCount = 0;
|
|
@@ -135,20 +162,31 @@ function runInPage(ctx) {
|
|
|
135
162
|
|
|
136
163
|
if (isAccTreeEligible) {
|
|
137
164
|
const elig = (() => {
|
|
138
|
-
try {
|
|
165
|
+
try {
|
|
166
|
+
return isAccTreeEligible(el, ctx);
|
|
167
|
+
} catch {
|
|
168
|
+
return { eligible: true, reasons: [] };
|
|
169
|
+
}
|
|
139
170
|
})();
|
|
140
171
|
if (elig && elig.eligible === false) continue;
|
|
141
172
|
}
|
|
142
173
|
|
|
143
174
|
const role = trim(el.getAttribute('role')).toLowerCase();
|
|
144
175
|
if (role === 'presentation' || role === 'none') {
|
|
145
|
-
let focusable
|
|
176
|
+
let focusable;
|
|
146
177
|
if (isFocusableInfo) {
|
|
147
|
-
const fi = (() => {
|
|
178
|
+
const fi = (() => {
|
|
179
|
+
try {
|
|
180
|
+
return isFocusableInfo(el, ctx);
|
|
181
|
+
} catch {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
})();
|
|
148
185
|
focusable = !!(fi && fi.focusable);
|
|
149
186
|
} else {
|
|
150
187
|
const tabindex = el.getAttribute('tabindex');
|
|
151
|
-
focusable =
|
|
188
|
+
focusable =
|
|
189
|
+
tabindex != null && trim(tabindex) !== '' && !Number.isNaN(Number(trim(tabindex)));
|
|
152
190
|
}
|
|
153
191
|
if (!focusable) continue;
|
|
154
192
|
}
|
|
@@ -162,25 +200,49 @@ function runInPage(ctx) {
|
|
|
162
200
|
if (desc) continue;
|
|
163
201
|
|
|
164
202
|
// Only now check “accessible name” (but scoped to allowed mechanisms)
|
|
165
|
-
const ariaLabelRaw = (() => {
|
|
166
|
-
|
|
167
|
-
|
|
203
|
+
const ariaLabelRaw = (() => {
|
|
204
|
+
try {
|
|
205
|
+
return el.getAttribute('aria-label');
|
|
206
|
+
} catch {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
})();
|
|
210
|
+
const ariaLabelledbyRaw = (() => {
|
|
211
|
+
try {
|
|
212
|
+
return el.getAttribute('aria-labelledby');
|
|
213
|
+
} catch {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
})();
|
|
217
|
+
const titleAttrRaw = (() => {
|
|
218
|
+
try {
|
|
219
|
+
return el.getAttribute('title');
|
|
220
|
+
} catch {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
})();
|
|
168
224
|
|
|
169
225
|
const ariaLabel = trim(ariaLabelRaw);
|
|
170
226
|
const ariaLabelledby = trim(ariaLabelledbyRaw);
|
|
171
227
|
const titleAttr = trim(titleAttrRaw);
|
|
172
228
|
|
|
173
229
|
const hasMechanism =
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
230
|
+
(ariaLabelRaw !== null && ariaLabel.length > 0) ||
|
|
231
|
+
(ariaLabelledbyRaw !== null && ariaLabelledby.length > 0) ||
|
|
232
|
+
(titleAttrRaw !== null && titleAttr.length > 0);
|
|
177
233
|
|
|
178
|
-
let nameInfo
|
|
234
|
+
let nameInfo;
|
|
179
235
|
let hasName = false;
|
|
180
236
|
|
|
181
237
|
if (hasMechanism) {
|
|
182
238
|
if (getAccessibleNameInfo) {
|
|
183
|
-
nameInfo = (() => {
|
|
239
|
+
nameInfo = (() => {
|
|
240
|
+
try {
|
|
241
|
+
return getAccessibleNameInfo(el, ctx);
|
|
242
|
+
} catch {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
})();
|
|
184
246
|
hasName = !!(nameInfo && nameInfo.present && trim(nameInfo.value));
|
|
185
247
|
} else {
|
|
186
248
|
// Without helper, accept presence of non-empty allowed attributes
|
|
@@ -212,9 +274,16 @@ function runInPage(ctx) {
|
|
|
212
274
|
}
|
|
213
275
|
}
|
|
214
276
|
|
|
215
|
-
if (applicableCount === 0)
|
|
216
|
-
|
|
217
|
-
|
|
277
|
+
if (applicableCount === 0)
|
|
278
|
+
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
279
|
+
if (!occurrences.length)
|
|
280
|
+
return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
|
|
281
|
+
return {
|
|
282
|
+
ruleId: rule.ruleId,
|
|
283
|
+
outcome: 'fail',
|
|
284
|
+
severity: rule.defaultSeverity || 'minor',
|
|
285
|
+
occurrences
|
|
286
|
+
};
|
|
218
287
|
}
|
|
219
288
|
|
|
220
289
|
module.exports = { id, meta, runInPage };
|