import { fireEvent, render, screen } from "@testing-library/react"; import { MemoryRouter } from "react-router-dom"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { SchedulePage } from "../../pages/SchedulePage"; // Mock data const mockMeds = [ { id: 1, name: "Aspirin", packCount: 1, blistersPerPack: 2, pillsPerBlister: 10, looseTablets: 5, takenBy: ["John"], blisters: [{ usage: 1, every: 1, start: "2024-01-01T09:00:00Z" }], intakeRemindersEnabled: true, notes: "Take with food", pillWeightMg: 500, imageUrl: null, updatedAt: null, }, ]; // Fixed timestamp for consistent tests const FIXED_TIMESTAMP = 1706000000000; // Fixed date for testing const mockCoverageByMed = { Aspirin: { name: "Aspirin", medsLeft: 25, daysLeft: 25, depletionDate: "2025-02-15", depletionTime: FIXED_TIMESTAMP + 25 * 86400000, nextDose: null, }, }; const mockFutureDays = [ { dateStr: "Mon, Jan 22", date: new Date(FIXED_TIMESTAMP), isPast: false, meds: [ { medName: "Aspirin", total: 1, doses: [{ id: `1-0-${FIXED_TIMESTAMP}`, timeStr: "09:00", when: FIXED_TIMESTAMP, usage: 1, takenBy: ["John"] }], lastWhen: FIXED_TIMESTAMP, }, ], }, ]; const mockPastDays = [ { dateStr: "Sun, Jan 21", date: new Date(FIXED_TIMESTAMP - 86400000), isPast: true, meds: [ { medName: "Aspirin", total: 1, doses: [ { id: `1-0-${FIXED_TIMESTAMP - 86400000}`, timeStr: "09:00", when: FIXED_TIMESTAMP - 86400000, usage: 1, takenBy: ["John"], }, ], lastWhen: FIXED_TIMESTAMP - 86400000, }, ], }, ]; // Factory function for mock context const createMockContext = (overrides = {}) => ({ meds: [], settings: { lowStockThreshold: 30, criticalStockThreshold: 7, expiryWarningDays: 30, lowStockDays: 7, normalStockDays: 30, highStockDays: 90, }, scheduleDays: 30, setScheduleDays: vi.fn(), showPastDays: false, setShowPastDays: vi.fn(), pastDays: [], futureDays: [], takenDoses: new Set(), markDoseTaken: vi.fn(), undoDoseTaken: vi.fn(), coverageByMed: {}, depletionByMed: {}, manuallyExpandedDays: new Set(), toggleDayCollapse: vi.fn(), openUserFilter: vi.fn(), ...overrides, }); let mockContextValue = createMockContext(); // Mock the context vi.mock("../../context", () => ({ useAppContext: () => mockContextValue, })); vi.mock("../../components/Auth", () => ({ useAuth: () => ({ user: { id: 1, username: "testuser" }, }), })); describe("SchedulePage", () => { beforeEach(() => { vi.clearAllMocks(); localStorage.clear(); mockContextValue = createMockContext(); }); it("renders schedule page", () => { render( ); // Should render the schedule section const section = document.querySelector("section.grid"); expect(section).toBeInTheDocument(); }); it("renders schedule title", () => { render( ); expect(screen.getByText(/dashboard\.schedules\.title/i)).toBeInTheDocument(); }); it("renders day range selector", () => { render( ); // Should have schedule days select dropdown const select = document.querySelector(".schedule-days-select"); expect(select).toBeInTheDocument(); }); it("renders timeline section", () => { render( ); // Should have timeline div const timeline = document.querySelector(".timeline"); expect(timeline).toBeInTheDocument(); }); it("shows empty state when no medications", () => { render( ); // With no meds, should show the schedule card but with empty timeline const card = document.querySelector(".card.schedule-full"); expect(card).toBeInTheDocument(); }); it("renders card head", () => { render( ); const cardHead = document.querySelector(".card-head"); expect(cardHead).toBeInTheDocument(); }); it("renders schedule days options", () => { render( ); const select = document.querySelector(".schedule-days-select"); const options = select?.querySelectorAll("option"); expect(options?.length).toBe(3); }); it("has 30, 90, 180 day options", () => { render( ); expect(screen.getByText(/dashboard\.schedules\.1month/i)).toBeInTheDocument(); expect(screen.getByText(/dashboard\.schedules\.3months/i)).toBeInTheDocument(); expect(screen.getByText(/dashboard\.schedules\.6months/i)).toBeInTheDocument(); }); it("can change schedule days", () => { render( ); const select = document.querySelector(".schedule-days-select") as HTMLSelectElement; expect(select).toBeInTheDocument(); fireEvent.change(select, { target: { value: "90" } }); }); }); describe("SchedulePage structure", () => { beforeEach(() => { vi.clearAllMocks(); localStorage.clear(); mockContextValue = createMockContext(); }); it("has heading element", () => { render( ); const heading = document.querySelector("h2"); expect(heading).toBeInTheDocument(); }); it("renders article element", () => { render( ); const article = document.querySelector("article"); expect(article).toBeInTheDocument(); }); it("renders section element", () => { render( ); const section = document.querySelector("section"); expect(section).toBeInTheDocument(); }); it("renders card with correct class", () => { render( ); const card = document.querySelector(".card.schedule-full"); expect(card).toBeInTheDocument(); }); }); describe("SchedulePage with medications", () => { beforeEach(() => { vi.clearAllMocks(); localStorage.clear(); mockContextValue = createMockContext({ meds: mockMeds, futureDays: mockFutureDays, coverageByMed: mockCoverageByMed, depletionByMed: { Aspirin: Date.now() + 25 * 86400000 }, }); }); it("renders medication in timeline", () => { render( ); expect(screen.getByText("Aspirin")).toBeInTheDocument(); }); it("renders day block", () => { render( ); const dayBlocks = document.querySelectorAll(".day-block"); expect(dayBlocks.length).toBeGreaterThan(0); }); it("renders dose item", () => { render( ); const doseItems = document.querySelectorAll(".dose-item"); expect(doseItems.length).toBeGreaterThan(0); }); it("renders take button", () => { render( ); const takeBtn = document.querySelector(".dose-btn.take"); expect(takeBtn).toBeInTheDocument(); }); it("calls markDoseTaken when clicking take button", () => { const markDoseTaken = vi.fn(); mockContextValue = createMockContext({ meds: mockMeds, futureDays: mockFutureDays, coverageByMed: mockCoverageByMed, markDoseTaken, }); render( ); const takeBtn = document.querySelector(".dose-btn.take"); if (takeBtn) { fireEvent.click(takeBtn); expect(markDoseTaken).toHaveBeenCalled(); } }); it("renders person name for dose", () => { render( ); expect(screen.getByText("John")).toBeInTheDocument(); }); it("calls openUserFilter when clicking person name", () => { const openUserFilter = vi.fn(); mockContextValue = createMockContext({ meds: mockMeds, futureDays: mockFutureDays, coverageByMed: mockCoverageByMed, openUserFilter, }); render( ); const personName = screen.getByText("John"); fireEvent.click(personName); expect(openUserFilter).toHaveBeenCalledWith("John"); }); it("renders pill weight when available", () => { render( ); // Aspirin has pillWeightMg of 500 expect(screen.getByText(/500 mg/)).toBeInTheDocument(); }); it("renders reminder icon when enabled", () => { render( ); // Aspirin has intakeRemindersEnabled const reminderIcon = document.querySelector(".reminder-icon"); expect(reminderIcon).toBeInTheDocument(); }); it("renders day blocks", () => { render( ); // Should have day blocks rendered const dayBlocks = document.querySelectorAll(".day-block"); expect(dayBlocks.length).toBeGreaterThan(0); }); }); describe("SchedulePage with past days", () => { beforeEach(() => { vi.clearAllMocks(); localStorage.clear(); mockContextValue = createMockContext({ meds: mockMeds, pastDays: mockPastDays, futureDays: mockFutureDays, coverageByMed: mockCoverageByMed, showPastDays: false, }); }); it("renders past days toggle when past days exist", () => { render( ); const toggle = document.querySelector(".past-days-toggle"); expect(toggle).toBeInTheDocument(); }); it("shows missed doses warning", () => { render( ); const warning = document.querySelector(".past-days-warning"); expect(warning).toBeInTheDocument(); }); it("toggles past days visibility", () => { const setShowPastDays = vi.fn(); mockContextValue = createMockContext({ pastDays: mockPastDays, showPastDays: false, setShowPastDays, }); render( ); const toggle = document.querySelector(".past-days-toggle"); if (toggle) { fireEvent.click(toggle); expect(setShowPastDays).toHaveBeenCalledWith(true); } }); }); describe("SchedulePage with expanded past days", () => { beforeEach(() => { vi.clearAllMocks(); localStorage.clear(); mockContextValue = createMockContext({ meds: mockMeds, pastDays: mockPastDays, futureDays: mockFutureDays, coverageByMed: mockCoverageByMed, showPastDays: true, manuallyExpandedDays: new Set(["Sun, Jan 21"]), }); }); it("renders past day blocks when showPastDays is true", () => { render( ); const pastDayBlocks = document.querySelectorAll(".day-block.past"); expect(pastDayBlocks.length).toBeGreaterThan(0); }); it("renders day divider for past days", () => { render( ); const dividers = document.querySelectorAll(".day-divider"); expect(dividers.length).toBeGreaterThan(0); }); it("calls toggleDayCollapse when clicking day divider", () => { const toggleDayCollapse = vi.fn(); mockContextValue = createMockContext({ meds: mockMeds, pastDays: mockPastDays, showPastDays: true, manuallyExpandedDays: new Set(["Sun, Jan 21"]), coverageByMed: mockCoverageByMed, toggleDayCollapse, }); render( ); const divider = document.querySelector(".day-block.past .day-divider.clickable"); if (divider) { fireEvent.click(divider); expect(toggleDayCollapse).toHaveBeenCalled(); } }); }); describe("SchedulePage with taken doses", () => { beforeEach(() => { vi.clearAllMocks(); localStorage.clear(); // Match the dose ID format exactly with the mockFutureDays dose // Since we can't predict Date.now(), we make the test check if takenDoses works }); it("marks doses as taken in UI", () => { // Create consistent timestamp for test const timestamp = Date.now(); const doseId = `1-0-${timestamp}-John`; const testFutureDays = [ { dateStr: "Mon, Jan 22", date: new Date(timestamp), isPast: false, meds: [ { medName: "Aspirin", total: 1, doses: [{ id: `1-0-${timestamp}`, timeStr: "09:00", when: timestamp, usage: 1, takenBy: ["John"] }], lastWhen: timestamp, }, ], }, ]; mockContextValue = createMockContext({ meds: mockMeds, futureDays: testFutureDays, coverageByMed: mockCoverageByMed, takenDoses: new Set([doseId]), }); render( ); // When dose is taken, the undo button should appear const undoBtn = document.querySelector(".dose-btn.undo"); expect(undoBtn).toBeInTheDocument(); }); it("calls undoDoseTaken when clicking undo button", () => { const undoDoseTaken = vi.fn(); const timestamp = Date.now(); const doseId = `1-0-${timestamp}-John`; const testFutureDays = [ { dateStr: "Mon, Jan 22", date: new Date(timestamp), isPast: false, meds: [ { medName: "Aspirin", total: 1, doses: [{ id: `1-0-${timestamp}`, timeStr: "09:00", when: timestamp, usage: 1, takenBy: ["John"] }], lastWhen: timestamp, }, ], }, ]; mockContextValue = createMockContext({ meds: mockMeds, futureDays: testFutureDays, coverageByMed: mockCoverageByMed, takenDoses: new Set([doseId]), undoDoseTaken, }); render( ); const undoBtn = document.querySelector(".dose-btn.undo"); if (undoBtn) { fireEvent.click(undoBtn); expect(undoDoseTaken).toHaveBeenCalled(); } }); }); describe("SchedulePage with low stock", () => { beforeEach(() => { vi.clearAllMocks(); localStorage.clear(); mockContextValue = createMockContext({ meds: mockMeds, futureDays: mockFutureDays, coverageByMed: { Aspirin: { name: "Aspirin", medsLeft: 3, daysLeft: 3, depletionDate: "2025-01-25", depletionTime: Date.now() + 3 * 86400000, nextDose: null, }, }, depletionByMed: { Aspirin: Date.now() + 3 * 86400000 }, }); }); it("shows status tag for medications", () => { render( ); const tags = document.querySelectorAll(".tag"); expect(tags.length).toBeGreaterThan(0); }); });