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