5]); $component = new CounterComponent($id, $data); assert($component instanceof CounterComponent, 'Component should be CounterComponent'); assert($component->getId() instanceof ComponentId, 'getId() should return ComponentId'); assert($component->getData() instanceof ComponentData, 'getData() should return ComponentData'); assert($component->getData()->get('count') === 5, 'Initial count should be 5'); echo "✅ CounterComponent constructor accepts strict types\n"; $testsPassed++; } catch (\Throwable $e) { echo "❌ CounterComponent constructor test failed: {$e->getMessage()}\n"; $testsFailed++; } // Test 2: CounterComponent increment action returns ComponentData echo "\nTest 2: CounterComponent increment action returns ComponentData\n"; try { $id = ComponentId::create('counter', 'test-action'); $data = ComponentData::fromArray(['count' => 10]); $component = new CounterComponent($id, $data); $params = ActionParameters::empty(); $result = $component->increment($params); assert($result instanceof ComponentData, 'increment() should return ComponentData'); assert($result->get('count') === 11, 'Count should be incremented to 11'); echo "✅ CounterComponent increment returns ComponentData\n"; $testsPassed++; } catch (\Throwable $e) { echo "❌ CounterComponent action test failed: {$e->getMessage()}\n"; $testsFailed++; } // Test 3: NotificationBellComponent constructor with strict types echo "\nTest 3: NotificationBellComponent constructor with strict types\n"; try { $id = ComponentId::create('notification-bell', 'user-123'); $data = ComponentData::fromArray([ 'user_id' => '123', 'unread_count' => 3, ]); $component = new NotificationBellComponent($id, $data); assert($component instanceof NotificationBellComponent, 'Component should be NotificationBellComponent'); assert($component->getId() instanceof ComponentId, 'getId() should return ComponentId'); assert($component->getData() instanceof ComponentData, 'getData() should return ComponentData'); assert($component->getData()->get('unread_count') === 3, 'Initial unread_count should be 3'); echo "✅ NotificationBellComponent constructor accepts strict types\n"; $testsPassed++; } catch (\Throwable $e) { echo "❌ NotificationBellComponent test failed: {$e->getMessage()}\n"; $testsFailed++; } // Test 4: NotificationBellComponent poll returns ComponentData echo "\nTest 4: NotificationBellComponent poll returns ComponentData\n"; try { $id = ComponentId::create('notification-bell', 'user-poll'); $data = ComponentData::fromArray(['user_id' => '456']); $component = new NotificationBellComponent($id, $data); $params = ActionParameters::empty(); $result = $component->poll($params); assert($result instanceof ComponentData, 'poll() should return ComponentData'); assert($result->has('unread_count'), 'Result should have unread_count'); assert($result->has('notifications'), 'Result should have notifications'); echo "✅ NotificationBellComponent poll returns ComponentData\n"; $testsPassed++; } catch (\Throwable $e) { echo "❌ NotificationBellComponent poll test failed: {$e->getMessage()}\n"; $testsFailed++; } // Test 5: StatsComponent constructor with strict types echo "\nTest 5: StatsComponent constructor with strict types\n"; try { $id = ComponentId::create('stats', 'dashboard'); $data = ComponentData::fromArray(['cache_enabled' => true]); $component = new StatsComponent($id, $data); assert($component instanceof StatsComponent, 'Component should be StatsComponent'); assert($component->getId() instanceof ComponentId, 'getId() should return ComponentId'); assert($component->getData() instanceof ComponentData, 'getData() should return ComponentData'); assert($component->getData()->get('cache_enabled') === true, 'cache_enabled should be true'); echo "✅ StatsComponent constructor accepts strict types\n"; $testsPassed++; } catch (\Throwable $e) { echo "❌ StatsComponent test failed: {$e->getMessage()}\n"; $testsFailed++; } // Test 6: StatsComponent toggleCache returns ComponentData echo "\nTest 6: StatsComponent toggleCache returns ComponentData\n"; try { $id = ComponentId::create('stats', 'toggle-test'); $data = ComponentData::fromArray(['cache_enabled' => true]); $component = new StatsComponent($id, $data); $params = ActionParameters::empty(); $result = $component->toggleCache($params); assert($result instanceof ComponentData, 'toggleCache() should return ComponentData'); assert($result->get('cache_enabled') === false, 'cache_enabled should be toggled to false'); echo "✅ StatsComponent toggleCache returns ComponentData\n"; $testsPassed++; } catch (\Throwable $e) { echo "❌ StatsComponent toggle test failed: {$e->getMessage()}\n"; $testsFailed++; } // Test 7: Component constructor with null ComponentData echo "\nTest 7: Component constructor with null ComponentData\n"; try { $id = ComponentId::create('counter', 'null-data-test'); // Pass null, should use ComponentData::empty() $component = new CounterComponent($id, null); assert($component instanceof CounterComponent, 'Component should be CounterComponent'); assert($component->getData() instanceof ComponentData, 'getData() should return ComponentData'); assert($component->getData()->get('count', 0) === 0, 'Default count should be 0'); echo "✅ Component constructor handles null ComponentData correctly\n"; $testsPassed++; } catch (\Throwable $e) { echo "❌ Component null data test failed: {$e->getMessage()}\n"; $testsFailed++; } // Test 8: ComponentData immutability echo "\nTest 8: ComponentData immutability\n"; try { $id = ComponentId::create('counter', 'immutability-test'); $originalData = ComponentData::fromArray(['count' => 10]); $component = new CounterComponent($id, $originalData); $params = ActionParameters::empty(); $newData = $component->increment($params); assert($originalData->get('count') === 10, 'Original data should be unchanged'); assert($newData->get('count') === 11, 'New data should be incremented'); assert($originalData !== $newData, 'Should be different instances'); echo "✅ ComponentData immutability preserved\n"; $testsPassed++; } catch (\Throwable $e) { echo "❌ Immutability test failed: {$e->getMessage()}\n"; $testsFailed++; } // Test 9: ComponentId toString consistency echo "\nTest 9: ComponentId toString consistency\n"; try { $id = ComponentId::create('counter', 'id-test'); $idString = $id->toString(); assert($idString === 'counter:id-test', 'ComponentId toString should match expected format'); $parsedId = ComponentId::fromString($idString); assert($parsedId->toString() === $idString, 'Parsed ID should match original'); echo "✅ ComponentId toString/fromString working correctly\n"; $testsPassed++; } catch (\Throwable $e) { echo "❌ ComponentId test failed: {$e->getMessage()}\n"; $testsFailed++; } // Test 10: ActionParameters type coercion echo "\nTest 10: ActionParameters type coercion\n"; try { $params = ActionParameters::fromArray([ 'amount' => '5', // String 'enabled' => 'true', // String boolean 'data' => ['key' => 'value'], ]); assert($params->getInt('amount') === 5, 'getString should coerce to int'); assert($params->getBool('enabled') === true, 'getBool should coerce string to bool'); assert($params->getArray('data') === ['key' => 'value'], 'getArray should return array'); echo "✅ ActionParameters type coercion working\n"; $testsPassed++; } catch (\Throwable $e) { echo "❌ ActionParameters test failed: {$e->getMessage()}\n"; $testsFailed++; } // Summary echo "\n=== Test Summary ===\n"; echo "✅ Passed: {$testsPassed}\n"; echo "❌ Failed: {$testsFailed}\n"; echo "Total: " . ($testsPassed + $testsFailed) . "\n"; if ($testsFailed === 0) { echo "\n🎉 All strict type migration tests passed!\n"; exit(0); } else { echo "\n⚠️ Some tests failed. Please review the output above.\n"; exit(1); }