-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy patha11y.test.tsx
More file actions
77 lines (70 loc) · 3.35 KB
/
Copy patha11y.test.tsx
File metadata and controls
77 lines (70 loc) · 3.35 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
import { describe, it, expect, beforeEach } from 'vitest';
import { fireEvent, screen } from '@testing-library/react';
import { axe } from 'vitest-axe';
import App from './App';
import { renderWithI18n } from './test/render';
// AC-15 (the names / roles / ARIA half) — automated with axe-core against WCAG A/AA rules.
// color-contrast is excluded because jsdom has no layout engine to compute it; AA contrast stays
// on the manual release checklist (it needs a real browser). Asserting on results.violations (not
// a custom matcher) keeps the tsc build free of matcher type-augmentation.
async function violations(container: HTMLElement): Promise<string[]> {
const results = await axe(container, {
runOnly: ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'],
rules: { 'color-contrast': { enabled: false } },
// Only fully process violations — skipping pass/incomplete node detail keeps axe fast on the
// large composed-App DOM (otherwise it crawls under parallel worker load).
resultTypes: ['violations'],
});
return (results.violations ?? []).map((v) => `${v.id} — ${v.nodes.length} node(s)`);
}
// A generous timeout: axe on the whole app is heavier than a unit test, especially when the
// vitest workers run in parallel.
const A11Y_TIMEOUT = 30_000;
describe('Accessibility (AC-15) — axe-core, WCAG A/AA', () => {
beforeEach(() => localStorage.clear());
it(
'the composed app has no WCAG A/AA violations',
async () => {
const { container } = renderWithI18n(<App />, 'en');
expect(await violations(container)).toEqual([]);
},
A11Y_TIMEOUT,
);
it(
'no violations with Expert mode + the weight-override panel open',
async () => {
const { container } = renderWithI18n(<App />, 'en');
// Default view is now Home; go to the Advisor. Top nav + mobile bottom bar both render under
// css:false (jsdom applies no CSS media queries), so pick the first "Advisor" tab.
fireEvent.click(screen.getAllByRole('button', { name: 'Advisor' })[0]);
fireEvent.click(screen.getByRole('button', { name: 'Expert' }));
fireEvent.click(screen.getByRole('button', { name: /Adjust weights/ }));
expect(await violations(container)).toEqual([]);
},
A11Y_TIMEOUT,
);
it(
'no violations in the Manual/Guide with the detailed architecture explanations (lazy-loaded)',
async () => {
const { container } = renderWithI18n(<App />, 'en');
fireEvent.click(screen.getByRole('button', { name: 'Guide' }));
// Wait for the lazy Manual chunk to resolve and render its dialog.
await screen.findByRole('dialog', { name: /Manual/i });
expect(await violations(container)).toEqual([]);
},
A11Y_TIMEOUT,
);
it(
'no violations in the Learn content area, including an open article (lazy-loaded)',
async () => {
const { container } = renderWithI18n(<App />, 'en');
fireEvent.click(screen.getAllByRole('button', { name: 'Insights' })[0]); // top nav + mobile bar both in jsdom
// Drill into the Catalog (data-driven, all architectures) and open one architecture page.
fireEvent.click(await screen.findByRole('button', { name: /Catalog/ }));
fireEvent.click(await screen.findByRole('button', { name: /Microservices/ }));
await screen.findByText('TL;DR');
expect(await violations(container)).toEqual([]);
},
A11Y_TIMEOUT,
);
});