eaa-kit 0.3.0 → 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 -5
- package/dist/astro/index.js +1 -1
- package/dist/audit/runners/worker.js +1 -1
- package/dist/{audit-DcL73mOC.js → audit-CPoZMXGM.js} +180 -36
- package/dist/audit-CpXH2Mk8.js +2 -0
- package/dist/{baseline-CuKFq4IF.js → baseline-22Y1NWxM.js} +1 -1
- package/dist/{baseline-s9F3fXTN.js → baseline-DB9CZGnV.js} +2 -2
- package/dist/cli/index.js +267 -10
- package/dist/{collect-BkAQ0viT.js → collect-CFM8gEVv.js} +21 -10
- package/dist/{command-Dxpa00Ha.js → command-D8l_oYbV.js} +2 -2
- package/dist/{component-7kEBjv_y.js → component-C3GL1Mnu.js} +41 -6
- package/dist/component-DKd3EHOg.js +2 -0
- package/dist/coverage-B4IBKMO8.js +521 -0
- package/dist/{crawl-Oxt2Gaqo.js → crawl-BiI1Lau_.js} +2 -2
- package/dist/eleventy/index.d.ts +28 -0
- package/dist/eleventy/index.js +19 -0
- package/dist/{frameworks-BYa3tULg.js → frameworks-B4ClIJgE.js} +71 -0
- package/dist/{frameworks-DaDqrJOw.js → frameworks-etFg_O8K.js} +1 -1
- package/dist/{html-DKiI_3gs.js → html-BGTO3ypW.js} +130 -5
- package/dist/{impact-YdoOtFqm.js → impact-DZt2oBCP.js} +1 -1
- package/dist/index.js +2 -2
- package/dist/{init-DiLiYvN1.js → init-DIWDE35F.js} +2 -2
- package/dist/jsdom-4IMzv0eE.js +3 -0
- package/dist/jsdom-DCpGSLfW.js +81 -0
- package/dist/{json-Cnv9nd6U.js → json-DjEvy1nX.js} +8 -3
- package/dist/{json-B0Y7rNjt.js → json-QQuFIw1W.js} +1 -1
- package/dist/{load-UYXLqGV9.js → load-5wRGLvub.js} +1 -1
- package/dist/nuxt/index.d.ts +35 -0
- package/dist/nuxt/index.js +23 -0
- package/dist/{playwright-DYFsGUNd.js → playwright-BWniOain.js} +1 -1
- package/dist/{pool-BWkWZiJW.js → pool-BMevaLWD.js} +2 -2
- package/dist/{project-DW08TseF.js → project-CzOnkLH6.js} +8 -2
- package/dist/project-MFrXcw1M.js +2 -0
- package/dist/remediation-Dtowi2EC.js +321 -0
- package/dist/{render-DI_aCnAZ.js → render-DrvXRCEn.js} +3 -3
- package/dist/{result-DLxd2Eip.js → result-DoamKFsp.js} +78 -1
- package/dist/routes-CmdRUuOs.js +265 -0
- package/dist/{run-BW6CVuND.js → run-DB34BSOZ.js} +19 -2
- package/dist/{sarif-DB3WG7T9.js → sarif-SR3_lLYd.js} +14 -6
- package/dist/{schema-CMZ8ItGk.js → schema-is6CGX2D.js} +4 -1
- package/dist/{text-BFmNtMsV.js → text-CKKpzkYM.js} +1 -1
- package/dist/vite/index.js +1 -1
- package/dist/webpack/index.d.ts +33 -0
- package/dist/webpack/index.js +20 -0
- package/package.json +30 -8
- package/dist/audit-CuG2hYyo.js +0 -2
- package/dist/jsdom-BjpF-2V-.js +0 -158
- package/dist/jsdom-X4KYfTp8.js +0 -3
- package/dist/manual-Vz-oX1I_.js +0 -239
- package/dist/routes-C2Cgf6Ko.js +0 -119
|
@@ -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,4 +1,4 @@
|
|
|
1
|
-
import { i as stripBom } from "./collect-
|
|
1
|
+
import { i as stripBom } from "./collect-CFM8gEVv.js";
|
|
2
2
|
/** Requests in flight at once. Politeness, not throughput. */
|
|
3
3
|
const REQUEST_CONCURRENCY = 4;
|
|
4
4
|
var CrawlError = class extends Error {
|
|
@@ -184,7 +184,7 @@ async function crawlSite(entry, options = {}) {
|
|
|
184
184
|
depth
|
|
185
185
|
});
|
|
186
186
|
};
|
|
187
|
-
const sitemap = await fetchSiteFile(entry, impl, "/sitemap.xml");
|
|
187
|
+
const sitemap = await fetchSiteFile(entry, impl, options.sitemap ?? "/sitemap.xml");
|
|
188
188
|
const listed = sitemap === void 0 ? [] : urlsFromSitemap(sitemap, entry);
|
|
189
189
|
if (listed.length > 0) {
|
|
190
190
|
discovery = "sitemap";
|
|
@@ -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 };
|
|
@@ -156,6 +156,77 @@ const FRAMEWORKS = [
|
|
|
156
156
|
outputs: ["_site"],
|
|
157
157
|
serves: false
|
|
158
158
|
},
|
|
159
|
+
{
|
|
160
|
+
id: "wordpress",
|
|
161
|
+
name: "WordPress",
|
|
162
|
+
packages: [],
|
|
163
|
+
files: [
|
|
164
|
+
"wp-config.php",
|
|
165
|
+
"wp-config-sample.php",
|
|
166
|
+
"wp-load.php"
|
|
167
|
+
],
|
|
168
|
+
outputs: [],
|
|
169
|
+
serves: true,
|
|
170
|
+
serveCommand: "wp-env start, ddev start, or whichever local stack this site uses"
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
id: "typo3",
|
|
174
|
+
name: "TYPO3",
|
|
175
|
+
packages: [],
|
|
176
|
+
files: [
|
|
177
|
+
"typo3conf",
|
|
178
|
+
"public/typo3conf",
|
|
179
|
+
"typo3"
|
|
180
|
+
],
|
|
181
|
+
outputs: [],
|
|
182
|
+
serves: true,
|
|
183
|
+
serveCommand: "ddev start, or your usual local stack"
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
id: "craft",
|
|
187
|
+
name: "Craft CMS",
|
|
188
|
+
packages: [],
|
|
189
|
+
files: ["craft"],
|
|
190
|
+
outputs: [],
|
|
191
|
+
serves: true,
|
|
192
|
+
serveCommand: "ddev start, or php craft serve"
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
id: "laravel",
|
|
196
|
+
name: "Laravel",
|
|
197
|
+
packages: [],
|
|
198
|
+
files: ["artisan"],
|
|
199
|
+
outputs: [],
|
|
200
|
+
serves: true,
|
|
201
|
+
serveCommand: "php artisan serve"
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
id: "symfony",
|
|
205
|
+
name: "Symfony",
|
|
206
|
+
packages: [],
|
|
207
|
+
files: ["bin/console", "symfony.lock"],
|
|
208
|
+
outputs: [],
|
|
209
|
+
serves: true,
|
|
210
|
+
serveCommand: "symfony serve, or php -S localhost:8000 -t public"
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
id: "rails",
|
|
214
|
+
name: "Ruby on Rails",
|
|
215
|
+
packages: [],
|
|
216
|
+
files: ["bin/rails", "config.ru"],
|
|
217
|
+
outputs: [],
|
|
218
|
+
serves: true,
|
|
219
|
+
serveCommand: "bin/rails server"
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
id: "django",
|
|
223
|
+
name: "Django",
|
|
224
|
+
packages: [],
|
|
225
|
+
files: ["manage.py"],
|
|
226
|
+
outputs: [],
|
|
227
|
+
serves: true,
|
|
228
|
+
serveCommand: "python manage.py runserver"
|
|
229
|
+
},
|
|
159
230
|
{
|
|
160
231
|
id: "vite",
|
|
161
232
|
name: "Vite",
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as detectFramework, t as FALLBACK_OUTPUTS } from "./frameworks-
|
|
1
|
+
import { i as detectFramework, t as FALLBACK_OUTPUTS } from "./frameworks-B4ClIJgE.js";
|
|
2
2
|
export { FALLBACK_OUTPUTS, detectFramework };
|