refactor: lit/divi-design-restore plant über RestorePlanner; PresetRestorePlanner übernimmt den bisherigen Code unverändert
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
62f8465e60
commit
5af188d656
@@ -4,36 +4,36 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace LIT\MCP\Integrations\Divi\Design;
|
namespace LIT\MCP\Integrations\Divi\Design;
|
||||||
|
|
||||||
use Closure;
|
|
||||||
use LIT\MCP\Abilities\Ability;
|
use LIT\MCP\Abilities\Ability;
|
||||||
use LIT\MCP\Abilities\Annotations;
|
use LIT\MCP\Abilities\Annotations;
|
||||||
use LIT\MCP\Abilities\Errors;
|
use LIT\MCP\Abilities\Errors;
|
||||||
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
use LIT\MCP\Integrations\Divi\Release\FeatureSets;
|
||||||
use LIT\MCP\Integrations\Divi\Release\RequiresDiviFeatures;
|
use LIT\MCP\Integrations\Divi\Release\RequiresDiviFeatures;
|
||||||
use WP_Error;
|
use WP_Error;
|
||||||
|
|
||||||
defined('ABSPATH') || exit;
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* lit/divi-design-restore (spec §5, G6): writes a snapshot back through the change pipeline – dry run with the diff
|
* lit/divi-design-restore (spec §5, G6, ruling V10): writes a snapshot back through the change pipeline – dry run with
|
||||||
* to the current state, confirm with design_rev, and first a snapshot of the current state, so a restore can be
|
* the diff to the current state, confirm with design_rev, and first a snapshot of the current state, so a restore can
|
||||||
* undone as well. Etappe 4a restores presets; the other areas follow with their tools (4c, 4d).
|
* be undone as well. The area comes from the snapshot ID; its RestorePlanner brings the rights, the Divi features and
|
||||||
|
* the plan. Areas without a planner are not restorable yet.
|
||||||
*/
|
*/
|
||||||
final class DiviDesignRestoreAbility implements Ability, RequiresDiviFeatures
|
final class DiviDesignRestoreAbility implements Ability, RequiresDiviFeatures
|
||||||
{
|
{
|
||||||
private const ID = '/^(colors|variables|presets|theme_options)-\d{8}T\d{6}Z-[0-9a-f]{6}$/D';
|
private const ID = '/^(colors|variables|presets|theme_options)-\d{8}T\d{6}Z-[0-9a-f]{6}$/D';
|
||||||
|
|
||||||
private const MARKUP = 'Der Snapshot oder der aktuelle Bestand enthält Presets mit HTML/CSS, das beim Speichern '
|
/** @var array<string, RestorePlanner> area => planner */
|
||||||
. 'ohne das Recht unfiltered_html verloren ginge; nichts wurde gespeichert.';
|
private array $planners = [];
|
||||||
|
|
||||||
/** @param (Closure(): bool)|null $unfiltered Whether the user may save unfiltered HTML (DesignRights). */
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
private DesignSystemWriter $writer,
|
|
||||||
private DesignChange $change,
|
private DesignChange $change,
|
||||||
private SnapshotStore $snapshots,
|
private SnapshotStore $snapshots,
|
||||||
private ?Closure $unfiltered = null
|
RestorePlanner ...$planners
|
||||||
) {
|
) {
|
||||||
$this->unfiltered ??= DesignRights::unfilteredHtml();
|
foreach ($planners as $planner) {
|
||||||
|
$this->planners[$planner->area()] = $planner;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function name(): string
|
public function name(): string
|
||||||
@@ -78,37 +78,42 @@ final class DiviDesignRestoreAbility implements Ability, RequiresDiviFeatures
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Writes a stored snapshot back through the preset area (PresetWrite, reading it first: DesignRead). */
|
/** The features of the snapshot's area; without a restorable area those of every area (fail closed). */
|
||||||
public function diviFeatures(array $input): array
|
public function diviFeatures(array $input): array
|
||||||
{
|
{
|
||||||
return [DiviFeature::DesignRead, DiviFeature::PresetWrite];
|
return FeatureSets::merge(...array_map(
|
||||||
|
static fn (RestorePlanner $planner): array => $planner->features(),
|
||||||
|
$this->plannersFor($input)
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function permission(array $input): bool|WP_Error
|
public function permission(array $input): bool|WP_Error
|
||||||
{
|
{
|
||||||
return DesignRights::presets($this->writer) ?? true;
|
return $this->rights($input) ?? true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function execute(array $input): mixed
|
public function execute(array $input): mixed
|
||||||
{
|
{
|
||||||
$refusal = DesignRights::presets($this->writer);
|
$refusal = $this->rights($input);
|
||||||
if ($refusal !== null) {
|
if ($refusal !== null) {
|
||||||
return $refusal;
|
return $refusal;
|
||||||
}
|
}
|
||||||
$id = is_string($input['snapshot_id'] ?? null) ? $input['snapshot_id'] : '';
|
$id = is_string($input['snapshot_id'] ?? null) ? $input['snapshot_id'] : '';
|
||||||
if (preg_match(self::ID, $id, $match) !== 1) {
|
$area = self::areaOf($input);
|
||||||
|
if ($area === null) {
|
||||||
return Errors::invalidInput(
|
return Errors::invalidInput(
|
||||||
'Ungültige Snapshot-ID.',
|
'Ungültige Snapshot-ID.',
|
||||||
['snapshot_id' => 'z. B. presets-20261016T101500Z-a1b2c3']
|
['snapshot_id' => 'z. B. presets-20261016T101500Z-a1b2c3']
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if ($match[1] !== 'presets') {
|
$planner = $this->planners[$area] ?? null;
|
||||||
|
if ($planner === null) {
|
||||||
return Errors::unsupported(
|
return Errors::unsupported(
|
||||||
'Snapshots dieses Bereichs lassen sich noch nicht wiederherstellen.',
|
'Snapshots dieses Bereichs lassen sich noch nicht wiederherstellen.',
|
||||||
'Farben und Variablen folgen mit Etappe 4c, Theme-Optionen mit 4d.'
|
'Farben und Variablen folgen mit Etappe 4c, Theme-Optionen mit 4d.'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
$snapshot = $this->snapshots->get('presets', $id);
|
$snapshot = $this->snapshots->get($area, $id);
|
||||||
if ($snapshot === null) {
|
if ($snapshot === null) {
|
||||||
return Errors::notFound(
|
return Errors::notFound(
|
||||||
sprintf('Snapshot %s gibt es nicht (mehr).', $id),
|
sprintf('Snapshot %s gibt es nicht (mehr).', $id),
|
||||||
@@ -116,35 +121,47 @@ final class DiviDesignRestoreAbility implements Ability, RequiresDiviFeatures
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
$unfiltered = $this->unfiltered;
|
|
||||||
|
|
||||||
return $this->change->run(
|
return $this->change->run(
|
||||||
$this->name(),
|
$this->name(),
|
||||||
new PresetArea($this->writer),
|
$planner->designArea(),
|
||||||
$input,
|
$input,
|
||||||
static function (array $raw) use ($snapshot, $id, $unfiltered): DesignPlan|WP_Error {
|
static fn (array $raw): DesignPlan|WP_Error => $planner->plan($raw, $snapshot['data'], $id)
|
||||||
$before = PresetBook::fromDivi($raw);
|
|
||||||
$after = PresetBook::fromDivi($snapshot['data']);
|
|
||||||
$refusal = DesignRights::markup(
|
|
||||||
$unfiltered,
|
|
||||||
array_merge($before->markupOutside([]), $after->markupOutside([])),
|
|
||||||
self::MARKUP
|
|
||||||
);
|
|
||||||
if ($refusal !== null) {
|
|
||||||
return $refusal;
|
|
||||||
}
|
|
||||||
$diff = PresetDiff::between($before, $after);
|
|
||||||
|
|
||||||
return new DesignPlan(
|
|
||||||
$after->data(),
|
|
||||||
$diff,
|
|
||||||
sprintf('Snapshot %s wiederherstellen – %s', $id, PresetDiff::summary($diff)),
|
|
||||||
null,
|
|
||||||
['Seiten, die Presets verwenden, die es im Snapshot nicht gibt, behalten den Verweis; das Modul '
|
|
||||||
. 'fällt dann auf das Standard-Preset zurück.'],
|
|
||||||
true
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spec §10: the rights of the snapshot's area; without a restorable area those of every area (fail closed).
|
||||||
|
*
|
||||||
|
* @param array<string, mixed> $input
|
||||||
|
*/
|
||||||
|
private function rights(array $input): ?WP_Error
|
||||||
|
{
|
||||||
|
foreach ($this->plannersFor($input) as $planner) {
|
||||||
|
$refusal = $planner->rights();
|
||||||
|
if ($refusal !== null) {
|
||||||
|
return $refusal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $input
|
||||||
|
* @return list<RestorePlanner> The planner of the snapshot's area, or every planner.
|
||||||
|
*/
|
||||||
|
private function plannersFor(array $input): array
|
||||||
|
{
|
||||||
|
$planner = $this->planners[self::areaOf($input) ?? ''] ?? null;
|
||||||
|
|
||||||
|
return $planner !== null ? [$planner] : array_values($this->planners);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param array<string, mixed> $input */
|
||||||
|
private static function areaOf(array $input): ?string
|
||||||
|
{
|
||||||
|
$id = $input['snapshot_id'] ?? null;
|
||||||
|
|
||||||
|
return is_string($id) && preg_match(self::ID, $id, $match) === 1 ? $match[1] : null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Integrations\Divi\Design;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||||
|
use WP_Error;
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restores a snapshot of the presets (Etappe 4a): the snapshot replaces the whole inventory (presets missing in it are
|
||||||
|
* removed); without unfiltered_html it is refused while the snapshot or the current inventory holds HTML/CSS that
|
||||||
|
* Divi would strip (final review I-2).
|
||||||
|
*/
|
||||||
|
final class PresetRestorePlanner implements RestorePlanner
|
||||||
|
{
|
||||||
|
private const MARKUP = 'Der Snapshot oder der aktuelle Bestand enthält Presets mit HTML/CSS, das beim Speichern '
|
||||||
|
. 'ohne das Recht unfiltered_html verloren ginge; nichts wurde gespeichert.';
|
||||||
|
|
||||||
|
/** @param (Closure(): bool)|null $unfiltered Whether the user may save unfiltered HTML (DesignRights). */
|
||||||
|
public function __construct(private DesignSystemWriter $writer, private ?Closure $unfiltered = null)
|
||||||
|
{
|
||||||
|
$this->unfiltered ??= DesignRights::unfilteredHtml();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function area(): string
|
||||||
|
{
|
||||||
|
return 'presets';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function designArea(): DesignArea
|
||||||
|
{
|
||||||
|
return new PresetArea($this->writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rights(): ?WP_Error
|
||||||
|
{
|
||||||
|
return DesignRights::presets($this->writer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Writes the presets (PresetWrite), reading them first (DesignRead). */
|
||||||
|
public function features(): array
|
||||||
|
{
|
||||||
|
return [DiviFeature::DesignRead, DiviFeature::PresetWrite];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function plan(array $current, array $snapshot, string $snapshotId): DesignPlan|WP_Error
|
||||||
|
{
|
||||||
|
$before = PresetBook::fromDivi($current);
|
||||||
|
$after = PresetBook::fromDivi($snapshot);
|
||||||
|
$refusal = DesignRights::markup(
|
||||||
|
$this->unfiltered,
|
||||||
|
array_merge($before->markupOutside([]), $after->markupOutside([])),
|
||||||
|
self::MARKUP
|
||||||
|
);
|
||||||
|
if ($refusal !== null) {
|
||||||
|
return $refusal;
|
||||||
|
}
|
||||||
|
$diff = PresetDiff::between($before, $after);
|
||||||
|
|
||||||
|
return new DesignPlan(
|
||||||
|
$after->data(),
|
||||||
|
$diff,
|
||||||
|
sprintf('Snapshot %s wiederherstellen – %s', $snapshotId, PresetDiff::summary($diff)),
|
||||||
|
null,
|
||||||
|
['Seiten, die Presets verwenden, die es im Snapshot nicht gibt, behalten den Verweis; das Modul fällt '
|
||||||
|
. 'dann auf das Standard-Preset zurück.'],
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Integrations\Divi\Design;
|
||||||
|
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||||
|
use WP_Error;
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How lit/divi-design-restore writes a snapshot of one area back (spec §5, ruling V10): the area for DesignChange, the
|
||||||
|
* rights and Divi features of that area, and the plan from the current inventory to the restored one.
|
||||||
|
*/
|
||||||
|
interface RestorePlanner
|
||||||
|
{
|
||||||
|
/** The snapshot area this planner restores – one of SnapshotStore::AREAS. */
|
||||||
|
public function area(): string;
|
||||||
|
|
||||||
|
public function designArea(): DesignArea;
|
||||||
|
|
||||||
|
/** Why the current user may not restore this area (spec §10); null when allowed. */
|
||||||
|
public function rights(): ?WP_Error;
|
||||||
|
|
||||||
|
/** @return list<DiviFeature> */
|
||||||
|
public function features(): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<array-key, mixed> $current The area's inventory now (DesignArea::raw(), read by DesignChange).
|
||||||
|
* @param array<array-key, mixed> $snapshot The data of the snapshot $snapshotId.
|
||||||
|
*/
|
||||||
|
public function plan(array $current, array $snapshot, string $snapshotId): DesignPlan|WP_Error;
|
||||||
|
}
|
||||||
@@ -29,6 +29,7 @@ use LIT\MCP\Integrations\Divi\Design\DiviPresetsSetAbility;
|
|||||||
use LIT\MCP\Integrations\Divi\Design\PageRewriter;
|
use LIT\MCP\Integrations\Divi\Design\PageRewriter;
|
||||||
use LIT\MCP\Integrations\Divi\Design\PresetAssignment;
|
use LIT\MCP\Integrations\Divi\Design\PresetAssignment;
|
||||||
use LIT\MCP\Integrations\Divi\Design\PresetComposer;
|
use LIT\MCP\Integrations\Divi\Design\PresetComposer;
|
||||||
|
use LIT\MCP\Integrations\Divi\Design\PresetRestorePlanner;
|
||||||
use LIT\MCP\Integrations\Divi\Design\SnapshotStore;
|
use LIT\MCP\Integrations\Divi\Design\SnapshotStore;
|
||||||
use LIT\MCP\Integrations\Divi\Library\DiviLibraryStore;
|
use LIT\MCP\Integrations\Divi\Library\DiviLibraryStore;
|
||||||
use LIT\MCP\Integrations\Divi\Library\DiviRoles;
|
use LIT\MCP\Integrations\Divi\Library\DiviRoles;
|
||||||
@@ -263,10 +264,9 @@ final class DiviIntegration implements Integration, Bootable
|
|||||||
),
|
),
|
||||||
new DiviDesignSnapshotsAbility(new SnapshotStore()),
|
new DiviDesignSnapshotsAbility(new SnapshotStore()),
|
||||||
new DiviDesignRestoreAbility(
|
new DiviDesignRestoreAbility(
|
||||||
$this->designWriter,
|
|
||||||
$this->designChange(),
|
$this->designChange(),
|
||||||
new SnapshotStore(),
|
new SnapshotStore(),
|
||||||
DesignRights::unfilteredHtml()
|
new PresetRestorePlanner($this->designWriter, DesignRights::unfilteredHtml())
|
||||||
),
|
),
|
||||||
new DiviPresetsAssignAbility(
|
new DiviPresetsAssignAbility(
|
||||||
$this->designWriter,
|
$this->designWriter,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use Brain\Monkey\Functions;
|
|||||||
use LIT\MCP\Integrations\Divi\Design\DesignChange;
|
use LIT\MCP\Integrations\Divi\Design\DesignChange;
|
||||||
use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter;
|
use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter;
|
||||||
use LIT\MCP\Integrations\Divi\Design\DiviDesignRestoreAbility;
|
use LIT\MCP\Integrations\Divi\Design\DiviDesignRestoreAbility;
|
||||||
|
use LIT\MCP\Integrations\Divi\Design\PresetRestorePlanner;
|
||||||
use LIT\MCP\Integrations\Divi\Design\SnapshotStore;
|
use LIT\MCP\Integrations\Divi\Design\SnapshotStore;
|
||||||
use LIT\MCP\Tests\Support\FakeChangeLog;
|
use LIT\MCP\Tests\Support\FakeChangeLog;
|
||||||
use LIT\MCP\Tests\Unit\TestCase;
|
use LIT\MCP\Tests\Unit\TestCase;
|
||||||
@@ -37,7 +38,11 @@ final class DiviDesignRestoreAbilityTest extends TestCase
|
|||||||
'created' => 'x', 'user_id' => 1, 'ability' => 'lit/divi-presets-set', 'log_id' => 2, 'size' => 1]);
|
'created' => 'x', 'user_id' => 1, 'ability' => 'lit/divi-presets-set', 'log_id' => 2, 'size' => 1]);
|
||||||
$snapshots->expects('save')->with('presets', Mockery::type('array'), 'lit/divi-design-restore')->andReturn('presets-3');
|
$snapshots->expects('save')->with('presets', Mockery::type('array'), 'lit/divi-design-restore')->andReturn('presets-3');
|
||||||
$snapshots->expects('attachLog');
|
$snapshots->expects('attachLog');
|
||||||
$ability = new DiviDesignRestoreAbility($writer, new DesignChange($snapshots, new FakeChangeLog(8)), $snapshots);
|
$ability = new DiviDesignRestoreAbility(
|
||||||
|
new DesignChange($snapshots, new FakeChangeLog(8)),
|
||||||
|
$snapshots,
|
||||||
|
new PresetRestorePlanner($writer)
|
||||||
|
);
|
||||||
|
|
||||||
$dry = self::assertToolError($ability->execute(['snapshot_id' => 'presets-20261016T100000Z-aaaaaa']), 'confirm_required');
|
$dry = self::assertToolError($ability->execute(['snapshot_id' => 'presets-20261016T100000Z-aaaaaa']), 'confirm_required');
|
||||||
self::assertSame('removed', $dry['diff'][0]['action']);
|
self::assertSame('removed', $dry['diff'][0]['action']);
|
||||||
@@ -63,10 +68,9 @@ final class DiviDesignRestoreAbilityTest extends TestCase
|
|||||||
$snapshots->shouldNotReceive('save');
|
$snapshots->shouldNotReceive('save');
|
||||||
|
|
||||||
return new DiviDesignRestoreAbility(
|
return new DiviDesignRestoreAbility(
|
||||||
$writer,
|
|
||||||
new DesignChange($snapshots, FakeChangeLog::forbidden()),
|
new DesignChange($snapshots, FakeChangeLog::forbidden()),
|
||||||
$snapshots,
|
$snapshots,
|
||||||
static fn (): bool => $unfiltered
|
new PresetRestorePlanner($writer, static fn (): bool => $unfiltered)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,7 +100,11 @@ final class DiviDesignRestoreAbilityTest extends TestCase
|
|||||||
$writer->shouldNotReceive('savePresets');
|
$writer->shouldNotReceive('savePresets');
|
||||||
$snapshots = Mockery::mock(SnapshotStore::class);
|
$snapshots = Mockery::mock(SnapshotStore::class);
|
||||||
$snapshots->shouldNotReceive('get');
|
$snapshots->shouldNotReceive('get');
|
||||||
$ability = new DiviDesignRestoreAbility($writer, new DesignChange($snapshots, FakeChangeLog::forbidden()), $snapshots);
|
$ability = new DiviDesignRestoreAbility(
|
||||||
|
new DesignChange($snapshots, FakeChangeLog::forbidden()),
|
||||||
|
$snapshots,
|
||||||
|
new PresetRestorePlanner($writer)
|
||||||
|
);
|
||||||
$input = ['snapshot_id' => 'presets-20261016T100000Z-aaaaaa', 'confirm' => true, 'design_rev' => 'x'];
|
$input = ['snapshot_id' => 'presets-20261016T100000Z-aaaaaa', 'confirm' => true, 'design_rev' => 'x'];
|
||||||
|
|
||||||
self::assertToolError($ability->permission($input), 'forbidden');
|
self::assertToolError($ability->permission($input), 'forbidden');
|
||||||
@@ -110,7 +118,11 @@ final class DiviDesignRestoreAbilityTest extends TestCase
|
|||||||
$writer->allows('canUseVisualBuilder')->andReturn(true);
|
$writer->allows('canUseVisualBuilder')->andReturn(true);
|
||||||
$snapshots = Mockery::mock(SnapshotStore::class);
|
$snapshots = Mockery::mock(SnapshotStore::class);
|
||||||
$snapshots->allows('get')->andReturn(null);
|
$snapshots->allows('get')->andReturn(null);
|
||||||
$ability = new DiviDesignRestoreAbility($writer, new DesignChange($snapshots, FakeChangeLog::forbidden()), $snapshots);
|
$ability = new DiviDesignRestoreAbility(
|
||||||
|
new DesignChange($snapshots, FakeChangeLog::forbidden()),
|
||||||
|
$snapshots,
|
||||||
|
new PresetRestorePlanner($writer)
|
||||||
|
);
|
||||||
|
|
||||||
self::assertToolError($ability->execute(['snapshot_id' => 'presets-20261016T100000Z-abcdef']), 'not_found');
|
self::assertToolError($ability->execute(['snapshot_id' => 'presets-20261016T100000Z-abcdef']), 'not_found');
|
||||||
self::assertToolError($ability->execute(['snapshot_id' => 'colors-20261016T100000Z-abcdef']), 'unsupported');
|
self::assertToolError($ability->execute(['snapshot_id' => 'colors-20261016T100000Z-abcdef']), 'unsupported');
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Design;
|
||||||
|
|
||||||
|
use LIT\MCP\Integrations\Divi\Design\DesignPlan;
|
||||||
|
use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter;
|
||||||
|
use LIT\MCP\Integrations\Divi\Design\PresetArea;
|
||||||
|
use LIT\MCP\Integrations\Divi\Design\PresetRestorePlanner;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||||
|
use LIT\MCP\Tests\Unit\TestCase;
|
||||||
|
use Mockery;
|
||||||
|
|
||||||
|
final class PresetRestorePlannerTest extends TestCase
|
||||||
|
{
|
||||||
|
private static function planner(bool $unfiltered = true): PresetRestorePlanner
|
||||||
|
{
|
||||||
|
return new PresetRestorePlanner(Mockery::mock(DesignSystemWriter::class), static fn (): bool => $unfiltered);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAreaFeaturesAndDesignArea(): void
|
||||||
|
{
|
||||||
|
$planner = self::planner();
|
||||||
|
|
||||||
|
self::assertSame('presets', $planner->area());
|
||||||
|
self::assertSame([DiviFeature::DesignRead, DiviFeature::PresetWrite], $planner->features());
|
||||||
|
self::assertInstanceOf(PresetArea::class, $planner->designArea());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testPlansTheWholeSnapshotWithRemovals(): void
|
||||||
|
{
|
||||||
|
$older = PresetBookTest::stored();
|
||||||
|
unset($older['module']['divi/button']['items']['bbbbbbbbbb']);
|
||||||
|
|
||||||
|
$plan = self::planner()->plan(PresetBookTest::stored(), $older, 'presets-20261016T100000Z-aaaaaa');
|
||||||
|
|
||||||
|
self::assertInstanceOf(DesignPlan::class, $plan);
|
||||||
|
self::assertTrue($plan->mayRemove);
|
||||||
|
self::assertSame([['bbbbbbbbbb', 'removed']], array_map(
|
||||||
|
static fn (array $row): array => [$row['id'], $row['action']],
|
||||||
|
$plan->diff
|
||||||
|
));
|
||||||
|
self::assertStringStartsWith('Snapshot presets-20261016T100000Z-aaaaaa wiederherstellen – ', $plan->summary);
|
||||||
|
self::assertStringContainsString('Standard-Preset', $plan->warnings[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMarkupNeedsUnfilteredHtml(): void
|
||||||
|
{
|
||||||
|
$plan = self::planner(false)->plan(PresetBookTest::stored(), PresetBookTest::storedWithMarkup(), 'presets-1');
|
||||||
|
|
||||||
|
self::assertToolError($plan, 'forbidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user