From cbacfa34b330588810f0e116f805141db867cfd0 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 18 May 2026 01:20:28 +0000 Subject: [PATCH] Fix lint error and update tests for WCAG role changes - Replace el! non-null assertion with optional chaining in useFocusTrap - Update AutodraftSettings tests to query role="radio" instead of role="button" (buttons have an explicit radio role since the WCAG pass) - Update AvailableParticipantsSection watchlist tests to use getByRole/getAllByRole instead of getByTitle/getAllByTitle (watchlist buttons now use aria-label instead of title) https://claude.ai/code/session_01JXajpFxhqLf8aPCncP81k3 --- .../__tests__/AutodraftSettings.test.tsx | 64 +++++++++---------- .../AvailableParticipantsSection.test.tsx | 10 +-- app/hooks/useFocusTrap.ts | 2 +- 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/app/components/__tests__/AutodraftSettings.test.tsx b/app/components/__tests__/AutodraftSettings.test.tsx index 95a7fb8..68e4501 100644 --- a/app/components/__tests__/AutodraftSettings.test.tsx +++ b/app/components/__tests__/AutodraftSettings.test.tsx @@ -33,10 +33,10 @@ describe('AutodraftSettings Component', () => { it('renders all four option buttons', () => { render(); - expect(screen.getByRole('button', { name: 'Off' })).toBeInTheDocument(); - expect(screen.getByRole('button', { name: 'Next in Queue' })).toBeInTheDocument(); - expect(screen.getByRole('button', { name: 'All in Queue' })).toBeInTheDocument(); - expect(screen.getByRole('button', { name: 'All Picks' })).toBeInTheDocument(); + expect(screen.getByRole('radio', { name: 'Off' })).toBeInTheDocument(); + expect(screen.getByRole('radio', { name: 'Next in Queue' })).toBeInTheDocument(); + expect(screen.getByRole('radio', { name: 'All in Queue' })).toBeInTheDocument(); + expect(screen.getByRole('radio', { name: 'All Picks' })).toBeInTheDocument(); }); it('does not render a queue-only toggle switch', () => { @@ -51,24 +51,24 @@ describe('AutodraftSettings Component', () => { it('marks Off as active (muted border) when isEnabled=false', () => { render(); - expect(screen.getByRole('button', { name: 'Off' }).className).toContain( + expect(screen.getByRole('radio', { name: 'Off' }).className).toContain( 'border-l-muted-foreground' ); }); it('marks Next in Queue as active when isEnabled=true, mode=next_pick', () => { render(); - expect(screen.getByRole('button', { name: 'Next in Queue' }).className).toContain('bg-electric'); + expect(screen.getByRole('radio', { name: 'Next in Queue' }).className).toContain('bg-electric'); }); it('marks All in Queue as active when isEnabled=true, mode=while_on, queueOnly=true', () => { render(); - expect(screen.getByRole('button', { name: 'All in Queue' }).className).toContain('bg-electric'); + expect(screen.getByRole('radio', { name: 'All in Queue' }).className).toContain('bg-electric'); }); it('marks All Picks as active when isEnabled=true, mode=while_on, queueOnly=false', () => { render(); - expect(screen.getByRole('button', { name: 'All Picks' }).className).toContain('bg-electric'); + expect(screen.getByRole('radio', { name: 'All Picks' }).className).toContain('bg-electric'); }); }); @@ -79,7 +79,7 @@ describe('AutodraftSettings Component', () => { const onUpdate = vi.fn(); render(); - fireEvent.click(screen.getByRole('button', { name: 'Next in Queue' })); + fireEvent.click(screen.getByRole('radio', { name: 'Next in Queue' })); await waitFor(() => { expect(global.fetch).toHaveBeenCalledWith( @@ -94,7 +94,7 @@ describe('AutodraftSettings Component', () => { const onUpdate = vi.fn(); render(); - fireEvent.click(screen.getByRole('button', { name: 'All in Queue' })); + fireEvent.click(screen.getByRole('radio', { name: 'All in Queue' })); await waitFor(() => expect(onUpdate).toHaveBeenCalledWith(true, 'while_on', true)); }); @@ -103,7 +103,7 @@ describe('AutodraftSettings Component', () => { const onUpdate = vi.fn(); render(); - fireEvent.click(screen.getByRole('button', { name: 'All Picks' })); + fireEvent.click(screen.getByRole('radio', { name: 'All Picks' })); await waitFor(() => expect(onUpdate).toHaveBeenCalledWith(true, 'while_on', false)); }); @@ -114,7 +114,7 @@ describe('AutodraftSettings Component', () => { ); - fireEvent.click(screen.getByRole('button', { name: 'Off' })); + fireEvent.click(screen.getByRole('radio', { name: 'Off' })); await waitFor(() => expect(onUpdate).toHaveBeenCalledWith(false, 'next_pick', false)); }); @@ -122,21 +122,21 @@ describe('AutodraftSettings Component', () => { it('disables all buttons when isMyTurn=true', () => { render(); - expect(screen.getByRole('button', { name: 'Off' })).toBeDisabled(); - expect(screen.getByRole('button', { name: 'Next in Queue' })).toBeDisabled(); - expect(screen.getByRole('button', { name: 'All in Queue' })).toBeDisabled(); - expect(screen.getByRole('button', { name: 'All Picks' })).toBeDisabled(); + expect(screen.getByRole('radio', { name: 'Off' })).toBeDisabled(); + expect(screen.getByRole('radio', { name: 'Next in Queue' })).toBeDisabled(); + expect(screen.getByRole('radio', { name: 'All in Queue' })).toBeDisabled(); + expect(screen.getByRole('radio', { name: 'All Picks' })).toBeDisabled(); }); it('does not call fetch when a button is clicked during the user\'s turn', () => { render(); - fireEvent.click(screen.getByRole('button', { name: 'Next in Queue' })); + fireEvent.click(screen.getByRole('radio', { name: 'Next in Queue' })); expect(global.fetch).not.toHaveBeenCalled(); }); it('does not re-fire when the already-active option is clicked', async () => { render(); - fireEvent.click(screen.getByRole('button', { name: 'Off' })); + fireEvent.click(screen.getByRole('radio', { name: 'Off' })); expect(global.fetch).not.toHaveBeenCalled(); }); }); @@ -149,10 +149,10 @@ describe('AutodraftSettings Component', () => { (global.fetch as any).mockReturnValueOnce(new Promise((r) => { resolve = r; })); render(); - fireEvent.click(screen.getByRole('button', { name: 'Next in Queue' })); + fireEvent.click(screen.getByRole('radio', { name: 'Next in Queue' })); // Buttons stay enabled — optimistic UI does not block interaction - expect(screen.getByRole('button', { name: 'Off' })).not.toBeDisabled(); + expect(screen.getByRole('radio', { name: 'Off' })).not.toBeDisabled(); resolve?.({ ok: true, json: async () => ({ success: true }) }); }); @@ -160,9 +160,9 @@ describe('AutodraftSettings Component', () => { it('fires a fetch for every rapid click, aborting previous in-flight requests', async () => { render(); - fireEvent.click(screen.getByRole('button', { name: 'Next in Queue' })); - fireEvent.click(screen.getByRole('button', { name: 'All in Queue' })); - fireEvent.click(screen.getByRole('button', { name: 'Off' })); + fireEvent.click(screen.getByRole('radio', { name: 'Next in Queue' })); + fireEvent.click(screen.getByRole('radio', { name: 'All in Queue' })); + fireEvent.click(screen.getByRole('radio', { name: 'Off' })); await waitFor(() => expect(global.fetch).toHaveBeenCalledTimes(3)); }); @@ -175,10 +175,10 @@ describe('AutodraftSettings Component', () => { (global.fetch as any).mockResolvedValueOnce({ ok: false }); render(); - fireEvent.click(screen.getByRole('button', { name: 'Next in Queue' })); + fireEvent.click(screen.getByRole('radio', { name: 'Next in Queue' })); await waitFor(() => { - expect(screen.getByRole('button', { name: 'Off' }).className).toContain( + expect(screen.getByRole('radio', { name: 'Off' }).className).toContain( 'border-l-muted-foreground' ); }); @@ -188,10 +188,10 @@ describe('AutodraftSettings Component', () => { (global.fetch as any).mockRejectedValueOnce(new Error('Network error')); render(); - fireEvent.click(screen.getByRole('button', { name: 'Next in Queue' })); + fireEvent.click(screen.getByRole('radio', { name: 'Next in Queue' })); await waitFor(() => { - expect(screen.getByRole('button', { name: 'Off' }).className).toContain( + expect(screen.getByRole('radio', { name: 'Off' }).className).toContain( 'border-l-muted-foreground' ); }); @@ -203,7 +203,7 @@ describe('AutodraftSettings Component', () => { describe('API payload', () => { it('posts correct formData for Next in Queue (next_pick, queueOnly=true)', async () => { render(); - fireEvent.click(screen.getByRole('button', { name: 'Next in Queue' })); + fireEvent.click(screen.getByRole('radio', { name: 'Next in Queue' })); await waitFor(() => expect(global.fetch).toHaveBeenCalled()); @@ -217,7 +217,7 @@ describe('AutodraftSettings Component', () => { it('posts correct formData for All in Queue (while_on, queueOnly=true)', async () => { render(); - fireEvent.click(screen.getByRole('button', { name: 'All in Queue' })); + fireEvent.click(screen.getByRole('radio', { name: 'All in Queue' })); await waitFor(() => expect(global.fetch).toHaveBeenCalled()); @@ -229,7 +229,7 @@ describe('AutodraftSettings Component', () => { it('posts correct formData for All Picks (while_on, queueOnly=false)', async () => { render(); - fireEvent.click(screen.getByRole('button', { name: 'All Picks' })); + fireEvent.click(screen.getByRole('radio', { name: 'All Picks' })); await waitFor(() => expect(global.fetch).toHaveBeenCalled()); @@ -241,7 +241,7 @@ describe('AutodraftSettings Component', () => { it('posts isEnabled=false for Off', async () => { render(); - fireEvent.click(screen.getByRole('button', { name: 'Off' })); + fireEvent.click(screen.getByRole('radio', { name: 'Off' })); await waitFor(() => expect(global.fetch).toHaveBeenCalled()); @@ -251,7 +251,7 @@ describe('AutodraftSettings Component', () => { it('passes an AbortSignal to fetch', async () => { render(); - fireEvent.click(screen.getByRole('button', { name: 'Next in Queue' })); + fireEvent.click(screen.getByRole('radio', { name: 'Next in Queue' })); await waitFor(() => expect(global.fetch).toHaveBeenCalled()); diff --git a/app/components/__tests__/AvailableParticipantsSection.test.tsx b/app/components/__tests__/AvailableParticipantsSection.test.tsx index bcf5534..a941530 100644 --- a/app/components/__tests__/AvailableParticipantsSection.test.tsx +++ b/app/components/__tests__/AvailableParticipantsSection.test.tsx @@ -403,8 +403,8 @@ describe("AvailableParticipantsSection", () => { render( ); - expect(screen.queryByTitle("Add to watchlist")).not.toBeInTheDocument(); - expect(screen.queryByTitle("Remove from watchlist")).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: "Add to watchlist" })).not.toBeInTheDocument(); + expect(screen.queryByRole('button', { name: "Remove from watchlist" })).not.toBeInTheDocument(); }); it("renders EyeOff icon for unwatched participants when hasTeam is true", () => { @@ -415,7 +415,7 @@ describe("AvailableParticipantsSection", () => { watchedParticipantIds={new Set()} /> ); - const buttons = screen.getAllByTitle("Add to watchlist"); + const buttons = screen.getAllByRole('button', { name: "Add to watchlist" }); expect(buttons.length).toBeGreaterThan(0); }); @@ -427,7 +427,7 @@ describe("AvailableParticipantsSection", () => { watchedParticipantIds={new Set(["1"])} /> ); - const buttons = screen.getAllByTitle("Remove from watchlist"); + const buttons = screen.getAllByRole('button', { name: "Remove from watchlist" }); expect(buttons.length).toBeGreaterThan(0); }); @@ -441,7 +441,7 @@ describe("AvailableParticipantsSection", () => { watchedParticipantIds={new Set()} /> ); - const buttons = screen.getAllByTitle("Add to watchlist"); + const buttons = screen.getAllByRole('button', { name: "Add to watchlist" }); await user.click(buttons[0]); expect(onToggleWatchlist).toHaveBeenCalledWith("1"); }); diff --git a/app/hooks/useFocusTrap.ts b/app/hooks/useFocusTrap.ts index ac2ac6c..c5d70ae 100644 --- a/app/hooks/useFocusTrap.ts +++ b/app/hooks/useFocusTrap.ts @@ -14,7 +14,7 @@ export function useFocusTrap(ref: RefObject, active: boolean function onKeyDown(e: KeyboardEvent) { if (e.key !== "Tab") return; - const focusables = Array.from(el!.querySelectorAll(FOCUSABLE_SELECTOR)); + const focusables = Array.from(el?.querySelectorAll(FOCUSABLE_SELECTOR) ?? []); if (focusables.length === 0) return; const firstEl = focusables[0]; const lastEl = focusables[focusables.length - 1];