demarches-normaliennes/app/javascript/entrypoints/axe-core.ts

156 lines
3.8 KiB
TypeScript

import type { AxeResults, NodeResult, RelatedNode } from 'axe-core';
import axe from 'axe-core';
domReady().then(() => {
axe.run(document.body, { reporter: 'v2' }).then((results) => {
logToConsole(results);
});
});
// contrasted against Chrome default color of #ffffff
const lightTheme = {
serious: '#d93251',
minor: '#d24700',
text: 'black'
};
// contrasted against Safari dark mode color of #535353
const darkTheme = {
serious: '#ffb3b3',
minor: '#ffd500',
text: 'white'
};
const theme =
window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
? darkTheme
: lightTheme;
const boldCourier = 'font-weight:bold;font-family:Courier;';
const critical = `color:${theme.serious};font-weight:bold;`;
const serious = `color:${theme.serious};font-weight:normal;`;
const moderate = `color:${theme.minor};font-weight:bold;`;
const minor = `color:${theme.minor};font-weight:normal;`;
const defaultReset = `font-color:${theme.text};font-weight:normal;`;
function logToConsole(results: AxeResults): void {
console.group('%cNew axe issues', serious);
results.violations.forEach((result) => {
let fmt: string;
switch (result.impact) {
case 'critical':
fmt = critical;
break;
case 'serious':
fmt = serious;
break;
case 'moderate':
fmt = moderate;
break;
case 'minor':
fmt = minor;
break;
default:
fmt = minor;
break;
}
console.groupCollapsed(
'%c%s: %c%s %s',
fmt,
result.impact,
defaultReset,
result.help,
result.helpUrl
);
result.nodes.forEach((node) => {
failureSummary(node, 'any');
failureSummary(node, 'none');
});
console.groupEnd();
});
console.groupEnd();
}
function failureSummary(node: NodeResult, key: AxeCoreNodeResultKey): void {
if (node[key].length > 0) {
logElement(node, console.groupCollapsed);
logHtml(node);
logFailureMessage(node, key);
let relatedNodes: RelatedNode[] = [];
node[key].forEach((check) => {
relatedNodes = relatedNodes.concat(check.relatedNodes ?? []);
});
if (relatedNodes.length > 0) {
console.groupCollapsed('Related nodes');
relatedNodes.forEach((relatedNode) => {
logElement(relatedNode, console.log);
logHtml(relatedNode);
});
console.groupEnd();
}
console.groupEnd();
}
}
function logFailureMessage(node: NodeResult, key: AxeCoreNodeResultKey): void {
// this exists on axe but we don't export it as part of the typescript
// namespace, so just let me use it as I need
const message: string = (
axe as unknown as AxeWithAudit
)._audit.data.failureSummaries[key].failureMessage(
node[key].map((check) => check.message || '')
);
console.error(message);
}
function logElement(
node: NodeResult | RelatedNode,
logFn: (...args: unknown[]) => void
): void {
const el = document.querySelector(node.target.toString());
if (!el) {
logFn('Selector: %c%s', boldCourier, node.target.toString());
} else {
logFn('Element: %o', el);
}
}
function logHtml(node: NodeResult | RelatedNode): void {
console.log('HTML: %c%s', boldCourier, node.html);
}
type AxeCoreNodeResultKey = 'any' | 'all' | 'none';
interface AxeWithAudit {
_audit: {
data: {
failureSummaries: {
any: {
failureMessage: (args: string[]) => string;
};
all: {
failureMessage: (args: string[]) => string;
};
none: {
failureMessage: (args: string[]) => string;
};
};
};
};
}
function domReady() {
return new Promise<void>((resolve) => {
if (document.readyState == 'loading') {
document.addEventListener('DOMContentLoaded', () => resolve(), {
once: true
});
} else {
resolve();
}
});
}