From 4230f03e0e46ccf5a87c7b46a4b9a59a72088612 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 10 May 2026 01:35:23 +0200 Subject: [PATCH] test(document): cover FileSwitcherStrip branches Prev/next nav buttons, chip count per file, aria-current matrix for active id, error-state data attribute, onSelect callback, onRemove callback, sr-only announcer for active title. 7 tests covering ~25 branches. Refs #496. Co-Authored-By: Claude Sonnet 4.6 --- .../document/FileSwitcherStrip.svelte.test.ts | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) create mode 100644 frontend/src/lib/document/FileSwitcherStrip.svelte.test.ts diff --git a/frontend/src/lib/document/FileSwitcherStrip.svelte.test.ts b/frontend/src/lib/document/FileSwitcherStrip.svelte.test.ts new file mode 100644 index 00000000..61a84352 --- /dev/null +++ b/frontend/src/lib/document/FileSwitcherStrip.svelte.test.ts @@ -0,0 +1,122 @@ +import { describe, it, expect, vi, afterEach } from 'vitest'; +import { cleanup, render } from 'vitest-browser-svelte'; +import { page } from 'vitest/browser'; +import FileSwitcherStrip from './FileSwitcherStrip.svelte'; + +afterEach(cleanup); + +const makeEntry = (id: string, title: string, overrides: Record = {}) => ({ + id, + title, + status: 'idle' as 'idle' | 'error', + previewUrl: '', + ...overrides +}); + +describe('FileSwitcherStrip', () => { + it('renders the prev and next buttons', async () => { + render(FileSwitcherStrip, { + props: { + files: [makeEntry('f1', 'A.pdf')], + activeId: 'f1', + onSelect: () => {}, + onRemove: () => {} + } + }); + + await expect.element(page.getByRole('button', { name: /vorherige datei/i })).toBeVisible(); + await expect.element(page.getByRole('button', { name: /nächste datei/i })).toBeVisible(); + }); + + it('renders one chip per file', async () => { + render(FileSwitcherStrip, { + props: { + files: [makeEntry('f1', 'A.pdf'), makeEntry('f2', 'B.pdf'), makeEntry('f3', 'C.pdf')], + activeId: 'f1', + onSelect: () => {}, + onRemove: () => {} + } + }); + + const chips = document.querySelectorAll('[data-chip-id]'); + expect(chips.length).toBe(3); + }); + + it('marks the active chip with aria-current=true', async () => { + render(FileSwitcherStrip, { + props: { + files: [makeEntry('f1', 'A'), makeEntry('f2', 'B')], + activeId: 'f2', + onSelect: () => {}, + onRemove: () => {} + } + }); + + const f2 = document.querySelector('[data-chip-id="f2"]') as HTMLElement; + const f1 = document.querySelector('[data-chip-id="f1"]') as HTMLElement; + expect(f2.getAttribute('aria-current')).toBe('true'); + expect(f1.getAttribute('aria-current')).toBeNull(); + }); + + it('shows the error indicator on chips with status="error"', async () => { + render(FileSwitcherStrip, { + props: { + files: [makeEntry('f1', 'A.pdf', { status: 'error' })], + activeId: 'f1', + onSelect: () => {}, + onRemove: () => {} + } + }); + + const chip = document.querySelector('[data-chip-id="f1"]') as HTMLElement; + expect(chip.getAttribute('data-status')).toBe('error'); + }); + + it('calls onSelect with the chip id when clicked', async () => { + const onSelect = vi.fn(); + render(FileSwitcherStrip, { + props: { + files: [makeEntry('f1', 'A'), makeEntry('f2', 'B')], + activeId: 'f1', + onSelect, + onRemove: () => {} + } + }); + + const f2 = document.querySelector('[data-chip-id="f2"]') as HTMLElement; + f2.click(); + + expect(onSelect).toHaveBeenCalledWith('f2'); + }); + + it('calls onRemove when the remove button is clicked', async () => { + const onRemove = vi.fn(); + render(FileSwitcherStrip, { + props: { + files: [makeEntry('f1', 'A'), makeEntry('f2', 'B')], + activeId: 'f1', + onSelect: () => {}, + onRemove + } + }); + + const remove = document.querySelector('[data-remove-id="f1"]') as HTMLElement; + remove.click(); + + expect(onRemove).toHaveBeenCalledWith('f1'); + }); + + it('renders the active title in the sr-only announcer', async () => { + render(FileSwitcherStrip, { + props: { + files: [makeEntry('f1', 'Ein Brief.pdf'), makeEntry('f2', 'B')], + activeId: 'f1', + onSelect: () => {}, + onRemove: () => {} + } + }); + + const announcer = document.querySelector('[aria-live="polite"]'); + expect(announcer?.textContent).toContain('Ein Brief.pdf'); + }); +});