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>
This commit is contained in:
@@ -0,0 +1,262 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user