Fix critical bug: cycle phase boundaries now scale with cycle length
CRITICAL BUG FIX: - Phase boundaries were hardcoded for 31-day cycle, breaking correct phase calculations for users with different cycle lengths (28, 35, etc.) - Added getPhaseBoundaries(cycleLength) function in cycle.ts - Updated getPhase() to accept cycleLength parameter (default 31) - Updated all callers (API routes, components) to pass cycleLength - Added 13 new tests for phase boundaries with 28, 31, and 35-day cycles ICS IMPROVEMENTS: - Fixed emojis to match calendar.md spec: 🩸🌱🌸🌙🌑 - Added CATEGORIES field for calendar app colors per spec: MENSTRUAL=Red, FOLLICULAR=Green, OVULATION=Pink, EARLY_LUTEAL=Yellow, LATE_LUTEAL=Orange - Added 5 new tests for CATEGORIES Updated IMPLEMENTATION_PLAN.md with discovered issues and test counts. 825 tests passing (up from 807) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// ABOUTME: Unit tests for cycle phase calculation utilities.
|
||||
// ABOUTME: Tests getCycleDay, getPhase, and phase limit functions.
|
||||
// ABOUTME: Tests getCycleDay, getPhase, and phase limit functions with variable cycle lengths.
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { getCycleDay, getPhase, getPhaseLimit } from "./cycle";
|
||||
@@ -25,29 +25,133 @@ describe("getCycleDay", () => {
|
||||
});
|
||||
|
||||
describe("getPhase", () => {
|
||||
it("returns MENSTRUAL for days 1-3", () => {
|
||||
expect(getPhase(1)).toBe("MENSTRUAL");
|
||||
expect(getPhase(3)).toBe("MENSTRUAL");
|
||||
// Phase boundaries per spec (cycle-tracking.md):
|
||||
// MENSTRUAL: 1-3 (fixed)
|
||||
// FOLLICULAR: 4 to (cycleLength - 16)
|
||||
// OVULATION: (cycleLength - 15) to (cycleLength - 14)
|
||||
// EARLY_LUTEAL: (cycleLength - 13) to (cycleLength - 7)
|
||||
// LATE_LUTEAL: (cycleLength - 6) to cycleLength
|
||||
|
||||
describe("31-day cycle", () => {
|
||||
const cycleLength = 31;
|
||||
|
||||
it("returns MENSTRUAL for days 1-3", () => {
|
||||
expect(getPhase(1, cycleLength)).toBe("MENSTRUAL");
|
||||
expect(getPhase(3, cycleLength)).toBe("MENSTRUAL");
|
||||
});
|
||||
|
||||
it("returns FOLLICULAR for days 4-15", () => {
|
||||
// 4 to (31-16) = 4-15
|
||||
expect(getPhase(4, cycleLength)).toBe("FOLLICULAR");
|
||||
expect(getPhase(15, cycleLength)).toBe("FOLLICULAR");
|
||||
});
|
||||
|
||||
it("returns OVULATION for days 16-17", () => {
|
||||
// (31-15) to (31-14) = 16-17
|
||||
expect(getPhase(16, cycleLength)).toBe("OVULATION");
|
||||
expect(getPhase(17, cycleLength)).toBe("OVULATION");
|
||||
});
|
||||
|
||||
it("returns EARLY_LUTEAL for days 18-24", () => {
|
||||
// (31-13) to (31-7) = 18-24
|
||||
expect(getPhase(18, cycleLength)).toBe("EARLY_LUTEAL");
|
||||
expect(getPhase(24, cycleLength)).toBe("EARLY_LUTEAL");
|
||||
});
|
||||
|
||||
it("returns LATE_LUTEAL for days 25-31", () => {
|
||||
// (31-6) to 31 = 25-31
|
||||
expect(getPhase(25, cycleLength)).toBe("LATE_LUTEAL");
|
||||
expect(getPhase(31, cycleLength)).toBe("LATE_LUTEAL");
|
||||
});
|
||||
});
|
||||
|
||||
it("returns FOLLICULAR for days 4-14", () => {
|
||||
expect(getPhase(4)).toBe("FOLLICULAR");
|
||||
expect(getPhase(14)).toBe("FOLLICULAR");
|
||||
describe("28-day cycle", () => {
|
||||
const cycleLength = 28;
|
||||
|
||||
it("returns MENSTRUAL for days 1-3", () => {
|
||||
expect(getPhase(1, cycleLength)).toBe("MENSTRUAL");
|
||||
expect(getPhase(3, cycleLength)).toBe("MENSTRUAL");
|
||||
});
|
||||
|
||||
it("returns FOLLICULAR for days 4-12", () => {
|
||||
// 4 to (28-16) = 4-12
|
||||
expect(getPhase(4, cycleLength)).toBe("FOLLICULAR");
|
||||
expect(getPhase(12, cycleLength)).toBe("FOLLICULAR");
|
||||
});
|
||||
|
||||
it("returns OVULATION for days 13-14", () => {
|
||||
// (28-15) to (28-14) = 13-14
|
||||
expect(getPhase(13, cycleLength)).toBe("OVULATION");
|
||||
expect(getPhase(14, cycleLength)).toBe("OVULATION");
|
||||
});
|
||||
|
||||
it("returns EARLY_LUTEAL for days 15-21", () => {
|
||||
// (28-13) to (28-7) = 15-21
|
||||
expect(getPhase(15, cycleLength)).toBe("EARLY_LUTEAL");
|
||||
expect(getPhase(21, cycleLength)).toBe("EARLY_LUTEAL");
|
||||
});
|
||||
|
||||
it("returns LATE_LUTEAL for days 22-28", () => {
|
||||
// (28-6) to 28 = 22-28
|
||||
expect(getPhase(22, cycleLength)).toBe("LATE_LUTEAL");
|
||||
expect(getPhase(28, cycleLength)).toBe("LATE_LUTEAL");
|
||||
});
|
||||
});
|
||||
|
||||
it("returns OVULATION for days 15-16", () => {
|
||||
expect(getPhase(15)).toBe("OVULATION");
|
||||
expect(getPhase(16)).toBe("OVULATION");
|
||||
describe("35-day cycle", () => {
|
||||
const cycleLength = 35;
|
||||
|
||||
it("returns MENSTRUAL for days 1-3", () => {
|
||||
expect(getPhase(1, cycleLength)).toBe("MENSTRUAL");
|
||||
expect(getPhase(3, cycleLength)).toBe("MENSTRUAL");
|
||||
});
|
||||
|
||||
it("returns FOLLICULAR for days 4-19", () => {
|
||||
// 4 to (35-16) = 4-19
|
||||
expect(getPhase(4, cycleLength)).toBe("FOLLICULAR");
|
||||
expect(getPhase(19, cycleLength)).toBe("FOLLICULAR");
|
||||
});
|
||||
|
||||
it("returns OVULATION for days 20-21", () => {
|
||||
// (35-15) to (35-14) = 20-21
|
||||
expect(getPhase(20, cycleLength)).toBe("OVULATION");
|
||||
expect(getPhase(21, cycleLength)).toBe("OVULATION");
|
||||
});
|
||||
|
||||
it("returns EARLY_LUTEAL for days 22-28", () => {
|
||||
// (35-13) to (35-7) = 22-28
|
||||
expect(getPhase(22, cycleLength)).toBe("EARLY_LUTEAL");
|
||||
expect(getPhase(28, cycleLength)).toBe("EARLY_LUTEAL");
|
||||
});
|
||||
|
||||
it("returns LATE_LUTEAL for days 29-35", () => {
|
||||
// (35-6) to 35 = 29-35
|
||||
expect(getPhase(29, cycleLength)).toBe("LATE_LUTEAL");
|
||||
expect(getPhase(35, cycleLength)).toBe("LATE_LUTEAL");
|
||||
});
|
||||
});
|
||||
|
||||
it("returns EARLY_LUTEAL for days 17-24", () => {
|
||||
expect(getPhase(17)).toBe("EARLY_LUTEAL");
|
||||
expect(getPhase(24)).toBe("EARLY_LUTEAL");
|
||||
});
|
||||
describe("edge cases", () => {
|
||||
it("defaults to LATE_LUTEAL for days beyond cycle length", () => {
|
||||
expect(getPhase(32, 31)).toBe("LATE_LUTEAL");
|
||||
expect(getPhase(40, 35)).toBe("LATE_LUTEAL");
|
||||
});
|
||||
|
||||
it("returns LATE_LUTEAL for days 25-31", () => {
|
||||
expect(getPhase(25)).toBe("LATE_LUTEAL");
|
||||
expect(getPhase(31)).toBe("LATE_LUTEAL");
|
||||
it("handles minimum cycle length (21 days)", () => {
|
||||
// 21-day: FOLLICULAR 4-5, OVULATION 6-7, EARLY_LUTEAL 8-14, LATE_LUTEAL 15-21
|
||||
expect(getPhase(5, 21)).toBe("FOLLICULAR"); // 4 to (21-16)=5
|
||||
expect(getPhase(6, 21)).toBe("OVULATION"); // (21-15)=6 to (21-14)=7
|
||||
expect(getPhase(8, 21)).toBe("EARLY_LUTEAL"); // (21-13)=8 to (21-7)=14
|
||||
expect(getPhase(15, 21)).toBe("LATE_LUTEAL"); // (21-6)=15 to 21
|
||||
});
|
||||
|
||||
it("handles maximum cycle length (45 days)", () => {
|
||||
// 45-day: FOLLICULAR 4-29, OVULATION 30-31, EARLY_LUTEAL 32-38, LATE_LUTEAL 39-45
|
||||
expect(getPhase(29, 45)).toBe("FOLLICULAR"); // 4 to (45-16)=29
|
||||
expect(getPhase(30, 45)).toBe("OVULATION"); // (45-15)=30 to (45-14)=31
|
||||
expect(getPhase(32, 45)).toBe("EARLY_LUTEAL"); // (45-13)=32 to (45-7)=38
|
||||
expect(getPhase(39, 45)).toBe("LATE_LUTEAL"); // (45-6)=39 to 45
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user