feat(a11y): add extensible accessibility test infrastructure with axe-core" -m "- Add A11yChecker interface (types.ts) as the shared contract for accessibility checkers
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
import axe from 'axe-core';
|
||||
import type { A11yChecker, A11yCheckResult } from './types';
|
||||
|
||||
export const axeCoreChecker: A11yChecker = {
|
||||
name: 'axe-core',
|
||||
|
||||
async run(rootElement: Element): Promise<A11yCheckResult> {
|
||||
const results = await axe.run(rootElement);
|
||||
|
||||
return {
|
||||
checkerName: 'axe-core',
|
||||
violations: results.violations.map((violations) => ({
|
||||
id: violations.id,
|
||||
impact: violations.impact ?? null,
|
||||
description: violations.help,
|
||||
helpUrl: violations.helpUrl,
|
||||
nodes: violations.nodes.map((node) => node.target.join(' ')),
|
||||
})),
|
||||
};
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
export interface A11yViolation {
|
||||
id: string;
|
||||
impact: 'minor' | 'moderate' | 'serious' | 'critical' | null;
|
||||
description: string;
|
||||
helpUrl?: string;
|
||||
nodes: string[];
|
||||
}
|
||||
|
||||
export interface A11yCheckResult {
|
||||
checkerName: string;
|
||||
violations: A11yViolation[];
|
||||
}
|
||||
|
||||
export interface A11yChecker {
|
||||
name: string;
|
||||
run(root: Element): Promise<A11yCheckResult>;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import type { A11yChecker, A11yCheckResult } from './checkers/types';
|
||||
import { axeCoreChecker } from './checkers/axe-core.checker';
|
||||
|
||||
const a11yCheckerRegistry: A11yChecker[] = [
|
||||
axeCoreChecker,
|
||||
];
|
||||
|
||||
export async function runA11yChecks(rootElement: Element): Promise<A11yCheckResult[]> {
|
||||
return Promise.all(a11yCheckerRegistry.map((a11yChecker) => a11yChecker.run(rootElement)));
|
||||
}
|
||||
|
||||
export type { A11yChecker, A11yCheckResult, A11yViolation } from './checkers/types';
|
||||
@@ -0,0 +1,36 @@
|
||||
import { expect } from 'vitest';
|
||||
import type { A11yCheckResult } from './checkers/types';
|
||||
|
||||
expect.extend({
|
||||
toHaveNoA11yViolations(results: A11yCheckResult[]) {
|
||||
const failing = results.filter((results) => results.violations.length > 0);
|
||||
|
||||
if (failing.length === 0) {
|
||||
return {
|
||||
pass: true,
|
||||
message: () => 'expected accessibility violations, but none were found',
|
||||
};
|
||||
}
|
||||
|
||||
const message = failing
|
||||
.map(
|
||||
(results) =>
|
||||
`[${results.checkerName}] ${results.violations.length} violation(s):\n` +
|
||||
results.violations
|
||||
.map((validation) => ` - ${validation.id} (${validation.impact}): ${validation.description}\n affected: ${validation.nodes.join(', ')}`)
|
||||
.join('\n')
|
||||
)
|
||||
.join('\n\n');
|
||||
|
||||
return {
|
||||
pass: false,
|
||||
message: () => `Accessibility violations found:\n\n${message}`,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
declare module 'vitest' {
|
||||
interface Assertion {
|
||||
toHaveNoA11yViolations(): void;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user