@pageai/ralph-loop 1.0.0

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.
Files changed (120) hide show
  1. package/.agent/PROMPT.md +58 -0
  2. package/.agent/STEERING.md +3 -0
  3. package/.agent/logs/LOG.md +13 -0
  4. package/.agent/prd/.gitkeep +0 -0
  5. package/.agent/screenshots/.gitkeep +0 -0
  6. package/.agent/skills/component-refactoring/SKILL.md +247 -0
  7. package/.agent/skills/component-refactoring/references/complexity-patterns.md +485 -0
  8. package/.agent/skills/component-refactoring/references/component-splitting.md +419 -0
  9. package/.agent/skills/component-refactoring/references/hook-extraction.md +317 -0
  10. package/.agent/skills/e2e-tester/SKILL.md +595 -0
  11. package/.agent/skills/frontend-code-review/SKILL.md +73 -0
  12. package/.agent/skills/frontend-code-review/references/code-quality.md +28 -0
  13. package/.agent/skills/frontend-code-review/references/performance.md +36 -0
  14. package/.agent/skills/frontend-testing/SKILL.md +316 -0
  15. package/.agent/skills/frontend-testing/assets/component-test.template.tsx +293 -0
  16. package/.agent/skills/frontend-testing/assets/hook-test.template.ts +207 -0
  17. package/.agent/skills/frontend-testing/assets/utility-test.template.ts +154 -0
  18. package/.agent/skills/frontend-testing/references/async-testing.md +345 -0
  19. package/.agent/skills/frontend-testing/references/checklist.md +188 -0
  20. package/.agent/skills/frontend-testing/references/common-patterns.md +449 -0
  21. package/.agent/skills/frontend-testing/references/mocking.md +289 -0
  22. package/.agent/skills/frontend-testing/references/workflow.md +265 -0
  23. package/.agent/skills/prd-creator/JSON.md +613 -0
  24. package/.agent/skills/prd-creator/PRD.md +196 -0
  25. package/.agent/skills/prd-creator/SKILL.md +143 -0
  26. package/.agent/skills/skill-creator/SKILL.md +355 -0
  27. package/.agent/skills/skill-creator/references/output-patterns.md +86 -0
  28. package/.agent/skills/skill-creator/references/workflows.md +28 -0
  29. package/.agent/skills/skill-creator/scripts/init_skill.py +300 -0
  30. package/.agent/skills/skill-creator/scripts/package_skill.py +110 -0
  31. package/.agent/skills/vercel-react-best-practices/AGENTS.md +2249 -0
  32. package/.agent/skills/vercel-react-best-practices/SKILL.md +125 -0
  33. package/.agent/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
  34. package/.agent/skills/vercel-react-best-practices/rules/advanced-use-latest.md +49 -0
  35. package/.agent/skills/vercel-react-best-practices/rules/async-api-routes.md +38 -0
  36. package/.agent/skills/vercel-react-best-practices/rules/async-defer-await.md +80 -0
  37. package/.agent/skills/vercel-react-best-practices/rules/async-dependencies.md +36 -0
  38. package/.agent/skills/vercel-react-best-practices/rules/async-parallel.md +28 -0
  39. package/.agent/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +99 -0
  40. package/.agent/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +59 -0
  41. package/.agent/skills/vercel-react-best-practices/rules/bundle-conditional.md +31 -0
  42. package/.agent/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +49 -0
  43. package/.agent/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
  44. package/.agent/skills/vercel-react-best-practices/rules/bundle-preload.md +50 -0
  45. package/.agent/skills/vercel-react-best-practices/rules/client-event-listeners.md +74 -0
  46. package/.agent/skills/vercel-react-best-practices/rules/client-swr-dedup.md +56 -0
  47. package/.agent/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +82 -0
  48. package/.agent/skills/vercel-react-best-practices/rules/js-cache-function-results.md +80 -0
  49. package/.agent/skills/vercel-react-best-practices/rules/js-cache-property-access.md +28 -0
  50. package/.agent/skills/vercel-react-best-practices/rules/js-cache-storage.md +70 -0
  51. package/.agent/skills/vercel-react-best-practices/rules/js-combine-iterations.md +32 -0
  52. package/.agent/skills/vercel-react-best-practices/rules/js-early-exit.md +50 -0
  53. package/.agent/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +45 -0
  54. package/.agent/skills/vercel-react-best-practices/rules/js-index-maps.md +37 -0
  55. package/.agent/skills/vercel-react-best-practices/rules/js-length-check-first.md +49 -0
  56. package/.agent/skills/vercel-react-best-practices/rules/js-min-max-loop.md +82 -0
  57. package/.agent/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +24 -0
  58. package/.agent/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +57 -0
  59. package/.agent/skills/vercel-react-best-practices/rules/rendering-activity.md +26 -0
  60. package/.agent/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
  61. package/.agent/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +40 -0
  62. package/.agent/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +38 -0
  63. package/.agent/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
  64. package/.agent/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
  65. package/.agent/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +28 -0
  66. package/.agent/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +39 -0
  67. package/.agent/skills/vercel-react-best-practices/rules/rerender-dependencies.md +45 -0
  68. package/.agent/skills/vercel-react-best-practices/rules/rerender-derived-state.md +29 -0
  69. package/.agent/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +74 -0
  70. package/.agent/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
  71. package/.agent/skills/vercel-react-best-practices/rules/rerender-memo.md +44 -0
  72. package/.agent/skills/vercel-react-best-practices/rules/rerender-transitions.md +40 -0
  73. package/.agent/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +73 -0
  74. package/.agent/skills/vercel-react-best-practices/rules/server-cache-lru.md +41 -0
  75. package/.agent/skills/vercel-react-best-practices/rules/server-cache-react.md +26 -0
  76. package/.agent/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +79 -0
  77. package/.agent/skills/vercel-react-best-practices/rules/server-serialization.md +38 -0
  78. package/.agent/skills/vitest-best-practices/AGENTS.md +84 -0
  79. package/.agent/skills/vitest-best-practices/SKILL.md +130 -0
  80. package/.agent/skills/vitest-best-practices/references/aaa-pattern.md +260 -0
  81. package/.agent/skills/vitest-best-practices/references/assertions.md +393 -0
  82. package/.agent/skills/vitest-best-practices/references/async-testing.md +454 -0
  83. package/.agent/skills/vitest-best-practices/references/error-handling.md +382 -0
  84. package/.agent/skills/vitest-best-practices/references/organization.md +212 -0
  85. package/.agent/skills/vitest-best-practices/references/parameterized-tests.md +297 -0
  86. package/.agent/skills/vitest-best-practices/references/performance.md +528 -0
  87. package/.agent/skills/vitest-best-practices/references/snapshot-testing.md +483 -0
  88. package/.agent/skills/vitest-best-practices/references/test-doubles.md +499 -0
  89. package/.agent/skills/vitest-best-practices/references/vitest-features.md +529 -0
  90. package/.agent/skills/web-design-guidelines/SKILL.md +39 -0
  91. package/.agent/tasks/.gitkeep +0 -0
  92. package/.agent/tasks.json +1 -0
  93. package/.claude/agents/code-reviewer.md +172 -0
  94. package/.claude/commands/aw.md +50 -0
  95. package/.claude/hooks/play-sound.js +87 -0
  96. package/.claude/hooks/pre-tool-use.js +40 -0
  97. package/.claude/settings.json +54 -0
  98. package/.claude/settings.local.json +13 -0
  99. package/.mcp.json +31 -0
  100. package/AGENTS.md +44 -0
  101. package/CLAUDE.md +1 -0
  102. package/README.md +236 -0
  103. package/bin/cli.js +156 -0
  104. package/bin/lib/copy.js +149 -0
  105. package/bin/lib/display.js +137 -0
  106. package/package.json +65 -0
  107. package/ralph.sh +333 -0
  108. package/scripts/lib/args.sh +44 -0
  109. package/scripts/lib/cleanup.sh +53 -0
  110. package/scripts/lib/constants.sh +25 -0
  111. package/scripts/lib/display.sh +196 -0
  112. package/scripts/lib/logging.sh +30 -0
  113. package/scripts/lib/notify.sh +41 -0
  114. package/scripts/lib/output.sh +147 -0
  115. package/scripts/lib/preflight.sh +57 -0
  116. package/scripts/lib/preview.sh +77 -0
  117. package/scripts/lib/promise.sh +76 -0
  118. package/scripts/lib/spinner.sh +85 -0
  119. package/scripts/lib/terminal.sh +57 -0
  120. package/scripts/lib/timing.sh +223 -0
@@ -0,0 +1,36 @@
1
+ # Rule Catalog — Performance
2
+
3
+ ## Complex prop memoization
4
+
5
+ IsUrgent: True
6
+ Category: Performance
7
+
8
+ ### Description
9
+
10
+ Wrap complex prop values (objects, arrays, maps) in `useMemo` prior to passing them into child components to guarantee stable references and prevent unnecessary renders.
11
+
12
+ Update this file when adding, editing, or removing Performance rules so the catalog remains accurate.
13
+
14
+ Wrong:
15
+
16
+ ```tsx
17
+ <HeavyComp
18
+ config={{
19
+ provider: ...,
20
+ detail: ...
21
+ }}
22
+ />
23
+ ```
24
+
25
+ Right:
26
+
27
+ ```tsx
28
+ const config = useMemo(() => ({
29
+ provider: ...,
30
+ detail: ...
31
+ }), [provider, detail]);
32
+
33
+ <HeavyComp
34
+ config={config}
35
+ />
36
+ ```
@@ -0,0 +1,316 @@
1
+ ---
2
+ name: frontend-testing
3
+ description: Generate Vitest + React Testing Library tests for frontend components, hooks, and utilities. Triggers on testing, spec files, coverage, Vitest, RTL, unit tests, integration tests, or write/review test requests.
4
+ ---
5
+
6
+ This skill enables Claude to generate high-quality, comprehensive frontend tests following established conventions and best practices.
7
+
8
+ ## When to Apply This Skill
9
+
10
+ Apply this skill when the user:
11
+
12
+ - Asks to **write tests** for a component, hook, or utility
13
+ - Asks to **review existing tests** for completeness
14
+ - Mentions **Vitest**, **React Testing Library**, **RTL**, or **spec files**
15
+ - Requests **test coverage** improvement
16
+ - Mentions **testing**, **unit tests**, or **integration tests** for frontend code
17
+ - Wants to understand **testing patterns** in the frontend codebase
18
+
19
+ **Do NOT apply** when:
20
+
21
+ - User is asking about E2E tests (Playwright)
22
+ - User is only asking conceptual questions without code context
23
+
24
+ ## Quick Reference
25
+
26
+ ### Tech Stack
27
+
28
+ | Tool | Version | Purpose |
29
+ | --------------------- | ------- | ----------------- |
30
+ | Vitest | 4+ | Test runner |
31
+ | React Testing Library | 16+ | Component testing |
32
+ | jsdom | - | Test environment |
33
+ | TypeScript | 5+ | Type safety |
34
+
35
+ Note: keep this list up to date with the project's dependencies.
36
+
37
+ ### Key Commands
38
+
39
+ **Always prefer running specific tests over the entire suite** for faster feedback:
40
+
41
+ ```bash
42
+ # Run ALL tests (avoid during development)
43
+ npx vitest run
44
+
45
+ # ✅ PREFERRED: Run specific file
46
+ npx vitest run src/components/Button.spec.tsx
47
+
48
+ # ✅ PREFERRED: Run tests matching a pattern
49
+ npx vitest run --grep "Button"
50
+ npx vitest run -t "should render"
51
+
52
+ # ✅ PREFERRED: Run specific describe block
53
+ npx vitest run --grep "Button > Rendering"
54
+
55
+ # ✅ Run tests in a directory
56
+ npx vitest run src/components/
57
+
58
+ # ✅ Run single test by name
59
+ npx vitest run -t "should disable button when loading"
60
+ ```
61
+
62
+ ### Watch Mode (Background Testing)
63
+
64
+ Use watch mode for efficient iterative development:
65
+
66
+ ```bash
67
+ # ✅ Watch mode - reruns on file changes
68
+ npx vitest
69
+
70
+ # ✅ Watch specific file
71
+ npx vitest src/components/Button.spec.tsx
72
+
73
+ # ✅ Watch tests matching pattern
74
+ npx vitest --grep "Button"
75
+ ```
76
+
77
+ ### File Naming
78
+
79
+ - Test files: `ComponentName.spec.tsx` (same directory as component)
80
+
81
+ ## Test Structure Template
82
+
83
+ ```typescript
84
+ import { render, screen, fireEvent, waitFor } from '@testing-library/react'
85
+ import Component from './index'
86
+
87
+ // ✅ Import real project components (DO NOT mock these)
88
+ // import Loading from '@/app/components/base/loading'
89
+ // import { ChildComponent } from './child-component'
90
+
91
+ // ✅ Mock external dependencies only
92
+ vi.mock('@/service/api')
93
+ vi.mock('next/navigation', () => ({
94
+ useRouter: () => ({ push: vi.fn() }),
95
+ usePathname: () => '/test',
96
+ }))
97
+
98
+ // Shared state for mocks (if needed)
99
+ let mockSharedState = false
100
+
101
+ describe('ComponentName', () => {
102
+ beforeEach(() => {
103
+ vi.clearAllMocks() // ✅ Reset mocks BEFORE each test
104
+ mockSharedState = false // ✅ Reset shared state
105
+ })
106
+
107
+ // Rendering tests (REQUIRED)
108
+ describe('Rendering', () => {
109
+ it('should render without crashing', () => {
110
+ // Arrange
111
+ const props = { title: 'Test' }
112
+
113
+ // Act
114
+ render(<Component {...props} />)
115
+
116
+ // Assert
117
+ expect(screen.getByText('Test')).toBeInTheDocument()
118
+ })
119
+ })
120
+
121
+ // Props tests (REQUIRED)
122
+ describe('Props', () => {
123
+ it('should apply custom className', () => {
124
+ render(<Component className="custom" />)
125
+ expect(screen.getByRole('button')).toHaveClass('custom')
126
+ })
127
+ })
128
+
129
+ // User Interactions
130
+ describe('User Interactions', () => {
131
+ it('should handle click events', () => {
132
+ const handleClick = vi.fn()
133
+ render(<Component onClick={handleClick} />)
134
+
135
+ fireEvent.click(screen.getByRole('button'))
136
+
137
+ expect(handleClick).toHaveBeenCalledTimes(1)
138
+ })
139
+ })
140
+
141
+ // Edge Cases (REQUIRED)
142
+ describe('Edge Cases', () => {
143
+ it('should handle null data', () => {
144
+ render(<Component data={null} />)
145
+ expect(screen.getByText(/no data/i)).toBeInTheDocument()
146
+ })
147
+
148
+ it('should handle empty array', () => {
149
+ render(<Component items={[]} />)
150
+ expect(screen.getByText(/empty/i)).toBeInTheDocument()
151
+ })
152
+ })
153
+ })
154
+ ```
155
+
156
+ ## Testing Workflow (CRITICAL)
157
+
158
+ ### ⚠️ Incremental Approach Required
159
+
160
+ **NEVER generate all test files at once.** For complex components or multi-file directories:
161
+
162
+ 1. **Analyze & Plan**: List all files, order by complexity (simple → complex)
163
+ 1. **Process ONE at a time**: Write test → Run test → Fix if needed → Next
164
+ 1. **Verify before proceeding**: Do NOT continue to next file until current passes
165
+
166
+ ```
167
+ For each file:
168
+ ┌────────────────────────────────────────────────┐
169
+ │ 1. Write test │
170
+ │ 2. Run: npm run test-unit <file>.spec.tsx │
171
+ │ 3. PASS? → Mark complete, next file │
172
+ │ FAIL? → Fix first, then continue │
173
+ └────────────────────────────────────────────────┘
174
+ ```
175
+
176
+ ### Complexity-Based Order
177
+
178
+ Process in this order for multi-file testing:
179
+
180
+ 1. 🟢 Utility functions (simplest)
181
+ 2. 🟢 Custom hooks
182
+ 3. 🟡 Simple components (presentational)
183
+ 4. 🟡 Medium components (state, effects)
184
+ 5. 🔴 Complex components (API, routing)
185
+ 6. 🔴 Integration tests (index files - last)
186
+
187
+ ### When to Refactor First
188
+
189
+ - **Medium Complexity**: Break into smaller pieces before testing
190
+ - **500+ lines**: Consider splitting before testing
191
+ - **Many dependencies**: Extract logic into hooks first
192
+
193
+ ## Testing Strategy
194
+
195
+ ### Path-Level Testing (Directory Testing)
196
+
197
+ When assigned to test a directory/path, test **ALL content** within that path:
198
+
199
+ - Test all components, hooks, utilities in the directory (not just `index` file)
200
+ - Use incremental approach: one file at a time, verify each before proceeding
201
+ - Goal: 100% coverage of ALL files in the directory
202
+
203
+ ### Integration Testing First
204
+
205
+ **Prefer integration testing** when writing tests for a directory:
206
+
207
+ - ✅ **Import real project components** directly (including base components and siblings)
208
+ - ✅ **Only mock**: API services (`@/service/*`), `next/navigation`, complex context providers
209
+ - ❌ **DO NOT mock** base components (`@/app/components/base/*`)
210
+ - ❌ **DO NOT mock** sibling/child components in the same directory
211
+
212
+ ## Core Principles
213
+
214
+ ### 1. AAA Pattern (Arrange-Act-Assert)
215
+
216
+ Every test should clearly separate:
217
+
218
+ - **Arrange**: Setup test data and render component
219
+ - **Act**: Perform user actions
220
+ - **Assert**: Verify expected outcomes
221
+
222
+ ### 2. Black-Box Testing
223
+
224
+ - Test observable behavior, not implementation details
225
+ - Use semantic queries (getByRole, getByLabelText)
226
+ - Avoid testing internal state directly
227
+ - **Prefer pattern matching over hardcoded strings** in assertions:
228
+
229
+ ```typescript
230
+ // ❌ Avoid: hardcoded text assertions
231
+ expect(screen.getByText('Loading...')).toBeInTheDocument()
232
+
233
+ // ✅ Better: role-based queries
234
+ expect(screen.getByRole('status')).toBeInTheDocument()
235
+
236
+ // ✅ Better: pattern matching
237
+ expect(screen.getByText(/loading/i)).toBeInTheDocument()
238
+ ```
239
+
240
+ ### 3. Single Behavior Per Test
241
+
242
+ Each test verifies ONE user-observable behavior:
243
+
244
+ ```typescript
245
+ // ✅ Good: One behavior
246
+ it('should disable button when loading', () => {
247
+ render(<Button loading />)
248
+ expect(screen.getByRole('button')).toBeDisabled()
249
+ })
250
+
251
+ // ❌ Bad: Multiple behaviors
252
+ it('should handle loading state', () => {
253
+ render(<Button loading />)
254
+ expect(screen.getByRole('button')).toBeDisabled()
255
+ expect(screen.getByText('Loading...')).toBeInTheDocument()
256
+ expect(screen.getByRole('button')).toHaveClass('loading')
257
+ })
258
+ ```
259
+
260
+ ### 4. Semantic Naming
261
+
262
+ Use `should <behavior> when <condition>`:
263
+
264
+ ```typescript
265
+ it('should show error message when validation fails')
266
+ it('should call onSubmit when form is valid')
267
+ it('should disable input when isReadOnly is true')
268
+ ```
269
+
270
+ ## Required Test Scenarios
271
+
272
+ ### Always Required (All Components)
273
+
274
+ 1. **Rendering**: Component renders without crashing
275
+ 1. **Props**: Required props, optional props, default values
276
+ 1. **Edge Cases**: null, undefined, empty values, boundary conditions
277
+
278
+ ### Conditional (When Present)
279
+
280
+ | Feature | Test Focus |
281
+ | ----------------------- | ----------------------------------------- |
282
+ | `useState` | Initial state, transitions, cleanup |
283
+ | `useEffect` | Execution, dependencies, cleanup |
284
+ | Event handlers | All onClick, onChange, onSubmit, keyboard |
285
+ | API calls | Loading, success, error states |
286
+ | Routing | Navigation, params, query strings |
287
+ | `useCallback`/`useMemo` | Referential equality |
288
+ | Context | Provider values, consumer behavior |
289
+ | Forms | Validation, submission, error display |
290
+
291
+ ## Coverage Goals (Per File)
292
+
293
+ For each test file generated, aim for:
294
+
295
+ - ✅ **100%** function coverage
296
+ - ✅ **100%** statement coverage
297
+ - ✅ **>95%** branch coverage
298
+ - ✅ **>95%** line coverage
299
+
300
+ > **Note**: For multi-file directories, process one file at a time with full coverage each. See `references/workflow.md`.
301
+
302
+ ## Detailed Guides
303
+
304
+ For more detailed information, refer to:
305
+
306
+ - `references/workflow.md` - **Incremental testing workflow** (MUST READ for multi-file testing)
307
+ - `references/mocking.md` - Mock patterns and best practices
308
+ - `references/async-testing.md` - Async operations and API calls
309
+ - `references/common-patterns.md` - Frequently used testing patterns
310
+ - `references/checklist.md` - Test generation checklist and validation steps
311
+
312
+ ### Project Configuration
313
+
314
+ - `vitest.config.ts` - Vitest configuration
315
+ - `vitest.setup.ts` - Test environment setup
316
+ - Modules are not mocked automatically. Global mocks live in `vitest.setup.ts` (for example `react-i18next`, `next/image`); mock other modules like `ky` or `mime` locally in test files.
@@ -0,0 +1,293 @@
1
+ /**
2
+ * Test Template for React Components
3
+ *
4
+ * WHY THIS STRUCTURE?
5
+ * - Organized sections make tests easy to navigate and maintain
6
+ * - Mocks at top ensure consistent test isolation
7
+ * - Factory functions reduce duplication and improve readability
8
+ * - describe blocks group related scenarios for better debugging
9
+ *
10
+ * INSTRUCTIONS:
11
+ * 1. Replace `ComponentName` with your component name
12
+ * 2. Update import path
13
+ * 3. Add/remove test sections based on component features
14
+ * 4. Follow AAA pattern: Arrange → Act → Assert
15
+ *
16
+ * Analyze complexity first to identify required test scenarios
17
+ */
18
+
19
+ import { render, screen, fireEvent, waitFor } from '@testing-library/react'
20
+ import userEvent from '@testing-library/user-event'
21
+ // import ComponentName from './index'
22
+
23
+ // ============================================================================
24
+ // Mocks
25
+ // ============================================================================
26
+ // WHY: Mocks must be hoisted to top of file (Vitest requirement).
27
+ // They run BEFORE imports, so keep them before component imports.
28
+
29
+ // i18n (automatically mocked)
30
+ // WHY: Global mock in web/vitest.setup.ts is auto-loaded by Vitest setup
31
+ // The global mock provides: useTranslation, Trans, useMixedTranslation, useGetLanguage
32
+ // No explicit mock needed for most tests
33
+ //
34
+ // Override only if custom translations are required:
35
+ // import { createReactI18nextMock } from '@/test/i18n-mock'
36
+ // vi.mock('react-i18next', () => createReactI18nextMock({
37
+ // 'my.custom.key': 'Custom Translation',
38
+ // 'button.save': 'Save',
39
+ // }))
40
+
41
+ // Router (if component uses useRouter, usePathname, useSearchParams)
42
+ // WHY: Isolates tests from Next.js routing, enables testing navigation behavior
43
+ // const mockPush = vi.fn()
44
+ // vi.mock('next/navigation', () => ({
45
+ // useRouter: () => ({ push: mockPush }),
46
+ // usePathname: () => '/test-path',
47
+ // }))
48
+
49
+ // API services (if component fetches data)
50
+ // WHY: Prevents real network calls, enables testing all states (loading/success/error)
51
+ // vi.mock('@/service/api')
52
+ // import * as api from '@/service/api'
53
+ // const mockedApi = vi.mocked(api)
54
+
55
+ // Shared mock state (for portal/dropdown components)
56
+ // WHY: Portal components like PortalToFollowElem need shared state between
57
+ // parent and child mocks to correctly simulate open/close behavior
58
+ // let mockOpenState = false
59
+
60
+ // ============================================================================
61
+ // Test Data Factories
62
+ // ============================================================================
63
+ // WHY FACTORIES?
64
+ // - Avoid hard-coded test data scattered across tests
65
+ // - Easy to create variations with overrides
66
+ // - Type-safe when using actual types from source
67
+ // - Single source of truth for default test values
68
+
69
+ // const createMockProps = (overrides = {}) => ({
70
+ // // Default props that make component render successfully
71
+ // ...overrides,
72
+ // })
73
+
74
+ // const createMockItem = (overrides = {}) => ({
75
+ // id: 'item-1',
76
+ // name: 'Test Item',
77
+ // ...overrides,
78
+ // })
79
+
80
+ // ============================================================================
81
+ // Test Helpers
82
+ // ============================================================================
83
+
84
+ // const renderComponent = (props = {}) => {
85
+ // return render(<ComponentName {...createMockProps(props)} />)
86
+ // }
87
+
88
+ // ============================================================================
89
+ // Tests
90
+ // ============================================================================
91
+
92
+ describe('ComponentName', () => {
93
+ // WHY beforeEach with clearAllMocks?
94
+ // - Ensures each test starts with clean slate
95
+ // - Prevents mock call history from leaking between tests
96
+ // - MUST be beforeEach (not afterEach) to reset BEFORE assertions like toHaveBeenCalledTimes
97
+ beforeEach(() => {
98
+ vi.clearAllMocks()
99
+ // Reset shared mock state if used (CRITICAL for portal/dropdown tests)
100
+ // mockOpenState = false
101
+ })
102
+
103
+ // --------------------------------------------------------------------------
104
+ // Rendering Tests (REQUIRED - Every component MUST have these)
105
+ // --------------------------------------------------------------------------
106
+ // WHY: Catches import errors, missing providers, and basic render issues
107
+ describe('Rendering', () => {
108
+ it('should render without crashing', () => {
109
+ // Arrange - Setup data and mocks
110
+ // const props = createMockProps()
111
+
112
+ // Act - Render the component
113
+ // render(<ComponentName {...props} />)
114
+
115
+ // Assert - Verify expected output
116
+ // Prefer getByRole for accessibility; it's what users "see"
117
+ // expect(screen.getByRole('...')).toBeInTheDocument()
118
+ })
119
+
120
+ it('should render with default props', () => {
121
+ // WHY: Verifies component works without optional props
122
+ // render(<ComponentName />)
123
+ // expect(screen.getByText('...')).toBeInTheDocument()
124
+ })
125
+ })
126
+
127
+ // --------------------------------------------------------------------------
128
+ // Props Tests (REQUIRED - Every component MUST test prop behavior)
129
+ // --------------------------------------------------------------------------
130
+ // WHY: Props are the component's API contract. Test them thoroughly.
131
+ describe('Props', () => {
132
+ it('should apply custom className', () => {
133
+ // WHY: Common pattern - components should merge custom classes
134
+ // render(<ComponentName className="custom-class" />)
135
+ // expect(screen.getByTestId('component')).toHaveClass('custom-class')
136
+ })
137
+
138
+ it('should use default values for optional props', () => {
139
+ // WHY: Verifies TypeScript defaults work at runtime
140
+ // render(<ComponentName />)
141
+ // expect(screen.getByRole('...')).toHaveAttribute('...', 'default-value')
142
+ })
143
+ })
144
+
145
+ // --------------------------------------------------------------------------
146
+ // User Interactions (if component has event handlers - on*, handle*)
147
+ // --------------------------------------------------------------------------
148
+ // WHY: Event handlers are core functionality. Test from user's perspective.
149
+ describe('User Interactions', () => {
150
+ it('should call onClick when clicked', async () => {
151
+ // WHY userEvent over fireEvent?
152
+ // - userEvent simulates real user behavior (focus, hover, then click)
153
+ // - fireEvent is lower-level, doesn't trigger all browser events
154
+ // const user = userEvent.setup()
155
+ // const handleClick = vi.fn()
156
+ // render(<ComponentName onClick={handleClick} />)
157
+ //
158
+ // await user.click(screen.getByRole('button'))
159
+ //
160
+ // expect(handleClick).toHaveBeenCalledTimes(1)
161
+ })
162
+
163
+ it('should call onChange when value changes', async () => {
164
+ // const user = userEvent.setup()
165
+ // const handleChange = vi.fn()
166
+ // render(<ComponentName onChange={handleChange} />)
167
+ //
168
+ // await user.type(screen.getByRole('textbox'), 'new value')
169
+ //
170
+ // expect(handleChange).toHaveBeenCalled()
171
+ })
172
+ })
173
+
174
+ // --------------------------------------------------------------------------
175
+ // State Management (if component uses useState/useReducer)
176
+ // --------------------------------------------------------------------------
177
+ // WHY: Test state through observable UI changes, not internal state values
178
+ describe('State Management', () => {
179
+ it('should update state on interaction', async () => {
180
+ // WHY test via UI, not state?
181
+ // - State is implementation detail; UI is what users see
182
+ // - If UI works correctly, state must be correct
183
+ // const user = userEvent.setup()
184
+ // render(<ComponentName />)
185
+ //
186
+ // // Initial state - verify what user sees
187
+ // expect(screen.getByText('Initial')).toBeInTheDocument()
188
+ //
189
+ // // Trigger state change via user action
190
+ // await user.click(screen.getByRole('button'))
191
+ //
192
+ // // New state - verify UI updated
193
+ // expect(screen.getByText('Updated')).toBeInTheDocument()
194
+ })
195
+ })
196
+
197
+ // --------------------------------------------------------------------------
198
+ // Async Operations (if component fetches data - useQuery, fetch)
199
+ // --------------------------------------------------------------------------
200
+ // WHY: Async operations have 3 states users experience: loading, success, error
201
+ describe('Async Operations', () => {
202
+ it('should show loading state', () => {
203
+ // WHY never-resolving promise?
204
+ // - Keeps component in loading state for assertion
205
+ // - Alternative: use fake timers
206
+ // mockedApi.fetchData.mockImplementation(() => new Promise(() => {}))
207
+ // render(<ComponentName />)
208
+ //
209
+ // expect(screen.getByText(/loading/i)).toBeInTheDocument()
210
+ })
211
+
212
+ it('should show data on success', async () => {
213
+ // WHY waitFor?
214
+ // - Component updates asynchronously after fetch resolves
215
+ // - waitFor retries assertion until it passes or times out
216
+ // mockedApi.fetchData.mockResolvedValue({ items: ['Item 1'] })
217
+ // render(<ComponentName />)
218
+ //
219
+ // await waitFor(() => {
220
+ // expect(screen.getByText('Item 1')).toBeInTheDocument()
221
+ // })
222
+ })
223
+
224
+ it('should show error on failure', async () => {
225
+ // mockedApi.fetchData.mockRejectedValue(new Error('Network error'))
226
+ // render(<ComponentName />)
227
+ //
228
+ // await waitFor(() => {
229
+ // expect(screen.getByText(/error/i)).toBeInTheDocument()
230
+ // })
231
+ })
232
+ })
233
+
234
+ // --------------------------------------------------------------------------
235
+ // Edge Cases (REQUIRED - Every component MUST handle edge cases)
236
+ // --------------------------------------------------------------------------
237
+ // WHY: Real-world data is messy. Components must handle:
238
+ // - Null/undefined from API failures or optional fields
239
+ // - Empty arrays/strings from user clearing data
240
+ // - Boundary values (0, MAX_INT, special characters)
241
+ describe('Edge Cases', () => {
242
+ it('should handle null value', () => {
243
+ // WHY test null specifically?
244
+ // - API might return null for missing data
245
+ // - Prevents "Cannot read property of null" in production
246
+ // render(<ComponentName value={null} />)
247
+ // expect(screen.getByText(/no data/i)).toBeInTheDocument()
248
+ })
249
+
250
+ it('should handle undefined value', () => {
251
+ // WHY test undefined separately from null?
252
+ // - TypeScript treats them differently
253
+ // - Optional props are undefined, not null
254
+ // render(<ComponentName value={undefined} />)
255
+ // expect(screen.getByText(/no data/i)).toBeInTheDocument()
256
+ })
257
+
258
+ it('should handle empty array', () => {
259
+ // WHY: Empty state often needs special UI (e.g., "No items yet")
260
+ // render(<ComponentName items={[]} />)
261
+ // expect(screen.getByText(/empty/i)).toBeInTheDocument()
262
+ })
263
+
264
+ it('should handle empty string', () => {
265
+ // WHY: Empty strings are truthy in JS but visually empty
266
+ // render(<ComponentName text="" />)
267
+ // expect(screen.getByText(/placeholder/i)).toBeInTheDocument()
268
+ })
269
+ })
270
+
271
+ // --------------------------------------------------------------------------
272
+ // Accessibility (optional but recommended)
273
+ // --------------------------------------------------------------------------
274
+ // WHY: Accessibility is important for users
275
+ describe('Accessibility', () => {
276
+ it('should have accessible name', () => {
277
+ // WHY getByRole with name?
278
+ // - Tests that screen readers can identify the element
279
+ // - Enforces proper labeling practices
280
+ // render(<ComponentName label="Test Label" />)
281
+ // expect(screen.getByRole('button', { name: /test label/i })).toBeInTheDocument()
282
+ })
283
+
284
+ it('should support keyboard navigation', async () => {
285
+ // WHY: Some users can't use a mouse
286
+ // const user = userEvent.setup()
287
+ // render(<ComponentName />)
288
+ //
289
+ // await user.tab()
290
+ // expect(screen.getByRole('button')).toHaveFocus()
291
+ })
292
+ })
293
+ })