waffensachkunde

Waffensachkunde – Lernsoftware für die Sachkundeprüfung nach § 7 WaffG. Barrierefrei, offline, EUPL-1.2.

/ app tests ThemaWahl.test.tsx

4,1 KB Rohdatei
app/tests/ThemaWahl.test.tsx — 117 Zeilen
1 import { render, screen } from '@testing-library/react';
2 import userEvent from '@testing-library/user-event';
3 import axe from 'axe-core';
4 import { jsdomOptionen } from '../src/shared/wcag';
5 import { useState } from 'react';
6 import { describe, expect, it } from 'vitest';
7
8 import { ThemaWahl } from '../src/renderer/src/components/ThemaWahl';
9 import { type ThemeAuswahl } from '../src/shared/theme';
10
11 /** Minimale Hülle, die die Auswahl wie in der echten App zustandsbehaftet hält. */
12 function Pruefstand({ start = 'system' }: { start?: ThemeAuswahl }): React.JSX.Element {
13 const [auswahl, setzeAuswahl] = useState<ThemeAuswahl>(start);
14 return (
15 <>
16 <h3 id="farbschema-titel">Farbschema</h3>
17 <ThemaWahl auswahl={auswahl} onAuswahl={setzeAuswahl} beschriftetVon="farbschema-titel" />
18 </>
19 );
20 }
21
22 describe('ThemaWahl – ARIA-Struktur', () => {
23 it('rendert eine benannte Radiogruppe mit allen vier Optionen', () => {
24 render(<Pruefstand />);
25
26 const gruppe = screen.getByRole('radiogroup', { name: 'Farbschema' });
27 expect(gruppe).toBeInTheDocument();
28
29 const optionen = screen.getAllByRole('radio');
30 expect(optionen.map((o) => o.textContent.replace(/[●○]/gu, '').trim())).toEqual([
31 'Systemvorgabe',
32 'Hell',
33 'Dunkel',
34 'Hoher Kontrast',
35 ]);
36 });
37
38 it('markiert genau eine Option als ausgewählt', () => {
39 render(<Pruefstand start="dunkel" />);
40
41 expect(screen.getByRole('radio', { name: /Dunkel/u })).toBeChecked();
42 expect(
43 screen.getAllByRole('radio').filter((o) => o.getAttribute('aria-checked') === 'true'),
44 ).toHaveLength(1);
45 });
46
47 it('nutzt Roving Tabindex: nur die aktive Option ist per Tab erreichbar', () => {
48 render(<Pruefstand start="hell" />);
49
50 const optionen = screen.getAllByRole('radio');
51 const tabIndizes = optionen.map((o) => o.getAttribute('tabindex'));
52 expect(tabIndizes).toEqual(['-1', '0', '-1', '-1']);
53 });
54 });
55
56 describe('ThemaWahl – Tastaturbedienung nach ARIA APG', () => {
57 it('wählt mit Tab und Pfeiltasten ohne Maus', async () => {
58 const nutzer = userEvent.setup();
59 render(<Pruefstand start="system" />);
60
61 await nutzer.tab();
62 expect(screen.getByRole('radio', { name: /Systemvorgabe/u }).matches(':focus')).toBe(true);
63
64 await nutzer.keyboard('{ArrowRight}');
65 expect(screen.getByRole('radio', { name: /Hell/u })).toBeChecked();
66
67 await nutzer.keyboard('{ArrowRight}');
68 expect(screen.getByRole('radio', { name: /Dunkel/u })).toBeChecked();
69
70 await nutzer.keyboard('{ArrowLeft}');
71 expect(screen.getByRole('radio', { name: /Hell/u })).toBeChecked();
72 });
73
74 it('springt mit Pos1 und Ende an Anfang und Ende', async () => {
75 const nutzer = userEvent.setup();
76 render(<Pruefstand start="hell" />);
77
78 await nutzer.tab();
79 await nutzer.keyboard('{End}');
80 expect(screen.getByRole('radio', { name: /Hoher Kontrast/u })).toBeChecked();
81
82 await nutzer.keyboard('{Home}');
83 expect(screen.getByRole('radio', { name: /Systemvorgabe/u })).toBeChecked();
84 });
85
86 it('läuft an den Rändern rundherum weiter', async () => {
87 const nutzer = userEvent.setup();
88 render(<Pruefstand start="system" />);
89
90 await nutzer.tab();
91 await nutzer.keyboard('{ArrowLeft}');
92 expect(screen.getByRole('radio', { name: /Hoher Kontrast/u })).toBeChecked();
93
94 await nutzer.keyboard('{ArrowRight}');
95 expect(screen.getByRole('radio', { name: /Systemvorgabe/u })).toBeChecked();
96 });
97
98 it('wählt die fokussierte Option mit der Leertaste', async () => {
99 const nutzer = userEvent.setup();
100 render(<Pruefstand start="hell" />);
101
102 await nutzer.tab();
103 await nutzer.keyboard(' ');
104 expect(screen.getByRole('radio', { name: /Hell/u })).toBeChecked();
105 });
106 });
107
108 describe('ThemaWahl – Barrierefreiheits-Rauchtest (axe-core)', () => {
109 it('meldet keine Verstöße gegen WCAG 2.1 A/AA', async () => {
110 const { container } = render(<Pruefstand />);
111
112 const ergebnis = await axe.run(container, jsdomOptionen());
113
114 const zusammenfassung = ergebnis.violations.map((v) => `${v.id}: ${v.help}`);
115 expect(zusammenfassung).toEqual([]);
116 });
117 });