Files
WP-LIT-MCP/tests/Unit/Integrations/Divi/BlockTree/OutlineTest.php
T
janniclas.loosenandClaude Opus 5.5 ea1fd607b2 feat: Divi tree editor and quality warnings
TreeEditor (position/insert/move/remove/duplicate on the BlockTree) and
Warnings (post-write quality checks), plus S18's outline breadth limit
on the 2a Outline/DiviOutlineAbility.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
2026-09-26 14:42:26 +02:00

154 lines
6.6 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\BlockTree;
use LIT\MCP\Integrations\Divi\BlockTree\AttrMap;
use LIT\MCP\Integrations\Divi\BlockTree\Block;
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::assertFalse($outline->wasTruncated());
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,
'truncated' => false],
$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']);
}
/** Ruling S18: outline breadth limit – caps children per parent, marks the parent truncated. */
public function testBreadthLimitCapsChildrenPerParentAndMarksTruncation(): void
{
$index = NodeIndex::build((new Parser())->parse($this->fixture('synthetic-basic.html')));
$outline = new Outline();
// s0.r0.c1 has three children (heading, text, button); cap them at two.
$rows = $outline->rows($index, 's0.r0.c1', null, 2);
self::assertSame(['s0.r0.c1', 's0.r0.c1.m0', 's0.r0.c1.m1'], array_column($rows, 'id'));
self::assertTrue($rows[0]['truncated'], 'the parent whose children were cut is marked');
self::assertFalse($rows[1]['truncated']);
self::assertTrue($outline->wasTruncated());
$full = (new Outline())->rows($index, 's0.r0.c1');
self::assertSame(4, count($full), 'the default limit (50) does not cut three children');
}
/** The page root has no row of its own, so a top-level cap can only show up in wasTruncated(). */
public function testBreadthLimitAtTheTopLevelStillReportsTruncation(): void
{
$index = NodeIndex::build((new Parser())->parse($this->fixture('synthetic-basic.html')));
$outline = new Outline();
$rows = $outline->rows($index, null, 0, 1);
self::assertSame(['s0'], array_column($rows, 'id'));
self::assertTrue($outline->wasTruncated());
}
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 testLabelIsWhitespaceNormalizedAndBounded(): void
{
$block = self::blockWithAdminLabel("Hero \n Titel\twith space");
self::assertSame('Hero Titel with space', NodeSummary::label($block));
$long = str_repeat('a', 200);
$shortened = NodeSummary::label(self::blockWithAdminLabel($long));
self::assertLessThan(200, mb_strlen($shortened));
self::assertStringEndsWith('…', $shortened);
}
private static function blockWithAdminLabel(string $label): Block
{
return Block::create('divi/text', [
'module' => ['meta' => ['adminLabel' => ['desktop' => ['value' => $label]]]],
]);
}
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);
}
}