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);
});
});