From 686770bbbfe241a27a743d093744f56fd66d3278 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Wed, 22 Jul 2026 10:22:11 +0800 Subject: [PATCH 1/3] test(settings): destructive + account flow coverage for SettingsDialog (TCD-05) SettingsDialog owns the destructive privacy actions and the account flows, which had no render coverage (only the inert-preferences markers were tested). Add @testing-library/react tests for the wiring that matters: - Clear recent searches invokes clearRecentQueries (enabled only with a non-zero count). - Clear saved items invokes accountData.clearFavourites and surfaces the "Saved items cleared." status notice. - Sign out invokes the onSignOut callback. - Email sign-in submits the entered address to auth.signInWithEmail. Reuses the settings-inert-preferences harness shape: the data/auth/account hooks are mocked so the flows are asserted without real storage or auth; useTheme/useAppPreferences run unmocked in jsdom. Verified: 4/4 pass; typecheck, lint, format:check clean. Co-Authored-By: Claude Opus 4.8 --- tests/settings-dialog-actions.dom.test.tsx | 108 +++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 tests/settings-dialog-actions.dom.test.tsx diff --git a/tests/settings-dialog-actions.dom.test.tsx b/tests/settings-dialog-actions.dom.test.tsx new file mode 100644 index 00000000..2b09f84f --- /dev/null +++ b/tests/settings-dialog-actions.dom.test.tsx @@ -0,0 +1,108 @@ +import { fireEvent, render, screen } from "@testing-library/react"; +import { afterEach, describe, expect, it, vi } from "vitest"; + +// The settings surface owns the destructive privacy actions (clear recent +// searches, clear saved items), preference reset, and sign-out. These flows had +// no render coverage. The data/auth/account hooks are mocked (the same harness +// shape as settings-inert-preferences.dom.test.tsx) so the destructive wiring is +// asserted without real storage or auth; useTheme/useAppPreferences run +// unmocked in jsdom. + +const clearRecentQueries = vi.fn(); +vi.mock("@/lib/recent-query-storage", () => ({ + clearRecentQueries: () => clearRecentQueries(), + // Non-zero so the "Clear recent searches" action is enabled. + countRecentQueries: () => 3, +})); + +const clearFavourites = vi.fn(async () => true); +vi.mock("@/components/account-data-provider", () => ({ + useAccountData: () => ({ + // One saved item so the "Clear saved items" action is enabled. + favourites: { medications: [{ id: "m1" }] }, + clearFavourites, + isSaved: () => false, + setFavourite: vi.fn(async () => true), + }), +})); + +const signInWithEmail = vi.fn(); +vi.mock("@/lib/supabase/client", () => ({ + useAuthSession: () => ({ + status: "signed_out", + // Configured so the email sign-in submit is enabled; irrelevant to the + // signed-in destructive-action tests (which never render the sign-in form). + isConfigured: true, + error: null, + notice: null, + session: null, + signInWithEmail, + signInWithOAuth: vi.fn(), + signOut: vi.fn(), + }), +})); + +import { SettingsDialog } from "@/components/clinical-dashboard/settings-dialog"; + +function renderDialog(identityOverrides: Record = {}) { + const onSignOut = vi.fn(); + render( + , + ); + return { onSignOut }; +} + +afterEach(() => { + vi.clearAllMocks(); +}); + +describe("SettingsDialog — destructive and account actions", () => { + it("clears recent searches through the privacy action", () => { + renderDialog(); + const button = screen.getByRole("button", { name: "Clear recent searches" }); + expect(button).toBeEnabled(); + fireEvent.click(button); + expect(clearRecentQueries).toHaveBeenCalledTimes(1); + }); + + it("clears saved items and confirms via a status notice", async () => { + renderDialog(); + fireEvent.click(screen.getByRole("button", { name: "Clear saved items" })); + expect(clearFavourites).toHaveBeenCalledTimes(1); + expect(await screen.findByText("Saved items cleared.")).toBeVisible(); + }); + + it("signs out through the account action", () => { + const { onSignOut } = renderDialog(); + fireEvent.click(screen.getByRole("button", { name: "Sign out" })); + expect(onSignOut).toHaveBeenCalledTimes(1); + }); + + it("signs in with an entered email address", () => { + renderDialog({ signedIn: false }); + // "Sign in" (rendered in both the desktop and mobile button sets) opens the + // email entry form. + fireEvent.click(screen.getAllByRole("button", { name: "Sign in" })[0]); + + fireEvent.change(screen.getByLabelText("Email address"), { + target: { value: "clinician@clinic.example" }, + }); + const submit = screen.getByRole("button", { name: "Continue with email" }); + expect(submit).toBeEnabled(); + fireEvent.click(submit); + + expect(signInWithEmail).toHaveBeenCalledWith("clinician@clinic.example"); + }); +}); From d550a3d3b0b56c6c038ced76816a872b120bf645 Mon Sep 17 00:00:00 2001 From: "coderabbitai[bot]" <136622811+coderabbitai[bot]@users.noreply.github.com> Date: Wed, 22 Jul 2026 03:40:15 +0000 Subject: [PATCH 2/3] fix: apply CodeRabbit auto-fixes Fixed 1 file(s) based on 2 unresolved review comments. Co-authored-by: CodeRabbit --- tests/settings-dialog-actions.dom.test.tsx | 47 ++++++++++++++++++---- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/tests/settings-dialog-actions.dom.test.tsx b/tests/settings-dialog-actions.dom.test.tsx index 2b09f84f..51fa4559 100644 --- a/tests/settings-dialog-actions.dom.test.tsx +++ b/tests/settings-dialog-actions.dom.test.tsx @@ -8,25 +8,50 @@ import { afterEach, describe, expect, it, vi } from "vitest"; // asserted without real storage or auth; useTheme/useAppPreferences run // unmocked in jsdom. -const clearRecentQueries = vi.fn(); +const { + clearRecentQueries, + clearFavourites, + signInWithEmail, + mockRecentCount, + mockFavourites, +} = vi.hoisted(() => { + let recentCount = 3; + let favouritesData = { medications: [{ id: "m1" }] }; + return { + clearRecentQueries: vi.fn(), + clearFavourites: vi.fn(async () => true), + signInWithEmail: vi.fn(), + mockRecentCount: { + get: () => recentCount, + set: (count: number) => { + recentCount = count; + }, + }, + mockFavourites: { + get: () => favouritesData, + set: (data: { medications: Array<{ id: string }> }) => { + favouritesData = data; + }, + }, + }; +}); + vi.mock("@/lib/recent-query-storage", () => ({ clearRecentQueries: () => clearRecentQueries(), - // Non-zero so the "Clear recent searches" action is enabled. - countRecentQueries: () => 3, + // Non-zero so the "Clear recent searches" action is enabled by default. + countRecentQueries: () => mockRecentCount.get(), })); -const clearFavourites = vi.fn(async () => true); vi.mock("@/components/account-data-provider", () => ({ useAccountData: () => ({ - // One saved item so the "Clear saved items" action is enabled. - favourites: { medications: [{ id: "m1" }] }, + // One saved item so the "Clear saved items" action is enabled by default. + favourites: mockFavourites.get(), clearFavourites, isSaved: () => false, setFavourite: vi.fn(async () => true), }), })); -const signInWithEmail = vi.fn(); vi.mock("@/lib/supabase/client", () => ({ useAuthSession: () => ({ status: "signed_out", @@ -77,6 +102,14 @@ describe("SettingsDialog — destructive and account actions", () => { expect(clearRecentQueries).toHaveBeenCalledTimes(1); }); + it("disables clear recent searches when there are no recent queries", () => { + mockRecentCount.set(0); + renderDialog(); + const button = screen.getByRole("button", { name: "Clear recent searches" }); + expect(button).toBeDisabled(); + mockRecentCount.set(3); // Reset for other tests + }); + it("clears saved items and confirms via a status notice", async () => { renderDialog(); fireEvent.click(screen.getByRole("button", { name: "Clear saved items" })); From 427609be0846af96b7a4d13fddbd5189c3fc78bd Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Wed, 22 Jul 2026 11:55:45 +0800 Subject: [PATCH 3/3] style(test): prettier-format the settings dialog action tests format:check is a required CI gate and is not part of verify:cheap, so this slipped through the local run. Co-Authored-By: Claude Opus 4.8 --- tests/settings-dialog-actions.dom.test.tsx | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/tests/settings-dialog-actions.dom.test.tsx b/tests/settings-dialog-actions.dom.test.tsx index 51fa4559..c326fafd 100644 --- a/tests/settings-dialog-actions.dom.test.tsx +++ b/tests/settings-dialog-actions.dom.test.tsx @@ -8,13 +8,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"; // asserted without real storage or auth; useTheme/useAppPreferences run // unmocked in jsdom. -const { - clearRecentQueries, - clearFavourites, - signInWithEmail, - mockRecentCount, - mockFavourites, -} = vi.hoisted(() => { +const { clearRecentQueries, clearFavourites, signInWithEmail, mockRecentCount, mockFavourites } = vi.hoisted(() => { let recentCount = 3; let favouritesData = { medications: [{ id: "m1" }] }; return {