feat: Divi path IDs, attribute paths and outline
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Tests\Unit\Integrations\Divi\BlockTree;
|
||||
|
||||
use LIT\MCP\Integrations\Divi\BlockTree\NodeIndex;
|
||||
use LIT\MCP\Integrations\Divi\BlockTree\Parser;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class NodeIndexTest extends TestCase
|
||||
{
|
||||
public function testAssignsPathIdsPerParentAndLetter(): void
|
||||
{
|
||||
$index = NodeIndex::build((new Parser())->parse($this->fixture('synthetic-basic.html')));
|
||||
|
||||
self::assertSame(
|
||||
[
|
||||
's0', 's0.r0', 's0.r0.c0', 's0.r0.c0.m0', 's0.r0.c1', 's0.r0.c1.m0', 's0.r0.c1.m1', 's0.r0.c1.m2',
|
||||
's1', 's1.r0', 's1.r0.c0', 's1.r0.c0.m0', 's1.r0.c1', 's1.r0.c1.m0', 's1.r0.c1.m0.m0',
|
||||
's1.r0.c1.m0.m1',
|
||||
],
|
||||
array_column($index->entries(), 'id')
|
||||
);
|
||||
self::assertSame('divi/button', $index->find('s0.r0.c1.m2')?->name());
|
||||
self::assertSame('s0.r0.c1', $index->parentId($index->find('s0.r0.c1.m2')));
|
||||
self::assertNull($index->parentId($index->find('s1')));
|
||||
self::assertSame('divi/placeholder', $index->root() instanceof \LIT\MCP\Integrations\Divi\BlockTree\Block
|
||||
? $index->root()->name() : '');
|
||||
self::assertSame($index->root(), $index->parentOf($index->find('s0')));
|
||||
self::assertNull($index->find('s9'));
|
||||
}
|
||||
|
||||
public function testGlobalAndForeignBlocksAreLeavesWithOwnLetters(): void
|
||||
{
|
||||
$index = NodeIndex::build((new Parser())->parse($this->fixture('synthetic-mixed.html')));
|
||||
|
||||
self::assertSame(
|
||||
['x0', 's0', 's0.c0', 's0.c0.m0', 's0.c1', 's0.c1.r0', 's0.c1.r0.c0', 's0.c1.r0.c0.m0', 's0.c1.r0.c1',
|
||||
's1', 's1.g0', 's1.g1', 's1.g2'],
|
||||
array_column($index->entries(), 'id')
|
||||
);
|
||||
self::assertSame(1, preg_match(NodeIndex::ID_PATTERN, 's1.g2'));
|
||||
self::assertSame(0, preg_match(NodeIndex::ID_PATTERN, 's1..g2'));
|
||||
}
|
||||
|
||||
public function testDocumentWithoutPlaceholderIsItsOwnRoot(): void
|
||||
{
|
||||
$document = (new Parser())->parse('<!-- wp:divi/section /-->');
|
||||
$index = NodeIndex::build($document);
|
||||
|
||||
self::assertSame($document, $index->root());
|
||||
self::assertSame('s0', $index->entries()[0]['id']);
|
||||
}
|
||||
|
||||
private function fixture(string $name): string
|
||||
{
|
||||
return (string) file_get_contents(dirname(__DIR__, 4) . '/Fixtures/divi/' . $name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Tests\Unit\Integrations\Divi\BlockTree;
|
||||
|
||||
use LIT\MCP\Integrations\Divi\BlockTree\AttrMap;
|
||||
use LIT\MCP\Integrations\Divi\BlockTree\NodeIndex;
|
||||
use LIT\MCP\Integrations\Divi\BlockTree\NodeSummary;
|
||||
use LIT\MCP\Integrations\Divi\BlockTree\Outline;
|
||||
use LIT\MCP\Integrations\Divi\BlockTree\Parser;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use stdClass;
|
||||
|
||||
final class OutlineTest extends TestCase
|
||||
{
|
||||
public function testRowsAndTextOfAPage(): void
|
||||
{
|
||||
$index = NodeIndex::build((new Parser())->parse($this->fixture('synthetic-basic.html')));
|
||||
$outline = new Outline(['a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d' => 'Hero-Titel']);
|
||||
|
||||
$rows = $outline->rows($index);
|
||||
|
||||
self::assertSame(
|
||||
['id' => 's0.r0.c1.m0', 'type' => 'heading', 'label' => '', 'excerpt' => 'Willkommen bei Müller & Söhne',
|
||||
'info' => '', 'presets' => ['Hero-Titel'], 'children' => 0, 'editable' => true, 'depth' => 3],
|
||||
$rows[5]
|
||||
);
|
||||
self::assertSame('Spalten 8_24+16_24', $rows[1]['info']);
|
||||
self::assertSame('Seit 1987 zuverlässig – jetzt anfragen.', $rows[6]['excerpt']);
|
||||
self::assertSame(
|
||||
's0 section "Hero"' . "\n"
|
||||
. ' s0.r0 row "Hero – Zeile" [Spalten 8_24+16_24]' . "\n"
|
||||
. ' s0.r0.c0 column' . "\n"
|
||||
. ' s0.r0.c0.m0 image "Hero – Bild" – Unser Team vor dem Büro',
|
||||
implode("\n", array_slice(explode("\n", $outline->text($rows)), 0, 4))
|
||||
);
|
||||
}
|
||||
|
||||
public function testSubtreeAndDepthLimit(): void
|
||||
{
|
||||
$index = NodeIndex::build((new Parser())->parse($this->fixture('synthetic-basic.html')));
|
||||
$outline = new Outline();
|
||||
|
||||
$rows = $outline->rows($index, 's1.r0.c1', 1);
|
||||
|
||||
self::assertSame(['s1.r0.c1', 's1.r0.c1.m0'], array_column($rows, 'id'));
|
||||
self::assertSame([0, 1], array_column($rows, 'depth'));
|
||||
self::assertSame(2, $rows[1]['children']);
|
||||
}
|
||||
|
||||
public function testReadOnlyNodesAreMarked(): void
|
||||
{
|
||||
$index = NodeIndex::build((new Parser())->parse($this->fixture('synthetic-mixed.html')));
|
||||
$text = (new Outline())->text((new Outline())->rows($index));
|
||||
|
||||
self::assertStringContainsString('x0 core/paragraph (nur lesbar)', $text);
|
||||
self::assertStringContainsString('s1.g1 row "Globale Zeile" (nur lesbar)', $text);
|
||||
self::assertStringContainsString('s0 section [specialty]', $text);
|
||||
self::assertStringContainsString('s0.c1.r0 row-inner [Spalten 1_2+1_2]', $text);
|
||||
}
|
||||
|
||||
public function testSummaryHelpers(): void
|
||||
{
|
||||
self::assertSame('{post_title}', NodeSummary::textOf(
|
||||
'$variable({"type":"content","value":{"name":"post_title","settings":{}}})$'
|
||||
));
|
||||
self::assertSame('Mehr', NodeSummary::textOf(['linkUrl' => '/x', 'text' => 'Mehr']));
|
||||
self::assertSame('', NodeSummary::textOf(new stdClass()));
|
||||
}
|
||||
|
||||
public function testAttrMapFlattenGetSetRemove(): void
|
||||
{
|
||||
$attrs = [
|
||||
'builderVersion' => '5.2.1',
|
||||
'title' => ['innerContent' => ['desktop' => ['value' => 'Hallo']], 'decoration' => ['font' => [
|
||||
'font' => ['desktop' => ['value' => ['headingLevel' => 'h2']], 'phone' => ['value' => ['size' => '20px']]],
|
||||
]]],
|
||||
'module' => ['decoration' => ['spacing' => ['desktop' => ['value' => new stdClass()]]]],
|
||||
];
|
||||
|
||||
self::assertSame(
|
||||
['module.decoration.spacing', 'title.decoration.font.font', 'title.innerContent'],
|
||||
array_keys(AttrMap::flatten($attrs))
|
||||
);
|
||||
self::assertSame('Hallo', AttrMap::desktop($attrs, 'title.innerContent'));
|
||||
self::assertTrue(AttrMap::isBreakpointMap(['desktop' => [], 'phone' => []]));
|
||||
self::assertFalse(AttrMap::isBreakpointMap(['desktop' => [], 'font' => []]));
|
||||
|
||||
$changed = AttrMap::set($attrs, 'module.decoration.spacing.desktop.value.padding', '10px');
|
||||
self::assertSame('10px', AttrMap::get($changed, 'module.decoration.spacing.desktop.value.padding'));
|
||||
|
||||
$removed = AttrMap::remove($attrs, 'title.decoration.font.font');
|
||||
self::assertArrayNotHasKey('decoration', $removed['title']);
|
||||
self::assertSame($attrs, AttrMap::remove($attrs, 'nope.path'));
|
||||
}
|
||||
|
||||
private function fixture(string $name): string
|
||||
{
|
||||
return (string) file_get_contents(dirname(__DIR__, 4) . '/Fixtures/divi/' . $name);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user