From 6cf56ce456ab4ca307dc6b76e268e168d264bb74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sat, 25 Jul 2026 00:34:51 +0200 Subject: [PATCH] test: mock the unit-display hook in the crosstab, vitals, and MCP suites The crosstab, vitals, and cycle-summary renders are static server renders with no QueryClient; the new preferred-unit hook reads through the auth query, so mock it to a real metric-preference implementation (identity values, canonical symbols) that matches the render those suites assert. Register the new unit resolver on the MCP write-tools module mock so the preview path resolves. --- .../cycle-insight-summary-card.test.tsx | 23 ++++++++++++++++ .../__tests__/cycle-phase-crosstab.test.tsx | 27 ++++++++++++++++++- .../__tests__/vitals-dashboard.test.tsx | 25 +++++++++++++++++ src/lib/mcp/__tests__/write-tools.test.ts | 7 +++++ 4 files changed, 81 insertions(+), 1 deletion(-) diff --git a/src/components/cycle/__tests__/cycle-insight-summary-card.test.tsx b/src/components/cycle/__tests__/cycle-insight-summary-card.test.tsx index f7a72f259..385df6d05 100644 --- a/src/components/cycle/__tests__/cycle-insight-summary-card.test.tsx +++ b/src/components/cycle/__tests__/cycle-insight-summary-card.test.tsx @@ -1,6 +1,29 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; +// The embedded crosstab reads the unit preference through `useUnitDisplay`, +// which hangs off `useAuth` (a react-query hook). These are static SSR renders +// with no QueryClient, so mock the hook to a real metric-preference impl. +vi.mock("@/hooks/use-unit-display", async () => { + const dt = await import("@/lib/measurements/display-transform"); + const forType = (t: string) => dt.getDisplayTransform(t, "metric"); + return { + useUnitDisplay: () => ({ + preference: "metric" as const, + transformFor: forType, + toDisplay: (t: string, v: number) => + dt.applyDisplayTransform(v, forType(t)), + fromDisplay: (t: string, v: number) => + dt.invertDisplayTransform(v, forType(t)), + toDisplayDelta: (t: string, v: number) => + dt.applyDisplayTransformDelta(v, forType(t)), + unitFor: (t: string) => forType(t).displayUnit, + decimalsFor: (t: string) => forType(t).decimals, + isTransformed: (t: string) => dt.hasDisplayTransform(t), + }), + }; +}); + import { I18nProvider } from "@/lib/i18n/context"; import type { CalendarResponse } from "../types"; import type { CycleInsightsResponse } from "../use-cycle"; diff --git a/src/components/cycle/__tests__/cycle-phase-crosstab.test.tsx b/src/components/cycle/__tests__/cycle-phase-crosstab.test.tsx index 1691a7f67..82aec2b28 100644 --- a/src/components/cycle/__tests__/cycle-phase-crosstab.test.tsx +++ b/src/components/cycle/__tests__/cycle-phase-crosstab.test.tsx @@ -1,6 +1,31 @@ -import { describe, it, expect } from "vitest"; +import { describe, it, expect, vi } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; +// The crosstab reads the unit preference through `useUnitDisplay`, which hangs +// off `useAuth` (a react-query hook). These are static SSR renders with no +// QueryClient, so mock the hook to a real metric-preference implementation — +// metric is the identity on values and yields the canonical display symbols, +// so the assertions below (kg, °C, unchanged numbers) hold. +vi.mock("@/hooks/use-unit-display", async () => { + const dt = await import("@/lib/measurements/display-transform"); + const forType = (t: string) => dt.getDisplayTransform(t, "metric"); + return { + useUnitDisplay: () => ({ + preference: "metric" as const, + transformFor: forType, + toDisplay: (t: string, v: number) => + dt.applyDisplayTransform(v, forType(t)), + fromDisplay: (t: string, v: number) => + dt.invertDisplayTransform(v, forType(t)), + toDisplayDelta: (t: string, v: number) => + dt.applyDisplayTransformDelta(v, forType(t)), + unitFor: (t: string) => forType(t).displayUnit, + decimalsFor: (t: string) => forType(t).decimals, + isTransformed: (t: string) => dt.hasDisplayTransform(t), + }), + }; +}); + import { CyclePhaseCrosstab, CyclePhaseHeadline, diff --git a/src/components/insights/derived/__tests__/vitals-dashboard.test.tsx b/src/components/insights/derived/__tests__/vitals-dashboard.test.tsx index 479f26fb8..179104963 100644 --- a/src/components/insights/derived/__tests__/vitals-dashboard.test.tsx +++ b/src/components/insights/derived/__tests__/vitals-dashboard.test.tsx @@ -1,6 +1,31 @@ import { describe, it, expect, vi, beforeEach } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; +// The vitals tiles read the unit preference through `useUnitDisplay`, which +// hangs off `useAuth` (a react-query hook). These are static SSR renders with +// no QueryClient, so mock the hook to a real metric-preference implementation +// — metric is the identity on values and yields the canonical display symbols +// (e.g. °C), matching the pre-fix render the assertions expect. +vi.mock("@/hooks/use-unit-display", async () => { + const dt = await import("@/lib/measurements/display-transform"); + const forType = (t: string) => dt.getDisplayTransform(t, "metric"); + return { + useUnitDisplay: () => ({ + preference: "metric" as const, + transformFor: forType, + toDisplay: (t: string, v: number) => + dt.applyDisplayTransform(v, forType(t)), + fromDisplay: (t: string, v: number) => + dt.invertDisplayTransform(v, forType(t)), + toDisplayDelta: (t: string, v: number) => + dt.applyDisplayTransformDelta(v, forType(t)), + unitFor: (t: string) => forType(t).displayUnit, + decimalsFor: (t: string) => forType(t).decimals, + isTransformed: (t: string) => dt.hasDisplayTransform(t), + }), + }; +}); + import { I18nProvider } from "@/lib/i18n/context"; import type { DerivedMetricResponse, diff --git a/src/lib/mcp/__tests__/write-tools.test.ts b/src/lib/mcp/__tests__/write-tools.test.ts index d677f3ba6..d8eb0b8cd 100644 --- a/src/lib/mcp/__tests__/write-tools.test.ts +++ b/src/lib/mcp/__tests__/write-tools.test.ts @@ -5,12 +5,15 @@ const logMcpMood = vi.fn(); const logMcpBloodPressure = vi.fn(); const checkMcpMeasurement = vi.fn(); const checkMcpBloodPressure = vi.fn(); +const resolveMcpMeasurementUnit = vi.fn(); vi.mock("../writes", () => ({ logMcpMeasurement: (...a: unknown[]) => logMcpMeasurement(...a), logMcpMood: (...a: unknown[]) => logMcpMood(...a), logMcpBloodPressure: (...a: unknown[]) => logMcpBloodPressure(...a), checkMcpMeasurement: (...a: unknown[]) => checkMcpMeasurement(...a), checkMcpBloodPressure: (...a: unknown[]) => checkMcpBloodPressure(...a), + resolveMcpMeasurementUnit: (...a: unknown[]) => + resolveMcpMeasurementUnit(...a), })); const checkMcpWriteRateLimit = vi.fn(); @@ -49,6 +52,10 @@ beforeEach(() => { // would-be record. Individual tests override to assert wouldFail. checkMcpMeasurement.mockReturnValue({ ok: true }); checkMcpBloodPressure.mockReturnValue({ ok: true }); + // Default: the unit hint resolves to canonical, echoing the value through. + resolveMcpMeasurementUnit.mockImplementation( + (_type: string, value: number) => ({ ok: true, value, unit: "kg" }), + ); }); describe("write-tool surface", () => {