/** * Structural guard on the surface map (`src/lib/modules/surface.ts`). * * The map is the one answer to "which module owns this surface". It only * works if two things stay true, and each half of this file holds one: * * 1. Every id in the map names a surface that exists. A nav href, a slug, a * section id, a widget id that was renamed would leave an entry that * matches nothing, and the surface it meant would silently stop hiding. * Each family is checked against the list the app actually renders from. * * 2. Every surface that shows a module's data is owned by that module. This * half cannot come from the map itself, so it is derived from a second * source: the measurement types each surface plots, joined against the * summary owners the dashboard already gates on. A sleep page with no * sleep entry, or a trends slot charting a recovery-owned series with no * owner, fails here by name. A short table of owners that no data join * can derive (nav pages, the add menu, overview blocks) is pinned below. * * Plus the retirement half: the per-surface maps and guard hooks this map * replaced must not come back, so their names are searched for in source. * * Limits, stated so nobody reads more into a green run: the data join only * sees surfaces that declare the types they plot, so a surface that reads a * module's store without declaring a type would pass. The source search * matches names, not behaviour; a copy under a new name passes. Every match * set asserts a non-zero count so a matcher that stopped matching fails. */ import { readFileSync, existsSync } from "node:fs"; import { join } from "node:path"; import { describe, expect, it, vi } from "vitest"; // The capture picker and the Settings shell pull in forms and sections; the // guard needs only their exported lists. vi.mock("@/components/measurements/measurement-form", () => ({ MeasurementForm: () => null, })); vi.mock("@/components/mood/mood-form", () => ({ MoodForm: () => null })); vi.mock("@/components/dashboard/medication-intake-quick-add", () => ({ MedicationIntakeQuickAdd: () => null, })); import { walkSourceFiles, stripComments, } from "@/__tests__/helpers/source-files"; import { NAV_DESTINATIONS } from "@/components/layout/nav-model"; import { CAPTURE_KIND_ORDER } from "@/components/layout/capture-picker"; import { SETTINGS_SECTIONS } from "@/components/settings/settings-shell"; import { LAYOUT_GROUP_IDS } from "@/components/settings/layout-groups"; import { DASHBOARD_WIDGET_CATALOGUE_IDS, SCORE_RING_IDS, } from "@/lib/dashboard-layout"; import { SUMMARY_TYPE_MODULE } from "@/lib/dashboard/widget-modules"; import { ENVIRONMENT_FIELDS } from "@/lib/environment/fields"; import { DISCOVERY_BEHAVIOURS, DISCOVERY_OUTCOMES, FACTOR_CHANNEL_PREFIX, } from "@/lib/insights/correlation-discovery"; import { DERIVED_METRIC_IDS } from "@/lib/insights/derived/registry"; import { INSIGHTS_SECTION_IDS } from "@/lib/insights-layout"; import { SUB_PAGE_SLUGS, TYPE_TO_SUB_PAGE_SLUG, } from "@/lib/insights/sub-page-metric"; import { TREND_CHART_CONFIG } from "@/lib/insights/trend-chart-select"; import { SURFACE_KINDS, SURFACE_MODULE, surfaceModule, surfaceModulesOfKind, type SurfaceKind, } from "@/lib/modules/surface"; const SRC = join(process.cwd(), "src"); function localIds(kind: SurfaceKind): string[] { const ids = Object.keys(surfaceModulesOfKind(kind)); return ids; } describe("every surface id names a surface that exists", () => { it("covers every kind with at least one entry", () => { for (const kind of SURFACE_KINDS) { expect(localIds(kind).length, kind).toBeGreaterThan(0); } }); it("nav: a destination href", () => { const hrefs = new Set(NAV_DESTINATIONS.map((d) => d.href)); for (const id of localIds("nav")) expect(hrefs.has(id), id).toBe(true); }); it("insights-page: a routed Insights sub-page", () => { for (const slug of localIds("insights-page")) { expect( existsSync(join(SRC, "app/insights", slug)), `src/app/insights/${slug}`, ).toBe(true); } }); it("overview: an overview section id, or the cycle ring inside the scores strip", () => { const ids = new Set([...INSIGHTS_SECTION_IDS, "cycle-ring"]); for (const id of localIds("overview")) expect(ids.has(id), id).toBe(true); }); it("trend: a trends-row slot that charts something", () => { for (const id of localIds("trend")) { expect( TREND_CHART_CONFIG[id as keyof typeof TREND_CHART_CONFIG], id, ).toBeTruthy(); } }); it("capture: an add-menu kind", () => { const kinds = new Set(CAPTURE_KIND_ORDER); for (const id of localIds("capture")) expect(kinds.has(id), id).toBe(true); }); it("widget: a dashboard widget id", () => { const ids = new Set(DASHBOARD_WIDGET_CATALOGUE_IDS); for (const id of localIds("widget")) expect(ids.has(id), id).toBe(true); }); it("derived: a derived metric id", () => { const ids = new Set(DERIVED_METRIC_IDS); for (const id of localIds("derived")) expect(ids.has(id), id).toBe(true); }); it("score-ring: a hero score ring id, the derived ones owned like their score", () => { const ids = new Set(SCORE_RING_IDS); for (const id of localIds("score-ring")) { expect(ids.has(id), id).toBe(true); const derivedOwner = surfaceModule(`derived:${id}`); if (derivedOwner !== undefined) { expect(surfaceModule(`score-ring:${id}`), id).toBe(derivedOwner); } } }); it("settings: a Settings section slug", () => { const slugs = new Set(SETTINGS_SECTIONS.map((s) => s.slug)); for (const id of localIds("settings")) expect(slugs.has(id), id).toBe(true); }); it("settings-layout: a Layout group id", () => { const ids = new Set(LAYOUT_GROUP_IDS); for (const id of localIds("settings-layout")) { expect(ids.has(id), id).toBe(true); } }); it("correlation: a discovery channel, a weather field or the lab pass", () => { const channels = new Set([ ...DISCOVERY_BEHAVIOURS, ...DISCOVERY_OUTCOMES, ...ENVIRONMENT_FIELDS.map((f) => f.key), "LAB_DRAWS", ]); for (const id of localIds("correlation")) { expect(channels.has(id), id).toBe(true); } // `correlationChannelSurfaceId` folds rated factors onto mood by this // prefix; it has to be the prefix the engine actually writes. expect(FACTOR_CHANNEL_PREFIX).toBe("FACTOR:"); }); }); describe("every surface that shows a module's data is owned by it", () => { /** Summary owners are the dashboard's own gate; the join source of truth. */ const ownerOfType = (type: string) => SUMMARY_TYPE_MODULE[type]; /** * Sub-pages a module-owned type routes to but that are not that module's. * Each carries its reason; an entry here is a decision, not a silence. */ const NOT_OWNED_DESPITE_TYPE: Record = { // The HRV page leads with SDNN, a plain vital. RMSSD is recovery-owned // and routes here only as the multi-metric fallback. hrv: "SDNN page; RMSSD is only its fallback series", }; it("Insights sub-pages follow the owner of the types they plot", () => { let joined = 0; for (const [type, slug] of Object.entries(TYPE_TO_SUB_PAGE_SLUG)) { const owner = ownerOfType(type); if (!owner || slug in NOT_OWNED_DESPITE_TYPE) continue; joined += 1; expect( surfaceModule(`insights-page:${slug}`), `${slug} plots ${type}, which ${owner} owns`, ).toBe(owner); } expect(joined).toBeGreaterThan(0); }); it("trends-row slots follow the owner of the series they chart", () => { let joined = 0; for (const [metric, config] of Object.entries(TREND_CHART_CONFIG)) { if (!config) continue; const owners = new Set( config.types.map(ownerOfType).filter((o) => o !== undefined), ); if (config.kind === "mood") owners.add("mood"); if (owners.size === 0) { expect(surfaceModule(`trend:${metric}`), metric).toBeUndefined(); continue; } joined += 1; expect(owners.size, metric).toBe(1); expect(surfaceModule(`trend:${metric}`), metric).toBe([...owners][0]); } expect(joined).toBeGreaterThan(0); }); it("correlation channels that are summary types follow the summary owner", () => { let joined = 0; for (const key of [...DISCOVERY_BEHAVIOURS, ...DISCOVERY_OUTCOMES]) { const owner = ownerOfType(key); if (!owner) continue; joined += 1; expect(surfaceModule(`correlation:${key}`), key).toBe(owner); } expect(joined).toBeGreaterThan(0); }); it("every weather channel belongs to the environment module", () => { for (const field of ENVIRONMENT_FIELDS) { expect(surfaceModule(`correlation:${field.key}`), field.key).toBe( "environment", ); } }); // Owners no data join can derive. Pinned by hand; each is a surface the // audit found leaking when its module was off. it.each([ ["nav:/mood", "mood"], ["nav:/cycle", "cycle"], ["nav:/medications", "medications"], ["nav:/labs", "labs"], ["nav:/coach", "coach"], ["insights-page:mood", "mood"], ["insights-page:medications", "medications"], ["insights-page:workouts", "workouts"], ["insights-page:recovery", "recovery"], ["insights-page:nutrients", "nutrients"], ["overview:breathing", "sleep"], ["overview:labs-changes", "labs"], ["overview:cycle-summary", "cycle"], ["overview:cycle-ring", "cycle"], ["capture:mood", "mood"], ["capture:medication", "medications"], ["trend:mood", "mood"], ["widget:medications", "medications"], ["correlation:MEDICATION_COMPLIANCE", "medications"], ["correlation:SYMPTOM_SEVERITY", "illness"], ["correlation:LAB_DRAWS", "labs"], ])("%s is owned by %s", (id, owner) => { expect(surfaceModule(id)).toBe(owner); }); it("the insights key owns nothing (it is AI analysis, not an area)", () => { expect(Object.values(SURFACE_MODULE)).not.toContain("insights"); }); it("the tab strip covers every sub-page slug it can render", () => { // Every owned sub-page that is a strip slug is reachable by the strip's // lookup; recovery is the one composite pill without a slug. const slugs = new Set(SUB_PAGE_SLUGS); for (const id of localIds("insights-page")) { expect(slugs.has(id) || id === "recovery", id).toBe(true); } }); }); describe("the replaced maps and guards stay gone", () => { const files = walkSourceFiles(SRC, { floor: 1000 }).filter( (rel) => !rel.startsWith("generated/") && !rel.includes("__tests__/") && !/\.test\.tsx?$/.test(rel), ); const sources = files.map((rel) => ({ rel, code: stripComments(readFileSync(join(SRC, rel), "utf8")), })); it.each([ ["a per-destination module key", /\brequiresModule\s*[?:]/], ["a per-section module key", /\bmoduleGate\s*\?\s*:|\bmoduleGate\s*:\s*"/], ["the tab strip's own slug map", /\bSUB_PAGE_MODULE\b/], ["the redirecting page guard", /\buseModulePageGuard\b/], ])("no source declares %s", (_label, pattern) => { const hits = sources.filter((s) => pattern.test(s.code)).map((s) => s.rel); expect(hits).toEqual([]); }); it("every discovery-matrix caller passes the record's module map", () => { // `modules` is a required option, so omitting it fails to compile; this // catches the other way to get it wrong, a hand-built map, by requiring // each calling file to resolve the record's own. A file that forwards a // map it received from its caller would need an entry here. const callers = sources.filter( (s) => /\bassembleDiscoveryMatrix\s*\(/.test(s.code) && s.rel !== "lib/insights/discovery-matrix.ts", ); expect(callers.length).toBeGreaterThanOrEqual(4); for (const caller of callers) { expect(caller.code, caller.rel).toMatch(/\bresolveModuleMap\s*\(/); expect(caller.code, caller.rel).toMatch( /assembleDiscoveryMatrix\s*\([\s\S]*?\bmodules\b/, ); } }); it("the dashboard maps are views, built from the surface map and nowhere else", () => { const widgetModules = sources.find( (s) => s.rel === "lib/dashboard/widget-modules.ts", ); expect(widgetModules).toBeDefined(); expect(widgetModules!.code).toMatch( /WIDGET_MODULE_BY_ID[^=]*=\s*surfaceModulesOfKind\(\s*"widget"\s*\)/, ); expect(widgetModules!.code).toMatch( /SUMMARY_TYPE_MODULE[^=]*=\s*surfaceModulesOfKind\(\s*"summary"\s*\)/, ); }); it("the derived-score gate agrees with the surface map", () => { // `DERIVED_MODULE` lives in the derived route, which this guard reads as // text rather than importing (it pulls the whole server stack). Parsed // entry by entry and compared, so a drift in either direction fails. const route = readFileSync( join(SRC, "app/api/insights/derived/route.ts"), "utf8", ); const body = /DERIVED_MODULE[^=]*=\s*\{([\s\S]*?)\}/.exec(route)?.[1]; expect(body).toBeDefined(); const entries = Object.fromEntries( [...body!.matchAll(/([A-Z_]+)\s*:\s*"([A-Za-z]+)"/g)].map((m) => [ m[1], m[2], ]), ); expect(Object.keys(entries).length).toBeGreaterThan(0); expect(entries).toEqual(surfaceModulesOfKind("derived")); }); });