import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, waitFor } from '@testing-library/react'; const { mockUseApi } = vi.hoisted(() => ({ mockUseApi: vi.fn(), })); const { mockListRegimens } = vi.hoisted(() => ({ mockListRegimens: vi.fn(), })); vi.mock('@/lib/useApi', () => ({ useApi: mockUseApi })); vi.mock('@/services/regimens', () => ({ listRegimens: mockListRegimens, })); vi.mock('next/link', () => ({ // eslint-disable-next-line @typescript-eslint/no-explicit-any default: (props: any) => {props.children}, })); import SchedulePage from '../../../../../src/app/(dashboard)/medicines/schedule/page'; beforeEach(() => vi.clearAllMocks()); const makeRegimen = (overrides = {}) => ({ _id: 'reg1', householdId: 'hh1', name: 'Daily Vitamins', isActive: true, startDate: '2026-01-01', medications: [ { medicineId: 'm1', medicineName: 'Vitamin D', medicineStrength: '1000', medicineStrengthUnit: 'IU', dosage: 1, dosageUnit: 'tablet', frequency: 'daily', timeOfDay: 'morning', }, ], createdBy: 'u1', createdAt: '2026-01-01T00:00:00.000Z', updatedAt: '2026-01-01T00:00:00.000Z', ...overrides, }); describe('SchedulePage', () => { it('shows loading skeleton when session is loading', () => { mockUseApi.mockReturnValue({ householdId: null, isLoading: true }); render(); expect(screen.getByText('Schedule & Log')).toBeInTheDocument(); }); it('shows no household message', () => { mockUseApi.mockReturnValue({ householdId: null, isLoading: false }); render(); expect(screen.getByText(/create or join a household/)).toBeInTheDocument(); }); it('shows empty state when no regimens', async () => { mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false }); mockListRegimens.mockResolvedValue({ data: [], pagination: { cursor: null, hasMore: false }, }); render(); await waitFor(() => { expect(screen.getByText('No active regimens found.')).toBeInTheDocument(); }); }); it('shows set up link in empty state', async () => { mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false }); mockListRegimens.mockResolvedValue({ data: [], pagination: { cursor: null, hasMore: false }, }); render(); await waitFor(() => { expect(screen.getByText('Set up a regimen')).toBeInTheDocument(); }); }); it('renders medication in morning slot', async () => { mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false }); mockListRegimens.mockResolvedValue({ data: [makeRegimen()], pagination: { cursor: null, hasMore: false }, }); render(); await waitFor(() => { expect(screen.getByText('Vitamin D')).toBeInTheDocument(); expect(screen.getByText('Morning')).toBeInTheDocument(); expect(screen.getByText('1000 IU')).toBeInTheDocument(); expect(screen.getByText('Daily Vitamins')).toBeInTheDocument(); }); }); it('shows frequency label', async () => { mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false }); mockListRegimens.mockResolvedValue({ data: [makeRegimen()], pagination: { cursor: null, hasMore: false }, }); render(); await waitFor(() => { expect(screen.getByText(/Once daily/)).toBeInTheDocument(); }); }); it('shows custom frequency', async () => { mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false }); mockListRegimens.mockResolvedValue({ data: [ makeRegimen({ medications: [ { medicineId: 'm1', medicineName: 'Custom Med', medicineStrength: '50', medicineStrengthUnit: 'mg', dosage: 1, dosageUnit: 'tablet', frequency: 'custom', customFrequencyPerDay: 4, timeOfDay: 'morning', }, ], }), ], pagination: { cursor: null, hasMore: false }, }); render(); await waitFor(() => { expect(screen.getByText(/4x daily/)).toBeInTheDocument(); }); }); it('shows instructions when present', async () => { mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false }); mockListRegimens.mockResolvedValue({ data: [ makeRegimen({ medications: [ { medicineId: 'm1', medicineName: 'Med A', medicineStrength: '10', medicineStrengthUnit: 'mg', dosage: 1, dosageUnit: 'pill', frequency: 'daily', timeOfDay: 'evening', instructions: 'Take with food', }, ], }), ], pagination: { cursor: null, hasMore: false }, }); render(); await waitFor(() => { expect(screen.getByText('Take with food')).toBeInTheDocument(); expect(screen.getByText('Evening')).toBeInTheDocument(); }); }); it('shows dose count', async () => { mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false }); mockListRegimens.mockResolvedValue({ data: [makeRegimen()], pagination: { cursor: null, hasMore: false }, }); render(); await waitFor(() => { expect(screen.getByText('1 dose')).toBeInTheDocument(); }); }); it('shows error when fetch fails', async () => { mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false }); mockListRegimens.mockRejectedValue(new Error('Network error')); render(); await waitFor(() => { expect(screen.getByText('Network error')).toBeInTheDocument(); }); }); it('shows fallback error for non-Error failures', async () => { mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false }); mockListRegimens.mockRejectedValue('unexpected'); render(); await waitFor(() => { expect(screen.getByText('Failed to load regimens')).toBeInTheDocument(); }); }); it('groups into "any" slot when timeOfDay is missing', async () => { mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false }); mockListRegimens.mockResolvedValue({ data: [ makeRegimen({ medications: [ { medicineId: 'm2', medicineName: 'Aspirin', medicineStrength: '100', medicineStrengthUnit: 'mg', dosage: 1, dosageUnit: 'tablet', frequency: 'as_needed', }, ], }), ], pagination: { cursor: null, hasMore: false }, }); render(); await waitFor(() => { expect(screen.getByText('Aspirin')).toBeInTheDocument(); expect(screen.getByText('Any time')).toBeInTheDocument(); }); }); it('paginates through regimens', async () => { mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false }); mockListRegimens .mockResolvedValueOnce({ data: [makeRegimen()], pagination: { cursor: 'next', hasMore: true }, }) .mockResolvedValueOnce({ data: [ makeRegimen({ _id: 'reg2', name: 'Second Regimen', medications: [ { medicineId: 'm2', medicineName: 'Iron', medicineStrength: '65', medicineStrengthUnit: 'mg', dosage: 1, dosageUnit: 'tablet', frequency: 'daily', timeOfDay: 'afternoon', }, ], }), ], pagination: { cursor: null, hasMore: false }, }); render(); await waitFor(() => { expect(screen.getByText('Vitamin D')).toBeInTheDocument(); expect(screen.getByText('Iron')).toBeInTheDocument(); }); expect(mockListRegimens).toHaveBeenCalledTimes(2); }); it('shows regimen and dose summary', async () => { mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false }); mockListRegimens.mockResolvedValue({ data: [makeRegimen()], pagination: { cursor: null, hasMore: false }, }); render(); await waitFor(() => { expect(screen.getByText(/1 active regimen/)).toBeInTheDocument(); expect(screen.getByText(/1 dose per day/)).toBeInTheDocument(); }); }); });