diff --git a/src/components/charts/__tests__/health-chart-overlay-defaults.test.tsx b/src/components/charts/__tests__/health-chart-overlay-defaults.test.tsx new file mode 100644 index 000000000..623d4bbfe --- /dev/null +++ b/src/components/charts/__tests__/health-chart-overlay-defaults.test.tsx @@ -0,0 +1,91 @@ +import { describe, it, expect, vi } from "vitest"; +import { renderToStaticMarkup } from "react-dom/server"; +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; + +/** + * v1.4.18 — Default overlay state on chart wrappers. + * + * Marc rejected the always-on personal-baseline reference line that B1a + * painted on every chart. The default surface for a new chart should be + * a clean line + axes + tooltip — no baseline, no target-zone shading, + * no trend line. Overlays are user-opt-in via the per-chart settings + * popover (commit "feat(charts): per-chart overlay-controls component") + * and persisted (commit "feat(charts): persist per-chart overlay + * prefs"). The default state of every toggle is OFF. + * + * The personal baseline used to render unconditionally as a + * ``. We assert the SSR markup ships + * no `personalBaseline` label and no in-target reference area when the + * chart starts up. + */ + +const sampleSeries = vi.hoisted(() => { + const out: Array<{ measuredAt: string; value: number }> = []; + for (let i = 0; i < 30; i++) { + const dt = new Date(Date.UTC(2026, 4, 1 + i, 12, 0, 0)); + out.push({ + measuredAt: dt.toISOString(), + value: 118 + (i % 5) - 2 + i * 0.05, + }); + } + return out; +}); + +vi.mock("@tanstack/react-query", () => ({ + useQuery: () => ({ + data: sampleSeries.map((row) => { + const ts = Date.parse(row.measuredAt); + return { + date: new Date(ts).toDateString(), + timestamp: ts, + BLOOD_PRESSURE_SYS: row.value, + }; + }), + isLoading: false, + }), +})); + +vi.mock("@/hooks/use-auth", () => ({ + useAuth: () => ({ + isAuthenticated: true, + user: null, + isLoading: false, + }), +})); + +describe("HealthChart default overlay state", () => { + it("source: personal baseline is gated behind a toggle (no unconditional ReferenceLine)", () => { + // The B1a wrapper rendered the baseline `` for every + // type unconditionally. Make sure the source no longer paints it + // outside an opt-in branch — we look for the tell-tale pattern of + // `types.map((type, i) => { ... \{[\s\S]*?const baseline = personalBaselines\.get\(type\);[\s\S]*?\}\)\}/, + ); + if (baselineSection) { + // If the section still exists, it's not gated. Allow a guard + // wrapper test instead — match `{showSomething &&`. + throw new Error( + "Personal baseline ReferenceLine is rendered unconditionally — must be gated behind an opt-in toggle.", + ); + } + }); + + it("does NOT paint the personal-baseline label by default in MoodChart source", () => { + const moduleUrl = new URL("../mood-chart.tsx", import.meta.url); + const src = readFileSync(fileURLToPath(moduleUrl), "utf8"); + // Same guard — the baseline ReferenceLine must be wrapped in a + // condition (showTrend / similar), not always painted. + expect(src).not.toMatch( + /\{personalBaseline != null && \(\n\s+ ))} - {/* v1.4.16 B1a — personal-baseline reference line. - 90-day rolling median per type, painted as a faint - dashed line labelled "Your normal" / "Dein Mittel". - Only the *first* baseline gets an inline label so - multi-type charts (e.g. systolic + diastolic) don't - paint two overlapping labels. */} - {types.map((type, i) => { - const baseline = personalBaselines.get(type); - if (baseline == null) return null; - return ( - - ); - })} + {/* v1.4.18 — personal-baseline reference line is now + opt-in via the Trend toggle. Marc rejected the + always-on dashed mean line; it now only paints when + the user actively shows the trend overlay (matching + his rule: "only when a trend is being displayed"). + 90-day rolling median per type, faint dashed line, + only the first type gets the inline label so + multi-type charts don't paint duplicate labels. */} + {showTrend && + types.map((type, i) => { + const baseline = personalBaselines.get(type); + if (baseline == null) return null; + return ( + + ); + })} ))} - {personalBaseline != null && ( + {/* v1.4.18 — personal-baseline gated behind the + Trend toggle (Marc: "only when a trend is being + displayed"). Default OFF. */} + {showTrend && personalBaseline != null && (