From 4244e8cd29eb9884627dff559b8c64f16e8fa001 Mon Sep 17 00:00:00 2001 From: Lucas Berger Date: Fri, 5 Jun 2026 20:40:40 -0400 Subject: [PATCH] =?UTF-8?q?test(03-12):=20RED=20=E2=80=94=20WR-07=20focus?= =?UTF-8?q?=20trap=20Tab/Shift+Tab=20cycle=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add two failing tests for the focus trap: - Tab from last focusable element must wrap to first inside dialog - Shift+Tab from first focusable element must wrap to last inside dialog Both fail today because EventForm only calls .focus() once on open; Tab escapes the modal to background content. --- apps/pwa/src/components/EventForm.test.tsx | 54 ++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/apps/pwa/src/components/EventForm.test.tsx b/apps/pwa/src/components/EventForm.test.tsx index 7be13f9..3d07730 100644 --- a/apps/pwa/src/components/EventForm.test.tsx +++ b/apps/pwa/src/components/EventForm.test.tsx @@ -616,4 +616,58 @@ describe('EventForm — Plan 03-12 gap closures', () => { // Should return a YYYY-MM-DD string expect(result).toMatch(/^\d{4}-\d{2}-\d{2}$/) }) + + // ── WR-07: Tab/Shift+Tab focus trap ────────────────────────────────────── + // + // The dialog must trap Tab focus: pressing Tab from the last focusable element + // wraps to the first, and Shift+Tab from the first wraps to the last. + // Today EventForm only calls .focus() once on open — Tab escapes the modal. + + it('WR-07: Tab from last focusable element wraps focus to first inside dialog', () => { + renderForm({ mode: 'create' }) + + const dialog = screen.getByRole('dialog') + const focusable = Array.from( + dialog.querySelectorAll( + 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])', + ), + ) + expect(focusable.length).toBeGreaterThan(1) + + const lastElement = focusable[focusable.length - 1] + const firstElement = focusable[0] + + // Focus the last element, then dispatch Tab + lastElement.focus() + expect(document.activeElement).toBe(lastElement) + + fireEvent.keyDown(dialog, { key: 'Tab', shiftKey: false }) + + // WR-07 fix: focus should have wrapped to first element inside dialog + expect(document.activeElement).toBe(firstElement) + }) + + it('WR-07: Shift+Tab from first focusable element wraps focus to last inside dialog', () => { + renderForm({ mode: 'create' }) + + const dialog = screen.getByRole('dialog') + const focusable = Array.from( + dialog.querySelectorAll( + 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])', + ), + ) + expect(focusable.length).toBeGreaterThan(1) + + const firstElement = focusable[0] + const lastElement = focusable[focusable.length - 1] + + // Focus the first element, then dispatch Shift+Tab + firstElement.focus() + expect(document.activeElement).toBe(firstElement) + + fireEvent.keyDown(dialog, { key: 'Tab', shiftKey: true }) + + // WR-07 fix: focus should have wrapped to last element inside dialog + expect(document.activeElement).toBe(lastElement) + }) })