import React from 'react'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { BrowserRouter } from 'react-router-dom'; import Login from '../Login'; import { AuthContext } from '../../context/AuthContext'; // Mock useNavigate const mockedNavigate = jest.fn(); jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), Navigate: ({ to }) => { mockedNavigate(to); return null; }, })); describe('Login Component', () => { const mockLogin = jest.fn(); const mockAuthContext = { auth: { isAuthenticated: false, loading: false, user: null, }, login: mockLogin, }; const renderLogin = (contextValue = mockAuthContext) => { return render( ); }; beforeEach(() => { mockLogin.mockClear(); mockedNavigate.mockClear(); }); describe('Rendering', () => { it('should render login form', () => { renderLogin(); expect(screen.getByRole('heading', { name: /login/i })).toBeInTheDocument(); expect(screen.getByPlaceholderText(/email/i)).toBeInTheDocument(); expect(screen.getByPlaceholderText(/password/i)).toBeInTheDocument(); expect(screen.getByRole('button', { name: /login/i })).toBeInTheDocument(); }); it('should render email input field', () => { renderLogin(); const emailInput = screen.getByPlaceholderText(/email/i); expect(emailInput).toBeInTheDocument(); expect(emailInput).toHaveAttribute('type', 'email'); expect(emailInput).toHaveAttribute('required'); }); it('should render password input field', () => { renderLogin(); const passwordInput = screen.getByPlaceholderText(/password/i); expect(passwordInput).toBeInTheDocument(); expect(passwordInput).toHaveAttribute('type', 'password'); expect(passwordInput).toHaveAttribute('required'); }); }); describe('Form Validation', () => { it('should update email field on change', () => { renderLogin(); const emailInput = screen.getByPlaceholderText(/email/i); fireEvent.change(emailInput, { target: { value: 'test@example.com' } }); expect(emailInput).toHaveValue('test@example.com'); }); it('should update password field on change', () => { renderLogin(); const passwordInput = screen.getByPlaceholderText(/password/i); fireEvent.change(passwordInput, { target: { value: 'password123' } }); expect(passwordInput).toHaveValue('password123'); }); it('should have required fields', () => { renderLogin(); const emailInput = screen.getByPlaceholderText(/email/i); const passwordInput = screen.getByPlaceholderText(/password/i); expect(emailInput).toBeRequired(); expect(passwordInput).toBeRequired(); }); }); describe('Form Submission', () => { it('should call login function on form submit', async () => { renderLogin(); const emailInput = screen.getByPlaceholderText(/email/i); const passwordInput = screen.getByPlaceholderText(/password/i); const submitButton = screen.getByRole('button', { name: /login/i }); fireEvent.change(emailInput, { target: { value: 'test@example.com' } }); fireEvent.change(passwordInput, { target: { value: 'password123' } }); fireEvent.click(submitButton); await waitFor(() => { expect(mockLogin).toHaveBeenCalledWith('test@example.com', 'password123'); }); }); it('should disable form fields during submission', async () => { mockLogin.mockImplementation(() => new Promise(resolve => setTimeout(resolve, 100))); renderLogin(); const emailInput = screen.getByPlaceholderText(/email/i); const passwordInput = screen.getByPlaceholderText(/password/i); const submitButton = screen.getByRole('button', { name: /login/i }); fireEvent.change(emailInput, { target: { value: 'test@example.com' } }); fireEvent.change(passwordInput, { target: { value: 'password123' } }); fireEvent.click(submitButton); await waitFor(() => { expect(emailInput).toBeDisabled(); expect(passwordInput).toBeDisabled(); expect(submitButton).toBeDisabled(); }); }); it('should show loading state during submission', async () => { mockLogin.mockImplementation(() => new Promise(resolve => setTimeout(resolve, 100))); renderLogin(); const emailInput = screen.getByPlaceholderText(/email/i); const passwordInput = screen.getByPlaceholderText(/password/i); const submitButton = screen.getByRole('button', { name: /login/i }); fireEvent.change(emailInput, { target: { value: 'test@example.com' } }); fireEvent.change(passwordInput, { target: { value: 'password123' } }); fireEvent.click(submitButton); await waitFor(() => { expect(screen.getByText(/logging in/i)).toBeInTheDocument(); }); }); it('should handle login errors gracefully', async () => { const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(); mockLogin.mockRejectedValue(new Error('Login failed')); renderLogin(); const emailInput = screen.getByPlaceholderText(/email/i); const passwordInput = screen.getByPlaceholderText(/password/i); const submitButton = screen.getByRole('button', { name: /login/i }); fireEvent.change(emailInput, { target: { value: 'test@example.com' } }); fireEvent.change(passwordInput, { target: { value: 'wrong' } }); fireEvent.click(submitButton); await waitFor(() => { expect(consoleErrorSpy).toHaveBeenCalled(); }); consoleErrorSpy.mockRestore(); }); }); describe('Authentication State', () => { it('should redirect to /map when already authenticated', () => { const authenticatedContext = { auth: { isAuthenticated: true, loading: false, user: { name: 'Test User' }, }, login: mockLogin, }; renderLogin(authenticatedContext); expect(mockedNavigate).toHaveBeenCalledWith('/map'); }); it('should show loading spinner when auth is loading', () => { const loadingContext = { auth: { isAuthenticated: false, loading: true, user: null, }, login: mockLogin, }; renderLogin(loadingContext); expect(screen.getByRole('status')).toBeInTheDocument(); expect(screen.getByText(/loading/i)).toBeInTheDocument(); }); it('should not show form when auth is loading', () => { const loadingContext = { auth: { isAuthenticated: false, loading: true, user: null, }, login: mockLogin, }; renderLogin(loadingContext); expect(screen.queryByPlaceholderText(/email/i)).not.toBeInTheDocument(); expect(screen.queryByPlaceholderText(/password/i)).not.toBeInTheDocument(); }); }); describe('Accessibility', () => { it('should have accessible form elements', () => { renderLogin(); const emailInput = screen.getByPlaceholderText(/email/i); const passwordInput = screen.getByPlaceholderText(/password/i); expect(emailInput).toHaveAttribute('name', 'email'); expect(passwordInput).toHaveAttribute('name', 'password'); }); it('should have accessible button', () => { renderLogin(); const submitButton = screen.getByRole('button', { name: /login/i }); expect(submitButton).toHaveAttribute('type', 'submit'); }); }); describe('Empty Form Submission', () => { it('should not submit with empty fields', () => { renderLogin(); const submitButton = screen.getByRole('button', { name: /login/i }); fireEvent.click(submitButton); expect(mockLogin).not.toHaveBeenCalled(); }); }); });