feat: ColorRestorePlanner und VariableRestorePlanner – Restore-Pläne für Farben und Variablen (V1b, V10)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-10-19 14:22:23 +02:00
co-authored by Claude Sonnet 5.5
parent 5af188d656
commit bd2043564b
4 changed files with 322 additions and 0 deletions
@@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Design;
use Brain\Monkey\Functions;
use LIT\MCP\Integrations\Divi\Design\ColorArea;
use LIT\MCP\Integrations\Divi\Design\ColorBook;
use LIT\MCP\Integrations\Divi\Design\ColorRestorePlanner;
use LIT\MCP\Integrations\Divi\Design\DesignPlan;
use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter;
use LIT\MCP\Integrations\Divi\Design\GlobalColorsAdapter;
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
use LIT\MCP\Tests\Unit\TestCase;
use Mockery;
final class ColorRestorePlannerTest extends TestCase
{
public const SNAPSHOT_ID = 'colors-20261016T100000Z-aaaaaa';
/** One stored color in ColorBook::data() form. */
public static function color(string $label, string $color): array
{
return ['color' => $color, 'folder' => '', 'label' => $label, 'lastUpdated' => '2026-10-01T10:00:00.000Z',
'status' => 'active', 'usedInPosts' => []];
}
/** @return array<string, array<string, mixed>> The colors now: "Marke" red, "Akzent" created after the snapshot. */
public static function current(): array
{
return [
'gcid-brand00001' => self::color('Marke', '#ff0000'),
'gcid-akzent0001' => self::color('Akzent', '#00ff00'),
];
}
/** @return array<string, array<string, mixed>> The snapshot: "Marke" blue, no "Akzent". */
public static function snapshot(): array
{
return ['gcid-brand00001' => self::color('Marke', '#0000ff')];
}
private static function planner(bool $visualBuilder = true): ColorRestorePlanner
{
$writer = Mockery::mock(DesignSystemWriter::class);
$writer->allows('canUseVisualBuilder')->andReturn($visualBuilder);
return new ColorRestorePlanner($writer, Mockery::mock(GlobalColorsAdapter::class));
}
public function testAreaFeaturesAndDesignArea(): void
{
$planner = self::planner();
self::assertSame('colors', $planner->area());
self::assertSame([DiviFeature::DesignRead, DiviFeature::ColorWrite], $planner->features());
self::assertInstanceOf(ColorArea::class, $planner->designArea());
}
public function testNeedsTheColorRights(): void
{
Functions\when('current_user_can')->justReturn(true);
self::assertNull(self::planner()->rights());
self::assertToolError(self::planner(false)->rights(), 'forbidden');
}
public function testTheSnapshotReplacesEveryColorAndNamesTheRemovedOnes(): void
{
$plan = self::planner()->plan(self::current(), self::snapshot(), self::SNAPSHOT_ID);
self::assertInstanceOf(DesignPlan::class, $plan);
self::assertTrue($plan->mayRemove);
self::assertSame(ColorBook::fromDivi(self::snapshot())->data(), $plan->raw);
self::assertSame(
[['gcid-brand00001', 'changed'], ['gcid-akzent0001', 'removed']],
array_map(static fn (array $row): array => [$row['id'], $row['action']], $plan->diff)
);
self::assertStringStartsWith('Snapshot ' . self::SNAPSHOT_ID . ' wiederherstellen – Farben: ', $plan->summary);
self::assertStringEndsWith(' Entfernt: 1.', $plan->summary);
self::assertSame(
['Farben, die es im Snapshot nicht gibt, entfallen („Akzent“); Seiten und Presets, die sie verwenden, '
. 'behalten den Verweis ohne Wert.'],
$plan->warnings
);
}
public function testTheSameColorsAreNoChange(): void
{
$plan = self::planner()->plan(self::snapshot(), self::snapshot(), self::SNAPSHOT_ID);
self::assertInstanceOf(DesignPlan::class, $plan);
self::assertTrue($plan->isEmpty());
self::assertSame([], $plan->warnings);
}
}
@@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Design;
use Brain\Monkey\Functions;
use LIT\MCP\Integrations\Divi\Design\DesignPlan;
use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter;
use LIT\MCP\Integrations\Divi\Design\GlobalVariablesAdapter;
use LIT\MCP\Integrations\Divi\Design\VariableArea;
use LIT\MCP\Integrations\Divi\Design\VariableBook;
use LIT\MCP\Integrations\Divi\Design\VariableRestorePlanner;
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
use LIT\MCP\Tests\Unit\TestCase;
use Mockery;
final class VariableRestorePlannerTest extends TestCase
{
public const SNAPSHOT_ID = 'variables-20261016T100000Z-aaaaaa';
/** One stored variable of type numbers. */
public static function number(string $id, string $label, string $value): array
{
return ['id' => $id, 'label' => $label, 'value' => $value, 'order' => '1', 'status' => 'active',
'lastUpdated' => '2026-10-01T10:00:00.000Z', 'variableType' => 'numbers'];
}
/** @return array<string, array<string, array<string, mixed>>> Now: radius 8px and "Neu", created afterwards. */
public static function current(): array
{
return ['numbers' => [
'gvid-radius0001' => self::number('gvid-radius0001', 'Eckenradius', '8px'),
'gvid-neu0000001' => self::number('gvid-neu0000001', 'Neu', '2rem'),
]];
}
/** @return array<string, array<string, array<string, mixed>>> The snapshot: radius 4px. */
public static function snapshot(): array
{
return ['numbers' => ['gvid-radius0001' => self::number('gvid-radius0001', 'Eckenradius', '4px')]];
}
private static function planner(bool $manage = true): VariableRestorePlanner
{
$writer = Mockery::mock(DesignSystemWriter::class);
$writer->allows('canUseVisualBuilder')->andReturn(true);
$variables = Mockery::mock(GlobalVariablesAdapter::class);
$variables->allows('canManage')->andReturn($manage);
return new VariableRestorePlanner($writer, $variables);
}
public function testAreaFeaturesAndDesignArea(): void
{
$planner = self::planner();
self::assertSame('variables', $planner->area());
self::assertSame([DiviFeature::DesignRead, DiviFeature::VariableWrite], $planner->features());
self::assertInstanceOf(VariableArea::class, $planner->designArea());
}
public function testNeedsTheVariableRights(): void
{
Functions\when('current_user_can')->justReturn(true);
self::assertNull(self::planner()->rights());
self::assertToolError(self::planner(false)->rights(), 'forbidden');
}
public function testRestoresTheSnapshotAndKeepsNewerVariablesArchived(): void
{
$current = VariableBook::fromDivi(self::current());
$snapshot = VariableBook::fromDivi(self::snapshot());
$plan = self::planner()->plan(self::current(), self::snapshot(), self::SNAPSHOT_ID);
self::assertInstanceOf(DesignPlan::class, $plan);
self::assertFalse($plan->mayRemove, 'V1: variables are never removed');
self::assertSame($current->restoredFrom($snapshot)->data(), $plan->raw);
self::assertSame('4px', $plan->raw['numbers']['gvid-radius0001']['value']);
self::assertSame('archived', $plan->raw['numbers']['gvid-neu0000001']['status']);
self::assertEqualsCanonicalizing(
[['gvid-radius0001', 'changed'], ['gvid-neu0000001', 'archived']],
array_map(static fn (array $row): array => [$row['id'], $row['action']], $plan->diff)
);
self::assertStringStartsWith('Snapshot ' . self::SNAPSHOT_ID . ' wiederherstellen – Variablen: ', $plan->summary);
self::assertSame(
['Variable „Neu“ gibt es im Snapshot nicht; sie bleibt archiviert erhalten (Divi löscht keine Variablen).'],
$plan->warnings
);
}
}