Files
WP-LIT-MCP/tests/Unit/Integrations/Divi/Design/PresetComposerTest.php
T

248 lines
11 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Design;
use LIT\MCP\Integrations\Divi\BlockTree\Block;
use LIT\MCP\Integrations\Divi\Compose\StoredNodeGuard;
use LIT\MCP\Integrations\Divi\Design\PresetBook;
use LIT\MCP\Integrations\Divi\Design\PresetComposer;
use LIT\MCP\Integrations\Divi\Design\PresetScope;
use LIT\MCP\Tests\Support\ComposeFixture;
use PHPUnit\Framework\TestCase;
final class PresetComposerTest extends TestCase
{
private const NOW = 1760000000000;
/** Divi's mapping in small: decoration paths are preset attributes, innerContent is content. */
private static function presetAttrs(): \Closure
{
return static function (string $module, array $attrs): array {
$all = [];
foreach ($attrs as $key => $value) {
if (is_array($value)) {
$kept = array_diff_key($value, ['innerContent' => true]);
if ($kept !== []) {
$all[$key] = $kept;
}
}
}
return ['all' => $all, 'style' => $all, 'render' => []];
};
}
private static function composer(bool $unfiltered = true, ?\Closure $groupsOf = null): PresetComposer
{
return new PresetComposer(
ComposeFixture::composer($unfiltered),
new StoredNodeGuard(
ComposeFixture::cleaner($unfiltered),
ComposeFixture::references(),
ComposeFixture::sanitizer($unfiltered)
),
self::presetAttrs(),
$groupsOf ?? static fn (string $module): array => [
'module.decoration.spacing' => 'divi/spacing',
'button.decoration.font' => 'divi/font',
],
'5.15.1',
static fn (): int => self::NOW
);
}
private static function book(): PresetBook
{
return PresetBook::fromDivi(PresetBookTest::stored());
}
public function testNewModulePresetFromAttrs(): void
{
$result = self::composer()->compose(self::book(), new PresetScope('module', 'divi/button'), null, [
'name' => 'CTA sekundär',
'attrs' => ['button.decoration.background' => ['desktop' => ['color' => '#123456']]],
], 'items[0]', null);
self::assertSame([], $result['errors']);
$item = $result['item'];
self::assertMatchesRegularExpression('/^[0-9a-z]{10}$/', $item['id']);
self::assertSame(['module', 'CTA sekundär', 'divi/button', '5.15.1'], [$item['type'], $item['name'],
$item['moduleName'], $item['version']]);
self::assertSame([self::NOW, self::NOW], [$item['created'], $item['updated']]);
self::assertSame('#123456', $item['attrs']['button']['decoration']['background']['desktop']['value']['color']);
self::assertSame($item['attrs'], $item['styleAttrs']);
self::assertArrayNotHasKey('renderAttrs', $item);
}
public function testContentPathsAreRefused(): void
{
$result = self::composer()->compose(self::book(), new PresetScope('module', 'divi/button'), null, [
'name' => 'Mit Text',
'set' => ['text' => 'Jetzt buchen'],
], 'items[0]', null);
self::assertArrayHasKey('items[0].attrs.button.innerContent', $result['errors']);
self::assertStringContainsString('kein Preset-Attribut', $result['errors']['items[0].attrs.button.innerContent']);
}
public function testChangingMergesKeepsIdCreatedAndOrder(): void
{
$existing = self::book()->items(new PresetScope('module', 'divi/button'))['aaaaaaaaaa'];
$existing['created'] = 5;
$existing['order'] = 3;
$existing['attrs'] = ['button' => ['decoration' => ['border' => ['desktop' => ['value' => ['radius' => '4px']]]]]];
$result = self::composer()->compose(self::book(), new PresetScope('module', 'divi/button'), $existing, [
'name' => 'CTA Primär',
'attrs' => ['button.decoration.background' => ['desktop' => ['color' => '#000000']]],
], 'items[0]', null);
self::assertSame([], $result['errors']);
self::assertSame(['aaaaaaaaaa', 'CTA Primär', 5, self::NOW, 3], [$result['item']['id'], $result['item']['name'],
$result['item']['created'], $result['item']['updated'], $result['item']['order']]);
self::assertSame('4px', $result['item']['attrs']['button']['decoration']['border']['desktop']['value']['radius']);
}
/** Final review M-5: the ability resolves name/rename; the composer only takes "name" as given. */
public function testOnlyTheResolvedNameCounts(): void
{
$existing = self::book()->items(new PresetScope('module', 'divi/button'))['aaaaaaaaaa'];
$kept = self::composer()->compose(self::book(), new PresetScope('module', 'divi/button'), $existing, [
'rename' => 'Ignoriert',
], 'items[0]', null);
$missing = self::composer()->compose(self::book(), new PresetScope('module', 'divi/button'), null, [
'rename' => 'Ignoriert',
], 'items[0]', null);
self::assertSame('CTA primär', $kept['item']['name']);
self::assertSame('Name des neuen Presets fehlt', $missing['errors']['items[0].name']);
}
public function testFromNodeTakesTheDesignButNotTheContent(): void
{
$node = Block::create('divi/button', [
'button' => [
'innerContent' => ['desktop' => ['value' => ['text' => 'Hallo']]],
'decoration' => ['background' => ['desktop' => ['value' => ['color' => '#abcdef']]]],
],
]);
$result = self::composer()->compose(self::book(), new PresetScope('module', 'divi/button'), null, [
'name' => 'Aus Seite',
], 'items[0]', $node);
self::assertSame([], $result['errors']);
self::assertSame('#abcdef', $result['item']['attrs']['button']['decoration']['background']['desktop']['value']['color']);
self::assertArrayNotHasKey('innerContent', $result['item']['attrs']['button']);
}
public function testEditorCannotCarryBuilderOnlyValuesFromANode(): void
{
$node = Block::create('divi/button', [
'css' => ['desktop' => ['value' => ['mainElement' => 'color:red']]],
'button' => ['decoration' => ['background' => ['desktop' => ['value' => ['color' => '#000000']]]]],
]);
$result = self::composer(false)->compose(self::book(), new PresetScope('module', 'divi/button'), null, [
'name' => 'Mit CSS',
], 'items[0]', $node);
self::assertArrayHasKey('items[0].from_node', $result['errors']);
}
public function testGroupPresetOnlyTakesItsGroup(): void
{
$result = self::composer()->compose(self::book(), new PresetScope('group', 'divi/spacing'), null, [
'name' => 'Abstand groß',
'source_module' => 'button',
'attrs' => [
'module.decoration.spacing' => ['desktop' => ['padding' => ['top' => '40px']]],
'button.decoration.background' => ['desktop' => ['color' => '#000000']],
],
], 'items[0]', null);
self::assertSame(
'gehört nicht zur Gruppe module.decoration.spacing',
$result['errors']['items[0].attrs.button.decoration.background']
);
self::assertSame(['group', 'divi/spacing', 'module.decoration.spacing', 'divi/button'], [
$result['item']['type'], $result['item']['groupName'], $result['item']['groupId'],
$result['item']['moduleName'],
]);
}
public function testNestedGroupPresetsByShortName(): void
{
$book = self::book()->with(new PresetScope('group', 'divi/font'), [
'type' => 'group', 'id' => 'ffffffffff', 'name' => 'Button-Schrift', 'groupName' => 'divi/font',
'groupId' => 'button.decoration.font', 'moduleName' => 'divi/button', 'attrs' => [],
]);
$result = self::composer()->compose($book, new PresetScope('module', 'divi/button'), null, [
'name' => 'Mit Schrift',
'group_presets' => ['font' => 'Button-Schrift'],
], 'items[0]', null);
self::assertSame([], $result['errors']);
self::assertSame(
['button.decoration.font' => ['presetId' => ['ffffffffff'], 'groupName' => 'divi/font']],
$result['item']['groupPresets']
);
}
public function testUnknownGroupAndPresetAreErrors(): void
{
$result = self::composer()->compose(self::book(), new PresetScope('module', 'divi/button'), null, [
'name' => 'X',
'group_presets' => ['sizing' => 'Gibt es nicht', 'font' => 'Fehlt'],
], 'items[0]', null);
self::assertStringContainsString('keine Gruppe', $result['errors']['items[0].group_presets.sizing']);
self::assertStringContainsString('unbekanntes Gruppen-Preset', $result['errors']['items[0].group_presets.font']);
}
/** Ruling R3: GroupKeys replaces the first-match search – a name two groups share needs the path. */
public function testAmbiguousGroupNameIsAnErrorAndThePathResolvesIt(): void
{
$groupsOf = static fn (string $module): array => [
'button.decoration.font' => 'divi/font',
'title.decoration.font' => 'divi/font',
];
$book = self::book()->with(new PresetScope('group', 'divi/font'), [
'type' => 'group', 'id' => 'ffffffffff', 'name' => 'Schrift', 'groupName' => 'divi/font',
'groupId' => 'title.decoration.font', 'moduleName' => 'divi/button', 'attrs' => [],
]);
$composer = self::composer(true, $groupsOf);
$scope = new PresetScope('module', 'divi/button');
$ambiguous = $composer->compose($book, $scope, null, [
'name' => 'X', 'group_presets' => ['font' => 'Schrift'],
], 'items[0]', null);
self::assertSame(
'Gruppe "font" ist mehrdeutig; bitte den Attributpfad angeben: '
. 'button.decoration.font, title.decoration.font.',
$ambiguous['errors']['items[0].group_presets.font']
);
$byPath = $composer->compose($book, $scope, null, [
'name' => 'X', 'group_presets' => ['title.decoration.font' => 'Schrift'],
], 'items[0]', null);
self::assertSame([], $byPath['errors']);
self::assertSame(
['title.decoration.font' => ['presetId' => ['ffffffffff'], 'groupName' => 'divi/font']],
$byPath['item']['groupPresets']
);
}
public function testNewPresetNeedsAName(): void
{
$result = self::composer()->compose(self::book(), new PresetScope('module', 'divi/button'), null, [],
'items[0]', null);
self::assertSame('Name des neuen Presets fehlt', $result['errors']['items[0].name']);
}
}