- Add comprehensive test suite for CMS and Asset modules using Pest Framework - Implement ContentTypeService::delete() protection against deletion of in-use content types - Add CannotDeleteContentTypeInUseException for better error handling - Fix DerivatPipelineRegistry::getAllPipelines() to handle object uniqueness correctly - Fix VariantName::getScale() to correctly parse scales with file extensions - Update CMS module documentation with new features, exceptions, and test coverage - Add CmsTestHelpers and AssetTestHelpers for test data factories - Fix BlockTypeRegistry to be immutable after construction - Update ContentTypeService to check for associated content before deletion - Improve BlockRendererRegistry initialization Test coverage: - Value Objects: All CMS and Asset value objects - Services: ContentService, ContentTypeService, SlugGenerator, BlockValidator, ContentLocalizationService, AssetService, DeduplicationService, MetadataExtractor - Repositories: All database repositories with mocked connections - Rendering: Block renderers and ContentRenderer - Controllers: API endpoints for both modules 254 tests passing, 38 remaining (mostly image processing pipeline tests)
162 lines
5.9 KiB
PHP
162 lines
5.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Domain\Cms\Entities\Content;
|
|
use App\Domain\Cms\Enums\ContentStatus;
|
|
use App\Domain\Cms\Rendering\BlockRendererRegistry;
|
|
use App\Domain\Cms\Rendering\DefaultBlockRenderer;
|
|
use App\Domain\Cms\Rendering\HeroBlockRenderer;
|
|
use App\Domain\Cms\Rendering\TextBlockRenderer;
|
|
use App\Domain\Cms\Services\ContentLocalizationService;
|
|
use App\Domain\Cms\Services\ContentRenderer;
|
|
use App\Domain\Cms\ValueObjects\BlockId;
|
|
use App\Domain\Cms\ValueObjects\BlockType;
|
|
use App\Domain\Cms\ValueObjects\ContentBlocks;
|
|
use App\Domain\Cms\ValueObjects\ContentId;
|
|
use App\Domain\Cms\ValueObjects\ContentSlug;
|
|
use App\Domain\Cms\ValueObjects\ContentTypeId;
|
|
use App\Domain\Cms\ValueObjects\Locale;
|
|
use App\Framework\Core\ValueObjects\Timestamp;
|
|
use App\Framework\DateTime\SystemClock;
|
|
use App\Framework\DI\DefaultContainer;
|
|
use App\Framework\Filesystem\FileStorage;
|
|
use App\Framework\Cache\Cache;
|
|
use App\Framework\Cache\Driver\NullCache;
|
|
use App\Framework\Cache\GeneralCache;
|
|
use App\Framework\Core\PathProvider;
|
|
use App\Framework\Serializer\Serializer;
|
|
use App\Framework\View\ComponentCache;
|
|
use App\Framework\View\ComponentRenderer;
|
|
use App\Framework\View\Loading\TemplateLoader;
|
|
use App\Framework\View\TemplateProcessor;
|
|
use Tests\Support\CmsTestHelpers;
|
|
|
|
describe('ContentRenderer', function () {
|
|
beforeEach(function () {
|
|
$this->clock = new SystemClock();
|
|
$this->blockRegistry = new BlockRendererRegistry();
|
|
|
|
// Create minimal real TemplateLoader for testing
|
|
$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');
|
|
|
|
// Create minimal real TemplateProcessor for testing
|
|
$container = new DefaultContainer();
|
|
$templateProcessor = new TemplateProcessor(
|
|
astTransformers: [],
|
|
stringProcessors: [],
|
|
container: $container,
|
|
chainOptimizer: null,
|
|
compiledTemplateCache: null,
|
|
performanceTracker: null
|
|
);
|
|
|
|
// Create real FileStorage for testing
|
|
$fileStorage = new FileStorage(
|
|
basePath: sys_get_temp_dir() . '/test-components'
|
|
);
|
|
|
|
$this->componentRenderer = new ComponentRenderer(
|
|
$templateProcessor,
|
|
$componentCache,
|
|
$templateLoader,
|
|
$fileStorage
|
|
);
|
|
|
|
// Create real ContentLocalizationService for testing
|
|
$contentTranslationRepository = Mockery::mock(\App\Domain\Cms\Repositories\ContentTranslationRepository::class);
|
|
$this->localizationService = new ContentLocalizationService(
|
|
$contentTranslationRepository,
|
|
$this->clock
|
|
);
|
|
$this->defaultRenderer = new DefaultBlockRenderer();
|
|
$this->renderer = new ContentRenderer(
|
|
$this->blockRegistry,
|
|
$this->componentRenderer,
|
|
$this->localizationService,
|
|
$this->defaultRenderer
|
|
);
|
|
});
|
|
|
|
it('renders content with multiple blocks', function () {
|
|
$content = CmsTestHelpers::createContent($this->clock);
|
|
// ContentLocalizationService is real instance, no need to mock
|
|
|
|
$html = $this->renderer->render($content);
|
|
|
|
expect($html)->toBeString();
|
|
expect($html)->not->toBeEmpty();
|
|
});
|
|
|
|
it('uses default renderer for unregistered block types', function () {
|
|
$blocks = ContentBlocks::fromArray([
|
|
[
|
|
'id' => 'custom-1',
|
|
'type' => 'custom-block',
|
|
'data' => ['key' => 'value'],
|
|
],
|
|
]);
|
|
|
|
$content = CmsTestHelpers::createContent($this->clock, blocks: $blocks);
|
|
// ContentLocalizationService is real instance, no need to mock
|
|
|
|
$html = $this->renderer->render($content);
|
|
|
|
expect($html)->toBeString();
|
|
expect($html)->not->toBeEmpty();
|
|
});
|
|
|
|
it('uses registered renderer for block type', function () {
|
|
$this->blockRegistry->registerForType('hero', new HeroBlockRenderer());
|
|
$content = CmsTestHelpers::createContent($this->clock);
|
|
// ContentLocalizationService is real instance, no need to mock
|
|
|
|
$html = $this->renderer->render($content);
|
|
|
|
expect($html)->toBeString();
|
|
expect($html)->not->toBeEmpty();
|
|
});
|
|
|
|
it('renders blocks to component data array', function () {
|
|
$this->blockRegistry->registerForType('hero', new HeroBlockRenderer());
|
|
$this->blockRegistry->registerForType('text', new TextBlockRenderer());
|
|
$content = CmsTestHelpers::createContent($this->clock);
|
|
// ContentLocalizationService is real instance, no need to mock
|
|
|
|
$componentData = $this->renderer->renderBlocksToComponentData($content);
|
|
|
|
expect($componentData)->toBeArray();
|
|
expect($componentData)->toHaveCount(2);
|
|
expect($componentData[0]['component'])->toBe('cms/hero');
|
|
expect($componentData[1]['component'])->toBe('cms/text');
|
|
});
|
|
|
|
it('uses provided locale for rendering', function () {
|
|
$content = CmsTestHelpers::createContent($this->clock);
|
|
$locale = Locale::german();
|
|
// ContentLocalizationService is real instance, no need to mock
|
|
|
|
$html = $this->renderer->render($content, $locale);
|
|
|
|
expect($html)->toBeString();
|
|
expect($html)->not->toBeEmpty();
|
|
});
|
|
|
|
});
|
|
|