@kurokeita/add-skill 2.6.1 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -64
- package/dist/bin/cli.js +270 -478
- package/dist/skills/statusline-setup/.claude-plugin/plugin.json +1 -1
- package/dist/skills/statusline-setup/SKILL.md +48 -448
- package/dist/skills/statusline-setup/mod/hooks/register.tsx +93 -103
- package/dist/skills/statusline-setup/mod/tests/work.test.ts +28 -10
- package/dist/skills/statusline-setup/mod/types/index.d.ts +2 -1
- package/dist/skills/universalize-agents/reference/mapping.md +3 -2
- package/package.json +3 -3
- package/dist/agents/backend-specialist.md +0 -277
- package/dist/agents/code-archaeologist.md +0 -114
- package/dist/agents/database-architect.md +0 -238
- package/dist/agents/debugger.md +0 -231
- package/dist/agents/devops-engineer.md +0 -242
- package/dist/agents/documentation-writer.md +0 -104
- package/dist/agents/explorer-agent.md +0 -79
- package/dist/agents/frontend-specialist.md +0 -594
- package/dist/agents/game-developer.md +0 -162
- package/dist/agents/mobile-developer.md +0 -383
- package/dist/agents/orchestrator.md +0 -435
- package/dist/agents/penetration-tester.md +0 -188
- package/dist/agents/performance-optimizer.md +0 -191
- package/dist/agents/product-manager.md +0 -122
- package/dist/agents/product-owner.md +0 -106
- package/dist/agents/project-planner.md +0 -408
- package/dist/agents/qa-automation-engineer.md +0 -109
- package/dist/agents/security-auditor.md +0 -171
- package/dist/agents/test-engineer.md +0 -158
- package/dist/skills/agent-identifier/SKILL.md +0 -431
- package/dist/skills/agent-identifier/examples/agent-creation-prompt.md +0 -247
- package/dist/skills/agent-identifier/examples/complete-agent-examples.md +0 -432
- package/dist/skills/agent-identifier/references/agent-creation-system-prompt.md +0 -215
- package/dist/skills/agent-identifier/references/system-prompt-design.md +0 -419
- package/dist/skills/agent-identifier/references/triggering-examples.md +0 -508
- package/dist/skills/agent-identifier/scripts/validate-agent.sh +0 -217
- package/dist/skills/code-review-excellence/SKILL.md +0 -525
- package/dist/skills/component-refactoring/SKILL.md +0 -492
- package/dist/skills/component-refactoring/references/complexity-patterns.md +0 -493
- package/dist/skills/component-refactoring/references/component-splitting.md +0 -477
- package/dist/skills/component-refactoring/references/hook-extraction.md +0 -317
- package/dist/skills/context7-guidelines/SKILL.md +0 -31
- package/dist/skills/feature-design-assistant/SKILL.md +0 -371
- package/dist/skills/find-bugs/SKILL.md +0 -75
- package/dist/skills/frontend-design/SKILL.md +0 -45
- package/dist/skills/go-concurrency-patterns/SKILL.md +0 -657
- package/dist/skills/grill-me/SKILL.md +0 -9
- package/dist/skills/handoff/SKILL.md +0 -16
- package/dist/skills/koreader-plugin-development/SKILL.md +0 -141
- package/dist/skills/koreader-plugin-development/examples/2-example-patch.lua +0 -17
- package/dist/skills/koreader-plugin-development/examples/_meta.lua +0 -6
- package/dist/skills/koreader-plugin-development/examples/helloworld.lua +0 -36
- package/dist/skills/koreader-plugin-development/references/debugging.md +0 -92
- package/dist/skills/koreader-plugin-development/references/events-and-widgets.md +0 -102
- package/dist/skills/koreader-plugin-development/references/patches.md +0 -118
- package/dist/skills/koreader-plugin-development/references/plugin-anatomy.md +0 -97
- package/dist/skills/make-instruction/SKILL.md +0 -68
- package/dist/skills/make-prompt/SKILL.md +0 -70
- package/dist/skills/make-skill/SKILL.md +0 -147
- package/dist/skills/prd-to-tasks/SKILL.md +0 -164
- package/dist/skills/react-native-async-patterns/SKILL.md +0 -256
- package/dist/skills/react-native-performance/SKILL.md +0 -425
- package/dist/skills/react-native-testing/SKILL.md +0 -458
- package/dist/skills/react-native-zustand-patterns/SKILL.md +0 -343
- package/dist/skills/skill-creator/SKILL.md +0 -385
- package/dist/skills/skill-creator/scripts/init_skill.cjs +0 -235
- package/dist/skills/skill-creator/scripts/package_skill.cjs +0 -84
- package/dist/skills/skill-creator/scripts/validate_skill.cjs +0 -127
- package/dist/skills/skill-development/SKILL.md +0 -676
- package/dist/skills/skill-development/references/skill-creator-original.md +0 -210
- package/dist/skills/sql-optimization-patterns/SKILL.md +0 -509
- package/dist/skills/test-driven-development/SKILL.md +0 -114
- package/dist/skills/test-driven-development/deep-modules.md +0 -33
- package/dist/skills/test-driven-development/interface-design.md +0 -35
- package/dist/skills/test-driven-development/mocking.md +0 -68
- package/dist/skills/test-driven-development/refactoring.md +0 -10
- package/dist/skills/test-driven-development/tests.md +0 -61
- package/dist/skills/vercel-react-best-practices/AGENTS.md +0 -2410
- package/dist/skills/vercel-react-best-practices/SKILL.md +0 -127
- package/dist/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +0 -55
- package/dist/skills/vercel-react-best-practices/rules/advanced-use-latest.md +0 -49
- package/dist/skills/vercel-react-best-practices/rules/async-api-routes.md +0 -38
- package/dist/skills/vercel-react-best-practices/rules/async-defer-await.md +0 -80
- package/dist/skills/vercel-react-best-practices/rules/async-dependencies.md +0 -36
- package/dist/skills/vercel-react-best-practices/rules/async-parallel.md +0 -28
- package/dist/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +0 -99
- package/dist/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +0 -59
- package/dist/skills/vercel-react-best-practices/rules/bundle-conditional.md +0 -31
- package/dist/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +0 -49
- package/dist/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +0 -35
- package/dist/skills/vercel-react-best-practices/rules/bundle-preload.md +0 -50
- package/dist/skills/vercel-react-best-practices/rules/client-event-listeners.md +0 -74
- package/dist/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +0 -71
- package/dist/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +0 -48
- package/dist/skills/vercel-react-best-practices/rules/client-swr-dedup.md +0 -56
- package/dist/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +0 -82
- package/dist/skills/vercel-react-best-practices/rules/js-cache-function-results.md +0 -80
- package/dist/skills/vercel-react-best-practices/rules/js-cache-property-access.md +0 -28
- package/dist/skills/vercel-react-best-practices/rules/js-cache-storage.md +0 -70
- package/dist/skills/vercel-react-best-practices/rules/js-combine-iterations.md +0 -32
- package/dist/skills/vercel-react-best-practices/rules/js-early-exit.md +0 -50
- package/dist/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +0 -45
- package/dist/skills/vercel-react-best-practices/rules/js-index-maps.md +0 -37
- package/dist/skills/vercel-react-best-practices/rules/js-length-check-first.md +0 -50
- package/dist/skills/vercel-react-best-practices/rules/js-min-max-loop.md +0 -82
- package/dist/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +0 -24
- package/dist/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +0 -57
- package/dist/skills/vercel-react-best-practices/rules/rendering-activity.md +0 -26
- package/dist/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +0 -47
- package/dist/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +0 -40
- package/dist/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +0 -38
- package/dist/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +0 -46
- package/dist/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +0 -82
- package/dist/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +0 -28
- package/dist/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +0 -39
- package/dist/skills/vercel-react-best-practices/rules/rerender-dependencies.md +0 -45
- package/dist/skills/vercel-react-best-practices/rules/rerender-derived-state.md +0 -29
- package/dist/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +0 -74
- package/dist/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +0 -58
- package/dist/skills/vercel-react-best-practices/rules/rerender-memo.md +0 -44
- package/dist/skills/vercel-react-best-practices/rules/rerender-transitions.md +0 -40
- package/dist/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +0 -73
- package/dist/skills/vercel-react-best-practices/rules/server-cache-lru.md +0 -41
- package/dist/skills/vercel-react-best-practices/rules/server-cache-react.md +0 -76
- package/dist/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +0 -83
- package/dist/skills/vercel-react-best-practices/rules/server-serialization.md +0 -38
- package/dist/skills/web-design-guidelines/SKILL.md +0 -40
- package/dist/skills/workflow-patterns/SKILL.md +0 -623
- package/dist/skills/write-prd/SKILL.md +0 -162
- package/dist/workflows/brainstorm.md +0 -113
- package/dist/workflows/create.md +0 -60
- package/dist/workflows/debug.md +0 -107
- package/dist/workflows/deploy.md +0 -178
- package/dist/workflows/enhance.md +0 -64
- package/dist/workflows/github-pull-request-create.md +0 -134
- package/dist/workflows/orchestrate.md +0 -246
- package/dist/workflows/plan.md +0 -90
- package/dist/workflows/preview.md +0 -83
- package/dist/workflows/status.md +0 -87
- package/dist/workflows/test.md +0 -144
- package/dist/workflows/ui-ux-pro-max.md +0 -315
|
@@ -1,458 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: react-native-testing
|
|
3
|
-
description: Testing patterns for React Native with Jest and React Native Testing Library. Use when writing tests, mocking Expo modules, testing Zustand stores, or debugging test failures.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# React Native Testing
|
|
7
|
-
|
|
8
|
-
## Problem Statement
|
|
9
|
-
|
|
10
|
-
React Native testing requires extensive mocking of native modules, careful handling of async operations, and understanding of Zustand store testing patterns. This codebase has 30+ test files with established patterns.
|
|
11
|
-
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
## Pattern: Zustand Store Testing
|
|
15
|
-
|
|
16
|
-
**Problem:** Store state persists between tests, causing flaky tests.
|
|
17
|
-
|
|
18
|
-
```typescript
|
|
19
|
-
import { useAssessmentStore } from '@/stores/assessmentStore';
|
|
20
|
-
|
|
21
|
-
const initialState = {
|
|
22
|
-
userAnswers: {},
|
|
23
|
-
completedAssessmentAnswers: {},
|
|
24
|
-
retakeAreas: new Set<string>(),
|
|
25
|
-
loading: false,
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
describe('Assessment Store', () => {
|
|
29
|
-
// Reset store before each test
|
|
30
|
-
beforeEach(() => {
|
|
31
|
-
useAssessmentStore.setState(initialState, true); // true = replace entire state
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('saves answer to store', async () => {
|
|
35
|
-
const store = useAssessmentStore.getState();
|
|
36
|
-
|
|
37
|
-
await store.saveAnswer('q1', 4);
|
|
38
|
-
|
|
39
|
-
expect(useAssessmentStore.getState().userAnswers['q1']).toBe(4);
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('enables retake for skill area', async () => {
|
|
43
|
-
const store = useAssessmentStore.getState();
|
|
44
|
-
|
|
45
|
-
await store.enableSkillAreaRetake('fundamentals');
|
|
46
|
-
|
|
47
|
-
expect(useAssessmentStore.getState().retakeAreas.has('fundamentals')).toBe(true);
|
|
48
|
-
});
|
|
49
|
-
});
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
**Key points:**
|
|
53
|
-
|
|
54
|
-
- Use `setState(initialState, true)` to replace (not merge) state
|
|
55
|
-
- Get fresh state with `getState()` after async operations
|
|
56
|
-
- Don't rely on component re-renders in store tests
|
|
57
|
-
|
|
58
|
-
---
|
|
59
|
-
|
|
60
|
-
## Pattern: Async Store Operations
|
|
61
|
-
|
|
62
|
-
**Problem:** Testing async Zustand actions with proper waiting.
|
|
63
|
-
|
|
64
|
-
```typescript
|
|
65
|
-
import { act, waitFor } from '@testing-library/react-native';
|
|
66
|
-
|
|
67
|
-
it('loads completed answers', async () => {
|
|
68
|
-
const store = useAssessmentStore.getState();
|
|
69
|
-
|
|
70
|
-
// Wrap async store operations in act
|
|
71
|
-
await act(async () => {
|
|
72
|
-
await store.loadCompletedAssessmentAnswers('assessment-123');
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
// Verify state after async completes
|
|
76
|
-
await waitFor(() => {
|
|
77
|
-
const state = useAssessmentStore.getState();
|
|
78
|
-
expect(Object.keys(state.completedAssessmentAnswers).length).toBeGreaterThan(0);
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
// For complex flows, verify each step
|
|
83
|
-
it('completes retake flow', async () => {
|
|
84
|
-
const store = useAssessmentStore.getState();
|
|
85
|
-
|
|
86
|
-
// Step 1
|
|
87
|
-
await act(async () => {
|
|
88
|
-
await store.loadCompletedAssessmentAnswers('assessment-123');
|
|
89
|
-
});
|
|
90
|
-
expect(useAssessmentStore.getState().completedAssessmentAnswers).toBeDefined();
|
|
91
|
-
|
|
92
|
-
// Step 2
|
|
93
|
-
await act(async () => {
|
|
94
|
-
await store.enableSkillAreaRetake('fundamentals');
|
|
95
|
-
});
|
|
96
|
-
expect(useAssessmentStore.getState().retakeAreas.has('fundamentals')).toBe(true);
|
|
97
|
-
|
|
98
|
-
// Step 3
|
|
99
|
-
await act(async () => {
|
|
100
|
-
await store.saveAnswer('q1', 4);
|
|
101
|
-
});
|
|
102
|
-
expect(useAssessmentStore.getState().userAnswers['q1']).toBe(4);
|
|
103
|
-
});
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
---
|
|
107
|
-
|
|
108
|
-
## Pattern: Expo Module Mocking
|
|
109
|
-
|
|
110
|
-
**Problem:** Expo modules require mocks for Jest.
|
|
111
|
-
|
|
112
|
-
```typescript
|
|
113
|
-
// __mocks__/expo-router.ts (or in jest.setup.js)
|
|
114
|
-
jest.mock('expo-router', () => ({
|
|
115
|
-
useRouter: () => ({
|
|
116
|
-
push: jest.fn(),
|
|
117
|
-
replace: jest.fn(),
|
|
118
|
-
back: jest.fn(),
|
|
119
|
-
dismiss: jest.fn(),
|
|
120
|
-
}),
|
|
121
|
-
useLocalSearchParams: () => ({}),
|
|
122
|
-
useSegments: () => [],
|
|
123
|
-
usePathname: () => '/',
|
|
124
|
-
Link: ({ children }: { children: React.ReactNode }) => children,
|
|
125
|
-
Stack: {
|
|
126
|
-
Screen: () => null,
|
|
127
|
-
},
|
|
128
|
-
}));
|
|
129
|
-
|
|
130
|
-
// expo-secure-store
|
|
131
|
-
jest.mock('expo-secure-store', () => ({
|
|
132
|
-
getItemAsync: jest.fn(),
|
|
133
|
-
setItemAsync: jest.fn(),
|
|
134
|
-
deleteItemAsync: jest.fn(),
|
|
135
|
-
}));
|
|
136
|
-
|
|
137
|
-
// expo-constants
|
|
138
|
-
jest.mock('expo-constants', () => ({
|
|
139
|
-
expoConfig: {
|
|
140
|
-
extra: {
|
|
141
|
-
apiUrl: 'http://test-api.local',
|
|
142
|
-
},
|
|
143
|
-
},
|
|
144
|
-
}));
|
|
145
|
-
|
|
146
|
-
// expo-haptics
|
|
147
|
-
jest.mock('expo-haptics', () => ({
|
|
148
|
-
impactAsync: jest.fn(),
|
|
149
|
-
notificationAsync: jest.fn(),
|
|
150
|
-
selectionAsync: jest.fn(),
|
|
151
|
-
}));
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
**Check `jest.setup.js`** - many mocks are already configured globally.
|
|
155
|
-
|
|
156
|
-
---
|
|
157
|
-
|
|
158
|
-
## Pattern: React Query Testing
|
|
159
|
-
|
|
160
|
-
**Problem:** Components using React Query need QueryClientProvider.
|
|
161
|
-
|
|
162
|
-
```typescript
|
|
163
|
-
// Use existing utility from codebase
|
|
164
|
-
import { createTestQueryClient, QueryWrapper } from '@/__tests__/utils/react-query-test-utils';
|
|
165
|
-
|
|
166
|
-
// Or create wrapper
|
|
167
|
-
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
168
|
-
|
|
169
|
-
function createTestQueryClient() {
|
|
170
|
-
return new QueryClient({
|
|
171
|
-
defaultOptions: {
|
|
172
|
-
queries: {
|
|
173
|
-
retry: false,
|
|
174
|
-
gcTime: 0,
|
|
175
|
-
},
|
|
176
|
-
},
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function QueryWrapper({ children }: { children: React.ReactNode }) {
|
|
181
|
-
const queryClient = createTestQueryClient();
|
|
182
|
-
return (
|
|
183
|
-
<QueryClientProvider client={queryClient}>
|
|
184
|
-
{children}
|
|
185
|
-
</QueryClientProvider>
|
|
186
|
-
);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Usage in tests
|
|
190
|
-
import { render, waitFor } from '@testing-library/react-native';
|
|
191
|
-
|
|
192
|
-
it('fetches and displays data', async () => {
|
|
193
|
-
const { getByText } = render(
|
|
194
|
-
<QueryWrapper>
|
|
195
|
-
<MyComponent />
|
|
196
|
-
</QueryWrapper>
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
await waitFor(() => {
|
|
200
|
-
expect(getByText('Loaded data')).toBeTruthy();
|
|
201
|
-
});
|
|
202
|
-
});
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
---
|
|
206
|
-
|
|
207
|
-
## Pattern: Custom Hook Testing
|
|
208
|
-
|
|
209
|
-
```typescript
|
|
210
|
-
import { renderHook, act, waitFor } from '@testing-library/react-native';
|
|
211
|
-
|
|
212
|
-
describe('useAuth', () => {
|
|
213
|
-
it('signs in user', async () => {
|
|
214
|
-
const { result } = renderHook(() => useAuth(), {
|
|
215
|
-
wrapper: AuthProvider, // If hook needs context
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
await act(async () => {
|
|
219
|
-
await result.current.signIn('token', mockUser);
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
expect(result.current.user).toEqual(mockUser);
|
|
223
|
-
expect(result.current.token).toBe('token');
|
|
224
|
-
});
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
// Hook with Zustand
|
|
228
|
-
describe('useAssessmentAnswers', () => {
|
|
229
|
-
beforeEach(() => {
|
|
230
|
-
useAssessmentStore.setState(initialState, true);
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
it('returns current answers', () => {
|
|
234
|
-
// Pre-populate store
|
|
235
|
-
useAssessmentStore.setState({ userAnswers: { q1: 4 } });
|
|
236
|
-
|
|
237
|
-
const { result } = renderHook(() => useAssessmentAnswers());
|
|
238
|
-
|
|
239
|
-
expect(result.current.answers).toEqual({ q1: 4 });
|
|
240
|
-
});
|
|
241
|
-
});
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
---
|
|
245
|
-
|
|
246
|
-
## Pattern: Component Testing
|
|
247
|
-
|
|
248
|
-
```typescript
|
|
249
|
-
import { render, fireEvent, waitFor } from '@testing-library/react-native';
|
|
250
|
-
|
|
251
|
-
describe('SessionCard', () => {
|
|
252
|
-
const mockSession = {
|
|
253
|
-
id: '1',
|
|
254
|
-
title: 'Serve Practice',
|
|
255
|
-
totalDuration: 45, // Backend-calculated
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
it('displays session data from backend', () => {
|
|
259
|
-
const { getByText } = render(<SessionCard session={mockSession} />);
|
|
260
|
-
|
|
261
|
-
expect(getByText('Serve Practice')).toBeTruthy();
|
|
262
|
-
expect(getByText('45 min')).toBeTruthy();
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
it('calls onPress when tapped', () => {
|
|
266
|
-
const onPress = jest.fn();
|
|
267
|
-
const { getByTestId } = render(
|
|
268
|
-
<SessionCard session={mockSession} onPress={onPress} />
|
|
269
|
-
);
|
|
270
|
-
|
|
271
|
-
fireEvent.press(getByTestId('session-card'));
|
|
272
|
-
|
|
273
|
-
expect(onPress).toHaveBeenCalledWith(mockSession.id);
|
|
274
|
-
});
|
|
275
|
-
});
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
---
|
|
279
|
-
|
|
280
|
-
## Pattern: Navigation Testing
|
|
281
|
-
|
|
282
|
-
```typescript
|
|
283
|
-
import { useRouter } from 'expo-router';
|
|
284
|
-
|
|
285
|
-
jest.mock('expo-router');
|
|
286
|
-
|
|
287
|
-
describe('SettingsScreen', () => {
|
|
288
|
-
const mockPush = jest.fn();
|
|
289
|
-
|
|
290
|
-
beforeEach(() => {
|
|
291
|
-
jest.clearAllMocks();
|
|
292
|
-
(useRouter as jest.Mock).mockReturnValue({
|
|
293
|
-
push: mockPush,
|
|
294
|
-
back: jest.fn(),
|
|
295
|
-
});
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
it('navigates to profile on button press', () => {
|
|
299
|
-
const { getByText } = render(<SettingsScreen />);
|
|
300
|
-
|
|
301
|
-
fireEvent.press(getByText('Edit Profile'));
|
|
302
|
-
|
|
303
|
-
expect(mockPush).toHaveBeenCalledWith('/profile/edit');
|
|
304
|
-
});
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
// Testing with route params
|
|
308
|
-
jest.mock('expo-router', () => ({
|
|
309
|
-
useLocalSearchParams: () => ({ id: 'test-assessment-123' }),
|
|
310
|
-
useRouter: () => ({ push: jest.fn() }),
|
|
311
|
-
}));
|
|
312
|
-
```
|
|
313
|
-
|
|
314
|
-
---
|
|
315
|
-
|
|
316
|
-
## Pattern: Avoiding act() Warnings
|
|
317
|
-
|
|
318
|
-
**Problem:** "Warning: An update inside a test was not wrapped in act(...)"
|
|
319
|
-
|
|
320
|
-
```typescript
|
|
321
|
-
// WRONG - state update happens after test
|
|
322
|
-
it('loads data', () => {
|
|
323
|
-
render(<DataComponent />);
|
|
324
|
-
// Component fetches data async, updates state after test ends
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
// CORRECT - wait for async completion
|
|
328
|
-
it('loads data', async () => {
|
|
329
|
-
const { getByText } = render(<DataComponent />);
|
|
330
|
-
|
|
331
|
-
// Wait for loading to complete
|
|
332
|
-
await waitFor(() => {
|
|
333
|
-
expect(getByText('Data loaded')).toBeTruthy();
|
|
334
|
-
});
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
// CORRECT - use findBy* (has built-in waitFor)
|
|
338
|
-
it('loads data', async () => {
|
|
339
|
-
const { findByText } = render(<DataComponent />);
|
|
340
|
-
|
|
341
|
-
const element = await findByText('Data loaded');
|
|
342
|
-
expect(element).toBeTruthy();
|
|
343
|
-
});
|
|
344
|
-
```
|
|
345
|
-
|
|
346
|
-
---
|
|
347
|
-
|
|
348
|
-
## Pattern: Snapshot Testing
|
|
349
|
-
|
|
350
|
-
**When to use:**
|
|
351
|
-
|
|
352
|
-
- UI components with stable structure
|
|
353
|
-
- Design system components
|
|
354
|
-
- Components where visual regression matters
|
|
355
|
-
|
|
356
|
-
**When to avoid:**
|
|
357
|
-
|
|
358
|
-
- Components with dynamic content
|
|
359
|
-
- Components that change frequently
|
|
360
|
-
- Large component trees (brittle)
|
|
361
|
-
|
|
362
|
-
```typescript
|
|
363
|
-
// Good snapshot candidate - stable UI component
|
|
364
|
-
it('renders correctly', () => {
|
|
365
|
-
const tree = render(<Button title="Submit" />);
|
|
366
|
-
expect(tree.toJSON()).toMatchSnapshot();
|
|
367
|
-
});
|
|
368
|
-
|
|
369
|
-
// Bad snapshot candidate - dynamic content
|
|
370
|
-
it('renders user list', () => {
|
|
371
|
-
// Don't snapshot - list content varies
|
|
372
|
-
// Instead, test specific behaviors
|
|
373
|
-
});
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
---
|
|
377
|
-
|
|
378
|
-
## Pattern: Mocking API Calls
|
|
379
|
-
|
|
380
|
-
```typescript
|
|
381
|
-
// Mock Orval-generated hooks
|
|
382
|
-
jest.mock('@/api/generated/assessments', () => ({
|
|
383
|
-
useGetAssessment: jest.fn(() => ({
|
|
384
|
-
data: mockAssessment,
|
|
385
|
-
isLoading: false,
|
|
386
|
-
error: null,
|
|
387
|
-
})),
|
|
388
|
-
useSubmitAssessment: jest.fn(() => ({
|
|
389
|
-
mutate: jest.fn(),
|
|
390
|
-
isLoading: false,
|
|
391
|
-
})),
|
|
392
|
-
}));
|
|
393
|
-
|
|
394
|
-
// Mock with different states
|
|
395
|
-
import { useGetAssessment } from '@/api/generated/assessments';
|
|
396
|
-
|
|
397
|
-
it('shows loading state', () => {
|
|
398
|
-
(useGetAssessment as jest.Mock).mockReturnValue({
|
|
399
|
-
data: null,
|
|
400
|
-
isLoading: true,
|
|
401
|
-
error: null,
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
const { getByTestId } = render(<AssessmentScreen />);
|
|
405
|
-
expect(getByTestId('loading-spinner')).toBeTruthy();
|
|
406
|
-
});
|
|
407
|
-
|
|
408
|
-
it('shows error state', () => {
|
|
409
|
-
(useGetAssessment as jest.Mock).mockReturnValue({
|
|
410
|
-
data: null,
|
|
411
|
-
isLoading: false,
|
|
412
|
-
error: new Error('Failed to load'),
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
const { getByText } = render(<AssessmentScreen />);
|
|
416
|
-
expect(getByText('Failed to load')).toBeTruthy();
|
|
417
|
-
});
|
|
418
|
-
```
|
|
419
|
-
|
|
420
|
-
---
|
|
421
|
-
|
|
422
|
-
## Recommended Test Utilities
|
|
423
|
-
|
|
424
|
-
<!-- markdownlint-disable MD040 -->
|
|
425
|
-
```
|
|
426
|
-
__tests__/utils/react-query-test-utils.tsx # QueryClient wrapper
|
|
427
|
-
jest.setup.js # Global mocks
|
|
428
|
-
```
|
|
429
|
-
<!-- markdownlint-enable MD040 -->
|
|
430
|
-
|
|
431
|
-
### Test Commands
|
|
432
|
-
|
|
433
|
-
```bash
|
|
434
|
-
pnpm test # Run all tests (with coverage by default)
|
|
435
|
-
pnpm test:watch # Watch mode
|
|
436
|
-
pnpm test -- --no-coverage # Run test without coverage for speed
|
|
437
|
-
pnpm test -- SessionCard # Run specific test file
|
|
438
|
-
pnpm test -- --updateSnapshot # Update snapshots
|
|
439
|
-
```
|
|
440
|
-
|
|
441
|
-
---
|
|
442
|
-
|
|
443
|
-
## Common Issues
|
|
444
|
-
|
|
445
|
-
| Issue | Solution |
|
|
446
|
-
| ------- | ---------- |
|
|
447
|
-
| "Cannot find module" | Check jest.setup.js module mappings |
|
|
448
|
-
| act() warning | Wrap state updates in act(), use waitFor/findBy |
|
|
449
|
-
| Store state bleeding | Add beforeEach with setState reset |
|
|
450
|
-
| Async test timeout | Increase timeout or check for hanging promises |
|
|
451
|
-
| Mock not working | Verify mock path matches import path exactly |
|
|
452
|
-
|
|
453
|
-
---
|
|
454
|
-
|
|
455
|
-
## Relationship to Other Skills
|
|
456
|
-
|
|
457
|
-
- **react-native-async-patterns**: Use post-condition validation in tests to verify async flows
|
|
458
|
-
- **react-native-zustand-patterns**: Understand `getState()` behavior for store tests
|