feat: Divi BlockTree – byte-exact parser/serializer with round-trip fixtures

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
2026-09-26 11:26:14 +02:00
co-authored by Claude Opus 5.5
parent c9b31a61da
commit 8222240ed6
18 changed files with 819 additions and 0 deletions
@@ -0,0 +1,35 @@
<?php
/**
* Generates a Divi 5 fixture from a Divi 4 shortcode file with Divi's own server-side
* conversion (the same code the Divi 5 migrator uses) and writes it into tests/Fixtures/divi/.
* Usage (inside DDEV, from the WordPress root):
* wp eval-file wp-content/plugins/WP-LIT-MCP/tests/Integration/divi-convert-fixture.php <source.shortcode> <target.html>
* Paths are relative to the plugin's tests/Fixtures/divi directory.
*/
use ET\Builder\Packages\Conversion\Conversion;
$args = $args ?? [];
if (count($args) !== 2) {
WP_CLI::error('Usage: divi-convert-fixture.php <source.shortcode> <target.html>');
}
if (! class_exists(Conversion::class)) {
WP_CLI::error('Divi 5 is not active.');
}
$dir = dirname(__DIR__) . '/Fixtures/divi/';
$source = file_get_contents($dir . $args[0]);
if ($source === false) {
WP_CLI::error('Cannot read ' . $dir . $args[0]);
}
$converted = Conversion::maybeConvertContent(trim($source));
if (! str_contains($converted, '<!-- wp:divi/section')) {
WP_CLI::error('Conversion produced no Divi 5 sections.');
}
$markup = '<!-- wp:divi/placeholder -->' . $converted . '<!-- /wp:divi/placeholder -->';
if (file_put_contents($dir . $args[1], $markup) === false) {
WP_CLI::error('Cannot write ' . $dir . $args[1]);
}
WP_CLI::success(sprintf('%s: %d bytes', $args[1], strlen($markup)));
+45
View File
@@ -0,0 +1,45 @@
<?php
/**
* Exports the post_content of a page built in the Divi Visual Builder byte-exact into
* tests/Fixtures/divi/builder-<name>.html. Every file there is part of the parse → serialize
* round-trip test (tests/Unit/Integrations/Divi/BlockTree/ParserTest.php).
* Usage (DDEV, WordPress root):
* wp eval-file wp-content/plugins/WP-LIT-MCP/tests/Integration/divi-export-page.php <post_id> <name>
* <name>: lowercase letters, digits and dashes, e.g. "startseite-mit-global".
* Only use pages without personal data – fixtures are committed.
* No `declare(strict_types=1);` – wp eval-file runs the file through eval().
*/
defined('WP_CLI') && WP_CLI || exit;
$args = $args ?? [];
$postId = (int) ($args[0] ?? 0);
$name = (string) ($args[1] ?? '');
if ($postId <= 0 || preg_match('/^[a-z0-9]+(-[a-z0-9]+)*$/', $name) !== 1) {
WP_CLI::error('Usage: divi-export-page.php <post_id> <name>');
}
$post = get_post($postId);
if (! $post instanceof WP_Post) {
WP_CLI::error('No post ' . $postId);
}
$content = (string) $post->post_content;
if (! str_contains($content, '<!-- wp:divi/')) {
WP_CLI::error('Post ' . $postId . ' has no Divi 5 content (open and save it in the Divi builder first).');
}
$file = dirname(__DIR__) . '/Fixtures/divi/builder-' . $name . '.html';
if (file_put_contents($file, $content) === false) {
WP_CLI::error('Cannot write ' . $file);
}
WP_CLI::success(sprintf(
'%s: %d bytes, %d blocks, globalModule: %s, tree_rev %s. Now run composer test.',
basename($file),
strlen($content),
substr_count($content, '<!-- wp:'),
str_contains($content, 'globalModule') ? 'yes' : 'no',
substr(hash('sha256', $content), 0, 12)
));