From 78644ab77e239e8bbc86d7fb54e96bc6f8fab494 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 26 Jul 2026 00:27:09 +0200 Subject: [PATCH] test(ui): type the about-me mock and drop the es2018-only regex flag --- .../destructive-control-guard.test.ts | 2 +- .../__tests__/about-me-clear-scope.test.tsx | 47 +++++++++++++------ 2 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/__tests__/destructive-control-guard.test.ts b/src/__tests__/destructive-control-guard.test.ts index 053e3211a..16f6ca168 100644 --- a/src/__tests__/destructive-control-guard.test.ts +++ b/src/__tests__/destructive-control-guard.test.ts @@ -539,7 +539,7 @@ const D2 = /["`]\/api\/[^"`?\s]*(?:disconnect|revoke|reset|purge|clear|wipe|bulk-delete)[^"`?\s]*/; /** D3 — a write payload that blanks a named field. */ const D3 = - /(?:\.mutate|\.mutateAsync|apiPut|apiPatch|apiPost)[<(]?\(\s*(?:"[^"]*",\s*)?\{[^{}]*?\b\w+:\s*(?:""|null)/s; + /(?:\.mutate|\.mutateAsync|apiPut|apiPatch|apiPost)[<(]?\(\s*(?:"[^"]*",\s*)?\{[^{}]*?\b\w+:\s*(?:""|null)/; /** * Where a destructive CONTROL can live. Route handlers are the other end of diff --git a/src/components/settings/__tests__/about-me-clear-scope.test.tsx b/src/components/settings/__tests__/about-me-clear-scope.test.tsx index 99088eed4..60bfef5cf 100644 --- a/src/components/settings/__tests__/about-me-clear-scope.test.tsx +++ b/src/components/settings/__tests__/about-me-clear-scope.test.tsx @@ -33,20 +33,39 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { I18nProvider } from "@/lib/i18n/context"; import { queryKeys } from "@/lib/query-keys"; -const apiPut = vi.fn(async () => ({ - aboutMe: null, - conditions: null, - allergies: "penicillin", - coachFocus: null, - pendingQuestions: [], - updatedAt: "2026-07-26T00:00:00.000Z", - maxChars: 4000, - fieldMaxChars: 500, +type AboutMePayload = { + aboutMe: string | null; + conditions: string | null; + allergies: string | null; + coachFocus: string | null; + pendingQuestions: string[]; + updatedAt: string; + maxChars: number; + fieldMaxChars: number; +}; + +// Hoisted, because `vi.mock`'s factory is lifted above the imports and may not +// close over a normal top-level binding. Typed with the real two-argument +// signature so `mock.calls[0]` is a tuple and the payload assertion below +// reads the second argument rather than `unknown[]`. +const { apiPut } = vi.hoisted(() => ({ + apiPut: vi.fn<(url: string, payload: unknown) => Promise>( + async () => ({ + aboutMe: null, + conditions: null, + allergies: "penicillin", + coachFocus: null, + pendingQuestions: [], + updatedAt: "2026-07-26T00:00:00.000Z", + maxChars: 4000, + fieldMaxChars: 500, + }), + ), })); vi.mock("@/lib/api/api-fetch", async (importOriginal) => { const actual = await importOriginal(); - return { ...actual, apiPut: (...args: unknown[]) => apiPut(...args) }; + return { ...actual, apiPut }; }); /** Captured props of every `` the panel rendered. */ @@ -64,7 +83,7 @@ vi.mock("@/components/ui/confirm-button", () => ({ const { AboutMeSection } = await import("../about-me-section"); -const STORED = { +const STORED: AboutMePayload = { aboutMe: "Shift work, training for a half marathon.", conditions: null, allergies: "penicillin", @@ -124,10 +143,8 @@ describe("the Clear control is scoped to the note", () => { (confirmButtons[0].onConfirm as () => void)(); await vi.waitFor(() => expect(apiPut).toHaveBeenCalledTimes(1)); - const [url, payload] = apiPut.mock.calls[0] as [ - string, - Record, - ]; + const [url, raw] = apiPut.mock.calls[0]!; + const payload = raw as Record; expect(url).toBe("/api/coach/about-me"); expect(payload.aboutMe).toBe(""); // Not "sent as the stored value" — absent. The route leaves an omitted