'; self::assertStringContainsString($buttonBytes, $this->original, 'precondition: exact bytes as expected'); $result = $this->delete()->execute([ 'post_id' => 7, 'tree_rev' => $this->treeRev(), 'ids' => ['s0.r0.c1.m2'], 'confirm' => true, ]); self::assertSame([['id' => 's0.r0.c1.m2', 'type' => 'divi/button', 'action' => 'deleted', 'label' => 'Hero – Button']], $result['changed']); self::assertSame(str_replace($buttonBytes, '', $this->original), $this->saved[0]); self::assertSame(811, $result['revision_id'], 'the restore-point revision'); self::assertStringContainsString('Hero', (string) $result['outline'], 'the outline is embedded in the result'); } /** An opaque/global module is removed as a whole – it never gets descended into. */ public function testDeleteRemovesAnOpaqueGlobalModuleAsAWhole(): void { $mixed = DiviFixtures::markup('synthetic-mixed.html'); $this->post->post_content = $mixed; $globalRow = ''; self::assertStringContainsString($globalRow, $mixed, 'precondition: exact bytes as expected'); $result = $this->delete()->execute([ 'post_id' => 7, 'tree_rev' => Rev::of($mixed), 'ids' => ['s1.g1'], 'confirm' => true, ]); self::assertSame([['id' => 's1.g1', 'type' => 'divi/row', 'action' => 'deleted', 'label' => 'Globale Zeile']], $result['changed']); self::assertSame(str_replace($globalRow, '', $mixed), $this->saved[0]); } public function testDeletingTheLastChildLeavesAValidTree(): void { $mixed = DiviFixtures::markup('synthetic-mixed.html'); $this->post->post_content = $mixed; $result = $this->delete()->execute([ 'post_id' => 7, 'tree_rev' => Rev::of($mixed), 'ids' => ['s0.c1.r0.c0.m0'], 'confirm' => true, ]); self::assertTrue($result['ok'], (string) json_encode($result)); $index = NodeIndex::build((new Parser())->parse($this->saved[0])); $emptyColumn = $index->find('s0.c1.r0.c0'); self::assertNotNull($emptyColumn, 'the now-empty column-inner is still a valid, findable node'); self::assertSame([], $emptyColumn->children()); } public function testDeleteUnknownIdReturnsNotFound(): void { $result = $this->delete()->execute([ 'post_id' => 7, 'tree_rev' => $this->treeRev(), 'ids' => ['s9'], 'confirm' => true, ]); self::assertToolError($result, 'not_found'); self::assertSame('not_found', self::clientError($result)['code']); self::assertSame([], $this->saved); } /** Same annotations as the existing destructive tools (e.g. MediaDeleteAbility). */ public function testDeleteAnnotationsMatchExistingDestructiveTools(): void { self::assertSame( ['readonly' => false, 'destructive' => true, 'idempotent' => false], $this->delete()->annotations() ); } /** Ruling S14 (Task 7): the deleted node's own confirm question, unaffected by other tests. */ public function testDeleteAsksBeforeRemovingNestedNodesOnlyOnce(): void { $duplicated = $this->duplicate()->execute(['post_id' => 7, 'tree_rev' => $this->treeRev(), 'id' => 's1']); self::assertSame('s2', $duplicated['changed'][0]['id']); $delete = $this->delete(); $ask = $delete->execute(['post_id' => 7, 'tree_rev' => Rev::of($this->saved[0]), 'ids' => ['s2', 's2.r0']]); self::assertToolError($ask, 'confirm_required'); self::assertSame( '1 Divi-Knoten würden samt Inhalt entfernt: s2 section "Leistungen (Kopie)" (1 Kind-Knoten).', self::clientError($ask)['message'] ); $deleted = $delete->execute(['post_id' => 7, 'tree_rev' => Rev::of($this->saved[0]), 'ids' => ['s2'], 'confirm' => true]); self::assertSame([['id' => 's2', 'type' => 'divi/section', 'action' => 'deleted', 'label' => 'Leistungen (Kopie)']], $deleted['changed']); self::assertSame($this->original, $this->saved[1], 'deleting the copy restores the exact bytes'); } }