recordRender('test-component-123', 45.5, false); $metrics = $collector->getMetrics(); expect(count($metrics))->toBeGreaterThan(0); expect(isset($metrics['livecomponent_renders_total{component_id=test-component-123,cached=false}']))->toBeTrue(); }); it('records cached vs non-cached renders separately', function () { $collector = new ComponentMetricsCollector(); $collector->recordRender('comp-1', 45.5, false); // non-cached $collector->recordRender('comp-2', 10.2, true); // cached $metrics = $collector->getMetrics(); // Should have separate metrics for cached and non-cached $renderMetrics = array_filter( $metrics, fn($m) => str_contains($m->name, 'livecomponent_renders_total') ); expect(count($renderMetrics))->toBeGreaterThanOrEqual(1); }); it('records action execution with duration', function () { $collector = new ComponentMetricsCollector(); $collector->recordAction('comp-123', 'handleClick', 25.5, true); $metrics = $collector->getMetrics(); expect($metrics)->toHaveKey('livecomponent_actions_total'); expect($metrics['livecomponent_actions_total']->value)->toBe(1.0); }); it('tracks action errors separately', function () { $collector = new ComponentMetricsCollector(); $collector->recordAction('comp-123', 'handleClick', 25.5, false); $metrics = $collector->getMetrics(); expect($metrics)->toHaveKey('livecomponent_action_errors_total'); expect($metrics['livecomponent_action_errors_total']->value)->toBe(1.0); }); it('records cache hits and misses', function () { $collector = new ComponentMetricsCollector(); $collector->recordCacheHit('comp-1', true); $collector->recordCacheHit('comp-2', false); $collector->recordCacheHit('comp-3', true); $metrics = $collector->getMetrics(); expect($metrics)->toHaveKey('livecomponent_cache_hits_total'); expect($metrics)->toHaveKey('livecomponent_cache_misses_total'); expect($metrics['livecomponent_cache_hits_total']->value)->toBe(2.0); expect($metrics['livecomponent_cache_misses_total']->value)->toBe(1.0); }); it('records event dispatching', function () { $collector = new ComponentMetricsCollector(); $collector->recordEventDispatched('comp-1', 'user:updated'); $collector->recordEventDispatched('comp-2', 'data:loaded'); $metrics = $collector->getMetrics(); $eventMetrics = array_filter( $metrics, fn($m) => str_contains($m->name, 'livecomponent_events_dispatched_total') ); expect(count($eventMetrics))->toBeGreaterThanOrEqual(1); }); it('records event receiving', function () { $collector = new ComponentMetricsCollector(); $collector->recordEventReceived('comp-1', 'user:updated'); $collector->recordEventReceived('comp-2', 'data:loaded'); $metrics = $collector->getMetrics(); $eventMetrics = array_filter( $metrics, fn($m) => str_contains($m->name, 'livecomponent_events_received_total') ); expect(count($eventMetrics))->toBeGreaterThanOrEqual(1); }); it('records hydration time', function () { $collector = new ComponentMetricsCollector(); $collector->recordHydration('comp-123', 15.5); $metrics = $collector->getMetrics(); $hydrationMetrics = array_filter( $metrics, fn($m) => str_contains($m->name, 'livecomponent_hydration_duration_ms') ); expect(count($hydrationMetrics))->toBeGreaterThanOrEqual(1); }); it('records batch operations', function () { $collector = new ComponentMetricsCollector(); $collector->recordBatch(10, 125.5, 8, 2); $metrics = $collector->getMetrics(); expect($metrics)->toHaveKey('livecomponent_batch_operations_total'); expect($metrics)->toHaveKey('livecomponent_batch_success_total'); expect($metrics)->toHaveKey('livecomponent_batch_failure_total'); expect($metrics['livecomponent_batch_success_total']->value)->toBe(8.0); expect($metrics['livecomponent_batch_failure_total']->value)->toBe(2.0); }); it('records fragment updates', function () { $collector = new ComponentMetricsCollector(); $collector->recordFragmentUpdate('comp-123', 3, 45.5); $metrics = $collector->getMetrics(); expect($metrics)->toHaveKey('livecomponent_fragment_updates_total'); }); it('records upload chunks', function () { $collector = new ComponentMetricsCollector(); $collector->recordUploadChunk('session-abc', 0, 125.5, true); $collector->recordUploadChunk('session-abc', 1, 130.2, true); $collector->recordUploadChunk('session-abc', 2, 128.8, false); $metrics = $collector->getMetrics(); $uploadMetrics = array_filter( $metrics, fn($m) => str_contains($m->name, 'livecomponent_upload_chunks_total') ); expect(count($uploadMetrics))->toBeGreaterThanOrEqual(1); }); it('records upload completion', function () { $collector = new ComponentMetricsCollector(); $collector->recordUploadComplete('session-abc', 1500.5, 5); $metrics = $collector->getMetrics(); expect($metrics)->toHaveKey('livecomponent_uploads_completed_total'); }); it('calculates summary statistics', function () { $collector = new ComponentMetricsCollector(); // Record various operations $collector->recordRender('comp-1', 45.5, false); $collector->recordRender('comp-2', 30.2, true); $collector->recordAction('comp-1', 'handleClick', 25.5, true); $collector->recordAction('comp-2', 'handleSubmit', 35.8, false); $collector->recordCacheHit('comp-1', true); $collector->recordCacheHit('comp-2', false); $collector->recordCacheHit('comp-3', true); $summary = $collector->getSummary(); expect($summary)->toHaveKey('total_renders'); expect($summary)->toHaveKey('total_actions'); expect($summary)->toHaveKey('cache_hits'); expect($summary)->toHaveKey('cache_misses'); expect($summary)->toHaveKey('cache_hit_rate'); expect($summary)->toHaveKey('action_errors'); expect($summary['total_renders'])->toBe(2); expect($summary['total_actions'])->toBe(2); expect($summary['cache_hits'])->toBe(2); expect($summary['cache_misses'])->toBe(1); expect($summary['action_errors'])->toBe(1); expect($summary['cache_hit_rate'])->toBeGreaterThan(60.0); }); it('exports metrics in Prometheus format', function () { $collector = new ComponentMetricsCollector(); $collector->recordRender('comp-1', 45.5, false); $collector->recordAction('comp-1', 'handleClick', 25.5, true); $prometheus = $collector->exportPrometheus(); expect($prometheus)->toBeString(); expect($prometheus)->toContain('# HELP LiveComponents metrics'); expect($prometheus)->toContain('livecomponent_'); }); it('resets all metrics', function () { $collector = new ComponentMetricsCollector(); $collector->recordRender('comp-1', 45.5, false); $collector->recordAction('comp-1', 'handleClick', 25.5, true); expect($collector->getMetrics())->not->toBeEmpty(); $collector->reset(); expect($collector->getMetrics())->toBeEmpty(); }); it('integrates with PerformanceCollector', function () { $performanceCollector = $this->createMock(PerformanceCollector::class); $performanceCollector->expects($this->once()) ->method('recordMetric') ->with( $this->stringContains('livecomponent.render'), $this->anything(), $this->equalTo(45.5), $this->anything() ); $collector = new ComponentMetricsCollector($performanceCollector); $collector->recordRender('comp-1', 45.5, false); }); it('tracks multiple components independently', function () { $collector = new ComponentMetricsCollector(); // Component 1: 3 renders, 2 actions $collector->recordRender('comp-1', 45.5, false); $collector->recordRender('comp-1', 40.2, false); $collector->recordRender('comp-1', 35.8, false); $collector->recordAction('comp-1', 'handleClick', 25.5, true); $collector->recordAction('comp-1', 'handleSubmit', 30.2, true); // Component 2: 2 renders, 1 action $collector->recordRender('comp-2', 20.5, true); $collector->recordRender('comp-2', 18.2, true); $collector->recordAction('comp-2', 'handleClick', 15.5, true); $metrics = $collector->getMetrics(); // Should have metrics for both components $renderMetrics = array_filter( $metrics, fn($m) => str_contains($m->name, 'livecomponent_renders_total') ); expect(count($renderMetrics))->toBeGreaterThanOrEqual(1); }); it('handles edge case with zero operations', function () { $collector = new ComponentMetricsCollector(); $summary = $collector->getSummary(); expect($summary['total_renders'])->toBe(0); expect($summary['total_actions'])->toBe(0); expect($summary['cache_hit_rate'])->toBe(0.0); }); it('handles edge case with all cache misses', function () { $collector = new ComponentMetricsCollector(); $collector->recordCacheHit('comp-1', false); $collector->recordCacheHit('comp-2', false); $collector->recordCacheHit('comp-3', false); $summary = $collector->getSummary(); expect($summary['cache_hits'])->toBe(0); expect($summary['cache_misses'])->toBe(3); expect($summary['cache_hit_rate'])->toBe(0.0); }); it('handles edge case with all cache hits', function () { $collector = new ComponentMetricsCollector(); $collector->recordCacheHit('comp-1', true); $collector->recordCacheHit('comp-2', true); $collector->recordCacheHit('comp-3', true); $summary = $collector->getSummary(); expect($summary['cache_hits'])->toBe(3); expect($summary['cache_misses'])->toBe(0); expect($summary['cache_hit_rate'])->toBe(100.0); }); });