Skip to content

Commit e213ac4

Browse files
committed
html - add minimal axe checker implementation
1 parent e877271 commit e213ac4

File tree

6 files changed

+85
-3
lines changed

6 files changed

+85
-3
lines changed

src/format/html/format-html-axe.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* format-html-axe.ts
3+
*
4+
* Copyright (C) 2020-2025 Posit Software, PBC
5+
*/
6+
7+
import { kIncludeInHeader } from "../../config/constants.ts";
8+
import { Format, FormatExtras } from "../../config/types.ts";
9+
import { TempContext } from "../../core/temp-types.ts";
10+
import { encodeBase64 } from "../../deno_ral/encoding.ts";
11+
12+
export function axeFormatDependencies(
13+
_format: Format,
14+
temp: TempContext,
15+
options?: unknown,
16+
): FormatExtras {
17+
if (options === undefined) {
18+
return {};
19+
}
20+
21+
return {
22+
[kIncludeInHeader]: [
23+
temp.createFileFromString(
24+
`<script id="quarto-axe-checker-options" type="text/plain">${
25+
encodeBase64(JSON.stringify(options))
26+
}</script>`,
27+
),
28+
],
29+
};
30+
}

src/format/html/format-html-shared.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ export const kComments = "comments";
5656
export const kHypothesis = "hypothesis";
5757
export const kUtterances = "utterances";
5858
export const kGiscus = "giscus";
59+
export const kAxe = "axe";
5960

6061
export const kGiscusRepoId = "repo-id";
6162
export const kGiscusCategoryId = "category-id";

src/format/html/format-html.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*
66
* Copyright (C) 2020-2022 Posit Software, PBC
77
*/
8-
import { dirname, join, relative } from "../../deno_ral/path.ts";
8+
import { join, relative } from "../../deno_ral/path.ts";
99
import { warning } from "../../deno_ral/log.ts";
1010

1111
import * as ld from "../../core/lodash.ts";
@@ -68,6 +68,7 @@ import {
6868
clipboardDependency,
6969
createCodeCopyButton,
7070
kAnchorSections,
71+
kAxe,
7172
kBootstrapDependencyName,
7273
kCitationsHover,
7374
kCodeAnnotations,
@@ -116,8 +117,9 @@ import {
116117
import { kQuartoHtmlDependency } from "./format-html-constants.ts";
117118
import { registerWriterFormatHandler } from "../format-handlers.ts";
118119
import { brandSassFormatExtras } from "../../core/sass/brand.ts";
119-
import { ESBuildAnalysis, esbuildAnalyze } from "../../core/esbuild.ts";
120+
import { ESBuildAnalysis } from "../../core/esbuild.ts";
120121
import { assert } from "testing/asserts";
122+
import { axeFormatDependencies } from "./format-html-axe.ts";
121123

122124
let esbuildAnalysisCache: Record<string, ESBuildAnalysis> | undefined;
123125
export function esbuildCachedAnalysis(
@@ -245,6 +247,10 @@ export async function htmlFormatExtras(
245247
tippyOptions?: HtmlFormatTippyOptions,
246248
scssOptions?: HtmlFormatScssOptions,
247249
): Promise<FormatExtras> {
250+
const configurableExtras: FormatExtras[] = [
251+
axeFormatDependencies(format, temp, format.metadata[kAxe]),
252+
];
253+
248254
// note whether we are targeting bootstrap
249255
const bootstrap = formatHasBootstrap(format);
250256

@@ -645,7 +651,7 @@ export async function htmlFormatExtras(
645651
}
646652

647653
const metadata: Metadata = {};
648-
return {
654+
const result: FormatExtras = {
649655
[kIncludeInHeader]: includeInHeader,
650656
[kIncludeBeforeBody]: includeBeforeBody,
651657
[kIncludeAfterBody]: includeAfterBody,
@@ -657,6 +663,11 @@ export async function htmlFormatExtras(
657663
[kHtmlPostprocessors]: htmlPostProcessors,
658664
},
659665
};
666+
667+
return mergeConfigs(
668+
result,
669+
...configurableExtras,
670+
) as FormatExtras;
660671
}
661672

662673
const kFormatHasBootstrap = "has-bootstrap";
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
class QuartoAxeChecker {
2+
constructor(opts) {
3+
this.options = opts;
4+
}
5+
async init() {
6+
const axe = (await import("https://cdn.skypack.dev/pin/[email protected]/mode=imports,min/optimized/axe-core.js")).default;
7+
const result = await axe.run({
8+
preload: { assets: ['cssom'], timeout: 50000 }
9+
});
10+
if (this.options.output === "json") {
11+
console.log(JSON.stringify(result, null, 2));
12+
return;
13+
}
14+
for (const violation of result.violations) {
15+
console.log(violation.description);
16+
for (const node of violation.nodes) {
17+
for (const target of node.target) {
18+
console.log(target);
19+
console.log(document.querySelector(target));
20+
}
21+
}
22+
}
23+
}
24+
}
25+
26+
export async function init() {
27+
const opts = document.querySelector("#quarto-axe-checker-options");
28+
if (opts) {
29+
const jsonOptions = JSON.parse(atob(opts.textContent));
30+
const checker = new QuartoAxeChecker(jsonOptions);
31+
await checker.init();
32+
}
33+
}

src/resources/formats/html/esbuild-analysis-cache.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@
1414
"path": "./tabsets/tabsets.js",
1515
"kind": "import-statement",
1616
"external": true
17+
},
18+
{
19+
"path": "./axe/axe-check.js",
20+
"kind": "import-statement",
21+
"external": true
1722
}
1823
],
1924
"exports": [],

src/resources/formats/html/quarto.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import * as tabsets from "./tabsets/tabsets.js";
2+
import * as axe from "./axe/axe-check.js";
23

34
const sectionChanged = new CustomEvent("quarto-sectionChanged", {
45
detail: {},
@@ -826,6 +827,7 @@ window.document.addEventListener("DOMContentLoaded", function (_event) {
826827
});
827828

828829
tabsets.init();
830+
axe.init();
829831

830832
function throttle(func, wait) {
831833
let waiting = false;

0 commit comments

Comments
 (0)