refactor: small Divi tree helpers out of DiviWriter (M3)

Block::isPlaceholder replaces the divi/placeholder literals (ledger T4);
NodeIndex::parentBlock/sectionLabel replace the two parentBlock copies and
DiviWriter::sectionLabel; DiviChange::idOf and Outline::summary replace the
duplicated id resolution and outline text; DiviSchema holds the shared
input properties. Behaviour unchanged.

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:
2026-09-26 18:24:29 +02:00
co-authored by Claude Opus 5.5
parent ecfece719d
commit d2cc82f31b
18 changed files with 188 additions and 86 deletions
@@ -38,6 +38,13 @@ final class BlockTest extends TestCase
self::assertSame('divi/button', Block::create('divi/button')->name());
}
public function testOnlyThePlaceholderIsAPlaceholder(): void
{
self::assertTrue(Block::create('divi/placeholder')->isPlaceholder());
self::assertFalse(Block::create('divi/section')->isPlaceholder());
self::assertFalse(Block::create('core/placeholder')->isPlaceholder());
}
public function testSetAttrsRefusesOnAnOpaqueGlobalModule(): void
{
$document = (new Parser())->parse('<!-- wp:divi/section {"globalModule":"x"} --><!-- /wp:divi/section -->');
@@ -45,6 +45,20 @@ final class NodeIndexTest extends TestCase
self::assertSame(0, preg_match(NodeIndex::ID_PATTERN, "s1\n"));
}
public function testParentBlockSkipsThePlaceholderAndSectionLabelWalksUp(): void
{
$index = NodeIndex::build((new Parser())->parse($this->fixture('synthetic-basic.html')));
$button = $index->find('s0.r0.c1.m2');
self::assertTrue($index->root() instanceof \LIT\MCP\Integrations\Divi\BlockTree\Block
&& $index->root()->isPlaceholder());
self::assertSame($index->find('s0.r0.c1'), $index->parentBlock($button));
self::assertNull($index->parentBlock($index->find('s0')), 'the placeholder is no parent node');
self::assertSame('Hero', $index->sectionLabel($button));
self::assertSame('Hero', $index->sectionLabel($index->find('s0')));
self::assertSame('', $index->sectionLabel(null));
}
public function testDocumentWithoutPlaceholderIsItsOwnRoot(): void
{
$document = (new Parser())->parse('<!-- wp:divi/section /-->');
@@ -82,6 +82,22 @@ final class OutlineTest extends TestCase
self::assertTrue($outline->wasTruncated());
}
public function testSummaryOfThePageOrOfSections(): void
{
$index = NodeIndex::build((new Parser())->parse($this->fixture('synthetic-basic.html')));
$outline = new Outline();
self::assertSame(
['text' => $outline->text($outline->rows($index)), 'truncated' => false],
$outline->summary($index)
);
self::assertSame(
$outline->text($outline->rows($index, 's0')) . "\n" . $outline->text($outline->rows($index, 's1')),
$outline->summary($index, ['s0', 's1'])['text']
);
self::assertSame(['text' => null, 'truncated' => false], $outline->summary($index, []));
}
public function testReadOnlyNodesAreMarked(): void
{
$index = NodeIndex::build((new Parser())->parse($this->fixture('synthetic-mixed.html')));