Some checks failed
Deploy Application / deploy (push) Has been cancelled
276 lines
12 KiB
PHP
276 lines
12 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Feature\Framework\LiveComponents;
|
|
|
|
use App\Application\LiveComponents\Counter\CounterComponent;
|
|
use App\Application\LiveComponents\Dashboard\FailedJobsListComponent;
|
|
use App\Application\LiveComponents\Dashboard\PerformanceMetricsComponent;
|
|
use App\Application\LiveComponents\UserStatsComponent;
|
|
use App\Framework\Core\ValueObjects\Duration;
|
|
use App\Framework\LiveComponents\Contracts\Cacheable;
|
|
use App\Framework\LiveComponents\Contracts\LifecycleAware;
|
|
use App\Framework\LiveComponents\Contracts\LiveComponentContract;
|
|
use App\Framework\LiveComponents\Contracts\Pollable;
|
|
use App\Framework\LiveComponents\Contracts\SupportsFileUpload;
|
|
use App\Framework\LiveComponents\Contracts\SupportsSlots;
|
|
use App\Framework\LiveComponents\ValueObjects\ComponentId;
|
|
use App\Framework\LiveComponents\ValueObjects\ComponentState;
|
|
use ReflectionClass;
|
|
use ReflectionMethod;
|
|
|
|
describe('Contract Compliance Tests', function () {
|
|
describe('Pollable Interface', function () {
|
|
it('verifies Pollable components implement required methods', function () {
|
|
$component = new PerformanceMetricsComponent(
|
|
ComponentId::create('performance-metrics', 'test'),
|
|
ComponentState::empty()
|
|
);
|
|
|
|
expect($component)->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);
|
|
});
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
|