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 {