clock = new SystemClock(); $this->repository = Mockery::mock(ContentRepository::class); $this->blockTypeRegistry = new BlockTypeRegistry(); $this->blockValidator = new BlockValidator($this->blockTypeRegistry); $this->slugGenerator = new SlugGenerator($this->repository); $this->contentService = new ContentService( $this->repository, $this->blockValidator, $this->slugGenerator, $this->clock ); // Setup ContentRenderer with real dependencies (similar to ContentRendererTest) $blockRendererRegistry = new BlockRendererRegistry(); $blockRendererRegistry->registerForType('hero', new HeroBlockRenderer()); $blockRendererRegistry->registerForType('text', new TextBlockRenderer()); $blockRendererRegistry->registerForType('image', new ImageBlockRenderer()); // Create real ComponentRenderer $pathProvider = new PathProvider(__DIR__ . '/../../../../'); $nullCacheDriver = new NullCache(); $serializer = Mockery::mock(Serializer::class); $serializer->shouldReceive('serialize')->andReturnUsing(fn($data) => serialize($data)); $serializer->shouldReceive('unserialize')->andReturnUsing(fn($data) => unserialize($data)); $cache = new GeneralCache($nullCacheDriver, $serializer); $templateLoader = new TemplateLoader( pathProvider: $pathProvider, cache: $cache, discoveryRegistry: null, templates: [], templatePath: '/src/Framework/View/templates', cacheEnabled: false ); $componentCache = new ComponentCache('/tmp/test-cache'); $container = new DefaultContainer(); $templateProcessor = new TemplateProcessor( astTransformers: [], stringProcessors: [], container: $container, chainOptimizer: null, compiledTemplateCache: null, performanceTracker: null ); $fileStorage = new FileStorage( basePath: sys_get_temp_dir() . '/test-components' ); $componentRenderer = new ComponentRenderer( $templateProcessor, $componentCache, $templateLoader, $fileStorage ); $contentTranslationRepository = Mockery::mock(\App\Domain\Cms\Repositories\ContentTranslationRepository::class); $localizationService = new ContentLocalizationService( $contentTranslationRepository, $this->clock ); $defaultRenderer = new DefaultBlockRenderer(); $this->contentRenderer = new ContentRenderer( $blockRendererRegistry, $componentRenderer, $localizationService, $defaultRenderer ); $this->cacheManager = Mockery::mock(CacheManager::class); $this->controller = new ShowCmsContent( $this->contentService, $this->contentRenderer, $this->cacheManager ); }); it('renders published homepage content', function () { $slug = ContentSlug::fromString('homepage'); $content = CmsTestHelpers::createContent( $this->clock, slug: $slug, title: 'Homepage', status: ContentStatus::PUBLISHED ); $renderedHtml = '

Homepage

'; $this->repository->shouldReceive('findBySlug') ->once() ->with(Mockery::on(function ($arg) use ($slug) { return $arg instanceof ContentSlug && $arg->toString() === $slug->toString(); })) ->andReturn($content); $this->cacheManager->shouldReceive('render') ->once() ->andReturn($renderedHtml); $result = $this->controller->home(); expect($result)->toBeInstanceOf(\App\Framework\Router\Result\ViewResult::class); expect($result->template)->toBe('cms-content'); expect($result->metaData->title)->toBe('Homepage'); expect($result->data['bodyContent'])->toBeInstanceOf(\App\Framework\View\RawHtml::class); }); it('throws exception when homepage content not found', function () { $slug = ContentSlug::fromString('homepage'); $this->repository->shouldReceive('findBySlug') ->once() ->with(Mockery::on(function ($arg) use ($slug) { return $arg instanceof ContentSlug && $arg->toString() === $slug->toString(); })) ->andReturn(null); expect(fn () => $this->controller->home()) ->toThrow(ContentNotFoundException::class); }); it('throws exception when content is not published', function () { $slug = ContentSlug::fromString('homepage'); $content = CmsTestHelpers::createContent( $this->clock, slug: $slug, status: ContentStatus::DRAFT ); $this->repository->shouldReceive('findBySlug') ->once() ->with(Mockery::on(function ($arg) use ($slug) { return $arg instanceof ContentSlug && $arg->toString() === $slug->toString(); })) ->andReturn($content); expect(fn () => $this->controller->home()) ->toThrow(ContentNotFoundException::class); }); it('extracts meta data from CMS content', function () { $slug = ContentSlug::fromString('homepage'); $metaData = BlockData::fromArray([ 'title' => 'SEO Title', 'description' => 'SEO Description', ]); $content = new Content( id: ContentId::generate($this->clock), contentTypeId: ContentTypeId::fromString('page'), slug: $slug, title: 'Homepage', blocks: ContentBlocks::fromArray([]), status: ContentStatus::PUBLISHED, authorId: null, publishedAt: Timestamp::now(), metaData: $metaData, defaultLocale: Locale::english(), createdAt: Timestamp::now(), updatedAt: Timestamp::now() ); $renderedHtml = '
Content
'; $this->repository->shouldReceive('findBySlug') ->once() ->with(Mockery::on(function ($arg) use ($slug) { return $arg instanceof ContentSlug && $arg->toString() === $slug->toString(); })) ->andReturn($content); $this->cacheManager->shouldReceive('render') ->once() ->andReturn($renderedHtml); $result = $this->controller->home(); expect($result->metaData->title)->toBe('SEO Title'); expect($result->metaData->description)->toBe('SEO Description'); }); it('falls back to content title when meta data is missing', function () { $slug = ContentSlug::fromString('homepage'); $content = CmsTestHelpers::createContent( $this->clock, slug: $slug, title: 'Homepage Title', status: ContentStatus::PUBLISHED ); $renderedHtml = '
Content
'; $this->repository->shouldReceive('findBySlug') ->once() ->with(Mockery::on(function ($arg) use ($slug) { return $arg instanceof ContentSlug && $arg->toString() === $slug->toString(); })) ->andReturn($content); $this->cacheManager->shouldReceive('render') ->once() ->andReturn($renderedHtml); $result = $this->controller->home(); expect($result->metaData->title)->toBe('Homepage Title'); expect($result->metaData->description)->toBe(''); }); it('uses cache manager for rendering', function () { $slug = ContentSlug::fromString('homepage'); $content = CmsTestHelpers::createContent( $this->clock, slug: $slug, status: ContentStatus::PUBLISHED ); $renderedHtml = '
Cached Content
'; $this->repository->shouldReceive('findBySlug') ->once() ->with(Mockery::on(function ($arg) use ($slug) { return $arg instanceof ContentSlug && $arg->toString() === $slug->toString(); })) ->andReturn($content); $this->cacheManager->shouldReceive('render') ->once() ->with( Mockery::type(\App\Framework\View\Caching\TemplateContext::class), Mockery::type('Closure') ) ->andReturn($renderedHtml); $result = $this->controller->home(); expect($result->data['bodyContent']->content)->toBe($renderedHtml); }); });