Files
adopt-a-street/frontend/src/components/__tests__/Login.test.js
T
William Valentin 2df5a303ed refactor: convert frontend from submodule to true monorepo
Convert frontend from Git submodule to a regular monorepo directory for simplified development workflow.

Changes:
- Remove frontend submodule tracking (mode 160000 gitlink)
- Add all frontend source files directly to main repository
- Remove frontend/.git directory
- Update CLAUDE.md to clarify true monorepo structure
- Update Frontend Architecture documentation (React Router v6, Socket.IO, Leaflet, ErrorBoundary)

Benefits of Monorepo:
- Single git clone for entire project
- Unified commit history
- Simpler CI/CD pipeline
- Easier for new developers
- No submodule sync issues
- Atomic commits across frontend and backend

Frontend Files Added:
- All React components (MapView, ErrorBoundary, TaskList, SocialFeed, etc.)
- Context providers (AuthContext, SocketContext)
- Complete test suite with MSW
- Dependencies and configuration files

Branch Cleanup:
- Using 'main' as default branch (develop deleted)
- Frontend no longer has separate Git history

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-01 11:01:06 -07:00

259 lines
7.9 KiB
JavaScript

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(
<BrowserRouter>
<AuthContext.Provider value={contextValue}>
<Login />
</AuthContext.Provider>
</BrowserRouter>
);
};
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();
});
});
});