From fbba4fd73bee80da595c596867311898e14fb327 Mon Sep 17 00:00:00 2001 From: Maurice Date: Tue, 20 Oct 2026 14:46:24 +0200 Subject: [PATCH] fix(tours): refresh the wanderer watch after a fixed token and truly disable its switch offline The watch row only re-read its state when the connection came or went, so after a passing connection test or a newly saved token it kept saying "Token refused" until a reload. The connection card now hands it a key built from the saved connection and its last check, and the row re-reads the watch whenever that changes. Without a connection the switch was only dimmed: the button stayed in the tab order and was announced as active. ToggleSwitch gains a disabled prop that disables the button and marks it aria-disabled, and the reason stays reachable through a focusable wrapper carrying the tooltip. --- .../components/Settings/ToggleSwitch.test.tsx | 22 +++++++++++++++ .../src/components/Settings/ToggleSwitch.tsx | 8 ++++-- .../Settings/WandererConnectionSection.tsx | 2 +- .../Settings/WandererWatchRow.test.tsx | 15 ++++++---- .../components/Settings/WandererWatchRow.tsx | 18 +++++++++--- .../Settings/useWandererConnection.ts | 8 ++++++ .../Settings/useWandererWatch.test.ts | 28 ++++++++++++++----- .../components/Settings/useWandererWatch.ts | 8 ++++-- 8 files changed, 85 insertions(+), 24 deletions(-) diff --git a/client/src/components/Settings/ToggleSwitch.test.tsx b/client/src/components/Settings/ToggleSwitch.test.tsx index 88a3d205a..692dc188e 100644 --- a/client/src/components/Settings/ToggleSwitch.test.tsx +++ b/client/src/components/Settings/ToggleSwitch.test.tsx @@ -64,4 +64,26 @@ describe('ToggleSwitch', () => { rerender( {}} />); expect(button.querySelector('span')!.style.left).toBe('22px'); }); + + it('FE-COMP-TOGGLESWITCH-009: a disabled switch is out of reach and says so', async () => { + const user = userEvent.setup(); + const onToggle = vi.fn(); + render(); + const button = screen.getByRole('button'); + expect(button).toBeDisabled(); + expect(button).toHaveAttribute('aria-disabled', 'true'); + expect(button.style.cursor).toBe('not-allowed'); + await user.click(button); + await user.tab(); + expect(onToggle).not.toHaveBeenCalled(); + expect(button).not.toHaveFocus(); + }); + + it('FE-COMP-TOGGLESWITCH-010: an enabled switch keeps the pointer and no aria-disabled', () => { + render( {}} />); + const button = screen.getByRole('button'); + expect(button).toBeEnabled(); + expect(button).not.toHaveAttribute('aria-disabled'); + expect(button.style.cursor).toBe('pointer'); + }); }); diff --git a/client/src/components/Settings/ToggleSwitch.tsx b/client/src/components/Settings/ToggleSwitch.tsx index a19040e9a..932674627 100644 --- a/client/src/components/Settings/ToggleSwitch.tsx +++ b/client/src/components/Settings/ToggleSwitch.tsx @@ -5,16 +5,18 @@ const EASE = 'cubic-bezier(0.32, 0.72, 0, 1)' /** * `describedBy` names the element that explains the switch, for a switch whose label alone - * does not say what flipping it changes. + * does not say what flipping it changes. `disabled` takes it out of the tab order and tells + * assistive tech it cannot act; the caller dims it and says why. */ -export default function ToggleSwitch({ on, onToggle, label, describedBy }: { on: boolean; onToggle: () => void; label?: string; describedBy?: string }) { +export default function ToggleSwitch({ on, onToggle, label, describedBy, disabled = false }: { on: boolean; onToggle: () => void; label?: string; describedBy?: string; disabled?: boolean }) { return (