import { render, screen, waitFor, fireEvent } from '@testing-library/react';
import { useRouter } from 'next/navigation';
import DashboardPage from '../page';
import api from '@/lib/api';
import { useAuthStore } from '@/stores/authStore';
import type { Beneficiary } from '@/types';
// Mock dependencies
jest.mock('next/navigation', () => ({
useRouter: jest.fn(),
}));
jest.mock('@/lib/api');
jest.mock('@/stores/authStore');
describe('DashboardPage', () => {
const mockRouter = {
push: jest.fn(),
};
const mockBeneficiaries: Beneficiary[] = [
{
id: 1,
name: 'Maria Garcia',
displayName: 'Maria Garcia',
email: 'maria@example.com',
status: 'offline',
hasDevices: true,
equipmentStatus: 'active',
subscription: {
status: 'active',
planType: 'monthly',
endDate: '2025-12-31',
cancelAtPeriodEnd: false,
},
role: 'custodian',
address: '123 Main St, City',
avatar: 'https://example.com/avatar.jpg',
},
{
id: 2,
name: 'John Smith',
displayName: 'John Smith',
email: 'john@example.com',
status: 'offline',
hasDevices: false,
equipmentStatus: 'none',
role: 'caretaker',
},
];
beforeEach(() => {
jest.clearAllMocks();
(useRouter as jest.Mock).mockReturnValue(mockRouter);
(useAuthStore as unknown as jest.Mock).mockReturnValue({
isAuthenticated: true,
user: { firstName: 'Test', user_id: 1 },
});
});
describe('Loading State', () => {
it('should show loading spinner while fetching data', () => {
(api.getAllBeneficiaries as jest.Mock).mockImplementation(
() => new Promise(() => {}) // Never resolves
);
render();
expect(screen.getByRole('status')).toBeInTheDocument();
});
});
describe('Authentication', () => {
it('should redirect to login if not authenticated', () => {
(useAuthStore as unknown as jest.Mock).mockReturnValue({
isAuthenticated: false,
user: null,
});
render();
expect(mockRouter.push).toHaveBeenCalledWith('/login');
});
it('should not redirect if authenticated', async () => {
(api.getAllBeneficiaries as jest.Mock).mockResolvedValue({
ok: true,
data: [],
});
render();
await waitFor(() => {
expect(mockRouter.push).not.toHaveBeenCalled();
});
});
});
describe('Data Fetching', () => {
it('should fetch beneficiaries on mount', async () => {
(api.getAllBeneficiaries as jest.Mock).mockResolvedValue({
ok: true,
data: mockBeneficiaries,
});
render();
await waitFor(() => {
expect(api.getAllBeneficiaries).toHaveBeenCalledTimes(1);
});
});
it('should display beneficiaries after successful fetch', async () => {
(api.getAllBeneficiaries as jest.Mock).mockResolvedValue({
ok: true,
data: mockBeneficiaries,
});
render();
await waitFor(() => {
expect(screen.getByText('Maria Garcia')).toBeInTheDocument();
expect(screen.getByText('John Smith')).toBeInTheDocument();
});
});
it('should display error message on fetch failure', async () => {
const errorMessage = 'Failed to load beneficiaries';
(api.getAllBeneficiaries as jest.Mock).mockResolvedValue({
ok: false,
error: { message: errorMessage },
});
render();
await waitFor(() => {
expect(screen.getByText(errorMessage)).toBeInTheDocument();
});
});
it('should handle network errors gracefully', async () => {
(api.getAllBeneficiaries as jest.Mock).mockRejectedValue(
new Error('Network error')
);
render();
await waitFor(() => {
expect(screen.getByText('Network error')).toBeInTheDocument();
});
});
});
describe('Header and Welcome Message', () => {
it('should display welcome message with user first name', async () => {
(api.getAllBeneficiaries as jest.Mock).mockResolvedValue({
ok: true,
data: [],
});
render();
await waitFor(() => {
expect(screen.getByText(/Welcome, Test/i)).toBeInTheDocument();
});
});
it('should display welcome message without name if not available', async () => {
(useAuthStore as unknown as jest.Mock).mockReturnValue({
isAuthenticated: true,
user: { user_id: 1 },
});
(api.getAllBeneficiaries as jest.Mock).mockResolvedValue({
ok: true,
data: [],
});
render();
await waitFor(() => {
expect(screen.getByText(/Welcome$/i)).toBeInTheDocument();
});
});
});
describe('Summary Cards', () => {
it('should display correct total beneficiaries count', async () => {
(api.getAllBeneficiaries as jest.Mock).mockResolvedValue({
ok: true,
data: mockBeneficiaries,
});
render();
await waitFor(() => {
expect(screen.getByText('Total Beneficiaries')).toBeInTheDocument();
const totalCard = screen.getByText('Total Beneficiaries').closest('div');
expect(totalCard).toHaveTextContent('2');
});
});
it('should display correct equipment count', async () => {
(api.getAllBeneficiaries as jest.Mock).mockResolvedValue({
ok: true,
data: mockBeneficiaries,
});
render();
await waitFor(() => {
expect(screen.getByText('With Equipment')).toBeInTheDocument();
const equipmentCard = screen.getByText('With Equipment').closest('div');
expect(equipmentCard).toHaveTextContent('1'); // Only Maria has equipment
});
});
it('should display correct active subscriptions count', async () => {
(api.getAllBeneficiaries as jest.Mock).mockResolvedValue({
ok: true,
data: mockBeneficiaries,
});
render();
await waitFor(() => {
expect(screen.getByText('Active Subscriptions')).toBeInTheDocument();
const subscriptionCard = screen.getByText('Active Subscriptions').closest('div');
expect(subscriptionCard).toHaveTextContent('1'); // Only Maria has active subscription
});
});
});
describe('Empty State', () => {
it('should display empty state when no beneficiaries', async () => {
(api.getAllBeneficiaries as jest.Mock).mockResolvedValue({
ok: true,
data: [],
});
render();
await waitFor(() => {
expect(screen.getByText('No beneficiaries yet')).toBeInTheDocument();
expect(
screen.getByText(/Add your first loved one to start monitoring/)
).toBeInTheDocument();
});
});
it('should show add beneficiary button in empty state', async () => {
(api.getAllBeneficiaries as jest.Mock).mockResolvedValue({
ok: true,
data: [],
});
render();
await waitFor(() => {
const addButton = screen.getByText('Add Your First Beneficiary');
expect(addButton).toBeInTheDocument();
});
});
});
describe('Beneficiary Cards', () => {
it('should display beneficiary avatar if available', async () => {
(api.getAllBeneficiaries as jest.Mock).mockResolvedValue({
ok: true,
data: mockBeneficiaries,
});
render();
await waitFor(() => {
const avatar = screen.getByRole('img', { name: 'Maria Garcia' });
expect(avatar).toBeInTheDocument();
expect(avatar).toHaveAttribute('src', 'https://example.com/avatar.jpg');
});
});
it('should display default avatar icon if no image', async () => {
(api.getAllBeneficiaries as jest.Mock).mockResolvedValue({
ok: true,
data: [mockBeneficiaries[1]], // John has no avatar
});
render();
await waitFor(() => {
const card = screen.getByText('John Smith').closest('button');
expect(card).toHaveTextContent('👤');
});
});
it('should display equipment status badges correctly', async () => {
(api.getAllBeneficiaries as jest.Mock).mockResolvedValue({
ok: true,
data: mockBeneficiaries,
});
render();
await waitFor(() => {
const activeBadges = screen.getAllByText('Active');
expect(activeBadges.length).toBeGreaterThan(0);
expect(screen.getByText('No Equipment')).toBeInTheDocument();
});
});
it('should display subscription status badges', async () => {
(api.getAllBeneficiaries as jest.Mock).mockResolvedValue({
ok: true,
data: mockBeneficiaries,
});
render();
await waitFor(() => {
// Maria has active subscription - "Active" text appears twice (equipment + subscription)
const activeBadges = screen.getAllByText('Active');
expect(activeBadges.length).toBeGreaterThanOrEqual(1);
});
});
it('should display role badges', async () => {
(api.getAllBeneficiaries as jest.Mock).mockResolvedValue({
ok: true,
data: mockBeneficiaries,
});
render();
await waitFor(() => {
expect(screen.getByText('Custodian')).toBeInTheDocument();
expect(screen.getByText('Caretaker')).toBeInTheDocument();
});
});
it('should display address if available', async () => {
(api.getAllBeneficiaries as jest.Mock).mockResolvedValue({
ok: true,
data: mockBeneficiaries,
});
render();
await waitFor(() => {
expect(screen.getByText(/📍 123 Main St, City/)).toBeInTheDocument();
});
});
});
describe('Navigation', () => {
it('should navigate to add beneficiary page when clicking add button', async () => {
(api.getAllBeneficiaries as jest.Mock).mockResolvedValue({
ok: true,
data: mockBeneficiaries,
});
render();
await waitFor(() => {
const addButton = screen.getByText('+ Add Beneficiary');
fireEvent.click(addButton);
});
expect(mockRouter.push).toHaveBeenCalledWith('/add-loved-one');
});
it('should navigate to beneficiary detail when clicking card', async () => {
(api.getAllBeneficiaries as jest.Mock).mockResolvedValue({
ok: true,
data: mockBeneficiaries,
});
render();
await waitFor(() => {
const mariaCard = screen.getByText('Maria Garcia').closest('button');
if (mariaCard) {
fireEvent.click(mariaCard);
}
});
expect(mockRouter.push).toHaveBeenCalledWith('/beneficiaries/1');
});
});
describe('Equipment Status Badge Colors', () => {
it.each([
['none', 'No Equipment', 'bg-slate-100'],
['ordered', 'Ordered', 'bg-blue-100'],
['shipped', 'Shipped', 'bg-yellow-100'],
['delivered', 'Delivered', 'bg-green-100'],
['active', 'Active', 'bg-green-100'],
['demo', 'Demo', 'bg-purple-100'],
])(
'should display %s status with correct badge',
async (status, text, colorClass) => {
const beneficiary: Beneficiary = {
...mockBeneficiaries[0],
equipmentStatus: status as any,
// Remove subscription for "active" status test to avoid duplicate "Active" text
subscription: status === 'active' ? undefined : mockBeneficiaries[0].subscription,
};
(api.getAllBeneficiaries as jest.Mock).mockResolvedValue({
ok: true,
data: [beneficiary],
});
const { container } = render();
await waitFor(() => {
// Use getAllByText for "Active" which may appear in both equipment and subscription
const badges = screen.getAllByText(text);
expect(badges.length).toBeGreaterThan(0);
expect(badges[0].className).toContain(colorClass);
}, { timeout: 3000 });
}
);
});
describe('Subscription Status Badge Colors', () => {
it.each([
['active', 'Active', 'bg-green-100'],
['trial', 'Trial', 'bg-blue-100'],
['expired', 'Expired', 'bg-red-100'],
['cancelled', 'Cancelled', 'bg-slate-100'],
])(
'should display %s subscription with correct badge',
async (status, text, colorClass) => {
const beneficiary: Beneficiary = {
...mockBeneficiaries[0],
subscription: {
status: status as any,
planType: 'monthly',
endDate: '2025-12-31',
cancelAtPeriodEnd: false,
},
};
(api.getAllBeneficiaries as jest.Mock).mockResolvedValue({
ok: true,
data: [beneficiary],
});
const { container } = render();
await waitFor(() => {
const badges = screen.getAllByText(text);
expect(badges.length).toBeGreaterThan(0);
expect(badges[0].className).toContain(colorClass);
}, { timeout: 3000 });
}
);
});
describe('Error Retry', () => {
it('should show retry button on error', async () => {
(api.getAllBeneficiaries as jest.Mock).mockResolvedValue({
ok: false,
error: { message: 'Test error' },
});
render();
await waitFor(() => {
// ErrorMessage component uses "Retry" button text
const retryButton = screen.getByText('Retry');
expect(retryButton).toBeInTheDocument();
});
});
});
});