eaa-kit 0.3.0 → 0.5.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 +82 -14
- package/dist/astro/index.d.ts +1 -1
- package/dist/astro/index.js +1 -1
- package/dist/audit/runners/worker.js +5 -1
- package/dist/{audit-DcL73mOC.js → audit-B282A3EA.js} +207 -41
- package/dist/audit-DyHPmGeD.js +2 -0
- package/dist/{baseline-CuKFq4IF.js → baseline-0u4df12v.js} +1 -1
- package/dist/{baseline-s9F3fXTN.js → baseline-CuAS2lMk.js} +6 -5
- package/dist/cli/index.js +306 -18
- package/dist/{collect-BkAQ0viT.js → collect-Cu1q9NTB.js} +53 -11
- package/dist/command-C3D7JWn6.js +160 -0
- package/dist/{component-7kEBjv_y.js → component-C3GL1Mnu.js} +41 -6
- package/dist/component-DKd3EHOg.js +2 -0
- package/dist/coverage-B_Y6l-Ra.js +522 -0
- package/dist/{crawl-Oxt2Gaqo.js → crawl-BUWPgaGW.js} +83 -9
- package/dist/eleventy/index.d.ts +28 -0
- package/dist/eleventy/index.js +19 -0
- package/dist/fingerprint-BjYV_0F7.js +67 -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-C4vktg0A.js} +130 -5
- package/dist/{impact-YdoOtFqm.js → impact-DZt2oBCP.js} +1 -1
- package/dist/index.d.ts +119 -4
- package/dist/index.js +2 -2
- package/dist/{init-DiLiYvN1.js → init-CW7LfGT5.js} +10 -4
- package/dist/jsdom-22Bkt65v.js +3 -0
- package/dist/jsdom-B--cEH-G.js +89 -0
- package/dist/{json-Cnv9nd6U.js → json-DROX33kh.js} +9 -4
- package/dist/{json-B0Y7rNjt.js → json-D_Mnnft5.js} +1 -1
- package/dist/load-CFq2VQtT.js +2 -0
- package/dist/{load-UYXLqGV9.js → load-yAR4wzez.js} +133 -8
- package/dist/nuxt/index.d.ts +35 -0
- package/dist/nuxt/index.js +23 -0
- package/dist/{playwright-DYFsGUNd.js → playwright-BojtYVUa.js} +30 -6
- package/dist/{pool-BWkWZiJW.js → pool-BO25OIez.js} +52 -2
- package/dist/{project-DW08TseF.js → project-CzOnkLH6.js} +8 -2
- package/dist/project-MFrXcw1M.js +2 -0
- package/dist/remediation-CMBIrnpN.js +321 -0
- package/dist/{render-DI_aCnAZ.js → render-DbGOVmhx.js} +48 -10
- package/dist/{result-DLxd2Eip.js → result-BWcYXeRs.js} +114 -3
- package/dist/routes-CmdRUuOs.js +265 -0
- package/dist/{run-BW6CVuND.js → run-C2nKFcb-.js} +19 -2
- package/dist/{run-BMASMmwO.d.ts → run-CtcEUhbe.d.ts} +7 -0
- package/dist/{sarif-DB3WG7T9.js → sarif-B-UBcVu8.js} +16 -8
- package/dist/{schema-CMZ8ItGk.js → schema-DJSF4K05.js} +18 -1
- package/dist/statement/templates/es.en.md +125 -0
- package/dist/statement/templates/es.es.md +127 -0
- package/dist/statement/templates/fr.en.md +128 -0
- package/dist/statement/templates/fr.fr.md +131 -0
- package/dist/statement/templates/it.en.md +127 -0
- package/dist/statement/templates/it.it.md +130 -0
- package/dist/statement/templates/nl.en.md +125 -0
- package/dist/statement/templates/nl.nl.md +127 -0
- package/dist/{text-BFmNtMsV.js → text-CKKpzkYM.js} +1 -1
- package/dist/vite/index.d.ts +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 +35 -9
- package/dist/audit-CuG2hYyo.js +0 -2
- package/dist/command-Dxpa00Ha.js +0 -77
- package/dist/fingerprint-DRoneAjj.js +0 -20
- 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
package/dist/jsdom-BjpF-2V-.js
DELETED
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import { i as pageUrl, n as failedPage, o as runOptions, s as shapeResults, t as DEFAULT_TAGS } from "./result-DLxd2Eip.js";
|
|
2
|
-
import axe from "axe-core";
|
|
3
|
-
import { Script } from "node:vm";
|
|
4
|
-
import { JSDOM, VirtualConsole } from "jsdom";
|
|
5
|
-
//#region src/audit/runners/jsdom.ts
|
|
6
|
-
/**
|
|
7
|
-
* Rules this engine structurally cannot decide.
|
|
8
|
-
*
|
|
9
|
-
* jsdom has no layout: every element reports a 0x0 box, computed style is
|
|
10
|
-
* limited to the inline cascade, and nothing is ever fetched. axe-core does not
|
|
11
|
-
* know that, so it happily returns `pass` for some of these — `target-size`
|
|
12
|
-
* passes on any page because a 0x0 target gets measured against nothing, and
|
|
13
|
-
* `color-contrast` passes on pages whose colours were never computed. Both were
|
|
14
|
-
* observed on real sites. Reporting either as a pass would be a false clean
|
|
15
|
-
* bill of health, so every rule listed here is force-reported as incomplete no
|
|
16
|
-
* matter which bucket axe-core put it in.
|
|
17
|
-
*
|
|
18
|
-
* The browser runner passes an empty map instead: with real layout these rules
|
|
19
|
-
* are exactly the ones it exists to answer.
|
|
20
|
-
*
|
|
21
|
-
* Rules tagged `experimental` are listed for completeness but filtered out at
|
|
22
|
-
* scope time: axe-core does not run them by default, so a browser run would not
|
|
23
|
-
* evaluate them either and telling the user to re-run with --browser would be
|
|
24
|
-
* misleading.
|
|
25
|
-
*/
|
|
26
|
-
const ENGINE_BLIND_RULES = {
|
|
27
|
-
"color-contrast": {
|
|
28
|
-
detail: "needs rendered foreground and background colours",
|
|
29
|
-
applicabilityUnreliable: false
|
|
30
|
-
},
|
|
31
|
-
"color-contrast-enhanced": {
|
|
32
|
-
detail: "needs rendered foreground and background colours",
|
|
33
|
-
applicabilityUnreliable: false
|
|
34
|
-
},
|
|
35
|
-
"target-size": {
|
|
36
|
-
detail: "needs element geometry; every box is 0x0 without layout",
|
|
37
|
-
applicabilityUnreliable: false
|
|
38
|
-
},
|
|
39
|
-
"scrollable-region-focusable": {
|
|
40
|
-
detail: "needs computed overflow",
|
|
41
|
-
applicabilityUnreliable: true
|
|
42
|
-
},
|
|
43
|
-
"link-in-text-block": {
|
|
44
|
-
detail: "needs rendered colours and text decoration",
|
|
45
|
-
applicabilityUnreliable: true
|
|
46
|
-
},
|
|
47
|
-
"no-autoplay-audio": {
|
|
48
|
-
detail: "needs media duration, and media is never loaded",
|
|
49
|
-
applicabilityUnreliable: true
|
|
50
|
-
},
|
|
51
|
-
"avoid-inline-spacing": {
|
|
52
|
-
detail: "needs computed spacing after the full cascade",
|
|
53
|
-
applicabilityUnreliable: false
|
|
54
|
-
},
|
|
55
|
-
"p-as-heading": {
|
|
56
|
-
detail: "needs computed font size and weight",
|
|
57
|
-
applicabilityUnreliable: false
|
|
58
|
-
},
|
|
59
|
-
"css-orientation-lock": {
|
|
60
|
-
detail: "needs CSS media query evaluation",
|
|
61
|
-
applicabilityUnreliable: true
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
/** Per-page ceiling; one pathological document must not stall a CI run. */
|
|
65
|
-
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
66
|
-
/**
|
|
67
|
-
* Audit collected pages with axe-core inside jsdom.
|
|
68
|
-
*
|
|
69
|
-
* Pages are processed sequentially: jsdom parsing and axe-core are both
|
|
70
|
-
* CPU-bound on the main thread, so concurrency buys nothing here. A page that
|
|
71
|
-
* throws or times out is recorded with an `error` and the run continues.
|
|
72
|
-
*/
|
|
73
|
-
async function runJsdomAudit(pages, options = {}) {
|
|
74
|
-
const audits = [];
|
|
75
|
-
for (const page of pages) audits.push(await auditPage(page, options));
|
|
76
|
-
return audits;
|
|
77
|
-
}
|
|
78
|
-
async function auditPage(page, options = {}) {
|
|
79
|
-
const tags = options.tags ?? DEFAULT_TAGS;
|
|
80
|
-
const url = pageUrl(page, options.baseUrl);
|
|
81
|
-
const startedAt = Date.now();
|
|
82
|
-
const identity = {
|
|
83
|
-
relativePath: page.relativePath,
|
|
84
|
-
absolutePath: page.absolutePath,
|
|
85
|
-
url,
|
|
86
|
-
engine: "jsdom"
|
|
87
|
-
};
|
|
88
|
-
let dom;
|
|
89
|
-
try {
|
|
90
|
-
dom = createDom(page.html, url);
|
|
91
|
-
injectAxe(dom);
|
|
92
|
-
const { axe: pageAxe } = dom.window;
|
|
93
|
-
const results = await withTimeout(pageAxe.run(dom.window.document, runOptions(tags)), options.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
94
|
-
return shapeResults(results, {
|
|
95
|
-
...identity,
|
|
96
|
-
durationMs: Date.now() - startedAt,
|
|
97
|
-
blind: blindRulesInScope(tags)
|
|
98
|
-
});
|
|
99
|
-
} catch (cause) {
|
|
100
|
-
return failedPage({
|
|
101
|
-
...identity,
|
|
102
|
-
durationMs: Date.now() - startedAt
|
|
103
|
-
}, cause instanceof Error ? cause.message : String(cause));
|
|
104
|
-
} finally {
|
|
105
|
-
dom?.window.close();
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
function createDom(html, url) {
|
|
109
|
-
const virtualConsole = new VirtualConsole();
|
|
110
|
-
virtualConsole.on("jsdomError", () => {});
|
|
111
|
-
return new JSDOM(html, {
|
|
112
|
-
url,
|
|
113
|
-
virtualConsole,
|
|
114
|
-
runScripts: "outside-only",
|
|
115
|
-
pretendToBeVisual: true
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
/**
|
|
119
|
-
* axe-core is 1.3 MB of source. Compiling it once and re-running the compiled
|
|
120
|
-
* script in each window's context avoids re-parsing it for every page.
|
|
121
|
-
*/
|
|
122
|
-
let axeScript;
|
|
123
|
-
function injectAxe(dom) {
|
|
124
|
-
axeScript ??= new Script(axe.source, { filename: "axe-core.js" });
|
|
125
|
-
axeScript.runInContext(dom.getInternalVMContext());
|
|
126
|
-
}
|
|
127
|
-
const blindScopeCache = /* @__PURE__ */ new Map();
|
|
128
|
-
/**
|
|
129
|
-
* Blind rules the requested tag filter would actually have run. Experimental
|
|
130
|
-
* rules are excluded: axe-core leaves them off by default, so a browser run
|
|
131
|
-
* would not have evaluated them either.
|
|
132
|
-
*/
|
|
133
|
-
function blindRulesInScope(tags) {
|
|
134
|
-
const key = [...tags].sort().join(",");
|
|
135
|
-
const cached = blindScopeCache.get(key);
|
|
136
|
-
if (cached) return cached;
|
|
137
|
-
const inScope = /* @__PURE__ */ new Map();
|
|
138
|
-
for (const rule of axe.getRules([...tags])) {
|
|
139
|
-
if (rule.tags.includes("experimental")) continue;
|
|
140
|
-
const blind = ENGINE_BLIND_RULES[rule.ruleId];
|
|
141
|
-
if (blind) inScope.set(rule.ruleId, blind);
|
|
142
|
-
}
|
|
143
|
-
blindScopeCache.set(key, inScope);
|
|
144
|
-
return inScope;
|
|
145
|
-
}
|
|
146
|
-
async function withTimeout(promise, ms) {
|
|
147
|
-
promise.catch(() => {});
|
|
148
|
-
let timer;
|
|
149
|
-
try {
|
|
150
|
-
return await Promise.race([promise, new Promise((_resolve, reject) => {
|
|
151
|
-
timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`axe-core timed out after ${ms}ms`)), ms);
|
|
152
|
-
})]);
|
|
153
|
-
} finally {
|
|
154
|
-
clearTimeout(timer);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
//#endregion
|
|
158
|
-
export { runJsdomAudit as i, auditPage as n, blindRulesInScope as r, ENGINE_BLIND_RULES as t };
|
package/dist/jsdom-X4KYfTp8.js
DELETED
package/dist/manual-Vz-oX1I_.js
DELETED
|
@@ -1,239 +0,0 @@
|
|
|
1
|
-
import { a as impactRank } from "./impact-YdoOtFqm.js";
|
|
2
|
-
import { t as elementFingerprint } from "./fingerprint-DRoneAjj.js";
|
|
3
|
-
import { r as findingElements } from "./result-DLxd2Eip.js";
|
|
4
|
-
/** Whether this element looks like one component rendered on many pages. */
|
|
5
|
-
function isShared(element) {
|
|
6
|
-
return element.pages.length >= 3;
|
|
7
|
-
}
|
|
8
|
-
/**
|
|
9
|
-
* Fold a run's violations into one entry per rule, and one per element within it.
|
|
10
|
-
*
|
|
11
|
-
* Accepted violations are not included: a baseline moves them out of what fails
|
|
12
|
-
* the build, and this is a view of what fails.
|
|
13
|
-
*/
|
|
14
|
-
function groupIssues(audits) {
|
|
15
|
-
const byRule = /* @__PURE__ */ new Map();
|
|
16
|
-
for (const audit of audits) for (const finding of audit.violations) {
|
|
17
|
-
let issue = byRule.get(finding.ruleId);
|
|
18
|
-
if (issue === void 0) {
|
|
19
|
-
issue = {
|
|
20
|
-
ruleId: finding.ruleId,
|
|
21
|
-
help: finding.help,
|
|
22
|
-
impact: finding.impact ?? null,
|
|
23
|
-
successCriteria: finding.successCriteria,
|
|
24
|
-
enClauses: finding.enClauses,
|
|
25
|
-
helpUrl: finding.helpUrl,
|
|
26
|
-
elements: [],
|
|
27
|
-
pages: [],
|
|
28
|
-
occurrences: 0
|
|
29
|
-
};
|
|
30
|
-
byRule.set(finding.ruleId, issue);
|
|
31
|
-
}
|
|
32
|
-
if (!issue.pages.includes(audit.relativePath)) issue.pages.push(audit.relativePath);
|
|
33
|
-
for (const node of findingElements(finding)) {
|
|
34
|
-
issue.occurrences += 1;
|
|
35
|
-
const fingerprint = elementFingerprint(finding.ruleId, node.selector, node.html);
|
|
36
|
-
const existing = issue.elements.find((element) => element.fingerprint === fingerprint);
|
|
37
|
-
if (existing) {
|
|
38
|
-
if (!existing.pages.includes(audit.relativePath)) existing.pages.push(audit.relativePath);
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
issue.elements.push({
|
|
42
|
-
fingerprint,
|
|
43
|
-
selector: node.selector,
|
|
44
|
-
html: node.html,
|
|
45
|
-
pages: [audit.relativePath]
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
const issues = [...byRule.values()];
|
|
50
|
-
for (const issue of issues) {
|
|
51
|
-
issue.pages.sort();
|
|
52
|
-
for (const element of issue.elements) element.pages.sort();
|
|
53
|
-
issue.elements.sort(byReachThenSelector);
|
|
54
|
-
}
|
|
55
|
-
issues.sort(bySeverityThenReach);
|
|
56
|
-
return issues;
|
|
57
|
-
}
|
|
58
|
-
/**
|
|
59
|
-
* Unevaluated rules folded across the site, sorted by rule id.
|
|
60
|
-
*
|
|
61
|
-
* Both reports list these once at the end rather than under every page: on a
|
|
62
|
-
* large site the same handful recurs on each one, and a wall of "not evaluated"
|
|
63
|
-
* would bury the findings that are real.
|
|
64
|
-
*/
|
|
65
|
-
function blindRules(audits) {
|
|
66
|
-
const byRule = /* @__PURE__ */ new Map();
|
|
67
|
-
for (const audit of audits) for (const finding of audit.incomplete) {
|
|
68
|
-
if (finding.reason !== "engine-limitation") continue;
|
|
69
|
-
const entry = byRule.get(finding.ruleId);
|
|
70
|
-
if (entry) entry.pages += 1;
|
|
71
|
-
else byRule.set(finding.ruleId, {
|
|
72
|
-
ruleId: finding.ruleId,
|
|
73
|
-
pages: 1,
|
|
74
|
-
finding
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
return [...byRule.values()].sort((a, b) => a.ruleId.localeCompare(b.ruleId));
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* What one page's result actually rests on, as phrases both reports print.
|
|
81
|
-
*
|
|
82
|
-
* The counts stay separate on purpose. Only `passed` is evidence that a
|
|
83
|
-
* criterion was met here; `not applicable` means the rule found nothing to
|
|
84
|
-
* check, and adding the two together would turn an empty page into a
|
|
85
|
-
* near-perfect score.
|
|
86
|
-
*/
|
|
87
|
-
function coverageParts(audit) {
|
|
88
|
-
const blind = audit.incomplete.filter((finding) => finding.reason === "engine-limitation").length;
|
|
89
|
-
const review = audit.incomplete.length - blind;
|
|
90
|
-
const parts = [`${audit.passes.length} passed`, `${audit.inapplicable.length} not applicable`];
|
|
91
|
-
if (review > 0) parts.push(`${review} to review`);
|
|
92
|
-
if (blind > 0) parts.push(`${blind} not evaluated`);
|
|
93
|
-
return parts;
|
|
94
|
-
}
|
|
95
|
-
/** Widest reach first, then by selector so two runs agree. */
|
|
96
|
-
function byReachThenSelector(a, b) {
|
|
97
|
-
return b.pages.length - a.pages.length || a.selector.localeCompare(b.selector);
|
|
98
|
-
}
|
|
99
|
-
/** Worst first, then widest reach, then by rule id. */
|
|
100
|
-
function bySeverityThenReach(a, b) {
|
|
101
|
-
return impactRank(a.impact) - impactRank(b.impact) || b.pages.length - a.pages.length || a.ruleId.localeCompare(b.ruleId);
|
|
102
|
-
}
|
|
103
|
-
//#endregion
|
|
104
|
-
//#region src/audit/manual.ts
|
|
105
|
-
/**
|
|
106
|
-
* What a person has to check, and where to read about it.
|
|
107
|
-
*
|
|
108
|
-
* Automated testing finds a minority of accessibility barriers. This tool says
|
|
109
|
-
* so everywhere, and saying so is not much use on its own: a reader told that
|
|
110
|
-
* six rules "could not be evaluated" is left knowing there is a gap and not
|
|
111
|
-
* what to do about it.
|
|
112
|
-
*
|
|
113
|
-
* So each rule the browserless engine cannot decide carries the check somebody
|
|
114
|
-
* would do by hand, and each success criterion carries a link to what it
|
|
115
|
-
* actually requires. The point is to turn the honest disclaimer into the part
|
|
116
|
-
* of the report with the most work in it.
|
|
117
|
-
*/
|
|
118
|
-
/**
|
|
119
|
-
* WCAG 2.2 Understanding pages, by success criterion.
|
|
120
|
-
*
|
|
121
|
-
* The Understanding document rather than the criterion text: the text says what
|
|
122
|
-
* conformance is, and the Understanding page says what it is for and how to
|
|
123
|
-
* meet it, which is what somebody reading a report needs.
|
|
124
|
-
*
|
|
125
|
-
* Level A and AA only, since that is what this tool audits against.
|
|
126
|
-
*/
|
|
127
|
-
const UNDERSTANDING = {
|
|
128
|
-
"1.1.1": "non-text-content",
|
|
129
|
-
"1.2.1": "audio-only-and-video-only-prerecorded",
|
|
130
|
-
"1.2.2": "captions-prerecorded",
|
|
131
|
-
"1.2.3": "audio-description-or-media-alternative-prerecorded",
|
|
132
|
-
"1.2.4": "captions-live",
|
|
133
|
-
"1.2.5": "audio-description-prerecorded",
|
|
134
|
-
"1.3.1": "info-and-relationships",
|
|
135
|
-
"1.3.2": "meaningful-sequence",
|
|
136
|
-
"1.3.3": "sensory-characteristics",
|
|
137
|
-
"1.3.4": "orientation",
|
|
138
|
-
"1.3.5": "identify-input-purpose",
|
|
139
|
-
"1.4.1": "use-of-color",
|
|
140
|
-
"1.4.2": "audio-control",
|
|
141
|
-
"1.4.3": "contrast-minimum",
|
|
142
|
-
"1.4.4": "resize-text",
|
|
143
|
-
"1.4.5": "images-of-text",
|
|
144
|
-
"1.4.10": "reflow",
|
|
145
|
-
"1.4.11": "non-text-contrast",
|
|
146
|
-
"1.4.12": "text-spacing",
|
|
147
|
-
"1.4.13": "content-on-hover-or-focus",
|
|
148
|
-
"2.1.1": "keyboard",
|
|
149
|
-
"2.1.2": "no-keyboard-trap",
|
|
150
|
-
"2.1.4": "character-key-shortcuts",
|
|
151
|
-
"2.2.1": "timing-adjustable",
|
|
152
|
-
"2.2.2": "pause-stop-hide",
|
|
153
|
-
"2.3.1": "three-flashes-or-below-threshold",
|
|
154
|
-
"2.4.1": "bypass-blocks",
|
|
155
|
-
"2.4.2": "page-titled",
|
|
156
|
-
"2.4.3": "focus-order",
|
|
157
|
-
"2.4.4": "link-purpose-in-context",
|
|
158
|
-
"2.4.5": "multiple-ways",
|
|
159
|
-
"2.4.6": "headings-and-labels",
|
|
160
|
-
"2.4.7": "focus-visible",
|
|
161
|
-
"2.4.11": "focus-not-obscured-minimum",
|
|
162
|
-
"2.5.1": "pointer-gestures",
|
|
163
|
-
"2.5.2": "pointer-cancellation",
|
|
164
|
-
"2.5.3": "label-in-name",
|
|
165
|
-
"2.5.4": "motion-actuation",
|
|
166
|
-
"2.5.7": "dragging-movements",
|
|
167
|
-
"2.5.8": "target-size-minimum",
|
|
168
|
-
"3.1.1": "language-of-page",
|
|
169
|
-
"3.1.2": "language-of-parts",
|
|
170
|
-
"3.2.1": "on-focus",
|
|
171
|
-
"3.2.2": "on-input",
|
|
172
|
-
"3.2.3": "consistent-navigation",
|
|
173
|
-
"3.2.4": "consistent-identification",
|
|
174
|
-
"3.2.6": "consistent-help",
|
|
175
|
-
"3.3.1": "error-identification",
|
|
176
|
-
"3.3.2": "labels-or-instructions",
|
|
177
|
-
"3.3.3": "error-suggestion",
|
|
178
|
-
"3.3.4": "error-prevention-legal-financial-data",
|
|
179
|
-
"3.3.7": "redundant-entry",
|
|
180
|
-
"3.3.8": "accessible-authentication-minimum",
|
|
181
|
-
"4.1.2": "name-role-value",
|
|
182
|
-
"4.1.3": "status-messages"
|
|
183
|
-
};
|
|
184
|
-
/** Where to read what a success criterion requires, or undefined if unknown. */
|
|
185
|
-
function understandingUrl(criterion) {
|
|
186
|
-
const slug = UNDERSTANDING[criterion];
|
|
187
|
-
return slug === void 0 ? void 0 : `https://www.w3.org/WAI/WCAG22/Understanding/${slug}.html`;
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* The check a person does for a rule this engine could not decide.
|
|
191
|
-
*
|
|
192
|
-
* Written as an action rather than a restatement of the criterion. "Ensure
|
|
193
|
-
* sufficient contrast" tells somebody nothing they did not already know; the
|
|
194
|
-
* useful sentence names what to open and what to look at.
|
|
195
|
-
*/
|
|
196
|
-
const MANUAL_CHECKS = {
|
|
197
|
-
"color-contrast": {
|
|
198
|
-
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.",
|
|
199
|
-
browserAnswers: true
|
|
200
|
-
},
|
|
201
|
-
"color-contrast-enhanced": {
|
|
202
|
-
check: "Only needed if you are claiming AAA. Body text needs 7:1 and large text 4.5:1.",
|
|
203
|
-
browserAnswers: true
|
|
204
|
-
},
|
|
205
|
-
"target-size": {
|
|
206
|
-
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.",
|
|
207
|
-
browserAnswers: true
|
|
208
|
-
},
|
|
209
|
-
"scrollable-region-focusable": {
|
|
210
|
-
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.",
|
|
211
|
-
browserAnswers: true
|
|
212
|
-
},
|
|
213
|
-
"link-in-text-block": {
|
|
214
|
-
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.",
|
|
215
|
-
browserAnswers: true
|
|
216
|
-
},
|
|
217
|
-
"no-autoplay-audio": {
|
|
218
|
-
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.",
|
|
219
|
-
browserAnswers: false
|
|
220
|
-
},
|
|
221
|
-
"avoid-inline-spacing": {
|
|
222
|
-
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.",
|
|
223
|
-
browserAnswers: true
|
|
224
|
-
},
|
|
225
|
-
"p-as-heading": {
|
|
226
|
-
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.",
|
|
227
|
-
browserAnswers: false
|
|
228
|
-
},
|
|
229
|
-
"css-orientation-lock": {
|
|
230
|
-
check: "Rotate a phone or tablet. The content should work in both orientations unless one is essential, which is rare outside games and instruments.",
|
|
231
|
-
browserAnswers: false
|
|
232
|
-
}
|
|
233
|
-
};
|
|
234
|
-
/** The check for a rule, if there is one written for it. */
|
|
235
|
-
function manualCheckFor(ruleId) {
|
|
236
|
-
return MANUAL_CHECKS[ruleId];
|
|
237
|
-
}
|
|
238
|
-
//#endregion
|
|
239
|
-
export { groupIssues as a, coverageParts as i, understandingUrl as n, isShared as o, blindRules as r, manualCheckFor as t };
|
package/dist/routes-C2Cgf6Ko.js
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { i as toPosix, n as isDirectory } from "./fs-BmPtmFke.js";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { glob } from "tinyglobby";
|
|
4
|
-
//#region src/audit/routes.ts
|
|
5
|
-
/** Route file conventions, most specific first. */
|
|
6
|
-
const CONVENTIONS = [
|
|
7
|
-
{
|
|
8
|
-
framework: "next-app",
|
|
9
|
-
dir: "app",
|
|
10
|
-
pattern: "**/page.{tsx,ts,jsx,js,mdx}"
|
|
11
|
-
},
|
|
12
|
-
{
|
|
13
|
-
framework: "next-app",
|
|
14
|
-
dir: "src/app",
|
|
15
|
-
pattern: "**/page.{tsx,ts,jsx,js,mdx}"
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
framework: "next-pages",
|
|
19
|
-
dir: "pages",
|
|
20
|
-
pattern: "**/*.{tsx,ts,jsx,js,mdx}"
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
framework: "next-pages",
|
|
24
|
-
dir: "src/pages",
|
|
25
|
-
pattern: "**/*.{tsx,ts,jsx,js,mdx}"
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
framework: "astro",
|
|
29
|
-
dir: "src/pages",
|
|
30
|
-
pattern: "**/*.{astro,md,mdx}"
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
framework: "nuxt",
|
|
34
|
-
dir: "pages",
|
|
35
|
-
pattern: "**/*.vue"
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
framework: "sveltekit",
|
|
39
|
-
dir: "src/routes",
|
|
40
|
-
pattern: "**/+page.{svelte,ts,js}"
|
|
41
|
-
}
|
|
42
|
-
];
|
|
43
|
-
/**
|
|
44
|
-
* The URL path a route file serves.
|
|
45
|
-
*
|
|
46
|
-
* Returns undefined for anything that is not a page: a Next.js route group
|
|
47
|
-
* `(marketing)` contributes no segment, a private folder `_lib` contributes no
|
|
48
|
-
* route at all, and a dynamic segment `[slug]` cannot be resolved to one path
|
|
49
|
-
* without knowing the data behind it.
|
|
50
|
-
*/
|
|
51
|
-
function routePathFor(relativeFile, framework) {
|
|
52
|
-
let route = toPosix(relativeFile);
|
|
53
|
-
if (framework === "next-app" || framework === "sveltekit") route = route.includes("/") ? route.replace(/\/[^/]+$/, "") : "";
|
|
54
|
-
else {
|
|
55
|
-
route = route.replace(/\.[^./]+$/, "");
|
|
56
|
-
route = route.replace(/\/index$/, "");
|
|
57
|
-
if (route === "index") route = "";
|
|
58
|
-
}
|
|
59
|
-
const segments = route.split("/").filter((segment) => segment !== "");
|
|
60
|
-
const kept = [];
|
|
61
|
-
for (const segment of segments) {
|
|
62
|
-
if (/^[[(]|^_|^@/.test(segment)) {
|
|
63
|
-
if (/^\(.*\)$/.test(segment)) continue;
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
kept.push(segment);
|
|
67
|
-
}
|
|
68
|
-
return kept.join("/");
|
|
69
|
-
}
|
|
70
|
-
/** Every page path a build could have emitted for one route. */
|
|
71
|
-
function emittedPathsFor(route) {
|
|
72
|
-
if (route === "") return [
|
|
73
|
-
"/",
|
|
74
|
-
"index.html",
|
|
75
|
-
"index.htm"
|
|
76
|
-
];
|
|
77
|
-
return [
|
|
78
|
-
route,
|
|
79
|
-
`${route}/`,
|
|
80
|
-
`${route}.html`,
|
|
81
|
-
`${route}/index.html`
|
|
82
|
-
];
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Build a page-to-source map by reading the project's route files.
|
|
86
|
-
*
|
|
87
|
-
* Returns undefined when the project uses no convention this understands, which
|
|
88
|
-
* is not a failure — most builds are not framework projects.
|
|
89
|
-
*/
|
|
90
|
-
async function buildRouteMap(cwd) {
|
|
91
|
-
for (const convention of CONVENTIONS) {
|
|
92
|
-
const directory = path.join(cwd, convention.dir);
|
|
93
|
-
if (!await isDirectory(directory)) continue;
|
|
94
|
-
const files = await glob([convention.pattern], {
|
|
95
|
-
cwd: directory,
|
|
96
|
-
ignore: ["**/node_modules/**"],
|
|
97
|
-
onlyFiles: true
|
|
98
|
-
});
|
|
99
|
-
if (files.length === 0) continue;
|
|
100
|
-
const sources = /* @__PURE__ */ new Map();
|
|
101
|
-
for (const file of files) {
|
|
102
|
-
const route = routePathFor(file, convention.framework);
|
|
103
|
-
if (route === void 0) continue;
|
|
104
|
-
const source = `${convention.dir}/${toPosix(file)}`;
|
|
105
|
-
for (const emitted of emittedPathsFor(route)) if (!sources.has(emitted)) sources.set(emitted, source);
|
|
106
|
-
}
|
|
107
|
-
if (sources.size > 0) return {
|
|
108
|
-
framework: convention.framework,
|
|
109
|
-
sources
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
/** The source file for an audited page, if the map knows one. */
|
|
114
|
-
function sourceFor(map, pagePath) {
|
|
115
|
-
if (map === void 0) return void 0;
|
|
116
|
-
return map.sources.get(pagePath) ?? map.sources.get(pagePath.replace(/^\//, ""));
|
|
117
|
-
}
|
|
118
|
-
//#endregion
|
|
119
|
-
export { buildRouteMap, sourceFor };
|