MeshiTrack/packages/web/tests/app/(dashboard)/dashboard/page.test.tsx

338 lines
11 KiB
TypeScript
Raw Normal View History

2026-04-26 18:44:59 +09:00
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen } from '@testing-library/react';
2026-04-26 18:44:59 +09:00
const { mockUseApi } = vi.hoisted(() => ({ mockUseApi: vi.fn() }));
2026-05-14 14:47:23 +09:00
const { mockUseSWR } = vi.hoisted(() => ({ mockUseSWR: vi.fn() }));
2026-04-26 18:44:59 +09:00
vi.mock('@/lib/useApi', () => ({ useApi: mockUseApi }));
2026-05-14 14:47:23 +09:00
vi.mock('swr', () => ({ default: mockUseSWR }));
vi.mock('@/services/cabinet', () => ({
getCabinetSummary: vi.fn(),
2026-05-19 10:13:40 +09:00
}));
vi.mock('@/services/regimens', () => ({
getBurnRates: vi.fn(),
2026-05-14 14:47:23 +09:00
}));
vi.mock('@/services/purchases', () => ({
listPurchases: vi.fn(),
}));
vi.mock('@/services/refills', () => ({
getRefillAlerts: vi.fn(),
}));
vi.mock('@/services/cabinet-events', () => ({
listCabinetEvents: vi.fn(),
}));
vi.mock('next/link', () => ({
// eslint-disable-next-line @typescript-eslint/no-explicit-any
default: (props: any) => <a href={props.href}>{props.children}</a>,
}));
2026-05-19 11:06:03 +09:00
import DashboardPage from '../../../../src/app/(dashboard)/dashboard/page';
2026-04-26 18:44:59 +09:00
beforeEach(() => {
vi.clearAllMocks();
mockUseApi.mockReturnValue({ householdId: null, isLoading: true, profile: undefined });
2026-05-14 14:47:23 +09:00
mockUseSWR.mockReturnValue({ data: undefined });
2026-04-26 18:44:59 +09:00
});
describe(DashboardPage.name, () => {
it('renders heading', () => {
render(<DashboardPage />);
expect(screen.getByText('Dashboard')).toBeInTheDocument();
});
2026-04-26 18:44:59 +09:00
it('shows loading skeleton when session loading', () => {
render(<DashboardPage />);
2026-04-26 18:44:59 +09:00
expect(screen.getByText('Dashboard')).toBeInTheDocument();
});
2026-05-14 14:47:23 +09:00
it('renders greeting with user name', () => {
2026-04-26 18:44:59 +09:00
mockUseApi.mockReturnValue({
householdId: 'hh1',
isLoading: false,
2026-05-14 14:47:23 +09:00
profile: { displayName: 'John Doe' },
2026-04-26 18:44:59 +09:00
});
2026-05-14 14:47:23 +09:00
render(<DashboardPage />);
2026-05-14 14:47:23 +09:00
expect(screen.getByText(/John/)).toBeInTheDocument();
});
it('renders generic greeting without profile', () => {
mockUseApi.mockReturnValue({ householdId: 'hh1', isLoading: false, profile: null });
render(<DashboardPage />);
expect(screen.getByText(/there/)).toBeInTheDocument();
});
2026-05-19 10:13:40 +09:00
it('renders cabinet items that are in active regimens', () => {
2026-05-14 14:47:23 +09:00
mockUseApi.mockReturnValue({
householdId: 'hh1',
isLoading: false,
profile: { displayName: 'Jane' },
});
2026-05-19 10:13:40 +09:00
const summaryData = {
data: [
{ medicineId: '1', medicineName: 'Aspirin', totalQuantity: 50, unit: 'tablets' },
{ medicineId: '2', medicineName: 'Ibuprofen', totalQuantity: 100, unit: 'tablets' },
],
};
const burnRateData = {
data: [
{ medicineId: '1', medicineName: 'Aspirin', daysUntilEmpty: 10 },
],
2026-05-14 14:47:23 +09:00
};
mockUseSWR.mockImplementation((key: string) => {
if (!key) return { data: undefined };
if (key.includes('cabinet-summary')) return { data: summaryData };
2026-05-19 10:13:40 +09:00
if (key.includes('burn-rates')) return { data: burnRateData };
2026-05-14 14:47:23 +09:00
if (key.includes('refill-alerts')) return { data: { data: [] } };
if (key.includes('purchases')) return { data: { data: [] } };
if (key.includes('cabinet-events')) return { data: { data: [] } };
return { data: undefined };
});
render(<DashboardPage />);
expect(screen.getByText('Aspirin')).toBeInTheDocument();
2026-05-19 10:13:40 +09:00
// Ibuprofen should NOT be in the document because it has no burn rate
expect(screen.queryByText('Ibuprofen')).not.toBeInTheDocument();
2026-05-14 14:47:23 +09:00
});
it('shows empty states when no data', () => {
mockUseApi.mockReturnValue({
householdId: 'hh1',
isLoading: false,
profile: { displayName: 'Jane' },
});
mockUseSWR.mockImplementation((key: string) => {
if (!key) return { data: undefined };
if (key.includes('cabinet-summary')) return { data: { data: [] } };
2026-05-19 10:13:40 +09:00
if (key.includes('burn-rates')) return { data: { data: [] } };
2026-05-14 14:47:23 +09:00
if (key.includes('refill-alerts')) return { data: { data: [] } };
if (key.includes('purchases')) return { data: { data: [] } };
if (key.includes('cabinet-events')) return { data: { data: [] } };
return { data: undefined };
});
render(<DashboardPage />);
2026-05-19 10:13:40 +09:00
expect(screen.getByText('No active medicines in regimens.')).toBeInTheDocument();
2026-05-14 14:47:23 +09:00
expect(screen.getByText('No alerts — all stocked.')).toBeInTheDocument();
expect(screen.getByText('No pending orders.')).toBeInTheDocument();
expect(screen.getByText('No recent activity.')).toBeInTheDocument();
});
it('shows refill alerts with days left', () => {
mockUseApi.mockReturnValue({
householdId: 'hh1',
isLoading: false,
profile: { displayName: 'Jane' },
});
const refillData = {
data: [{ medicineId: 'm1', medicineName: 'Vitamin C', daysUntilEmpty: 5 }],
};
mockUseSWR.mockImplementation((key: string) => {
if (!key) return { data: undefined };
2026-05-19 10:13:40 +09:00
if (key.includes('cabinet-summary')) return { data: { data: [{ medicineId: '1' }] } };
if (key.includes('burn-rates')) return { data: { data: [] } };
2026-05-14 14:47:23 +09:00
if (key.includes('refill-alerts')) return { data: refillData };
if (key.includes('purchases')) return { data: { data: [] } };
if (key.includes('cabinet-events')) return { data: { data: [] } };
return { data: undefined };
});
render(<DashboardPage />);
expect(screen.getByText('Vitamin C')).toBeInTheDocument();
expect(screen.getByText('5d')).toBeInTheDocument();
expect(screen.getByText(/critically low/)).toBeInTheDocument();
});
it('shows pending purchases', () => {
mockUseApi.mockReturnValue({
householdId: 'hh1',
isLoading: false,
profile: { displayName: 'Jane' },
});
const purchaseData = {
data: [
{
_id: 'p1',
storeName: 'Pharmacy Plus',
items: [{ name: 'A' }, { name: 'B' }],
status: 'ordered',
},
],
};
mockUseSWR.mockImplementation((key: string) => {
if (!key) return { data: undefined };
if (key.includes('cabinet-summary')) return { data: { data: [] } };
2026-05-19 10:13:40 +09:00
if (key.includes('burn-rates')) return { data: { data: [] } };
2026-05-14 14:47:23 +09:00
if (key.includes('refill-alerts')) return { data: { data: [] } };
if (key.includes('purchases')) return { data: purchaseData };
if (key.includes('cabinet-events')) return { data: { data: [] } };
return { data: undefined };
});
render(<DashboardPage />);
expect(screen.getByText('Pharmacy Plus')).toBeInTheDocument();
expect(screen.getByText('2 items')).toBeInTheDocument();
expect(screen.getByText('ordered')).toBeInTheDocument();
});
it('shows recent events', () => {
mockUseApi.mockReturnValue({
householdId: 'hh1',
isLoading: false,
profile: { displayName: 'Jane' },
});
const eventData = {
data: [
{
_id: 'e1',
eventType: 'consumed',
medicineName: 'Aspirin',
createdAt: '2026-01-15T10:00:00.000Z',
},
],
};
mockUseSWR.mockImplementation((key: string) => {
if (!key) return { data: undefined };
if (key.includes('cabinet-summary')) return { data: { data: [] } };
2026-05-19 10:13:40 +09:00
if (key.includes('burn-rates')) return { data: { data: [] } };
2026-05-14 14:47:23 +09:00
if (key.includes('refill-alerts')) return { data: { data: [] } };
if (key.includes('purchases')) return { data: { data: [] } };
if (key.includes('cabinet-events')) return { data: eventData };
return { data: undefined };
});
render(<DashboardPage />);
expect(screen.getByText('consumed')).toBeInTheDocument();
expect(screen.getByText('Aspirin')).toBeInTheDocument();
});
it('shows "good shape" message when no critical alerts', () => {
mockUseApi.mockReturnValue({
householdId: 'hh1',
isLoading: false,
profile: { displayName: 'Jane' },
});
mockUseSWR.mockImplementation((key: string) => {
if (!key) return { data: undefined };
2026-05-19 10:13:40 +09:00
if (key.includes('cabinet-summary')) return { data: { data: [{ medicineId: '1' }] } };
if (key.includes('burn-rates')) return { data: { data: [] } };
2026-05-14 14:47:23 +09:00
if (key.includes('refill-alerts')) return { data: { data: [{ daysUntilEmpty: 20 }] } };
if (key.includes('purchases')) return { data: { data: [] } };
if (key.includes('cabinet-events')) return { data: { data: [] } };
return { data: undefined };
});
render(<DashboardPage />);
expect(screen.getByText('Your cabinet is in good shape.')).toBeInTheDocument();
});
it('shows stat badges with summary data', () => {
mockUseApi.mockReturnValue({
householdId: 'hh1',
isLoading: false,
profile: { displayName: 'Jane' },
});
mockUseSWR.mockImplementation((key: string) => {
if (!key) return { data: undefined };
if (key.includes('cabinet-summary'))
2026-05-19 10:13:40 +09:00
return { data: { data: [{ medicineId: '1' }, { medicineId: '2' }, { medicineId: '3' }] } };
if (key.includes('burn-rates')) return { data: { data: [] } };
2026-05-14 14:47:23 +09:00
if (key.includes('refill-alerts'))
return { data: { data: [{ daysUntilEmpty: 5 }, { daysUntilEmpty: 3 }] } };
if (key.includes('purchases')) return { data: { data: [] } };
if (key.includes('cabinet-events')) return { data: { data: [] } };
return { data: undefined };
});
render(<DashboardPage />);
expect(screen.getByText('Total medicines')).toBeInTheDocument();
expect(screen.getByText('3')).toBeInTheDocument();
expect(screen.getAllByText('Running low')).toHaveLength(2);
expect(screen.getByText('2')).toBeInTheDocument();
});
it('handles store without name', () => {
mockUseApi.mockReturnValue({
householdId: 'hh1',
isLoading: false,
profile: { displayName: 'Jane' },
});
const purchaseData = {
data: [{ _id: 'p1', storeName: undefined, items: [{ name: 'A' }], status: 'ordered' }],
};
mockUseSWR.mockImplementation((key: string) => {
if (!key) return { data: undefined };
if (key.includes('cabinet-summary')) return { data: { data: [] } };
2026-05-19 10:13:40 +09:00
if (key.includes('burn-rates')) return { data: { data: [] } };
2026-05-14 14:47:23 +09:00
if (key.includes('refill-alerts')) return { data: { data: [] } };
if (key.includes('purchases')) return { data: purchaseData };
if (key.includes('cabinet-events')) return { data: { data: [] } };
return { data: undefined };
});
render(<DashboardPage />);
expect(screen.getByText('Unknown store')).toBeInTheDocument();
});
it('handles cabinet item with no name', () => {
mockUseApi.mockReturnValue({
householdId: 'hh1',
isLoading: false,
profile: { displayName: 'Jane' },
});
2026-05-19 10:13:40 +09:00
const summaryData = {
data: [{ medicineId: 'c1', medicineName: undefined, totalQuantity: 10, unit: 'pills' }],
};
const burnRateData = {
data: [{ medicineId: 'c1', medicineName: undefined, daysUntilEmpty: 5 }],
2026-05-14 14:47:23 +09:00
};
mockUseSWR.mockImplementation((key: string) => {
if (!key) return { data: undefined };
2026-05-19 10:13:40 +09:00
if (key.includes('cabinet-summary')) return { data: summaryData };
if (key.includes('burn-rates')) return { data: burnRateData };
2026-05-14 14:47:23 +09:00
if (key.includes('refill-alerts')) return { data: { data: [] } };
if (key.includes('purchases')) return { data: { data: [] } };
if (key.includes('cabinet-events')) return { data: { data: [] } };
return { data: undefined };
});
render(<DashboardPage />);
expect(screen.getByText('Unknown')).toBeInTheDocument();
});
});