eaa-kit 0.2.1 → 0.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/README.md +51 -4
- package/dist/astro/index.d.ts +7 -34
- package/dist/astro/index.js +6 -26
- package/dist/audit/runners/worker.js +1 -1
- package/dist/audit-CPoZMXGM.js +779 -0
- package/dist/audit-CpXH2Mk8.js +2 -0
- package/dist/{baseline-CV_3lbER.js → baseline-22Y1NWxM.js} +1 -1
- package/dist/{baseline-CgBmzFTr.js → baseline-DB9CZGnV.js} +17 -27
- package/dist/cli/index.js +336 -142
- package/dist/collect-CFM8gEVv.js +134 -0
- package/dist/command-D8l_oYbV.js +77 -0
- package/dist/component-C3GL1Mnu.js +146 -0
- package/dist/component-DKd3EHOg.js +2 -0
- package/dist/coverage-B4IBKMO8.js +521 -0
- package/dist/{crawl-CtJbMNNb.js → crawl-BiI1Lau_.js} +13 -23
- package/dist/eleventy/index.d.ts +28 -0
- package/dist/eleventy/index.js +19 -0
- package/dist/frameworks-B4ClIJgE.js +314 -0
- package/dist/frameworks-etFg_O8K.js +2 -0
- package/dist/fs-BmPtmFke.js +40 -0
- package/dist/html-BGTO3ypW.js +543 -0
- package/dist/{impact-DvgBjupx.js → impact-DZt2oBCP.js} +15 -1
- package/dist/index.js +2 -2
- package/dist/{init-DRKIdpK1.js → init-DIWDE35F.js} +13 -10
- package/dist/jsdom-4IMzv0eE.js +3 -0
- package/dist/jsdom-DCpGSLfW.js +81 -0
- package/dist/{json-C9xS1PNC.js → json-DjEvy1nX.js} +19 -22
- package/dist/json-QQuFIw1W.js +2 -0
- package/dist/{load-sCkKsvGQ.js → load-5wRGLvub.js} +3 -9
- package/dist/nuxt/index.d.ts +35 -0
- package/dist/nuxt/index.js +23 -0
- package/dist/{playwright-DSRnXmcd.js → playwright-BWniOain.js} +70 -23
- package/dist/{pool-DixLeu8L.js → pool-BMevaLWD.js} +4 -12
- package/dist/{project-CufCqIE2.js → project-CzOnkLH6.js} +14 -21
- package/dist/project-MFrXcw1M.js +2 -0
- package/dist/remediation-Dtowi2EC.js +321 -0
- package/dist/{render-BO0nVrrZ.js → render-DrvXRCEn.js} +8 -16
- package/dist/{result-2aZPfM8w.js → result-DoamKFsp.js} +115 -1
- package/dist/routes-CmdRUuOs.js +265 -0
- package/dist/run-BMASMmwO.d.ts +45 -0
- package/dist/run-DB34BSOZ.js +53 -0
- package/dist/{sarif-eSCuI0eX.js → sarif-SR3_lLYd.js} +22 -36
- package/dist/{schema-CMZ8ItGk.js → schema-is6CGX2D.js} +4 -1
- package/dist/text-CKKpzkYM.js +43 -0
- package/dist/vite/index.d.ts +39 -0
- package/dist/vite/index.js +40 -0
- package/dist/webpack/index.d.ts +33 -0
- package/dist/webpack/index.js +20 -0
- package/package.json +36 -9
- package/dist/audit-B2dIKpJ5.js +0 -2
- package/dist/audit-DXpKkXsC.js +0 -950
- package/dist/escape-Dm1o_RAk.js +0 -21
- package/dist/html-BLEuzep6.js +0 -337
- package/dist/jsdom-BEu6Ra_2.js +0 -163
- package/dist/jsdom-C6dIyaxN.js +0 -3
- package/dist/routes-BxbSZKXC.js +0 -123
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
import { n as ENGINE_BLIND_RULES, t as DEFAULT_TAGS, u as successCriteria } from "./result-DoamKFsp.js";
|
|
2
|
+
import axe from "axe-core";
|
|
3
|
+
//#region src/audit/manual.ts
|
|
4
|
+
/**
|
|
5
|
+
* What a person has to check, and where to read about it.
|
|
6
|
+
*
|
|
7
|
+
* Automated testing finds a minority of accessibility barriers. This tool says
|
|
8
|
+
* so everywhere, and saying so is not much use on its own: a reader told that
|
|
9
|
+
* six rules "could not be evaluated" is left knowing there is a gap and not
|
|
10
|
+
* what to do about it.
|
|
11
|
+
*
|
|
12
|
+
* So each rule the browserless engine cannot decide carries the check somebody
|
|
13
|
+
* would do by hand, and each success criterion carries a link to what it
|
|
14
|
+
* actually requires. The point is to turn the honest disclaimer into the part
|
|
15
|
+
* of the report with the most work in it.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* WCAG 2.2 Understanding pages, by success criterion.
|
|
19
|
+
*
|
|
20
|
+
* The Understanding document rather than the criterion text: the text says what
|
|
21
|
+
* conformance is, and the Understanding page says what it is for and how to
|
|
22
|
+
* meet it, which is what somebody reading a report needs.
|
|
23
|
+
*
|
|
24
|
+
* Level A and AA only, since that is what this tool audits against.
|
|
25
|
+
*/
|
|
26
|
+
const UNDERSTANDING = {
|
|
27
|
+
"1.1.1": "non-text-content",
|
|
28
|
+
"1.2.1": "audio-only-and-video-only-prerecorded",
|
|
29
|
+
"1.2.2": "captions-prerecorded",
|
|
30
|
+
"1.2.3": "audio-description-or-media-alternative-prerecorded",
|
|
31
|
+
"1.2.4": "captions-live",
|
|
32
|
+
"1.2.5": "audio-description-prerecorded",
|
|
33
|
+
"1.3.1": "info-and-relationships",
|
|
34
|
+
"1.3.2": "meaningful-sequence",
|
|
35
|
+
"1.3.3": "sensory-characteristics",
|
|
36
|
+
"1.3.4": "orientation",
|
|
37
|
+
"1.3.5": "identify-input-purpose",
|
|
38
|
+
"1.4.1": "use-of-color",
|
|
39
|
+
"1.4.2": "audio-control",
|
|
40
|
+
"1.4.3": "contrast-minimum",
|
|
41
|
+
"1.4.4": "resize-text",
|
|
42
|
+
"1.4.5": "images-of-text",
|
|
43
|
+
"1.4.10": "reflow",
|
|
44
|
+
"1.4.11": "non-text-contrast",
|
|
45
|
+
"1.4.12": "text-spacing",
|
|
46
|
+
"1.4.13": "content-on-hover-or-focus",
|
|
47
|
+
"2.1.1": "keyboard",
|
|
48
|
+
"2.1.2": "no-keyboard-trap",
|
|
49
|
+
"2.1.4": "character-key-shortcuts",
|
|
50
|
+
"2.2.1": "timing-adjustable",
|
|
51
|
+
"2.2.2": "pause-stop-hide",
|
|
52
|
+
"2.3.1": "three-flashes-or-below-threshold",
|
|
53
|
+
"2.4.1": "bypass-blocks",
|
|
54
|
+
"2.4.2": "page-titled",
|
|
55
|
+
"2.4.3": "focus-order",
|
|
56
|
+
"2.4.4": "link-purpose-in-context",
|
|
57
|
+
"2.4.5": "multiple-ways",
|
|
58
|
+
"2.4.6": "headings-and-labels",
|
|
59
|
+
"2.4.7": "focus-visible",
|
|
60
|
+
"2.4.11": "focus-not-obscured-minimum",
|
|
61
|
+
"2.5.1": "pointer-gestures",
|
|
62
|
+
"2.5.2": "pointer-cancellation",
|
|
63
|
+
"2.5.3": "label-in-name",
|
|
64
|
+
"2.5.4": "motion-actuation",
|
|
65
|
+
"2.5.7": "dragging-movements",
|
|
66
|
+
"2.5.8": "target-size-minimum",
|
|
67
|
+
"3.1.1": "language-of-page",
|
|
68
|
+
"3.1.2": "language-of-parts",
|
|
69
|
+
"3.2.1": "on-focus",
|
|
70
|
+
"3.2.2": "on-input",
|
|
71
|
+
"3.2.3": "consistent-navigation",
|
|
72
|
+
"3.2.4": "consistent-identification",
|
|
73
|
+
"3.2.6": "consistent-help",
|
|
74
|
+
"3.3.1": "error-identification",
|
|
75
|
+
"3.3.2": "labels-or-instructions",
|
|
76
|
+
"3.3.3": "error-suggestion",
|
|
77
|
+
"3.3.4": "error-prevention-legal-financial-data",
|
|
78
|
+
"3.3.7": "redundant-entry",
|
|
79
|
+
"3.3.8": "accessible-authentication-minimum",
|
|
80
|
+
"4.1.2": "name-role-value",
|
|
81
|
+
"4.1.3": "status-messages"
|
|
82
|
+
};
|
|
83
|
+
/** Where to read what a success criterion requires, or undefined if unknown. */
|
|
84
|
+
function understandingUrl(criterion) {
|
|
85
|
+
const slug = UNDERSTANDING[criterion];
|
|
86
|
+
return slug === void 0 ? void 0 : `https://www.w3.org/WAI/WCAG22/Understanding/${slug}.html`;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The check a person does for a rule this engine could not decide.
|
|
90
|
+
*
|
|
91
|
+
* Written as an action rather than a restatement of the criterion. "Ensure
|
|
92
|
+
* sufficient contrast" tells somebody nothing they did not already know; the
|
|
93
|
+
* useful sentence names what to open and what to look at.
|
|
94
|
+
*/
|
|
95
|
+
const MANUAL_CHECKS = {
|
|
96
|
+
"color-contrast": {
|
|
97
|
+
check: "Open the page and check text against its background with a contrast checker. Body text needs 4.5:1, and large or bold text 3:1. Check the states too — hover, focus, visited, disabled and placeholder text are the ones usually missed.",
|
|
98
|
+
browserAnswers: true
|
|
99
|
+
},
|
|
100
|
+
"color-contrast-enhanced": {
|
|
101
|
+
check: "Only needed if you are claiming AAA. Body text needs 7:1 and large text 4.5:1.",
|
|
102
|
+
browserAnswers: true
|
|
103
|
+
},
|
|
104
|
+
"target-size": {
|
|
105
|
+
check: "Measure the clickable area of buttons, icon links and form controls: 24×24 CSS pixels at minimum, unless they are inline in a sentence or have that much clear space around them. Icon-only controls in a header or a media player are where this usually fails.",
|
|
106
|
+
browserAnswers: true
|
|
107
|
+
},
|
|
108
|
+
"scrollable-region-focusable": {
|
|
109
|
+
check: "Find anything that scrolls inside the page — a code block, a wide table, a long list — and try reaching it with the Tab key alone. If it cannot take focus, a keyboard user cannot scroll it.",
|
|
110
|
+
browserAnswers: true
|
|
111
|
+
},
|
|
112
|
+
"link-in-text-block": {
|
|
113
|
+
check: "Look at links inside paragraphs. If the only thing distinguishing them from the surrounding text is colour, they need an underline or a 3:1 contrast difference against that text as well.",
|
|
114
|
+
browserAnswers: true
|
|
115
|
+
},
|
|
116
|
+
"no-autoplay-audio": {
|
|
117
|
+
check: "Load the page and listen. Anything that plays for more than three seconds on its own needs a pause or stop control, or a volume control independent of the system volume.",
|
|
118
|
+
browserAnswers: false
|
|
119
|
+
},
|
|
120
|
+
"avoid-inline-spacing": {
|
|
121
|
+
check: "Override line height to 1.5×, paragraph spacing to 2×, letter spacing to 0.12× and word spacing to 0.16× the font size, then check nothing is clipped or overlapping. Inline styles that set spacing with !important are what break this.",
|
|
122
|
+
browserAnswers: true
|
|
123
|
+
},
|
|
124
|
+
"p-as-heading": {
|
|
125
|
+
check: "Look for paragraphs styled to look like headings. A screen reader reads them as body text, so they are invisible to anyone navigating by heading.",
|
|
126
|
+
browserAnswers: false
|
|
127
|
+
},
|
|
128
|
+
"css-orientation-lock": {
|
|
129
|
+
check: "Rotate a phone or tablet. The content should work in both orientations unless one is essential, which is rare outside games and instruments.",
|
|
130
|
+
browserAnswers: false
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
/** The check for a rule, if there is one written for it. */
|
|
134
|
+
function manualCheckFor(ruleId) {
|
|
135
|
+
return MANUAL_CHECKS[ruleId];
|
|
136
|
+
}
|
|
137
|
+
//#endregion
|
|
138
|
+
//#region src/audit/coverage.ts
|
|
139
|
+
/**
|
|
140
|
+
* WCAG 2.2 at Levels A and AA, which is what this tool audits against.
|
|
141
|
+
*
|
|
142
|
+
* 4.1.1 Parsing is deliberately absent: it was removed in WCAG 2.2 and counting
|
|
143
|
+
* it would inflate the denominator with a criterion nobody has to meet.
|
|
144
|
+
*/
|
|
145
|
+
const WCAG22_AA_CRITERIA = [
|
|
146
|
+
{
|
|
147
|
+
number: "1.1.1",
|
|
148
|
+
title: "Non-text Content",
|
|
149
|
+
level: "A"
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
number: "1.2.1",
|
|
153
|
+
title: "Audio-only and Video-only (Prerecorded)",
|
|
154
|
+
level: "A"
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
number: "1.2.2",
|
|
158
|
+
title: "Captions (Prerecorded)",
|
|
159
|
+
level: "A"
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
number: "1.2.3",
|
|
163
|
+
title: "Audio Description or Media Alternative (Prerecorded)",
|
|
164
|
+
level: "A"
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
number: "1.2.4",
|
|
168
|
+
title: "Captions (Live)",
|
|
169
|
+
level: "AA"
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
number: "1.2.5",
|
|
173
|
+
title: "Audio Description (Prerecorded)",
|
|
174
|
+
level: "AA"
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
number: "1.3.1",
|
|
178
|
+
title: "Info and Relationships",
|
|
179
|
+
level: "A"
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
number: "1.3.2",
|
|
183
|
+
title: "Meaningful Sequence",
|
|
184
|
+
level: "A"
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
number: "1.3.3",
|
|
188
|
+
title: "Sensory Characteristics",
|
|
189
|
+
level: "A"
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
number: "1.3.4",
|
|
193
|
+
title: "Orientation",
|
|
194
|
+
level: "AA"
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
number: "1.3.5",
|
|
198
|
+
title: "Identify Input Purpose",
|
|
199
|
+
level: "AA"
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
number: "1.4.1",
|
|
203
|
+
title: "Use of Color",
|
|
204
|
+
level: "A"
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
number: "1.4.2",
|
|
208
|
+
title: "Audio Control",
|
|
209
|
+
level: "A"
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
number: "1.4.3",
|
|
213
|
+
title: "Contrast (Minimum)",
|
|
214
|
+
level: "AA"
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
number: "1.4.4",
|
|
218
|
+
title: "Resize Text",
|
|
219
|
+
level: "AA"
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
number: "1.4.5",
|
|
223
|
+
title: "Images of Text",
|
|
224
|
+
level: "AA"
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
number: "1.4.10",
|
|
228
|
+
title: "Reflow",
|
|
229
|
+
level: "AA"
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
number: "1.4.11",
|
|
233
|
+
title: "Non-text Contrast",
|
|
234
|
+
level: "AA"
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
number: "1.4.12",
|
|
238
|
+
title: "Text Spacing",
|
|
239
|
+
level: "AA"
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
number: "1.4.13",
|
|
243
|
+
title: "Content on Hover or Focus",
|
|
244
|
+
level: "AA"
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
number: "2.1.1",
|
|
248
|
+
title: "Keyboard",
|
|
249
|
+
level: "A"
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
number: "2.1.2",
|
|
253
|
+
title: "No Keyboard Trap",
|
|
254
|
+
level: "A"
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
number: "2.1.4",
|
|
258
|
+
title: "Character Key Shortcuts",
|
|
259
|
+
level: "A"
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
number: "2.2.1",
|
|
263
|
+
title: "Timing Adjustable",
|
|
264
|
+
level: "A"
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
number: "2.2.2",
|
|
268
|
+
title: "Pause, Stop, Hide",
|
|
269
|
+
level: "A"
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
number: "2.3.1",
|
|
273
|
+
title: "Three Flashes or Below Threshold",
|
|
274
|
+
level: "A"
|
|
275
|
+
},
|
|
276
|
+
{
|
|
277
|
+
number: "2.4.1",
|
|
278
|
+
title: "Bypass Blocks",
|
|
279
|
+
level: "A"
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
number: "2.4.2",
|
|
283
|
+
title: "Page Titled",
|
|
284
|
+
level: "A"
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
number: "2.4.3",
|
|
288
|
+
title: "Focus Order",
|
|
289
|
+
level: "A"
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
number: "2.4.4",
|
|
293
|
+
title: "Link Purpose (In Context)",
|
|
294
|
+
level: "A"
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
number: "2.4.5",
|
|
298
|
+
title: "Multiple Ways",
|
|
299
|
+
level: "AA"
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
number: "2.4.6",
|
|
303
|
+
title: "Headings and Labels",
|
|
304
|
+
level: "AA"
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
number: "2.4.7",
|
|
308
|
+
title: "Focus Visible",
|
|
309
|
+
level: "AA"
|
|
310
|
+
},
|
|
311
|
+
{
|
|
312
|
+
number: "2.4.11",
|
|
313
|
+
title: "Focus Not Obscured (Minimum)",
|
|
314
|
+
level: "AA"
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
number: "2.5.1",
|
|
318
|
+
title: "Pointer Gestures",
|
|
319
|
+
level: "A"
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
number: "2.5.2",
|
|
323
|
+
title: "Pointer Cancellation",
|
|
324
|
+
level: "A"
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
number: "2.5.3",
|
|
328
|
+
title: "Label in Name",
|
|
329
|
+
level: "A"
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
number: "2.5.4",
|
|
333
|
+
title: "Motion Actuation",
|
|
334
|
+
level: "A"
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
number: "2.5.7",
|
|
338
|
+
title: "Dragging Movements",
|
|
339
|
+
level: "AA"
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
number: "2.5.8",
|
|
343
|
+
title: "Target Size (Minimum)",
|
|
344
|
+
level: "AA"
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
number: "3.1.1",
|
|
348
|
+
title: "Language of Page",
|
|
349
|
+
level: "A"
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
number: "3.1.2",
|
|
353
|
+
title: "Language of Parts",
|
|
354
|
+
level: "AA"
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
number: "3.2.1",
|
|
358
|
+
title: "On Focus",
|
|
359
|
+
level: "A"
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
number: "3.2.2",
|
|
363
|
+
title: "On Input",
|
|
364
|
+
level: "A"
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
number: "3.2.3",
|
|
368
|
+
title: "Consistent Navigation",
|
|
369
|
+
level: "AA"
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
number: "3.2.4",
|
|
373
|
+
title: "Consistent Identification",
|
|
374
|
+
level: "AA"
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
number: "3.2.6",
|
|
378
|
+
title: "Consistent Help",
|
|
379
|
+
level: "A"
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
number: "3.3.1",
|
|
383
|
+
title: "Error Identification",
|
|
384
|
+
level: "A"
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
number: "3.3.2",
|
|
388
|
+
title: "Labels or Instructions",
|
|
389
|
+
level: "A"
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
number: "3.3.3",
|
|
393
|
+
title: "Error Suggestion",
|
|
394
|
+
level: "AA"
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
number: "3.3.4",
|
|
398
|
+
title: "Error Prevention (Legal, Financial, Data)",
|
|
399
|
+
level: "AA"
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
number: "3.3.7",
|
|
403
|
+
title: "Redundant Entry",
|
|
404
|
+
level: "A"
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
number: "3.3.8",
|
|
408
|
+
title: "Accessible Authentication (Minimum)",
|
|
409
|
+
level: "AA"
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
number: "4.1.2",
|
|
413
|
+
title: "Name, Role, Value",
|
|
414
|
+
level: "A"
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
number: "4.1.3",
|
|
418
|
+
title: "Status Messages",
|
|
419
|
+
level: "AA"
|
|
420
|
+
}
|
|
421
|
+
];
|
|
422
|
+
/**
|
|
423
|
+
* Rules axe-core would run under these tags, mapped to the criteria they cover.
|
|
424
|
+
*
|
|
425
|
+
* Experimental rules are excluded, matching `blindRulesInScope`: axe-core leaves
|
|
426
|
+
* them off by default, so counting them as coverage would promise checks that
|
|
427
|
+
* never run.
|
|
428
|
+
*/
|
|
429
|
+
function rulesByCriterion(tags) {
|
|
430
|
+
const byCriterion = /* @__PURE__ */ new Map();
|
|
431
|
+
for (const rule of axe.getRules([...tags])) {
|
|
432
|
+
if (rule.tags.includes("experimental")) continue;
|
|
433
|
+
for (const criterion of successCriteria(rule.tags)) {
|
|
434
|
+
const rules = byCriterion.get(criterion);
|
|
435
|
+
if (rules) rules.push(rule.ruleId);
|
|
436
|
+
else byCriterion.set(criterion, [rule.ruleId]);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
for (const rules of byCriterion.values()) rules.sort();
|
|
440
|
+
return byCriterion;
|
|
441
|
+
}
|
|
442
|
+
/** Rule ids this run reached a pass or a violation on, across every page. */
|
|
443
|
+
function decidedRules(audits) {
|
|
444
|
+
const decided = /* @__PURE__ */ new Set();
|
|
445
|
+
for (const audit of audits) {
|
|
446
|
+
for (const finding of audit.violations) decided.add(finding.ruleId);
|
|
447
|
+
for (const finding of audit.accepted ?? []) decided.add(finding.ruleId);
|
|
448
|
+
for (const outcome of audit.passes) decided.add(outcome.ruleId);
|
|
449
|
+
}
|
|
450
|
+
return decided;
|
|
451
|
+
}
|
|
452
|
+
/** Rule ids this run reached no verdict on because the engine cannot see them. */
|
|
453
|
+
function blindedRules(audits) {
|
|
454
|
+
const blind = /* @__PURE__ */ new Set();
|
|
455
|
+
for (const audit of audits) for (const finding of audit.incomplete) if (finding.reason === "engine-limitation") blind.add(finding.ruleId);
|
|
456
|
+
return blind;
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* What this run reached, criterion by criterion.
|
|
460
|
+
*
|
|
461
|
+
* `audits` decides `evaluated` rather than the engine's capabilities alone: a
|
|
462
|
+
* rule that could have run and never matched anything has not established that
|
|
463
|
+
* the criterion is met on this site, and saying otherwise would be a coverage
|
|
464
|
+
* claim resting on an empty page.
|
|
465
|
+
*/
|
|
466
|
+
function buildCoverage(audits, tags = DEFAULT_TAGS) {
|
|
467
|
+
const byCriterion = rulesByCriterion(tags);
|
|
468
|
+
const decided = decidedRules(audits);
|
|
469
|
+
const blinded = blindedRules(audits);
|
|
470
|
+
const criteria = WCAG22_AA_CRITERIA.map((criterion) => {
|
|
471
|
+
const rules = byCriterion.get(criterion.number) ?? [];
|
|
472
|
+
if (rules.length === 0) return {
|
|
473
|
+
...criterion,
|
|
474
|
+
status: "no-automated-rule",
|
|
475
|
+
rules,
|
|
476
|
+
browserWouldAnswer: false
|
|
477
|
+
};
|
|
478
|
+
if (rules.some((rule) => decided.has(rule))) return {
|
|
479
|
+
...criterion,
|
|
480
|
+
status: "evaluated",
|
|
481
|
+
rules,
|
|
482
|
+
browserWouldAnswer: false
|
|
483
|
+
};
|
|
484
|
+
const engineBlind = rules.filter((rule) => blinded.has(rule) || ENGINE_BLIND_RULES[rule] !== void 0);
|
|
485
|
+
if (engineBlind.length > 0) return {
|
|
486
|
+
...criterion,
|
|
487
|
+
status: "not-evaluated",
|
|
488
|
+
rules,
|
|
489
|
+
browserWouldAnswer: engineBlind.every((rule) => manualCheckFor(rule)?.browserAnswers === true)
|
|
490
|
+
};
|
|
491
|
+
return {
|
|
492
|
+
...criterion,
|
|
493
|
+
status: "nothing-to-check",
|
|
494
|
+
rules,
|
|
495
|
+
browserWouldAnswer: false
|
|
496
|
+
};
|
|
497
|
+
});
|
|
498
|
+
const count = (status) => criteria.filter((criterion) => criterion.status === status).length;
|
|
499
|
+
return {
|
|
500
|
+
criteria,
|
|
501
|
+
evaluated: count("evaluated"),
|
|
502
|
+
notEvaluated: count("not-evaluated"),
|
|
503
|
+
nothingToCheck: count("nothing-to-check"),
|
|
504
|
+
noAutomatedRule: count("no-automated-rule"),
|
|
505
|
+
browserWouldAnswer: criteria.filter((criterion) => criterion.browserWouldAnswer).length,
|
|
506
|
+
total: WCAG22_AA_CRITERIA.length
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* The sentence a reader needs before drawing anything from a clean report.
|
|
511
|
+
*
|
|
512
|
+
* Deliberately leads with what was not checked. A report that opened with "23
|
|
513
|
+
* criteria evaluated" would read as a score, which is precisely what this
|
|
514
|
+
* refuses to be.
|
|
515
|
+
*/
|
|
516
|
+
function coverageSummary(coverage) {
|
|
517
|
+
const unautomatable = coverage.noAutomatedRule;
|
|
518
|
+
return `Of the ${coverage.total} WCAG 2.2 A and AA success criteria, ${unautomatable} cannot be checked by any automated engine and need a person. This run reached a verdict on ${coverage.evaluated}.`;
|
|
519
|
+
}
|
|
520
|
+
//#endregion
|
|
521
|
+
export { understandingUrl as i, coverageSummary as n, manualCheckFor as r, buildCoverage as t };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { i as stripBom } from "./collect-CFM8gEVv.js";
|
|
1
2
|
/** Requests in flight at once. Politeness, not throughput. */
|
|
2
3
|
const REQUEST_CONCURRENCY = 4;
|
|
3
4
|
var CrawlError = class extends Error {
|
|
@@ -142,31 +143,18 @@ function disallowedPaths(robots) {
|
|
|
142
143
|
return paths;
|
|
143
144
|
}
|
|
144
145
|
/**
|
|
145
|
-
*
|
|
146
|
+
* One of the two files a site publishes about itself, or undefined.
|
|
146
147
|
*
|
|
147
148
|
* Read directly rather than through fetchPage, which correctly refuses anything
|
|
148
|
-
* that is not a page. A site
|
|
149
|
+
* that is not a page. A site that does not publish one is the ordinary case,
|
|
150
|
+
* not a failure, so it comes back empty either way.
|
|
149
151
|
*/
|
|
150
|
-
async function
|
|
152
|
+
async function fetchSiteFile(entry, impl, name) {
|
|
151
153
|
try {
|
|
152
|
-
const response = await impl(new URL(
|
|
153
|
-
return response.ok ?
|
|
154
|
+
const response = await impl(new URL(name, entry).href, { redirect: "follow" });
|
|
155
|
+
return response.ok ? await response.text() : void 0;
|
|
154
156
|
} catch {
|
|
155
|
-
return
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Page URLs the site lists for itself.
|
|
160
|
-
*
|
|
161
|
-
* Worth one request: a sitemap finds pages nothing links to, which link
|
|
162
|
-
* following alone never reaches.
|
|
163
|
-
*/
|
|
164
|
-
async function sitemapUrls(entry, impl) {
|
|
165
|
-
try {
|
|
166
|
-
const response = await impl(new URL("/sitemap.xml", entry).href, { redirect: "follow" });
|
|
167
|
-
return response.ok ? urlsFromSitemap(await response.text(), entry) : [];
|
|
168
|
-
} catch {
|
|
169
|
-
return [];
|
|
157
|
+
return;
|
|
170
158
|
}
|
|
171
159
|
}
|
|
172
160
|
/**
|
|
@@ -182,7 +170,8 @@ async function crawlSite(entry, options = {}) {
|
|
|
182
170
|
const maxDepth = options.maxDepth ?? 3;
|
|
183
171
|
const timeoutMs = options.timeoutMs ?? 15e3;
|
|
184
172
|
const failures = [];
|
|
185
|
-
const
|
|
173
|
+
const robots = options.ignoreRobots ? void 0 : await fetchSiteFile(entry, impl, "/robots.txt");
|
|
174
|
+
const blocked = robots === void 0 ? [] : disallowedPaths(robots);
|
|
186
175
|
const allowed = (url) => !blocked.some((path) => url.pathname.startsWith(path));
|
|
187
176
|
let discovery = "links";
|
|
188
177
|
const queue = [];
|
|
@@ -195,7 +184,8 @@ async function crawlSite(entry, options = {}) {
|
|
|
195
184
|
depth
|
|
196
185
|
});
|
|
197
186
|
};
|
|
198
|
-
const
|
|
187
|
+
const sitemap = await fetchSiteFile(entry, impl, options.sitemap ?? "/sitemap.xml");
|
|
188
|
+
const listed = sitemap === void 0 ? [] : urlsFromSitemap(sitemap, entry);
|
|
199
189
|
if (listed.length > 0) {
|
|
200
190
|
discovery = "sitemap";
|
|
201
191
|
for (const url of listed) enqueue(url, 0);
|
|
@@ -220,7 +210,7 @@ async function crawlSite(entry, options = {}) {
|
|
|
220
210
|
pages.push({
|
|
221
211
|
absolutePath: url.href,
|
|
222
212
|
relativePath: pageIdentity(url),
|
|
223
|
-
html:
|
|
213
|
+
html: stripBom(html)
|
|
224
214
|
});
|
|
225
215
|
options.onProgress?.(pages.length, queue.length);
|
|
226
216
|
if (item.depth < maxDepth) for (const link of linksFrom(html, url)) enqueue(link, item.depth + 1);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { n as IntegrationOptions, t as BuildAuditError } from "../run-BMASMmwO.js";
|
|
2
|
+
//#region src/eleventy/index.d.ts
|
|
3
|
+
/** What `eleventy.after` is given. Only two of its fields are read. */
|
|
4
|
+
interface EleventyAfterEvent {
|
|
5
|
+
dir: {
|
|
6
|
+
input: string;
|
|
7
|
+
output: string;
|
|
8
|
+
};
|
|
9
|
+
/** `build`, `watch` or `serve`. Only a one-shot build is audited. */
|
|
10
|
+
runMode?: string;
|
|
11
|
+
/** `fs`, `json`, `ndjson` or `text`. Only `fs` writes files to audit. */
|
|
12
|
+
outputMode?: string;
|
|
13
|
+
}
|
|
14
|
+
/** The part of Eleventy's config object this plugin touches. */
|
|
15
|
+
interface EleventyConfigLike {
|
|
16
|
+
on(event: 'eleventy.after', handler: (event: EleventyAfterEvent) => Promise<void>): void;
|
|
17
|
+
}
|
|
18
|
+
type EaaKitEleventyOptions = IntegrationOptions;
|
|
19
|
+
/**
|
|
20
|
+
* Usable both as a bare plugin and as a configured one.
|
|
21
|
+
*
|
|
22
|
+
* `addPlugin(eaaKit)` and `addPlugin(eaaKit, { failOn: 'moderate' })` are both
|
|
23
|
+
* how Eleventy plugins are written, and a plugin that only supported the second
|
|
24
|
+
* would be the one people got wrong.
|
|
25
|
+
*/
|
|
26
|
+
declare function eaaKit(eleventyConfig: EleventyConfigLike, options?: EaaKitEleventyOptions): void;
|
|
27
|
+
//#endregion
|
|
28
|
+
export { BuildAuditError, EaaKitEleventyOptions, EleventyAfterEvent, EleventyConfigLike, eaaKit as default };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { n as auditBuild, r as stderrLogger, t as BuildAuditError } from "../run-DB34BSOZ.js";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
//#region src/eleventy/index.ts
|
|
4
|
+
/**
|
|
5
|
+
* Usable both as a bare plugin and as a configured one.
|
|
6
|
+
*
|
|
7
|
+
* `addPlugin(eaaKit)` and `addPlugin(eaaKit, { failOn: 'moderate' })` are both
|
|
8
|
+
* how Eleventy plugins are written, and a plugin that only supported the second
|
|
9
|
+
* would be the one people got wrong.
|
|
10
|
+
*/
|
|
11
|
+
function eaaKit(eleventyConfig, options = {}) {
|
|
12
|
+
eleventyConfig.on("eleventy.after", async (event) => {
|
|
13
|
+
if (event.runMode !== void 0 && event.runMode !== "build") return;
|
|
14
|
+
if (event.outputMode !== void 0 && event.outputMode !== "fs") return;
|
|
15
|
+
await auditBuild(path.resolve(event.dir.output), options, stderrLogger());
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
export { BuildAuditError, eaaKit as default };
|