/** * Tests for the AuthContext * @jest-environment jsdom */ import React from 'react'; import { render, screen, waitFor, act, fireEvent } from '@testing-library/react'; import { AuthProvider, useAuth } from '@/contexts/AuthContext'; import { authService } from '@/services/auth.service'; import toast from 'react-hot-toast'; // Mock authService jest.mock('@/services/auth.service', () => ({ authService: { login: jest.fn(), register: jest.fn(), logout: jest.fn(), getCurrentUser: jest.fn(), googleLogin: jest.fn(), facebookLogin: jest.fn(), }, })); // Mock react-hot-toast jest.mock('react-hot-toast', () => ({ success: jest.fn(), error: jest.fn(), })); // Mock utils/helpers jest.mock('@/utils/helpers', () => ({ setToken: jest.fn(), removeToken: jest.fn(), getToken: jest.fn(), storage: { get: jest.fn(), set: jest.fn(), remove: jest.fn(), }, })); import { setToken, removeToken, getToken, storage } from '@/utils/helpers'; // Mock next/navigation const mockPush = jest.fn(); const mockReplace = jest.fn(); jest.mock('next/navigation', () => ({ useRouter: () => ({ push: mockPush, replace: mockReplace, prefetch: jest.fn(), }), })); // Test component that uses the hook const TestComponent: React.FC = () => { const { user, isLoading, isAuthenticated, login, logout, register } = useAuth(); if (isLoading) return
Loading...
; const handleLogin = async () => { try { await login({ email: 'test@test.com', password: 'password' }); } catch { // Error is handled in AuthContext } }; const handleRegister = async () => { try { await register({ email: 'new@test.com', password: 'password', firstName: 'John', lastName: 'Doe', }); } catch { // Error is handled in AuthContext } }; return (
{isAuthenticated ? 'yes' : 'no'}
{user ? JSON.stringify(user) : 'null'}
); }; describe('AuthContext', () => { beforeEach(() => { jest.clearAllMocks(); (getToken as jest.Mock).mockReturnValue(null); }); describe('Initial state', () => { it('should show loading state when token exists', async () => { // When token exists, it fetches user data which shows loading (getToken as jest.Mock).mockReturnValue('valid-token'); // Delay the response to ensure loading state is visible let resolveUser: (value: unknown) => void; const userPromise = new Promise((resolve) => { resolveUser = resolve; }); (authService.getCurrentUser as jest.Mock).mockReturnValue(userPromise); render( ); // Should show loading while fetching user expect(screen.getByText('Loading...')).toBeInTheDocument(); // Resolve the promise await act(async () => { resolveUser!({ id: '1', email: 'test@test.com', role: 'CLIENT' }); }); await waitFor(() => { expect(screen.queryByText('Loading...')).not.toBeInTheDocument(); }); }); it('should have null user when no token exists', async () => { render( ); // When no token, loading completes immediately await waitFor(() => { expect(screen.getByTestId('user')).toHaveTextContent('null'); expect(screen.getByTestId('authenticated')).toHaveTextContent('no'); }); }); it('should load user when token exists', async () => { const mockUser = { id: '1', email: 'test@test.com', firstName: 'John', lastName: 'Doe', role: 'CLIENT', }; (getToken as jest.Mock).mockReturnValue('valid-token'); (authService.getCurrentUser as jest.Mock).mockResolvedValueOnce(mockUser); render( ); await waitFor(() => { expect(screen.getByTestId('authenticated')).toHaveTextContent('yes'); }); }); it('should remove token if getCurrentUser fails', async () => { (getToken as jest.Mock).mockReturnValue('invalid-token'); (authService.getCurrentUser as jest.Mock).mockRejectedValueOnce( new Error('Invalid token') ); render( ); await waitFor(() => { expect(removeToken).toHaveBeenCalled(); expect(storage.remove).toHaveBeenCalled(); }); }); }); describe('login', () => { it('should login successfully and redirect CLIENT', async () => { const mockResponse = { token: 'new-token', user: { id: '1', email: 'test@test.com', firstName: 'John', lastName: 'Doe', role: 'CLIENT', }, }; (authService.login as jest.Mock).mockResolvedValueOnce(mockResponse); render( ); await waitFor(() => { expect(screen.queryByText('Loading...')).not.toBeInTheDocument(); }); const loginButton = screen.getByText('Login'); await act(async () => { fireEvent.click(loginButton); }); await waitFor(() => { expect(setToken).toHaveBeenCalledWith('new-token'); expect(toast.success).toHaveBeenCalledWith('Connexion réussie !'); expect(mockReplace).toHaveBeenCalledWith('/client'); }); }); it('should redirect ADMIN to admin dashboard', async () => { const mockResponse = { token: 'admin-token', user: { id: '1', email: 'admin@test.com', firstName: 'Admin', lastName: 'User', role: 'ADMIN', }, }; (authService.login as jest.Mock).mockResolvedValueOnce(mockResponse); render( ); await waitFor(() => { expect(screen.queryByText('Loading...')).not.toBeInTheDocument(); }); const loginButton = screen.getByText('Login'); await act(async () => { fireEvent.click(loginButton); }); await waitFor(() => { expect(mockReplace).toHaveBeenCalledWith('/admin/dashboard'); }); }); it('should redirect EMPLOYEE to employee dashboard', async () => { const mockResponse = { token: 'employee-token', user: { id: '1', email: 'employee@test.com', firstName: 'Employee', lastName: 'User', role: 'EMPLOYEE', }, }; (authService.login as jest.Mock).mockResolvedValueOnce(mockResponse); render( ); await waitFor(() => { expect(screen.queryByText('Loading...')).not.toBeInTheDocument(); }); const loginButton = screen.getByText('Login'); await act(async () => { fireEvent.click(loginButton); }); await waitFor(() => { expect(mockReplace).toHaveBeenCalledWith('/employe/dashboard'); }); }); it('should show error toast on login failure', async () => { const errorMessage = 'Invalid credentials'; (authService.login as jest.Mock).mockRejectedValueOnce( new Error(errorMessage) ); render( ); await waitFor(() => { expect(screen.queryByText('Loading...')).not.toBeInTheDocument(); }); const loginButton = screen.getByText('Login'); await act(async () => { fireEvent.click(loginButton); }); await waitFor(() => { expect(toast.error).toHaveBeenCalledWith(errorMessage); }); }); }); describe('register', () => { it('should register successfully', async () => { const mockResponse = { token: 'new-user-token', user: { id: '2', email: 'new@test.com', firstName: 'John', lastName: 'Doe', role: 'CLIENT', }, }; (authService.register as jest.Mock).mockResolvedValueOnce(mockResponse); render( ); await waitFor(() => { expect(screen.queryByText('Loading...')).not.toBeInTheDocument(); }); const registerButton = screen.getByText('Register'); await act(async () => { fireEvent.click(registerButton); }); await waitFor(() => { expect(setToken).toHaveBeenCalledWith('new-user-token'); expect(toast.success).toHaveBeenCalledWith('Inscription réussie !'); expect(mockPush).toHaveBeenCalledWith('/client'); }); }); }); describe('logout', () => { it('should logout and redirect to login page', async () => { const mockUser = { id: '1', email: 'test@test.com', firstName: 'John', lastName: 'Doe', role: 'CLIENT', }; (getToken as jest.Mock).mockReturnValue('valid-token'); (authService.getCurrentUser as jest.Mock).mockResolvedValueOnce(mockUser); (authService.logout as jest.Mock).mockResolvedValueOnce(undefined); render( ); await waitFor(() => { expect(screen.getByTestId('authenticated')).toHaveTextContent('yes'); }); const logoutButton = screen.getByText('Logout'); await act(async () => { fireEvent.click(logoutButton); }); await waitFor(() => { expect(removeToken).toHaveBeenCalled(); expect(storage.remove).toHaveBeenCalled(); expect(toast.success).toHaveBeenCalledWith('Déconnexion réussie'); expect(mockPush).toHaveBeenCalledWith('/login'); }); }); it('should still logout even if API call fails', async () => { (getToken as jest.Mock).mockReturnValue('valid-token'); (authService.getCurrentUser as jest.Mock).mockResolvedValueOnce({ id: '1', email: 'test@test.com', role: 'CLIENT', }); (authService.logout as jest.Mock).mockRejectedValueOnce( new Error('Logout failed') ); render( ); await waitFor(() => { expect(screen.getByTestId('authenticated')).toHaveTextContent('yes'); }); const logoutButton = screen.getByText('Logout'); await act(async () => { fireEvent.click(logoutButton); }); await waitFor(() => { expect(removeToken).toHaveBeenCalled(); expect(mockPush).toHaveBeenCalledWith('/login'); }); }); }); describe('useAuth hook error', () => { it('should throw error when used outside AuthProvider', () => { const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {}); expect(() => { render(); }).toThrow('useAuth must be used within an AuthProvider'); consoleError.mockRestore(); }); }); });