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();
});
});
});