Files
WP-LIT-MCP/tests/Unit/Integrations/Divi/Pages/DiviDuplicateNodeAbilityTest.php
T
janniclas.loosenandClaude Opus 5.5 7963aa19bd test: split DiviWriteAbilitiesTest per tool (T5)
One test class per Divi write tool on a shared DiviWriteAbilityTestCase;
the 30 tests move verbatim (per-test assertion counts identical). Adds the
ledger T4 test for a TreeEditor::move() StructureRules refusal.

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

115 lines
5.8 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\Pages;
use LIT\MCP\Abilities\Rev;
use LIT\MCP\Integrations\Divi\BlockTree\AttrMap;
use LIT\MCP\Integrations\Divi\BlockTree\NodeIndex;
use LIT\MCP\Integrations\Divi\BlockTree\Parser;
use LIT\MCP\Tests\Support\DiviFixtures;
final class DiviDuplicateNodeAbilityTest extends DiviWriteAbilityTestCase
{
/** The copy sits directly after the original; its own child content is byte-identical, nothing else changes. */
public function testDuplicateInsertsTheCopyDirectlyAfterTheOriginal(): void
{
$sectionOpen = '<!-- wp:divi/section {"module":{"meta":{"adminLabel":{"desktop":{"value":"Leistungen"}}}},'
. '"builderVersion":"5.2.1"} -->';
$sectionClose = '<!-- /wp:divi/section -->';
$start = strpos($this->original, $sectionOpen) + strlen($sectionOpen);
$end = strpos($this->original, $sectionClose, $start);
$rowBytes = substr($this->original, $start, $end - $start);
$original = $sectionOpen . $rowBytes . $sectionClose;
self::assertStringContainsString($original, $this->original, 'precondition: exact bytes as expected');
$result = $this->duplicate()->execute(['post_id' => 7, 'tree_rev' => $this->treeRev(), 'id' => 's1']);
self::assertSame('s2', $result['changed'][0]['id']);
self::assertSame('Leistungen (Kopie)', $result['changed'][0]['label']);
self::assertStringContainsString('Leistungen', (string) $result['outline'], 'the outline is embedded in the result');
$copyOpen = str_replace('"Leistungen"', '"Leistungen (Kopie)"', $sectionOpen);
$copy = $copyOpen . $rowBytes . $sectionClose;
self::assertSame(
str_replace($original, $original . $copy, $this->original),
$this->saved[0],
'copy directly after the original, byte-identical child content, nothing else changes'
);
}
/** A global module copy stays a byte-identical reference (read-only nodes move/duplicate as a whole). */
public function testDuplicateGlobalModuleCopyIsAByteIdenticalReference(): void
{
$mixed = DiviFixtures::markup('synthetic-mixed.html');
$this->post->post_content = $mixed;
$globalRow = '<!-- wp:divi/row {"globalModule":"913","module":{"meta":{"adminLabel":{"desktop":{"value":'
. '"Globale Zeile"}}}}} --><!-- wp:divi/column {"builderVersion":"5.2.1"} --><!-- wp:divi/text '
. '{"content":{"innerContent":{"desktop":{"value":"lokal"}}}} /--><!-- /wp:divi/column --><!-- /wp:divi/row'
. ' -->';
self::assertStringContainsString($globalRow, $mixed, 'precondition: exact bytes as expected');
$result = $this->duplicate()->execute(['post_id' => 7, 'tree_rev' => Rev::of($mixed), 'id' => 's1.g1']);
self::assertSame('s1.g2', $result['changed'][0]['id']);
self::assertSame('divi/row', $result['changed'][0]['type']);
self::assertSame('Globale Zeile', $result['changed'][0]['label'], 'opaque copies keep their label unchanged');
self::assertSame(str_replace($globalRow, $globalRow . $globalRow, $mixed), $this->saved[0]);
}
/**
* Fix round 1 (Important-1): the copy's admin label must be the raw stored desktop value
* plus " (Kopie)" – never NodeSummary::label()'s display-shortened text (80-char limit).
*/
public function testDuplicateCopiesALongAdminLabelVerbatim(): void
{
$long = str_repeat('Ausführliche Beschriftung, ', 4) . 'Ende';
self::assertGreaterThan(80, strlen($long), 'precondition: longer than the NodeSummary::label() display limit');
$content = str_replace(
'"module":{"meta":{"adminLabel":{"desktop":{"value":"Hero – Button"}}}}',
'"module":{"meta":{"adminLabel":{"desktop":{"value":"' . $long . '"}}}}',
$this->original
);
self::assertNotSame($content, $this->original, 'precondition: replaced exactly once');
$this->post->post_content = $content;
$result = $this->duplicate()->execute(['post_id' => 7, 'tree_rev' => Rev::of($content), 'id' => 's0.r0.c1.m2']);
self::assertTrue($result['ok'], (string) json_encode($result));
$index = NodeIndex::build((new Parser())->parse($this->saved[0]));
$copy = $index->find('s0.r0.c1.m3');
self::assertNotNull($copy, 'the duplicate sits right after the original module');
self::assertSame(
$long . ' (Kopie)',
AttrMap::desktop($copy->attrs(), 'module.meta.adminLabel'),
'stored verbatim, not shortened/ellipsised for display'
);
}
public function testDuplicateCopiesALabelWithNewlinesAndMultipleSpacesVerbatim(): void
{
$messy = "Erste Zeile mit viel Leerraum\nZweite Zeile";
$encoded = substr((string) json_encode($messy, JSON_UNESCAPED_UNICODE), 1, -1);
$content = str_replace(
'"module":{"meta":{"adminLabel":{"desktop":{"value":"Hero – Button"}}}}',
'"module":{"meta":{"adminLabel":{"desktop":{"value":"' . $encoded . '"}}}}',
$this->original
);
self::assertNotSame($content, $this->original, 'precondition: replaced exactly once');
$this->post->post_content = $content;
$result = $this->duplicate()->execute(['post_id' => 7, 'tree_rev' => Rev::of($content), 'id' => 's0.r0.c1.m2']);
self::assertTrue($result['ok'], (string) json_encode($result));
$index = NodeIndex::build((new Parser())->parse($this->saved[0]));
$copy = $index->find('s0.r0.c1.m3');
self::assertNotNull($copy, 'the duplicate sits right after the original module');
self::assertSame(
$messy . ' (Kopie)',
AttrMap::desktop($copy->attrs(), 'module.meta.adminLabel'),
'whitespace/newlines kept verbatim, not collapsed'
);
}
}