'; $sectionClose = ''; $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 = ''; 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' ); } }