stateManager = InMemoryStateManager::for(ComponentState::class); $this->cache = new ComponentStateCache($this->stateManager); $this->componentId = ComponentId::create('counter', 'instance-1'); }); it('stores component state', function () { $state = ComponentState::fromArray([ 'count' => 5, 'lastUpdated' => time() ]); $this->cache->store($this->componentId, $state); expect($this->cache->has($this->componentId))->toBeTrue(); }); it('retrieves stored component state', function () { $state = ComponentState::fromArray([ 'count' => 10, 'label' => 'Counter' ]); $this->cache->store($this->componentId, $state); $retrieved = $this->cache->retrieve($this->componentId); expect($retrieved)->toBeInstanceOf(ComponentState::class); expect($retrieved->toArray()['count'])->toBe(10); expect($retrieved->toArray()['label'])->toBe('Counter'); }); it('returns null for non-existent state', function () { $nonExistentId = ComponentId::create('counter', 'does-not-exist'); $retrieved = $this->cache->retrieve($nonExistentId); expect($retrieved)->toBeNull(); }); it('checks state existence', function () { $state = ComponentState::fromArray(['value' => 42]); expect($this->cache->has($this->componentId))->toBeFalse(); $this->cache->store($this->componentId, $state); expect($this->cache->has($this->componentId))->toBeTrue(); }); it('invalidates cached state', function () { $state = ComponentState::fromArray(['value' => 100]); $this->cache->store($this->componentId, $state); expect($this->cache->has($this->componentId))->toBeTrue(); $this->cache->invalidate($this->componentId); expect($this->cache->has($this->componentId))->toBeFalse(); expect($this->cache->retrieve($this->componentId))->toBeNull(); }); it('clears all cached states', function () { $state1 = ComponentState::fromArray(['value' => 1]); $state2 = ComponentState::fromArray(['value' => 2]); $id1 = ComponentId::create('counter', 'instance-1'); $id2 = ComponentId::create('counter', 'instance-2'); $this->cache->store($id1, $state1); $this->cache->store($id2, $state2); $this->cache->clear(); expect($this->cache->has($id1))->toBeFalse(); expect($this->cache->has($id2))->toBeFalse(); }); it('atomically updates component state', function () { $initialState = ComponentState::fromArray([ 'count' => 5, 'version' => 1 ]); $this->cache->store($this->componentId, $initialState); $updatedState = $this->cache->update( $this->componentId, function ($state) { if ($state === null) { return ComponentState::fromArray(['count' => 1, 'version' => 1]); } $data = $state->toArray(); return ComponentState::fromArray([ 'count' => $data['count'] + 1, 'version' => $data['version'] + 1 ]); } ); expect($updatedState->toArray()['count'])->toBe(6); expect($updatedState->toArray()['version'])->toBe(2); }); it('handles null state in atomic update', function () { $newState = $this->cache->update( $this->componentId, function ($state) { return $state ?? ComponentState::fromArray([ 'count' => 1, 'initialized' => true ]); } ); expect($newState->toArray()['count'])->toBe(1); expect($newState->toArray()['initialized'])->toBe(true); }); it('respects custom TTL', function () { $state = ComponentState::fromArray(['value' => 42]); $customTtl = Duration::fromMinutes(30); $this->cache->store($this->componentId, $state, $customTtl); expect($this->cache->has($this->componentId))->toBeTrue(); }); it('uses default TTL when not specified', function () { $state = ComponentState::fromArray(['value' => 100]); $this->cache->store($this->componentId, $state); expect($this->cache->has($this->componentId))->toBeTrue(); }); it('provides cache statistics', function () { $state = ComponentState::fromArray(['value' => 1]); $this->cache->store($this->componentId, $state); $this->cache->retrieve($this->componentId); $this->cache->has($this->componentId); $stats = $this->cache->getStats(); expect($stats)->toBeInstanceOf(StateManagerStatistics::class); expect($stats->totalKeys)->toBeGreaterThanOrEqual(1); expect($stats->setCount)->toBeGreaterThanOrEqual(1); expect($stats->hitCount)->toBeGreaterThanOrEqual(1); }); it('stores with automatic TTL based on component type', function () { $state = ComponentState::fromArray(['value' => 42]); $this->cache->storeWithAutoTTL($this->componentId, $state, 'counter'); expect($this->cache->has($this->componentId))->toBeTrue(); }); it('applies different TTLs for different component types', function () { $timerState = ComponentState::fromArray(['seconds' => 30]); $chartState = ComponentState::fromArray(['data' => [1, 2, 3]]); $cardState = ComponentState::fromArray(['title' => 'Card Title']); $timerId = ComponentId::create('timer', 'instance-1'); $chartId = ComponentId::create('chart', 'instance-1'); $cardId = ComponentId::create('card', 'instance-1'); // timer: 5 minutes TTL $this->cache->storeWithAutoTTL($timerId, $timerState, 'timer'); // chart: 30 minutes TTL $this->cache->storeWithAutoTTL($chartId, $chartState, 'chart'); // card: 2 hours TTL $this->cache->storeWithAutoTTL($cardId, $cardState, 'card'); expect($this->cache->has($timerId))->toBeTrue(); expect($this->cache->has($chartId))->toBeTrue(); expect($this->cache->has($cardId))->toBeTrue(); }); it('uses default TTL for unknown component types', function () { $state = ComponentState::fromArray(['value' => 123]); $unknownId = ComponentId::create('unknown-type', 'instance-1'); $this->cache->storeWithAutoTTL($unknownId, $state, 'unknown-type'); expect($this->cache->has($unknownId))->toBeTrue(); }); it('generates consistent cache keys for same component', function () { $state = ComponentState::fromArray(['value' => 42]); $id1 = ComponentId::create('counter', 'instance-1'); $id2 = ComponentId::create('counter', 'instance-1'); $this->cache->store($id1, $state); // Same component ID should find the cached state expect($this->cache->has($id2))->toBeTrue(); expect($this->cache->retrieve($id2))->not->toBeNull(); }); it('generates different cache keys for different instances', function () { $state1 = ComponentState::fromArray(['value' => 1]); $state2 = ComponentState::fromArray(['value' => 2]); $id1 = ComponentId::create('counter', 'instance-1'); $id2 = ComponentId::create('counter', 'instance-2'); $this->cache->store($id1, $state1); $this->cache->store($id2, $state2); $retrieved1 = $this->cache->retrieve($id1); $retrieved2 = $this->cache->retrieve($id2); expect($retrieved1->toArray()['value'])->toBe(1); expect($retrieved2->toArray()['value'])->toBe(2); }); it('handles complex state data', function () { $complexState = ComponentState::fromArray([ 'user' => [ 'id' => 123, 'name' => 'John Doe', 'email' => 'john@example.com' ], 'preferences' => [ 'theme' => 'dark', 'notifications' => true ], 'metadata' => [ 'created_at' => time(), 'updated_at' => time() ] ]); $this->cache->store($this->componentId, $complexState); $retrieved = $this->cache->retrieve($this->componentId); expect($retrieved->toArray()['user']['name'])->toBe('John Doe'); expect($retrieved->toArray()['preferences']['theme'])->toBe('dark'); expect($retrieved->toArray()['metadata'])->toHaveKey('created_at'); }); it('preserves state through multiple updates', function () { $initialState = ComponentState::fromArray(['count' => 0]); $this->cache->store($this->componentId, $initialState); // Update 1 $this->cache->update( $this->componentId, fn($state) => ComponentState::fromArray(['count' => $state->toArray()['count'] + 1]) ); // Update 2 $this->cache->update( $this->componentId, fn($state) => ComponentState::fromArray(['count' => $state->toArray()['count'] + 1]) ); // Update 3 $finalState = $this->cache->update( $this->componentId, fn($state) => ComponentState::fromArray(['count' => $state->toArray()['count'] + 1]) ); expect($finalState->toArray()['count'])->toBe(3); }); it('correctly reports statistics after various operations', function () { $state = ComponentState::fromArray(['value' => 1]); // Set operations $this->cache->store($this->componentId, $state); $this->cache->store($this->componentId, $state); // Get operations $this->cache->retrieve($this->componentId); $this->cache->retrieve($this->componentId); // Has operations $this->cache->has($this->componentId); // Update operation $this->cache->update( $this->componentId, fn($s) => ComponentState::fromArray(['value' => 2]) ); $stats = $this->cache->getStats(); expect($stats->setCount)->toBeGreaterThanOrEqual(2); expect($stats->hitCount)->toBeGreaterThanOrEqual(2); expect($stats->updateCount)->toBeGreaterThanOrEqual(1); }); });