diff --git a/src/Integrations/Divi/Design/ColorRestorePlanner.php b/src/Integrations/Divi/Design/ColorRestorePlanner.php new file mode 100644 index 0000000..6b9a2ed --- /dev/null +++ b/src/Integrations/Divi/Design/ColorRestorePlanner.php @@ -0,0 +1,72 @@ +colors); + } + + public function rights(): ?WP_Error + { + return DesignRights::colors($this->writer); + } + + /** Writes the colors (ColorWrite), reading the design system first (DesignRead). */ + public function features(): array + { + return [DiviFeature::DesignRead, DiviFeature::ColorWrite]; + } + + public function plan(array $current, array $snapshot, string $snapshotId): DesignPlan|WP_Error + { + $before = ColorBook::fromDivi($current); + $after = ColorBook::fromDivi($snapshot); + $kept = array_values(array_filter( + ColorDiff::between($before, $after), + static fn (array $row): bool => $after->entry($row['id']) !== null + )); + $removed = []; + foreach (array_keys(array_diff_key($before->data(), $after->data())) as $id) { + $removed[] = ['id' => (string) $id, 'name' => $before->label((string) $id), 'action' => 'removed']; + } + + $summary = sprintf('Snapshot %s wiederherstellen – %s', $snapshotId, ColorDiff::summary($kept)); + $warnings = []; + if ($removed !== []) { + $summary .= sprintf(' Entfernt: %d.', count($removed)); + $warnings[] = sprintf( + self::REMOVED, + implode(', ', array_map(static fn (array $row): string => '„' . $row['name'] . '“', $removed)) + ); + } + + return new DesignPlan($after->data(), array_merge($kept, $removed), $summary, null, $warnings, true); + } +} diff --git a/src/Integrations/Divi/Design/VariableRestorePlanner.php b/src/Integrations/Divi/Design/VariableRestorePlanner.php new file mode 100644 index 0000000..e4a3542 --- /dev/null +++ b/src/Integrations/Divi/Design/VariableRestorePlanner.php @@ -0,0 +1,62 @@ +variables); + } + + public function rights(): ?WP_Error + { + return DesignRights::variables($this->writer, $this->variables); + } + + /** Writes the variables (VariableWrite), reading the design system first (DesignRead). */ + public function features(): array + { + return [DiviFeature::DesignRead, DiviFeature::VariableWrite]; + } + + public function plan(array $current, array $snapshot, string $snapshotId): DesignPlan|WP_Error + { + $before = VariableBook::fromDivi($current); + $older = VariableBook::fromDivi($snapshot); + $after = $before->restoredFrom($older); + $diff = VariableDiff::between($before, $after); + + return new DesignPlan( + $after->data(), + $diff, + sprintf('Snapshot %s wiederherstellen – %s', $snapshotId, VariableDiff::summary($diff)), + null, + array_map(static fn (array $row): string => sprintf(self::KEPT, $row['label']), $before->onlyHere($older)) + ); + } +} diff --git a/tests/Unit/Integrations/Divi/Design/ColorRestorePlannerTest.php b/tests/Unit/Integrations/Divi/Design/ColorRestorePlannerTest.php new file mode 100644 index 0000000..c4713eb --- /dev/null +++ b/tests/Unit/Integrations/Divi/Design/ColorRestorePlannerTest.php @@ -0,0 +1,96 @@ + $color, 'folder' => '', 'label' => $label, 'lastUpdated' => '2026-10-01T10:00:00.000Z', + 'status' => 'active', 'usedInPosts' => []]; + } + + /** @return array> 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> 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); + } +} diff --git a/tests/Unit/Integrations/Divi/Design/VariableRestorePlannerTest.php b/tests/Unit/Integrations/Divi/Design/VariableRestorePlannerTest.php new file mode 100644 index 0000000..06554be --- /dev/null +++ b/tests/Unit/Integrations/Divi/Design/VariableRestorePlannerTest.php @@ -0,0 +1,92 @@ + $id, 'label' => $label, 'value' => $value, 'order' => '1', 'status' => 'active', + 'lastUpdated' => '2026-10-01T10:00:00.000Z', 'variableType' => 'numbers']; + } + + /** @return array>> 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>> 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 + ); + } +}