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,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
|
+
}
|