Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
141 lines
5.4 KiB
PHP
141 lines
5.4 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Pages;
|
||
|
||
use LIT\MCP\Integrations\Divi\BlockTree\AttrMap;
|
||
use LIT\MCP\Integrations\Divi\BlockTree\Block;
|
||
use LIT\MCP\Integrations\Divi\BlockTree\Document;
|
||
use LIT\MCP\Integrations\Divi\BlockTree\NodeIndex;
|
||
use LIT\MCP\Integrations\Divi\Compose\TreeEditor;
|
||
use LIT\MCP\Integrations\Divi\Compose\VariableRef;
|
||
use LIT\MCP\Integrations\Divi\Design\DesignSystemReader;
|
||
use LIT\MCP\Integrations\Divi\Pages\DiviComposition;
|
||
use LIT\MCP\Integrations\Divi\Pages\DynamicContentMap;
|
||
use LIT\MCP\Integrations\Divi\Schema\DiviModules;
|
||
use LIT\MCP\Tests\Support\ComposeFixture;
|
||
use LIT\MCP\Tests\Support\DiviFixtures;
|
||
use LIT\MCP\Tests\Unit\TestCase;
|
||
use Mockery;
|
||
|
||
final class DiviCompositionTest extends TestCase
|
||
{
|
||
/** @var list<int> */
|
||
private array $registryCalls = [];
|
||
|
||
private function composition(): DiviComposition
|
||
{
|
||
$modules = Mockery::mock(DiviModules::class);
|
||
$modules->allows('catalog')->andReturn(DiviFixtures::catalog());
|
||
$modules->allows('builderVersion')->andReturn('5.2.1');
|
||
$design = Mockery::mock(DesignSystemReader::class);
|
||
$design->allows('read')->andReturn(DiviFixtures::designSystem());
|
||
$registry = new DynamicContentMap(function (int $postId): array {
|
||
$this->registryCalls[] = $postId;
|
||
|
||
return DiviFixtures::dynamicContentRegistry();
|
||
});
|
||
|
||
return new DiviComposition(
|
||
$modules,
|
||
$design,
|
||
ComposeFixture::sanitizer(),
|
||
ComposeFixture::media(),
|
||
$registry,
|
||
ComposeFixture::links()
|
||
);
|
||
}
|
||
|
||
private static function siteTitle(): string
|
||
{
|
||
return VariableRef::serialize('content', 'site_title', ['before' => '', 'after' => '']);
|
||
}
|
||
|
||
public function testOneLiveMapIsSharedByTheFullAndTheShortForm(): void
|
||
{
|
||
$composition = $this->composition()->forPost(7);
|
||
|
||
// Full form (AttrWriter).
|
||
$full = $composition->attrWriter()->apply(
|
||
'divi/text',
|
||
[],
|
||
['content.innerContent' => ['desktop' => self::siteTitle()]],
|
||
'attrs'
|
||
);
|
||
self::assertSame([], $full['errors']);
|
||
self::assertSame(self::siteTitle(), AttrMap::desktop($full['attrs'], 'content.innerContent'));
|
||
|
||
// Short form through the Composer (ShortformWriter) – same map, same AttrWriter.
|
||
$short = $composition->composer()->compose(
|
||
[['type' => 'button', 'text' => self::siteTitle()]],
|
||
Block::create('divi/column'),
|
||
'nodes'
|
||
);
|
||
self::assertSame([], $short['errors']);
|
||
self::assertStringContainsString('site_title', json_encode($short['blocks'][0]->attrs(), JSON_THROW_ON_ERROR));
|
||
|
||
self::assertSame([7], $this->registryCalls, 'the registry is read once per post');
|
||
self::assertSame($composition->attrWriter(), $composition->attrWriter());
|
||
self::assertSame($composition->placeholders(), $composition->placeholders());
|
||
self::assertSame($composition->references(), $composition->references());
|
||
}
|
||
|
||
/** E13a: the composition hands its link targets to Placeholders; the result still passes ValueCleaner. */
|
||
public function testInternalLinksUseTheLinkTargets(): void
|
||
{
|
||
$composer = $this->composition()->forPost(7)->composer();
|
||
|
||
$column = Block::create('divi/column');
|
||
|
||
$page = $composer->compose([['type' => 'button', 'text' => 'Kontakt', 'url' => '{{link:839}}']], $column, 'nodes');
|
||
self::assertSame([], $page['errors']);
|
||
self::assertStringContainsString(
|
||
'post_link_url_page',
|
||
json_encode($page['blocks'][0]->attrs(), JSON_THROW_ON_ERROR)
|
||
);
|
||
|
||
$missing = $composer->compose([['type' => 'button', 'url' => '{{link:999}}']], $column, 'nodes');
|
||
self::assertStringContainsString('Eintrag 999', $missing['errors']['nodes[0].url']);
|
||
}
|
||
|
||
public function testWithoutAPostTheDefaultMapApplies(): void
|
||
{
|
||
$composition = $this->composition();
|
||
|
||
$result = $composition->attrWriter()->apply(
|
||
'divi/text',
|
||
[],
|
||
['content.innerContent' => ['desktop' => self::siteTitle()]],
|
||
'attrs'
|
||
);
|
||
|
||
self::assertStringContainsString('Unbekannter Name "site_title"', $result['errors']['attrs.content.innerContent']);
|
||
self::assertSame([], $this->registryCalls);
|
||
}
|
||
|
||
public function testForPostIsAFreshCompositionPerPost(): void
|
||
{
|
||
$base = $this->composition();
|
||
$seven = $base->forPost(7);
|
||
$eight = $base->forPost(8);
|
||
|
||
self::assertNotSame($base, $seven);
|
||
self::assertNotSame($seven->placeholders(), $eight->placeholders());
|
||
self::assertNotSame($base->placeholders(), $seven->placeholders());
|
||
self::assertNotSame($seven->references(), $eight->references());
|
||
self::assertNotSame($base->references(), $seven->references());
|
||
self::assertSame([7, 8], $this->registryCalls);
|
||
}
|
||
|
||
public function testRulesEditorAndWarnings(): void
|
||
{
|
||
$composition = $this->composition();
|
||
|
||
self::assertNull($composition->rules()->violation(Block::create('divi/column'), 'divi/button'));
|
||
self::assertNotNull($composition->rules()->violation(null, 'divi/button'));
|
||
self::assertSame([], $composition->warnings()->collect(NodeIndex::build(new Document()), []));
|
||
self::assertInstanceOf(TreeEditor::class, $composition->editor());
|
||
}
|
||
}
|