Skip to content

Commit eb25867

Browse files
committed
fix(tests): enhance AddTodo component tests with stateful mocks and reset logic
This update introduces stateful mocks for input fields and enhances the test setup for the AddTodo component. It includes a reset mechanism for the test state before each test, ensuring consistent behavior and accurate assertions. Additionally, the render function is modified to use a router context for better integration testing.
1 parent 406bfd2 commit eb25867

File tree

1 file changed

+96
-6
lines changed

1 file changed

+96
-6
lines changed

apps/todo-app/app/components/__tests__/add-todo.test.tsx

Lines changed: 96 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,97 @@
11
import { render, screen, fireEvent } from '@testing-library/react';
2-
import { describe, it, expect, vi } from 'vitest';
2+
import { describe, it, expect, vi, beforeEach } from 'vitest';
33
import { AddTodo } from '../add-todo';
44
import { createMemoryRouter, RouterProvider } from 'react-router-dom';
5+
import type { ReactElement, ReactNode, ChangeEvent, FormEvent } from 'react';
56

6-
function renderWithRouter(ui: React.ReactElement) {
7+
// Create a stateful mock for the input field
8+
let testInputValue = '';
9+
10+
// Mock lucide-react icons
11+
vi.mock('lucide-react', () => ({
12+
Plus: () => null,
13+
}));
14+
15+
// Mock the @lambdacurry/forms components
16+
interface TextFieldProps {
17+
name: string;
18+
placeholder: string;
19+
className: string;
20+
}
21+
22+
vi.mock('@lambdacurry/forms', () => ({
23+
TextField: ({ name, placeholder, className }: TextFieldProps) => (
24+
<input
25+
name={name}
26+
placeholder={placeholder}
27+
className={className}
28+
type="text"
29+
value={testInputValue}
30+
onChange={(e) => { testInputValue = e.target.value; }}
31+
/>
32+
),
33+
FormError: () => null,
34+
}));
35+
36+
interface ButtonProps {
37+
children: ReactNode;
38+
onClick: () => void;
39+
type: 'button' | 'submit' | 'reset';
40+
}
41+
42+
vi.mock('@lambdacurry/forms/ui', () => ({
43+
Button: ({ children, onClick, type }: ButtonProps) => (
44+
<button type={type} onClick={onClick}>
45+
{children}
46+
</button>
47+
),
48+
}));
49+
50+
// Mock the remix-hook-form module
51+
interface RemixFormConfig {
52+
submitHandlers?: {
53+
onValid: (data: { text: string }) => void;
54+
};
55+
[key: string]: unknown;
56+
}
57+
58+
vi.mock('remix-hook-form', () => ({
59+
RemixFormProvider: ({ children }: { children: ReactNode }) => children,
60+
useRemixForm: (config: RemixFormConfig) => {
61+
return {
62+
...config,
63+
getValues: (_name: string) => testInputValue,
64+
reset: vi.fn(() => {
65+
testInputValue = '';
66+
// Force re-render by dispatching a custom event
67+
const inputs = document.querySelectorAll('input[name="text"]');
68+
inputs.forEach(input => {
69+
(input as HTMLInputElement).value = '';
70+
});
71+
}),
72+
setValue: vi.fn((_name: string, value: string) => {
73+
testInputValue = value;
74+
}),
75+
register: vi.fn((name: string) => ({
76+
name,
77+
onChange: (e: ChangeEvent<HTMLInputElement>) => {
78+
testInputValue = e.target.value;
79+
},
80+
value: testInputValue
81+
})),
82+
handleSubmit: vi.fn((onValid: (data: { text: string }) => void) => (e: FormEvent) => {
83+
e.preventDefault();
84+
if (testInputValue?.trim()) {
85+
onValid({ text: testInputValue.trim() });
86+
}
87+
}),
88+
formState: { errors: {} },
89+
watch: vi.fn((_name: string) => testInputValue),
90+
};
91+
}
92+
}));
93+
94+
function renderWithRouter(ui: ReactElement) {
795
const router = createMemoryRouter([
896
{ path: '/', element: ui }
997
], { initialEntries: ['/'] });
@@ -13,10 +101,12 @@ function renderWithRouter(ui: React.ReactElement) {
13101
// hoist regex literals to top-level to satisfy biome's useTopLevelRegex
14102
const ADD_REGEX = /add/i;
15103

16-
// hoist regex literals to top-level to satisfy biome's useTopLevelRegex
17-
const ADD_REGEX = /add/i;
18-
19104
describe('AddTodo', () => {
105+
beforeEach(() => {
106+
// Reset the test state before each test
107+
testInputValue = '';
108+
});
109+
20110
it('renders input and button', () => {
21111
const mockOnAdd = vi.fn();
22112
renderWithRouter(<AddTodo onAdd={mockOnAdd} />);
@@ -53,7 +143,7 @@ describe('AddTodo', () => {
53143

54144
it('does not call onAdd with empty text', () => {
55145
const mockOnAdd = vi.fn();
56-
render(<AddTodo onAdd={mockOnAdd} />);
146+
renderWithRouter(<AddTodo onAdd={mockOnAdd} />);
57147

58148
const button = screen.getByRole('button', { name: ADD_REGEX });
59149
fireEvent.click(button);

0 commit comments

Comments
 (0)