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'); + }); });