feat: Umschreib-Strategien für Preset-Zuweisung und -Entkopplung (TreeRewrite, AssignRewrite, DetachRewrite)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-10-18 16:57:33 +02:00
co-authored by Claude Sonnet 5.5
parent 763d765660
commit d36e8286e8
7 changed files with 787 additions and 0 deletions
@@ -0,0 +1,117 @@
<?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\BlockTree\NodeIndex;
use LIT\MCP\Integrations\Divi\BlockTree\Parser;
use LIT\MCP\Integrations\Divi\Design\DetachRewrite;
use LIT\MCP\Integrations\Divi\Design\PresetScope;
use PHPUnit\Framework\TestCase;
final class DetachRewriteTest extends TestCase
{
/** @return NodeIndex Blocks below s0.r0.c0 are m0, m1, … (g0, … for instances) in the order of $inner. */
private static function index(string $inner): NodeIndex
{
return NodeIndex::build((new Parser())->parse(
'<!-- wp:divi/section --><!-- wp:divi/row --><!-- wp:divi/column -->' . $inner
. '<!-- /wp:divi/column --><!-- /wp:divi/row --><!-- /wp:divi/section -->'
));
}
private static function node(NodeIndex $index, string $id): Block
{
$block = $index->find('s0.r0.c0.' . $id);
self::assertNotNull($block);
return $block;
}
public function testRemovesAModulePresetFromTheStack(): void
{
$index = self::index(
'<!-- wp:divi/button {"modulePreset":["aaaaaaaaaa","bbbbbbbbbb"]} /-->'
. '<!-- wp:divi/button {"modulePreset":["aaaaaaaaaa"],"x":1} /-->'
. '<!-- wp:divi/text {"modulePreset":["aaaaaaaaaa"]} /-->'
. '<!-- wp:divi/button {"modulePreset":"aaaaaaaaaa"} /-->'
. '<!-- wp:divi/button {"modulePreset":["cccccccccc"]} /-->'
);
$rewrite = new DetachRewrite(new PresetScope('module', 'divi/button'), 'aaaaaaaaaa');
self::assertSame(['modulePreset' => ['bbbbbbbbbb']], $rewrite->rewrite(self::node($index, 'm0')));
self::assertSame(['x' => 1], $rewrite->rewrite(self::node($index, 'm1')), 'an empty stack leaves no key');
self::assertNull($rewrite->rewrite(self::node($index, 'm2')), 'another module');
self::assertSame([], $rewrite->rewrite(self::node($index, 'm3')), 'legacy string form');
self::assertNull($rewrite->rewrite(self::node($index, 'm4')), 'the ID is not in the stack');
}
public function testRemovesAGroupPresetFromEveryEntryOfTheGroup(): void
{
$index = self::index(
'<!-- wp:divi/text {"groupPreset":{'
. '"content.decoration.bodyFont":{"presetId":["gggggggggg","hhhhhhhhhh"],"groupName":"divi/font-body"},'
. '"module.decoration.spacing":{"presetId":["gggggggggg"],"groupName":"divi/spacing"}}} /-->'
. '<!-- wp:divi/text {"groupPreset":{"content.decoration.bodyFont":'
. '{"presetId":["gggggggggg"],"groupName":"divi/font-body"}},"x":1} /-->'
. '<!-- wp:divi/text {"groupPreset":{"module.decoration.spacing":'
. '{"presetId":["gggggggggg"],"groupName":"divi/spacing"}}} /-->'
. '<!-- wp:core/paragraph {"groupPreset":{"content.decoration.bodyFont":'
. '{"presetId":["gggggggggg"],"groupName":"divi/font-body"}}} /-->'
);
$rewrite = new DetachRewrite(new PresetScope('group', 'divi/font-body'), 'gggggggggg');
$first = $rewrite->rewrite(self::node($index, 'm0'));
self::assertSame(['hhhhhhhhhh'], $first['groupPreset']['content.decoration.bodyFont']['presetId'] ?? null);
self::assertSame(
['presetId' => ['gggggggggg'], 'groupName' => 'divi/spacing'],
$first['groupPreset']['module.decoration.spacing'] ?? null,
'another group keeps its preset'
);
self::assertSame(['x' => 1], $rewrite->rewrite(self::node($index, 'm1')), 'empty entry and map are removed');
self::assertNull($rewrite->rewrite(self::node($index, 'm2')), 'the ID sits in another group');
self::assertNull($rewrite->rewrite(self::node($index, 'x0')), 'not a Divi block');
}
public function testAnUntouchedBlockStaysNull(): void
{
$index = self::index('<!-- wp:divi/button /--><!-- wp:divi/text {"groupPreset":{}} /-->');
self::assertNull(
(new DetachRewrite(new PresetScope('module', 'divi/button'), 'aaaaaaaaaa'))->rewrite(self::node($index, 'm0'))
);
self::assertNull(
(new DetachRewrite(new PresetScope('group', 'divi/font-body'), 'gggggggggg'))->rewrite(self::node($index, 'm1'))
);
}
public function testNotesAreEmpty(): void
{
$index = self::index('<!-- wp:divi/button /-->');
self::assertSame(
[],
(new DetachRewrite(new PresetScope('module', 'divi/button'), 'aaaaaaaaaa'))
->notes(self::node($index, 'm0'), 's0.r0.c0.m0')
);
}
public function testInstancesThatMentionTheIdConcernTheRewrite(): void
{
$index = self::index(
'<!-- wp:divi/global-layout {"globalModule":"1058","blockName":"divi/button",'
. '"localAttrs":{"modulePreset":["aaaaaaaaaa"]}} /-->'
. '<!-- wp:divi/global-layout {"globalModule":"1059","blockName":"divi/section"} -->'
. '<!-- wp:divi/button {"modulePreset":["aaaaaaaaaa"],"globalParent":"1059"} /-->'
. '<!-- /wp:divi/global-layout -->'
);
$rewrite = new DetachRewrite(new PresetScope('module', 'divi/button'), 'aaaaaaaaaa');
$other = new DetachRewrite(new PresetScope('module', 'divi/button'), 'bbbbbbbbbb');
self::assertTrue($rewrite->concernsInstance(self::node($index, 'g0')), 'localAttrs');
self::assertTrue($rewrite->concernsInstance(self::node($index, 'g1')), 'snapshot of a child');
self::assertFalse($other->concernsInstance(self::node($index, 'g0')));
}
}