feat: admin check steps with throwaway write test and reports

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-30 02:21:09 +02:00
co-authored by Claude Opus 5.5
parent c6a1996be6
commit 47040aa43d
14 changed files with 1026 additions and 26 deletions
@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
namespace LIT\MCP\Integrations\Divi\Release;
use Closure;
use LIT\MCP\Integrations\Divi\BlockTree\Block;
use LIT\MCP\Integrations\Divi\BlockTree\Parser;
use LIT\MCP\Integrations\Divi\BlockTree\Serializer;
defined('ABSPATH') || exit;
/**
* Spec §6.2 step 3: a builder-made Divi 5 page (reference-page.html, from the converted fixture) parses and
* serializes byte-identically, and WordPress' parse_blocks() sees the same block names and nesting as the plugin's
* parser. Nothing is written. Not final so CompatibilityCheck can be tested with a mock.
*/
class FormatCheck
{
public const REFERENCE = __DIR__ . '/reference-page.html';
/** @param (Closure(string): array<int, mixed>)|null $wpBlocks null = parse_blocks(). */
public function __construct(private ?string $file = null, private ?Closure $wpBlocks = null)
{
}
/** @return list<string> */
public function problems(): array
{
$file = $this->file ?? self::REFERENCE;
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- local plugin file.
$content = is_file($file) ? file_get_contents($file) : false;
if (! is_string($content) || $content === '') {
return ['Der Referenzinhalt der Prüfung fehlt im Plugin.'];
}
$document = (new Parser())->parse($content);
$problems = [];
if (Serializer::document($document) !== $content) {
$problems[] = 'Parser und Serializer geben den Referenzinhalt nicht byte-gleich zurück.';
}
$wordPress = ($this->wpBlocks ?? static fn (string $markup): array => parse_blocks($markup))($content);
if (self::structure($document->children()) !== self::wpStructure($wordPress)) {
$problems[] = 'WordPress liest eine andere Blockstruktur als LIT MCP.';
}
return $problems;
}
/**
* @param list<Block> $blocks
* @return list<array{0: string, 1: list<mixed>}>
*/
private static function structure(array $blocks): array
{
return array_map(
static fn (Block $block): array => [$block->name(), self::structure($block->children())],
$blocks
);
}
/**
* @param array<int, mixed> $blocks parse_blocks() result; freeform parts (blockName null) are left out.
* @return list<array{0: string, 1: list<mixed>}>
*/
private static function wpStructure(array $blocks): array
{
$structure = [];
foreach ($blocks as $block) {
if (is_array($block) && is_string($block['blockName'] ?? null)) {
$inner = is_array($block['innerBlocks'] ?? null) ? $block['innerBlocks'] : [];
$structure[] = [$block['blockName'], self::wpStructure($inner)];
}
}
return $structure;
}
}