feat: PresetDiff – Unterschied zweier Preset-Bestände für den Probelauf

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-10-16 17:29:51 +02:00
co-authored by Claude Haiku 5.5
parent 578ccba118
commit 388b52ea76
2 changed files with 174 additions and 0 deletions
+125
View File
@@ -0,0 +1,125 @@
<?php
declare(strict_types=1);
namespace LIT\MCP\Integrations\Divi\Design;
defined('ABSPATH') || exit;
/**
* What a preset change does (spec §4 step 4, "diff"): per preset added, changed (with the changed top-level keys),
* removed, and per module/group a default that is set or cleared. Keys the plugin derives or Divi maintains
* (updated, styleAttrs, renderAttrs, version) are no change of their own. Sorted by kind, scope, id, action.
* Pure PHP.
*/
final class PresetDiff
{
private const DERIVED = ['updated', 'styleAttrs', 'renderAttrs', 'version'];
/**
* @return list<array{kind: string, scope: string, id: string, name: string, action: string,
* fields?: list<string>}>
*/
public static function between(PresetBook $before, PresetBook $after): array
{
$old = $before->data();
$new = $after->data();
$rows = [];
foreach ([PresetScope::MODULE, PresetScope::GROUP] as $kind) {
$names = array_unique(array_merge(array_keys($old[$kind] ?? []), array_keys($new[$kind] ?? [])));
sort($names);
foreach ($names as $name) {
$was = $old[$kind][$name] ?? ['default' => '', 'items' => []];
$is = $new[$kind][$name] ?? ['default' => '', 'items' => []];
array_push($rows, ...self::items($kind, (string) $name, $was['items'], $is['items']));
array_push($rows, ...self::defaults($kind, (string) $name, $was, $is));
}
}
return $rows;
}
/**
* @param array<string, array<string, mixed>> $was
* @param array<string, array<string, mixed>> $is
* @return list<array<string, mixed>>
*/
private static function items(string $kind, string $scope, array $was, array $is): array
{
$ids = array_unique(array_merge(array_keys($was), array_keys($is)));
sort($ids);
$rows = [];
foreach ($ids as $id) {
$key = (string) $id;
$before = $was[$key] ?? null;
$after = $is[$key] ?? null;
if ($before === null || $after === null) {
$rows[] = self::row($kind, $scope, $key, $after ?? $before, $before === null ? 'added' : 'removed');
continue;
}
$fields = self::changedFields($before, $after);
if ($fields !== []) {
$rows[] = self::row($kind, $scope, $key, $after, 'changed') + ['fields' => $fields];
}
}
return $rows;
}
/**
* @param array{default: string, items: array<string, array<string, mixed>>} $was
* @param array{default: string, items: array<string, array<string, mixed>>} $is
* @return list<array<string, mixed>>
*/
private static function defaults(string $kind, string $scope, array $was, array $is): array
{
if ($was['default'] === $is['default']) {
return [];
}
if ($is['default'] === '') {
$gone = $was['items'][$was['default']] ?? null;
return [self::row($kind, $scope, $was['default'], $gone, 'default_cleared')];
}
$item = $is['items'][$is['default']] ?? null;
return [self::row($kind, $scope, $is['default'], $item, 'default_set')];
}
/**
* @param array<string, mixed> $before
* @param array<string, mixed> $after
* @return list<string>
*/
private static function changedFields(array $before, array $after): array
{
$keys = array_diff(array_unique(array_merge(array_keys($before), array_keys($after))), self::DERIVED);
$fields = [];
foreach ($keys as $key) {
$was = $before[$key] ?? null;
$is = $after[$key] ?? null;
if ($was != $is) { // phpcs:ignore Universal.Operators.StrictComparisons
$fields[] = (string) $key;
}
}
sort($fields);
return $fields;
}
/**
* @param array<string, mixed>|null $item
* @return array{kind: string, scope: string, id: string, name: string, action: string}
*/
private static function row(string $kind, string $scope, string $id, ?array $item, string $action): array
{
return [
'kind' => $kind,
'scope' => $scope,
'id' => $id,
'name' => is_string($item['name'] ?? null) ? $item['name'] : $id,
'action' => $action,
];
}
}
@@ -0,0 +1,49 @@
<?php
declare(strict_types=1);
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Design;
use LIT\MCP\Integrations\Divi\Design\PresetBook;
use LIT\MCP\Integrations\Divi\Design\PresetDiff;
use LIT\MCP\Integrations\Divi\Design\PresetScope;
use PHPUnit\Framework\TestCase;
final class PresetDiffTest extends TestCase
{
public function testNoChangeIsAnEmptyDiff(): void
{
$book = PresetBook::fromDivi(PresetBookTest::stored());
self::assertSame([], PresetDiff::between($book, PresetBook::fromDivi(PresetBookTest::stored())));
}
public function testAddedChangedRemovedAndDefaults(): void
{
$before = PresetBook::fromDivi(PresetBookTest::stored());
$button = new PresetScope('module', 'divi/button');
$text = new PresetScope('module', 'divi/text');
$changed = $before->items($button)['aaaaaaaaaa'];
$changed['attrs'] = ['button' => ['x' => 2]];
$changed['updated'] = 99;
$changed['styleAttrs'] = ['ignored' => true];
$after = $before
->with($button, $changed)
->with($button, ['id' => 'eeeeeeeeee', 'name' => 'Neu', 'attrs' => []])
->withDefault($button, 'eeeeeeeeee')
->without($button, 'bbbbbbbbbb')
->withDefault($text, '');
self::assertSame([
['kind' => 'module', 'scope' => 'divi/button', 'id' => 'aaaaaaaaaa', 'name' => 'CTA primär',
'action' => 'changed', 'fields' => ['attrs']],
['kind' => 'module', 'scope' => 'divi/button', 'id' => 'bbbbbbbbbb', 'name' => 'Doppelt',
'action' => 'removed'],
['kind' => 'module', 'scope' => 'divi/button', 'id' => 'eeeeeeeeee', 'name' => 'Neu', 'action' => 'added'],
['kind' => 'module', 'scope' => 'divi/button', 'id' => 'eeeeeeeeee', 'name' => 'Neu',
'action' => 'default_set'],
['kind' => 'module', 'scope' => 'divi/text', 'id' => 'dddddddddd', 'name' => 'Fließtext',
'action' => 'default_cleared'],
], PresetDiff::between($before, $after));
}
}