Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
286 lines
13 KiB
PHP
286 lines
13 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Design;
|
|
|
|
use InvalidArgumentException;
|
|
use LIT\MCP\Integrations\Divi\Compose\VariableRef;
|
|
use LIT\MCP\Integrations\Divi\Design\PresetBook;
|
|
use LIT\MCP\Integrations\Divi\Design\PresetDiff;
|
|
use LIT\MCP\Integrations\Divi\Design\PresetScope;
|
|
use PHPUnit\Framework\TestCase;
|
|
use stdClass;
|
|
|
|
final class PresetBookTest extends TestCase
|
|
{
|
|
/** @return array<string, mixed> Stored format like GlobalPreset::get_data(). */
|
|
public static function stored(): array
|
|
{
|
|
return [
|
|
'module' => [
|
|
'divi/button' => ['default' => '', 'items' => [
|
|
'aaaaaaaaaa' => ['type' => 'module', 'id' => 'aaaaaaaaaa', 'name' => 'CTA primär',
|
|
'moduleName' => 'divi/button', 'attrs' => ['button' => ['x' => 1]]],
|
|
'bbbbbbbbbb' => ['type' => 'module', 'id' => 'bbbbbbbbbb', 'name' => 'Doppelt',
|
|
'moduleName' => 'divi/button', 'attrs' => []],
|
|
'cccccccccc' => ['type' => 'module', 'id' => 'cccccccccc', 'name' => 'doppelt',
|
|
'moduleName' => 'divi/button', 'attrs' => []],
|
|
]],
|
|
'divi/text' => ['default' => 'dddddddddd', 'items' => [
|
|
'dddddddddd' => ['type' => 'module', 'id' => 'dddddddddd', 'name' => 'Fließtext',
|
|
'moduleName' => 'divi/text', 'attrs' => [],
|
|
'groupPresets' => ['content.decoration.bodyFont' => [
|
|
'presetId' => ['gggggggggg'], 'groupName' => 'divi/font-body']]],
|
|
]],
|
|
],
|
|
'group' => [
|
|
'divi/font-body' => ['default' => '', 'items' => [
|
|
'gggggggggg' => ['type' => 'group', 'id' => 'gggggggggg', 'name' => 'Lesetext',
|
|
'groupName' => 'divi/font-body', 'groupId' => 'content.decoration.bodyFont',
|
|
'moduleName' => 'divi/text', 'attrs' => []],
|
|
]],
|
|
],
|
|
];
|
|
}
|
|
|
|
/** @return array<string, mixed> stored() with HTML in one preset and CSS with ">" in another. */
|
|
public static function storedWithMarkup(): array
|
|
{
|
|
$stored = self::stored();
|
|
$stored['module']['divi/button']['items']['bbbbbbbbbb']['attrs'] = [
|
|
'module' => ['advanced' => ['html' => ['desktop' => ['value' => ['htmlBefore' => '<b>x</b>']]]]],
|
|
];
|
|
$stored['group']['divi/font-body']['items']['gggggggggg']['styleAttrs'] = [
|
|
'css' => ['desktop' => ['value' => ['freeForm' => 'selector > p { color: red; }']]],
|
|
];
|
|
|
|
return $stored;
|
|
}
|
|
|
|
/**
|
|
* stored() with references: CTA primär uses color gcid-holz000001, Doppelt variable gvid-slogan0001 (whole with
|
|
* before/after, and embedded in a longer value), Lesetext variable gvid-radius0001 in its styleAttrs.
|
|
*
|
|
* @return array<string, mixed>
|
|
*/
|
|
public static function storedWithReferences(): array
|
|
{
|
|
$stored = self::stored();
|
|
$stored['module']['divi/button']['items']['aaaaaaaaaa']['attrs'] = [
|
|
'button' => ['decoration' => ['background' => ['desktop' => ['value' => [
|
|
'color' => VariableRef::serialize('color', 'gcid-holz000001', []),
|
|
]]]]],
|
|
];
|
|
$stored['module']['divi/button']['items']['bbbbbbbbbb']['attrs'] = [
|
|
'button' => ['innerContent' => ['desktop' => ['value' => [
|
|
'text' => VariableRef::serialize('content', 'gvid-slogan0001', ['before' => 'Ab ', 'after' => '!']),
|
|
]]]],
|
|
'module' => ['meta' => ['adminLabel' => ['desktop' => [
|
|
'value' => 'Vorher ' . VariableRef::serialize('content', 'gvid-slogan0001', []),
|
|
]]]],
|
|
];
|
|
$stored['group']['divi/font-body']['items']['gggggggggg']['styleAttrs'] = [
|
|
'font' => ['size' => VariableRef::serialize('content', 'gvid-radius0001', [])],
|
|
];
|
|
|
|
return $stored;
|
|
}
|
|
|
|
public function testReferencingListsThePresetsThatNameAColorOrVariable(): void
|
|
{
|
|
$book = PresetBook::fromDivi(self::storedWithReferences());
|
|
|
|
self::assertSame(
|
|
[['id' => 'aaaaaaaaaa', 'name' => 'CTA primär', 'scope' => 'divi/button']],
|
|
$book->referencing('gcid-holz000001')
|
|
);
|
|
self::assertSame(
|
|
[['id' => 'bbbbbbbbbb', 'name' => 'Doppelt', 'scope' => 'divi/button']],
|
|
$book->referencing('gvid-slogan0001')
|
|
);
|
|
self::assertSame(
|
|
[['id' => 'gggggggggg', 'name' => 'Lesetext', 'scope' => 'divi/font-body']],
|
|
$book->referencing('gvid-radius0001')
|
|
);
|
|
self::assertSame([], $book->referencing('gvid-fehlt00001'));
|
|
}
|
|
|
|
public function testWithVariableResolvedReplacesWholeReferencesOnly(): void
|
|
{
|
|
$stored = self::storedWithReferences();
|
|
$button = new PresetScope('module', 'divi/button');
|
|
$book = PresetBook::fromDivi($stored)->withVariableResolved('gvid-slogan0001', 'Holz mit Herz');
|
|
$attrs = $book->items($button)['bbbbbbbbbb']['attrs'];
|
|
|
|
self::assertSame('Holz mit Herz', $attrs['button']['innerContent']['desktop']['value']['text']);
|
|
self::assertSame(
|
|
$stored['module']['divi/button']['items']['bbbbbbbbbb']['attrs']['module'],
|
|
$attrs['module'],
|
|
'an embedded reference stays'
|
|
);
|
|
self::assertSame($stored['module']['divi/button']['items']['aaaaaaaaaa'], $book->items($button)['aaaaaaaaaa']);
|
|
}
|
|
|
|
/** Ruling V9: a strings variable keeps the before/after of the reference. */
|
|
public function testWithVariableResolvedFramesTheValueWithBeforeAndAfter(): void
|
|
{
|
|
$book = PresetBook::fromDivi(self::storedWithReferences())
|
|
->withVariableResolved('gvid-slogan0001', 'Holz mit Herz', true);
|
|
|
|
self::assertSame(
|
|
'Ab Holz mit Herz!',
|
|
$book->items(new PresetScope('module', 'divi/button'))['bbbbbbbbbb']['attrs']['button']['innerContent']
|
|
['desktop']['value']['text']
|
|
);
|
|
}
|
|
|
|
public function testWithVariableResolvedReachesStyleAttrsAndLeavesOtherPresetsAlone(): void
|
|
{
|
|
$stored = self::storedWithReferences();
|
|
$book = PresetBook::fromDivi($stored)->withVariableResolved('gvid-radius0001', '8px');
|
|
|
|
self::assertSame(
|
|
['font' => ['size' => '8px']],
|
|
$book->items(new PresetScope('group', 'divi/font-body'))['gggggggggg']['styleAttrs']
|
|
);
|
|
self::assertSame(
|
|
PresetBook::fromDivi($stored)->items(new PresetScope('module', 'divi/button')),
|
|
$book->items(new PresetScope('module', 'divi/button'))
|
|
);
|
|
self::assertSame(
|
|
PresetBook::fromDivi($stored)->data(),
|
|
PresetBook::fromDivi($stored)->withVariableResolved('gvid-fehlt00001', 'x')->data()
|
|
);
|
|
}
|
|
/** Final review I-2: presets whose HTML/CSS Divi's prepare_data() would strip for editors. */
|
|
public function testMarkupOutsideListsUntouchedPresetsWithHtmlOrCss(): void
|
|
{
|
|
$book = PresetBook::fromDivi(self::storedWithMarkup());
|
|
|
|
self::assertSame([], PresetBook::fromDivi(self::stored())->markupOutside([]));
|
|
self::assertSame(['bbbbbbbbbb', 'gggggggggg'], $book->markupOutside([]));
|
|
self::assertSame(['gggggggggg'], $book->markupOutside(['bbbbbbbbbb', 'aaaaaaaaaa']));
|
|
self::assertSame([], $book->markupOutside(['bbbbbbbbbb', 'gggggggggg']));
|
|
}
|
|
|
|
public function testFindsByIdOrUniqueName(): void
|
|
{
|
|
$book = PresetBook::fromDivi(self::stored());
|
|
$button = new PresetScope('module', 'divi/button');
|
|
|
|
self::assertSame('aaaaaaaaaa', $book->find($button, 'aaaaaaaaaa')['id']);
|
|
self::assertSame('aaaaaaaaaa', $book->find($button, 'cta PRIMÄR')['id']);
|
|
self::assertNull($book->find($button, 'Fehlt'));
|
|
self::assertNull($book->find(new PresetScope('module', 'divi/text'), 'aaaaaaaaaa'));
|
|
self::assertSame(['bbbbbbbbbb', 'cccccccccc'], $book->idsNamed($button, 'DOPPELT'));
|
|
}
|
|
|
|
public function testAmbiguousNameListsTheIds(): void
|
|
{
|
|
$this->expectException(InvalidArgumentException::class);
|
|
$this->expectExceptionMessage(
|
|
'Preset-Name "Doppelt" ist mehrdeutig; bitte die ID angeben: bbbbbbbbbb, cccccccccc.'
|
|
);
|
|
|
|
PresetBook::fromDivi(self::stored())->find(new PresetScope('module', 'divi/button'), 'Doppelt');
|
|
}
|
|
|
|
public function testStdClassGroupsBecomeArraysAndCountCoversBothKinds(): void
|
|
{
|
|
$raw = self::stored();
|
|
$raw['group'] = (object) ['divi/font-body' => (object) $raw['group']['divi/font-body']];
|
|
$book = PresetBook::fromDivi($raw);
|
|
|
|
self::assertSame(5, $book->count());
|
|
self::assertIsArray($book->data()['group']['divi/font-body']['items']);
|
|
self::assertSame('group', $book->scopeOf('gggggggggg')?->kind);
|
|
self::assertNull($book->scopeOf('zzzzzzzzzz'));
|
|
self::assertSame(0, PresetBook::fromDivi(new stdClass())->count());
|
|
}
|
|
|
|
public function testWithWithoutAndDefaultAreImmutable(): void
|
|
{
|
|
$book = PresetBook::fromDivi(self::stored());
|
|
$button = new PresetScope('module', 'divi/button');
|
|
$new = [
|
|
'type' => 'module', 'id' => 'eeeeeeeeee', 'name' => 'Neu', 'moduleName' => 'divi/button', 'attrs' => [],
|
|
];
|
|
|
|
$added = $book->with($button, $new)->withDefault($button, 'eeeeeeeeee');
|
|
$removed = $added->without($button, 'eeeeeeeeee');
|
|
|
|
self::assertSame(5, $book->count());
|
|
self::assertSame(6, $added->count());
|
|
self::assertSame('eeeeeeeeee', $added->defaultId($button));
|
|
self::assertSame('', $removed->defaultId($button), 'removing the default clears it');
|
|
self::assertNotSame($book->rev(), $added->rev());
|
|
self::assertSame($book->rev(), PresetBook::fromDivi(self::stored())->rev());
|
|
self::assertSame('', $book->defaultId(new PresetScope('group', 'divi/spacing')));
|
|
}
|
|
|
|
public function testNestedUsersOfAGroupPreset(): void
|
|
{
|
|
$users = PresetBook::fromDivi(self::stored())->nestedUsers('gggggggggg');
|
|
|
|
self::assertCount(1, $users);
|
|
self::assertSame(['dddddddddd', 'Fließtext'], [$users[0]['id'], $users[0]['name']]);
|
|
self::assertSame('divi/text', $users[0]['scope']->name);
|
|
}
|
|
|
|
public function testRestFormatListsItemsPerName(): void
|
|
{
|
|
$rest = PresetBook::fromDivi(self::stored())->toRest();
|
|
|
|
self::assertSame('dddddddddd', $rest['module'][1]['default']);
|
|
self::assertSame(['aaaaaaaaaa', 'bbbbbbbbbb', 'cccccccccc'], array_column($rest['module'][0]['items'], 'id'));
|
|
self::assertSame('gggggggggg', $rest['group'][0]['items'][0]['id']);
|
|
self::assertTrue(array_is_list($rest['module']));
|
|
}
|
|
|
|
public function testNewIdsLookLikeTheBuildersIds(): void
|
|
{
|
|
self::assertMatchesRegularExpression('/^[0-9a-z]{10}$/', PresetBook::newId());
|
|
self::assertNotSame(PresetBook::newId(), PresetBook::newId());
|
|
}
|
|
|
|
public function testWithoutNestedRefRemovesTheIdFromEveryStack(): void
|
|
{
|
|
$stored = self::stored();
|
|
$stored['module']['divi/button']['items']['aaaaaaaaaa']['groupPresets'] = [
|
|
'button.decoration.font' => ['presetId' => ['hhhhhhhhhh', 'gggggggggg'], 'groupName' => 'divi/font'],
|
|
];
|
|
$book = PresetBook::fromDivi($stored);
|
|
|
|
$after = $book->withoutNestedRef('gggggggggg');
|
|
|
|
self::assertSame([], $after->nestedUsers('gggggggggg'));
|
|
self::assertSame([], $after->items(new PresetScope('module', 'divi/text'))['dddddddddd']['groupPresets']);
|
|
self::assertSame(
|
|
['button.decoration.font' => ['presetId' => ['hhhhhhhhhh'], 'groupName' => 'divi/font']],
|
|
$after->items(new PresetScope('module', 'divi/button'))['aaaaaaaaaa']['groupPresets']
|
|
);
|
|
self::assertCount(2, $book->nestedUsers('gggggggggg'), 'immutable');
|
|
self::assertSame(
|
|
[['dddddddddd', 'changed', ['groupPresets']], ['aaaaaaaaaa', 'changed', ['groupPresets']]],
|
|
array_map(
|
|
static fn (array $row): array => [$row['id'], $row['action'], $row['fields']],
|
|
array_reverse(PresetDiff::between($book, $after))
|
|
)
|
|
);
|
|
}
|
|
|
|
public function testWithoutNestedRefTakesAStringIdAndLeavesOthersAlone(): void
|
|
{
|
|
$book = PresetBook::fromDivi(self::stored());
|
|
self::assertSame($book->rev(), $book->withoutNestedRef('zzzzzzzzzz')->rev());
|
|
|
|
$stored = self::stored();
|
|
$text = &$stored['module']['divi/text']['items']['dddddddddd'];
|
|
$text['groupPresets']['content.decoration.bodyFont']['presetId'] = 'gggggggggg';
|
|
unset($text);
|
|
|
|
self::assertSame([], PresetBook::fromDivi($stored)->withoutNestedRef('gggggggggg')->nestedUsers('gggggggggg'));
|
|
}
|
|
}
|