TB layouts load through ThemeBuilderAccess (P12), live layouts (attached + enabled, Q8) need a dry run and confirm (Q9) and get the shared restore point of VisibleEdits, extracted from GlobalEdits (Q2: backup when revisions are off, P9 refusals, P14 naming). divi/post-content only in body layouts (F22), also for nested stored nodes. SaveScope::ThemeBuilderLayout sets Divi flags and clears site CSS + TB cache. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
196 lines
8.2 KiB
PHP
196 lines
8.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Pages;
|
|
|
|
use Brain\Monkey\Functions;
|
|
use LIT\MCP\Abilities\Rev;
|
|
use LIT\MCP\Abilities\WriteFailed;
|
|
use LIT\MCP\Integrations\Divi\Pages\DiviDeleteAbility;
|
|
use LIT\MCP\Integrations\Divi\Pages\DiviInsertAbility;
|
|
use LIT\MCP\Integrations\Divi\Pages\DiviReplaceTextAbility;
|
|
use LIT\MCP\Integrations\Divi\Pages\DiviSaver;
|
|
use LIT\MCP\Integrations\Divi\Pages\DiviWriter;
|
|
use LIT\MCP\Integrations\Divi\Pages\VisibleEdits;
|
|
use LIT\MCP\Integrations\Divi\ThemeBuilder\Template;
|
|
use LIT\MCP\Integrations\Divi\ThemeBuilder\ThemeBuilderEdits;
|
|
use LIT\MCP\Tests\Support\DiviFixtures;
|
|
use LIT\MCP\Tests\Support\DiviWriterFixture;
|
|
use LIT\MCP\Tests\Support\LibraryFixtures;
|
|
use LIT\MCP\Tests\Support\ThemeBuilderFixtures;
|
|
use LIT\MCP\Tests\Unit\TestCase;
|
|
use Mockery;
|
|
use Throwable;
|
|
use WP_Post;
|
|
|
|
/** The Divi write tools on Theme Builder layouts (E1, Task 5). */
|
|
final class DiviWriterThemeBuilderTest extends TestCase
|
|
{
|
|
/** @var list<string> */
|
|
private array $saved = [];
|
|
|
|
/** @var list<Throwable> */
|
|
private array $failures = [];
|
|
|
|
private WP_Post $body;
|
|
|
|
private WP_Post $header;
|
|
|
|
protected function setUp(): void
|
|
{
|
|
parent::setUp();
|
|
$this->body = ThemeBuilderFixtures::layout(ThemeBuilderFixtures::BODY);
|
|
$this->header = ThemeBuilderFixtures::layout(ThemeBuilderFixtures::HEADER);
|
|
LibraryFixtures::given([$this->body, $this->header]);
|
|
Functions\when('admin_url')->alias(static fn (string $path): string => 'https://kunde.test/wp-admin/' . $path);
|
|
Functions\when('wp_revisions_enabled')->justReturn(true);
|
|
}
|
|
|
|
/** @param list<Template> $templates */
|
|
private function writer(array $templates, ?VisibleEdits $visible = null): DiviWriter
|
|
{
|
|
$access = ThemeBuilderFixtures::access([], true, ThemeBuilderFixtures::store($templates));
|
|
|
|
return DiviWriterFixture::writer(
|
|
$this->saved,
|
|
$this->failures,
|
|
false,
|
|
ThemeBuilderFixtures::loader($access),
|
|
null,
|
|
$visible === null ? new ThemeBuilderEdits($access) : new ThemeBuilderEdits($access, $visible)
|
|
);
|
|
}
|
|
|
|
/** @param array<string, mixed> $extra @return array<string, mixed> */
|
|
private function insert(WP_Post $post, array $extra): array
|
|
{
|
|
return array_merge([
|
|
'post_id' => (int) $post->ID,
|
|
'tree_rev' => Rev::of((string) $post->post_content),
|
|
'parent' => 's0.r0.c0',
|
|
'nodes' => [['type' => 'text', 'content' => 'Zusatz']],
|
|
], $extra);
|
|
}
|
|
|
|
public function testInsertIntoALiveLayoutIsADryRunFirst(): void
|
|
{
|
|
$ability = new DiviInsertAbility($this->writer([ThemeBuilderFixtures::template(1066, ['enabled' => true])]));
|
|
|
|
$dry = $ability->execute($this->insert($this->body, []));
|
|
self::assertToolError($dry, 'confirm_required');
|
|
self::assertSame([], $this->saved);
|
|
|
|
$result = $ability->execute($this->insert($this->body, ['confirm' => true]));
|
|
self::assertTrue($result['ok']);
|
|
self::assertCount(1, $this->saved);
|
|
self::assertSame(1066, $result['templates'][0]['id']);
|
|
}
|
|
|
|
public function testLayoutOfADisabledTemplateSavesAtOnce(): void
|
|
{
|
|
$result = (new DiviInsertAbility($this->writer([ThemeBuilderFixtures::template()])))
|
|
->execute($this->insert($this->body, []));
|
|
|
|
self::assertTrue($result['ok']);
|
|
self::assertSame([], $result['templates']);
|
|
self::assertStringContainsString('Zusatz', $this->saved[0]);
|
|
self::assertSame('https://kunde.test/wp-admin/admin.php?page=et_theme_builder', $result['edit_url'] ?? null);
|
|
}
|
|
|
|
public function testDeleteInALiveLayoutAsksOnceForBoth(): void
|
|
{
|
|
$ability = new DiviDeleteAbility($this->writer([ThemeBuilderFixtures::template(1066, ['enabled' => true])]));
|
|
$input = ['post_id' => 1062, 'tree_rev' => Rev::of((string) $this->body->post_content), 'ids' => ['s0.r0.c0.m1']];
|
|
|
|
$dry = $ability->execute($input);
|
|
self::assertToolError($dry, 'confirm_required');
|
|
self::assertArrayHasKey('templates', $dry->get_error_data());
|
|
|
|
$result = $ability->execute($input + ['confirm' => true]);
|
|
self::assertTrue($result['ok']);
|
|
self::assertCount(1, $this->saved);
|
|
}
|
|
|
|
public function testPostContentOnlyInTheBody(): void
|
|
{
|
|
$ability = new DiviInsertAbility($this->writer([]));
|
|
$postContent = ['nodes' => [['type' => 'post-content']]];
|
|
|
|
self::assertToolError($ability->execute($this->insert($this->header, $postContent)), 'validation_failed');
|
|
self::assertTrue($ability->execute($this->insert($this->body, $postContent))['ok']);
|
|
}
|
|
|
|
/** Ruling Q2 / P14: a failed save of a live layout after its backup names the backup. */
|
|
public function testAFailedSaveAfterTheBackupNamesIt(): void
|
|
{
|
|
Functions\when('wp_revisions_enabled')->justReturn(false);
|
|
$this->failures = [WriteFailed::rolledBack(DiviSaver::REASON, new \RuntimeException('hook'), postId: 1062)];
|
|
$backups = 0;
|
|
$visible = new VisibleEdits(static function (WP_Post $post) use (&$backups): int {
|
|
++$backups;
|
|
|
|
return 1071;
|
|
}, static fn (): bool => true);
|
|
$ability = new DiviInsertAbility(
|
|
$this->writer([ThemeBuilderFixtures::template(1066, ['enabled' => true])], $visible)
|
|
);
|
|
|
|
try {
|
|
$ability->execute($this->insert($this->body, ['confirm' => true]));
|
|
self::fail('WriteFailed expected');
|
|
} catch (WriteFailed $e) {
|
|
$error = $e->toError();
|
|
}
|
|
|
|
self::assertSame(1, $backups);
|
|
self::assertSame(1071, $error->get_error_data()['backup_id']);
|
|
self::assertStringContainsString('Sicherung 1071 liegt im Papierkorb', $error->get_error_data()['hint']);
|
|
self::assertStringContainsString('Theme-Builder-Layouts', $error->get_error_data()['hint']);
|
|
self::assertSame([], $this->saved);
|
|
}
|
|
|
|
/** Without a ThemeBuilderEdits (no Theme Builder wiring) a layout is never written. */
|
|
public function testWithoutThemeBuilderEditsLayoutsAreNotWritten(): void
|
|
{
|
|
$access = ThemeBuilderFixtures::access([], true, ThemeBuilderFixtures::store([]));
|
|
$writer = DiviWriterFixture::writer($this->saved, $this->failures, false, ThemeBuilderFixtures::loader($access));
|
|
|
|
self::assertToolError((new DiviInsertAbility($writer))->execute($this->insert($this->body, [])), 'unsupported');
|
|
self::assertSame([], $this->saved);
|
|
}
|
|
|
|
/** lit/divi-replace-text never sends confirm: a live layout only answers with its templates. */
|
|
public function testReplaceTextLeavesALiveLayoutAndNamesItsTemplates(): void
|
|
{
|
|
Functions\when('get_post_type_object')->justReturn(
|
|
(object) ['cap' => (object) ['edit_posts' => 'edit_pages', 'edit_others_posts' => 'edit_others_pages']]
|
|
);
|
|
$access = ThemeBuilderFixtures::access(
|
|
[],
|
|
true,
|
|
ThemeBuilderFixtures::store([ThemeBuilderFixtures::template(1065, ['enabled' => true])])
|
|
);
|
|
$loader = ThemeBuilderFixtures::loader($access);
|
|
$writer = DiviWriterFixture::writer($this->saved, $this->failures, false, $loader, null, new ThemeBuilderEdits($access));
|
|
$ability = new DiviReplaceTextAbility(DiviFixtures::schema(), $loader, $writer, Mockery::mock(\wpdb::class)->makePartial());
|
|
|
|
$result = $ability->execute([
|
|
'search' => 'Globaler Header der Standard-Vorlage',
|
|
'replace' => 'Kopfzeile',
|
|
'dry_run' => false,
|
|
'confirm' => true,
|
|
'targets' => [['post_id' => 1061, 'tree_rev' => Rev::of((string) $this->header->post_content)]],
|
|
]);
|
|
|
|
self::assertIsArray($result, $result instanceof \WP_Error ? $result->get_error_message() : '');
|
|
$entry = $result['results'][0];
|
|
self::assertFalse($entry['ok']);
|
|
self::assertStringContainsString('Theme-Builder-Layout 1061', $entry['error']);
|
|
self::assertStringContainsString('lit-divi-update', $entry['error']);
|
|
self::assertStringNotContainsString('Globale Bibliothekseinträge', $entry['error']);
|
|
self::assertSame(1065, $entry['templates'][0]['id']);
|
|
self::assertSame([], $this->saved);
|
|
}
|
|
}
|