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