fix: DesignArea mit restore() fürs Zurückschreiben und differences() für bereinigte Werte
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
60c31f9697
commit
fc8ffeba9e
@@ -9,7 +9,8 @@ defined('ABSPATH') || exit;
|
||||
/**
|
||||
* One area of the design system for DesignChange (spec §2, §4): colors, variables, presets or theme options – its
|
||||
* raw inventory as Divi stores it (also the snapshot), its design_rev, how it is written through Divi and how a
|
||||
* read-back is judged. write() throws on failure; it is also how a snapshot is written back ($mayRemove = true).
|
||||
* read-back is judged. write() applies a planned change, restore() writes a snapshot back (full replace); both throw
|
||||
* on failure. verify() judges the structure (entries, defaults), differences() the values Divi changed.
|
||||
*/
|
||||
interface DesignArea
|
||||
{
|
||||
@@ -32,10 +33,28 @@ interface DesignArea
|
||||
public function write(array $raw, bool $mayRemove): array;
|
||||
|
||||
/**
|
||||
* Whether Divi stored what was written – sanitizing may change values, never which entries exist.
|
||||
* Writes $raw back as the whole inventory (full replace: entries missing in $raw are removed).
|
||||
*
|
||||
* @param array<array-key, mixed> $raw A snapshot.
|
||||
* @return array<array-key, mixed> The inventory as read back after writing.
|
||||
*/
|
||||
public function restore(array $raw): array;
|
||||
|
||||
/**
|
||||
* Whether Divi stored the entries and defaults that were written – sanitizing may change values, never which
|
||||
* entries exist.
|
||||
*
|
||||
* @param array<array-key, mixed> $expected
|
||||
* @param array<array-key, mixed> $actual
|
||||
*/
|
||||
public function verify(array $expected, array $actual): bool;
|
||||
|
||||
/**
|
||||
* German warnings for entries whose values Divi changed while saving (e.g. sanitized settings).
|
||||
*
|
||||
* @param array<array-key, mixed> $expected
|
||||
* @param array<array-key, mixed> $actual
|
||||
* @return list<string>
|
||||
*/
|
||||
public function differences(array $expected, array $actual): array;
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ final class DesignChange
|
||||
return ToolResult::ok($planned->summary)
|
||||
->snapshotId($snapshotId)
|
||||
->logId($logId)
|
||||
->warnings($planned->warnings)
|
||||
->warnings(array_merge($planned->warnings, $area->differences($planned->raw, $saved)))
|
||||
->with('diff', $planned->diff)
|
||||
->with('design_rev', $area->rev($saved))
|
||||
->toArray();
|
||||
@@ -203,7 +203,7 @@ final class DesignChange
|
||||
Throwable $cause
|
||||
): never {
|
||||
$reason = sprintf('Die %s konnten nicht gespeichert werden.', $area->label());
|
||||
$failures = Rollback::attempt(static fn () => $area->write($current, true));
|
||||
$failures = Rollback::attempt(static fn () => $area->restore($current));
|
||||
if ($failures === []) {
|
||||
throw WriteFailed::rolledBack(
|
||||
$reason,
|
||||
|
||||
@@ -9,7 +9,7 @@ defined('ABSPATH') || exit;
|
||||
/**
|
||||
* The area "presets" (spec §6): the inventory in Divi's stored format (PresetBook), written as REST format through
|
||||
* DesignSystemWriter::savePresets(). Divi's prepare_data() may sanitize values; verify() only insists on the same
|
||||
* presets and defaults (PresetDiff without "changed").
|
||||
* presets and defaults (PresetDiff without "changed"), differences() names the presets whose values changed.
|
||||
*/
|
||||
final class PresetArea implements DesignArea
|
||||
{
|
||||
@@ -43,6 +43,11 @@ final class PresetArea implements DesignArea
|
||||
->data();
|
||||
}
|
||||
|
||||
public function restore(array $raw): array
|
||||
{
|
||||
return $this->write($raw, true);
|
||||
}
|
||||
|
||||
public function verify(array $expected, array $actual): bool
|
||||
{
|
||||
$structural = array_filter(
|
||||
@@ -52,4 +57,19 @@ final class PresetArea implements DesignArea
|
||||
|
||||
return $structural === [];
|
||||
}
|
||||
|
||||
public function differences(array $expected, array $actual): array
|
||||
{
|
||||
$warnings = [];
|
||||
foreach (PresetDiff::between(PresetBook::fromDivi($expected), PresetBook::fromDivi($actual)) as $row) {
|
||||
if ($row['action'] === 'changed') {
|
||||
$warnings[] = sprintf(
|
||||
'Divi hat die Einstellungen von Preset „%s“ beim Speichern bereinigt.',
|
||||
$row['name']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $warnings;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,14 +22,27 @@ use RuntimeException;
|
||||
|
||||
final class DesignChangeTest extends TestCase
|
||||
{
|
||||
/** A DesignArea over an array; $failWrite/$failRestore make write()/the rollback throw. */
|
||||
private static function area(array &$stored, int $failWrites = 0, bool $verify = true): DesignArea
|
||||
{
|
||||
return new class ($stored, $failWrites, $verify) implements DesignArea {
|
||||
/**
|
||||
* A DesignArea over an array; the first $failWrites calls of write()/restore() throw, $differences are the
|
||||
* value warnings of differences().
|
||||
*/
|
||||
private static function area(
|
||||
array &$stored,
|
||||
int $failWrites = 0,
|
||||
bool $verify = true,
|
||||
array $differences = []
|
||||
): DesignArea {
|
||||
return new class ($stored, $failWrites, $verify, $differences) implements DesignArea {
|
||||
public array $writes = [];
|
||||
|
||||
public function __construct(private array &$stored, private int $failWrites, private bool $verify)
|
||||
{
|
||||
public array $restores = [];
|
||||
|
||||
public function __construct(
|
||||
private array &$stored,
|
||||
private int $failWrites,
|
||||
private bool $verify,
|
||||
private array $differences
|
||||
) {
|
||||
}
|
||||
|
||||
public function name(): string
|
||||
@@ -64,10 +77,27 @@ final class DesignChangeTest extends TestCase
|
||||
return $raw;
|
||||
}
|
||||
|
||||
public function restore(array $raw): array
|
||||
{
|
||||
$this->restores[] = $raw;
|
||||
if ($this->failWrites > 0) {
|
||||
--$this->failWrites;
|
||||
throw new RuntimeException('divi refused the restore');
|
||||
}
|
||||
$this->stored = $raw;
|
||||
|
||||
return $raw;
|
||||
}
|
||||
|
||||
public function verify(array $expected, array $actual): bool
|
||||
{
|
||||
return $this->verify;
|
||||
}
|
||||
|
||||
public function differences(array $expected, array $actual): array
|
||||
{
|
||||
return $this->differences;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -178,6 +208,26 @@ final class DesignChangeTest extends TestCase
|
||||
self::assertSame('presets-1', $log->records[0]['snapshot_id']);
|
||||
}
|
||||
|
||||
/** Final review I-3b: values Divi changed while saving are reported as warnings. */
|
||||
public function testValuesDiviChangedWhileSavingAreWarnings(): void
|
||||
{
|
||||
$stored = ['v' => 1];
|
||||
$area = self::area($stored, 0, true, ['Divi hat die Einstellungen von Preset „CTA“ beim Speichern bereinigt.']);
|
||||
$snapshots = Mockery::mock(SnapshotStore::class);
|
||||
$snapshots->expects('save')->andReturn('presets-1');
|
||||
$snapshots->expects('attachLog');
|
||||
|
||||
$result = (new DesignChange($snapshots, new FakeChangeLog(9)))->run(
|
||||
'lit/divi-presets-set',
|
||||
$area,
|
||||
['confirm' => true, 'design_rev' => $area->rev($stored)],
|
||||
self::plan(['v' => 2])
|
||||
);
|
||||
|
||||
self::assertTrue($result['ok']);
|
||||
self::assertContains('Divi hat die Einstellungen von Preset „CTA“ beim Speichern bereinigt.', $result['warnings']);
|
||||
}
|
||||
|
||||
public function testLockedDiviWritesNoSnapshot(): void
|
||||
{
|
||||
ReleaseGate::useResolver(static fn (): ReleaseStatus => new ReleaseStatus(ReleaseState::Untested, '5.16.0'));
|
||||
@@ -234,7 +284,8 @@ final class DesignChangeTest extends TestCase
|
||||
self::assertSame('presets-1', $e->details['snapshot_id']);
|
||||
}
|
||||
|
||||
self::assertSame([[['v' => 2], false], [['v' => 1], true]], $area->writes);
|
||||
self::assertSame([[['v' => 2], false]], $area->writes);
|
||||
self::assertSame([['v' => 1]], $area->restores, 'the rollback writes the snapshot back with restore()');
|
||||
self::assertSame(['v' => 1], $stored);
|
||||
}
|
||||
|
||||
|
||||
@@ -43,4 +43,32 @@ final class PresetAreaTest extends TestCase
|
||||
self::assertFalse($area->verify($expected, $missing));
|
||||
self::assertFalse($area->verify($expected, $otherDefault));
|
||||
}
|
||||
|
||||
/** Final review I-3a: a snapshot is written back with full-replace semantics. */
|
||||
public function testRestoreReplacesTheWholeInventory(): void
|
||||
{
|
||||
$writer = Mockery::mock(DesignSystemWriter::class);
|
||||
$stored = PresetBookTest::stored();
|
||||
$writer->expects('savePresets')->with(PresetBook::fromDivi($stored)->toRest(), true)->andReturn($stored);
|
||||
|
||||
self::assertSame(PresetBook::fromDivi($stored)->data(), (new PresetArea($writer))->restore($stored));
|
||||
}
|
||||
|
||||
/** Final review I-3b: values Divi changed while saving, by preset name; structure is verify()'s job. */
|
||||
public function testDifferencesNameThePresetsDiviSanitized(): void
|
||||
{
|
||||
$area = new PresetArea(Mockery::mock(DesignSystemWriter::class));
|
||||
$expected = PresetBookTest::stored();
|
||||
$sanitized = $expected;
|
||||
$sanitized['module']['divi/button']['items']['aaaaaaaaaa']['attrs'] = ['button' => ['x' => 'bereinigt']];
|
||||
$missing = $expected;
|
||||
unset($missing['module']['divi/button']['items']['bbbbbbbbbb']);
|
||||
|
||||
self::assertSame([], $area->differences($expected, $expected));
|
||||
self::assertSame([], $area->differences($expected, $missing));
|
||||
self::assertSame(
|
||||
['Divi hat die Einstellungen von Preset „CTA primär“ beim Speichern bereinigt.'],
|
||||
$area->differences($expected, $sanitized)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user