update()->execute(['post_id' => 7, 'tree_rev' => $this->treeRev(), 'changes' => [ ['id' => 's0.r0.c1.m2', 'set' => ['text' => 'Jetzt buchen']], ['id' => 's0.r0.c1.m0', 'set' => ['level' => 'h9']], ['id' => 's0.r0.c1.m9', 'label' => 'x'], ]]); self::assertToolError($failed, 'validation_failed'); $fields = self::clientError($failed)['fields']; self::assertArrayHasKey('changes[1].set.level', $fields); self::assertArrayHasKey('changes[2].id', $fields, 'every error of the call in one response (S39)'); self::assertSame([], $this->saved); $result = $this->update()->execute(['post_id' => 7, 'tree_rev' => $this->treeRev(), 'changes' => [ ['id' => 's0.r0.c1.m2', 'set' => ['text' => 'Jetzt buchen']], ['id' => 's0.r0.c1.m0', 'attrs' => ['title.decoration.font.font' => ['phone' => null]]], ]]); self::assertTrue($result['ok']); self::assertSame(['s0.r0.c1.m2', 's0.r0.c1.m0'], array_column($result['changed'], 'id')); self::assertSame(['updated', 'updated'], array_column($result['changed'], 'action')); self::assertSame(Rev::of($this->saved[0]), $result['tree_rev']); $index = NodeIndex::build((new Parser())->parse($this->saved[0])); self::assertSame('Jetzt buchen', $index->find('s0.r0.c1.m2')?->attrs()['button']['innerContent']['desktop']['value']['text']); self::assertArrayNotHasKey('phone', $index->find('s0.r0.c1.m0')?->attrs()['title']['decoration']['font']['font']); // Untouched section s1 keeps its exact bytes. self::assertStringContainsString(substr($this->original, (int) strpos($this->original, '