Skip to content

Commit e5c169f

Browse files
authored
Merge pull request #5 from lambda-curry/codegen-bot/remove-zustand-state-management-1754516674
2 parents 303ac5e + 32ced59 commit e5c169f

File tree

8 files changed

+375
-242
lines changed

8 files changed

+375
-242
lines changed

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ A modern, full-featured todo application built with React Router 7, showcasing b
99
- **Tailwind CSS v4** - Modern styling with CSS variables
1010
- **shadcn/ui Components** - Beautiful, accessible UI components
1111
- **TypeScript** - Full type safety throughout
12-
- **Zustand** - Lightweight state management
12+
- **React Context + useReducer** - Built-in state management
1313
- **Vitest** - Fast unit testing
1414
- **Bun** - Fast package manager and runtime
1515
- **Biome** - Fast linting and formatting
@@ -82,11 +82,12 @@ This project uses a monorepo structure with the following packages:
8282
- **@todo-starter/utils** - Shared utilities, types, and helpers
8383

8484
### State Management
85-
The app uses Zustand for state management with the following features:
85+
The app uses React's built-in Context API with useReducer for state management with the following features:
8686
- In-memory todo storage
8787
- CRUD operations for todos
8888
- Filtering (all, active, completed)
8989
- Bulk operations (clear completed)
90+
- Type-safe actions and state updates
9091

9192
### Component Architecture
9293
Components are organized by feature and follow these principles:
@@ -181,7 +182,7 @@ The app supports:
181182
- [React Router 7 Documentation](https://reactrouter.com/)
182183
- [Tailwind CSS v4](https://tailwindcss.com/)
183184
- [shadcn/ui](https://ui.shadcn.com/)
184-
- [Zustand](https://zustand-demo.pmnd.rs/)
185+
- [React Context](https://react.dev/reference/react/useContext)
185186
- [Vitest](https://vitest.dev/)
186187
- [Turbo](https://turbo.build/)
187188

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { render, screen, act } from '@testing-library/react';
3+
import { TodoProvider, useTodoStore, getFilteredTodos } from '../todo-context';
4+
import type { Todo } from '@todo-starter/utils';
5+
6+
// Mock crypto.randomUUID for consistent testing
7+
Object.defineProperty(global, 'crypto', {
8+
value: {
9+
randomUUID: () => 'test-uuid'
10+
}
11+
});
12+
13+
// Test component to access the context
14+
function TestComponent() {
15+
const {
16+
todos,
17+
filter,
18+
addTodo,
19+
toggleTodo,
20+
deleteTodo,
21+
updateTodo,
22+
setFilter,
23+
clearCompleted
24+
} = useTodoStore();
25+
26+
return (
27+
<div>
28+
<div data-testid="todos-count">{todos.length}</div>
29+
<div data-testid="filter">{filter}</div>
30+
<button onClick={() => addTodo('New todo')} data-testid="add-todo">
31+
Add Todo
32+
</button>
33+
<button
34+
onClick={() => todos.length > 0 && toggleTodo(todos[0].id)}
35+
data-testid="toggle-todo"
36+
>
37+
Toggle First Todo
38+
</button>
39+
<button
40+
onClick={() => todos.length > 0 && deleteTodo(todos[0].id)}
41+
data-testid="delete-todo"
42+
>
43+
Delete First Todo
44+
</button>
45+
<button
46+
onClick={() => todos.length > 0 && updateTodo(todos[0].id, 'Updated text')}
47+
data-testid="update-todo"
48+
>
49+
Update First Todo
50+
</button>
51+
<button onClick={() => setFilter('active')} data-testid="set-filter">
52+
Set Active Filter
53+
</button>
54+
<button onClick={() => clearCompleted()} data-testid="clear-completed">
55+
Clear Completed
56+
</button>
57+
{todos.map(todo => (
58+
<div key={todo.id} data-testid={`todo-${todo.id}`}>
59+
{todo.text} - {todo.completed ? 'completed' : 'active'}
60+
</div>
61+
))}
62+
</div>
63+
);
64+
}
65+
66+
function renderWithProvider() {
67+
return render(
68+
<TodoProvider>
69+
<TestComponent />
70+
</TodoProvider>
71+
);
72+
}
73+
74+
describe('todo-context', () => {
75+
describe('TodoProvider and useTodoStore', () => {
76+
it('provides initial todos', () => {
77+
renderWithProvider();
78+
79+
expect(screen.getByTestId('todos-count')).toHaveTextContent('3');
80+
expect(screen.getByTestId('filter')).toHaveTextContent('all');
81+
});
82+
83+
it('adds a new todo', () => {
84+
renderWithProvider();
85+
86+
act(() => {
87+
screen.getByTestId('add-todo').click();
88+
});
89+
90+
expect(screen.getByTestId('todos-count')).toHaveTextContent('4');
91+
expect(screen.getByTestId('todo-test-uuid')).toHaveTextContent('New todo - active');
92+
});
93+
94+
it('toggles todo completion status', () => {
95+
renderWithProvider();
96+
97+
// First todo should be active initially
98+
expect(screen.getByTestId('todo-1')).toHaveTextContent('Learn React Router 7 - active');
99+
100+
act(() => {
101+
screen.getByTestId('toggle-todo').click();
102+
});
103+
104+
expect(screen.getByTestId('todo-1')).toHaveTextContent('Learn React Router 7 - completed');
105+
});
106+
107+
it('deletes a todo', () => {
108+
renderWithProvider();
109+
110+
expect(screen.getByTestId('todos-count')).toHaveTextContent('3');
111+
112+
act(() => {
113+
screen.getByTestId('delete-todo').click();
114+
});
115+
116+
expect(screen.getByTestId('todos-count')).toHaveTextContent('2');
117+
expect(screen.queryByTestId('todo-1')).not.toBeInTheDocument();
118+
});
119+
120+
it('updates todo text', () => {
121+
renderWithProvider();
122+
123+
expect(screen.getByTestId('todo-1')).toHaveTextContent('Learn React Router 7 - active');
124+
125+
act(() => {
126+
screen.getByTestId('update-todo').click();
127+
});
128+
129+
expect(screen.getByTestId('todo-1')).toHaveTextContent('Updated text - active');
130+
});
131+
132+
it('sets filter', () => {
133+
renderWithProvider();
134+
135+
expect(screen.getByTestId('filter')).toHaveTextContent('all');
136+
137+
act(() => {
138+
screen.getByTestId('set-filter').click();
139+
});
140+
141+
expect(screen.getByTestId('filter')).toHaveTextContent('active');
142+
});
143+
144+
it('clears completed todos', () => {
145+
renderWithProvider();
146+
147+
// Toggle first todo to completed
148+
act(() => {
149+
screen.getByTestId('toggle-todo').click();
150+
});
151+
152+
expect(screen.getByTestId('todos-count')).toHaveTextContent('3');
153+
154+
act(() => {
155+
screen.getByTestId('clear-completed').click();
156+
});
157+
158+
expect(screen.getByTestId('todos-count')).toHaveTextContent('2');
159+
});
160+
161+
it('throws error when used outside provider', () => {
162+
// Suppress console.error for this test
163+
const originalError = console.error;
164+
console.error = () => {};
165+
166+
expect(() => {
167+
render(<TestComponent />);
168+
}).toThrow('useTodoStore must be used within a TodoProvider');
169+
170+
console.error = originalError;
171+
});
172+
});
173+
174+
describe('getFilteredTodos', () => {
175+
const mockTodos: Todo[] = [
176+
{
177+
id: '1',
178+
text: 'Active todo',
179+
completed: false,
180+
createdAt: new Date(),
181+
updatedAt: new Date()
182+
},
183+
{
184+
id: '2',
185+
text: 'Completed todo',
186+
completed: true,
187+
createdAt: new Date(),
188+
updatedAt: new Date()
189+
}
190+
];
191+
192+
it('returns all todos when filter is "all"', () => {
193+
const filtered = getFilteredTodos(mockTodos, 'all');
194+
expect(filtered).toHaveLength(2);
195+
});
196+
197+
it('returns only active todos when filter is "active"', () => {
198+
const filtered = getFilteredTodos(mockTodos, 'active');
199+
expect(filtered).toHaveLength(1);
200+
expect(filtered[0].completed).toBe(false);
201+
});
202+
203+
it('returns only completed todos when filter is "completed"', () => {
204+
const filtered = getFilteredTodos(mockTodos, 'completed');
205+
expect(filtered).toHaveLength(1);
206+
expect(filtered[0].completed).toBe(true);
207+
});
208+
});
209+
});

apps/todo-app/app/lib/__tests__/todo-store.test.ts

Lines changed: 0 additions & 145 deletions
This file was deleted.

0 commit comments

Comments
 (0)