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.
Files changed (50) hide show
  1. package/README.md +51 -5
  2. package/dist/astro/index.js +1 -1
  3. package/dist/audit/runners/worker.js +1 -1
  4. package/dist/{audit-DcL73mOC.js → audit-CPoZMXGM.js} +180 -36
  5. package/dist/audit-CpXH2Mk8.js +2 -0
  6. package/dist/{baseline-CuKFq4IF.js → baseline-22Y1NWxM.js} +1 -1
  7. package/dist/{baseline-s9F3fXTN.js → baseline-DB9CZGnV.js} +2 -2
  8. package/dist/cli/index.js +267 -10
  9. package/dist/{collect-BkAQ0viT.js → collect-CFM8gEVv.js} +21 -10
  10. package/dist/{command-Dxpa00Ha.js → command-D8l_oYbV.js} +2 -2
  11. package/dist/{component-7kEBjv_y.js → component-C3GL1Mnu.js} +41 -6
  12. package/dist/component-DKd3EHOg.js +2 -0
  13. package/dist/coverage-B4IBKMO8.js +521 -0
  14. package/dist/{crawl-Oxt2Gaqo.js → crawl-BiI1Lau_.js} +2 -2
  15. package/dist/eleventy/index.d.ts +28 -0
  16. package/dist/eleventy/index.js +19 -0
  17. package/dist/{frameworks-BYa3tULg.js → frameworks-B4ClIJgE.js} +71 -0
  18. package/dist/{frameworks-DaDqrJOw.js → frameworks-etFg_O8K.js} +1 -1
  19. package/dist/{html-DKiI_3gs.js → html-BGTO3ypW.js} +130 -5
  20. package/dist/{impact-YdoOtFqm.js → impact-DZt2oBCP.js} +1 -1
  21. package/dist/index.js +2 -2
  22. package/dist/{init-DiLiYvN1.js → init-DIWDE35F.js} +2 -2
  23. package/dist/jsdom-4IMzv0eE.js +3 -0
  24. package/dist/jsdom-DCpGSLfW.js +81 -0
  25. package/dist/{json-Cnv9nd6U.js → json-DjEvy1nX.js} +8 -3
  26. package/dist/{json-B0Y7rNjt.js → json-QQuFIw1W.js} +1 -1
  27. package/dist/{load-UYXLqGV9.js → load-5wRGLvub.js} +1 -1
  28. package/dist/nuxt/index.d.ts +35 -0
  29. package/dist/nuxt/index.js +23 -0
  30. package/dist/{playwright-DYFsGUNd.js → playwright-BWniOain.js} +1 -1
  31. package/dist/{pool-BWkWZiJW.js → pool-BMevaLWD.js} +2 -2
  32. package/dist/{project-DW08TseF.js → project-CzOnkLH6.js} +8 -2
  33. package/dist/project-MFrXcw1M.js +2 -0
  34. package/dist/remediation-Dtowi2EC.js +321 -0
  35. package/dist/{render-DI_aCnAZ.js → render-DrvXRCEn.js} +3 -3
  36. package/dist/{result-DLxd2Eip.js → result-DoamKFsp.js} +78 -1
  37. package/dist/routes-CmdRUuOs.js +265 -0
  38. package/dist/{run-BW6CVuND.js → run-DB34BSOZ.js} +19 -2
  39. package/dist/{sarif-DB3WG7T9.js → sarif-SR3_lLYd.js} +14 -6
  40. package/dist/{schema-CMZ8ItGk.js → schema-is6CGX2D.js} +4 -1
  41. package/dist/{text-BFmNtMsV.js → text-CKKpzkYM.js} +1 -1
  42. package/dist/vite/index.js +1 -1
  43. package/dist/webpack/index.d.ts +33 -0
  44. package/dist/webpack/index.js +20 -0
  45. package/package.json +30 -8
  46. package/dist/audit-CuG2hYyo.js +0 -2
  47. package/dist/jsdom-BjpF-2V-.js +0 -158
  48. package/dist/jsdom-X4KYfTp8.js +0 -3
  49. package/dist/manual-Vz-oX1I_.js +0 -239
  50. package/dist/routes-C2Cgf6Ko.js +0 -119
@@ -1,5 +1,22 @@
1
1
  //#region src/integration/run.ts
2
2
  /**
3
+ * Where an integration writes when its host offers no logger of its own.
4
+ *
5
+ * stderr rather than stdout, so a line about accessibility never lands in the
6
+ * middle of a report being piped to a file, and prefixed either way: a message
7
+ * in somebody's build log has to say what produced it.
8
+ */
9
+ function stderrLogger() {
10
+ const write = (message) => {
11
+ process.stderr.write(`eaa-kit: ${message}\n`);
12
+ };
13
+ return {
14
+ info: write,
15
+ warn: write,
16
+ error: write
17
+ };
18
+ }
19
+ /**
3
20
  * Thrown to fail the build. Its own class so a consumer can tell an audit
4
21
  * failure from the build tool falling over.
5
22
  */
@@ -18,7 +35,7 @@ async function auditBuild(directory, options, logger) {
18
35
  logger.info("skipped (enabled: false)");
19
36
  return;
20
37
  }
21
- const { runAuditCommand } = await import("./audit-CuG2hYyo.js");
38
+ const { runAuditCommand } = await import("./audit-CpXH2Mk8.js");
22
39
  const { exitCode } = await runAuditCommand(directory, auditOptions);
23
40
  if (exitCode === 0) {
24
41
  logger.info("no violations at or above the threshold");
@@ -33,4 +50,4 @@ async function auditBuild(directory, options, logger) {
33
50
  throw new BuildAuditError(`eaa-kit: ${message}`);
34
51
  }
35
52
  //#endregion
36
- export { auditBuild as n, BuildAuditError as t };
53
+ export { auditBuild as n, stderrLogger as r, BuildAuditError as t };
@@ -1,8 +1,8 @@
1
- import { o as isImpactLevel } from "./impact-YdoOtFqm.js";
2
- import { a as standardsReference } from "./text-BFmNtMsV.js";
1
+ import { o as isImpactLevel } from "./impact-DZt2oBCP.js";
2
+ import { o as standardsReference } from "./text-CKKpzkYM.js";
3
3
  import { t as TOOL_VERSION } from "./version-B3v4rNoG.js";
4
4
  import { t as elementFingerprint } from "./fingerprint-DRoneAjj.js";
5
- import { a as ruleOutcomes, r as findingElements } from "./result-DLxd2Eip.js";
5
+ import { a as findingElements, s as ruleOutcomes } from "./result-DoamKFsp.js";
6
6
  import path from "node:path";
7
7
  //#region src/audit/report/sarif.ts
8
8
  const SARIF_VERSION = "2.1.0";
@@ -75,7 +75,7 @@ function buildSarifReport(audits, options) {
75
75
  toolExecutionNotifications: notifications
76
76
  }],
77
77
  results,
78
- properties: summaryProperties(audits)
78
+ properties: summaryProperties(audits, options.completeness)
79
79
  }]
80
80
  };
81
81
  }
@@ -140,7 +140,7 @@ function toSarifRule(outcome) {
140
140
  * Coverage that has no place in `results` but should not vanish: a SARIF log
141
141
  * with no results must not be mistaken for "everything was checked".
142
142
  */
143
- function summaryProperties(audits) {
143
+ function summaryProperties(audits, completeness) {
144
144
  let needsReview = 0;
145
145
  let notEvaluated = 0;
146
146
  const notEvaluatedRules = /* @__PURE__ */ new Set();
@@ -153,7 +153,15 @@ function summaryProperties(audits) {
153
153
  pages: audits.length,
154
154
  needsReview,
155
155
  notEvaluated,
156
- notEvaluatedRules: [...notEvaluatedRules].sort()
156
+ notEvaluatedRules: [...notEvaluatedRules].sort(),
157
+ ...completeness ? {
158
+ complete: completeness.complete,
159
+ discovery: completeness.discovery,
160
+ pagesAudited: completeness.audited,
161
+ pagesErrored: completeness.errored,
162
+ pagesUnreachable: completeness.unreachable.length,
163
+ truncated: completeness.truncated
164
+ } : {}
157
165
  };
158
166
  }
159
167
  /**
@@ -64,6 +64,9 @@ function string(options = {}) {
64
64
  return value;
65
65
  } };
66
66
  }
67
+ function boolean() {
68
+ return { read: (value, path, issues) => typeof value === "boolean" ? value : fail(issues, path, "expected true or false") };
69
+ }
67
70
  function number() {
68
71
  return { read: (value, path, issues) => typeof value === "number" && Number.isFinite(value) ? value : fail(issues, path, "expected a number") };
69
72
  }
@@ -189,4 +192,4 @@ function email() {
189
192
  } };
190
193
  }
191
194
  //#endregion
192
- export { withDefault as _, isoDateTime as a, object as c, record as d, safeParse as f, url as g, union as h, isoDate as i, optional as l, transform as m, email as n, nullable as o, string as p, enumeration as r, number as s, array as t, pipe as u };
195
+ export { url as _, isoDate as a, number as c, pipe as d, record as f, union as g, transform as h, enumeration as i, object as l, string as m, boolean as n, isoDateTime as o, safeParse as p, email as r, nullable as s, array as t, optional as u, withDefault as v };
@@ -40,4 +40,4 @@ function standardsReference(successCriteria, enClauses) {
40
40
  return [...successCriteria.map((criterion) => `WCAG ${criterion}`), ...enClauses.map((clause) => `EN 301 549 ${clause}`)].join(", ");
41
41
  }
42
42
  //#endregion
43
- export { standardsReference as a, escapeText as i, count as n, escapeAttribute as r, collapse as t };
43
+ export { plural as a, escapeText as i, count as n, standardsReference as o, escapeAttribute as r, collapse as t };
@@ -1,4 +1,4 @@
1
- import { n as auditBuild, t as BuildAuditError } from "../run-BW6CVuND.js";
1
+ import { n as auditBuild, t as BuildAuditError } from "../run-DB34BSOZ.js";
2
2
  import path from "node:path";
3
3
  //#region src/vite/index.ts
4
4
  function eaaKit(options = {}) {
@@ -0,0 +1,33 @@
1
+ import { n as IntegrationOptions, t as BuildAuditError } from "../run-BMASMmwO.js";
2
+ //#region src/webpack/index.d.ts
3
+ /** The part of a webpack compilation this reads. */
4
+ interface CompilationLike {
5
+ /** Non-empty when the build itself failed; there is nothing to audit then. */
6
+ errors: readonly unknown[];
7
+ }
8
+ /** The part of the webpack compiler this plugin touches. */
9
+ interface CompilerLike {
10
+ options: {
11
+ output?: {
12
+ path?: string;
13
+ };
14
+ mode?: string;
15
+ };
16
+ watchMode?: boolean;
17
+ hooks: {
18
+ afterEmit: {
19
+ tapPromise(name: string, handler: (compilation: CompilationLike) => Promise<void>): void;
20
+ };
21
+ };
22
+ }
23
+ interface EaaKitWebpackOptions extends IntegrationOptions {
24
+ /** Directory to audit. Defaults to webpack's own `output.path`. */
25
+ directory?: string;
26
+ }
27
+ declare class EaaKitWebpackPlugin {
28
+ private readonly options;
29
+ constructor(options?: EaaKitWebpackOptions);
30
+ apply(compiler: CompilerLike): void;
31
+ }
32
+ //#endregion
33
+ export { BuildAuditError, CompilationLike, CompilerLike, EaaKitWebpackOptions, EaaKitWebpackPlugin as default };
@@ -0,0 +1,20 @@
1
+ import { n as auditBuild, r as stderrLogger, t as BuildAuditError } from "../run-DB34BSOZ.js";
2
+ import path from "node:path";
3
+ //#region src/webpack/index.ts
4
+ var EaaKitWebpackPlugin = class {
5
+ options;
6
+ constructor(options = {}) {
7
+ this.options = options;
8
+ }
9
+ apply(compiler) {
10
+ compiler.hooks.afterEmit.tapPromise("eaa-kit", async (compilation) => {
11
+ if (compiler.watchMode === true) return;
12
+ if (compilation.errors.length > 0) return;
13
+ const output = this.options.directory ?? compiler.options.output?.path;
14
+ if (output === void 0) throw new BuildAuditError("eaa-kit: webpack has no output.path, so there is no directory to audit. Set one, or pass `directory` to the plugin.");
15
+ await auditBuild(path.resolve(output), this.options, stderrLogger());
16
+ });
17
+ }
18
+ };
19
+ //#endregion
20
+ export { BuildAuditError, EaaKitWebpackPlugin as default };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eaa-kit",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "WCAG 2.2 AA auditor and EU accessibility statement generator (EAA / BFSG / BaFG). Audits a static build or a running site from the command line.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -34,15 +34,33 @@
34
34
  "exports": {
35
35
  ".": {
36
36
  "types": "./dist/index.d.ts",
37
- "import": "./dist/index.js"
37
+ "import": "./dist/index.js",
38
+ "require": "./dist/index.js"
38
39
  },
39
40
  "./astro": {
40
41
  "types": "./dist/astro/index.d.ts",
41
- "import": "./dist/astro/index.js"
42
+ "import": "./dist/astro/index.js",
43
+ "require": "./dist/astro/index.js"
42
44
  },
43
45
  "./vite": {
44
46
  "types": "./dist/vite/index.d.ts",
45
- "import": "./dist/vite/index.js"
47
+ "import": "./dist/vite/index.js",
48
+ "require": "./dist/vite/index.js"
49
+ },
50
+ "./eleventy": {
51
+ "types": "./dist/eleventy/index.d.ts",
52
+ "import": "./dist/eleventy/index.js",
53
+ "require": "./dist/eleventy/index.js"
54
+ },
55
+ "./webpack": {
56
+ "types": "./dist/webpack/index.d.ts",
57
+ "import": "./dist/webpack/index.js",
58
+ "require": "./dist/webpack/index.js"
59
+ },
60
+ "./nuxt": {
61
+ "types": "./dist/nuxt/index.d.ts",
62
+ "import": "./dist/nuxt/index.js",
63
+ "require": "./dist/nuxt/index.js"
46
64
  },
47
65
  "./package.json": "./package.json"
48
66
  },
@@ -57,13 +75,13 @@
57
75
  "typecheck": "tsc --noEmit",
58
76
  "lint": "biome check .",
59
77
  "format": "biome check --write .",
60
- "smoke": "pnpm build && node dist/cli/index.js audit tests/fixtures/site --include about/** --format json && node dist/cli/index.js audit tests/fixtures/site --include \"about/**\" \"blog/**\" \"drafts/**\" \"legacy.htm\" --concurrency 2 && node dist/cli/index.js audit tests/fixtures/site --include about/** --format html && node dist/cli/index.js audit tests/fixtures/site --baseline examples/baseline.json && node -e \"import('./dist/astro/index.js').then(m => { const i = m.default(); if (i.name !== 'eaa-kit' || typeof i.hooks['astro:build:done'] !== 'function') { throw new Error('astro entry point is not an integration') } console.log('astro entry ok') })\" && node dist/cli/index.js statement --config examples/eaa.config.json && node dist/cli/index.js statement --config examples/eaa.config.json --audit examples/report.json --format html",
78
+ "smoke": "pnpm build && node dist/cli/index.js audit tests/fixtures/site --include about/** --format json && node dist/cli/index.js audit tests/fixtures/site --include \"about/**\" \"blog/**\" \"drafts/**\" \"legacy.htm\" --concurrency 2 && node dist/cli/index.js audit tests/fixtures/site --include about/** --format html && node dist/cli/index.js audit tests/fixtures/site --baseline examples/baseline.json && node -e \"import('./dist/astro/index.js').then(m => { const i = m.default(); if (i.name !== 'eaa-kit' || typeof i.hooks['astro:build:done'] !== 'function') { throw new Error('astro entry point is not an integration') } console.log('astro entry ok') })\" && node dist/cli/index.js statement --config examples/eaa.config.json && node dist/cli/index.js statement --config examples/eaa.config.json --audit examples/report.json --format html && node dist/cli/index.js audit tests/fixtures/site --include about/** --format json --output .eaa-kit/smoke-base.json && node dist/cli/index.js audit tests/fixtures/site --include about/** --format json --output .eaa-kit/smoke-head.json && node dist/cli/index.js diff .eaa-kit/smoke-base.json .eaa-kit/smoke-head.json --format json",
61
79
  "examples": "pnpm build && node scripts/generate-examples.mjs",
62
80
  "test:packaged": "pnpm build && node scripts/test-packaged.mjs"
63
81
  },
64
82
  "peerDependencies": {
65
- "playwright": ">=1.40.0",
66
- "astro": ">=4.0.0"
83
+ "astro": ">=4.0.0",
84
+ "playwright": ">=1.40.0"
67
85
  },
68
86
  "peerDependenciesMeta": {
69
87
  "playwright": {
@@ -81,16 +99,20 @@
81
99
  "tinyglobby": "^0.2.17"
82
100
  },
83
101
  "devDependencies": {
102
+ "@11ty/eleventy": "^3.1.6",
84
103
  "@biomejs/biome": "^2.5.9",
85
104
  "@types/jsdom": "^30.0.0",
86
105
  "@types/node": "^26.2.0",
87
106
  "ajv": "^8.20.0",
88
107
  "ajv-formats": "^3.0.1",
89
108
  "astro": "^7.2.9",
109
+ "nuxt": "^4.5.2",
90
110
  "playwright": "^1.62.1",
91
111
  "tsdown": "^0.22.14",
92
112
  "typescript": "^7.0.2",
93
- "vitest": "^4.1.11"
113
+ "vite": "^8.2.2",
114
+ "vitest": "^4.1.11",
115
+ "webpack": "^5.110.2"
94
116
  },
95
117
  "publishConfig": {
96
118
  "access": "public",
@@ -1,2 +0,0 @@
1
- import { n as runAuditCommand } from "./audit-DcL73mOC.js";
2
- export { runAuditCommand };
@@ -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 };
@@ -1,3 +0,0 @@
1
- import "./result-DLxd2Eip.js";
2
- import { i as runJsdomAudit } from "./jsdom-BjpF-2V-.js";
3
- export { runJsdomAudit };
@@ -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 };