|
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