tracker = new NestedPerformanceTracker( new SystemClock(), new SystemHighResolutionClock(), new MemoryMonitor() ); $this->profiler = new ActionProfiler($this->tracker); // Mock dependencies for LiveComponentHandler $this->eventDispatcher = new ComponentEventDispatcher(); // Simple anonymous class mocks instead of Mockery (to avoid readonly property issues) $this->session = new class implements \App\Framework\Http\Session\SessionInterface { public readonly object $csrf; public readonly object $validation; public readonly object $form; public function __construct() { $this->csrf = new class { public function validateToken(string $formId, string $token): bool { return true; } }; $this->validation = new class {}; $this->form = new class {}; } public function get(string $key, mixed $default = null): mixed { return $default; } public function set(string $key, mixed $value): void {} public function has(string $key): bool { return false; } public function remove(string $key): void {} public function clear(): void {} public function all(): array { return []; } public function getId(): \App\Framework\Http\Session\SessionId { return new \App\Framework\Http\Session\SessionId('test-session-id'); } public static function fromArray(\App\Framework\Http\Session\SessionId $sessionId, \App\Framework\DateTime\Clock $clock, \App\Framework\Security\CsrfTokenGenerator $csrfTokenGenerator, array $data): self { return new self(); } }; $this->authChecker = new class implements \App\Framework\LiveComponents\Security\ActionAuthorizationChecker { public function isAuthorized($component, string $method, $permissionAttribute): bool { return true; } public function getUserPermissions(): array { return []; } public function hasPermission(string $permission): bool { return true; } public function isAuthenticated(): bool { return true; } }; $this->schemaCache = new SchemaCache(); $this->rateLimiter = new class { public function checkActionLimit($component, string $method, string $clientId, $actionAttr): \App\Framework\LiveComponents\ValueObjects\RateLimitResult { return new \App\Framework\LiveComponents\ValueObjects\RateLimitResult( allowed: true, limit: 100, current: 1, retryAfter: null ); } }; $this->idempotency = new class { public function execute(mixed $key, callable $operation, mixed $ttl): mixed { return $operation(); } }; $this->parameterBinder = new class { public function bindParameters(\ReflectionMethod $method, ActionParameters $params): array { return []; } }; $this->frameworkDispatcher = new class { public function dispatch($event): void {} }; // Create handler with performance tracker $this->handler = new LiveComponentHandler( $this->eventDispatcher, $this->session, $this->authChecker, $this->schemaCache, $this->rateLimiter, $this->idempotency, $this->parameterBinder, $this->frameworkDispatcher, $this->tracker // PerformanceTracker injected here ); }); it('tracks performance for component action execution', function () { // Create test component $component = new class implements LiveComponentContract { public ComponentId $id; public object $state; public function __construct() { $this->id = ComponentId::create('test-counter', 'profiling-1'); $this->state = new class { public int $count = 0; public function toArray(): array { return ['count' => $this->count]; } }; } #[Action] public function increment(): object { // Simulate some work usleep(5000); // 5ms $newState = clone $this->state; $newState->count++; return $newState; } }; // Execute action $params = ActionParameters::create([ '_csrf_token' => 'valid-token' ]); $this->handler->handle($component, 'increment', $params); // Verify performance tracking $timeline = $this->tracker->generateTimeline(); expect($timeline)->toBeArray(); expect($timeline)->not->toBeEmpty(); // Find the main component action measurement $componentMeasurements = array_filter($timeline, function ($event) { return str_contains($event['name'], 'livecomponent.test-counter.increment'); }); expect($componentMeasurements)->not->toBeEmpty(); $mainMeasurement = array_values($componentMeasurements)[0]; expect($mainMeasurement['category'])->toBe('custom'); expect($mainMeasurement['duration_ms'])->toBeGreaterThan(4); // At least 5ms from usleep }); it('tracks nested profiling for schema derivation', function () { $component = new class implements LiveComponentContract { public ComponentId $id; public object $state; public function __construct() { $this->id = ComponentId::create('schema-test', 'nested-1'); $this->state = new class { public string $value = 'test'; public function toArray(): array { return ['value' => $this->value]; } }; } #[Action] public function update(): object { return $this->state; } }; $params = ActionParameters::create(['_csrf_token' => 'valid-token']); $this->handler->handle($component, 'update', $params); $timeline = $this->tracker->generateTimeline(); // Should have nested measurements for: // 1. Main component action // 2. Schema derivation // 3. Action execution // 4. State validation $schemaMeasurements = array_filter($timeline, fn($e) => str_contains($e['name'], 'schema.derive')); $actionMeasurements = array_filter($timeline, fn($e) => str_contains($e['name'], 'action.execute')); $validationMeasurements = array_filter($timeline, fn($e) => str_contains($e['name'], 'state.validate')); expect($schemaMeasurements)->not->toBeEmpty(); expect($actionMeasurements)->not->toBeEmpty(); expect($validationMeasurements)->not->toBeEmpty(); }); it('tracks lifecycle hook performance (onMount)', function () { $component = new class implements LiveComponentContract, \App\Framework\LiveComponents\Contracts\LifecycleAware { public ComponentId $id; public object $state; public bool $mountCalled = false; public function __construct() { $this->id = ComponentId::create('lifecycle-test', 'mount-1'); $this->state = new class { public array $data = []; public function toArray(): array { return $this->data; } }; } public function onMount(): void { // Simulate initialization work usleep(2000); // 2ms $this->mountCalled = true; } public function onUpdate(): void { // Not tested here } #[Action] public function test(): object { return $this->state; } }; // Call onMount via handler $this->handler->callMountHook($component); expect($component->mountCalled)->toBeTrue(); $timeline = $this->tracker->generateTimeline(); $mountMeasurements = array_filter($timeline, fn($e) => str_contains($e['name'], 'lifecycle.onMount')); expect($mountMeasurements)->not->toBeEmpty(); $mountMeasurement = array_values($mountMeasurements)[0]; expect($mountMeasurement['duration_ms'])->toBeGreaterThan(1); // At least 2ms expect($mountMeasurement['context'])->toHaveKey('component'); expect($mountMeasurement['context']['component'])->toBe('lifecycle-test'); }); it('tracks lifecycle hook performance (onUpdate)', function () { $component = new class implements LiveComponentContract, \App\Framework\LiveComponents\Contracts\LifecycleAware { public ComponentId $id; public object $state; public bool $updateCalled = false; public function __construct() { $this->id = ComponentId::create('lifecycle-test', 'update-1'); $this->state = new class { public int $counter = 0; public function toArray(): array { return ['counter' => $this->counter]; } }; } public function onMount(): void { // Not tested here } public function onUpdate(): void { // Simulate update work usleep(3000); // 3ms $this->updateCalled = true; } #[Action] public function increment(): object { $newState = clone $this->state; $newState->counter++; return $newState; } }; $params = ActionParameters::create(['_csrf_token' => 'valid-token']); $this->handler->handle($component, 'increment', $params); expect($component->updateCalled)->toBeTrue(); $timeline = $this->tracker->generateTimeline(); $updateMeasurements = array_filter($timeline, fn($e) => str_contains($e['name'], 'lifecycle.onUpdate')); expect($updateMeasurements)->not->toBeEmpty(); $updateMeasurement = array_values($updateMeasurements)[0]; expect($updateMeasurement['duration_ms'])->toBeGreaterThan(2); // At least 3ms }); it('provides context data for all profiled operations', function () { $component = new class implements LiveComponentContract { public ComponentId $id; public object $state; public function __construct() { $this->id = ComponentId::create('context-component', 'ctx-1'); $this->state = new class { public string $name = 'test'; public function toArray(): array { return ['name' => $this->name]; } }; } #[Action] public function doSomething(): object { usleep(1000); return $this->state; } }; $params = ActionParameters::create(['_csrf_token' => 'valid-token']); $this->handler->handle($component, 'doSomething', $params); $timeline = $this->tracker->generateTimeline(); // Check that context is provided for measurements foreach ($timeline as $event) { if (str_contains($event['name'], 'livecomponent.context-component')) { expect($event)->toHaveKey('context'); expect($event['context'])->toHaveKey('component'); expect($event['context']['component'])->toBe('context-component'); if (str_contains($event['name'], 'livecomponent.context-component.doSomething')) { expect($event['context'])->toHaveKey('action'); expect($event['context']['action'])->toBe('doSomething'); } } } }); it('tracks memory usage for component actions', function () { $component = new class implements LiveComponentContract { public ComponentId $id; public object $state; public function __construct() { $this->id = ComponentId::create('memory-test', 'mem-1'); $this->state = new class { public array $data = []; public function toArray(): array { return $this->data; } }; } #[Action] public function allocateMemory(): object { // Allocate some memory $data = array_fill(0, 5000, str_repeat('x', 100)); // ~0.5MB $newState = clone $this->state; $newState->data = $data; return $newState; } }; $params = ActionParameters::create(['_csrf_token' => 'valid-token']); $this->handler->handle($component, 'allocateMemory', $params); $timeline = $this->tracker->generateTimeline(); // Find component action measurement $componentMeasurements = array_filter($timeline, function ($event) { return str_contains($event['name'], 'livecomponent.memory-test.allocateMemory'); }); expect($componentMeasurements)->not->toBeEmpty(); $measurement = array_values($componentMeasurements)[0]; // Should have memory tracking expect($measurement)->toHaveKey('memory_delta_mb'); expect($measurement['memory_delta_mb'])->toBeGreaterThan(0); }); it('integrates with ActionProfiler for detailed metrics', function () { $component = new class implements LiveComponentContract { public ComponentId $id; public object $state; public function __construct() { $this->id = ComponentId::create('profiler-test', 'prof-1'); $this->state = new class { public int $value = 0; public function toArray(): array { return ['value' => $this->value]; } }; } #[Action] public function process(): object { usleep(10000); // 10ms return $this->state; } }; $params = ActionParameters::create(['_csrf_token' => 'valid-token']); // Execute action multiple times for ($i = 0; $i < 3; $i++) { $this->handler->handle($component, 'process', $params); $this->tracker->reset(); // Reset for next execution } // Note: ActionProfiler methods would need to be used here // This test verifies the integration point exists expect($this->profiler)->toBeInstanceOf(ActionProfiler::class); }); it('handles fast actions without overhead', function () { $component = new class implements LiveComponentContract { public ComponentId $id; public object $state; public function __construct() { $this->id = ComponentId::create('fast-action', 'fast-1'); $this->state = new class { public bool $flag = false; public function toArray(): array { return ['flag' => $this->flag]; } }; } #[Action] public function toggle(): object { // Very fast action - no usleep $newState = clone $this->state; $newState->flag = !$newState->flag; return $newState; } }; $params = ActionParameters::create(['_csrf_token' => 'valid-token']); $start = microtime(true); $this->handler->handle($component, 'toggle', $params); $duration = (microtime(true) - $start) * 1000; // Performance tracking should add minimal overhead (<5ms for very fast actions) expect($duration)->toBeLessThan(10); $timeline = $this->tracker->generateTimeline(); expect($timeline)->not->toBeEmpty(); }); it('tracks performance categories correctly', function () { $component = new class implements LiveComponentContract { public ComponentId $id; public object $state; public function __construct() { $this->id = ComponentId::create('category-test', 'cat-1'); $this->state = new class { public array $items = []; public function toArray(): array { return ['items' => $this->items]; } }; } #[Action] public function load(): object { return $this->state; } }; $params = ActionParameters::create(['_csrf_token' => 'valid-token']); $this->handler->handle($component, 'load', $params); $timeline = $this->tracker->generateTimeline(); // Verify categories $categoryCounts = []; foreach ($timeline as $event) { $category = $event['category']; $categoryCounts[$category] = ($categoryCounts[$category] ?? 0) + 1; } // Should have: // - CUSTOM for main component action // - CACHE for schema derivation expect($categoryCounts)->toHaveKey('custom'); expect($categoryCounts)->toHaveKey('cache'); }); }); describe('LiveComponent Profiling - Error Scenarios', function () { beforeEach(function () { $this->tracker = new NestedPerformanceTracker( new SystemClock(), new SystemHighResolutionClock(), new MemoryMonitor() ); $this->profiler = new ActionProfiler($this->tracker); $this->eventDispatcher = new ComponentEventDispatcher(); // Simple mocks (copy from first describe block) $this->session = new class { public readonly object $csrf; public readonly object $validation; public readonly object $form; public function __construct() { $this->csrf = new class { public function validateToken(string $formId, string $token): bool { return true; } }; $this->validation = new class {}; $this->form = new class {}; } }; $this->authChecker = new class implements \App\Framework\LiveComponents\Security\ActionAuthorizationChecker { public function isAuthorized($component, string $method, $permissionAttribute): bool { return true; } public function getUserPermissions(): array { return []; } public function hasPermission(string $permission): bool { return true; } public function isAuthenticated(): bool { return true; } }; $this->schemaCache = new SchemaCache(); $this->rateLimiter = new class { public function checkActionLimit($component, string $method, string $clientId, $actionAttr): \App\Framework\LiveComponents\ValueObjects\RateLimitResult { return new \App\Framework\LiveComponents\ValueObjects\RateLimitResult(allowed: true, limit: 100, current: 1, retryAfter: null); } }; $this->idempotency = new class { public function execute(mixed $key, callable $operation, mixed $ttl): mixed { return $operation(); } }; $this->parameterBinder = new class { public function bindParameters(\ReflectionMethod $method, ActionParameters $params): array { return []; } }; $this->frameworkDispatcher = new class { public function dispatch($event): void {} }; $this->handler = new LiveComponentHandler( $this->eventDispatcher, $this->session, $this->authChecker, $this->schemaCache, $this->rateLimiter, $this->idempotency, $this->parameterBinder, $this->frameworkDispatcher, $this->tracker ); }); it('tracks performance even when action throws exception', function () { $component = new class implements LiveComponentContract { public ComponentId $id; public object $state; public function __construct() { $this->id = ComponentId::create('error-test', 'err-1'); $this->state = new class { public function toArray(): array { return []; } }; } #[Action] public function failing(): object { usleep(3000); // 3ms before error throw new \RuntimeException('Action failed'); } }; $params = ActionParameters::create(['_csrf_token' => 'valid-token']); try { $this->handler->handle($component, 'failing', $params); expect(true)->toBeFalse('Should have thrown exception'); } catch (\RuntimeException $e) { expect($e->getMessage())->toBe('Action failed'); } // Performance should still be tracked even though action failed $timeline = $this->tracker->generateTimeline(); $actionMeasurements = array_filter($timeline, fn($e) => str_contains($e['name'], 'action.execute')); expect($actionMeasurements)->not->toBeEmpty(); // The execution time should reflect the work done before the exception $measurement = array_values($actionMeasurements)[0]; expect($measurement['duration_ms'])->toBeGreaterThan(2); }); it('handles lifecycle hook errors gracefully while still tracking', function () { $component = new class implements LiveComponentContract, \App\Framework\LiveComponents\Contracts\LifecycleAware { public ComponentId $id; public object $state; public function __construct() { $this->id = ComponentId::create('lifecycle-error', 'err-2'); $this->state = new class { public function toArray(): array { return []; } }; } public function onMount(): void { usleep(1000); throw new \Exception('Mount failed'); } public function onUpdate(): void { // Not tested } #[Action] public function test(): object { return $this->state; } }; // Should not throw - lifecycle hook errors are caught $this->handler->callMountHook($component); $timeline = $this->tracker->generateTimeline(); // Performance tracking should still work $mountMeasurements = array_filter($timeline, fn($e) => str_contains($e['name'], 'lifecycle.onMount')); expect($mountMeasurements)->not->toBeEmpty(); }); }); describe('LiveComponent Profiling - Performance Benchmarks', function () { beforeEach(function () { $this->tracker = new NestedPerformanceTracker( new SystemClock(), new SystemHighResolutionClock(), new MemoryMonitor() ); $this->profiler = new ActionProfiler($this->tracker); $this->eventDispatcher = new ComponentEventDispatcher(); // Simple mocks (copy from first describe block) $this->session = new class { public readonly object $csrf; public readonly object $validation; public readonly object $form; public function __construct() { $this->csrf = new class { public function validateToken(string $formId, string $token): bool { return true; } }; $this->validation = new class {}; $this->form = new class {}; } }; $this->authChecker = new class implements \App\Framework\LiveComponents\Security\ActionAuthorizationChecker { public function isAuthorized($component, string $method, $permissionAttribute): bool { return true; } public function getUserPermissions(): array { return []; } public function hasPermission(string $permission): bool { return true; } public function isAuthenticated(): bool { return true; } }; $this->schemaCache = new SchemaCache(); $this->rateLimiter = new class { public function checkActionLimit($component, string $method, string $clientId, $actionAttr): \App\Framework\LiveComponents\ValueObjects\RateLimitResult { return new \App\Framework\LiveComponents\ValueObjects\RateLimitResult(allowed: true, limit: 100, current: 1, retryAfter: null); } }; $this->idempotency = new class { public function execute(mixed $key, callable $operation, mixed $ttl): mixed { return $operation(); } }; $this->parameterBinder = new class { public function bindParameters(\ReflectionMethod $method, ActionParameters $params): array { return []; } }; $this->frameworkDispatcher = new class { public function dispatch($event): void {} }; $this->handler = new LiveComponentHandler( $this->eventDispatcher, $this->session, $this->authChecker, $this->schemaCache, $this->rateLimiter, $this->idempotency, $this->parameterBinder, $this->frameworkDispatcher, $this->tracker ); }); it('measures overhead of profiling system', function () { $component = new class implements LiveComponentContract { public ComponentId $id; public object $state; public function __construct() { $this->id = ComponentId::create('benchmark', 'bench-1'); $this->state = new class { public int $counter = 0; public function toArray(): array { return ['counter' => $this->counter]; } }; } #[Action] public function noop(): object { // Minimal work - just to measure overhead return $this->state; } }; $params = ActionParameters::create(['_csrf_token' => 'valid-token']); // Execute multiple times to get average $executions = 100; $totalTime = 0; for ($i = 0; $i < $executions; $i++) { $start = microtime(true); $this->handler->handle($component, 'noop', $params); $totalTime += (microtime(true) - $start) * 1000; $this->tracker->reset(); } $avgTime = $totalTime / $executions; // Average execution time should be reasonable (<5ms per action with profiling) expect($avgTime)->toBeLessThan(5); // Should be able to handle 200+ actions per second $actionsPerSecond = 1000 / $avgTime; expect($actionsPerSecond)->toBeGreaterThan(200); }); });