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,515 @@
1
+ <?php
2
+ declare(strict_types=1);
3
+
4
+ namespace PhpFp\Examples\Tests;
5
+
6
+ use PHPUnit\Framework\TestCase;
7
+ use function PhpFp\Examples\calculateDiscount;
8
+ use function PhpFp\Examples\validateEmail;
9
+ use function PhpFp\Examples\formatDisplayName;
10
+ use function PhpFp\Examples\createValidator;
11
+ use function PhpFp\Examples\createFieldTransformer;
12
+ use function PhpFp\Examples\processActiveUsers;
13
+ use function PhpFp\Examples\calculateAccountAge;
14
+ use function PhpFp\Examples\groupBy;
15
+ use function PhpFp\Examples\updateUser;
16
+ use function PhpFp\Examples\addItem;
17
+ use function PhpFp\Examples\removeItem;
18
+ use function PhpFp\Examples\parseUserData;
19
+ use function PhpFp\Examples\calculateShipping;
20
+ use function PhpFp\Examples\processOrder;
21
+ use function PhpFp\Examples\calculateTax;
22
+ use function PhpFp\Examples\calculateShippingCost;
23
+ use function PhpFp\Examples\applyDiscountCode;
24
+
25
+ /**
26
+ * Comprehensive test suite for PHP FP patterns
27
+ *
28
+ * Demonstrates:
29
+ * - Data providers for comprehensive edge case testing
30
+ * - Pure function testing (no mocks needed)
31
+ * - Result type validation
32
+ * - Performance pattern validation
33
+ */
34
+ class PureFunctionsTest extends TestCase
35
+ {
36
+ // ───────────────────────────────────────────────────────
37
+ // Basic Pure Functions Tests
38
+ // ───────────────────────────────────────────────────────
39
+
40
+ /**
41
+ * @dataProvider discountProvider
42
+ */
43
+ public function testCalculateDiscount(
44
+ float $price,
45
+ string $tier,
46
+ int $days,
47
+ float $expected
48
+ ): void {
49
+ $result = calculateDiscount($price, $tier, $days);
50
+ $this->assertEquals($expected, $result);
51
+ }
52
+
53
+ public function discountProvider(): array
54
+ {
55
+ return [
56
+ 'bronze_new_member' => [100.00, 'bronze', 30, 95.00],
57
+ 'silver_1year_member' => [100.00, 'silver', 365, 88.00],
58
+ 'gold_5year_member' => [100.00, 'gold', 1825, 80.00],
59
+ 'platinum_10year_member' => [100.00, 'platinum', 3650, 75.00],
60
+ 'invalid_tier' => [100.00, 'invalid', 365, 100.00],
61
+ 'zero_days' => [100.00, 'bronze', 0, 95.00],
62
+ 'negative_days' => [100.00, 'gold', -100, 85.00],
63
+ 'max_loyalty_bronze' => [100.00, 'bronze', 10000, 90.00],
64
+ 'zero_price' => [0.00, 'gold', 365, 0.00],
65
+ 'high_price' => [9999.99, 'platinum', 3650, 7499.99]
66
+ ];
67
+ }
68
+
69
+ /**
70
+ * @dataProvider emailProvider
71
+ */
72
+ public function testValidateEmail(string $email, bool $expectedValid): void
73
+ {
74
+ $result = validateEmail($email);
75
+ $this->assertEquals($expectedValid, $result['valid']);
76
+
77
+ if (!$expectedValid) {
78
+ $this->assertNotNull($result['error']);
79
+ } else {
80
+ $this->assertNull($result['error']);
81
+ }
82
+ }
83
+
84
+ public function emailProvider(): array
85
+ {
86
+ return [
87
+ 'valid_simple' => ['test@example.com', true],
88
+ 'valid_subdomain' => ['user@mail.example.com', true],
89
+ 'valid_plus' => ['user+tag@example.com', true],
90
+ 'invalid_no_at' => ['userexample.com', false],
91
+ 'invalid_no_domain' => ['user@', false],
92
+ 'invalid_no_tld' => ['user@example', false],
93
+ 'invalid_spaces' => ['user @example.com', false],
94
+ 'invalid_empty' => ['', false],
95
+ 'invalid_double_at' => ['user@@example.com', false]
96
+ ];
97
+ }
98
+
99
+ /**
100
+ * @dataProvider displayNameProvider
101
+ */
102
+ public function testFormatDisplayName(array $user, string $expected): void
103
+ {
104
+ $result = formatDisplayName($user);
105
+ $this->assertEquals($expected, $result);
106
+ }
107
+
108
+ public function displayNameProvider(): array
109
+ {
110
+ return [
111
+ 'full_name' => [
112
+ ['first_name' => 'John', 'last_name' => 'Doe'],
113
+ 'John Doe'
114
+ ],
115
+ 'first_only' => [
116
+ ['first_name' => 'John'],
117
+ 'John'
118
+ ],
119
+ 'last_only' => [
120
+ ['last_name' => 'Doe'],
121
+ 'Doe'
122
+ ],
123
+ 'empty_both' => [
124
+ [],
125
+ 'Anonymous'
126
+ ],
127
+ 'whitespace_trimmed' => [
128
+ ['first_name' => ' John ', 'last_name' => ' Doe '],
129
+ 'John Doe'
130
+ ],
131
+ 'empty_strings' => [
132
+ ['first_name' => '', 'last_name' => ''],
133
+ 'Anonymous'
134
+ ]
135
+ ];
136
+ }
137
+
138
+ // ───────────────────────────────────────────────────────
139
+ // Configuration Pre-Compilation Tests
140
+ // ───────────────────────────────────────────────────────
141
+
142
+ public function testCreateValidator(): void
143
+ {
144
+ $validator = createValidator([
145
+ 'email' => 'email',
146
+ 'age' => 'numeric',
147
+ 'name' => 'required'
148
+ ]);
149
+
150
+ // Valid data
151
+ $result = $validator([
152
+ 'email' => 'test@example.com',
153
+ 'age' => '25',
154
+ 'name' => 'John'
155
+ ]);
156
+ $this->assertTrue($result['valid']);
157
+ $this->assertEmpty($result['errors']);
158
+
159
+ // Invalid email
160
+ $result = $validator([
161
+ 'email' => 'invalid',
162
+ 'age' => '25',
163
+ 'name' => 'John'
164
+ ]);
165
+ $this->assertFalse($result['valid']);
166
+ $this->assertArrayHasKey('email', $result['errors']);
167
+
168
+ // Missing required
169
+ $result = $validator([
170
+ 'email' => 'test@example.com',
171
+ 'age' => '25'
172
+ ]);
173
+ $this->assertFalse($result['valid']);
174
+ $this->assertArrayHasKey('name', $result['errors']);
175
+
176
+ // Invalid numeric
177
+ $result = $validator([
178
+ 'email' => 'test@example.com',
179
+ 'age' => 'not-a-number',
180
+ 'name' => 'John'
181
+ ]);
182
+ $this->assertFalse($result['valid']);
183
+ $this->assertArrayHasKey('age', $result['errors']);
184
+ }
185
+
186
+ public function testCreateValidatorPerformance(): void
187
+ {
188
+ // Pre-compile validator once
189
+ $validator = createValidator([
190
+ 'email' => 'email',
191
+ 'age' => 'numeric'
192
+ ]);
193
+
194
+ $data = array_fill(0, 1000, [
195
+ 'email' => 'test@example.com',
196
+ 'age' => '25'
197
+ ]);
198
+
199
+ $start = microtime(true);
200
+ foreach ($data as $item) {
201
+ $validator($item);
202
+ }
203
+ $preCompiledTime = microtime(true) - $start;
204
+
205
+ // This should be significantly faster than creating validator N times
206
+ $this->assertLessThan(0.1, $preCompiledTime, 'Pre-compiled validator should be fast');
207
+ }
208
+
209
+ public function testCreateFieldTransformer(): void
210
+ {
211
+ $transformer = createFieldTransformer([
212
+ 'name' => 'capitalize',
213
+ 'email' => 'lowercase',
214
+ 'code' => 'uppercase'
215
+ ]);
216
+
217
+ $result = $transformer([
218
+ 'name' => 'john doe',
219
+ 'email' => 'USER@EXAMPLE.COM',
220
+ 'code' => 'abc123'
221
+ ]);
222
+
223
+ $this->assertEquals('John doe', $result['name']);
224
+ $this->assertEquals('user@example.com', $result['email']);
225
+ $this->assertEquals('ABC123', $result['code']);
226
+ }
227
+
228
+ // ───────────────────────────────────────────────────────
229
+ // Composition Tests
230
+ // ───────────────────────────────────────────────────────
231
+
232
+ public function testProcessActiveUsers(): void
233
+ {
234
+ $users = [
235
+ ['id' => 1, 'first_name' => 'John', 'last_name' => 'Doe', 'active' => true, 'created_at' => '2020-01-01'],
236
+ ['id' => 2, 'first_name' => 'Jane', 'last_name' => 'Smith', 'active' => false, 'created_at' => '2021-01-01'],
237
+ ['id' => 3, 'first_name' => 'Bob', 'last_name' => 'Johnson', 'active' => true, 'created_at' => '2019-01-01']
238
+ ];
239
+
240
+ $result = processActiveUsers($users, 10);
241
+
242
+ // Only active users
243
+ $this->assertCount(2, $result);
244
+
245
+ // Check display names added
246
+ $this->assertEquals('John Doe', $result[0]['display_name']);
247
+ $this->assertEquals('Bob Johnson', $result[1]['display_name']);
248
+
249
+ // Check account age added
250
+ $this->assertArrayHasKey('account_age', $result[0]);
251
+ $this->assertIsInt($result[0]['account_age']);
252
+ }
253
+
254
+ public function testProcessActiveUsersLimit(): void
255
+ {
256
+ $users = array_map(
257
+ fn($i) => [
258
+ 'id' => $i,
259
+ 'first_name' => "User{$i}",
260
+ 'last_name' => 'Test',
261
+ 'active' => true,
262
+ 'created_at' => '2020-01-01'
263
+ ],
264
+ range(1, 20)
265
+ );
266
+
267
+ $result = processActiveUsers($users, 5);
268
+ $this->assertCount(5, $result);
269
+ }
270
+
271
+ public function testGroupBy(): void
272
+ {
273
+ $items = [
274
+ ['id' => 1, 'category' => 'A', 'name' => 'Item 1'],
275
+ ['id' => 2, 'category' => 'B', 'name' => 'Item 2'],
276
+ ['id' => 3, 'category' => 'A', 'name' => 'Item 3'],
277
+ ['id' => 4, 'category' => 'C', 'name' => 'Item 4']
278
+ ];
279
+
280
+ $grouped = groupBy($items, 'category');
281
+
282
+ $this->assertArrayHasKey('A', $grouped);
283
+ $this->assertArrayHasKey('B', $grouped);
284
+ $this->assertArrayHasKey('C', $grouped);
285
+ $this->assertCount(2, $grouped['A']);
286
+ $this->assertCount(1, $grouped['B']);
287
+ $this->assertCount(1, $grouped['C']);
288
+ }
289
+
290
+ // ───────────────────────────────────────────────────────
291
+ // Immutability Tests
292
+ // ───────────────────────────────────────────────────────
293
+
294
+ public function testUpdateUserImmutability(): void
295
+ {
296
+ $original = ['id' => 1, 'name' => 'John', 'email' => 'john@example.com'];
297
+ $updated = updateUser($original, ['email' => 'newemail@example.com']);
298
+
299
+ // Original unchanged
300
+ $this->assertEquals('john@example.com', $original['email']);
301
+
302
+ // New array created
303
+ $this->assertEquals('newemail@example.com', $updated['email']);
304
+ $this->assertArrayHasKey('updated_at', $updated);
305
+ }
306
+
307
+ public function testAddItemImmutability(): void
308
+ {
309
+ $original = [
310
+ ['id' => 1, 'name' => 'Item 1'],
311
+ ['id' => 2, 'name' => 'Item 2']
312
+ ];
313
+
314
+ $newCollection = addItem($original, ['id' => 3, 'name' => 'Item 3']);
315
+
316
+ // Original unchanged
317
+ $this->assertCount(2, $original);
318
+
319
+ // New collection created
320
+ $this->assertCount(3, $newCollection);
321
+ $this->assertEquals('Item 3', $newCollection[2]['name']);
322
+ }
323
+
324
+ public function testRemoveItemImmutability(): void
325
+ {
326
+ $original = [
327
+ ['id' => '1', 'name' => 'Item 1'],
328
+ ['id' => '2', 'name' => 'Item 2'],
329
+ ['id' => '3', 'name' => 'Item 3']
330
+ ];
331
+
332
+ $newCollection = removeItem($original, '2');
333
+
334
+ // Original unchanged
335
+ $this->assertCount(3, $original);
336
+
337
+ // New collection created with item removed
338
+ $this->assertCount(2, $newCollection);
339
+ $this->assertEquals('1', $newCollection[0]['id']);
340
+ $this->assertEquals('3', $newCollection[1]['id']);
341
+ }
342
+
343
+ // ───────────────────────────────────────────────────────
344
+ // Result Type Pattern Tests
345
+ // ───────────────────────────────────────────────────────
346
+
347
+ public function testParseUserDataSuccess(): void
348
+ {
349
+ $result = parseUserData([
350
+ 'email' => 'test@example.com',
351
+ 'name' => 'John Doe',
352
+ 'age' => '25'
353
+ ]);
354
+
355
+ $this->assertTrue($result['success']);
356
+ $this->assertNull($result['error']);
357
+ $this->assertIsArray($result['data']);
358
+ $this->assertEquals('test@example.com', $result['data']['email']);
359
+ $this->assertEquals('John Doe', $result['data']['name']);
360
+ $this->assertEquals(25, $result['data']['age']);
361
+ }
362
+
363
+ public function testParseUserDataMissingFields(): void
364
+ {
365
+ $result = parseUserData(['email' => 'test@example.com']);
366
+
367
+ $this->assertFalse($result['success']);
368
+ $this->assertNotNull($result['error']);
369
+ $this->assertStringContainsString('Missing required fields', $result['error']);
370
+ $this->assertNull($result['data']);
371
+ }
372
+
373
+ public function testParseUserDataInvalidEmail(): void
374
+ {
375
+ $result = parseUserData([
376
+ 'email' => 'invalid-email',
377
+ 'name' => 'John Doe'
378
+ ]);
379
+
380
+ $this->assertFalse($result['success']);
381
+ $this->assertNotNull($result['error']);
382
+ $this->assertStringContainsString('email', $result['error']);
383
+ $this->assertNull($result['data']);
384
+ }
385
+
386
+ /**
387
+ * @dataProvider shippingProvider
388
+ */
389
+ public function testCalculateShipping(
390
+ float $weight,
391
+ string $zone,
392
+ bool $expectedSuccess,
393
+ ?float $expectedCost
394
+ ): void {
395
+ $result = calculateShipping($weight, $zone);
396
+
397
+ $this->assertEquals($expectedSuccess, $result['success']);
398
+
399
+ if ($expectedSuccess) {
400
+ $this->assertNull($result['error']);
401
+ $this->assertEquals($expectedCost, $result['data']);
402
+ } else {
403
+ $this->assertNotNull($result['error']);
404
+ $this->assertNull($result['data']);
405
+ }
406
+ }
407
+
408
+ public function shippingProvider(): array
409
+ {
410
+ return [
411
+ 'domestic_1kg' => [1.0, 'domestic', true, 5.50],
412
+ 'international_2kg' => [2.0, 'international', true, 16.00],
413
+ 'express_3kg' => [3.0, 'express', true, 26.50],
414
+ 'invalid_zone' => [1.0, 'invalid', false, null],
415
+ 'zero_weight' => [0.0, 'domestic', false, null],
416
+ 'negative_weight' => [-1.0, 'domestic', false, null]
417
+ ];
418
+ }
419
+
420
+ // ───────────────────────────────────────────────────────
421
+ // Dependency Injection Tests
422
+ // ───────────────────────────────────────────────────────
423
+
424
+ public function testProcessOrder(): void
425
+ {
426
+ $order = [
427
+ 'items' => [
428
+ ['name' => 'Product 1', 'price' => 50.00],
429
+ ['name' => 'Product 2', 'price' => 30.00]
430
+ ],
431
+ 'weight' => 2.0,
432
+ 'tax_zone' => 'CA',
433
+ 'shipping_zone' => 'domestic',
434
+ 'discount_code' => 'SAVE10'
435
+ ];
436
+
437
+ $result = processOrder(
438
+ $order,
439
+ 'PhpFp\Examples\calculateTax',
440
+ 'PhpFp\Examples\calculateShippingCost',
441
+ 'PhpFp\Examples\applyDiscountCode'
442
+ );
443
+
444
+ $this->assertEquals(80.00, $result['subtotal']);
445
+ $this->assertEquals(5.80, $result['tax']); // CA tax 7.25%
446
+ $this->assertEquals(6.00, $result['shipping']); // 5 + 2*0.5
447
+ $this->assertEquals(8.00, $result['discount']); // 10% of 80
448
+ $this->assertEquals(83.80, $result['total']); // 80 + 5.80 + 6 - 8
449
+ }
450
+
451
+ public function testProcessOrderWithMockedDependencies(): void
452
+ {
453
+ $order = [
454
+ 'items' => [['price' => 100.00]],
455
+ 'weight' => 1.0,
456
+ 'tax_zone' => 'CA',
457
+ 'shipping_zone' => 'domestic',
458
+ 'discount_code' => null
459
+ ];
460
+
461
+ // Mock dependencies for testing
462
+ $mockTax = fn($amount, $zone) => 10.00;
463
+ $mockShipping = fn($weight, $zone) => 5.00;
464
+ $mockDiscount = fn($amount, $code) => 0.00;
465
+
466
+ $result = processOrder($order, $mockTax, $mockShipping, $mockDiscount);
467
+
468
+ $this->assertEquals(100.00, $result['subtotal']);
469
+ $this->assertEquals(10.00, $result['tax']);
470
+ $this->assertEquals(5.00, $result['shipping']);
471
+ $this->assertEquals(0.00, $result['discount']);
472
+ $this->assertEquals(115.00, $result['total']);
473
+ }
474
+
475
+ /**
476
+ * @dataProvider taxProvider
477
+ */
478
+ public function testCalculateTax(float $amount, string $zone, float $expected): void
479
+ {
480
+ $result = calculateTax($amount, $zone);
481
+ $this->assertEquals($expected, $result);
482
+ }
483
+
484
+ public function taxProvider(): array
485
+ {
486
+ return [
487
+ 'CA_100' => [100.00, 'CA', 7.25],
488
+ 'NY_100' => [100.00, 'NY', 8.88],
489
+ 'TX_100' => [100.00, 'TX', 6.25],
490
+ 'unknown_zone' => [100.00, 'XX', 0.00],
491
+ 'zero_amount' => [0.00, 'CA', 0.00]
492
+ ];
493
+ }
494
+
495
+ /**
496
+ * @dataProvider discountCodeProvider
497
+ */
498
+ public function testApplyDiscountCode(float $amount, ?string $code, float $expected): void
499
+ {
500
+ $result = applyDiscountCode($amount, $code);
501
+ $this->assertEquals($expected, $result);
502
+ }
503
+
504
+ public function discountCodeProvider(): array
505
+ {
506
+ return [
507
+ 'SAVE10' => [100.00, 'SAVE10', 10.00],
508
+ 'SAVE20' => [100.00, 'SAVE20', 20.00],
509
+ 'SAVE30' => [100.00, 'SAVE30', 30.00],
510
+ 'invalid_code' => [100.00, 'INVALID', 0.00],
511
+ 'null_code' => [100.00, null, 0.00],
512
+ 'zero_amount' => [0.00, 'SAVE10', 0.00]
513
+ ];
514
+ }
515
+ }