176 lines
6.4 KiB
TypeScript
176 lines
6.4 KiB
TypeScript
import { vi, describe, it, expect, afterEach } from 'vitest';
|
|
import { cleanup, render } from 'vitest-browser-svelte';
|
|
import { page } from 'vitest/browser';
|
|
|
|
import PdfControls from './PdfControls.svelte';
|
|
|
|
afterEach(cleanup);
|
|
|
|
const defaultProps = {
|
|
currentPage: 1,
|
|
totalPages: 3,
|
|
isLoaded: true,
|
|
showAnnotations: false,
|
|
annotationCount: 0,
|
|
onPrev: vi.fn(),
|
|
onNext: vi.fn(),
|
|
onZoomIn: vi.fn(),
|
|
onZoomOut: vi.fn(),
|
|
onToggleAnnotations: vi.fn()
|
|
};
|
|
|
|
describe('PdfControls — annotation toggle visibility', () => {
|
|
it('renders annotation toggle when annotationCount is greater than zero', async () => {
|
|
render(PdfControls, { ...defaultProps, annotationCount: 3 });
|
|
await expect
|
|
.element(page.getByRole('button', { name: /annotierungen anzeigen/i }))
|
|
.toBeInTheDocument();
|
|
});
|
|
|
|
it('does not render annotation toggle when annotationCount is zero', async () => {
|
|
render(PdfControls, { ...defaultProps, annotationCount: 0 });
|
|
await expect
|
|
.element(page.getByRole('button', { name: /annotierungen/i }))
|
|
.not.toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('PdfControls — annotation toggle label', () => {
|
|
it('shows "Annotierungen anzeigen" label when annotations are hidden', async () => {
|
|
render(PdfControls, { ...defaultProps, annotationCount: 2, showAnnotations: false });
|
|
const btn = page.getByRole('button', { name: /annotierungen anzeigen/i });
|
|
await expect.element(btn).toBeInTheDocument();
|
|
});
|
|
|
|
it('shows "Annotierungen verbergen" label when annotations are visible', async () => {
|
|
render(PdfControls, { ...defaultProps, annotationCount: 2, showAnnotations: true });
|
|
const btn = page.getByRole('button', { name: /annotierungen verbergen/i });
|
|
await expect.element(btn).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('PdfControls — annotation toggle contrast (WCAG 2.1 AA)', () => {
|
|
it('uses text-primary class on annotation toggle button when annotations are hidden', async () => {
|
|
const { container } = render(PdfControls, {
|
|
...defaultProps,
|
|
annotationCount: 2,
|
|
showAnnotations: false
|
|
});
|
|
const allButtons = container.querySelectorAll('button');
|
|
const annotationBtn = Array.from(allButtons).find((b) =>
|
|
b.getAttribute('aria-label')?.toLowerCase().includes('annotierungen')
|
|
);
|
|
expect(annotationBtn).not.toBeNull();
|
|
expect(annotationBtn!.className).toContain('text-primary');
|
|
expect(annotationBtn!.className).not.toContain('text-accent');
|
|
});
|
|
});
|
|
|
|
describe('PdfControls — focus rings (WCAG 2.1 §2.4.7)', () => {
|
|
it('annotation toggle button has focus-visible:ring-2 focus ring', async () => {
|
|
const { container } = render(PdfControls, {
|
|
...defaultProps,
|
|
annotationCount: 2,
|
|
showAnnotations: false
|
|
});
|
|
const allButtons = container.querySelectorAll('button');
|
|
const annotationBtn = Array.from(allButtons).find((b) =>
|
|
b.getAttribute('aria-label')?.toLowerCase().includes('annotierungen')
|
|
);
|
|
expect(annotationBtn).not.toBeNull();
|
|
expect(annotationBtn!.className).toContain('focus-visible:ring-2');
|
|
});
|
|
|
|
it('icon-only nav/zoom buttons each have focus-visible:ring-2 focus ring', async () => {
|
|
const { container } = render(PdfControls, { ...defaultProps });
|
|
const allButtons = container.querySelectorAll('button');
|
|
const iconOnlyButtons = Array.from(allButtons).filter((b) => {
|
|
const label = b.getAttribute('aria-label') ?? '';
|
|
return ['zurück', 'weiter', 'verkleinern', 'vergrößern'].includes(label.toLowerCase());
|
|
});
|
|
expect(iconOnlyButtons).toHaveLength(4);
|
|
for (const btn of iconOnlyButtons) {
|
|
expect(btn.className).toContain('focus-visible:ring-2');
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('PdfControls — touch targets (WCAG 2.2 §2.5.8)', () => {
|
|
it('annotation toggle button has min-h-[44px] touch target', async () => {
|
|
const { container } = render(PdfControls, {
|
|
...defaultProps,
|
|
annotationCount: 2,
|
|
showAnnotations: false
|
|
});
|
|
const allButtons = container.querySelectorAll('button');
|
|
const annotationBtn = Array.from(allButtons).find((b) =>
|
|
b.getAttribute('aria-label')?.toLowerCase().includes('annotierungen')
|
|
);
|
|
expect(annotationBtn).not.toBeNull();
|
|
expect(annotationBtn!.className).toContain('min-h-[44px]');
|
|
});
|
|
|
|
it('annotation toggle button has min-w-[44px] touch target', async () => {
|
|
const { container } = render(PdfControls, {
|
|
...defaultProps,
|
|
annotationCount: 2,
|
|
showAnnotations: false
|
|
});
|
|
const allButtons = container.querySelectorAll('button');
|
|
const annotationBtn = Array.from(allButtons).find((b) =>
|
|
b.getAttribute('aria-label')?.toLowerCase().includes('annotierungen')
|
|
);
|
|
expect(annotationBtn).not.toBeNull();
|
|
expect(annotationBtn!.className).toContain('min-w-[44px]');
|
|
});
|
|
|
|
it('annotation toggle reflects pressed state via aria-pressed', async () => {
|
|
const { container: c1 } = render(PdfControls, {
|
|
...defaultProps,
|
|
annotationCount: 2,
|
|
showAnnotations: false
|
|
});
|
|
const btn1 = Array.from(c1.querySelectorAll('button')).find((b) =>
|
|
b.getAttribute('aria-label')?.toLowerCase().includes('annotierungen')
|
|
);
|
|
expect(btn1!.getAttribute('aria-pressed')).toBe('false');
|
|
cleanup();
|
|
|
|
const { container: c2 } = render(PdfControls, {
|
|
...defaultProps,
|
|
annotationCount: 2,
|
|
showAnnotations: true
|
|
});
|
|
const btn2 = Array.from(c2.querySelectorAll('button')).find((b) =>
|
|
b.getAttribute('aria-label')?.toLowerCase().includes('annotierungen')
|
|
);
|
|
expect(btn2!.getAttribute('aria-pressed')).toBe('true');
|
|
});
|
|
|
|
it('icon-only nav/zoom buttons each have min-h-[44px] touch target', async () => {
|
|
const { container } = render(PdfControls, { ...defaultProps });
|
|
const allButtons = container.querySelectorAll('button');
|
|
const iconOnlyButtons = Array.from(allButtons).filter((b) => {
|
|
const label = b.getAttribute('aria-label') ?? '';
|
|
return ['zurück', 'weiter', 'verkleinern', 'vergrößern'].includes(label.toLowerCase());
|
|
});
|
|
expect(iconOnlyButtons).toHaveLength(4);
|
|
for (const btn of iconOnlyButtons) {
|
|
expect(btn.className).toContain('min-h-[44px]');
|
|
}
|
|
});
|
|
|
|
it('icon-only nav/zoom buttons each have min-w-[44px] touch target', async () => {
|
|
const { container } = render(PdfControls, { ...defaultProps });
|
|
const allButtons = container.querySelectorAll('button');
|
|
const iconOnlyButtons = Array.from(allButtons).filter((b) => {
|
|
const label = b.getAttribute('aria-label') ?? '';
|
|
return ['zurück', 'weiter', 'verkleinern', 'vergrößern'].includes(label.toLowerCase());
|
|
});
|
|
expect(iconOnlyButtons).toHaveLength(4);
|
|
for (const btn of iconOnlyButtons) {
|
|
expect(btn.className).toContain('min-w-[44px]');
|
|
}
|
|
});
|
|
});
|