Skip to content

Commit 9a7b467

Browse files
committed
refactor(todos): streamline todo context reducer and improve formatting
This update simplifies the todo context reducer by consolidating conditional expressions for better readability. Additionally, it enhances the test suite by allowing real localStorage usage in tests, ensuring more accurate persistence behavior during testing. Minor formatting adjustments are also made for consistency.
1 parent 501a061 commit 9a7b467

File tree

3 files changed

+21
-20
lines changed

3 files changed

+21
-20
lines changed

apps/todo-app/app/lib/__tests__/todo-context.test.tsx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2-
import { render, screen, act } from '@testing-library/react';
2+
import { render, screen, act, waitFor } from '@testing-library/react';
33
import { TodoProvider, useTodoStore, getFilteredTodos } from '../todo-context';
44
import type { Todo } from '@todo-starter/utils';
55

@@ -62,6 +62,8 @@ describe('todo-context', () => {
6262
const ORIGINAL_ENV = process.env.NODE_ENV;
6363

6464
beforeEach(() => {
65+
// Opt-in to using real localStorage inside tests for this suite
66+
Object.defineProperty(globalThis, '__ALLOW_STORAGE_IN_TESTS__', { value: true, configurable: true });
6567
// allow storage helpers to operate by switching env off 'test' for these tests
6668
process.env.NODE_ENV = 'development';
6769
try {
@@ -74,6 +76,8 @@ describe('todo-context', () => {
7476
afterEach(() => {
7577
// restore jsdom localStorage cleanliness and env
7678
process.env.NODE_ENV = ORIGINAL_ENV;
79+
// Remove opt-in flag after each test to avoid cross-suite leakage
80+
Object.defineProperty(globalThis, '__ALLOW_STORAGE_IN_TESTS__', { value: undefined, configurable: true });
7781
try {
7882
window.localStorage.removeItem(STORAGE_KEY);
7983
} catch {
@@ -237,8 +241,9 @@ describe('todo-context', () => {
237241
expect(screen.getByTestId('todos-count')).toHaveTextContent('1');
238242
});
239243

240-
it('persists on addTodo, toggleTodo, setFilter', () => {
241-
const spy = vi.spyOn(window.localStorage, 'setItem');
244+
it('persists on addTodo, toggleTodo, setFilter', async () => {
245+
const utils = await import('@todo-starter/utils');
246+
const spy = vi.spyOn(utils, 'saveToStorage');
242247

243248
renderWithProvider();
244249

@@ -252,8 +257,8 @@ describe('todo-context', () => {
252257
screen.getByTestId('set-filter').click();
253258
});
254259

255-
// Called multiple times through effect
256-
expect(spy).toHaveBeenCalled();
260+
// Called via utils wrapper (effects may be scheduled)
261+
await waitFor(() => expect(spy).toHaveBeenCalled());
257262

258263
spy.mockRestore();
259264
});

apps/todo-app/app/lib/todo-context.tsx

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,7 @@ function todoReducer(state: TodoState, action: TodoAction): TodoState {
6666
return {
6767
...state,
6868
todos: state.todos.map(todo =>
69-
todo.id === action.payload
70-
? { ...todo, completed: !todo.completed, updatedAt: new Date() }
71-
: todo
69+
todo.id === action.payload ? { ...todo, completed: !todo.completed, updatedAt: new Date() } : todo
7270
)
7371
};
7472
case 'DELETE_TODO':
@@ -80,9 +78,7 @@ function todoReducer(state: TodoState, action: TodoAction): TodoState {
8078
return {
8179
...state,
8280
todos: state.todos.map(todo =>
83-
todo.id === action.payload.id
84-
? { ...todo, text: action.payload.text.trim(), updatedAt: new Date() }
85-
: todo
81+
todo.id === action.payload.id ? { ...todo, text: action.payload.text.trim(), updatedAt: new Date() } : todo
8682
)
8783
};
8884
case 'SET_FILTER':
@@ -151,17 +147,12 @@ export function TodoProvider({ children }: { children: ReactNode }) {
151147
addTodo: (text: string) => dispatch({ type: 'ADD_TODO', payload: text }),
152148
toggleTodo: (id: string) => dispatch({ type: 'TOGGLE_TODO', payload: id }),
153149
deleteTodo: (id: string) => dispatch({ type: 'DELETE_TODO', payload: id }),
154-
updateTodo: (id: string, text: string) =>
155-
dispatch({ type: 'UPDATE_TODO', payload: { id, text } }),
150+
updateTodo: (id: string, text: string) => dispatch({ type: 'UPDATE_TODO', payload: { id, text } }),
156151
setFilter: (filter: TodoFilter) => dispatch({ type: 'SET_FILTER', payload: filter }),
157152
clearCompleted: () => dispatch({ type: 'CLEAR_COMPLETED' })
158153
};
159154

160-
return (
161-
<TodoContext.Provider value={contextValue}>
162-
{children}
163-
</TodoContext.Provider>
164-
);
155+
return <TodoContext.Provider value={contextValue}>{children}</TodoContext.Provider>;
165156
}
166157

167158
// Custom hook to use the todo context

packages/utils/src/storage.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@
33
export type StorageLike = Pick<Storage, 'getItem' | 'setItem' | 'removeItem'>;
44

55
function getStorage(): StorageLike | null {
6-
// Disable in test environments to keep tests deterministic
7-
if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'test') return null;
6+
// Allow tests to opt-in to real storage by setting a runtime flag
7+
const allowInTests =
8+
typeof globalThis !== 'undefined' &&
9+
// Use `unknown` and index signature to avoid `any`
10+
(globalThis as unknown as Record<string, unknown>).__ALLOW_STORAGE_IN_TESTS__ === true;
11+
// Disable in test environments unless explicitly allowed
12+
if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'test' && !allowInTests) return null;
813
if (typeof window === 'undefined') return null;
914
try {
1015
return window.localStorage;

0 commit comments

Comments
 (0)