From 043d2ba1433d42d13c745c46ed85eeca026cc27c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sat, 1 Aug 2026 10:48:51 +0200 Subject: [PATCH] Give the mobile cycle smoke test a verdict to render The spec hand-builds a calendar response, and the wheel now reads the resolved verdict from it. Without the field the ring paints empty, so the fixture derives the verdict from the same phase plan it derives the day grid from and the two cannot drift apart. --- e2e/cycle-insights-mobile.spec.ts | 61 +++++++++++++++---- .../cycle-insight-summary-card.test.tsx | 13 ---- 2 files changed, 50 insertions(+), 24 deletions(-) diff --git a/e2e/cycle-insights-mobile.spec.ts b/e2e/cycle-insights-mobile.spec.ts index a33ea0ad4..030481e19 100644 --- a/e2e/cycle-insights-mobile.spec.ts +++ b/e2e/cycle-insights-mobile.spec.ts @@ -37,20 +37,29 @@ function ymd(d: Date): string { return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`; } +/** The labelled run the fixture paints: 5 menstrual, 6 follicular, 1 ovulatory, + * then luteal through today and one day past it. */ +const RUN_START_OFFSET = -12; +const PHASE_PLAN: string[] = [ + ...Array(5).fill("MENSTRUAL"), + ...Array(6).fill("FOLLICULAR"), + "OVULATORY", + "LUTEAL", + "LUTEAL", +]; + +function runStart(): Date { + const start = new Date(); + start.setDate(start.getDate() + RUN_START_OFFSET); + return start; +} + /** A 90-day-back → 180-day-forward calendar with a recent labelled cycle run - * so the wheel resolves a current phase + day and the calendar paints cells. */ + * so the wheel has a phase + day to show and the calendar paints cells. */ function buildCalendarDays(): Array> { const days: Array> = []; - const start = new Date(); - start.setDate(start.getDate() - 12); - // 5 menstrual, 6 follicular, 1 ovulatory, then luteal through today. - const plan: string[] = [ - ...Array(5).fill("MENSTRUAL"), - ...Array(6).fill("FOLLICULAR"), - "OVULATORY", - "LUTEAL", - "LUTEAL", - ]; + const start = runStart(); + const plan = PHASE_PLAN; for (let i = 0; i < plan.length; i++) { const d = new Date(start); d.setDate(start.getDate() + i); @@ -72,6 +81,34 @@ function buildCalendarDays(): Array> { return days; } +/** + * The verdict the server resolves for that grid. The real route publishes this + * and the wheel renders it, so a fixture that omitted it would paint an empty + * ring — deriving it here from the SAME plan keeps the two from drifting. + */ +function buildVerdict(): Record { + const plan = PHASE_PLAN; + const counts = plan.reduce>((acc, phase) => { + acc[phase] = (acc[phase] ?? 0) + 1; + return acc; + }, {}); + const order = ["MENSTRUAL", "FOLLICULAR", "OVULATORY", "LUTEAL"]; + return { + state: "IN_CYCLE", + // The run starts 12 days back, so today is its 13th day. + dayOfCycle: 1 - RUN_START_OFFSET, + cycleLength: plan.length, + phase: plan[-RUN_START_OFFSET], + spans: order + .filter((phase) => (counts[phase] ?? 0) > 0) + .map((phase) => ({ phase, fraction: counts[phase] / plan.length })), + cycleStartDate: ymd(runStart()), + overdueDays: null, + daysUntilNext: null, + fertileWindow: { start: null, end: null, active: false }, + }; +} + const CALENDAR_BODY = { data: { profile: { @@ -81,6 +118,8 @@ const CALENDAR_BODY = { cyclesObserved: 1, }, prediction: null, + verdict: buildVerdict(), + stillLearning: false, days: buildCalendarDays(), meta: { generatedAt: new Date().toISOString() }, }, 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 509433639..1dc11f247 100644 --- a/src/components/cycle/__tests__/cycle-insight-summary-card.test.tsx +++ b/src/components/cycle/__tests__/cycle-insight-summary-card.test.tsx @@ -109,19 +109,6 @@ const LUTEAL_VERDICT: CycleVerdict = { fertileWindow: { start: null, end: null, active: false }, }; -/** The verdict for an account with nothing to say about today. */ -const NO_CYCLE_VERDICT: CycleVerdict = { - state: "INSUFFICIENT_DATA", - dayOfCycle: null, - cycleLength: null, - phase: null, - spans: [], - cycleStartDate: null, - overdueDays: null, - daysUntilNext: null, - fertileWindow: { start: null, end: null, active: false }, -}; - /** A calendar read whose SERVER-RESOLVED verdict puts today on LUTEAL, day 3. * The card renders the verdict; it no longer derives anything from `days`. */ function lutealCalendar(today: string): CalendarResponse {