305 lines
8.5 KiB
TypeScript
305 lines
8.5 KiB
TypeScript
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) => <a href={props.href}>{props.children}</a>,
|
|
}));
|
|
|
|
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(<SchedulePage />);
|
|
|
|
expect(screen.getByText('Schedule & Log')).toBeInTheDocument();
|
|
});
|
|
|
|
it('shows no household message', () => {
|
|
mockUseApi.mockReturnValue({ householdId: null, isLoading: false });
|
|
|
|
render(<SchedulePage />);
|
|
|
|
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(<SchedulePage />);
|
|
|
|
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(<SchedulePage />);
|
|
|
|
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(<SchedulePage />);
|
|
|
|
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(<SchedulePage />);
|
|
|
|
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(<SchedulePage />);
|
|
|
|
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(<SchedulePage />);
|
|
|
|
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(<SchedulePage />);
|
|
|
|
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(<SchedulePage />);
|
|
|
|
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(<SchedulePage />);
|
|
|
|
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(<SchedulePage />);
|
|
|
|
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(<SchedulePage />);
|
|
|
|
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(<SchedulePage />);
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText(/1 active regimen/)).toBeInTheDocument();
|
|
expect(screen.getByText(/1 dose per day/)).toBeInTheDocument();
|
|
});
|
|
});
|
|
});
|