feat: PresetComposer – Preset aus Kurz-/Vollform, Modul und Gruppen-Presets
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
bb4d9b3baf
commit
cc20100cbc
@@ -7,6 +7,7 @@ namespace LIT\MCP\Integrations\Divi\Design;
|
||||
use ET\Builder\Framework\UserRole\UserRole;
|
||||
use ET\Builder\Packages\Conversion\Conversion;
|
||||
use ET\Builder\Packages\GlobalData\GlobalPreset;
|
||||
use ET\Builder\Packages\ModuleLibrary\ModuleRegistration;
|
||||
use LIT\MCP\Integrations\Divi\Release\Call;
|
||||
use LIT\MCP\Integrations\Divi\Release\DiviAdapter;
|
||||
use LIT\MCP\Integrations\Divi\Release\DiviLocked;
|
||||
@@ -51,6 +52,8 @@ class DesignSystemWriter implements DiviAdapter
|
||||
new Call(GlobalPreset::class . '::save_data', 1),
|
||||
new Call(Conversion::class . '::get_preset_attrs_mapping', 1),
|
||||
new Call(Conversion::class . '::get_preset_attrs', 3),
|
||||
new Call(GlobalPreset::class . '::get_group_preset_default_attr', 1),
|
||||
new Call(ModuleRegistration::class . '::get_module_settings', 1),
|
||||
new Call(UserRole::class . '::can_current_user_use_visual_builder', 0),
|
||||
],
|
||||
mirrors: [
|
||||
@@ -113,6 +116,33 @@ class DesignSystemWriter implements DiviAdapter
|
||||
return is_array($saved) ? $saved : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* The option groups of $moduleName that take group presets: attribute path (groupId) => group name, e.g.
|
||||
* "module.decoration.spacing" => "divi/spacing" (formats §3). [] without Divi or for an unknown module.
|
||||
*
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function groupsOf(string $moduleName): array
|
||||
{
|
||||
if (! class_exists(ModuleRegistration::class)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$settings = ModuleRegistration::get_module_settings($moduleName);
|
||||
if ($settings === null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$groups = [];
|
||||
foreach (GlobalPreset::get_group_preset_default_attr($settings) as $groupId => $group) {
|
||||
if (is_array($group) && is_string($group['groupName'] ?? null)) {
|
||||
$groups[(string) $groupId] = $group['groupName'];
|
||||
}
|
||||
}
|
||||
|
||||
return $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* The parts of $attrs a preset of $moduleName carries (Divi's preset attribute mapping): all, style
|
||||
* (styleAttrs) and render (renderAttrs: html + script).
|
||||
|
||||
@@ -0,0 +1,324 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Integrations\Divi\Design;
|
||||
|
||||
use Closure;
|
||||
use InvalidArgumentException;
|
||||
use LIT\MCP\Integrations\Divi\BlockTree\AttrMap;
|
||||
use LIT\MCP\Integrations\Divi\BlockTree\Block;
|
||||
use LIT\MCP\Integrations\Divi\Compose\Composer;
|
||||
use LIT\MCP\Integrations\Divi\Compose\StoredNodeGuard;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
/**
|
||||
* Builds one preset item (spec §6.3, formats §3) from the tool input: the short form ("set") and the full form
|
||||
* ("attrs") pass Composer::update() on a virtual block of the preset's module – the same choke point, placeholders
|
||||
* and S7/S26 locks as for modules (D7) –, from_node takes the preset attributes of a stored node after
|
||||
* StoredNodeGuard (E6), group_presets nest option group presets. Every resulting path must be a preset attribute
|
||||
* of the module (Divi's mapping, $presetAttrs) and, for a group preset, lie below its group. styleAttrs/renderAttrs
|
||||
* are split with the same mapping, like the Visual Builder does. Pure PHP; Divi enters through the closures.
|
||||
*/
|
||||
final class PresetComposer
|
||||
{
|
||||
private const CONTENT = 'kein Preset-Attribut (Inhalte wie Texte, Links und Bilder gehören ins Modul)';
|
||||
|
||||
/**
|
||||
* @param Closure(string, array<string, mixed>): array{all: array<string, mixed>, style: array<string, mixed>,
|
||||
* render: array<string, mixed>} $presetAttrs DesignSystemWriter::presetAttrs()
|
||||
* @param Closure(string): array<string, string> $groupsOf DesignSystemWriter::groupsOf()
|
||||
* @param Closure(): int $now Milliseconds, like the builder's Date.now().
|
||||
*/
|
||||
public function __construct(
|
||||
private Composer $composer,
|
||||
private StoredNodeGuard $guard,
|
||||
private Closure $presetAttrs,
|
||||
private Closure $groupsOf,
|
||||
private string $version,
|
||||
private Closure $now
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed>|null $existing The preset to change, null for a new one.
|
||||
* @param array<string, mixed> $input One item of lit/divi-presets-set.
|
||||
* @return array{item: array<string, mixed>, errors: array<string, string>, invalid_paths: list<string>}
|
||||
*/
|
||||
public function compose(
|
||||
PresetBook $book,
|
||||
PresetScope $scope,
|
||||
?array $existing,
|
||||
array $input,
|
||||
string $field,
|
||||
?Block $source
|
||||
): array {
|
||||
$errors = [];
|
||||
$module = $this->moduleOf($scope, $existing, $input, $source);
|
||||
if ($module === null) {
|
||||
$errors[$field . '.source_module'] = 'Modul des Gruppen-Presets fehlt (source_module oder from_node)';
|
||||
|
||||
return ['item' => [], 'errors' => $errors, 'invalid_paths' => []];
|
||||
}
|
||||
|
||||
$groupId = null;
|
||||
if ($scope->kind === PresetScope::GROUP) {
|
||||
$groupId = $this->groupIdOf($scope, $module, $existing, $input, $field, $errors);
|
||||
if ($groupId === null) {
|
||||
return ['item' => [], 'errors' => $errors, 'invalid_paths' => []];
|
||||
}
|
||||
}
|
||||
|
||||
$attrs = is_array($existing['attrs'] ?? null) ? $existing['attrs'] : [];
|
||||
if ($source !== null) {
|
||||
$problems = $this->guard->problems($source);
|
||||
if ($problems !== []) {
|
||||
$errors[$field . '.from_node'] = implode('; ', $problems);
|
||||
}
|
||||
$taken = ($this->presetAttrs)($module, $source->attrs())['all'];
|
||||
$attrs = array_replace_recursive($attrs, $groupId === null ? $taken : self::below($taken, $groupId));
|
||||
}
|
||||
|
||||
$change = array_intersect_key($input, ['set' => true, 'attrs' => true]);
|
||||
if ($change !== []) {
|
||||
$updated = $this->composer->update(Block::create($module, $attrs), $change, $field);
|
||||
$attrs = $updated['attrs'];
|
||||
$errors += $updated['errors'];
|
||||
if ($updated['invalid_paths'] !== []) {
|
||||
return ['item' => [], 'errors' => $errors, 'invalid_paths' => $updated['invalid_paths']];
|
||||
}
|
||||
}
|
||||
|
||||
$split = ($this->presetAttrs)($module, $attrs);
|
||||
$allowed = AttrMap::flatten($split['all']);
|
||||
foreach (array_keys(AttrMap::flatten($attrs)) as $path) {
|
||||
if (! array_key_exists($path, $allowed)) {
|
||||
$errors[$field . '.attrs.' . $path] = self::CONTENT;
|
||||
} elseif ($groupId !== null && ! self::inGroup($path, $groupId)) {
|
||||
$errors[$field . '.attrs.' . $path] = 'gehört nicht zur Gruppe ' . $groupId;
|
||||
}
|
||||
}
|
||||
|
||||
$nested = $this->groupPresets($book, $scope, $module, $existing, $input, $field, $errors);
|
||||
$name = $this->nameOf($existing, $input, $field, $errors);
|
||||
|
||||
return [
|
||||
'item' => $this->item($scope, $existing, $name, $module, $groupId, $attrs, $split, $nested),
|
||||
'errors' => $errors,
|
||||
'invalid_paths' => [],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed>|null $existing
|
||||
* @param array<string, mixed> $input
|
||||
*/
|
||||
private function moduleOf(PresetScope $scope, ?array $existing, array $input, ?Block $source): ?string
|
||||
{
|
||||
if ($scope->kind === PresetScope::MODULE) {
|
||||
return $scope->name;
|
||||
}
|
||||
if (is_string($existing['moduleName'] ?? null)) {
|
||||
return $existing['moduleName'];
|
||||
}
|
||||
if ($source !== null) {
|
||||
return $source->name();
|
||||
}
|
||||
$short = $input['source_module'] ?? null;
|
||||
if (! is_string($short) || preg_match('/^[a-z0-9][a-z0-9-]*(\/[a-z0-9][a-z0-9-]*)?$/D', $short) !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return str_contains($short, '/') ? $short : 'divi/' . $short;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed>|null $existing
|
||||
* @param array<string, mixed> $input
|
||||
* @param array<string, string> $errors
|
||||
*/
|
||||
private function groupIdOf(
|
||||
PresetScope $scope,
|
||||
string $module,
|
||||
?array $existing,
|
||||
array $input,
|
||||
string $field,
|
||||
array &$errors
|
||||
): ?string {
|
||||
if (is_string($existing['groupId'] ?? null)) {
|
||||
return $existing['groupId'];
|
||||
}
|
||||
|
||||
$paths = array_keys(array_filter(
|
||||
($this->groupsOf)($module),
|
||||
static fn (string $group): bool => $group === $scope->name
|
||||
));
|
||||
$wanted = $input['group_id'] ?? null;
|
||||
if (is_string($wanted) && in_array($wanted, $paths, true)) {
|
||||
return $wanted;
|
||||
}
|
||||
if (count($paths) === 1 && $wanted === null) {
|
||||
return (string) $paths[0];
|
||||
}
|
||||
|
||||
$errors[$field . '.group_id'] = $paths === []
|
||||
? sprintf('%s hat keine Gruppe %s', $module, $scope->name)
|
||||
: sprintf('mehrere Gruppen %s in %s – group_id angeben: %s', $scope->name, $module, implode(', ', $paths));
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed>|null $existing
|
||||
* @param array<string, mixed> $input
|
||||
* @param array<string, string> $errors
|
||||
* @return array<string, array{presetId: list<string>, groupName: string}>
|
||||
*/
|
||||
private function groupPresets(
|
||||
PresetBook $book,
|
||||
PresetScope $scope,
|
||||
string $module,
|
||||
?array $existing,
|
||||
array $input,
|
||||
string $field,
|
||||
array &$errors
|
||||
): array {
|
||||
$nested = is_array($existing['groupPresets'] ?? null) ? $existing['groupPresets'] : [];
|
||||
$wanted = $input['group_presets'] ?? null;
|
||||
if ($wanted === null) {
|
||||
return $nested;
|
||||
}
|
||||
if ($scope->kind !== PresetScope::MODULE || ! is_array($wanted)) {
|
||||
$errors[$field . '.group_presets'] = 'nur bei Modul-Presets, als Objekt Gruppe → Preset';
|
||||
|
||||
return $nested;
|
||||
}
|
||||
|
||||
$groups = ($this->groupsOf)($module);
|
||||
foreach ($wanted as $group => $preset) {
|
||||
$path = $field . '.group_presets.' . $group;
|
||||
$groupId = self::findGroup($groups, (string) $group);
|
||||
if ($groupId === null) {
|
||||
$errors[$path] = sprintf('keine Gruppe "%s" in %s', $group, $module);
|
||||
continue;
|
||||
}
|
||||
if ($preset === null) {
|
||||
unset($nested[$groupId]);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
$found = is_string($preset)
|
||||
? $book->find(new PresetScope(PresetScope::GROUP, $groups[$groupId]), $preset)
|
||||
: null;
|
||||
} catch (InvalidArgumentException $e) {
|
||||
$errors[$path] = $e->getMessage();
|
||||
continue;
|
||||
}
|
||||
if ($found === null) {
|
||||
$errors[$path] = sprintf('unbekanntes Gruppen-Preset "%s" (%s)', (string) $preset, $groups[$groupId]);
|
||||
continue;
|
||||
}
|
||||
$nested[$groupId] = ['presetId' => [(string) $found['id']], 'groupName' => $groups[$groupId]];
|
||||
}
|
||||
|
||||
return $nested;
|
||||
}
|
||||
|
||||
/** @param array<string, string> $groups groupId => groupName */
|
||||
private static function findGroup(array $groups, string $wanted): ?string
|
||||
{
|
||||
foreach ($groups as $groupId => $name) {
|
||||
if ($wanted === $groupId || $wanted === $name || 'divi/' . $wanted === $name) {
|
||||
return (string) $groupId;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed>|null $existing
|
||||
* @param array<string, mixed> $input
|
||||
* @param array<string, string> $errors
|
||||
*/
|
||||
private function nameOf(?array $existing, array $input, string $field, array &$errors): string
|
||||
{
|
||||
foreach (['rename', 'name'] as $key) {
|
||||
if (is_string($input[$key] ?? null) && trim($input[$key]) !== '') {
|
||||
return trim($input[$key]);
|
||||
}
|
||||
}
|
||||
if (is_string($existing['name'] ?? null)) {
|
||||
return $existing['name'];
|
||||
}
|
||||
$errors[$field . '.name'] = 'Name des neuen Presets fehlt';
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed>|null $existing
|
||||
* @param array<string, mixed> $attrs
|
||||
* @param array{all: array<string, mixed>, style: array<string, mixed>, render: array<string, mixed>} $split
|
||||
* @param array<string, mixed> $nested
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function item(
|
||||
PresetScope $scope,
|
||||
?array $existing,
|
||||
string $name,
|
||||
string $module,
|
||||
?string $groupId,
|
||||
array $attrs,
|
||||
array $split,
|
||||
array $nested
|
||||
): array {
|
||||
$now = ($this->now)();
|
||||
$item = ['type' => $scope->kind, 'id' => (string) ($existing['id'] ?? PresetBook::newId()), 'name' => $name];
|
||||
if ($groupId !== null) {
|
||||
$item += ['groupName' => $scope->name, 'groupId' => $groupId];
|
||||
}
|
||||
$item += [
|
||||
'moduleName' => $module,
|
||||
'version' => $this->version,
|
||||
'created' => (int) ($existing['created'] ?? $now),
|
||||
'updated' => $now,
|
||||
'attrs' => $attrs,
|
||||
];
|
||||
if ($split['style'] !== []) {
|
||||
$item['styleAttrs'] = $split['style'];
|
||||
}
|
||||
if ($split['render'] !== []) {
|
||||
$item['renderAttrs'] = $split['render'];
|
||||
}
|
||||
if ($nested !== []) {
|
||||
$item['groupPresets'] = $nested;
|
||||
}
|
||||
foreach (['priority', 'order'] as $key) {
|
||||
if (isset($existing[$key])) {
|
||||
$item[$key] = $existing[$key];
|
||||
}
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
private static function inGroup(string $path, string $groupId): bool
|
||||
{
|
||||
return $path === $groupId || str_starts_with($path, $groupId . '.');
|
||||
}
|
||||
|
||||
/**
|
||||
* The part of $attrs below the dotted path $groupId.
|
||||
*
|
||||
* @param array<string, mixed> $attrs
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private static function below(array $attrs, string $groupId): array
|
||||
{
|
||||
$value = AttrMap::get($attrs, $groupId);
|
||||
|
||||
return $value === null ? [] : AttrMap::set([], $groupId, $value);
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ final class DesignSystemWriterTest extends TestCase
|
||||
'GlobalPreset::get_data()', 'GlobalPreset::prepare_data()', 'GlobalPreset::validate_preset_count()',
|
||||
'GlobalPreset::get_deleted_preset_ids()', 'GlobalPreset::remove_preset_from_legacy_option()',
|
||||
'GlobalPreset::save_data()', 'Conversion::get_preset_attrs_mapping()', 'Conversion::get_preset_attrs()',
|
||||
'GlobalPreset::get_group_preset_default_attr()', 'ModuleRegistration::get_module_settings()',
|
||||
'UserRole::can_current_user_use_visual_builder()',
|
||||
],
|
||||
array_map(static fn (Call $call): string => $call->label(), $manifest->calls)
|
||||
@@ -48,4 +49,9 @@ final class DesignSystemWriterTest extends TestCase
|
||||
{
|
||||
self::assertFalse((new DesignSystemWriter())->canUseVisualBuilder());
|
||||
}
|
||||
|
||||
public function testWithoutDiviAModuleHasNoGroups(): void
|
||||
{
|
||||
self::assertSame([], (new DesignSystemWriter())->groupsOf('divi/button'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
<?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): PresetComposer
|
||||
{
|
||||
return new PresetComposer(
|
||||
ComposeFixture::composer($unfiltered),
|
||||
new StoredNodeGuard(
|
||||
ComposeFixture::cleaner($unfiltered),
|
||||
ComposeFixture::references(),
|
||||
ComposeFixture::sanitizer($unfiltered)
|
||||
),
|
||||
self::presetAttrs(),
|
||||
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, [
|
||||
'rename' => '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']);
|
||||
}
|
||||
|
||||
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']);
|
||||
}
|
||||
|
||||
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']);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user