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.
- package/LICENSE +21 -0
- package/README.md +463 -0
- package/dist/cli.js +1064 -0
- package/package.json +49 -0
- package/platforms/claude/adapter.ts +115 -0
- package/platforms/junie/adapter.ts +254 -0
- package/platforms/junie/agents-template.md +113 -0
- package/platforms/junie/hook-translations.md +84 -0
- package/platforms/shared/detector.ts +27 -0
- package/platforms/shared/installer.ts +202 -0
- package/platforms/shared/types.ts +78 -0
- package/plugins/ima-claude/.claude-plugin/plugin.json +25 -0
- package/plugins/ima-claude/agents/explorer.md +30 -0
- package/plugins/ima-claude/agents/implementer.md +30 -0
- package/plugins/ima-claude/agents/memory.md +42 -0
- package/plugins/ima-claude/agents/reviewer.md +53 -0
- package/plugins/ima-claude/agents/tester.md +33 -0
- package/plugins/ima-claude/agents/wp-developer.md +46 -0
- package/plugins/ima-claude/hooks/README.md +145 -0
- package/plugins/ima-claude/hooks/atlassian_prereqs.py +112 -0
- package/plugins/ima-claude/hooks/block_sed_edits.py +59 -0
- package/plugins/ima-claude/hooks/bootstrap.sh +90 -0
- package/plugins/ima-claude/hooks/bootstrap_utility_check.py +94 -0
- package/plugins/ima-claude/hooks/composer_autoload_check.py +70 -0
- package/plugins/ima-claude/hooks/docs_organization.py +104 -0
- package/plugins/ima-claude/hooks/enforce_rg_over_grep.py +56 -0
- package/plugins/ima-claude/hooks/fp_utility_check.py +90 -0
- package/plugins/ima-claude/hooks/hook_logger.py +69 -0
- package/plugins/ima-claude/hooks/hooks.json +239 -0
- package/plugins/ima-claude/hooks/jira_issue_fetch.py +79 -0
- package/plugins/ima-claude/hooks/jquery_in_wordpress.py +92 -0
- package/plugins/ima-claude/hooks/memory_bootstrap.py +79 -0
- package/plugins/ima-claude/hooks/memory_store_reminder.py +75 -0
- package/plugins/ima-claude/hooks/prompt_coach.py +125 -0
- package/plugins/ima-claude/hooks/prompt_coach_digest.md +48 -0
- package/plugins/ima-claude/hooks/prompt_coach_system.md +30 -0
- package/plugins/ima-claude/hooks/sequential_thinking_check.py +81 -0
- package/plugins/ima-claude/hooks/serena_over_grep.py +96 -0
- package/plugins/ima-claude/hooks/serena_over_read.py +66 -0
- package/plugins/ima-claude/hooks/serena_project_check.py +133 -0
- package/plugins/ima-claude/hooks/sql_injection_check.py +73 -0
- package/plugins/ima-claude/hooks/task_master_after_plan.py +31 -0
- package/plugins/ima-claude/hooks/task_master_before_impl.py +93 -0
- package/plugins/ima-claude/hooks/tavily_extract_advanced.py +48 -0
- package/plugins/ima-claude/hooks/vestige_before_external.py +86 -0
- package/plugins/ima-claude/hooks/webfetch_to_tavily.py +42 -0
- package/plugins/ima-claude/hooks/websearch_to_tavily.py +41 -0
- package/plugins/ima-claude/hooks/wp_security_check.py +150 -0
- package/plugins/ima-claude/personalities/README.md +45 -0
- package/plugins/ima-claude/personalities/enable-40k.md +69 -0
- package/plugins/ima-claude/personalities/enable-templars.md +69 -0
- package/plugins/ima-claude/skills/.research-summary.md +340 -0
- package/plugins/ima-claude/skills/architect/SKILL.md +304 -0
- package/plugins/ima-claude/skills/compound-bridge/SKILL.md +200 -0
- package/plugins/ima-claude/skills/discourse/SKILL.md +440 -0
- package/plugins/ima-claude/skills/discourse-admin/SKILL.md +192 -0
- package/plugins/ima-claude/skills/discourse-admin/references/api-endpoints.md +441 -0
- package/plugins/ima-claude/skills/discourse-admin/references/gotchas.md +107 -0
- package/plugins/ima-claude/skills/discourse-admin/references/staging-defaults.md +98 -0
- package/plugins/ima-claude/skills/discourse-admin/scripts/discourse-admin.py +319 -0
- package/plugins/ima-claude/skills/docs-organize/SKILL.md +254 -0
- package/plugins/ima-claude/skills/docs-organize/templates/active-README.md +50 -0
- package/plugins/ima-claude/skills/docs-organize/templates/archive-README.md +57 -0
- package/plugins/ima-claude/skills/docs-organize/templates/docs-README.md +43 -0
- package/plugins/ima-claude/skills/docs-organize/templates/phase-archive-README.md +83 -0
- package/plugins/ima-claude/skills/docs-organize/templates/section-README.md +48 -0
- package/plugins/ima-claude/skills/docs-organize/templates/transient-README.md +79 -0
- package/plugins/ima-claude/skills/docs-organize/templates/transient-gitignore +9 -0
- package/plugins/ima-claude/skills/ember-discourse/SKILL.md +496 -0
- package/plugins/ima-claude/skills/functional-programmer/SKILL.md +258 -0
- package/plugins/ima-claude/skills/ima-bootstrap/SKILL.md +278 -0
- package/plugins/ima-claude/skills/ima-bootstrap/references/bootstrap-patterns.md +356 -0
- package/plugins/ima-claude/skills/ima-bootstrap/references/ima-brand.md +273 -0
- package/plugins/ima-claude/skills/ima-bootstrap/references/theme-integration.md +212 -0
- package/plugins/ima-claude/skills/ima-brand/SKILL.md +108 -0
- package/plugins/ima-claude/skills/ima-brand/references/brand-identity.md +140 -0
- package/plugins/ima-claude/skills/ima-brand/references/digital-standards.md +180 -0
- package/plugins/ima-claude/skills/ima-brand/references/visual-system.md +173 -0
- package/plugins/ima-claude/skills/ima-forms-expert/SKILL.md +175 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/container-components.md +154 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/examples.md +328 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/field-components.md +298 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/form-factory.md +193 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/quick-reference.md +153 -0
- package/plugins/ima-claude/skills/ima-forms-expert/references/validation-engine.md +336 -0
- package/plugins/ima-claude/skills/jira-checkpoint/SKILL.md +178 -0
- package/plugins/ima-claude/skills/jquery/SKILL.md +413 -0
- package/plugins/ima-claude/skills/js-fp/SKILL.md +463 -0
- package/plugins/ima-claude/skills/js-fp/core-principles.md +487 -0
- package/plugins/ima-claude/skills/js-fp/examples/pure-functions.js +260 -0
- package/plugins/ima-claude/skills/js-fp/examples/tests/pure-functions.test.js +262 -0
- package/plugins/ima-claude/skills/js-fp/references/anti-patterns.md +120 -0
- package/plugins/ima-claude/skills/js-fp/references/performance-patterns.md +116 -0
- package/plugins/ima-claude/skills/js-fp/references/testing-patterns.md +134 -0
- package/plugins/ima-claude/skills/js-fp-api/SKILL.md +280 -0
- package/plugins/ima-claude/skills/js-fp-api/examples/crud-endpoint.js +258 -0
- package/plugins/ima-claude/skills/js-fp-api/references/middleware-patterns.md +134 -0
- package/plugins/ima-claude/skills/js-fp-api/references/security-sql.md +110 -0
- package/plugins/ima-claude/skills/js-fp-api/references/validation-patterns.md +165 -0
- package/plugins/ima-claude/skills/js-fp-react/SKILL.md +447 -0
- package/plugins/ima-claude/skills/js-fp-react/examples/ProductCard.tsx +65 -0
- package/plugins/ima-claude/skills/js-fp-react/references/hooks-advanced.md +136 -0
- package/plugins/ima-claude/skills/js-fp-react/references/performance-patterns.md +175 -0
- package/plugins/ima-claude/skills/js-fp-vue/SKILL.md +322 -0
- package/plugins/ima-claude/skills/js-fp-vue/references/complete-examples.md +397 -0
- package/plugins/ima-claude/skills/js-fp-vue/references/composables-advanced.md +282 -0
- package/plugins/ima-claude/skills/js-fp-vue/references/reactivity-patterns.md +348 -0
- package/plugins/ima-claude/skills/js-fp-vue/references/testing.md +314 -0
- package/plugins/ima-claude/skills/js-fp-wordpress/SKILL.md +301 -0
- package/plugins/ima-claude/skills/js-fp-wordpress/references/ajax-patterns.md +192 -0
- package/plugins/ima-claude/skills/js-fp-wordpress/references/event-patterns.md +136 -0
- package/plugins/ima-claude/skills/js-fp-wordpress/references/wp-integration.md +248 -0
- package/plugins/ima-claude/skills/livecanvas/SKILL.md +209 -0
- package/plugins/ima-claude/skills/livecanvas/references/livecanvas-features.md +311 -0
- package/plugins/ima-claude/skills/livecanvas/references/loops-and-logic.md +730 -0
- package/plugins/ima-claude/skills/livecanvas/references/picostrap.md +227 -0
- package/plugins/ima-claude/skills/mcp-atlassian/SKILL.md +339 -0
- package/plugins/ima-claude/skills/mcp-context7/SKILL.md +109 -0
- package/plugins/ima-claude/skills/mcp-memory/SKILL.md +182 -0
- package/plugins/ima-claude/skills/mcp-qdrant/SKILL.md +233 -0
- package/plugins/ima-claude/skills/mcp-sequential/SKILL.md +149 -0
- package/plugins/ima-claude/skills/mcp-serena/SKILL.md +174 -0
- package/plugins/ima-claude/skills/mcp-tavily/SKILL.md +118 -0
- package/plugins/ima-claude/skills/mcp-vestige/SKILL.md +259 -0
- package/plugins/ima-claude/skills/php-authnet/SKILL.md +275 -0
- package/plugins/ima-claude/skills/php-authnet/references/api-reference.md +624 -0
- package/plugins/ima-claude/skills/php-authnet/references/sandbox-testing.md +424 -0
- package/plugins/ima-claude/skills/php-fp/SKILL.md +333 -0
- package/plugins/ima-claude/skills/php-fp/examples/pure-functions.php +403 -0
- package/plugins/ima-claude/skills/php-fp/examples/tests/PureFunctionsTest.php +515 -0
- package/plugins/ima-claude/skills/php-fp/references/core-principles.md +277 -0
- package/plugins/ima-claude/skills/php-fp/references/testing-patterns.md +374 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/SKILL.md +216 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/references/fp-patterns.md +275 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/references/plugin-architecture.md +295 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/references/security-examples.md +203 -0
- package/plugins/ima-claude/skills/php-fp-wordpress/references/testing-strategy.md +259 -0
- package/plugins/ima-claude/skills/phpunit-wp/SKILL.md +716 -0
- package/plugins/ima-claude/skills/playwright/SKILL.md +434 -0
- package/plugins/ima-claude/skills/playwright/references/accessibility-testing.md +153 -0
- package/plugins/ima-claude/skills/playwright/references/ci-cd.md +268 -0
- package/plugins/ima-claude/skills/playwright/references/network-mocking.md +270 -0
- package/plugins/ima-claude/skills/playwright/references/visual-regression.md +215 -0
- package/plugins/ima-claude/skills/py-fp/SKILL.md +663 -0
- package/plugins/ima-claude/skills/py-fp/examples/pure-functions.py +185 -0
- package/plugins/ima-claude/skills/py-fp/examples/tests/test_pure_functions.py +244 -0
- package/plugins/ima-claude/skills/py-fp/references/core-principles.md +381 -0
- package/plugins/ima-claude/skills/py-fp/references/testing-patterns.md +283 -0
- package/plugins/ima-claude/skills/quasar-fp/SKILL.md +327 -0
- package/plugins/ima-claude/skills/quasar-fp/metadata.json +85 -0
- package/plugins/ima-claude/skills/quasar-fp/references/component-patterns.md +257 -0
- package/plugins/ima-claude/skills/quasar-fp/references/theme-integration.md +233 -0
- package/plugins/ima-claude/skills/quasar-fp/references/utility-classes.md +237 -0
- package/plugins/ima-claude/skills/quickstart/SKILL.md +129 -0
- package/plugins/ima-claude/skills/rails/SKILL.md +359 -0
- package/plugins/ima-claude/skills/resume-session/SKILL.md +68 -0
- package/plugins/ima-claude/skills/rg/SKILL.md +205 -0
- package/plugins/ima-claude/skills/ruby-fp/SKILL.md +336 -0
- package/plugins/ima-claude/skills/save-session/SKILL.md +81 -0
- package/plugins/ima-claude/skills/scorecard/SKILL.md +96 -0
- package/plugins/ima-claude/skills/skill-analyzer/SKILL.md +127 -0
- package/plugins/ima-claude/skills/skill-analyzer/references/advanced-checklist.md +44 -0
- package/plugins/ima-claude/skills/skill-analyzer/references/core-checklist.md +60 -0
- package/plugins/ima-claude/skills/skill-analyzer/scripts/analyze_skill.py +418 -0
- package/plugins/ima-claude/skills/skill-creator/LICENSE.txt +202 -0
- package/plugins/ima-claude/skills/skill-creator/SKILL.md +343 -0
- package/plugins/ima-claude/skills/skill-creator/references/output-patterns.md +82 -0
- package/plugins/ima-claude/skills/skill-creator/references/workflows.md +28 -0
- package/plugins/ima-claude/skills/skill-creator/scripts/init_skill.py +303 -0
- package/plugins/ima-claude/skills/skill-creator/scripts/package_skill.py +110 -0
- package/plugins/ima-claude/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/plugins/ima-claude/skills/task-master/SKILL.md +51 -0
- package/plugins/ima-claude/skills/task-planner/SKILL.md +228 -0
- package/plugins/ima-claude/skills/task-runner/SKILL.md +192 -0
- package/plugins/ima-claude/skills/unit-testing/SKILL.md +198 -0
- package/plugins/ima-claude/skills/unit-testing/references/mock-patterns.md +181 -0
- package/plugins/ima-claude/skills/unit-testing/references/tdd-workflow.md +177 -0
- package/plugins/ima-claude/skills/unit-testing/references/test-strategy.md +126 -0
- package/plugins/ima-claude/skills/wp-local/SKILL.md +246 -0
- package/plugins/ima-claude/skills/wp-local/references/configuration.md +198 -0
- package/plugins/ima-claude/skills/wp-local/references/wp-cli-reference.md +406 -0
- package/plugins/ima-claude/skills/wp-local/scripts/wp-local.sh +61 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Mock Patterns for FP Codebases
|
|
2
|
+
|
|
3
|
+
## The FP Alternative to Mocking
|
|
4
|
+
|
|
5
|
+
Before reaching for a mock, ask: **can I extract the pure logic instead?**
|
|
6
|
+
|
|
7
|
+
### Before (Hard to Test — Needs Mocks)
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
// Impure: mixed logic + side effects
|
|
11
|
+
function processOrder(orderId) {
|
|
12
|
+
const order = db.getOrder(orderId) // side effect
|
|
13
|
+
const total = order.items.reduce((s, i) => s + i.price * i.qty, 0)
|
|
14
|
+
const tax = total * 0.08
|
|
15
|
+
const discount = total > 100 ? total * 0.1 : 0
|
|
16
|
+
db.updateOrder(orderId, { total, tax, discount }) // side effect
|
|
17
|
+
return { total, tax, discount }
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### After (Easy to Test — No Mocks Needed)
|
|
22
|
+
|
|
23
|
+
```javascript
|
|
24
|
+
// Pure: extract the logic
|
|
25
|
+
const calculateOrderTotals = (items, taxRate = 0.08, discountThreshold = 100) => {
|
|
26
|
+
const total = items.reduce((s, i) => s + i.price * i.qty, 0)
|
|
27
|
+
const tax = total * taxRate
|
|
28
|
+
const discount = total > discountThreshold ? total * 0.1 : 0
|
|
29
|
+
return { total, tax, discount }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Impure shell: thin wrapper
|
|
33
|
+
function processOrder(orderId) {
|
|
34
|
+
const order = db.getOrder(orderId)
|
|
35
|
+
const totals = calculateOrderTotals(order.items)
|
|
36
|
+
db.updateOrder(orderId, totals)
|
|
37
|
+
return totals
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Now `calculateOrderTotals` is tested with zero mocks. The impure shell is thin enough to verify by inspection or a single integration test.
|
|
42
|
+
|
|
43
|
+
## When Mocking IS Appropriate
|
|
44
|
+
|
|
45
|
+
Mock when the dependency is:
|
|
46
|
+
|
|
47
|
+
| Dependency | Mock? | Why |
|
|
48
|
+
|-----------|-------|-----|
|
|
49
|
+
| External API (Stripe, Twilio) | Yes | Unreliable, slow, costs money |
|
|
50
|
+
| Database | Yes (for unit tests) | Slow, requires setup/teardown |
|
|
51
|
+
| Filesystem | Yes | Non-deterministic, platform-specific |
|
|
52
|
+
| Time/Date | Yes | Non-deterministic |
|
|
53
|
+
| Random/UUID | Yes | Non-deterministic |
|
|
54
|
+
| Your own pure functions | **No** | Use the real thing |
|
|
55
|
+
| Your own classes | **Rarely** | Extract the logic instead |
|
|
56
|
+
|
|
57
|
+
## Mock Types: When to Use Each
|
|
58
|
+
|
|
59
|
+
### Stub (Default Choice)
|
|
60
|
+
|
|
61
|
+
Returns canned data. Use for most cases.
|
|
62
|
+
|
|
63
|
+
```javascript
|
|
64
|
+
// JS: Simple stub
|
|
65
|
+
const getUser = vi.fn().mockReturnValue({ id: 1, name: 'Alice' })
|
|
66
|
+
|
|
67
|
+
// PHP: PHPUnit stub
|
|
68
|
+
$repo = $this->createStub(UserRepository::class);
|
|
69
|
+
$repo->method('find')->willReturn(new User(1, 'Alice'));
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Spy (Verify Calls)
|
|
73
|
+
|
|
74
|
+
Records calls for later assertion. Use when you need to verify a side effect happened.
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
// JS: Spy on a method
|
|
78
|
+
const sendEmail = vi.fn()
|
|
79
|
+
processSignup(user, { sendEmail })
|
|
80
|
+
expect(sendEmail).toHaveBeenCalledWith(user.email, expect.any(String))
|
|
81
|
+
|
|
82
|
+
// PHP: PHPUnit expects
|
|
83
|
+
$mailer = $this->createMock(Mailer::class);
|
|
84
|
+
$mailer->expects($this->once())
|
|
85
|
+
->method('send')
|
|
86
|
+
->with($this->equalTo('alice@example.com'));
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Fake (Simplified Implementation)
|
|
90
|
+
|
|
91
|
+
A working but simplified version. Use for complex interfaces.
|
|
92
|
+
|
|
93
|
+
```javascript
|
|
94
|
+
// JS: In-memory store as a fake
|
|
95
|
+
const createFakeStore = () => {
|
|
96
|
+
const data = new Map()
|
|
97
|
+
return {
|
|
98
|
+
get: (key) => data.get(key),
|
|
99
|
+
set: (key, value) => data.set(key, value),
|
|
100
|
+
delete: (key) => data.delete(key),
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Full Mock (Almost Never)
|
|
106
|
+
|
|
107
|
+
Strict expectations on exact call sequences. Couples tests tightly to implementation.
|
|
108
|
+
|
|
109
|
+
**Use only when:** the exact call sequence IS the behavior (e.g., protocol compliance, ordered operations).
|
|
110
|
+
|
|
111
|
+
## Language-Specific Tooling
|
|
112
|
+
|
|
113
|
+
### JavaScript/TypeScript
|
|
114
|
+
|
|
115
|
+
| Tool | Best For |
|
|
116
|
+
|------|----------|
|
|
117
|
+
| `vi.fn()` / `jest.fn()` | Stubs and spies |
|
|
118
|
+
| `vi.mock()` / `jest.mock()` | Module-level mocking |
|
|
119
|
+
| `msw` (Mock Service Worker) | HTTP API mocking (integration/E2E) |
|
|
120
|
+
| `@testing-library/*` | Component testing without implementation details |
|
|
121
|
+
|
|
122
|
+
### PHP
|
|
123
|
+
|
|
124
|
+
| Tool | Best For |
|
|
125
|
+
|------|----------|
|
|
126
|
+
| PHPUnit `createStub()` | Simple return values |
|
|
127
|
+
| PHPUnit `createMock()` | Stubs with expectations |
|
|
128
|
+
| `Brain\Monkey` | WordPress function mocking |
|
|
129
|
+
| `WP_Mock` | WordPress hook/filter mocking |
|
|
130
|
+
| `Mockery` | Complex mocking (use sparingly) |
|
|
131
|
+
|
|
132
|
+
## Anti-Patterns
|
|
133
|
+
|
|
134
|
+
### Mocking What You Own
|
|
135
|
+
|
|
136
|
+
```javascript
|
|
137
|
+
// BAD: Mocking your own utility
|
|
138
|
+
const calculateTax = vi.fn().mockReturnValue(8.0)
|
|
139
|
+
const result = processOrder(items, { calculateTax })
|
|
140
|
+
expect(result.tax).toBe(8.0) // This proves nothing!
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
You're testing that your test setup works, not that the code works. Use the real function.
|
|
144
|
+
|
|
145
|
+
### Deep Mock Chains
|
|
146
|
+
|
|
147
|
+
```javascript
|
|
148
|
+
// BAD: Mock chain hell
|
|
149
|
+
const mockDb = {
|
|
150
|
+
getConnection: vi.fn().mockReturnValue({
|
|
151
|
+
query: vi.fn().mockReturnValue({
|
|
152
|
+
rows: vi.fn().mockReturnValue([{ id: 1 }])
|
|
153
|
+
})
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
If you need this, the code needs refactoring, not more mocks. Extract the pure logic.
|
|
159
|
+
|
|
160
|
+
### Mocking to Achieve Coverage
|
|
161
|
+
|
|
162
|
+
```javascript
|
|
163
|
+
// BAD: Mocking just to hit a coverage number
|
|
164
|
+
const fs = vi.mock('fs')
|
|
165
|
+
fs.readFileSync.mockReturnValue('data')
|
|
166
|
+
// ... test that proves readFileSync was called with the right path
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
This tests that you called `readFileSync`, not that your logic is correct. Extract the logic that processes the data and test that instead.
|
|
170
|
+
|
|
171
|
+
### Over-Specified Mocks
|
|
172
|
+
|
|
173
|
+
```javascript
|
|
174
|
+
// BAD: Testing implementation, not behavior
|
|
175
|
+
expect(mockDb.query).toHaveBeenCalledWith(
|
|
176
|
+
'SELECT * FROM users WHERE id = ? AND active = ?',
|
|
177
|
+
[1, true]
|
|
178
|
+
)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
This breaks if you rename a column or change the query. Test the behavior: "given user ID 1, returns the user's data."
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# TDD Workflow
|
|
2
|
+
|
|
3
|
+
## Red-Green-Refactor Mechanics
|
|
4
|
+
|
|
5
|
+
### Red: Write a Failing Test
|
|
6
|
+
|
|
7
|
+
Write the test FIRST. It must fail for the right reason.
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
// 1. Start with the desired behavior
|
|
11
|
+
test('filters out inactive users', () => {
|
|
12
|
+
const users = [
|
|
13
|
+
{ name: 'Alice', active: true },
|
|
14
|
+
{ name: 'Bob', active: false },
|
|
15
|
+
{ name: 'Carol', active: true },
|
|
16
|
+
]
|
|
17
|
+
expect(getActiveUsers(users)).toEqual([
|
|
18
|
+
{ name: 'Alice', active: true },
|
|
19
|
+
{ name: 'Carol', active: true },
|
|
20
|
+
])
|
|
21
|
+
})
|
|
22
|
+
// Fails: getActiveUsers is not defined ✓ (correct failure)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Key:** The test should fail because the feature doesn't exist yet — not because of a typo or setup error.
|
|
26
|
+
|
|
27
|
+
### Green: Make It Pass (Minimum Code)
|
|
28
|
+
|
|
29
|
+
Write the simplest code that makes the test pass. Fight the urge to generalize.
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
// 2. Simplest passing implementation
|
|
33
|
+
const getActiveUsers = (users) => users.filter(u => u.active)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
**Key:** Don't optimize. Don't handle edge cases you haven't tested. Just make it green.
|
|
37
|
+
|
|
38
|
+
### Refactor: Clean Up
|
|
39
|
+
|
|
40
|
+
With a passing test as your safety net, improve the code.
|
|
41
|
+
|
|
42
|
+
- Rename for clarity
|
|
43
|
+
- Extract shared logic
|
|
44
|
+
- Remove duplication
|
|
45
|
+
- Improve performance (only if needed)
|
|
46
|
+
|
|
47
|
+
**Key:** Tests stay green throughout refactoring. If they go red, you broke something.
|
|
48
|
+
|
|
49
|
+
## TDD with Pure Functions
|
|
50
|
+
|
|
51
|
+
Pure functions are TDD's sweet spot. No setup, no teardown, no mocking.
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
Input → Function → Output
|
|
55
|
+
Test: Input → Function → Assert Output
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
The cycle is fast:
|
|
59
|
+
1. Write test with input/output pair (10 seconds)
|
|
60
|
+
2. Write function (10 seconds)
|
|
61
|
+
3. Run test (1 second)
|
|
62
|
+
4. Refactor if needed (30 seconds)
|
|
63
|
+
|
|
64
|
+
### Table-Driven TDD
|
|
65
|
+
|
|
66
|
+
For pure functions, use parameterized tests to cover multiple cases efficiently:
|
|
67
|
+
|
|
68
|
+
```javascript
|
|
69
|
+
test.each([
|
|
70
|
+
[0, 'F'],
|
|
71
|
+
[59, 'F'],
|
|
72
|
+
[60, 'D'],
|
|
73
|
+
[70, 'C'],
|
|
74
|
+
[80, 'B'],
|
|
75
|
+
[90, 'A'],
|
|
76
|
+
[100, 'A'],
|
|
77
|
+
])('score %i gets grade %s', (score, expected) => {
|
|
78
|
+
expect(getGrade(score)).toBe(expected)
|
|
79
|
+
})
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Write all cases first (red), then implement until all pass (green).
|
|
83
|
+
|
|
84
|
+
## TDD for Impure Boundaries
|
|
85
|
+
|
|
86
|
+
When TDD meets side effects, use the **functional core / imperative shell** pattern:
|
|
87
|
+
|
|
88
|
+
### 1. Define the Interface
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
// What does the boundary need to provide?
|
|
92
|
+
type UserRepository = {
|
|
93
|
+
findById: (id: string) => Promise<User | null>
|
|
94
|
+
save: (user: User) => Promise<void>
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### 2. TDD the Pure Logic
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
// Test the decision logic, not the I/O
|
|
102
|
+
test('deactivates user when last login > 90 days ago', () => {
|
|
103
|
+
const user = { lastLogin: daysAgo(91), active: true }
|
|
104
|
+
expect(shouldDeactivate(user)).toBe(true)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test('keeps user active when last login < 90 days ago', () => {
|
|
108
|
+
const user = { lastLogin: daysAgo(30), active: true }
|
|
109
|
+
expect(shouldDeactivate(user)).toBe(false)
|
|
110
|
+
})
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 3. Wire the Shell (Minimal Integration Test)
|
|
114
|
+
|
|
115
|
+
```javascript
|
|
116
|
+
// One integration test to verify wiring
|
|
117
|
+
test('deactivation job processes inactive users', async () => {
|
|
118
|
+
const fakeRepo = createFakeUserRepo([
|
|
119
|
+
{ id: '1', lastLogin: daysAgo(91), active: true },
|
|
120
|
+
])
|
|
121
|
+
await runDeactivationJob(fakeRepo)
|
|
122
|
+
expect(fakeRepo.findById('1')).resolves.toMatchObject({ active: false })
|
|
123
|
+
})
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## When TDD Slows You Down
|
|
127
|
+
|
|
128
|
+
TDD is a tool, not a dogma. Skip it when:
|
|
129
|
+
|
|
130
|
+
### Exploratory / Prototype Code
|
|
131
|
+
|
|
132
|
+
You're figuring out what the code should even do. TDD assumes you know the desired behavior.
|
|
133
|
+
|
|
134
|
+
**Instead:** Spike first, extract and test after.
|
|
135
|
+
|
|
136
|
+
### Trivial Glue Code
|
|
137
|
+
|
|
138
|
+
Code that just wires things together with no logic.
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
// No TDD needed for this
|
|
142
|
+
app.get('/users', authenticate, userController.list)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### UI Layout / Styling
|
|
146
|
+
|
|
147
|
+
Visual output is better verified by looking at it, not by asserting CSS properties.
|
|
148
|
+
|
|
149
|
+
**Instead:** Use visual regression tests (screenshot comparison) after the layout stabilizes.
|
|
150
|
+
|
|
151
|
+
### Rapidly Changing Requirements
|
|
152
|
+
|
|
153
|
+
If the spec changes daily, tests written today are deleted tomorrow.
|
|
154
|
+
|
|
155
|
+
**Instead:** Wait for requirements to stabilize, then add tests.
|
|
156
|
+
|
|
157
|
+
## TDD Anti-Patterns
|
|
158
|
+
|
|
159
|
+
### Testing Too Much at Once
|
|
160
|
+
|
|
161
|
+
Write one test, make it pass. Repeat. Don't write 10 failing tests and try to make them all pass simultaneously.
|
|
162
|
+
|
|
163
|
+
### Gold-Plating in Green Phase
|
|
164
|
+
|
|
165
|
+
The green phase is about making it work, not making it elegant. Save elegance for refactor.
|
|
166
|
+
|
|
167
|
+
### Skipping Refactor Phase
|
|
168
|
+
|
|
169
|
+
Red-green without refactor accumulates technical debt. The refactor step is where design emerges.
|
|
170
|
+
|
|
171
|
+
### Testing Private Methods
|
|
172
|
+
|
|
173
|
+
If you feel the need to test a private method, it's a sign that the method should be extracted as a separate pure function.
|
|
174
|
+
|
|
175
|
+
### Assertion-Free Tests
|
|
176
|
+
|
|
177
|
+
A test that only checks "it doesn't throw" is not a test. Assert on the output.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# Test Strategy for FP Codebases
|
|
2
|
+
|
|
3
|
+
## The Test Pyramid (FP Edition)
|
|
4
|
+
|
|
5
|
+
FP codebases are bottom-heavy by nature. Pure functions are trivially testable — lean into this.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
/ E2E \ Few: critical user journeys only
|
|
9
|
+
/----------\
|
|
10
|
+
/ Integration \ Some: boundary wiring, API contracts
|
|
11
|
+
/----------------\
|
|
12
|
+
/ Unit Tests \ Many: pure functions, transformations, validators
|
|
13
|
+
/____________________\
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### Why FP Pyramids Are Bottom-Heavy
|
|
17
|
+
|
|
18
|
+
- Pure functions need zero setup → unit tests are cheap
|
|
19
|
+
- Side effects are isolated at boundaries → integration tests are focused
|
|
20
|
+
- Business logic is testable without infrastructure → fast feedback
|
|
21
|
+
|
|
22
|
+
## Coverage Strategy
|
|
23
|
+
|
|
24
|
+
**Coverage is a floor, not a ceiling.**
|
|
25
|
+
|
|
26
|
+
### Setting the Floor
|
|
27
|
+
|
|
28
|
+
- New projects: 80% line coverage as a starting point
|
|
29
|
+
- Legacy projects: measure current coverage, set floor 5% below, ratchet up
|
|
30
|
+
- Never chase 100% — the last 20% costs more than the first 80%
|
|
31
|
+
|
|
32
|
+
### What to Cover
|
|
33
|
+
|
|
34
|
+
| Priority | What | Why |
|
|
35
|
+
|----------|------|-----|
|
|
36
|
+
| High | Pure business logic | Core value, easy to test |
|
|
37
|
+
| High | Data transformations | Bugs here corrupt everything downstream |
|
|
38
|
+
| High | Validation functions | Security and correctness boundary |
|
|
39
|
+
| Medium | Integration wiring | Verify components connect correctly |
|
|
40
|
+
| Medium | Error handling paths | Prevent silent failures |
|
|
41
|
+
| Low | Glue code / configuration | Low logic density |
|
|
42
|
+
| Skip | Framework boilerplate | Trust the framework |
|
|
43
|
+
|
|
44
|
+
### What NOT to Cover
|
|
45
|
+
|
|
46
|
+
- Trivial getters/setters with no logic
|
|
47
|
+
- Framework-generated code (migrations, stubs)
|
|
48
|
+
- Configuration files
|
|
49
|
+
- Type definitions (TypeScript interfaces, PHP type hints)
|
|
50
|
+
|
|
51
|
+
## When to Write Each Test Type
|
|
52
|
+
|
|
53
|
+
### Unit Tests
|
|
54
|
+
|
|
55
|
+
Write when:
|
|
56
|
+
- Function takes input, returns output (pure)
|
|
57
|
+
- Logic has branches (if/else, switch, pattern matching)
|
|
58
|
+
- Data transformation or validation
|
|
59
|
+
- Algorithm implementation
|
|
60
|
+
|
|
61
|
+
Skip when:
|
|
62
|
+
- Code is pure glue (just wires things together)
|
|
63
|
+
- Logic is trivially obvious (`return this.name`)
|
|
64
|
+
|
|
65
|
+
### Integration Tests
|
|
66
|
+
|
|
67
|
+
Write when:
|
|
68
|
+
- Verifying database queries return correct data
|
|
69
|
+
- API endpoint contracts (request → response shape)
|
|
70
|
+
- Multiple components must work together
|
|
71
|
+
- Third-party service integration points
|
|
72
|
+
|
|
73
|
+
Skip when:
|
|
74
|
+
- You can test the logic as a pure function instead
|
|
75
|
+
- The integration is trivial and well-typed
|
|
76
|
+
|
|
77
|
+
### E2E Tests
|
|
78
|
+
|
|
79
|
+
Write when:
|
|
80
|
+
- Critical user journeys (login, checkout, signup)
|
|
81
|
+
- Complex multi-step workflows
|
|
82
|
+
- Regression tests for bugs that slipped through unit/integration
|
|
83
|
+
|
|
84
|
+
Skip when:
|
|
85
|
+
- The behavior is fully covered by unit + integration tests
|
|
86
|
+
- The UI is rapidly changing (tests will be constantly rewritten)
|
|
87
|
+
- The test would be flaky by nature (animations, timing)
|
|
88
|
+
|
|
89
|
+
## Retrofitting Tests onto Legacy Code
|
|
90
|
+
|
|
91
|
+
Legacy code without tests is the norm. Don't try to add 100% coverage at once.
|
|
92
|
+
|
|
93
|
+
### The Characterization Test Approach
|
|
94
|
+
|
|
95
|
+
1. **Identify the change point** — what code are you about to modify?
|
|
96
|
+
2. **Write characterization tests** — tests that document current behavior (even if buggy)
|
|
97
|
+
3. **Extract pure logic** — pull testable functions out of the tangled code
|
|
98
|
+
4. **Test the extracted logic** — proper unit tests for the new pure functions
|
|
99
|
+
5. **Make your change** — now you have a safety net
|
|
100
|
+
6. **Update tests** — adjust characterization tests if behavior intentionally changed
|
|
101
|
+
|
|
102
|
+
### Priority Order for Legacy Code
|
|
103
|
+
|
|
104
|
+
1. Code you're about to change (safety net)
|
|
105
|
+
2. Code with known bugs (prevent regression)
|
|
106
|
+
3. Business-critical paths (high impact if broken)
|
|
107
|
+
4. Frequently modified code (high churn = high risk)
|
|
108
|
+
5. Everything else (only if you have time)
|
|
109
|
+
|
|
110
|
+
### The Strangler Pattern for Tests
|
|
111
|
+
|
|
112
|
+
Don't rewrite all tests at once. Instead:
|
|
113
|
+
- Every time you touch legacy code, add tests for what you changed
|
|
114
|
+
- Over time, coverage grows organically where it matters most
|
|
115
|
+
- Old untested code stays untested until it needs to change
|
|
116
|
+
|
|
117
|
+
## When NOT to Test
|
|
118
|
+
|
|
119
|
+
Testing has diminishing returns. Don't test:
|
|
120
|
+
|
|
121
|
+
- **Prototype/spike code** — it's throwaway by definition
|
|
122
|
+
- **One-off scripts** — run it, verify manually, done
|
|
123
|
+
- **Configuration** — JSON/YAML files don't need unit tests
|
|
124
|
+
- **Type system guarantees** — if TypeScript/PHP types prevent it, don't test it
|
|
125
|
+
- **Third-party library internals** — test your usage, not their code
|
|
126
|
+
- **Obvious glue code** — `app.use(cors())` doesn't need a test
|