/** * AuthModal Component Tests * * Tests for the AuthModal component including login, signup, * form validation, and modal behavior. */ import React from 'react'; import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import AuthModal from '../../components/AuthModal'; // Mock the auth context const mockLogin = jest.fn(); const mockRegister = jest.fn(); jest.mock('../../contexts/AuthContext', () => ({ ...jest.requireActual('../../contexts/AuthContext'), useAuth: () => ({ login: mockLogin, register: mockRegister, user: null, loading: false, }), })); // Mock child components jest.mock('../../components/PasswordStrengthMeter', () => { return function MockPasswordStrengthMeter({ password }: { password: string }) { return
Strength: {password.length > 8 ? 'Strong' : 'Weak'}
; }; }); jest.mock('../../components/PasswordInput', () => { return function MockPasswordInput({ id, label, value, onChange, required }: { id: string; label: string; value: string; onChange: (e: React.ChangeEvent) => void; required?: boolean; }) { return (
); }; }); jest.mock('../../components/ForgotPasswordModal', () => { return function MockForgotPasswordModal({ show, onHide, onBackToLogin }: { show: boolean; onHide: () => void; onBackToLogin: () => void; }) { if (!show) return null; return (
); }; }); jest.mock('../../components/VerificationCodeModal', () => { return function MockVerificationCodeModal({ show, onHide, email, onVerified }: { show: boolean; onHide: () => void; email: string; onVerified: () => void; }) { if (!show) return null; return (

Verify email: {email}

); }; }); describe('AuthModal', () => { const defaultProps = { show: true, onHide: jest.fn(), }; beforeEach(() => { jest.clearAllMocks(); }); // Helper to get email input (it's a textbox with type email) const getEmailInput = () => screen.getByRole('textbox', { hidden: false }); // Helper to get inputs by their preceding label text const getInputByLabelText = (container: HTMLElement, labelText: string) => { const label = Array.from(container.querySelectorAll('label')).find( l => l.textContent === labelText ); if (!label) throw new Error(`Label "${labelText}" not found`); // Get the next sibling input or the input inside the same parent const parent = label.parentElement; return parent?.querySelector('input') as HTMLInputElement; }; describe('Rendering', () => { it('should render login form by default', () => { const { container } = render(); expect(screen.getByText('Welcome to CommunityRentals.App')).toBeInTheDocument(); expect(getInputByLabelText(container, 'Email')).toBeInTheDocument(); expect(screen.getByTestId('password-input')).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Log in' })).toBeInTheDocument(); }); it('should render signup form when initialMode is signup', () => { const { container } = render(); expect(getInputByLabelText(container, 'First Name')).toBeInTheDocument(); expect(getInputByLabelText(container, 'Last Name')).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Sign up' })).toBeInTheDocument(); expect(screen.getByTestId('password-strength-meter')).toBeInTheDocument(); }); it('should not render when show is false', () => { render(); expect(screen.queryByText('Welcome to CommunityRentals.App')).not.toBeInTheDocument(); }); it('should render Google login button', () => { render(); expect(screen.getByRole('button', { name: /continue with google/i })).toBeInTheDocument(); }); it('should render forgot password link in login mode', () => { render(); expect(screen.getByText('Forgot password?')).toBeInTheDocument(); }); it('should not render forgot password link in signup mode', () => { render(); expect(screen.queryByText('Forgot password?')).not.toBeInTheDocument(); }); }); describe('Mode Switching', () => { it('should switch from login to signup mode', async () => { const { container } = render(); // Initially in login mode expect(screen.getByRole('button', { name: 'Log in' })).toBeInTheDocument(); // Click "Sign up" link fireEvent.click(screen.getByText('Sign up')); // Should now be in signup mode expect(screen.getByRole('button', { name: 'Sign up' })).toBeInTheDocument(); expect(getInputByLabelText(container, 'First Name')).toBeInTheDocument(); }); it('should switch from signup to login mode', async () => { render(); // Initially in signup mode expect(screen.getByRole('button', { name: 'Sign up' })).toBeInTheDocument(); // Click "Log in" link fireEvent.click(screen.getByText('Log in')); // Should now be in login mode expect(screen.getByRole('button', { name: 'Log in' })).toBeInTheDocument(); expect(screen.queryByText('First Name')).not.toBeInTheDocument(); }); }); describe('Login Form Submission', () => { it('should call login with email and password', async () => { mockLogin.mockResolvedValue({}); const { container } = render(); // Fill in the form const emailInput = getInputByLabelText(container, 'Email'); await userEvent.type(emailInput, 'test@example.com'); await userEvent.type(screen.getByTestId('password-input'), 'password123'); // Submit the form fireEvent.click(screen.getByRole('button', { name: 'Log in' })); await waitFor(() => { expect(mockLogin).toHaveBeenCalledWith('test@example.com', 'password123'); }); }); it('should call onHide after successful login', async () => { mockLogin.mockResolvedValue({}); const { container } = render(); const emailInput = getInputByLabelText(container, 'Email'); await userEvent.type(emailInput, 'test@example.com'); await userEvent.type(screen.getByTestId('password-input'), 'password123'); fireEvent.click(screen.getByRole('button', { name: 'Log in' })); await waitFor(() => { expect(defaultProps.onHide).toHaveBeenCalled(); }); }); it('should display error message on login failure', async () => { mockLogin.mockRejectedValue({ response: { data: { error: 'Invalid credentials' } }, }); const { container } = render(); const emailInput = getInputByLabelText(container, 'Email'); await userEvent.type(emailInput, 'test@example.com'); await userEvent.type(screen.getByTestId('password-input'), 'wrongpassword'); fireEvent.click(screen.getByRole('button', { name: 'Log in' })); await waitFor(() => { expect(screen.getByText('Invalid credentials')).toBeInTheDocument(); }); }); it('should show loading state during login', async () => { // Make login take some time mockLogin.mockImplementation(() => new Promise(resolve => setTimeout(resolve, 100))); const { container } = render(); const emailInput = getInputByLabelText(container, 'Email'); await userEvent.type(emailInput, 'test@example.com'); await userEvent.type(screen.getByTestId('password-input'), 'password123'); fireEvent.click(screen.getByRole('button', { name: 'Log in' })); expect(screen.getByRole('button', { name: 'Loading...' })).toBeInTheDocument(); }); }); describe('Signup Form Submission', () => { it('should call register with user data', async () => { mockRegister.mockResolvedValue({}); const { container } = render(); await userEvent.type(getInputByLabelText(container, 'First Name'), 'John'); await userEvent.type(getInputByLabelText(container, 'Last Name'), 'Doe'); await userEvent.type(getInputByLabelText(container, 'Email'), 'john@example.com'); await userEvent.type(screen.getByTestId('password-input'), 'StrongPass123!'); fireEvent.click(screen.getByRole('button', { name: 'Sign up' })); await waitFor(() => { expect(mockRegister).toHaveBeenCalledWith({ email: 'john@example.com', password: 'StrongPass123!', firstName: 'John', lastName: 'Doe', username: 'john', // Generated from email }); }); }); it('should show verification modal after successful signup', async () => { mockRegister.mockResolvedValue({}); const { container } = render(); await userEvent.type(getInputByLabelText(container, 'First Name'), 'John'); await userEvent.type(getInputByLabelText(container, 'Last Name'), 'Doe'); await userEvent.type(getInputByLabelText(container, 'Email'), 'john@example.com'); await userEvent.type(screen.getByTestId('password-input'), 'StrongPass123!'); fireEvent.click(screen.getByRole('button', { name: 'Sign up' })); await waitFor(() => { expect(screen.getByTestId('verification-modal')).toBeInTheDocument(); expect(screen.getByText('Verify email: john@example.com')).toBeInTheDocument(); }); }); it('should display error message on signup failure', async () => { mockRegister.mockRejectedValue({ response: { data: { error: 'Email already exists' } }, }); const { container } = render(); await userEvent.type(getInputByLabelText(container, 'First Name'), 'John'); await userEvent.type(getInputByLabelText(container, 'Last Name'), 'Doe'); await userEvent.type(getInputByLabelText(container, 'Email'), 'existing@example.com'); await userEvent.type(screen.getByTestId('password-input'), 'StrongPass123!'); fireEvent.click(screen.getByRole('button', { name: 'Sign up' })); await waitFor(() => { expect(screen.getByText('Email already exists')).toBeInTheDocument(); }); }); }); describe('Forgot Password', () => { it('should show forgot password modal when link is clicked', async () => { render(); fireEvent.click(screen.getByText('Forgot password?')); expect(screen.getByTestId('forgot-password-modal')).toBeInTheDocument(); }); it('should hide forgot password modal and show login when back is clicked', async () => { render(); // Open forgot password modal fireEvent.click(screen.getByText('Forgot password?')); expect(screen.getByTestId('forgot-password-modal')).toBeInTheDocument(); // Click back to login fireEvent.click(screen.getByTestId('back-to-login')); // Should show login form again await waitFor(() => { expect(screen.queryByTestId('forgot-password-modal')).not.toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Log in' })).toBeInTheDocument(); }); }); }); describe('Modal Close', () => { it('should call onHide when close button is clicked', async () => { render(); // Click close button (btn-close class) const closeButton = document.querySelector('.btn-close') as HTMLButtonElement; fireEvent.click(closeButton); expect(defaultProps.onHide).toHaveBeenCalled(); }); }); describe('Google OAuth', () => { it('should redirect to Google OAuth when Google button is clicked', () => { // Mock window.location const originalLocation = window.location; delete (window as any).location; window.location = { ...originalLocation, href: '' } as Location; render(); fireEvent.click(screen.getByRole('button', { name: /continue with google/i })); // Check that window.location.href was set to Google OAuth URL expect(window.location.href).toContain('accounts.google.com'); // Restore window.location = originalLocation; }); }); describe('Accessibility', () => { it('should have password label associated with input', () => { render(); // Password input has proper htmlFor through the mock expect(screen.getByLabelText('Password')).toBeInTheDocument(); }); it('should display error in an alert role', async () => { mockLogin.mockRejectedValue({ response: { data: { error: 'Test error' } }, }); const { container } = render(); const emailInput = getInputByLabelText(container, 'Email'); await userEvent.type(emailInput, 'test@example.com'); await userEvent.type(screen.getByTestId('password-input'), 'password'); fireEvent.click(screen.getByRole('button', { name: 'Log in' })); await waitFor(() => { expect(screen.getByRole('alert')).toBeInTheDocument(); }); }); }); describe('Terms and Privacy Links', () => { it('should display terms and privacy links', () => { render(); expect(screen.getByText('Terms of Service')).toHaveAttribute('href', '/terms'); expect(screen.getByText('Privacy Policy')).toHaveAttribute('href', '/privacy'); }); }); });