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:
@@ -71,8 +71,9 @@ describe("MonthView", () => {
|
||||
render(<MonthView {...baseProps} />);
|
||||
|
||||
// Jan 15 is "today" - aria-label includes date, cycle day, and phase
|
||||
// For 28-day cycle, day 15 is EARLY_LUTEAL (days 15-21)
|
||||
const todayCell = screen.getByRole("button", {
|
||||
name: /January 15, 2026 - Cycle day 15 - Ovulation phase \(today\)/i,
|
||||
name: /January 15, 2026 - Cycle day 15 - Early Luteal phase \(today\)/i,
|
||||
});
|
||||
expect(todayCell).toHaveClass("ring-2", "ring-black");
|
||||
});
|
||||
@@ -99,40 +100,40 @@ describe("MonthView", () => {
|
||||
expect(day1).toHaveClass("bg-blue-100");
|
||||
});
|
||||
|
||||
it("applies follicular phase color to days 4-14", () => {
|
||||
it("applies follicular phase color to days 4-12", () => {
|
||||
render(<MonthView {...baseProps} />);
|
||||
|
||||
// Day 5 is FOLLICULAR (bg-green-100)
|
||||
// For 28-day cycle, FOLLICULAR is days 4-12
|
||||
const day5 = screen.getByRole("button", {
|
||||
name: /January 5, 2026 - Cycle day 5 - Follicular phase/i,
|
||||
});
|
||||
expect(day5).toHaveClass("bg-green-100");
|
||||
});
|
||||
|
||||
it("applies ovulation phase color to days 15-16", () => {
|
||||
it("applies ovulation phase color to days 13-14", () => {
|
||||
render(<MonthView {...baseProps} />);
|
||||
|
||||
// Day 15 is OVULATION (bg-purple-100)
|
||||
const day15 = screen.getByRole("button", {
|
||||
name: /January 15, 2026 - Cycle day 15 - Ovulation phase/i,
|
||||
// For 28-day cycle, OVULATION is days 13-14
|
||||
const day13 = screen.getByRole("button", {
|
||||
name: /January 13, 2026 - Cycle day 13 - Ovulation phase/i,
|
||||
});
|
||||
expect(day15).toHaveClass("bg-purple-100");
|
||||
expect(day13).toHaveClass("bg-purple-100");
|
||||
});
|
||||
|
||||
it("applies early luteal phase color to days 17-24", () => {
|
||||
it("applies early luteal phase color to days 15-21", () => {
|
||||
render(<MonthView {...baseProps} />);
|
||||
|
||||
// Day 20 is EARLY_LUTEAL (bg-yellow-100)
|
||||
const day20 = screen.getByRole("button", {
|
||||
name: /January 20, 2026 - Cycle day 20 - Early Luteal phase/i,
|
||||
// For 28-day cycle, EARLY_LUTEAL is days 15-21
|
||||
const day18 = screen.getByRole("button", {
|
||||
name: /January 18, 2026 - Cycle day 18 - Early Luteal phase/i,
|
||||
});
|
||||
expect(day20).toHaveClass("bg-yellow-100");
|
||||
expect(day18).toHaveClass("bg-yellow-100");
|
||||
});
|
||||
|
||||
it("applies late luteal phase color to days 25-31", () => {
|
||||
it("applies late luteal phase color to days 22-28", () => {
|
||||
render(<MonthView {...baseProps} />);
|
||||
|
||||
// Day 25 is LATE_LUTEAL (bg-red-100)
|
||||
// For 28-day cycle, LATE_LUTEAL is days 22-28
|
||||
const day25 = screen.getByRole("button", {
|
||||
name: /January 25, 2026 - Cycle day 25 - Late Luteal phase/i,
|
||||
});
|
||||
@@ -267,20 +268,22 @@ describe("MonthView", () => {
|
||||
});
|
||||
|
||||
describe("keyboard navigation", () => {
|
||||
// For 28-day cycle:
|
||||
// MENSTRUAL: 1-3, FOLLICULAR: 4-12, OVULATION: 13-14, EARLY_LUTEAL: 15-21, LATE_LUTEAL: 22-28
|
||||
it("moves focus to next day when pressing ArrowRight", () => {
|
||||
render(<MonthView {...baseProps} />);
|
||||
|
||||
// Focus on Jan 15 (today)
|
||||
// Focus on Jan 15 (today) - for 28-day cycle, day 15 is EARLY_LUTEAL
|
||||
const jan15 = screen.getByRole("button", {
|
||||
name: /January 15, 2026 - Cycle day 15 - Ovulation phase \(today\)/i,
|
||||
name: /January 15, 2026 - Cycle day 15 - Early Luteal phase \(today\)/i,
|
||||
});
|
||||
jan15.focus();
|
||||
|
||||
// Press ArrowRight to move to Jan 16
|
||||
// Press ArrowRight to move to Jan 16 - day 16 is EARLY_LUTEAL
|
||||
fireEvent.keyDown(jan15, { key: "ArrowRight" });
|
||||
|
||||
const jan16 = screen.getByRole("button", {
|
||||
name: /January 16, 2026 - Cycle day 16 - Ovulation phase$/i,
|
||||
name: /January 16, 2026 - Cycle day 16 - Early Luteal phase$/i,
|
||||
});
|
||||
expect(document.activeElement).toBe(jan16);
|
||||
});
|
||||
@@ -288,17 +291,17 @@ describe("MonthView", () => {
|
||||
it("moves focus to previous day when pressing ArrowLeft", () => {
|
||||
render(<MonthView {...baseProps} />);
|
||||
|
||||
// Focus on Jan 15 (today)
|
||||
// Focus on Jan 15 (today) - for 28-day cycle, day 15 is EARLY_LUTEAL
|
||||
const jan15 = screen.getByRole("button", {
|
||||
name: /January 15, 2026 - Cycle day 15 - Ovulation phase \(today\)/i,
|
||||
name: /January 15, 2026 - Cycle day 15 - Early Luteal phase \(today\)/i,
|
||||
});
|
||||
jan15.focus();
|
||||
|
||||
// Press ArrowLeft to move to Jan 14
|
||||
// Press ArrowLeft to move to Jan 14 - day 14 is OVULATION
|
||||
fireEvent.keyDown(jan15, { key: "ArrowLeft" });
|
||||
|
||||
const jan14 = screen.getByRole("button", {
|
||||
name: /January 14, 2026 - Cycle day 14 - Follicular phase$/i,
|
||||
name: /January 14, 2026 - Cycle day 14 - Ovulation phase$/i,
|
||||
});
|
||||
expect(document.activeElement).toBe(jan14);
|
||||
});
|
||||
@@ -306,17 +309,17 @@ describe("MonthView", () => {
|
||||
it("moves focus to same day next week when pressing ArrowDown", () => {
|
||||
render(<MonthView {...baseProps} />);
|
||||
|
||||
// Focus on Jan 15 (today)
|
||||
// Focus on Jan 15 (today) - for 28-day cycle, day 15 is EARLY_LUTEAL
|
||||
const jan15 = screen.getByRole("button", {
|
||||
name: /January 15, 2026 - Cycle day 15 - Ovulation phase \(today\)/i,
|
||||
name: /January 15, 2026 - Cycle day 15 - Early Luteal phase \(today\)/i,
|
||||
});
|
||||
jan15.focus();
|
||||
|
||||
// Press ArrowDown to move to Jan 22 (7 days later)
|
||||
// Press ArrowDown to move to Jan 22 (7 days later) - day 22 is LATE_LUTEAL
|
||||
fireEvent.keyDown(jan15, { key: "ArrowDown" });
|
||||
|
||||
const jan22 = screen.getByRole("button", {
|
||||
name: /January 22, 2026 - Cycle day 22 - Early Luteal phase$/i,
|
||||
name: /January 22, 2026 - Cycle day 22 - Late Luteal phase$/i,
|
||||
});
|
||||
expect(document.activeElement).toBe(jan22);
|
||||
});
|
||||
@@ -324,13 +327,13 @@ describe("MonthView", () => {
|
||||
it("moves focus to same day previous week when pressing ArrowUp", () => {
|
||||
render(<MonthView {...baseProps} />);
|
||||
|
||||
// Focus on Jan 15 (today)
|
||||
// Focus on Jan 15 (today) - for 28-day cycle, day 15 is EARLY_LUTEAL
|
||||
const jan15 = screen.getByRole("button", {
|
||||
name: /January 15, 2026 - Cycle day 15 - Ovulation phase \(today\)/i,
|
||||
name: /January 15, 2026 - Cycle day 15 - Early Luteal phase \(today\)/i,
|
||||
});
|
||||
jan15.focus();
|
||||
|
||||
// Press ArrowUp to move to Jan 8 (7 days earlier)
|
||||
// Press ArrowUp to move to Jan 8 (7 days earlier) - day 8 is FOLLICULAR
|
||||
fireEvent.keyDown(jan15, { key: "ArrowUp" });
|
||||
|
||||
const jan8 = screen.getByRole("button", {
|
||||
@@ -375,9 +378,9 @@ describe("MonthView", () => {
|
||||
it("wraps focus at row boundaries for Home and End keys", () => {
|
||||
render(<MonthView {...baseProps} />);
|
||||
|
||||
// Focus on Jan 15
|
||||
// Focus on Jan 15 - for 28-day cycle, day 15 is EARLY_LUTEAL
|
||||
const jan15 = screen.getByRole("button", {
|
||||
name: /January 15, 2026 - Cycle day 15 - Ovulation phase \(today\)/i,
|
||||
name: /January 15, 2026 - Cycle day 15 - Early Luteal phase \(today\)/i,
|
||||
});
|
||||
jan15.focus();
|
||||
|
||||
@@ -393,9 +396,9 @@ describe("MonthView", () => {
|
||||
it("moves focus to last day when pressing End key", () => {
|
||||
render(<MonthView {...baseProps} />);
|
||||
|
||||
// Focus on Jan 15
|
||||
// Focus on Jan 15 - for 28-day cycle, day 15 is EARLY_LUTEAL
|
||||
const jan15 = screen.getByRole("button", {
|
||||
name: /January 15, 2026 - Cycle day 15 - Ovulation phase \(today\)/i,
|
||||
name: /January 15, 2026 - Cycle day 15 - Early Luteal phase \(today\)/i,
|
||||
});
|
||||
jan15.focus();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user