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,716 @@
1
+ ---
2
+ name: "phpunit-wp"
3
+ description: "PHPUnit testing for WordPress plugins with FP principles - fast unit tests, minimal mocks, environment-aware setup. Triggers on: phpunit, unit test, test wordpress, composer test, test bootstrap, mock wordpress."
4
+ ---
5
+
6
+ # PHPUnit for WordPress Plugins
7
+
8
+ Expert guidance for PHPUnit testing in WordPress plugins, emphasizing pure function testing, minimal mocking, and FP principles.
9
+
10
+ ## When to Use This Skill
11
+
12
+ - Setting up PHPUnit for new WordPress plugins
13
+ - Debugging silent/hanging test runs
14
+ - Writing testable pure functions
15
+ - Mocking WordPress functions correctly
16
+ - Testing with FP principles
17
+
18
+ ## Core Philosophy
19
+
20
+ **Test pure functions, not WordPress integration.**
21
+
22
+ 1. **Pure business logic** = Fast unit tests (<100ms, no WordPress)
23
+ 2. **WordPress wrappers** = Integration tests (slower, full WP environment)
24
+ 3. **Mock minimally** = Only mock what's needed for pure function context
25
+
26
+ **Foundation**: Reference `../php-fp/SKILL.md` and `../php-fp-wordpress/SKILL.md` for FP patterns.
27
+
28
+ ---
29
+
30
+ ## THE TWO CRITICAL SETUP BUGS (We Keep Forgetting!)
31
+
32
+ ### 🐛 Bug #1: Silent PHPUnit Execution
33
+
34
+ **Symptom**: `composer test` produces ZERO output, not even "no tests found"
35
+
36
+ **Root Cause**: PHPUnit 9.x doesn't output anything without `--testdox` flag
37
+
38
+ **❌ BROKEN** (silent execution):
39
+ ```json
40
+ {
41
+ "scripts": {
42
+ "test": "phpunit"
43
+ }
44
+ }
45
+ ```
46
+
47
+ **✅ FIXED** (visible output):
48
+ ```json
49
+ {
50
+ "scripts": {
51
+ "test": "phpunit --colors=always --testdox",
52
+ "test:coverage": "phpunit --coverage-html coverage"
53
+ }
54
+ }
55
+ ```
56
+
57
+ ---
58
+
59
+ ### 🐛 Bug #2: Autoload Files Kill Tests
60
+
61
+ **Symptom**: Tests hang or exit silently with no error message
62
+
63
+ **Root Cause**: Composer autoload runs BEFORE test bootstrap defines `ABSPATH`
64
+
65
+ **The Fatal Flow**:
66
+ ```
67
+ 1. bootstrap.php line 17: require vendor/autoload.php
68
+ 2. Composer sees: "autoload": { "files": ["includes/helpers.php"] }
69
+ 3. Composer loads helpers.php IMMEDIATELY
70
+ 4. helpers.php line 14: if (!defined('ABSPATH')) { exit; }
71
+ 5. ABSPATH not defined yet (happens bootstrap.php line 22)
72
+ 6. Script exits silently
73
+ 7. PHPUnit never starts
74
+ ```
75
+
76
+ **❌ BROKEN** (causes silent exit):
77
+ ```json
78
+ {
79
+ "autoload": {
80
+ "files": [
81
+ "includes/helpers/url-validation.php",
82
+ "includes/helpers/share-urls.php"
83
+ ]
84
+ }
85
+ }
86
+ ```
87
+
88
+ **✅ FIXED** (no autoload files):
89
+ ```json
90
+ {
91
+ // NO autoload section with files array!
92
+ // Bootstrap loads helpers manually AFTER defining ABSPATH
93
+ "autoload-dev": {
94
+ "psr-4": {
95
+ "MyPlugin\\Tests\\": "tests/"
96
+ }
97
+ }
98
+ }
99
+ ```
100
+
101
+ ---
102
+
103
+ ## Environment Setup
104
+
105
+ ### Running Tests in Local WP Environment
106
+
107
+ **Important**: Composer and PHPUnit need Local WP's PHP environment. Git commands work in normal terminal.
108
+
109
+ **Use the project's configured Local WP environment**:
110
+
111
+ The `wp-local` skill provides the environment loader pattern. For composer/phpunit commands, create a similar wrapper or use the pattern directly:
112
+
113
+ ```bash
114
+ # Pattern from wp-local skill:
115
+ # 1. Sources ~/kitty/load-localwp-env.sh with site name from $WP_LOCAL_SITE or .wp-local
116
+ # 2. Runs command in that environment
117
+
118
+ # Example - Run tests:
119
+ bash -c "source ~/kitty/load-localwp-env.sh \$(cat .wp-local || echo \$WP_LOCAL_SITE) && composer test"
120
+
121
+ # Or install dependencies:
122
+ bash -c "source ~/kitty/load-localwp-env.sh \$(cat .wp-local || echo \$WP_LOCAL_SITE) && composer install"
123
+ ```
124
+
125
+ **Pro Tip**: Create a shell alias in your `~/.bashrc` or `~/.zshrc`:
126
+ ```bash
127
+ alias wptest='bash -c "source ~/kitty/load-localwp-env.sh \$(cat .wp-local || echo \$WP_LOCAL_SITE) && composer test"'
128
+ alias wpcomposer='bash -c "source ~/kitty/load-localwp-env.sh \$(cat .wp-local || echo \$WP_LOCAL_SITE) && composer $@"'
129
+ ```
130
+
131
+ Then simply:
132
+ ```bash
133
+ wptest # Run tests
134
+ wpcomposer install # Install dependencies
135
+ ```
136
+
137
+ ### Environment Configuration
138
+
139
+ **Priority order** (same as wp-local):
140
+ 1. `$WP_LOCAL_SITE` environment variable (set by Kitty terminal)
141
+ 2. `.wp-local` file in project root (site UUID like `19efkkzWB`)
142
+ 3. Error if neither configured
143
+
144
+ **For git operations** (no environment needed):
145
+ ```bash
146
+ # Regular terminal works fine - git doesn't need PHP environment
147
+ git status
148
+ git commit -m "fix: phpunit setup"
149
+ git push
150
+ ```
151
+
152
+ ### Quick Test Diagnosis
153
+
154
+ ```bash
155
+ # 1. Check if bootstrap prints (should see "Bootstrap Loaded")
156
+ php tests/bootstrap.php
157
+
158
+ # 2. Check if PHPUnit is installed
159
+ ls -la vendor/bin/phpunit
160
+
161
+ # 3. Check composer.json scripts
162
+ cat composer.json | grep -A 3 scripts
163
+
164
+ # 4. Check for autoload files bug
165
+ cat composer.json | grep -A 5 autoload
166
+ ```
167
+
168
+ ---
169
+
170
+ ## Copy-Paste Working Template
171
+
172
+ ### composer.json
173
+ ```json
174
+ {
175
+ "name": "ima-network/my-plugin",
176
+ "description": "Plugin description",
177
+ "type": "wordpress-plugin",
178
+ "license": "GPL-2.0-or-later",
179
+ "require": {
180
+ "php": ">=7.4"
181
+ },
182
+ "require-dev": {
183
+ "phpunit/phpunit": "^9.5"
184
+ },
185
+ "autoload-dev": {
186
+ "psr-4": {
187
+ "MyPlugin\\Tests\\": "tests/"
188
+ }
189
+ },
190
+ "scripts": {
191
+ "test": "phpunit --colors=always --testdox",
192
+ "test:coverage": "phpunit --coverage-html coverage"
193
+ }
194
+ }
195
+ ```
196
+
197
+ ### phpunit.xml
198
+ ```xml
199
+ <?xml version="1.0" encoding="UTF-8"?>
200
+ <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
201
+ xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd"
202
+ bootstrap="tests/bootstrap.php"
203
+ colors="true"
204
+ verbose="true"
205
+ stopOnFailure="false">
206
+ <testsuites>
207
+ <testsuite name="Unit">
208
+ <directory>tests/Unit</directory>
209
+ </testsuite>
210
+ </testsuites>
211
+ <coverage>
212
+ <include>
213
+ <directory suffix=".php">includes</directory>
214
+ </include>
215
+ <exclude>
216
+ <directory>tests</directory>
217
+ <directory>templates</directory>
218
+ </exclude>
219
+ </coverage>
220
+ <php>
221
+ <ini name="error_reporting" value="E_ALL"/>
222
+ <ini name="display_errors" value="true"/>
223
+ </php>
224
+ </phpunit>
225
+ ```
226
+
227
+ ### tests/bootstrap.php
228
+ ```php
229
+ <?php
230
+ /**
231
+ * PHPUnit Bootstrap for My Plugin
232
+ *
233
+ * Loads pure functions for unit testing without WordPress dependencies.
234
+ */
235
+ declare(strict_types=1);
236
+
237
+ // 1. Load Composer autoloader FIRST (but it won't autoload files - we removed that!)
238
+ require_once dirname(__DIR__) . '/vendor/autoload.php';
239
+
240
+ // 2. Define ABSPATH to prevent plugin file exits
241
+ if (!defined('ABSPATH')) {
242
+ define('ABSPATH', '/tmp/wordpress/');
243
+ }
244
+
245
+ // 3. Define plugin constants
246
+ if (!defined('MY_PLUGIN_PATH')) {
247
+ define('MY_PLUGIN_PATH', dirname(__DIR__) . '/');
248
+ }
249
+
250
+ // 4. NOW manually load helper files (AFTER ABSPATH defined!)
251
+ require_once MY_PLUGIN_PATH . 'includes/helpers/url-validation.php';
252
+ require_once MY_PLUGIN_PATH . 'includes/helpers/share-urls.php';
253
+
254
+ // 5. Mock WordPress functions minimally
255
+ if (!function_exists('home_url')) {
256
+ function home_url(string $path = ''): string {
257
+ return 'https://example.com' . $path;
258
+ }
259
+ }
260
+
261
+ if (!function_exists('sanitize_text_field')) {
262
+ function sanitize_text_field(string $str): string {
263
+ $filtered = strip_tags($str);
264
+ $filtered = str_replace(["\r", "\n"], '', $filtered);
265
+ return trim($filtered);
266
+ }
267
+ }
268
+
269
+ if (!function_exists('esc_html')) {
270
+ function esc_html(string $text): string {
271
+ return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
272
+ }
273
+ }
274
+
275
+ // 6. Confirmation message (proves bootstrap loaded)
276
+ echo "✅ My Plugin Test Bootstrap Loaded\n";
277
+ ```
278
+
279
+ ---
280
+
281
+ ## What to Test (FP Principles)
282
+
283
+ ### ✅ DO TEST: Pure Business Logic
284
+
285
+ ```php
286
+ <?php
287
+ // includes/helpers/validation.php
288
+ declare(strict_types=1);
289
+
290
+ /**
291
+ * PURE function - Zero WordPress dependencies
292
+ * Perfect for unit testing
293
+ */
294
+ function my_plugin_validate_email_pure(string $email): array {
295
+ if (empty($email)) {
296
+ return ['valid' => false, 'error' => 'Email required'];
297
+ }
298
+
299
+ if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
300
+ return ['valid' => false, 'error' => 'Invalid email format'];
301
+ }
302
+
303
+ // Check for disposable domains
304
+ $disposable = ['tempmail.com', 'throwaway.email'];
305
+ $domain = substr(strrchr($email, '@'), 1);
306
+ if (in_array($domain, $disposable)) {
307
+ return ['valid' => false, 'error' => 'Disposable email not allowed'];
308
+ }
309
+
310
+ return ['valid' => true];
311
+ }
312
+ ```
313
+
314
+ **Test**:
315
+ ```php
316
+ <?php
317
+ // tests/Unit/ValidationTest.php
318
+ use PHPUnit\Framework\TestCase;
319
+
320
+ class ValidationTest extends TestCase {
321
+ public function test_empty_email_returns_error() {
322
+ $result = my_plugin_validate_email_pure('');
323
+
324
+ $this->assertFalse($result['valid']);
325
+ $this->assertEquals('Email required', $result['error']);
326
+ }
327
+
328
+ public function test_invalid_format_returns_error() {
329
+ $result = my_plugin_validate_email_pure('not-an-email');
330
+
331
+ $this->assertFalse($result['valid']);
332
+ $this->assertEquals('Invalid email format', $result['error']);
333
+ }
334
+
335
+ public function test_disposable_email_rejected() {
336
+ $result = my_plugin_validate_email_pure('user@tempmail.com');
337
+
338
+ $this->assertFalse($result['valid']);
339
+ $this->assertEquals('Disposable email not allowed', $result['error']);
340
+ }
341
+
342
+ public function test_valid_email_passes() {
343
+ $result = my_plugin_validate_email_pure('user@example.com');
344
+
345
+ $this->assertTrue($result['valid']);
346
+ $this->assertArrayNotHasKey('error', $result);
347
+ }
348
+
349
+ // FP Principle: Test determinism
350
+ public function test_function_is_deterministic() {
351
+ $email = 'test@example.com';
352
+ $result1 = my_plugin_validate_email_pure($email);
353
+ $result2 = my_plugin_validate_email_pure($email);
354
+
355
+ $this->assertEquals($result1, $result2);
356
+ }
357
+ }
358
+ ```
359
+
360
+ ---
361
+
362
+ ### ❌ DON'T TEST: WordPress Integration Wrappers
363
+
364
+ ```php
365
+ <?php
366
+ // includes/ajax-handlers.php - WordPress wrapper
367
+ function my_plugin_ajax_validate_email() {
368
+ // DON'T unit test this - it's all WordPress integration!
369
+ check_ajax_referer('validate_email_nonce', 'nonce');
370
+
371
+ if (!current_user_can('read')) {
372
+ wp_send_json_error('Unauthorized', 403);
373
+ }
374
+
375
+ $email = sanitize_email($_POST['email']);
376
+
377
+ // Call pure function (which IS tested)
378
+ $result = my_plugin_validate_email_pure($email);
379
+
380
+ wp_send_json_success($result);
381
+ }
382
+ add_action('wp_ajax_my_plugin_validate_email', 'my_plugin_ajax_validate_email');
383
+ ```
384
+
385
+ **Why not test**: This is 100% WordPress integration. Testing it requires:
386
+ - Full WordPress environment
387
+ - Mocking `$_POST`, nonces, capabilities, AJAX functions
388
+ - Complex setup that's brittle and slow
389
+
390
+ **Better approach**:
391
+ - Unit test the pure function (`my_plugin_validate_email_pure`) ✅
392
+ - Integration test the AJAX handler with real WordPress (separate test suite)
393
+
394
+ ---
395
+
396
+ ## Mocking Rules (Minimal Philosophy)
397
+
398
+ ### Mock Only What's Needed for Pure Context
399
+
400
+ **❌ DON'T over-mock**:
401
+ ```php
402
+ <?php
403
+ // Excessive mocking for a pure function test
404
+ if (!function_exists('wp_remote_post')) { /* ... */ }
405
+ if (!function_exists('wp_remote_get')) { /* ... */ }
406
+ if (!function_exists('get_option')) { /* ... */ }
407
+ if (!function_exists('update_option')) { /* ... */ }
408
+ if (!function_exists('delete_option')) { /* ... */ }
409
+ // ... 50 more mocks you don't need
410
+ ```
411
+
412
+ **✅ DO mock minimally**:
413
+ ```php
414
+ <?php
415
+ // Only mock what pure functions actually use
416
+ if (!function_exists('sanitize_text_field')) {
417
+ function sanitize_text_field(string $str): string {
418
+ return trim(strip_tags($str));
419
+ }
420
+ }
421
+
422
+ if (!function_exists('home_url')) {
423
+ function home_url(string $path = ''): string {
424
+ return 'https://example.com' . $path;
425
+ }
426
+ }
427
+ ```
428
+
429
+ ### Common WordPress Mocks for Pure Functions
430
+
431
+ ```php
432
+ <?php
433
+ // WordPress utility functions (pure-ish)
434
+ if (!function_exists('wp_parse_args')) {
435
+ function wp_parse_args($args, $defaults = []): array {
436
+ if (is_string($args)) {
437
+ parse_str($args, $parsed_args);
438
+ $args = $parsed_args;
439
+ }
440
+ return array_merge($defaults, (array) $args);
441
+ }
442
+ }
443
+
444
+ // Sanitization functions
445
+ if (!function_exists('sanitize_email')) {
446
+ function sanitize_email(string $email): string {
447
+ return strtolower(trim($email));
448
+ }
449
+ }
450
+
451
+ if (!function_exists('esc_url_raw')) {
452
+ function esc_url_raw(string $url): string {
453
+ return filter_var($url, FILTER_SANITIZE_URL) ?: '';
454
+ }
455
+ }
456
+
457
+ // URL parsing (WordPress wrapper for parse_url)
458
+ if (!function_exists('wp_parse_url')) {
459
+ function wp_parse_url(string $url, int $component = -1) {
460
+ return $component === -1 ? parse_url($url) : parse_url($url, $component);
461
+ }
462
+ }
463
+ ```
464
+
465
+ ---
466
+
467
+ ## Test Organization Patterns
468
+
469
+ ### Pattern 1: Pure Function Testing (Fastest)
470
+
471
+ ```
472
+ tests/
473
+ └── Unit/
474
+ ├── ValidationTest.php # Pure validation functions
475
+ ├── CalculationTest.php # Pure calculation logic
476
+ └── FormatterTest.php # Pure formatting functions
477
+ ```
478
+
479
+ **Characteristics**:
480
+ - No WordPress dependencies
481
+ - Fast (<100ms total)
482
+ - No database, no HTTP, no filesystem
483
+ - Run on every commit
484
+
485
+ ### Pattern 2: Integration Testing (Slower)
486
+
487
+ ```
488
+ tests/
489
+ └── Integration/
490
+ ├── AjaxHandlerTest.php # Full WordPress + AJAX
491
+ ├── ShortcodeTest.php # Full WordPress + rendering
492
+ └── DatabaseTest.php # Full WordPress + DB
493
+ ```
494
+
495
+ **Characteristics**:
496
+ - Requires full WordPress installation
497
+ - Uses WP_UnitTestCase from wordpress-develop
498
+ - Slower (seconds to minutes)
499
+ - Run before releases
500
+
501
+ ---
502
+
503
+ ## Testing Patterns by Function Type
504
+
505
+ ### Pure Calculations
506
+ ```php
507
+ <?php
508
+ function calculate_discount_pure(float $price, float $rate): float {
509
+ return round($price * (1 - $rate), 2);
510
+ }
511
+
512
+ // Test: Assert exact values
513
+ $this->assertEquals(90.0, calculate_discount_pure(100.0, 0.10));
514
+ $this->assertEquals(95.0, calculate_discount_pure(100.0, 0.05));
515
+ ```
516
+
517
+ ### Pure Validation
518
+ ```php
519
+ <?php
520
+ function validate_age_pure(int $age): array {
521
+ if ($age < 0) return ['valid' => false, 'error' => 'Negative age'];
522
+ if ($age < 18) return ['valid' => false, 'error' => 'Must be 18+'];
523
+ return ['valid' => true];
524
+ }
525
+
526
+ // Test: Assert result structure
527
+ $result = validate_age_pure(15);
528
+ $this->assertFalse($result['valid']);
529
+ $this->assertArrayHasKey('error', $result);
530
+ ```
531
+
532
+ ### Pure Transformations
533
+ ```php
534
+ <?php
535
+ function format_phone_pure(string $phone): string {
536
+ $digits = preg_replace('/\D/', '', $phone);
537
+ if (strlen($digits) === 10) {
538
+ return sprintf('(%s) %s-%s',
539
+ substr($digits, 0, 3),
540
+ substr($digits, 3, 3),
541
+ substr($digits, 6, 4)
542
+ );
543
+ }
544
+ return $phone;
545
+ }
546
+
547
+ // Test: Assert transformations
548
+ $this->assertEquals('(555) 123-4567', format_phone_pure('5551234567'));
549
+ $this->assertEquals('(555) 123-4567', format_phone_pure('555-123-4567'));
550
+ $this->assertEquals('invalid', format_phone_pure('invalid'));
551
+ ```
552
+
553
+ ---
554
+
555
+ ## Performance Testing (FP Principle)
556
+
557
+ Pure functions enable easy performance testing:
558
+
559
+ ```php
560
+ <?php
561
+ public function test_performance_fast_execution() {
562
+ $iterations = 10000;
563
+ $start = microtime(true);
564
+
565
+ for ($i = 0; $i < $iterations; $i++) {
566
+ my_plugin_validate_email_pure('user@example.com');
567
+ }
568
+
569
+ $elapsed = microtime(true) - $start;
570
+
571
+ // Should complete 10k validations in < 100ms
572
+ $this->assertLessThan(0.1, $elapsed);
573
+ }
574
+ ```
575
+
576
+ ---
577
+
578
+ ## Common Anti-Patterns
579
+
580
+ ### ❌ Testing Private Methods
581
+
582
+ ```php
583
+ <?php
584
+ // DON'T use reflection to test private methods
585
+ class MyTest extends TestCase {
586
+ public function test_private_method() {
587
+ $reflection = new ReflectionClass(MyClass::class);
588
+ $method = $reflection->getMethod('privateMethod');
589
+ $method->setAccessible(true);
590
+ // ...
591
+ }
592
+ }
593
+
594
+ // DO extract pure function and test that
595
+ function my_plugin_process_data_pure(array $data): array {
596
+ // Extracted logic, now testable
597
+ return $data;
598
+ }
599
+ ```
600
+
601
+ ### ❌ Testing Implementation Details
602
+
603
+ ```php
604
+ <?php
605
+ // DON'T test internal variable values
606
+ public function test_internal_state() {
607
+ $obj = new MyClass();
608
+ $this->assertEquals(5, $obj->internalCounter); // Brittle!
609
+ }
610
+
611
+ // DO test public behavior
612
+ public function test_public_behavior() {
613
+ $result = my_plugin_process_items(['a', 'b', 'c']);
614
+ $this->assertCount(3, $result);
615
+ }
616
+ ```
617
+
618
+ ### ❌ Over-Mocking
619
+
620
+ ```php
621
+ <?php
622
+ // DON'T mock everything
623
+ $mock = $this->createMock(Database::class);
624
+ $mock->method('query')->willReturn([]);
625
+ $mock->method('insert')->willReturn(1);
626
+ $mock->method('update')->willReturn(true);
627
+ // ... 20 more mocks
628
+
629
+ // DO test pure functions that don't need mocks
630
+ function process_results_pure(array $results): array {
631
+ return array_map('strtoupper', $results);
632
+ }
633
+ ```
634
+
635
+ ---
636
+
637
+ ## Quality Gates
638
+
639
+ Before merging:
640
+ - [ ] `composer test` produces visible output (not silent)
641
+ - [ ] Bootstrap prints "Bootstrap Loaded" message
642
+ - [ ] Tests run in < 100ms for pure functions
643
+ - [ ] No autoload files section in composer.json
644
+ - [ ] Pure business logic separated from WordPress wrappers
645
+ - [ ] Mock count is minimal (< 10 functions)
646
+ - [ ] Test assertions are deterministic (no flaky tests)
647
+
648
+ ---
649
+
650
+ ## Working Examples
651
+
652
+ **Reference plugins with working tests**:
653
+ ```bash
654
+ # ima-forms: Gold standard
655
+ cd wp-content/plugins/ima-forms
656
+ composer test
657
+
658
+ # ima-shortcodes: Recently fixed
659
+ cd wp-content/plugins/ima-shortcodes
660
+ composer test
661
+ ```
662
+
663
+ ---
664
+
665
+ ## Troubleshooting
666
+
667
+ ### Issue: Silent test execution
668
+
669
+ **Symptom**: `composer test` produces no output
670
+
671
+ **Fix**: Add `--testdox` flag to composer.json scripts
672
+
673
+ ### Issue: Tests hang/exit silently
674
+
675
+ **Symptom**: Process hangs or exits without error
676
+
677
+ **Diagnosis**:
678
+ ```bash
679
+ # Check if bootstrap runs
680
+ php tests/bootstrap.php
681
+
682
+ # Check for autoload files bug
683
+ cat composer.json | grep -A 5 autoload
684
+ ```
685
+
686
+ **Fix**: Remove autoload files section, load manually in bootstrap
687
+
688
+ ### Issue: "Class not found" errors
689
+
690
+ **Symptom**: PHPUnit can't find test classes
691
+
692
+ **Fix**: Check `autoload-dev` PSR-4 mapping in composer.json:
693
+ ```json
694
+ "autoload-dev": {
695
+ "psr-4": {
696
+ "MyPlugin\\Tests\\": "tests/"
697
+ }
698
+ }
699
+ ```
700
+
701
+ Then run: `composer dump-autoload`
702
+
703
+ ---
704
+
705
+ ## References
706
+
707
+ - `../php-fp/SKILL.md` - Core FP principles
708
+ - `../php-fp-wordpress/SKILL.md` - WordPress security + FP
709
+ - `../wp-local/SKILL.md` - Local WP environment handling
710
+ - PHPUnit docs: https://phpunit.de/documentation.html
711
+ - WordPress test suite: https://make.wordpress.org/core/handbook/testing/automated-testing/phpunit/
712
+
713
+ ---
714
+
715
+ **Last Updated**: 2026-01-29
716
+ **Discovery**: After debugging the same bugs across multiple plugins (ima-forms, ima-shortcodes, ima-access-control)