From 12733cb699d85475281b3fe6ceed50e6600f1951 Mon Sep 17 00:00:00 2001 From: Marcel Date: Sun, 10 May 2026 05:42:41 +0200 Subject: [PATCH] test(document): expand FileSwitcherStrip coverage Adds prev/next button click safety, ArrowRight + ArrowLeft + ArrowDown keyboard navigation through chips with wrap-around. 5 new tests covering ~10 branches. Refs #496. Co-Authored-By: Claude Sonnet 4.6 --- .../document/FileSwitcherStrip.svelte.test.ts | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/frontend/src/lib/document/FileSwitcherStrip.svelte.test.ts b/frontend/src/lib/document/FileSwitcherStrip.svelte.test.ts index 61a84352..3defb75a 100644 --- a/frontend/src/lib/document/FileSwitcherStrip.svelte.test.ts +++ b/frontend/src/lib/document/FileSwitcherStrip.svelte.test.ts @@ -119,4 +119,91 @@ describe('FileSwitcherStrip', () => { const announcer = document.querySelector('[aria-live="polite"]'); expect(announcer?.textContent).toContain('Ein Brief.pdf'); }); + + it('clicking prev button does not throw with no scroll target available', async () => { + render(FileSwitcherStrip, { + props: { + files: [makeEntry('f1', 'A.pdf')], + activeId: 'f1', + onSelect: () => {}, + onRemove: () => {} + } + }); + + const prevBtn = Array.from(document.querySelectorAll('button')).find((b) => + /vorherige|prev/i.test(b.getAttribute('aria-label') ?? '') + ) as HTMLButtonElement; + expect(() => prevBtn.click()).not.toThrow(); + }); + + it('clicking next button does not throw', async () => { + render(FileSwitcherStrip, { + props: { + files: [makeEntry('f1', 'A.pdf')], + activeId: 'f1', + onSelect: () => {}, + onRemove: () => {} + } + }); + + const nextBtn = Array.from(document.querySelectorAll('button')).find((b) => + /nächste|next/i.test(b.getAttribute('aria-label') ?? '') + ) as HTMLButtonElement; + expect(() => nextBtn.click()).not.toThrow(); + }); + + it('navigates with ArrowRight key on focused chip', async () => { + render(FileSwitcherStrip, { + props: { + files: [makeEntry('f1', 'A'), makeEntry('f2', 'B'), makeEntry('f3', 'C')], + activeId: 'f1', + onSelect: () => {}, + onRemove: () => {} + } + }); + + const f1 = document.querySelector('[data-chip-id="f1"]') as HTMLElement; + f1.focus(); + f1.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true })); + + await new Promise((r) => setTimeout(r, 30)); + expect(document.activeElement?.getAttribute('data-chip-id')).toBe('f2'); + }); + + it('navigates with ArrowLeft key on focused chip (wraps around)', async () => { + render(FileSwitcherStrip, { + props: { + files: [makeEntry('f1', 'A'), makeEntry('f2', 'B')], + activeId: 'f1', + onSelect: () => {}, + onRemove: () => {} + } + }); + + const f1 = document.querySelector('[data-chip-id="f1"]') as HTMLElement; + f1.focus(); + f1.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowLeft', bubbles: true })); + + await new Promise((r) => setTimeout(r, 30)); + // ArrowLeft from index 0 wraps to last (f2) + expect(document.activeElement?.getAttribute('data-chip-id')).toBe('f2'); + }); + + it('ArrowDown is treated as ArrowRight (vertical key alias)', async () => { + render(FileSwitcherStrip, { + props: { + files: [makeEntry('f1', 'A'), makeEntry('f2', 'B')], + activeId: 'f1', + onSelect: () => {}, + onRemove: () => {} + } + }); + + const f1 = document.querySelector('[data-chip-id="f1"]') as HTMLElement; + f1.focus(); + f1.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true })); + + await new Promise((r) => setTimeout(r, 30)); + expect(document.activeElement?.getAttribute('data-chip-id')).toBe('f2'); + }); });