|
1 | 1 | import { render, screen, fireEvent } from '@testing-library/react';
|
2 | 2 | import { describe, it, expect, vi } from 'vitest';
|
3 | 3 | import { AddTodo } from '../add-todo';
|
| 4 | +import { createMemoryRouter, RouterProvider } from 'react-router-dom'; |
| 5 | + |
| 6 | +// Hoist regex to top-level to satisfy performance rule |
| 7 | +const addRegex = /add/i; |
| 8 | + |
| 9 | +function renderWithRouter(ui: React.ReactElement) { |
| 10 | + const router = createMemoryRouter([ |
| 11 | + { path: '/', element: ui } |
| 12 | + ], { initialEntries: ['/'] }); |
| 13 | + return render(<RouterProvider router={router} />); |
| 14 | +} |
4 | 15 |
|
5 | 16 | describe('AddTodo', () => {
|
6 | 17 | it('renders input and button', () => {
|
7 | 18 | const mockOnAdd = vi.fn();
|
8 |
| - render(<AddTodo onAdd={mockOnAdd} />); |
9 |
| - |
| 19 | + renderWithRouter(<AddTodo onAdd={mockOnAdd} />); |
| 20 | + |
10 | 21 | expect(screen.getByPlaceholderText('Add a new todo...')).toBeInTheDocument();
|
11 |
| - expect(screen.getByRole('button', { name: /add/i })).toBeInTheDocument(); |
| 22 | + expect(screen.getByRole('button', { name: addRegex })).toBeInTheDocument(); |
12 | 23 | });
|
13 | 24 |
|
14 | 25 | it('calls onAdd when form is submitted with text', () => {
|
15 | 26 | const mockOnAdd = vi.fn();
|
16 |
| - render(<AddTodo onAdd={mockOnAdd} />); |
17 |
| - |
| 27 | + renderWithRouter(<AddTodo onAdd={mockOnAdd} />); |
| 28 | + |
18 | 29 | const input = screen.getByPlaceholderText('Add a new todo...');
|
19 |
| - const button = screen.getByRole('button', { name: /add/i }); |
20 |
| - |
| 30 | + const button = screen.getByRole('button', { name: addRegex }); |
| 31 | + |
21 | 32 | fireEvent.change(input, { target: { value: 'New todo' } });
|
22 | 33 | fireEvent.click(button);
|
23 |
| - |
| 34 | + |
24 | 35 | expect(mockOnAdd).toHaveBeenCalledWith('New todo');
|
25 | 36 | });
|
26 | 37 |
|
27 | 38 | it('clears input after adding todo', () => {
|
28 | 39 | const mockOnAdd = vi.fn();
|
29 |
| - render(<AddTodo onAdd={mockOnAdd} />); |
30 |
| - |
| 40 | + renderWithRouter(<AddTodo onAdd={mockOnAdd} />); |
| 41 | + |
31 | 42 | const input = screen.getByPlaceholderText('Add a new todo...') as HTMLInputElement;
|
32 |
| - const button = screen.getByRole('button', { name: /add/i }); |
33 |
| - |
| 43 | + const button = screen.getByRole('button', { name: addRegex }); |
| 44 | + |
34 | 45 | fireEvent.change(input, { target: { value: 'New todo' } });
|
35 | 46 | fireEvent.click(button);
|
36 |
| - |
| 47 | + |
37 | 48 | expect(input.value).toBe('');
|
38 | 49 | });
|
39 | 50 |
|
40 | 51 | it('does not call onAdd with empty text', () => {
|
41 | 52 | const mockOnAdd = vi.fn();
|
42 |
| - render(<AddTodo onAdd={mockOnAdd} />); |
43 |
| - |
44 |
| - const button = screen.getByRole('button', { name: /add/i }); |
| 53 | + renderWithRouter(<AddTodo onAdd={mockOnAdd} />); |
| 54 | + |
| 55 | + const button = screen.getByRole('button', { name: addRegex }); |
45 | 56 | fireEvent.click(button);
|
46 |
| - |
| 57 | + |
47 | 58 | expect(mockOnAdd).not.toHaveBeenCalled();
|
48 | 59 | });
|
49 | 60 |
|
50 | 61 | it('trims whitespace from input', () => {
|
51 | 62 | const mockOnAdd = vi.fn();
|
52 |
| - render(<AddTodo onAdd={mockOnAdd} />); |
53 |
| - |
| 63 | + renderWithRouter(<AddTodo onAdd={mockOnAdd} />); |
| 64 | + |
54 | 65 | const input = screen.getByPlaceholderText('Add a new todo...');
|
55 |
| - const button = screen.getByRole('button', { name: /add/i }); |
56 |
| - |
| 66 | + const button = screen.getByRole('button', { name: addRegex }); |
| 67 | + |
57 | 68 | fireEvent.change(input, { target: { value: ' New todo ' } });
|
58 | 69 | fireEvent.click(button);
|
59 |
| - |
| 70 | + |
60 | 71 | expect(mockOnAdd).toHaveBeenCalledWith('New todo');
|
61 | 72 | });
|
62 | 73 | });
|
63 |
| - |
|
0 commit comments