toBeInstanceOf(Pollable::class); expect($component)->toBeInstanceOf(LiveComponentContract::class); // Check required methods exist $reflection = new ReflectionClass($component); expect($reflection->hasMethod('poll'))->toBeTrue(); expect($reflection->hasMethod('getPollInterval'))->toBeTrue(); // Check method signatures $pollMethod = $reflection->getMethod('poll'); expect($pollMethod->isPublic())->toBeTrue(); expect($pollMethod->getReturnType()?->getName())->toBe('App\Application\LiveComponents\LiveComponentState'); $intervalMethod = $reflection->getMethod('getPollInterval'); expect($intervalMethod->isPublic())->toBeTrue(); expect($intervalMethod->getReturnType()?->getName())->toBe('int'); }); it('verifies poll() returns LiveComponentState', function () { $component = new PerformanceMetricsComponent( ComponentId::create('performance-metrics', 'test'), ComponentState::empty() ); $result = $component->poll(); expect($result)->toBeInstanceOf(\App\Application\LiveComponents\LiveComponentState::class); }); it('verifies getPollInterval() returns positive integer', function () { $component = new PerformanceMetricsComponent( ComponentId::create('performance-metrics', 'test'), ComponentState::empty() ); $interval = $component->getPollInterval(); expect($interval)->toBeInt(); expect($interval)->toBeGreaterThan(0); }); }); describe('Cacheable Interface', function () { it('verifies Cacheable components implement required methods', function () { $component = new UserStatsComponent( ComponentId::create('user-stats', 'test'), ComponentState::empty() ); expect($component)->toBeInstanceOf(Cacheable::class); expect($component)->toBeInstanceOf(LiveComponentContract::class); $reflection = new ReflectionClass($component); // Check required methods expect($reflection->hasMethod('getCacheKey'))->toBeTrue(); expect($reflection->hasMethod('getCacheTTL'))->toBeTrue(); expect($reflection->hasMethod('shouldCache'))->toBeTrue(); expect($reflection->hasMethod('getCacheTags'))->toBeTrue(); expect($reflection->hasMethod('getVaryBy'))->toBeTrue(); expect($reflection->hasMethod('getStaleWhileRevalidate'))->toBeTrue(); // Check return types $ttlMethod = $reflection->getMethod('getCacheTTL'); expect($ttlMethod->getReturnType()?->getName())->toBe(Duration::class); $varyByMethod = $reflection->getMethod('getVaryBy'); $varyByReturnType = $varyByMethod->getReturnType(); expect($varyByReturnType?->allowsNull())->toBeTrue(); }); it('verifies getCacheKey() returns string', function () { $component = new UserStatsComponent( ComponentId::create('user-stats', 'test'), ComponentState::empty() ); $key = $component->getCacheKey(); expect($key)->toBeString(); expect($key)->not->toBeEmpty(); }); it('verifies getCacheTTL() returns Duration', function () { $component = new UserStatsComponent( ComponentId::create('user-stats', 'test'), ComponentState::empty() ); $ttl = $component->getCacheTTL(); expect($ttl)->toBeInstanceOf(Duration::class); }); it('verifies getCacheTags() returns array', function () { $component = new UserStatsComponent( ComponentId::create('user-stats', 'test'), ComponentState::empty() ); $tags = $component->getCacheTags(); expect($tags)->toBeArray(); }); }); describe('LifecycleAware Interface', function () { it('verifies LifecycleAware components implement required methods', function () { // Find a component that implements LifecycleAware $componentClass = CounterComponent::class; $reflection = new ReflectionClass($componentClass); if ($reflection->implementsInterface(LifecycleAware::class)) { $component = new $componentClass( ComponentId::create('counter', 'test'), \App\Application\LiveComponents\Counter\CounterState::empty() ); expect($component)->toBeInstanceOf(LifecycleAware::class); // Check required methods expect($reflection->hasMethod('onMount'))->toBeTrue(); expect($reflection->hasMethod('onUpdate'))->toBeTrue(); expect($reflection->hasMethod('onDestroy'))->toBeTrue(); // Check method signatures (all should return void) $onMountMethod = $reflection->getMethod('onMount'); expect($onMountMethod->getReturnType()?->getName())->toBe('void'); } else { // Skip if no component implements LifecycleAware in test scope $this->markTestSkipped('No LifecycleAware component found for testing'); } }); }); describe('SupportsFileUpload Interface', function () { it('verifies SupportsFileUpload components implement required methods', function () { // Find a component that implements SupportsFileUpload $componentClass = \App\Application\LiveComponents\ImageUploader\ImageUploaderComponent::class; if (!class_exists($componentClass)) { $this->markTestSkipped('ImageUploaderComponent not available'); return; } $reflection = new ReflectionClass($componentClass); if ($reflection->implementsInterface(SupportsFileUpload::class)) { expect($reflection->hasMethod('handleUpload'))->toBeTrue(); expect($reflection->hasMethod('validateUpload'))->toBeTrue(); expect($reflection->hasMethod('getAllowedMimeTypes'))->toBeTrue(); expect($reflection->hasMethod('getMaxFileSize'))->toBeTrue(); // Check handleUpload signature $handleUploadMethod = $reflection->getMethod('handleUpload'); $params = $handleUploadMethod->getParameters(); expect(count($params))->toBeGreaterThanOrEqual(1); expect($params[0]->getType()?->getName())->toBe(\App\Framework\Http\UploadedFile::class); } else { $this->markTestSkipped('No SupportsFileUpload component found for testing'); } }); }); describe('SupportsSlots Interface', function () { it('verifies SupportsSlots components implement required methods', function () { // Find a component that implements SupportsSlots $componentClass = \App\Application\LiveComponents\LayoutComponent::class; if (!class_exists($componentClass)) { $this->markTestSkipped('LayoutComponent not available'); return; } $reflection = new ReflectionClass($componentClass); if ($reflection->implementsInterface(SupportsSlots::class)) { expect($reflection->hasMethod('getSlotDefinitions'))->toBeTrue(); expect($reflection->hasMethod('processSlotContent'))->toBeTrue(); expect($reflection->hasMethod('getSlotContext'))->toBeTrue(); } else { $this->markTestSkipped('No SupportsSlots component found for testing'); } }); }); describe('LiveComponentContract Interface', function () { it('verifies all components implement LiveComponentContract', function () { $components = [ CounterComponent::class, PerformanceMetricsComponent::class, UserStatsComponent::class, FailedJobsListComponent::class, ]; foreach ($components as $componentClass) { $reflection = new ReflectionClass($componentClass); expect($reflection->implementsInterface(LiveComponentContract::class))->toBeTrue( "Component {$componentClass} must implement LiveComponentContract" ); // Check required properties expect($reflection->hasProperty('id'))->toBeTrue(); expect($reflection->hasProperty('state'))->toBeTrue(); // Check required methods expect($reflection->hasMethod('getRenderData'))->toBeTrue(); $getRenderDataMethod = $reflection->getMethod('getRenderData'); expect($getRenderDataMethod->isPublic())->toBeTrue(); expect($getRenderDataMethod->getReturnType()?->getName())->toBe( \App\Framework\LiveComponents\ValueObjects\ComponentRenderData::class ); } }); }); describe('Interface Method Signatures', function () { it('verifies Pollable::poll() signature', function () { $reflection = new ReflectionClass(Pollable::class); $pollMethod = $reflection->getMethod('poll'); expect($pollMethod->getReturnType()?->getName())->toBe(\App\Application\LiveComponents\LiveComponentState::class); expect($pollMethod->getParameters())->toBeEmpty(); }); it('verifies Cacheable::getCacheTTL() signature', function () { $reflection = new ReflectionClass(Cacheable::class); $ttlMethod = $reflection->getMethod('getCacheTTL'); expect($ttlMethod->getReturnType()?->getName())->toBe(Duration::class); expect($ttlMethod->getParameters())->toBeEmpty(); }); it('verifies SupportsFileUpload::handleUpload() signature', function () { $reflection = new ReflectionClass(SupportsFileUpload::class); $handleUploadMethod = $reflection->getMethod('handleUpload'); $params = $handleUploadMethod->getParameters(); expect(count($params))->toBeGreaterThanOrEqual(1); expect($params[0]->getType()?->getName())->toBe(\App\Framework\Http\UploadedFile::class); expect($handleUploadMethod->getReturnType()?->getName())->toBe(\App\Application\LiveComponents\LiveComponentState::class); }); }); });