ima-claude 2.9.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 (182) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +463 -0
  3. package/dist/cli.js +1064 -0
  4. package/package.json +49 -0
  5. package/platforms/claude/adapter.ts +115 -0
  6. package/platforms/junie/adapter.ts +254 -0
  7. package/platforms/junie/agents-template.md +113 -0
  8. package/platforms/junie/hook-translations.md +84 -0
  9. package/platforms/shared/detector.ts +27 -0
  10. package/platforms/shared/installer.ts +202 -0
  11. package/platforms/shared/types.ts +78 -0
  12. package/plugins/ima-claude/.claude-plugin/plugin.json +25 -0
  13. package/plugins/ima-claude/agents/explorer.md +30 -0
  14. package/plugins/ima-claude/agents/implementer.md +30 -0
  15. package/plugins/ima-claude/agents/memory.md +42 -0
  16. package/plugins/ima-claude/agents/reviewer.md +53 -0
  17. package/plugins/ima-claude/agents/tester.md +33 -0
  18. package/plugins/ima-claude/agents/wp-developer.md +46 -0
  19. package/plugins/ima-claude/hooks/README.md +145 -0
  20. package/plugins/ima-claude/hooks/atlassian_prereqs.py +112 -0
  21. package/plugins/ima-claude/hooks/block_sed_edits.py +59 -0
  22. package/plugins/ima-claude/hooks/bootstrap.sh +90 -0
  23. package/plugins/ima-claude/hooks/bootstrap_utility_check.py +94 -0
  24. package/plugins/ima-claude/hooks/composer_autoload_check.py +70 -0
  25. package/plugins/ima-claude/hooks/docs_organization.py +104 -0
  26. package/plugins/ima-claude/hooks/enforce_rg_over_grep.py +56 -0
  27. package/plugins/ima-claude/hooks/fp_utility_check.py +90 -0
  28. package/plugins/ima-claude/hooks/hook_logger.py +69 -0
  29. package/plugins/ima-claude/hooks/hooks.json +239 -0
  30. package/plugins/ima-claude/hooks/jira_issue_fetch.py +79 -0
  31. package/plugins/ima-claude/hooks/jquery_in_wordpress.py +92 -0
  32. package/plugins/ima-claude/hooks/memory_bootstrap.py +79 -0
  33. package/plugins/ima-claude/hooks/memory_store_reminder.py +75 -0
  34. package/plugins/ima-claude/hooks/prompt_coach.py +125 -0
  35. package/plugins/ima-claude/hooks/prompt_coach_digest.md +48 -0
  36. package/plugins/ima-claude/hooks/prompt_coach_system.md +30 -0
  37. package/plugins/ima-claude/hooks/sequential_thinking_check.py +81 -0
  38. package/plugins/ima-claude/hooks/serena_over_grep.py +96 -0
  39. package/plugins/ima-claude/hooks/serena_over_read.py +66 -0
  40. package/plugins/ima-claude/hooks/serena_project_check.py +133 -0
  41. package/plugins/ima-claude/hooks/sql_injection_check.py +73 -0
  42. package/plugins/ima-claude/hooks/task_master_after_plan.py +31 -0
  43. package/plugins/ima-claude/hooks/task_master_before_impl.py +93 -0
  44. package/plugins/ima-claude/hooks/tavily_extract_advanced.py +48 -0
  45. package/plugins/ima-claude/hooks/vestige_before_external.py +86 -0
  46. package/plugins/ima-claude/hooks/webfetch_to_tavily.py +42 -0
  47. package/plugins/ima-claude/hooks/websearch_to_tavily.py +41 -0
  48. package/plugins/ima-claude/hooks/wp_security_check.py +150 -0
  49. package/plugins/ima-claude/personalities/README.md +45 -0
  50. package/plugins/ima-claude/personalities/enable-40k.md +69 -0
  51. package/plugins/ima-claude/personalities/enable-templars.md +69 -0
  52. package/plugins/ima-claude/skills/.research-summary.md +340 -0
  53. package/plugins/ima-claude/skills/architect/SKILL.md +304 -0
  54. package/plugins/ima-claude/skills/compound-bridge/SKILL.md +200 -0
  55. package/plugins/ima-claude/skills/discourse/SKILL.md +440 -0
  56. package/plugins/ima-claude/skills/discourse-admin/SKILL.md +192 -0
  57. package/plugins/ima-claude/skills/discourse-admin/references/api-endpoints.md +441 -0
  58. package/plugins/ima-claude/skills/discourse-admin/references/gotchas.md +107 -0
  59. package/plugins/ima-claude/skills/discourse-admin/references/staging-defaults.md +98 -0
  60. package/plugins/ima-claude/skills/discourse-admin/scripts/discourse-admin.py +319 -0
  61. package/plugins/ima-claude/skills/docs-organize/SKILL.md +254 -0
  62. package/plugins/ima-claude/skills/docs-organize/templates/active-README.md +50 -0
  63. package/plugins/ima-claude/skills/docs-organize/templates/archive-README.md +57 -0
  64. package/plugins/ima-claude/skills/docs-organize/templates/docs-README.md +43 -0
  65. package/plugins/ima-claude/skills/docs-organize/templates/phase-archive-README.md +83 -0
  66. package/plugins/ima-claude/skills/docs-organize/templates/section-README.md +48 -0
  67. package/plugins/ima-claude/skills/docs-organize/templates/transient-README.md +79 -0
  68. package/plugins/ima-claude/skills/docs-organize/templates/transient-gitignore +9 -0
  69. package/plugins/ima-claude/skills/ember-discourse/SKILL.md +496 -0
  70. package/plugins/ima-claude/skills/functional-programmer/SKILL.md +258 -0
  71. package/plugins/ima-claude/skills/ima-bootstrap/SKILL.md +278 -0
  72. package/plugins/ima-claude/skills/ima-bootstrap/references/bootstrap-patterns.md +356 -0
  73. package/plugins/ima-claude/skills/ima-bootstrap/references/ima-brand.md +273 -0
  74. package/plugins/ima-claude/skills/ima-bootstrap/references/theme-integration.md +212 -0
  75. package/plugins/ima-claude/skills/ima-brand/SKILL.md +108 -0
  76. package/plugins/ima-claude/skills/ima-brand/references/brand-identity.md +140 -0
  77. package/plugins/ima-claude/skills/ima-brand/references/digital-standards.md +180 -0
  78. package/plugins/ima-claude/skills/ima-brand/references/visual-system.md +173 -0
  79. package/plugins/ima-claude/skills/ima-forms-expert/SKILL.md +175 -0
  80. package/plugins/ima-claude/skills/ima-forms-expert/references/container-components.md +154 -0
  81. package/plugins/ima-claude/skills/ima-forms-expert/references/examples.md +328 -0
  82. package/plugins/ima-claude/skills/ima-forms-expert/references/field-components.md +298 -0
  83. package/plugins/ima-claude/skills/ima-forms-expert/references/form-factory.md +193 -0
  84. package/plugins/ima-claude/skills/ima-forms-expert/references/quick-reference.md +153 -0
  85. package/plugins/ima-claude/skills/ima-forms-expert/references/validation-engine.md +336 -0
  86. package/plugins/ima-claude/skills/jira-checkpoint/SKILL.md +178 -0
  87. package/plugins/ima-claude/skills/jquery/SKILL.md +413 -0
  88. package/plugins/ima-claude/skills/js-fp/SKILL.md +463 -0
  89. package/plugins/ima-claude/skills/js-fp/core-principles.md +487 -0
  90. package/plugins/ima-claude/skills/js-fp/examples/pure-functions.js +260 -0
  91. package/plugins/ima-claude/skills/js-fp/examples/tests/pure-functions.test.js +262 -0
  92. package/plugins/ima-claude/skills/js-fp/references/anti-patterns.md +120 -0
  93. package/plugins/ima-claude/skills/js-fp/references/performance-patterns.md +116 -0
  94. package/plugins/ima-claude/skills/js-fp/references/testing-patterns.md +134 -0
  95. package/plugins/ima-claude/skills/js-fp-api/SKILL.md +280 -0
  96. package/plugins/ima-claude/skills/js-fp-api/examples/crud-endpoint.js +258 -0
  97. package/plugins/ima-claude/skills/js-fp-api/references/middleware-patterns.md +134 -0
  98. package/plugins/ima-claude/skills/js-fp-api/references/security-sql.md +110 -0
  99. package/plugins/ima-claude/skills/js-fp-api/references/validation-patterns.md +165 -0
  100. package/plugins/ima-claude/skills/js-fp-react/SKILL.md +447 -0
  101. package/plugins/ima-claude/skills/js-fp-react/examples/ProductCard.tsx +65 -0
  102. package/plugins/ima-claude/skills/js-fp-react/references/hooks-advanced.md +136 -0
  103. package/plugins/ima-claude/skills/js-fp-react/references/performance-patterns.md +175 -0
  104. package/plugins/ima-claude/skills/js-fp-vue/SKILL.md +322 -0
  105. package/plugins/ima-claude/skills/js-fp-vue/references/complete-examples.md +397 -0
  106. package/plugins/ima-claude/skills/js-fp-vue/references/composables-advanced.md +282 -0
  107. package/plugins/ima-claude/skills/js-fp-vue/references/reactivity-patterns.md +348 -0
  108. package/plugins/ima-claude/skills/js-fp-vue/references/testing.md +314 -0
  109. package/plugins/ima-claude/skills/js-fp-wordpress/SKILL.md +301 -0
  110. package/plugins/ima-claude/skills/js-fp-wordpress/references/ajax-patterns.md +192 -0
  111. package/plugins/ima-claude/skills/js-fp-wordpress/references/event-patterns.md +136 -0
  112. package/plugins/ima-claude/skills/js-fp-wordpress/references/wp-integration.md +248 -0
  113. package/plugins/ima-claude/skills/livecanvas/SKILL.md +209 -0
  114. package/plugins/ima-claude/skills/livecanvas/references/livecanvas-features.md +311 -0
  115. package/plugins/ima-claude/skills/livecanvas/references/loops-and-logic.md +730 -0
  116. package/plugins/ima-claude/skills/livecanvas/references/picostrap.md +227 -0
  117. package/plugins/ima-claude/skills/mcp-atlassian/SKILL.md +339 -0
  118. package/plugins/ima-claude/skills/mcp-context7/SKILL.md +109 -0
  119. package/plugins/ima-claude/skills/mcp-memory/SKILL.md +182 -0
  120. package/plugins/ima-claude/skills/mcp-qdrant/SKILL.md +233 -0
  121. package/plugins/ima-claude/skills/mcp-sequential/SKILL.md +149 -0
  122. package/plugins/ima-claude/skills/mcp-serena/SKILL.md +174 -0
  123. package/plugins/ima-claude/skills/mcp-tavily/SKILL.md +118 -0
  124. package/plugins/ima-claude/skills/mcp-vestige/SKILL.md +259 -0
  125. package/plugins/ima-claude/skills/php-authnet/SKILL.md +275 -0
  126. package/plugins/ima-claude/skills/php-authnet/references/api-reference.md +624 -0
  127. package/plugins/ima-claude/skills/php-authnet/references/sandbox-testing.md +424 -0
  128. package/plugins/ima-claude/skills/php-fp/SKILL.md +333 -0
  129. package/plugins/ima-claude/skills/php-fp/examples/pure-functions.php +403 -0
  130. package/plugins/ima-claude/skills/php-fp/examples/tests/PureFunctionsTest.php +515 -0
  131. package/plugins/ima-claude/skills/php-fp/references/core-principles.md +277 -0
  132. package/plugins/ima-claude/skills/php-fp/references/testing-patterns.md +374 -0
  133. package/plugins/ima-claude/skills/php-fp-wordpress/SKILL.md +216 -0
  134. package/plugins/ima-claude/skills/php-fp-wordpress/references/fp-patterns.md +275 -0
  135. package/plugins/ima-claude/skills/php-fp-wordpress/references/plugin-architecture.md +295 -0
  136. package/plugins/ima-claude/skills/php-fp-wordpress/references/security-examples.md +203 -0
  137. package/plugins/ima-claude/skills/php-fp-wordpress/references/testing-strategy.md +259 -0
  138. package/plugins/ima-claude/skills/phpunit-wp/SKILL.md +716 -0
  139. package/plugins/ima-claude/skills/playwright/SKILL.md +434 -0
  140. package/plugins/ima-claude/skills/playwright/references/accessibility-testing.md +153 -0
  141. package/plugins/ima-claude/skills/playwright/references/ci-cd.md +268 -0
  142. package/plugins/ima-claude/skills/playwright/references/network-mocking.md +270 -0
  143. package/plugins/ima-claude/skills/playwright/references/visual-regression.md +215 -0
  144. package/plugins/ima-claude/skills/py-fp/SKILL.md +663 -0
  145. package/plugins/ima-claude/skills/py-fp/examples/pure-functions.py +185 -0
  146. package/plugins/ima-claude/skills/py-fp/examples/tests/test_pure_functions.py +244 -0
  147. package/plugins/ima-claude/skills/py-fp/references/core-principles.md +381 -0
  148. package/plugins/ima-claude/skills/py-fp/references/testing-patterns.md +283 -0
  149. package/plugins/ima-claude/skills/quasar-fp/SKILL.md +327 -0
  150. package/plugins/ima-claude/skills/quasar-fp/metadata.json +85 -0
  151. package/plugins/ima-claude/skills/quasar-fp/references/component-patterns.md +257 -0
  152. package/plugins/ima-claude/skills/quasar-fp/references/theme-integration.md +233 -0
  153. package/plugins/ima-claude/skills/quasar-fp/references/utility-classes.md +237 -0
  154. package/plugins/ima-claude/skills/quickstart/SKILL.md +129 -0
  155. package/plugins/ima-claude/skills/rails/SKILL.md +359 -0
  156. package/plugins/ima-claude/skills/resume-session/SKILL.md +68 -0
  157. package/plugins/ima-claude/skills/rg/SKILL.md +205 -0
  158. package/plugins/ima-claude/skills/ruby-fp/SKILL.md +336 -0
  159. package/plugins/ima-claude/skills/save-session/SKILL.md +81 -0
  160. package/plugins/ima-claude/skills/scorecard/SKILL.md +96 -0
  161. package/plugins/ima-claude/skills/skill-analyzer/SKILL.md +127 -0
  162. package/plugins/ima-claude/skills/skill-analyzer/references/advanced-checklist.md +44 -0
  163. package/plugins/ima-claude/skills/skill-analyzer/references/core-checklist.md +60 -0
  164. package/plugins/ima-claude/skills/skill-analyzer/scripts/analyze_skill.py +418 -0
  165. package/plugins/ima-claude/skills/skill-creator/LICENSE.txt +202 -0
  166. package/plugins/ima-claude/skills/skill-creator/SKILL.md +343 -0
  167. package/plugins/ima-claude/skills/skill-creator/references/output-patterns.md +82 -0
  168. package/plugins/ima-claude/skills/skill-creator/references/workflows.md +28 -0
  169. package/plugins/ima-claude/skills/skill-creator/scripts/init_skill.py +303 -0
  170. package/plugins/ima-claude/skills/skill-creator/scripts/package_skill.py +110 -0
  171. package/plugins/ima-claude/skills/skill-creator/scripts/quick_validate.py +103 -0
  172. package/plugins/ima-claude/skills/task-master/SKILL.md +51 -0
  173. package/plugins/ima-claude/skills/task-planner/SKILL.md +228 -0
  174. package/plugins/ima-claude/skills/task-runner/SKILL.md +192 -0
  175. package/plugins/ima-claude/skills/unit-testing/SKILL.md +198 -0
  176. package/plugins/ima-claude/skills/unit-testing/references/mock-patterns.md +181 -0
  177. package/plugins/ima-claude/skills/unit-testing/references/tdd-workflow.md +177 -0
  178. package/plugins/ima-claude/skills/unit-testing/references/test-strategy.md +126 -0
  179. package/plugins/ima-claude/skills/wp-local/SKILL.md +246 -0
  180. package/plugins/ima-claude/skills/wp-local/references/configuration.md +198 -0
  181. package/plugins/ima-claude/skills/wp-local/references/wp-cli-reference.md +406 -0
  182. package/plugins/ima-claude/skills/wp-local/scripts/wp-local.sh +61 -0
@@ -0,0 +1,348 @@
1
+ # Reactivity Patterns
2
+
3
+ Deep reactivity patterns, performance optimization, and memory management for Vue.js.
4
+
5
+ ## Table of Contents
6
+
7
+ 1. [Reactive vs Ref](#reactive-vs-ref)
8
+ 2. [Computed Optimization](#computed-optimization)
9
+ 3. [Watch Anti-Patterns](#watch-anti-patterns)
10
+ 4. [ShallowRef for Performance](#shallowref-for-performance)
11
+ 5. [Memory Management](#memory-management)
12
+ 6. [Common Anti-Patterns](#common-anti-patterns)
13
+
14
+ ---
15
+
16
+ ## Reactive vs Ref
17
+
18
+ ### When to Use ref()
19
+
20
+ ```typescript
21
+ import { ref, computed } from 'vue'
22
+
23
+ // Primitives - always ref
24
+ const count = ref(0)
25
+ const name = ref('')
26
+ const isActive = ref(false)
27
+
28
+ // Simple objects - prefer ref for consistency
29
+ const user = ref<UserData>({ id: '1', name: 'Alice', email: 'alice@test.com' })
30
+
31
+ // Access with .value in script
32
+ console.log(user.value.name)
33
+
34
+ // Template auto-unwraps
35
+ // <p>{{ user.name }}</p>
36
+ ```
37
+
38
+ ### When to Use reactive()
39
+
40
+ ```typescript
41
+ import { reactive } from 'vue'
42
+
43
+ // Complex nested objects with many properties
44
+ const state = reactive({
45
+ users: [] as UserData[],
46
+ filters: {
47
+ search: '',
48
+ minAge: 0,
49
+ status: 'active' as 'active' | 'inactive'
50
+ },
51
+ pagination: {
52
+ page: 1,
53
+ perPage: 20,
54
+ total: 0
55
+ }
56
+ })
57
+
58
+ // No .value needed
59
+ console.log(state.filters.search)
60
+ ```
61
+
62
+ ### Decision Guide
63
+
64
+ | Scenario | Use |
65
+ |----------|-----|
66
+ | Primitive values | `ref()` |
67
+ | Single object | `ref()` |
68
+ | Form state with many fields | `reactive()` |
69
+ | Complex nested state | `reactive()` |
70
+ | Needs to be replaced entirely | `ref()` |
71
+ | Need to pass by reference | `ref()` |
72
+
73
+ ---
74
+
75
+ ## Computed Optimization
76
+
77
+ ### Always Computed for Derived State
78
+
79
+ ```typescript
80
+ import { ref, computed } from 'vue'
81
+
82
+ const users = ref<UserData[]>([])
83
+ const searchTerm = ref('')
84
+
85
+ // Good: computed automatically caches
86
+ const filteredUsers = computed(() =>
87
+ users.value.filter(u =>
88
+ u.name.toLowerCase().includes(searchTerm.value.toLowerCase())
89
+ )
90
+ )
91
+
92
+ // Good: computed chain
93
+ const activeFilteredUsers = computed(() =>
94
+ filteredUsers.value.filter(u => u.isActive)
95
+ )
96
+
97
+ const userCount = computed(() => activeFilteredUsers.value.length)
98
+ ```
99
+
100
+ ### Avoid Expensive Recomputation
101
+
102
+ ```typescript
103
+ // Bad: recomputes on every access
104
+ const getExpensiveData = () => {
105
+ return users.value.map(u => ({
106
+ ...u,
107
+ computed: expensiveCalculation(u)
108
+ }))
109
+ }
110
+
111
+ // Good: computed caches until dependencies change
112
+ const expensiveData = computed(() =>
113
+ users.value.map(u => ({
114
+ ...u,
115
+ computed: expensiveCalculation(u)
116
+ }))
117
+ )
118
+ ```
119
+
120
+ ### Computed with Getters and Setters
121
+
122
+ ```typescript
123
+ const firstName = ref('John')
124
+ const lastName = ref('Doe')
125
+
126
+ const fullName = computed({
127
+ get: () => `${firstName.value} ${lastName.value}`,
128
+ set: (value: string) => {
129
+ const [first, ...rest] = value.split(' ')
130
+ firstName.value = first
131
+ lastName.value = rest.join(' ')
132
+ }
133
+ })
134
+
135
+ // Usage
136
+ fullName.value = 'Jane Smith' // Sets firstName and lastName
137
+ ```
138
+
139
+ ---
140
+
141
+ ## Watch Anti-Patterns
142
+
143
+ ### Bad: Watch for Derived State
144
+
145
+ ```typescript
146
+ // Bad: watch to update derived state
147
+ const fullName = ref('')
148
+ watch([firstName, lastName], () => {
149
+ fullName.value = `${firstName.value} ${lastName.value}`
150
+ })
151
+
152
+ // Good: computed for derived state
153
+ const fullName = computed(() => `${firstName.value} ${lastName.value}`)
154
+ ```
155
+
156
+ ### Good: Watch for Side Effects Only
157
+
158
+ ```typescript
159
+ import { watch, watchEffect } from 'vue'
160
+
161
+ // Good: watch for API calls
162
+ watch(userId, async (newId) => {
163
+ if (newId) {
164
+ user.value = await fetchUser(newId)
165
+ }
166
+ })
167
+
168
+ // Good: watchEffect for multiple dependencies
169
+ watchEffect(() => {
170
+ if (user.value && isActive.value) {
171
+ analytics.track('user_active', { userId: user.value.id })
172
+ }
173
+ })
174
+
175
+ // Good: watch with cleanup
176
+ watch(source, (newValue, oldValue, onCleanup) => {
177
+ const controller = new AbortController()
178
+ fetchData(newValue, { signal: controller.signal })
179
+
180
+ onCleanup(() => controller.abort())
181
+ })
182
+ ```
183
+
184
+ ---
185
+
186
+ ## ShallowRef for Performance
187
+
188
+ Use shallowRef when you have large objects that are replaced, not mutated.
189
+
190
+ ```typescript
191
+ import { shallowRef, triggerRef } from 'vue'
192
+
193
+ // Large dataset that gets replaced entirely
194
+ const largeDataset = shallowRef<DataItem[]>([])
195
+
196
+ // Replacing works fine
197
+ const refreshData = async () => {
198
+ largeDataset.value = await fetchLargeDataset()
199
+ }
200
+
201
+ // Mutation requires manual trigger
202
+ const updateItem = (index: number, updates: Partial<DataItem>) => {
203
+ largeDataset.value[index] = { ...largeDataset.value[index], ...updates }
204
+ triggerRef(largeDataset) // Manual trigger needed
205
+ }
206
+ ```
207
+
208
+ ### When to Use shallowRef
209
+
210
+ | Scenario | Use |
211
+ |----------|-----|
212
+ | Large arrays (1000+ items) | `shallowRef()` |
213
+ | Data replaced, not mutated | `shallowRef()` |
214
+ | Deeply nested objects | `shallowRef()` or `shallowReactive()` |
215
+ | Small objects with mutations | `ref()` |
216
+
217
+ ---
218
+
219
+ ## Memory Management
220
+
221
+ ### Cleanup Subscriptions
222
+
223
+ ```typescript
224
+ import { onUnmounted } from 'vue'
225
+
226
+ export const useEventListener = (
227
+ target: EventTarget,
228
+ event: string,
229
+ handler: EventListener
230
+ ) => {
231
+ target.addEventListener(event, handler)
232
+
233
+ onUnmounted(() => {
234
+ target.removeEventListener(event, handler)
235
+ })
236
+ }
237
+ ```
238
+
239
+ ### Avoid Memory Leaks with watchEffect
240
+
241
+ ```typescript
242
+ import { watchEffect } from 'vue'
243
+
244
+ // watchEffect auto-stops when component unmounts
245
+ // But manual stop is available if needed
246
+ const stop = watchEffect(() => {
247
+ // Side effect
248
+ })
249
+
250
+ // Manual stop if needed
251
+ // stop()
252
+ ```
253
+
254
+ ### Cleanup in Async Operations
255
+
256
+ ```typescript
257
+ import { ref, onUnmounted } from 'vue'
258
+
259
+ export const useAsyncData = <T>(fetcher: () => Promise<T>) => {
260
+ const data = ref<T | null>(null)
261
+ const loading = ref(false)
262
+ let cancelled = false
263
+
264
+ const fetch = async () => {
265
+ cancelled = false
266
+ loading.value = true
267
+ try {
268
+ const result = await fetcher()
269
+ if (!cancelled) {
270
+ data.value = result
271
+ }
272
+ } finally {
273
+ if (!cancelled) {
274
+ loading.value = false
275
+ }
276
+ }
277
+ }
278
+
279
+ onUnmounted(() => {
280
+ cancelled = true
281
+ })
282
+
283
+ return { data, loading, fetch }
284
+ }
285
+ ```
286
+
287
+ ---
288
+
289
+ ## Common Anti-Patterns
290
+
291
+ ### Reactive Over-Engineering
292
+
293
+ ```typescript
294
+ // Bad: Unnecessary nesting
295
+ const user = reactive({
296
+ profile: reactive({
297
+ settings: reactive({
298
+ theme: 'dark'
299
+ })
300
+ })
301
+ })
302
+
303
+ // Good: Simple structure
304
+ const userSettings = ref({ theme: 'dark', notifications: true })
305
+ const isDarkTheme = computed(() => userSettings.value.theme === 'dark')
306
+ ```
307
+
308
+ ### Ref Unwrapping Confusion
309
+
310
+ ```typescript
311
+ // Bad: Inconsistent access
312
+ const count = ref(0)
313
+ console.log(count) // Ref object, not value
314
+
315
+ // Good: Always use .value in script
316
+ console.log(count.value)
317
+
318
+ // Template auto-unwraps, no .value needed
319
+ // <p>{{ count }}</p>
320
+ ```
321
+
322
+ ### Mutating Props
323
+
324
+ ```typescript
325
+ // Bad: Mutating prop
326
+ const props = defineProps<{ user: UserData }>()
327
+ props.user.name = 'New Name' // Mutation!
328
+
329
+ // Good: Emit update
330
+ const emit = defineEmits<{ update: [user: UserData] }>()
331
+ const updateName = (name: string) => {
332
+ emit('update', { ...props.user, name })
333
+ }
334
+ ```
335
+
336
+ ---
337
+
338
+ ## Performance Checklist
339
+
340
+ Before optimizing, always measure first. Then:
341
+
342
+ 1. Use `computed()` for derived state (caching)
343
+ 2. Avoid `watch()` for derived values
344
+ 3. Use `shallowRef()` for large datasets
345
+ 4. Clean up subscriptions in `onUnmounted()`
346
+ 5. Use `v-once` for static content in templates
347
+ 6. Use `v-memo` for expensive list renders
348
+ 7. Avoid reactive objects when plain objects suffice
@@ -0,0 +1,314 @@
1
+ # Testing Vue FP Components
2
+
3
+ Testing patterns for Vue.js functional programming architecture.
4
+
5
+ ## Table of Contents
6
+
7
+ 1. [Testing Pure Composables](#testing-pure-composables)
8
+ 2. [Testing Components](#testing-components)
9
+ 3. [Testing Wrapper Components](#testing-wrapper-components)
10
+ 4. [Mocking Dependencies](#mocking-dependencies)
11
+
12
+ ---
13
+
14
+ ## Testing Pure Composables
15
+
16
+ Composables with pure logic are trivially testable.
17
+
18
+ ```typescript
19
+ // composables/__tests__/useUserLogic.test.ts
20
+ import { ref } from 'vue'
21
+ import { useUserLogic } from '../useUserLogic'
22
+
23
+ describe('useUserLogic', () => {
24
+ it('computes display data correctly', () => {
25
+ const userData = ref({
26
+ id: '1',
27
+ name: ' John ',
28
+ email: 'john@test.com'
29
+ })
30
+ const config = ref({
31
+ showEmail: true,
32
+ variant: 'compact' as const
33
+ })
34
+
35
+ const { displayData } = useUserLogic(userData, config)
36
+
37
+ expect(displayData.value).toEqual({
38
+ id: '1',
39
+ name: ' John ',
40
+ email: 'john@test.com',
41
+ displayName: 'John' // Trimmed
42
+ })
43
+ })
44
+
45
+ it('hides email when config.showEmail is false', () => {
46
+ const userData = ref({
47
+ id: '1',
48
+ name: 'John',
49
+ email: 'john@test.com'
50
+ })
51
+ const config = ref({
52
+ showEmail: false,
53
+ variant: 'compact' as const
54
+ })
55
+
56
+ const { displayData } = useUserLogic(userData, config)
57
+
58
+ expect(displayData.value.email).toBeUndefined()
59
+ })
60
+
61
+ it('generates correct CSS classes for variant', () => {
62
+ const userData = ref({ id: '1', name: 'John', email: 'john@test.com' })
63
+ const config = ref({ showEmail: true, variant: 'detailed' as const })
64
+
65
+ const { cssClasses } = useUserLogic(userData, config)
66
+
67
+ expect(cssClasses.value.card).toContain('detailed')
68
+ })
69
+
70
+ it('reacts to config changes', () => {
71
+ const userData = ref({ id: '1', name: 'John', email: 'john@test.com' })
72
+ const config = ref({ showEmail: true, variant: 'compact' as const })
73
+
74
+ const { displayData } = useUserLogic(userData, config)
75
+ expect(displayData.value.email).toBe('john@test.com')
76
+
77
+ config.value = { showEmail: false, variant: 'compact' }
78
+ expect(displayData.value.email).toBeUndefined()
79
+ })
80
+ })
81
+ ```
82
+
83
+ ---
84
+
85
+ ## Testing Components
86
+
87
+ Use Vue Test Utils for component testing.
88
+
89
+ ```typescript
90
+ // components/__tests__/UserCard.test.ts
91
+ import { mount } from '@vue/test-utils'
92
+ import UserCard from '../UserCard.vue'
93
+
94
+ describe('UserCard', () => {
95
+ const defaultProps = {
96
+ userData: { id: '1', name: 'John', email: 'john@test.com' },
97
+ config: { showEmail: true, variant: 'compact' as const }
98
+ }
99
+
100
+ it('renders user data correctly', () => {
101
+ const wrapper = mount(UserCard, { props: defaultProps })
102
+
103
+ expect(wrapper.find('.user-card__name').text()).toBe('John')
104
+ expect(wrapper.find('p').text()).toBe('john@test.com')
105
+ })
106
+
107
+ it('hides email when config.showEmail is false', () => {
108
+ const wrapper = mount(UserCard, {
109
+ props: {
110
+ ...defaultProps,
111
+ config: { showEmail: false, variant: 'compact' }
112
+ }
113
+ })
114
+
115
+ expect(wrapper.find('p').exists()).toBe(false)
116
+ })
117
+
118
+ it('applies correct variant class', () => {
119
+ const wrapper = mount(UserCard, {
120
+ props: {
121
+ ...defaultProps,
122
+ config: { showEmail: true, variant: 'detailed' }
123
+ }
124
+ })
125
+
126
+ expect(wrapper.find('.user-card').classes()).toContain('user-card--detailed')
127
+ })
128
+
129
+ it('emits update event with new data', async () => {
130
+ const wrapper = mount(UserCard, { props: defaultProps })
131
+
132
+ await wrapper.find('button').trigger('click')
133
+
134
+ expect(wrapper.emitted('update')).toBeTruthy()
135
+ expect(wrapper.emitted('update')![0]).toEqual([
136
+ expect.objectContaining({ id: '1' })
137
+ ])
138
+ })
139
+ })
140
+ ```
141
+
142
+ ---
143
+
144
+ ## Testing Wrapper Components
145
+
146
+ Test wrappers separately from pure components.
147
+
148
+ ```typescript
149
+ // components/__tests__/UserDisplayWrapper.test.ts
150
+ import { mount, flushPromises } from '@vue/test-utils'
151
+ import UserDisplayWrapper from '../UserDisplayWrapper.vue'
152
+
153
+ // Mock the API module
154
+ vi.mock('@/api/userApi', () => ({
155
+ userApi: {
156
+ getUser: vi.fn(),
157
+ updateUser: vi.fn()
158
+ }
159
+ }))
160
+
161
+ import { userApi } from '@/api/userApi'
162
+
163
+ describe('UserDisplayWrapper', () => {
164
+ beforeEach(() => {
165
+ vi.clearAllMocks()
166
+ })
167
+
168
+ it('shows loading state initially', () => {
169
+ (userApi.getUser as Mock).mockImplementation(() => new Promise(() => {}))
170
+
171
+ const wrapper = mount(UserDisplayWrapper, {
172
+ props: { userId: '1' }
173
+ })
174
+
175
+ expect(wrapper.text()).toContain('Loading')
176
+ })
177
+
178
+ it('renders user data after fetch', async () => {
179
+ (userApi.getUser as Mock).mockResolvedValue({
180
+ id: '1',
181
+ name: 'John',
182
+ email: 'john@test.com'
183
+ })
184
+
185
+ const wrapper = mount(UserDisplayWrapper, {
186
+ props: { userId: '1' }
187
+ })
188
+
189
+ await flushPromises()
190
+
191
+ expect(wrapper.text()).toContain('John')
192
+ })
193
+
194
+ it('shows error state on fetch failure', async () => {
195
+ (userApi.getUser as Mock).mockRejectedValue(new Error('Network error'))
196
+
197
+ const wrapper = mount(UserDisplayWrapper, {
198
+ props: { userId: '1' }
199
+ })
200
+
201
+ await flushPromises()
202
+
203
+ expect(wrapper.text()).toContain('Error')
204
+ })
205
+
206
+ it('calls updateUser on update event', async () => {
207
+ const mockUser = { id: '1', name: 'John', email: 'john@test.com' }
208
+ ;(userApi.getUser as Mock).mockResolvedValue(mockUser)
209
+ ;(userApi.updateUser as Mock).mockResolvedValue(undefined)
210
+
211
+ const wrapper = mount(UserDisplayWrapper, {
212
+ props: { userId: '1' }
213
+ })
214
+
215
+ await flushPromises()
216
+
217
+ // Trigger update from child component
218
+ await wrapper.findComponent({ name: 'UserDisplayPure' }).vm.$emit('update', {
219
+ ...mockUser,
220
+ name: 'Updated'
221
+ })
222
+
223
+ expect(userApi.updateUser).toHaveBeenCalledWith(
224
+ expect.objectContaining({ name: 'Updated' })
225
+ )
226
+ })
227
+ })
228
+ ```
229
+
230
+ ---
231
+
232
+ ## Mocking Dependencies
233
+
234
+ ### Using Provide/Inject for DI
235
+
236
+ ```typescript
237
+ // Component that uses injected API
238
+ // UserFeature.vue uses `const api = inject('api')`
239
+
240
+ describe('UserFeature with injected API', () => {
241
+ const mockApi = {
242
+ getUser: vi.fn().mockResolvedValue({ id: '1', name: 'Test' }),
243
+ updateUser: vi.fn().mockResolvedValue(undefined)
244
+ }
245
+
246
+ it('uses injected API', async () => {
247
+ const wrapper = mount(UserFeature, {
248
+ global: {
249
+ provide: {
250
+ api: mockApi
251
+ }
252
+ },
253
+ props: { userId: '1' }
254
+ })
255
+
256
+ await flushPromises()
257
+
258
+ expect(mockApi.getUser).toHaveBeenCalledWith('1')
259
+ })
260
+ })
261
+ ```
262
+
263
+ ### Testing Composables with Mocked Dependencies
264
+
265
+ ```typescript
266
+ // composables/__tests__/useUserData.test.ts
267
+ import { ref } from 'vue'
268
+ import { useUserData } from '../useUserData'
269
+
270
+ describe('useUserData', () => {
271
+ it('fetches user with provided API', async () => {
272
+ const mockApi = {
273
+ getUser: vi.fn().mockResolvedValue({ id: '1', name: 'Test' })
274
+ }
275
+
276
+ // Composable accepts API as dependency
277
+ const { user, loading, fetchUser } = useUserData(mockApi)
278
+
279
+ expect(loading.value).toBe(false)
280
+ expect(user.value).toBeNull()
281
+
282
+ await fetchUser('1')
283
+
284
+ expect(mockApi.getUser).toHaveBeenCalledWith('1')
285
+ expect(user.value).toEqual({ id: '1', name: 'Test' })
286
+ })
287
+
288
+ it('handles fetch errors', async () => {
289
+ const mockApi = {
290
+ getUser: vi.fn().mockRejectedValue(new Error('Not found'))
291
+ }
292
+
293
+ const { error, fetchUser } = useUserData(mockApi)
294
+
295
+ await fetchUser('1')
296
+
297
+ expect(error.value).toBeInstanceOf(Error)
298
+ expect(error.value?.message).toBe('Not found')
299
+ })
300
+ })
301
+ ```
302
+
303
+ ---
304
+
305
+ ## Testing Best Practices
306
+
307
+ | Practice | Reason |
308
+ |----------|--------|
309
+ | Test composables in isolation | Faster, focused tests |
310
+ | Test components with minimal mocking | Verify integration |
311
+ | Test wrappers with API mocks | Isolate side effects |
312
+ | Use provide/inject for DI | Easy mock injection |
313
+ | Test reactive updates | Verify computed reactivity |
314
+ | Avoid snapshot tests for logic | Fragile, hard to review |