cache = Mockery::mock(Cache::class); $this->strategy = new ComponentCacheStrategy($this->cache); }); afterEach(function () { Mockery::close(); }); describe('shouldCache()', function () { it('returns true for templates with "component" in name', function () { $context = new TemplateContext( template: 'components/button', data: ['text' => 'Click me'] ); expect($this->strategy->shouldCache($context))->toBeTrue(); }); it('returns true for templates with "partial" in name', function () { $context = new TemplateContext( template: 'partials/navigation', data: ['items' => []] ); expect($this->strategy->shouldCache($context))->toBeTrue(); }); it('returns true for nested component paths', function () { $context = new TemplateContext( template: 'views/components/card/header', data: ['title' => 'Header'] ); expect($this->strategy->shouldCache($context))->toBeTrue(); }); it('returns true for partial in subdirectory', function () { $context = new TemplateContext( template: 'admin/partials/sidebar', data: [] ); expect($this->strategy->shouldCache($context))->toBeTrue(); }); it('returns false for regular templates', function () { $context = new TemplateContext( template: 'pages/home', data: [] ); expect($this->strategy->shouldCache($context))->toBeFalse(); }); it('returns false for layouts', function () { $context = new TemplateContext( template: 'layouts/main', data: [] ); expect($this->strategy->shouldCache($context))->toBeFalse(); }); it('is case sensitive for component keyword', function () { $context = new TemplateContext( template: 'COMPONENTS/button', // Uppercase data: [] ); expect($this->strategy->shouldCache($context))->toBeFalse(); }); }); describe('generateKey()', function () { it('generates consistent keys for same component and data', function () { $context = new TemplateContext( template: 'components/button', data: ['text' => 'Click', 'variant' => 'primary'] ); $key1 = $this->strategy->generateKey($context); $key2 = $this->strategy->generateKey($context); expect((string) $key1)->toBe((string) $key2); }); it('generates different keys for different components', function () { $context1 = new TemplateContext( template: 'components/button', data: ['text' => 'Click'] ); $context2 = new TemplateContext( template: 'components/link', data: ['text' => 'Click'] ); $key1 = $this->strategy->generateKey($context1); $key2 = $this->strategy->generateKey($context2); expect((string) $key1)->not->toBe((string) $key2); }); it('generates different keys for different data', function () { $context1 = new TemplateContext( template: 'components/button', data: ['text' => 'Submit'] ); $context2 = new TemplateContext( template: 'components/button', data: ['text' => 'Cancel'] ); $key1 = $this->strategy->generateKey($context1); $key2 = $this->strategy->generateKey($context2); expect((string) $key1)->not->toBe((string) $key2); }); it('uses basename for nested component paths', function () { $context1 = new TemplateContext( template: 'views/components/button', data: ['text' => 'Click'] ); $context2 = new TemplateContext( template: 'other/components/button', data: ['text' => 'Click'] ); $key1 = $this->strategy->generateKey($context1); $key2 = $this->strategy->generateKey($context2); // Same basename and data = same key expect((string) $key1)->toBe((string) $key2); }); it('includes component basename in cache key', function () { $context = new TemplateContext( template: 'components/alert', data: ['message' => 'Success'] ); $key = $this->strategy->generateKey($context); expect((string) $key)->toContain('component:alert:'); }); it('includes data hash in cache key', function () { $context = new TemplateContext( template: 'components/card', data: ['title' => 'Test', 'content' => 'Content'] ); $key = $this->strategy->generateKey($context); $keyString = (string) $key; // Key should have format: component:card:{hash} expect($keyString)->toStartWith('component:card:'); expect(strlen($keyString))->toBeGreaterThan(strlen('component:card:')); }); it('generates same key for identical nested data structures', function () { $data = [ 'user' => ['name' => 'John', 'role' => 'admin'], 'settings' => ['theme' => 'dark'] ]; $context1 = new TemplateContext( template: 'components/profile', data: $data ); $context2 = new TemplateContext( template: 'components/profile', data: $data ); $key1 = $this->strategy->generateKey($context1); $key2 = $this->strategy->generateKey($context2); expect((string) $key1)->toBe((string) $key2); }); }); describe('getTtl()', function () { it('returns 1800 seconds for all components', function () { $context = new TemplateContext( template: 'components/button', data: [] ); expect($this->strategy->getTtl($context))->toBe(1800); }); it('returns consistent TTL for different components', function () { $context1 = new TemplateContext( template: 'components/card', data: [] ); $context2 = new TemplateContext( template: 'partials/header', data: [] ); expect($this->strategy->getTtl($context1))->toBe(1800); expect($this->strategy->getTtl($context2))->toBe(1800); }); it('returns consistent TTL regardless of data', function () { $context1 = new TemplateContext( template: 'components/button', data: ['small' => 'data'] ); $context2 = new TemplateContext( template: 'components/button', data: ['large' => str_repeat('x', 1000)] ); expect($this->strategy->getTtl($context1))->toBe(1800); expect($this->strategy->getTtl($context2))->toBe(1800); }); }); describe('canInvalidate()', function () { it('returns true for component templates', function () { expect($this->strategy->canInvalidate('components/button'))->toBeTrue(); }); it('returns true for partial templates', function () { expect($this->strategy->canInvalidate('partials/navigation'))->toBeTrue(); }); it('returns true for nested component paths', function () { expect($this->strategy->canInvalidate('views/components/card'))->toBeTrue(); }); it('returns false for regular templates', function () { expect($this->strategy->canInvalidate('pages/home'))->toBeFalse(); }); it('returns false for layouts', function () { expect($this->strategy->canInvalidate('layouts/main'))->toBeFalse(); }); }); describe('edge cases', function () { it('handles empty data array', function () { $context = new TemplateContext( template: 'components/divider', data: [] ); expect($this->strategy->shouldCache($context))->toBeTrue(); $key = $this->strategy->generateKey($context); expect($key)->toBeInstanceOf(CacheKey::class); }); it('handles complex nested data structures', function () { $context = new TemplateContext( template: 'components/table', data: [ 'headers' => ['Name', 'Email', 'Role'], 'rows' => [ ['John Doe', 'john@example.com', 'admin'], ['Jane Smith', 'jane@example.com', 'user'] ], 'pagination' => [ 'page' => 1, 'total' => 10, 'perPage' => 20 ] ] ); $key = $this->strategy->generateKey($context); expect($key)->toBeInstanceOf(CacheKey::class); expect((string) $key)->toBeString(); expect(strlen((string) $key))->toBeGreaterThan(0); }); it('handles special characters in component name', function () { $context = new TemplateContext( template: 'components/user-profile_card', data: ['user_id' => 123] ); expect($this->strategy->shouldCache($context))->toBeTrue(); $key = $this->strategy->generateKey($context); expect((string) $key)->toContain('component:user-profile_card:'); }); it('treats "component" and "partial" equally for caching', function () { $componentContext = new TemplateContext( template: 'components/item', data: ['id' => 1] ); $partialContext = new TemplateContext( template: 'partials/item', data: ['id' => 1] ); expect($this->strategy->shouldCache($componentContext))->toBeTrue(); expect($this->strategy->shouldCache($partialContext))->toBeTrue(); expect($this->strategy->getTtl($componentContext))->toBe( $this->strategy->getTtl($partialContext) ); }); it('handles component path with multiple segments', function () { $context = new TemplateContext( template: 'admin/dashboard/components/widget/chart', data: ['data' => [1, 2, 3]] ); expect($this->strategy->shouldCache($context))->toBeTrue(); $key = $this->strategy->generateKey($context); // Basename should be 'chart' expect((string) $key)->toContain('component:chart:'); }); it('generates valid cache key from data with objects', function () { $context = new TemplateContext( template: 'components/user-card', data: [ 'user' => (object) ['name' => 'John', 'email' => 'john@example.com'], 'timestamp' => new \DateTimeImmutable('2024-01-01 12:00:00') ] ); $key = $this->strategy->generateKey($context); expect($key)->toBeInstanceOf(CacheKey::class); expect((string) $key)->toStartWith('component:user-card:'); }); }); });