2df5a303ed
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>
263 lines
9.3 KiB
JavaScript
263 lines
9.3 KiB
JavaScript
import React from 'react';
|
|
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
import { BrowserRouter } from 'react-router-dom';
|
|
import Register from '../Register';
|
|
import { AuthContext } from '../../context/AuthContext';
|
|
|
|
const mockedNavigate = jest.fn();
|
|
jest.mock('react-router-dom', () => ({
|
|
...jest.requireActual('react-router-dom'),
|
|
Navigate: ({ to }) => {
|
|
mockedNavigate(to);
|
|
return null;
|
|
},
|
|
}));
|
|
|
|
describe('Register Component', () => {
|
|
const mockRegister = jest.fn();
|
|
|
|
const mockAuthContext = {
|
|
auth: {
|
|
isAuthenticated: false,
|
|
loading: false,
|
|
user: null,
|
|
},
|
|
register: mockRegister,
|
|
};
|
|
|
|
const renderRegister = (contextValue = mockAuthContext) => {
|
|
return render(
|
|
<BrowserRouter>
|
|
<AuthContext.Provider value={contextValue}>
|
|
<Register />
|
|
</AuthContext.Provider>
|
|
</BrowserRouter>
|
|
);
|
|
};
|
|
|
|
beforeEach(() => {
|
|
mockRegister.mockClear();
|
|
mockedNavigate.mockClear();
|
|
});
|
|
|
|
describe('Rendering', () => {
|
|
it('should render registration form', () => {
|
|
renderRegister();
|
|
|
|
expect(screen.getByRole('heading', { name: /register/i })).toBeInTheDocument();
|
|
expect(screen.getByPlaceholderText(/name/i)).toBeInTheDocument();
|
|
expect(screen.getByPlaceholderText(/email/i)).toBeInTheDocument();
|
|
expect(screen.getByPlaceholderText(/^password$/i)).toBeInTheDocument();
|
|
expect(screen.getByPlaceholderText(/confirm password/i)).toBeInTheDocument();
|
|
expect(screen.getByRole('button', { name: /register/i })).toBeInTheDocument();
|
|
});
|
|
|
|
it('should render all required input fields', () => {
|
|
renderRegister();
|
|
|
|
const nameInput = screen.getByPlaceholderText(/name/i);
|
|
const emailInput = screen.getByPlaceholderText(/email/i);
|
|
const passwordInput = screen.getByPlaceholderText(/^password$/i);
|
|
const confirmPasswordInput = screen.getByPlaceholderText(/confirm password/i);
|
|
|
|
expect(nameInput).toBeRequired();
|
|
expect(emailInput).toBeRequired();
|
|
expect(passwordInput).toBeRequired();
|
|
expect(confirmPasswordInput).toBeRequired();
|
|
});
|
|
});
|
|
|
|
describe('Form Input Changes', () => {
|
|
it('should update name field on change', () => {
|
|
renderRegister();
|
|
|
|
const nameInput = screen.getByPlaceholderText(/name/i);
|
|
fireEvent.change(nameInput, { target: { value: 'John Doe' } });
|
|
|
|
expect(nameInput).toHaveValue('John Doe');
|
|
});
|
|
|
|
it('should update email field on change', () => {
|
|
renderRegister();
|
|
|
|
const emailInput = screen.getByPlaceholderText(/email/i);
|
|
fireEvent.change(emailInput, { target: { value: 'john@example.com' } });
|
|
|
|
expect(emailInput).toHaveValue('john@example.com');
|
|
});
|
|
|
|
it('should update password field on change', () => {
|
|
renderRegister();
|
|
|
|
const passwordInput = screen.getByPlaceholderText(/^password$/i);
|
|
fireEvent.change(passwordInput, { target: { value: 'password123' } });
|
|
|
|
expect(passwordInput).toHaveValue('password123');
|
|
});
|
|
|
|
it('should update confirm password field on change', () => {
|
|
renderRegister();
|
|
|
|
const confirmPasswordInput = screen.getByPlaceholderText(/confirm password/i);
|
|
fireEvent.change(confirmPasswordInput, { target: { value: 'password123' } });
|
|
|
|
expect(confirmPasswordInput).toHaveValue('password123');
|
|
});
|
|
});
|
|
|
|
describe('Form Submission', () => {
|
|
it('should call register function with valid data', async () => {
|
|
mockRegister.mockResolvedValue({ success: true });
|
|
renderRegister();
|
|
|
|
const nameInput = screen.getByPlaceholderText(/name/i);
|
|
const emailInput = screen.getByPlaceholderText(/email/i);
|
|
const passwordInput = screen.getByPlaceholderText(/^password$/i);
|
|
const confirmPasswordInput = screen.getByPlaceholderText(/confirm password/i);
|
|
const submitButton = screen.getByRole('button', { name: /register/i });
|
|
|
|
fireEvent.change(nameInput, { target: { value: 'John Doe' } });
|
|
fireEvent.change(emailInput, { target: { value: 'john@example.com' } });
|
|
fireEvent.change(passwordInput, { target: { value: 'password123' } });
|
|
fireEvent.change(confirmPasswordInput, { target: { value: 'password123' } });
|
|
fireEvent.click(submitButton);
|
|
|
|
await waitFor(() => {
|
|
expect(mockRegister).toHaveBeenCalledWith('John Doe', 'john@example.com', 'password123');
|
|
});
|
|
});
|
|
|
|
it('should disable form during submission', async () => {
|
|
mockRegister.mockImplementation(() => new Promise(resolve => setTimeout(resolve, 100)));
|
|
renderRegister();
|
|
|
|
const nameInput = screen.getByPlaceholderText(/name/i);
|
|
const emailInput = screen.getByPlaceholderText(/email/i);
|
|
const passwordInput = screen.getByPlaceholderText(/^password$/i);
|
|
const confirmPasswordInput = screen.getByPlaceholderText(/confirm password/i);
|
|
const submitButton = screen.getByRole('button', { name: /register/i });
|
|
|
|
fireEvent.change(nameInput, { target: { value: 'John Doe' } });
|
|
fireEvent.change(emailInput, { target: { value: 'john@example.com' } });
|
|
fireEvent.change(passwordInput, { target: { value: 'password123' } });
|
|
fireEvent.change(confirmPasswordInput, { target: { value: 'password123' } });
|
|
fireEvent.click(submitButton);
|
|
|
|
await waitFor(() => {
|
|
expect(nameInput).toBeDisabled();
|
|
expect(emailInput).toBeDisabled();
|
|
expect(passwordInput).toBeDisabled();
|
|
expect(confirmPasswordInput).toBeDisabled();
|
|
expect(submitButton).toBeDisabled();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('Password Validation', () => {
|
|
it('should validate minimum password length', async () => {
|
|
renderRegister();
|
|
|
|
const passwordInput = screen.getByPlaceholderText(/^password$/i);
|
|
const confirmPasswordInput = screen.getByPlaceholderText(/confirm password/i);
|
|
|
|
fireEvent.change(passwordInput, { target: { value: '12345' } });
|
|
fireEvent.change(confirmPasswordInput, { target: { value: '12345' } });
|
|
|
|
// Password should have minLength attribute
|
|
expect(passwordInput).toHaveAttribute('minLength');
|
|
});
|
|
|
|
it('should show error when passwords do not match', async () => {
|
|
renderRegister();
|
|
|
|
const nameInput = screen.getByPlaceholderText(/name/i);
|
|
const emailInput = screen.getByPlaceholderText(/email/i);
|
|
const passwordInput = screen.getByPlaceholderText(/^password$/i);
|
|
const confirmPasswordInput = screen.getByPlaceholderText(/confirm password/i);
|
|
const submitButton = screen.getByRole('button', { name: /register/i });
|
|
|
|
fireEvent.change(nameInput, { target: { value: 'John Doe' } });
|
|
fireEvent.change(emailInput, { target: { value: 'john@example.com' } });
|
|
fireEvent.change(passwordInput, { target: { value: 'password123' } });
|
|
fireEvent.change(confirmPasswordInput, { target: { value: 'different' } });
|
|
fireEvent.click(submitButton);
|
|
|
|
// Should not call register if passwords don't match
|
|
expect(mockRegister).not.toHaveBeenCalled();
|
|
});
|
|
});
|
|
|
|
describe('Authentication State', () => {
|
|
it('should redirect to /map when already authenticated', () => {
|
|
const authenticatedContext = {
|
|
auth: {
|
|
isAuthenticated: true,
|
|
loading: false,
|
|
user: { name: 'Test User' },
|
|
},
|
|
register: mockRegister,
|
|
};
|
|
|
|
renderRegister(authenticatedContext);
|
|
|
|
expect(mockedNavigate).toHaveBeenCalledWith('/map');
|
|
});
|
|
|
|
it('should show loading spinner when auth is loading', () => {
|
|
const loadingContext = {
|
|
auth: {
|
|
isAuthenticated: false,
|
|
loading: true,
|
|
user: null,
|
|
},
|
|
register: mockRegister,
|
|
};
|
|
|
|
renderRegister(loadingContext);
|
|
|
|
expect(screen.getByRole('status')).toBeInTheDocument();
|
|
});
|
|
});
|
|
|
|
describe('Field Types', () => {
|
|
it('should have correct input types', () => {
|
|
renderRegister();
|
|
|
|
const emailInput = screen.getByPlaceholderText(/email/i);
|
|
const passwordInput = screen.getByPlaceholderText(/^password$/i);
|
|
const confirmPasswordInput = screen.getByPlaceholderText(/confirm password/i);
|
|
|
|
expect(emailInput).toHaveAttribute('type', 'email');
|
|
expect(passwordInput).toHaveAttribute('type', 'password');
|
|
expect(confirmPasswordInput).toHaveAttribute('type', 'password');
|
|
});
|
|
});
|
|
|
|
describe('Error Handling', () => {
|
|
it('should handle registration errors', async () => {
|
|
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
mockRegister.mockRejectedValue(new Error('Registration failed'));
|
|
|
|
renderRegister();
|
|
|
|
const nameInput = screen.getByPlaceholderText(/name/i);
|
|
const emailInput = screen.getByPlaceholderText(/email/i);
|
|
const passwordInput = screen.getByPlaceholderText(/^password$/i);
|
|
const confirmPasswordInput = screen.getByPlaceholderText(/confirm password/i);
|
|
const submitButton = screen.getByRole('button', { name: /register/i });
|
|
|
|
fireEvent.change(nameInput, { target: { value: 'John Doe' } });
|
|
fireEvent.change(emailInput, { target: { value: 'john@example.com' } });
|
|
fireEvent.change(passwordInput, { target: { value: 'password123' } });
|
|
fireEvent.change(confirmPasswordInput, { target: { value: 'password123' } });
|
|
fireEvent.click(submitButton);
|
|
|
|
await waitFor(() => {
|
|
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
});
|
|
|
|
consoleErrorSpy.mockRestore();
|
|
});
|
|
});
|
|
});
|