Add initial frontend component tests
- Create login-form.test.tsx with 8 test cases (positive and negative) - Create register-form.test.tsx with 10 test cases - Set up test directory structure at frontend/src/components/__tests__ - Mock auth-context and next/navigation for testing
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { LoginForm } from '../login-form';
|
||||
import { useAuth } from '@/lib/auth-context';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
// Mock the auth context
|
||||
jest.mock('@/lib/auth-context', () => ({
|
||||
useAuth: jest.fn(),
|
||||
}));
|
||||
|
||||
// Mock next/navigation
|
||||
jest.mock('next/navigation', () => ({
|
||||
useRouter: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('LoginForm', () => {
|
||||
const mockLogin = jest.fn();
|
||||
const mockPush = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
(useAuth as jest.Mock).mockReturnValue({
|
||||
login: mockLogin,
|
||||
error: null,
|
||||
});
|
||||
(useRouter as jest.Mock).mockReturnValue({
|
||||
push: mockPush,
|
||||
});
|
||||
});
|
||||
|
||||
// Positive Tests
|
||||
describe('Positive Tests', () => {
|
||||
it('renders login form with all fields', () => {
|
||||
render(<LoginForm />);
|
||||
|
||||
expect(screen.getByLabelText(/email/i)).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/password/i)).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /login/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('submits form with valid credentials', async () => {
|
||||
mockLogin.mockResolvedValueOnce(undefined);
|
||||
|
||||
render(<LoginForm />);
|
||||
|
||||
fireEvent.change(screen.getByLabelText(/email/i), {
|
||||
target: { value: 'user@example.com' },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText(/password/i), {
|
||||
target: { value: 'password123' },
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /login/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockLogin).toHaveBeenCalledWith('user@example.com', 'password123');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockPush).toHaveBeenCalledWith('/dashboard');
|
||||
});
|
||||
});
|
||||
|
||||
it('shows loading state while submitting', async () => {
|
||||
mockLogin.mockImplementation(() => new Promise(() => {})); // Never resolves
|
||||
|
||||
render(<LoginForm />);
|
||||
|
||||
fireEvent.change(screen.getByLabelText(/email/i), {
|
||||
target: { value: 'user@example.com' },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText(/password/i), {
|
||||
target: { value: 'password123' },
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /login/i }));
|
||||
|
||||
expect(screen.getByText(/logging in/i)).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /logging in/i })).toBeDisabled();
|
||||
});
|
||||
|
||||
it('displays error message from auth context', () => {
|
||||
(useAuth as jest.Mock).mockReturnValue({
|
||||
login: mockLogin,
|
||||
error: 'Invalid credentials',
|
||||
});
|
||||
|
||||
render(<LoginForm />);
|
||||
|
||||
expect(screen.getByText(/invalid credentials/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
// Negative Tests
|
||||
describe('Negative Tests', () => {
|
||||
it('prevents submission when email is empty', () => {
|
||||
render(<LoginForm />);
|
||||
|
||||
fireEvent.change(screen.getByLabelText(/email/i), {
|
||||
target: { value: '' },
|
||||
});
|
||||
|
||||
const form = screen.getByRole('form');
|
||||
expect(form).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('prevents submission when password is empty', () => {
|
||||
render(<LoginForm />);
|
||||
|
||||
fireEvent.change(screen.getByLabelText(/password/i), {
|
||||
target: { value: '' },
|
||||
});
|
||||
|
||||
const form = screen.getByRole('form');
|
||||
expect(form).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('enforces minimum password length of 8 characters', () => {
|
||||
render(<LoginForm />);
|
||||
|
||||
const passwordInput = screen.getByLabelText(/password/i);
|
||||
expect(passwordInput).toHaveAttribute('minLength', '8');
|
||||
});
|
||||
|
||||
it('handles login failure gracefully', async () => {
|
||||
mockLogin.mockRejectedValueOnce(new Error('Login failed'));
|
||||
|
||||
render(<LoginForm />);
|
||||
|
||||
fireEvent.change(screen.getByLabelText(/email/i), {
|
||||
target: { value: 'user@example.com' },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText(/password/i), {
|
||||
target: { value: 'password123' },
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /login/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockPush).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,214 @@
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
||||
import { RegisterForm } from '../register-form';
|
||||
import { useAuth } from '@/lib/auth-context';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
// Mock the auth context
|
||||
jest.mock('@/lib/auth-context', () => ({
|
||||
useAuth: jest.fn(),
|
||||
}));
|
||||
|
||||
// Mock next/navigation
|
||||
jest.mock('next/navigation', () => ({
|
||||
useRouter: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('RegisterForm', () => {
|
||||
const mockRegister = jest.fn();
|
||||
const mockPush = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
(useAuth as jest.Mock).mockReturnValue({
|
||||
register: mockRegister,
|
||||
error: null,
|
||||
});
|
||||
(useRouter as jest.Mock).mockReturnValue({
|
||||
push: mockPush,
|
||||
});
|
||||
});
|
||||
|
||||
// Positive Tests
|
||||
describe('Positive Tests', () => {
|
||||
it('renders registration form with all fields', () => {
|
||||
render(<RegisterForm />);
|
||||
|
||||
expect(screen.getByLabelText(/full name/i)).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/email/i)).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/^password$/i)).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/confirm password/i)).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(/account type/i)).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /register/i })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('submits form with valid data', async () => {
|
||||
mockRegister.mockResolvedValueOnce(undefined);
|
||||
|
||||
render(<RegisterForm />);
|
||||
|
||||
fireEvent.change(screen.getByLabelText(/full name/i), {
|
||||
target: { value: 'John Doe' },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText(/email/i), {
|
||||
target: { value: 'john@example.com' },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText(/^password$/i), {
|
||||
target: { value: 'password123' },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText(/confirm password/i), {
|
||||
target: { value: 'password123' },
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /register/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockRegister).toHaveBeenCalledWith('john@example.com', 'password123', 'John Doe', 'Participant');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockPush).toHaveBeenCalledWith('/dashboard');
|
||||
});
|
||||
});
|
||||
|
||||
it('allows selecting organizer role', async () => {
|
||||
mockRegister.mockResolvedValueOnce(undefined);
|
||||
|
||||
render(<RegisterForm />);
|
||||
|
||||
fireEvent.change(screen.getByLabelText(/full name/i), {
|
||||
target: { value: 'Jane Doe' },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText(/email/i), {
|
||||
target: { value: 'jane@example.com' },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText(/^password$/i), {
|
||||
target: { value: 'password123' },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText(/confirm password/i), {
|
||||
target: { value: 'password123' },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText(/account type/i), {
|
||||
target: { value: 'Organizer' },
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /register/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockRegister).toHaveBeenCalledWith('jane@example.com', 'password123', 'Jane Doe', 'Organizer');
|
||||
});
|
||||
});
|
||||
|
||||
it('shows loading state while submitting', async () => {
|
||||
mockRegister.mockImplementation(() => new Promise(() => {}));
|
||||
|
||||
render(<RegisterForm />);
|
||||
|
||||
fireEvent.change(screen.getByLabelText(/full name/i), {
|
||||
target: { value: 'Test User' },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText(/email/i), {
|
||||
target: { value: 'test@example.com' },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText(/^password$/i), {
|
||||
target: { value: 'password123' },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText(/confirm password/i), {
|
||||
target: { value: 'password123' },
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /register/i }));
|
||||
|
||||
expect(screen.getByText(/registering/i)).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: /registering/i })).toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
// Negative Tests
|
||||
describe('Negative Tests', () => {
|
||||
it('shows error when passwords do not match', async () => {
|
||||
render(<RegisterForm />);
|
||||
|
||||
fireEvent.change(screen.getByLabelText(/full name/i), {
|
||||
target: { value: 'Test User' },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText(/email/i), {
|
||||
target: { value: 'test@example.com' },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText(/^password$/i), {
|
||||
target: { value: 'password123' },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText(/confirm password/i), {
|
||||
target: { value: 'differentpassword' },
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /register/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/passwords do not match/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(mockRegister).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('shows error when password is too short', async () => {
|
||||
render(<RegisterForm />);
|
||||
|
||||
fireEvent.change(screen.getByLabelText(/full name/i), {
|
||||
target: { value: 'Test User' },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText(/email/i), {
|
||||
target: { value: 'test@example.com' },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText(/^password$/i), {
|
||||
target: { value: 'short' },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText(/confirm password/i), {
|
||||
target: { value: 'short' },
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /register/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText(/password must be at least 8 characters/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(mockRegister).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('displays error message from auth context', () => {
|
||||
(useAuth as jest.Mock).mockReturnValue({
|
||||
register: mockRegister,
|
||||
error: 'Email already exists',
|
||||
});
|
||||
|
||||
render(<RegisterForm />);
|
||||
|
||||
expect(screen.getByText(/email already exists/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('handles registration failure gracefully', async () => {
|
||||
mockRegister.mockRejectedValueOnce(new Error('Registration failed'));
|
||||
|
||||
render(<RegisterForm />);
|
||||
|
||||
fireEvent.change(screen.getByLabelText(/full name/i), {
|
||||
target: { value: 'Test User' },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText(/email/i), {
|
||||
target: { value: 'test@example.com' },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText(/^password$/i), {
|
||||
target: { value: 'password123' },
|
||||
});
|
||||
fireEvent.change(screen.getByLabelText(/confirm password/i), {
|
||||
target: { value: 'password123' },
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /register/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockPush).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user