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