feat: Divi-Adapter sperren je Fähigkeit – ReleaseGate::feature/assertFeature(s), DiviLocked und Ablehnung nennen die betroffenen Fähigkeiten (D6), DiviSaver prüft Seiten schreiben und die Fähigkeit des Speicherbereichs (D5), DesignArea::feature; Test-Standard alle Fähigkeiten freigegeben
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c4efeb3ba4
commit
e387d8d29e
@@ -4,6 +4,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace LIT\MCP\Integrations\Divi\Design;
|
namespace LIT\MCP\Integrations\Divi\Design;
|
||||||
|
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||||
|
|
||||||
defined('ABSPATH') || exit;
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -20,6 +22,9 @@ interface DesignArea
|
|||||||
/** German name for messages, e.g. "Presets". */
|
/** German name for messages, e.g. "Presets". */
|
||||||
public function label(): string;
|
public function label(): string;
|
||||||
|
|
||||||
|
/** The Divi feature a write of this area needs (DesignChange asks ReleaseGate::assertFeature() for it). */
|
||||||
|
public function feature(): DiviFeature;
|
||||||
|
|
||||||
/** @return array<array-key, mixed> */
|
/** @return array<array-key, mixed> */
|
||||||
public function raw(): array;
|
public function raw(): array;
|
||||||
|
|
||||||
|
|||||||
@@ -159,7 +159,7 @@ final class DesignChange
|
|||||||
return $blocked;
|
return $blocked;
|
||||||
}
|
}
|
||||||
|
|
||||||
ReleaseGate::assertWritable();
|
ReleaseGate::assertFeature($area->feature());
|
||||||
try {
|
try {
|
||||||
$snapshotId = $this->snapshots->save($area->name(), $current, $ability);
|
$snapshotId = $this->snapshots->save($area->name(), $current, $ability);
|
||||||
} catch (SnapshotTooLarge $e) {
|
} catch (SnapshotTooLarge $e) {
|
||||||
|
|||||||
@@ -94,12 +94,12 @@ class DesignSystemWriter implements DiviAdapter
|
|||||||
* @param bool $mayRemove Deleting or restoring: fewer presets are allowed and removed ones
|
* @param bool $mayRemove Deleting or restoring: fewer presets are allowed and removed ones
|
||||||
* leave Divi's legacy option (like a DELETE_* sync).
|
* leave Divi's legacy option (like a DELETE_* sync).
|
||||||
* @return array<array-key, mixed> The inventory Divi stored.
|
* @return array<array-key, mixed> The inventory Divi stored.
|
||||||
* @throws DiviLocked
|
* @throws DiviLocked When preset-write is not released – before anything is written.
|
||||||
* @throws RuntimeException When Divi refuses the inventory.
|
* @throws RuntimeException When Divi refuses the inventory.
|
||||||
*/
|
*/
|
||||||
public function savePresets(array $rest, bool $mayRemove): array
|
public function savePresets(array $rest, bool $mayRemove): array
|
||||||
{
|
{
|
||||||
ReleaseGate::assertWritable();
|
ReleaseGate::assertFeature(DiviFeature::PresetWrite);
|
||||||
$prepared = GlobalPreset::prepare_data($rest);
|
$prepared = GlobalPreset::prepare_data($rest);
|
||||||
$current = GlobalPreset::get_data();
|
$current = GlobalPreset::get_data();
|
||||||
$refusal = GlobalPreset::validate_preset_count(
|
$refusal = GlobalPreset::validate_preset_count(
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace LIT\MCP\Integrations\Divi\Design;
|
namespace LIT\MCP\Integrations\Divi\Design;
|
||||||
|
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||||
|
|
||||||
defined('ABSPATH') || exit;
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -27,6 +29,11 @@ final class PresetArea implements DesignArea
|
|||||||
return 'Presets';
|
return 'Presets';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function feature(): DiviFeature
|
||||||
|
{
|
||||||
|
return DiviFeature::PresetWrite;
|
||||||
|
}
|
||||||
|
|
||||||
public function raw(): array
|
public function raw(): array
|
||||||
{
|
{
|
||||||
return PresetBook::fromDivi($this->writer->presets())->data();
|
return PresetBook::fromDivi($this->writer->presets())->data();
|
||||||
|
|||||||
@@ -222,11 +222,11 @@ class DiviLibraryStore implements DiviAdapter
|
|||||||
* when that delete fails; a throwing insert hook is a partial write without ID.
|
* when that delete fails; a throwing insert hook is a partial write without ID.
|
||||||
*
|
*
|
||||||
* @throws WriteFailed
|
* @throws WriteFailed
|
||||||
* @throws DiviLocked When Divi is not released (spec §5.5) – before anything is written.
|
* @throws DiviLocked When library-write is not released (spec §5.5) – before anything is written.
|
||||||
*/
|
*/
|
||||||
public function create(LibraryItemSpec $spec, NewDraft $draft): int
|
public function create(LibraryItemSpec $spec, NewDraft $draft): int
|
||||||
{
|
{
|
||||||
ReleaseGate::assertWritable();
|
ReleaseGate::assertFeature(DiviFeature::LibraryWrite);
|
||||||
$missing = $this->missingTaxonomies();
|
$missing = $this->missingTaxonomies();
|
||||||
if ($missing !== []) {
|
if ($missing !== []) {
|
||||||
throw WriteFailed::rolledBack(
|
throw WriteFailed::rolledBack(
|
||||||
|
|||||||
@@ -61,11 +61,11 @@ final class DiviPages implements DiviAdapter
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws RuntimeException When a flag does not read back as set.
|
* @throws RuntimeException When a flag does not read back as set.
|
||||||
* @throws DiviLocked When Divi is not released (spec §5.5) – before anything is written.
|
* @throws DiviLocked When page-write is not released (spec §5.5) – before anything is written.
|
||||||
*/
|
*/
|
||||||
public static function setFlags(int $postId): void
|
public static function setFlags(int $postId): void
|
||||||
{
|
{
|
||||||
ReleaseGate::assertWritable($postId);
|
ReleaseGate::assertFeature(DiviFeature::PageWrite, $postId);
|
||||||
foreach (self::FLAGS as $key => $value) {
|
foreach (self::FLAGS as $key => $value) {
|
||||||
update_post_meta($postId, $key, $value);
|
update_post_meta($postId, $key, $value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,12 +71,12 @@ class DiviSaver
|
|||||||
/**
|
/**
|
||||||
* @return array{revision_id: ?int, warnings: list<string>}
|
* @return array{revision_id: ?int, warnings: list<string>}
|
||||||
* @throws WriteFailed
|
* @throws WriteFailed
|
||||||
* @throws DiviLocked When Divi is not released (spec §5.5) – before anything is written.
|
* @throws DiviLocked When a feature of $scope is not released (spec §5.5, D5) – before anything is written.
|
||||||
*/
|
*/
|
||||||
public function save(WP_Post $post, string $content, SaveScope $scope = SaveScope::Page): array
|
public function save(WP_Post $post, string $content, SaveScope $scope = SaveScope::Page): array
|
||||||
{
|
{
|
||||||
$postId = (int) $post->ID;
|
$postId = (int) $post->ID;
|
||||||
ReleaseGate::assertWritable($postId);
|
ReleaseGate::assertFeatures($scope->features(), $postId);
|
||||||
$revisionId = null;
|
$revisionId = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -142,11 +142,11 @@ class DiviSaver
|
|||||||
*
|
*
|
||||||
* @return list<string> Warnings (CHANGED_ON_SAVE).
|
* @return list<string> Warnings (CHANGED_ON_SAVE).
|
||||||
* @throws RuntimeException
|
* @throws RuntimeException
|
||||||
* @throws DiviLocked When Divi is not released (spec §5.5) – before anything is written.
|
* @throws DiviLocked When a feature of $scope is not released (spec §5.5, D5) – before anything is written.
|
||||||
*/
|
*/
|
||||||
public function saveNew(int $postId, string $content, SaveScope $scope = SaveScope::Page): array
|
public function saveNew(int $postId, string $content, SaveScope $scope = SaveScope::Page): array
|
||||||
{
|
{
|
||||||
ReleaseGate::assertWritable($postId);
|
ReleaseGate::assertFeatures($scope->features(), $postId);
|
||||||
$refusal = self::refusal($this->writeContent($postId, $content));
|
$refusal = self::refusal($this->writeContent($postId, $content));
|
||||||
if ($refusal !== null) {
|
if ($refusal !== null) {
|
||||||
throw new RuntimeException(sprintf('wp_update_post(%d) refused the Divi layout: %s', $postId, $refusal));
|
throw new RuntimeException(sprintf('wp_update_post(%d) refused the Divi layout: %s', $postId, $refusal));
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace LIT\MCP\Integrations\Divi\Pages;
|
namespace LIT\MCP\Integrations\Divi\Pages;
|
||||||
|
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||||
|
|
||||||
defined('ABSPATH') || exit;
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
// The installed PHPCompatibility sniffs predate enums (PHP 8.1) and read their methods as plain functions.
|
// The installed PHPCompatibility sniffs predate enums (PHP 8.1) and read their methods as plain functions.
|
||||||
@@ -33,4 +35,14 @@ enum SaveScope
|
|||||||
{
|
{
|
||||||
return $this === self::GlobalItem || $this === self::ThemeBuilderLayout;
|
return $this === self::GlobalItem || $this === self::ThemeBuilderLayout;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return list<DiviFeature> What a write in this scope needs (D5): page-write and the feature of its area. */
|
||||||
|
public function features(): array
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::Page => [DiviFeature::PageWrite],
|
||||||
|
self::LibraryItem, self::GlobalItem => [DiviFeature::PageWrite, DiviFeature::LibraryWrite],
|
||||||
|
self::ThemeBuilderLayout => [DiviFeature::PageWrite, DiviFeature::ThemeBuilderWrite],
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,21 +11,28 @@ use WP_Error;
|
|||||||
|
|
||||||
defined('ABSPATH') || exit;
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
/** A write refused because the installed Divi is not released (spec §5.5); AbilityRegistrar returns toError(). */
|
/**
|
||||||
|
* A Divi call refused because a feature it needs is not released (spec §5.5, D6); AbilityRegistrar returns
|
||||||
|
* toError(). Without features it is the general write lock of the overall status (texts of spec §5.6).
|
||||||
|
*/
|
||||||
final class DiviLocked extends RuntimeException implements ToolRefusal
|
final class DiviLocked extends RuntimeException implements ToolRefusal
|
||||||
{
|
{
|
||||||
public function __construct(public readonly ReleaseStatus $status)
|
/** @param list<DiviFeature> $features The locked features the refused call needs; [] = writing in general. */
|
||||||
|
public function __construct(public readonly ReleaseStatus $status, public readonly array $features = [])
|
||||||
{
|
{
|
||||||
parent::__construct(sprintf(
|
parent::__construct(sprintf(
|
||||||
'%s is %s for LIT MCP – write refused',
|
'%s is %s for LIT MCP – %s refused',
|
||||||
$status->versionLabel(),
|
$status->versionLabel(),
|
||||||
$status->state->value
|
$status->state->value,
|
||||||
|
$features === []
|
||||||
|
? 'write'
|
||||||
|
: implode(', ', array_map(static fn (DiviFeature $feature): string => $feature->value, $features))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function toError(): WP_Error
|
public function toError(): WP_Error
|
||||||
{
|
{
|
||||||
return $this->status->refusal();
|
return $this->features === [] ? $this->status->refusal() : $this->status->refusalFor($this->features);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The lock anywhere in $e's chain of previous exceptions (callers wrap failures in WriteFailed). */
|
/** The lock anywhere in $e's chain of previous exceptions (callers wrap failures in WriteFailed). */
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ use Throwable;
|
|||||||
defined('ABSPATH') || exit;
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The release status of this request and the lock every writing Divi path asks (spec §5.5). whileProbing() lets the
|
* The release status of this request and the lock every writing Divi path asks for the feature it needs (spec §5.5,
|
||||||
* admin check write its throwaway page whatever the status – only that post, only during the call, and only from
|
* E1). whileProbing() lets the admin check write its throwaway page whatever the status – only that post, only
|
||||||
* code (there is no parameter or option for it).
|
* during the call, and only from code (there is no parameter or option for it).
|
||||||
*/
|
*/
|
||||||
final class ReleaseGate
|
final class ReleaseGate
|
||||||
{
|
{
|
||||||
@@ -57,18 +57,50 @@ final class ReleaseGate
|
|||||||
self::$status = null;
|
self::$status = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @throws DiviLocked */
|
public static function feature(DiviFeature $feature): FeatureStatus
|
||||||
public static function assertWritable(?int $postId = null): void
|
{
|
||||||
|
return self::status()->matrix->of($feature);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @throws DiviLocked When $feature is not usable – before anything is written. */
|
||||||
|
public static function assertFeature(DiviFeature $feature, ?int $postId = null): void
|
||||||
|
{
|
||||||
|
self::assertFeatures([$feature], $postId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Every one of $features usable, else DiviLocked with the locked ones. The probe post passes whatever the status.
|
||||||
|
*
|
||||||
|
* @param list<DiviFeature> $features
|
||||||
|
* @throws DiviLocked
|
||||||
|
*/
|
||||||
|
public static function assertFeatures(array $features, ?int $postId = null): void
|
||||||
{
|
{
|
||||||
if (self::$probe !== null && $postId === self::$probe) {
|
if (self::$probe !== null && $postId === self::$probe) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$status = self::status();
|
$status = self::status();
|
||||||
if (! $status->writable()) {
|
$locked = [];
|
||||||
throw new DiviLocked($status);
|
foreach ($features as $feature) {
|
||||||
|
if (! $status->matrix->of($feature)->usable()) {
|
||||||
|
$locked[$feature->value] = $feature;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ($locked !== []) {
|
||||||
|
throw new DiviLocked($status, array_values($locked));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Every writing feature (CompatibilityMatrix::writeFeatures()) – for callers that know no feature.
|
||||||
|
*
|
||||||
|
* @throws DiviLocked
|
||||||
|
*/
|
||||||
|
public static function assertWritable(?int $postId = null): void
|
||||||
|
{
|
||||||
|
self::assertFeatures(CompatibilityMatrix::writeFeatures(), $postId);
|
||||||
|
}
|
||||||
|
|
||||||
public static function whileProbing(int $postId, callable $write): mixed
|
public static function whileProbing(int $postId, callable $write): mixed
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -30,6 +30,15 @@ final class ReleaseStatus
|
|||||||
private const INCOMPATIBLE_HINT = 'Den Nutzer informieren, dass LIT MCP aktualisiert werden muss; lesende '
|
private const INCOMPATIBLE_HINT = 'Den Nutzer informieren, dass LIT MCP aktualisiert werden muss; lesende '
|
||||||
. 'Divi-Werkzeuge funktionieren weiter.';
|
. 'Divi-Werkzeuge funktionieren weiter.';
|
||||||
private const NOTICE = 'Achtung: %s ist für LIT MCP %s; Lesen funktioniert, Schreiben ist gesperrt.';
|
private const NOTICE = 'Achtung: %s ist für LIT MCP %s; Lesen funktioniert, Schreiben ist gesperrt.';
|
||||||
|
private const FEATURE_UNTESTED = 'Achtung: %1$s ist auf dieser Website für %2$s noch nicht für LIT MCP '
|
||||||
|
. 'freigegeben. Das ist gesperrt, bis ein Administrator unter Einstellungen → LIT MCP → Divi die Prüfung '
|
||||||
|
. 'ausführt und die Version freigibt. Es wurde nichts geändert.';
|
||||||
|
private const FEATURE_UNTESTED_HINT = 'Gesperrt: %s. Andere Divi-Werkzeuge funktionieren weiter, sofern ihre '
|
||||||
|
. 'Fähigkeiten freigegeben sind. Den Nutzer bitten, die Divi-Prüfung auszuführen und die Version freizugeben.';
|
||||||
|
private const FEATURE_INCOMPATIBLE = '%1$s hat Stellen geändert, die LIT MCP für %2$s nutzt. Das ist gesperrt, '
|
||||||
|
. 'bis LIT MCP aktualisiert wurde. Es wurde nichts geändert.';
|
||||||
|
private const FEATURE_INCOMPATIBLE_HINT = 'Gesperrt: %s. Andere Divi-Werkzeuge funktionieren weiter, sofern ihre '
|
||||||
|
. 'Fähigkeiten freigegeben sind. Den Nutzer informieren, dass LIT MCP aktualisiert werden muss.';
|
||||||
|
|
||||||
public readonly CompatibilityMatrix $matrix;
|
public readonly CompatibilityMatrix $matrix;
|
||||||
|
|
||||||
@@ -99,6 +108,56 @@ final class ReleaseStatus
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The error of a call refused because some of $features are locked (D6): divi_incompatible when one of them is
|
||||||
|
* incompatible, else divi_untested; HTTP 423. The message names the features in that state, the hint every
|
||||||
|
* locked one; details.features lists them (id, label, state, problems), details.problems the problems of an
|
||||||
|
* incompatible refusal as before.
|
||||||
|
*
|
||||||
|
* @param list<DiviFeature> $features What the refused call needs.
|
||||||
|
* @throws LogicException When all of them are usable.
|
||||||
|
*/
|
||||||
|
public function refusalFor(array $features): WP_Error
|
||||||
|
{
|
||||||
|
$locked = [];
|
||||||
|
foreach ($features as $feature) {
|
||||||
|
$status = $this->matrix->of($feature);
|
||||||
|
if (! $status->usable()) {
|
||||||
|
$locked[$feature->value] = $status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($locked === []) {
|
||||||
|
throw new LogicException('usable features refuse nothing');
|
||||||
|
}
|
||||||
|
|
||||||
|
$locked = array_values($locked);
|
||||||
|
$incompatible = array_values(array_filter(
|
||||||
|
$locked,
|
||||||
|
static fn (FeatureStatus $status): bool => $status->state === ReleaseState::Incompatible
|
||||||
|
));
|
||||||
|
$details = ['features' => array_map(static fn (FeatureStatus $status): array => $status->summary(), $locked)];
|
||||||
|
if ($incompatible !== []) {
|
||||||
|
$details['problems'] = array_values(array_unique(array_merge(
|
||||||
|
...array_map(static fn (FeatureStatus $status): array => $status->problems, $locked)
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Errors::make(
|
||||||
|
$incompatible !== [] ? 'divi_incompatible' : 'divi_untested',
|
||||||
|
sprintf(
|
||||||
|
$incompatible !== [] ? self::FEATURE_INCOMPATIBLE : self::FEATURE_UNTESTED,
|
||||||
|
$this->versionLabel(),
|
||||||
|
self::named($incompatible !== [] ? $incompatible : $locked)
|
||||||
|
),
|
||||||
|
sprintf(
|
||||||
|
$incompatible !== [] ? self::FEATURE_INCOMPATIBLE_HINT : self::FEATURE_UNTESTED_HINT,
|
||||||
|
self::named($locked)
|
||||||
|
),
|
||||||
|
423,
|
||||||
|
$details
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/** The warning a reading Divi tool carries while writes are locked. */
|
/** The warning a reading Divi tool carries while writes are locked. */
|
||||||
public function notice(): string
|
public function notice(): string
|
||||||
{
|
{
|
||||||
@@ -109,6 +168,22 @@ final class ReleaseStatus
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "„Seiten schreiben“", "„A“ und „B“", "„A“, „B“ und „C“".
|
||||||
|
*
|
||||||
|
* @param non-empty-list<FeatureStatus> $statuses
|
||||||
|
*/
|
||||||
|
private static function named(array $statuses): string
|
||||||
|
{
|
||||||
|
$labels = array_map(
|
||||||
|
static fn (FeatureStatus $status): string => '„' . $status->feature->label() . '“',
|
||||||
|
$statuses
|
||||||
|
);
|
||||||
|
$last = array_pop($labels);
|
||||||
|
|
||||||
|
return $labels === [] ? $last : implode(', ', $labels) . ' und ' . $last;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array{state: string, version: string, source: ?string, released_at: ?string, released_by: ?int,
|
* @return array{state: string, version: string, source: ?string, released_at: ?string, released_by: ?int,
|
||||||
* problems: list<string>}
|
* problems: list<string>}
|
||||||
|
|||||||
@@ -149,12 +149,12 @@ class DiviThemeBuilder implements DiviAdapter
|
|||||||
/**
|
/**
|
||||||
* The live Theme Builder post (publish, no library TB); $create makes it like Divi's first save. 0 = none.
|
* The live Theme Builder post (publish, no library TB); $create makes it like Divi's first save. 0 = none.
|
||||||
*
|
*
|
||||||
* @throws DiviLocked When $create and Divi is not released (spec §5.5) – before anything is written.
|
* @throws DiviLocked When $create and tb-write is not released (spec §5.5) – before anything is written.
|
||||||
*/
|
*/
|
||||||
public function liveId(bool $create = false): int
|
public function liveId(bool $create = false): int
|
||||||
{
|
{
|
||||||
if ($create) {
|
if ($create) {
|
||||||
ReleaseGate::assertWritable();
|
ReleaseGate::assertFeature(DiviFeature::ThemeBuilderWrite);
|
||||||
}
|
}
|
||||||
|
|
||||||
return function_exists('et_theme_builder_get_theme_builder_post_id')
|
return function_exists('et_theme_builder_get_theme_builder_post_id')
|
||||||
@@ -261,11 +261,11 @@ class DiviThemeBuilder implements DiviAdapter
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws RuntimeException
|
* @throws RuntimeException
|
||||||
* @throws DiviLocked When Divi is not released (spec §5.5) – before anything is written.
|
* @throws DiviLocked When tb-write is not released (spec §5.5) – before anything is written.
|
||||||
*/
|
*/
|
||||||
public function insertLayout(string $area, string $title): int
|
public function insertLayout(string $area, string $title): int
|
||||||
{
|
{
|
||||||
ReleaseGate::assertWritable();
|
ReleaseGate::assertFeature(DiviFeature::ThemeBuilderWrite);
|
||||||
$type = self::LAYOUT_TYPES[$area] ?? throw new RuntimeException('unknown Theme Builder area ' . $area);
|
$type = self::LAYOUT_TYPES[$area] ?? throw new RuntimeException('unknown Theme Builder area ' . $area);
|
||||||
$this->assertCanWrite('et_theme_builder_insert_layout');
|
$this->assertCanWrite('et_theme_builder_insert_layout');
|
||||||
$id = $this->guarded(
|
$id = $this->guarded(
|
||||||
@@ -287,11 +287,11 @@ class DiviThemeBuilder implements DiviAdapter
|
|||||||
* @param array{title: string, layouts: array<string, array{id: int, enabled: bool}>, use_on: list<string>,
|
* @param array{title: string, layouts: array<string, array{id: int, enabled: bool}>, use_on: list<string>,
|
||||||
* exclude_from: list<string>} $template
|
* exclude_from: list<string>} $template
|
||||||
* @throws RuntimeException
|
* @throws RuntimeException
|
||||||
* @throws DiviLocked When Divi is not released (spec §5.5) – before anything is written.
|
* @throws DiviLocked When tb-write is not released (spec §5.5) – before anything is written.
|
||||||
*/
|
*/
|
||||||
public function storeTemplate(int $themeBuilderId, array $template): int
|
public function storeTemplate(int $themeBuilderId, array $template): int
|
||||||
{
|
{
|
||||||
ReleaseGate::assertWritable();
|
ReleaseGate::assertFeature(DiviFeature::ThemeBuilderWrite);
|
||||||
$this->assertCanWrite('et_theme_builder_store_template', 'et_');
|
$this->assertCanWrite('et_theme_builder_store_template', 'et_');
|
||||||
$payload = [
|
$payload = [
|
||||||
'title' => $template['title'],
|
'title' => $template['title'],
|
||||||
@@ -321,11 +321,11 @@ class DiviThemeBuilder implements DiviAdapter
|
|||||||
* template unused (stage3b README finding 10) – detachedIds() finds it, callers unmarkUnused() on reattach.
|
* template unused (stage3b README finding 10) – detachedIds() finds it, callers unmarkUnused() on reattach.
|
||||||
*
|
*
|
||||||
* @throws RuntimeException
|
* @throws RuntimeException
|
||||||
* @throws DiviLocked When Divi is not released (spec §5.5) – before anything is written.
|
* @throws DiviLocked When tb-write is not released (spec §5.5) – before anything is written.
|
||||||
*/
|
*/
|
||||||
public function attach(int $themeBuilderId, int $templateId): void
|
public function attach(int $themeBuilderId, int $templateId): void
|
||||||
{
|
{
|
||||||
ReleaseGate::assertWritable();
|
ReleaseGate::assertFeature(DiviFeature::ThemeBuilderWrite);
|
||||||
$existing = get_post_meta($themeBuilderId, self::TEMPLATE_LIST, false);
|
$existing = get_post_meta($themeBuilderId, self::TEMPLATE_LIST, false);
|
||||||
if (is_array($existing) && in_array((string) $templateId, array_map('strval', $existing), true)) {
|
if (is_array($existing) && in_array((string) $templateId, array_map('strval', $existing), true)) {
|
||||||
return;
|
return;
|
||||||
@@ -342,10 +342,10 @@ class DiviThemeBuilder implements DiviAdapter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @throws DiviLocked When Divi is not released (spec §5.5) – before anything is written. */
|
/** @throws DiviLocked When tb-write is not released (spec §5.5) – before anything is written. */
|
||||||
public function detach(int $themeBuilderId, int $templateId): void
|
public function detach(int $themeBuilderId, int $templateId): void
|
||||||
{
|
{
|
||||||
ReleaseGate::assertWritable();
|
ReleaseGate::assertFeature(DiviFeature::ThemeBuilderWrite);
|
||||||
delete_post_meta($themeBuilderId, self::TEMPLATE_LIST, (string) $templateId);
|
delete_post_meta($themeBuilderId, self::TEMPLATE_LIST, (string) $templateId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,11 +356,11 @@ class DiviThemeBuilder implements DiviAdapter
|
|||||||
* @param list<string> $useOn
|
* @param list<string> $useOn
|
||||||
* @param list<string> $excludeFrom
|
* @param list<string> $excludeFrom
|
||||||
* @throws RuntimeException
|
* @throws RuntimeException
|
||||||
* @throws DiviLocked When Divi is not released (spec §5.5) – before anything is written.
|
* @throws DiviLocked When tb-write is not released (spec §5.5) – before anything is written.
|
||||||
*/
|
*/
|
||||||
public function setConditions(int $templateId, array $useOn, array $excludeFrom): void
|
public function setConditions(int $templateId, array $useOn, array $excludeFrom): void
|
||||||
{
|
{
|
||||||
ReleaseGate::assertWritable();
|
ReleaseGate::assertFeature(DiviFeature::ThemeBuilderWrite);
|
||||||
foreach ([self::USE_ON => $useOn, self::EXCLUDE_FROM => $excludeFrom] as $key => $conditions) {
|
foreach ([self::USE_ON => $useOn, self::EXCLUDE_FROM => $excludeFrom] as $key => $conditions) {
|
||||||
delete_post_meta($templateId, $key);
|
delete_post_meta($templateId, $key);
|
||||||
foreach (array_values(array_unique($conditions)) as $condition) {
|
foreach (array_values(array_unique($conditions)) as $condition) {
|
||||||
@@ -373,11 +373,11 @@ class DiviThemeBuilder implements DiviAdapter
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws RuntimeException
|
* @throws RuntimeException
|
||||||
* @throws DiviLocked When Divi is not released (spec §5.5) – before anything is written.
|
* @throws DiviLocked When tb-write is not released (spec §5.5) – before anything is written.
|
||||||
*/
|
*/
|
||||||
public function setEnabled(int $templateId, bool $enabled): void
|
public function setEnabled(int $templateId, bool $enabled): void
|
||||||
{
|
{
|
||||||
ReleaseGate::assertWritable();
|
ReleaseGate::assertFeature(DiviFeature::ThemeBuilderWrite);
|
||||||
$value = $enabled ? '1' : '0';
|
$value = $enabled ? '1' : '0';
|
||||||
update_post_meta($templateId, self::ENABLED, $value);
|
update_post_meta($templateId, self::ENABLED, $value);
|
||||||
if ((string) get_post_meta($templateId, self::ENABLED, true) !== $value) {
|
if ((string) get_post_meta($templateId, self::ENABLED, true) !== $value) {
|
||||||
@@ -388,11 +388,11 @@ class DiviThemeBuilder implements DiviAdapter
|
|||||||
/**
|
/**
|
||||||
* Removes Divi's "unused" mark (F19), as Divi's own save does for every listed post.
|
* Removes Divi's "unused" mark (F19), as Divi's own save does for every listed post.
|
||||||
*
|
*
|
||||||
* @throws DiviLocked When Divi is not released (spec §5.5) – before anything is written.
|
* @throws DiviLocked When tb-write is not released (spec §5.5) – before anything is written.
|
||||||
*/
|
*/
|
||||||
public function unmarkUnused(int $postId): void
|
public function unmarkUnused(int $postId): void
|
||||||
{
|
{
|
||||||
ReleaseGate::assertWritable();
|
ReleaseGate::assertFeature(DiviFeature::ThemeBuilderWrite);
|
||||||
delete_post_meta($postId, self::UNUSED);
|
delete_post_meta($postId, self::UNUSED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,12 +11,14 @@ use LIT\MCP\Integrations\Divi\Design\DesignPlan;
|
|||||||
use LIT\MCP\Integrations\Divi\Design\SnapshotStore;
|
use LIT\MCP\Integrations\Divi\Design\SnapshotStore;
|
||||||
use LIT\MCP\Integrations\Divi\Design\SnapshotTooLarge;
|
use LIT\MCP\Integrations\Divi\Design\SnapshotTooLarge;
|
||||||
use LIT\MCP\Integrations\Divi\Library\UsageReport;
|
use LIT\MCP\Integrations\Divi\Library\UsageReport;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||||
use LIT\MCP\Integrations\Divi\Release\DiviLocked;
|
use LIT\MCP\Integrations\Divi\Release\DiviLocked;
|
||||||
use LIT\MCP\Integrations\Divi\Release\ReleaseGate;
|
use LIT\MCP\Integrations\Divi\Release\ReleaseGate;
|
||||||
use LIT\MCP\Integrations\Divi\Release\ReleaseState;
|
use LIT\MCP\Integrations\Divi\Release\ReleaseState;
|
||||||
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
|
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
|
||||||
use LIT\MCP\Abilities\WriteFailed;
|
use LIT\MCP\Abilities\WriteFailed;
|
||||||
use LIT\MCP\Tests\Support\FakeChangeLog;
|
use LIT\MCP\Tests\Support\FakeChangeLog;
|
||||||
|
use LIT\MCP\Tests\Support\ReleaseMatrices;
|
||||||
use LIT\MCP\Tests\Unit\TestCase;
|
use LIT\MCP\Tests\Unit\TestCase;
|
||||||
use Brain\Monkey\Functions;
|
use Brain\Monkey\Functions;
|
||||||
use Mockery;
|
use Mockery;
|
||||||
@@ -80,6 +82,11 @@ final class DesignChangeTest extends TestCase
|
|||||||
return 'Presets';
|
return 'Presets';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function feature(): DiviFeature
|
||||||
|
{
|
||||||
|
return DiviFeature::PresetWrite;
|
||||||
|
}
|
||||||
|
|
||||||
public function raw(): array
|
public function raw(): array
|
||||||
{
|
{
|
||||||
return $this->stored;
|
return $this->stored;
|
||||||
@@ -383,6 +390,41 @@ final class DesignChangeTest extends TestCase
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testTheLockFollowsTheFeatureOfTheArea(): void
|
||||||
|
{
|
||||||
|
$others = array_values(array_filter(
|
||||||
|
DiviFeature::cases(),
|
||||||
|
static fn (DiviFeature $feature): bool => $feature !== DiviFeature::PresetWrite
|
||||||
|
));
|
||||||
|
ReleaseGate::useResolver(static fn (): ReleaseStatus => ReleaseMatrices::status($others));
|
||||||
|
$stored = ['v' => 1];
|
||||||
|
$area = self::area($stored);
|
||||||
|
$snapshots = Mockery::mock(SnapshotStore::class);
|
||||||
|
$snapshots->expects('save')->andReturn('s1');
|
||||||
|
$snapshots->expects('attachLog');
|
||||||
|
|
||||||
|
$written = (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($written['ok'], 'every other feature locked: presets are still written');
|
||||||
|
|
||||||
|
ReleaseGate::useResolver(static fn (): ReleaseStatus => ReleaseMatrices::status([DiviFeature::PresetWrite]));
|
||||||
|
try {
|
||||||
|
(new DesignChange(Mockery::mock(SnapshotStore::class), FakeChangeLog::forbidden()))->run(
|
||||||
|
'lit/divi-presets-set',
|
||||||
|
$area,
|
||||||
|
['confirm' => true, 'design_rev' => $area->rev($stored)],
|
||||||
|
self::plan(['v' => 3])
|
||||||
|
);
|
||||||
|
self::fail('preset-write locked must refuse');
|
||||||
|
} catch (DiviLocked $locked) {
|
||||||
|
self::assertSame([DiviFeature::PresetWrite], $locked->features);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function testTooLargeSnapshotRefusesTheChange(): void
|
public function testTooLargeSnapshotRefusesTheChange(): void
|
||||||
{
|
{
|
||||||
$stored = ['v' => 1];
|
$stored = ['v' => 1];
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace LIT\MCP\Tests\Unit\Integrations\Divi\Design;
|
|||||||
use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter;
|
use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter;
|
||||||
use LIT\MCP\Integrations\Divi\Design\PresetArea;
|
use LIT\MCP\Integrations\Divi\Design\PresetArea;
|
||||||
use LIT\MCP\Integrations\Divi\Design\PresetBook;
|
use LIT\MCP\Integrations\Divi\Design\PresetBook;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||||
use LIT\MCP\Tests\Unit\TestCase;
|
use LIT\MCP\Tests\Unit\TestCase;
|
||||||
use Mockery;
|
use Mockery;
|
||||||
|
|
||||||
@@ -23,6 +24,7 @@ final class PresetAreaTest extends TestCase
|
|||||||
$area = new PresetArea($writer);
|
$area = new PresetArea($writer);
|
||||||
|
|
||||||
self::assertSame(['presets', 'Presets'], [$area->name(), $area->label()]);
|
self::assertSame(['presets', 'Presets'], [$area->name(), $area->label()]);
|
||||||
|
self::assertSame(DiviFeature::PresetWrite, $area->feature());
|
||||||
self::assertSame(PresetBook::fromDivi($stored)->data(), $area->raw());
|
self::assertSame(PresetBook::fromDivi($stored)->data(), $area->raw());
|
||||||
self::assertSame(PresetBook::fromDivi($stored)->rev(), $area->rev($stored));
|
self::assertSame(PresetBook::fromDivi($stored)->rev(), $area->rev($stored));
|
||||||
self::assertSame(PresetBook::fromDivi($stored)->data(), $area->write($stored, true));
|
self::assertSame(PresetBook::fromDivi($stored)->data(), $area->write($stored, true));
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Pages;
|
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Pages;
|
||||||
|
|
||||||
use LIT\MCP\Integrations\Divi\Pages\SaveScope;
|
use LIT\MCP\Integrations\Divi\Pages\SaveScope;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
final class SaveScopeTest extends TestCase
|
final class SaveScopeTest extends TestCase
|
||||||
@@ -18,4 +19,16 @@ final class SaveScopeTest extends TestCase
|
|||||||
self::assertFalse(SaveScope::Page->clearsSiteCss());
|
self::assertFalse(SaveScope::Page->clearsSiteCss());
|
||||||
self::assertFalse(SaveScope::LibraryItem->setsFlags());
|
self::assertFalse(SaveScope::LibraryItem->setsFlags());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** D5: every save needs page-write, a library or Theme Builder save the feature of its area too. */
|
||||||
|
public function testFeaturesOfEachScope(): void
|
||||||
|
{
|
||||||
|
self::assertSame([DiviFeature::PageWrite], SaveScope::Page->features());
|
||||||
|
self::assertSame([DiviFeature::PageWrite, DiviFeature::LibraryWrite], SaveScope::LibraryItem->features());
|
||||||
|
self::assertSame([DiviFeature::PageWrite, DiviFeature::LibraryWrite], SaveScope::GlobalItem->features());
|
||||||
|
self::assertSame(
|
||||||
|
[DiviFeature::PageWrite, DiviFeature::ThemeBuilderWrite],
|
||||||
|
SaveScope::ThemeBuilderLayout->features()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Release;
|
||||||
|
|
||||||
|
use LIT\MCP\Abilities\ToolRefusal;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviLocked;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\ReleaseState;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
|
||||||
|
use LIT\MCP\Tests\Support\ReleaseMatrices;
|
||||||
|
use LIT\MCP\Tests\Unit\TestCase;
|
||||||
|
|
||||||
|
final class DiviLockedTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testWithFeaturesTheErrorIsTheRefusalOfThoseFeatures(): void
|
||||||
|
{
|
||||||
|
$status = ReleaseMatrices::status([DiviFeature::PresetWrite], ReleaseState::Incompatible);
|
||||||
|
$locked = new DiviLocked($status, [DiviFeature::PresetWrite]);
|
||||||
|
|
||||||
|
self::assertInstanceOf(ToolRefusal::class, $locked);
|
||||||
|
self::assertSame('Divi 5.3.0 is incompatible for LIT MCP – preset-write refused', $locked->getMessage());
|
||||||
|
self::assertEquals($status->refusalFor([DiviFeature::PresetWrite]), $locked->toError());
|
||||||
|
$payload = self::assertToolError($locked->toError(), 'divi_incompatible');
|
||||||
|
self::assertSame(['preset-write'], array_column($payload['features'], 'id'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testWithoutFeaturesTheErrorIsTheGeneralWriteLock(): void
|
||||||
|
{
|
||||||
|
$status = new ReleaseStatus(ReleaseState::Untested, '5.3.0');
|
||||||
|
$locked = new DiviLocked($status);
|
||||||
|
|
||||||
|
self::assertSame('Divi 5.3.0 is untested for LIT MCP – write refused', $locked->getMessage());
|
||||||
|
self::assertEquals($status->refusal(), $locked->toError());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,11 +5,13 @@ declare(strict_types=1);
|
|||||||
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Release;
|
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Release;
|
||||||
|
|
||||||
use LIT\MCP\Abilities\WriteFailed;
|
use LIT\MCP\Abilities\WriteFailed;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\CompatibilityMatrix;
|
||||||
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||||
use LIT\MCP\Integrations\Divi\Release\DiviLocked;
|
use LIT\MCP\Integrations\Divi\Release\DiviLocked;
|
||||||
use LIT\MCP\Integrations\Divi\Release\ReleaseGate;
|
use LIT\MCP\Integrations\Divi\Release\ReleaseGate;
|
||||||
use LIT\MCP\Integrations\Divi\Release\ReleaseState;
|
use LIT\MCP\Integrations\Divi\Release\ReleaseState;
|
||||||
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
|
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
|
||||||
|
use LIT\MCP\Tests\Support\ReleaseMatrices;
|
||||||
use LIT\MCP\Tests\Unit\TestCase;
|
use LIT\MCP\Tests\Unit\TestCase;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
|
||||||
@@ -20,10 +22,78 @@ final class ReleaseGateTest extends TestCase
|
|||||||
ReleaseGate::useResolver(static fn (): ReleaseStatus => new ReleaseStatus(ReleaseState::Untested, '5.3.0'));
|
ReleaseGate::useResolver(static fn (): ReleaseStatus => new ReleaseStatus(ReleaseState::Untested, '5.3.0'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testTheTestCaseDefaultIsReleased(): void
|
public function testTheTestCaseDefaultReleasesEveryFeature(): void
|
||||||
{
|
{
|
||||||
self::assertTrue(ReleaseGate::status()->writable());
|
self::assertTrue(ReleaseGate::status()->writable());
|
||||||
|
self::assertSame([], ReleaseGate::status()->matrix->unusable());
|
||||||
ReleaseGate::assertWritable(7);
|
ReleaseGate::assertWritable(7);
|
||||||
|
ReleaseGate::assertFeatures(DiviFeature::cases());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFeatureReadsTheMatrixOfThisRequest(): void
|
||||||
|
{
|
||||||
|
ReleaseGate::useResolver(static fn (): ReleaseStatus => ReleaseMatrices::status([DiviFeature::Catalog]));
|
||||||
|
|
||||||
|
self::assertSame(ReleaseState::Untested, ReleaseGate::feature(DiviFeature::Catalog)->state);
|
||||||
|
self::assertTrue(ReleaseGate::feature(DiviFeature::PageRead)->usable());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAssertFeatureRefusesOnlyALockedFeature(): void
|
||||||
|
{
|
||||||
|
ReleaseGate::useResolver(static fn (): ReleaseStatus => ReleaseMatrices::status(
|
||||||
|
[DiviFeature::PresetWrite],
|
||||||
|
ReleaseState::Incompatible
|
||||||
|
));
|
||||||
|
ReleaseGate::assertFeature(DiviFeature::PageWrite);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ReleaseGate::assertFeature(DiviFeature::PresetWrite, 7);
|
||||||
|
self::fail('a locked feature must refuse');
|
||||||
|
} catch (DiviLocked $locked) {
|
||||||
|
self::assertSame([DiviFeature::PresetWrite], $locked->features);
|
||||||
|
self::assertToolError($locked->toError(), 'divi_incompatible');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAssertFeaturesNamesEveryLockedFeatureOnce(): void
|
||||||
|
{
|
||||||
|
ReleaseGate::useResolver(static fn (): ReleaseStatus => ReleaseMatrices::status(
|
||||||
|
[DiviFeature::LibraryWrite, DiviFeature::ThemeBuilderWrite]
|
||||||
|
));
|
||||||
|
|
||||||
|
try {
|
||||||
|
ReleaseGate::assertFeatures([
|
||||||
|
DiviFeature::PageWrite,
|
||||||
|
DiviFeature::ThemeBuilderWrite,
|
||||||
|
DiviFeature::LibraryWrite,
|
||||||
|
DiviFeature::ThemeBuilderWrite,
|
||||||
|
]);
|
||||||
|
self::fail('locked features must refuse');
|
||||||
|
} catch (DiviLocked $locked) {
|
||||||
|
self::assertSame([DiviFeature::ThemeBuilderWrite, DiviFeature::LibraryWrite], $locked->features);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testAssertWritableNeedsEveryWritingFeature(): void
|
||||||
|
{
|
||||||
|
ReleaseGate::useResolver(static fn (): ReleaseStatus => ReleaseMatrices::status([DiviFeature::Catalog]));
|
||||||
|
ReleaseGate::assertWritable();
|
||||||
|
|
||||||
|
ReleaseGate::useResolver(static fn (): ReleaseStatus => ReleaseMatrices::status([DiviFeature::LibraryWrite]));
|
||||||
|
try {
|
||||||
|
ReleaseGate::assertWritable();
|
||||||
|
self::fail('a locked writing feature must refuse');
|
||||||
|
} catch (DiviLocked $locked) {
|
||||||
|
self::assertSame([DiviFeature::LibraryWrite], $locked->features);
|
||||||
|
}
|
||||||
|
|
||||||
|
ReleaseGate::useResolver(static fn (): ReleaseStatus => new ReleaseStatus(ReleaseState::Untested, '5.3.0'));
|
||||||
|
try {
|
||||||
|
ReleaseGate::assertWritable();
|
||||||
|
self::fail('an untested Divi must lock writes');
|
||||||
|
} catch (DiviLocked $locked) {
|
||||||
|
self::assertSame(CompatibilityMatrix::writeFeatures(), $locked->features);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testAssertWritableThrowsTheLockWithTheStatus(): void
|
public function testAssertWritableThrowsTheLockWithTheStatus(): void
|
||||||
@@ -45,6 +115,7 @@ final class ReleaseGateTest extends TestCase
|
|||||||
|
|
||||||
$result = ReleaseGate::whileProbing(41, static function (): string {
|
$result = ReleaseGate::whileProbing(41, static function (): string {
|
||||||
ReleaseGate::assertWritable(41);
|
ReleaseGate::assertWritable(41);
|
||||||
|
ReleaseGate::assertFeatures(DiviFeature::cases(), 41);
|
||||||
|
|
||||||
return 'written';
|
return 'written';
|
||||||
});
|
});
|
||||||
@@ -52,6 +123,10 @@ final class ReleaseGateTest extends TestCase
|
|||||||
|
|
||||||
foreach ([static fn () => ReleaseGate::whileProbing(41, static fn () => ReleaseGate::assertWritable(42)),
|
foreach ([static fn () => ReleaseGate::whileProbing(41, static fn () => ReleaseGate::assertWritable(42)),
|
||||||
static fn () => ReleaseGate::whileProbing(41, static fn () => ReleaseGate::assertWritable()),
|
static fn () => ReleaseGate::whileProbing(41, static fn () => ReleaseGate::assertWritable()),
|
||||||
|
static fn () => ReleaseGate::whileProbing(41, static fn () => ReleaseGate::assertFeature(
|
||||||
|
DiviFeature::PageWrite,
|
||||||
|
42
|
||||||
|
)),
|
||||||
static fn () => ReleaseGate::assertWritable(41)] as $attempt) {
|
static fn () => ReleaseGate::assertWritable(41)] as $attempt) {
|
||||||
try {
|
try {
|
||||||
$attempt();
|
$attempt();
|
||||||
@@ -118,7 +193,7 @@ final class ReleaseGateTest extends TestCase
|
|||||||
self::assertStringContainsString('theme API gone', (string) file_get_contents($log));
|
self::assertStringContainsString('theme API gone', (string) file_get_contents($log));
|
||||||
unlink($log);
|
unlink($log);
|
||||||
$this->expectException(DiviLocked::class);
|
$this->expectException(DiviLocked::class);
|
||||||
ReleaseGate::assertWritable();
|
ReleaseGate::assertFeature(DiviFeature::PageRead);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testFindsTheLockInsideAWrappedFailure(): void
|
public function testFindsTheLockInsideAWrappedFailure(): void
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ declare(strict_types=1);
|
|||||||
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Release;
|
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Release;
|
||||||
|
|
||||||
use Brain\Monkey\Functions;
|
use Brain\Monkey\Functions;
|
||||||
|
use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter;
|
||||||
use LIT\MCP\Integrations\Divi\Library\DiviLibraryStore;
|
use LIT\MCP\Integrations\Divi\Library\DiviLibraryStore;
|
||||||
use LIT\MCP\Integrations\Divi\Library\LibraryItemSpec;
|
use LIT\MCP\Integrations\Divi\Library\LibraryItemSpec;
|
||||||
use LIT\MCP\Integrations\Divi\Pages\DiviPages;
|
use LIT\MCP\Integrations\Divi\Pages\DiviPages;
|
||||||
use LIT\MCP\Integrations\Divi\Pages\DiviSaver;
|
use LIT\MCP\Integrations\Divi\Pages\DiviSaver;
|
||||||
|
use LIT\MCP\Integrations\Divi\Pages\SaveScope;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||||
use LIT\MCP\Integrations\Divi\Release\DiviLocked;
|
use LIT\MCP\Integrations\Divi\Release\DiviLocked;
|
||||||
use LIT\MCP\Integrations\Divi\Release\ReleaseGate;
|
use LIT\MCP\Integrations\Divi\Release\ReleaseGate;
|
||||||
use LIT\MCP\Integrations\Divi\Release\ReleaseState;
|
use LIT\MCP\Integrations\Divi\Release\ReleaseState;
|
||||||
@@ -16,19 +19,26 @@ use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
|
|||||||
use LIT\MCP\Integrations\Divi\ThemeBuilder\DiviThemeBuilder;
|
use LIT\MCP\Integrations\Divi\ThemeBuilder\DiviThemeBuilder;
|
||||||
use LIT\MCP\Integrations\WordPress\Content\NewDraft;
|
use LIT\MCP\Integrations\WordPress\Content\NewDraft;
|
||||||
use LIT\MCP\Tests\Support\FakeChangeLog;
|
use LIT\MCP\Tests\Support\FakeChangeLog;
|
||||||
|
use LIT\MCP\Tests\Support\ReleaseMatrices;
|
||||||
use LIT\MCP\Tests\Unit\TestCase;
|
use LIT\MCP\Tests\Unit\TestCase;
|
||||||
|
use RuntimeException;
|
||||||
|
use Throwable;
|
||||||
use WP_Post;
|
use WP_Post;
|
||||||
|
|
||||||
/** Spec §5.5: every writing adapter method refuses before its first write while Divi is not released. */
|
/**
|
||||||
|
* Spec §5.5, D5: every writing adapter method refuses before its first write exactly when a feature it needs is
|
||||||
|
* locked – and passes the gate when only other features are locked. (DesignChange: DesignChangeTest.)
|
||||||
|
*/
|
||||||
final class ReleaseGuardsTest extends TestCase
|
final class ReleaseGuardsTest extends TestCase
|
||||||
{
|
{
|
||||||
|
private const WRITES = ['wp_update_post', 'wp_insert_post', 'update_post_meta', 'add_post_meta', 'delete_post_meta',
|
||||||
|
'update_option', 'delete_option', 'wp_set_object_terms'];
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
ReleaseGate::useResolver(static fn (): ReleaseStatus => new ReleaseStatus(ReleaseState::Untested, '5.3.0'));
|
// Nothing may be written while locked: every write function WordPress offers fails the test.
|
||||||
// Nothing may be written: every write function WordPress offers fails the test.
|
foreach (self::WRITES as $write) {
|
||||||
foreach (['wp_update_post', 'wp_insert_post', 'update_post_meta', 'add_post_meta', 'delete_post_meta',
|
|
||||||
'update_option', 'delete_option', 'wp_set_object_terms'] as $write) {
|
|
||||||
Functions\when($write)->alias(static function () use ($write): never {
|
Functions\when($write)->alias(static function () use ($write): never {
|
||||||
self::fail($write . ' ran although Divi is locked');
|
self::fail($write . ' ran although Divi is locked');
|
||||||
});
|
});
|
||||||
@@ -36,41 +46,106 @@ final class ReleaseGuardsTest extends TestCase
|
|||||||
Functions\when('current_user_can')->justReturn(true);
|
Functions\when('current_user_can')->justReturn(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return iterable<string, array{callable(): mixed}> */
|
/** @return iterable<string, array{callable(): mixed, list<DiviFeature>}> */
|
||||||
public static function writes(): iterable
|
public static function writes(): iterable
|
||||||
{
|
{
|
||||||
$post = new WP_Post(['ID' => 7, 'post_content' => '', 'post_type' => 'page']);
|
$post = new WP_Post(['ID' => 7, 'post_content' => '', 'post_type' => 'page']);
|
||||||
$spec = new LibraryItemSpec('Hero', 'section', false, 'regular', null, 'page');
|
$spec = new LibraryItemSpec('Hero', 'section', false, 'regular', null, 'page');
|
||||||
|
$page = [DiviFeature::PageWrite];
|
||||||
|
$tb = [DiviFeature::ThemeBuilderWrite];
|
||||||
|
|
||||||
yield 'save' => [static fn () => (new DiviSaver())->save($post, '<!-- wp:divi/section /-->')];
|
yield 'save' => [static fn () => (new DiviSaver())->save($post, '<!-- wp:divi/section /-->'), $page];
|
||||||
yield 'saveNew' => [static fn () => (new DiviSaver())->saveNew(7, '<!-- wp:divi/section /-->')];
|
yield 'save library item' => [
|
||||||
|
static fn () => (new DiviSaver())->save($post, 'x', SaveScope::LibraryItem),
|
||||||
|
[DiviFeature::PageWrite, DiviFeature::LibraryWrite],
|
||||||
|
];
|
||||||
|
yield 'save global item' => [
|
||||||
|
static fn () => (new DiviSaver())->save($post, 'x', SaveScope::GlobalItem),
|
||||||
|
[DiviFeature::PageWrite, DiviFeature::LibraryWrite],
|
||||||
|
];
|
||||||
|
yield 'save tb layout' => [
|
||||||
|
static fn () => (new DiviSaver())->save($post, 'x', SaveScope::ThemeBuilderLayout),
|
||||||
|
[DiviFeature::PageWrite, DiviFeature::ThemeBuilderWrite],
|
||||||
|
];
|
||||||
|
yield 'saveNew' => [static fn () => (new DiviSaver())->saveNew(7, '<!-- wp:divi/section /-->'), $page];
|
||||||
|
yield 'saveNew library item' => [
|
||||||
|
static fn () => (new DiviSaver())->saveNew(7, 'x', SaveScope::LibraryItem),
|
||||||
|
[DiviFeature::PageWrite, DiviFeature::LibraryWrite],
|
||||||
|
];
|
||||||
|
yield 'pages setFlags' => [static fn () => DiviPages::setFlags(7), $page];
|
||||||
yield 'library create' => [static fn () => (new DiviLibraryStore())->create(
|
yield 'library create' => [static fn () => (new DiviLibraryStore())->create(
|
||||||
$spec,
|
$spec,
|
||||||
new NewDraft(null, FakeChangeLog::forbidden(), 'lit/library-save', 'et_pb_layout', 'Bibliothekseintrag')
|
new NewDraft(null, FakeChangeLog::forbidden(), 'lit/library-save', 'et_pb_layout', 'Bibliothekseintrag')
|
||||||
)];
|
), [DiviFeature::LibraryWrite]];
|
||||||
yield 'tb insertLayout' => [static fn () => (new DiviThemeBuilder())->insertLayout('header', 'Kopf')];
|
yield 'tb liveId create' => [static fn () => (new DiviThemeBuilder())->liveId(true), $tb];
|
||||||
|
yield 'tb insertLayout' => [static fn () => (new DiviThemeBuilder())->insertLayout('header', 'Kopf'), $tb];
|
||||||
yield 'tb storeTemplate' => [static fn () => (new DiviThemeBuilder())->storeTemplate(3, [
|
yield 'tb storeTemplate' => [static fn () => (new DiviThemeBuilder())->storeTemplate(3, [
|
||||||
'title' => 'V', 'layouts' => [], 'use_on' => [], 'exclude_from' => [],
|
'title' => 'V', 'layouts' => [], 'use_on' => [], 'exclude_from' => [],
|
||||||
])];
|
]), $tb];
|
||||||
yield 'tb attach' => [static fn () => (new DiviThemeBuilder())->attach(3, 9)];
|
yield 'tb attach' => [static fn () => (new DiviThemeBuilder())->attach(3, 9), $tb];
|
||||||
yield 'tb detach' => [static fn () => (new DiviThemeBuilder())->detach(3, 9)];
|
yield 'tb detach' => [static fn () => (new DiviThemeBuilder())->detach(3, 9), $tb];
|
||||||
yield 'tb setConditions' => [static fn () => (new DiviThemeBuilder())->setConditions(9, ['singular'], [])];
|
yield 'tb setConditions' => [static fn () => (new DiviThemeBuilder())->setConditions(9, ['singular'], []), $tb];
|
||||||
yield 'tb setEnabled' => [static fn () => (new DiviThemeBuilder())->setEnabled(9, true)];
|
yield 'tb setEnabled' => [static fn () => (new DiviThemeBuilder())->setEnabled(9, true), $tb];
|
||||||
yield 'tb unmarkUnused' => [static fn () => (new DiviThemeBuilder())->unmarkUnused(9)];
|
yield 'tb unmarkUnused' => [static fn () => (new DiviThemeBuilder())->unmarkUnused(9), $tb];
|
||||||
yield 'pages setFlags' => [static fn () => DiviPages::setFlags(7)];
|
yield 'design savePresets' => [
|
||||||
yield 'tb liveId create' => [static fn () => (new DiviThemeBuilder())->liveId(true)];
|
static fn () => (new DesignSystemWriter())->savePresets(['module' => [], 'group' => []], false),
|
||||||
|
[DiviFeature::PresetWrite],
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @dataProvider writes */
|
/**
|
||||||
public function testRefusesBeforeTheFirstWrite(callable $write): void
|
* @dataProvider writes
|
||||||
|
* @param list<DiviFeature> $needs
|
||||||
|
*/
|
||||||
|
public function testRefusesBeforeTheFirstWriteWhenAFeatureItNeedsIsLocked(callable $write, array $needs): void
|
||||||
{
|
{
|
||||||
$this->expectException(DiviLocked::class);
|
foreach ([ReleaseState::Untested, ReleaseState::Incompatible] as $state) {
|
||||||
|
foreach ($needs as $feature) {
|
||||||
|
ReleaseGate::useResolver(static fn (): ReleaseStatus => ReleaseMatrices::status([$feature], $state));
|
||||||
|
|
||||||
|
try {
|
||||||
$write();
|
$write();
|
||||||
|
self::fail(sprintf('%s %s must refuse', $feature->value, $state->value));
|
||||||
|
} catch (DiviLocked $locked) {
|
||||||
|
self::assertSame([$feature], $locked->features);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider writes
|
||||||
|
* @param list<DiviFeature> $needs
|
||||||
|
*/
|
||||||
|
public function testPassesTheGateWhenOnlyOtherFeaturesAreLocked(callable $write, array $needs): void
|
||||||
|
{
|
||||||
|
$others = array_values(array_filter(
|
||||||
|
DiviFeature::cases(),
|
||||||
|
static fn (DiviFeature $feature): bool => ! in_array($feature, $needs, true)
|
||||||
|
));
|
||||||
|
ReleaseGate::useResolver(
|
||||||
|
static fn (): ReleaseStatus => ReleaseMatrices::status($others, ReleaseState::Incompatible)
|
||||||
|
);
|
||||||
|
// Past the gate the method may write or fail on the missing Divi – anything but the lock.
|
||||||
|
foreach (self::WRITES as $name) {
|
||||||
|
Functions\when($name)->alias(static function () use ($name): never {
|
||||||
|
throw new RuntimeException($name . ' reached');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$write();
|
||||||
|
} catch (DiviLocked $locked) {
|
||||||
|
self::fail('refused although only other features are locked: ' . $locked->getMessage());
|
||||||
|
} catch (Throwable) {
|
||||||
|
// Reached the code behind the gate.
|
||||||
|
}
|
||||||
|
self::addToAssertionCount(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testTheCheckPageMayBeWrittenWhileProbing(): void
|
public function testTheCheckPageMayBeWrittenWhileProbing(): void
|
||||||
{
|
{
|
||||||
|
ReleaseGate::useResolver(static fn (): ReleaseStatus => new ReleaseStatus(ReleaseState::Untested, '5.3.0'));
|
||||||
Functions\when('wp_update_post')->justReturn(41);
|
Functions\when('wp_update_post')->justReturn(41);
|
||||||
Functions\when('update_post_meta')->justReturn(true);
|
Functions\when('update_post_meta')->justReturn(true);
|
||||||
Functions\when('get_post_meta')->alias(
|
Functions\when('get_post_meta')->alias(
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace LIT\MCP\Tests\Unit\Integrations\Divi\Release;
|
|||||||
|
|
||||||
use LIT\MCP\Integrations\Divi\Release\CompatibilityMatrix;
|
use LIT\MCP\Integrations\Divi\Release\CompatibilityMatrix;
|
||||||
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\FeatureStatus;
|
||||||
use LIT\MCP\Integrations\Divi\Release\ReleaseState;
|
use LIT\MCP\Integrations\Divi\Release\ReleaseState;
|
||||||
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
|
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
|
||||||
use LIT\MCP\Tests\Support\ReleaseMatrices;
|
use LIT\MCP\Tests\Support\ReleaseMatrices;
|
||||||
@@ -132,4 +133,78 @@ final class ReleaseStatusTest extends TestCase
|
|||||||
|
|
||||||
new ReleaseStatus(ReleaseState::Released, '5.2.1', 'code', [], null, CompatibilityMatrix::allIncompatible('x'));
|
new ReleaseStatus(ReleaseState::Released, '5.2.1', 'code', [], null, CompatibilityMatrix::allIncompatible('x'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testRefusalForAnUntestedFeatureNamesItAndKeepsTheOthersWorking(): void
|
||||||
|
{
|
||||||
|
$error = ReleaseMatrices::status([DiviFeature::PageWrite])
|
||||||
|
->refusalFor([DiviFeature::PageWrite, DiviFeature::PageRead]);
|
||||||
|
$payload = self::assertToolError($error, 'divi_untested');
|
||||||
|
|
||||||
|
self::assertSame(423, $error->get_error_data()['status']);
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
'Achtung: Divi 5.3.0 ist auf dieser Website für „Seiten schreiben“ noch nicht für LIT MCP freigegeben. '
|
||||||
|
. 'Das ist gesperrt, bis ein Administrator unter Einstellungen → LIT MCP → Divi die Prüfung ausführt '
|
||||||
|
. 'und die Version freigibt. Es wurde nichts geändert.',
|
||||||
|
$payload['message']
|
||||||
|
);
|
||||||
|
self::assertSame(
|
||||||
|
'Gesperrt: „Seiten schreiben“. Andere Divi-Werkzeuge funktionieren weiter, sofern ihre Fähigkeiten '
|
||||||
|
. 'freigegeben sind. Den Nutzer bitten, die Divi-Prüfung auszuführen und die Version freizugeben.',
|
||||||
|
$payload['hint']
|
||||||
|
);
|
||||||
|
self::assertSame(
|
||||||
|
[['id' => 'page-write', 'label' => 'Seiten schreiben', 'state' => 'untested', 'problems' => []]],
|
||||||
|
$payload['features']
|
||||||
|
);
|
||||||
|
self::assertArrayNotHasKey('problems', $payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRefusalForTakesTheWorstStateAndListsEveryLockedFeature(): void
|
||||||
|
{
|
||||||
|
$matrix = ReleaseMatrices::matrix([DiviFeature::ThemeBuilderWrite], ReleaseState::Incompatible);
|
||||||
|
$features = [];
|
||||||
|
foreach (DiviFeature::cases() as $feature) {
|
||||||
|
$features[$feature->value] = $matrix->of($feature);
|
||||||
|
}
|
||||||
|
$features['library-write'] = new FeatureStatus(DiviFeature::LibraryWrite, ReleaseState::Untested);
|
||||||
|
$status = ReleaseStatus::of('5.99.0', new CompatibilityMatrix($features));
|
||||||
|
|
||||||
|
$payload = self::assertToolError(
|
||||||
|
$status->refusalFor([DiviFeature::PageWrite, DiviFeature::LibraryWrite, DiviFeature::ThemeBuilderWrite]),
|
||||||
|
'divi_incompatible'
|
||||||
|
);
|
||||||
|
|
||||||
|
self::assertSame(
|
||||||
|
'Divi 5.99.0 hat Stellen geändert, die LIT MCP für „Theme Builder schreiben“ nutzt. Das ist gesperrt, bis '
|
||||||
|
. 'LIT MCP aktualisiert wurde. Es wurde nichts geändert.',
|
||||||
|
$payload['message']
|
||||||
|
);
|
||||||
|
self::assertStringStartsWith(
|
||||||
|
'Gesperrt: „Bibliothek schreiben“ und „Theme Builder schreiben“. Andere Divi-Werkzeuge',
|
||||||
|
$payload['hint']
|
||||||
|
);
|
||||||
|
self::assertStringEndsWith('dass LIT MCP aktualisiert werden muss.', $payload['hint']);
|
||||||
|
self::assertSame(['library-write', 'tb-write'], array_column($payload['features'], 'id'));
|
||||||
|
self::assertSame(['untested', 'incompatible'], array_column($payload['features'], 'state'));
|
||||||
|
self::assertSame(['Seiten: et_x() – Signatur geändert'], $payload['problems']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testRefusalForNamesThreeFeatures(): void
|
||||||
|
{
|
||||||
|
$locked = [DiviFeature::PageWrite, DiviFeature::LibraryWrite, DiviFeature::PresetWrite];
|
||||||
|
$payload = self::assertToolError(ReleaseMatrices::status($locked)->refusalFor($locked), 'divi_untested');
|
||||||
|
|
||||||
|
self::assertStringStartsWith(
|
||||||
|
'Gesperrt: „Seiten schreiben“, „Bibliothek schreiben“ und „Presets schreiben“.',
|
||||||
|
$payload['hint']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUsableFeaturesHaveNoRefusal(): void
|
||||||
|
{
|
||||||
|
$this->expectException(LogicException::class);
|
||||||
|
|
||||||
|
ReleaseMatrices::status([DiviFeature::Catalog])->refusalFor([DiviFeature::PageWrite, DiviFeature::PageRead]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace LIT\MCP\Tests\Unit;
|
|||||||
|
|
||||||
use Brain\Monkey;
|
use Brain\Monkey;
|
||||||
use Brain\Monkey\Functions;
|
use Brain\Monkey\Functions;
|
||||||
|
use LIT\MCP\Integrations\Divi\Release\CompatibilityMatrix;
|
||||||
use LIT\MCP\Integrations\Divi\Release\ReleaseGate;
|
use LIT\MCP\Integrations\Divi\Release\ReleaseGate;
|
||||||
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
|
use LIT\MCP\Integrations\Divi\Release\ReleaseStatus;
|
||||||
use LIT\MCP\Tests\Support\AssertsToolErrors;
|
use LIT\MCP\Tests\Support\AssertsToolErrors;
|
||||||
@@ -35,8 +36,10 @@ abstract class TestCase extends BaseTestCase
|
|||||||
'sanitize_text_field' => static fn ($value): string => trim(strip_tags((string) $value)),
|
'sanitize_text_field' => static fn ($value): string => trim(strip_tags((string) $value)),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Divi is released unless a test says otherwise (the release gate would ask WordPress and Divi).
|
// Every Divi feature is released unless a test says otherwise (the gate would ask WordPress and Divi).
|
||||||
ReleaseGate::useResolver(static fn (): ReleaseStatus => ReleaseStatus::released('5.2.1'));
|
ReleaseGate::useResolver(
|
||||||
|
static fn (): ReleaseStatus => ReleaseStatus::of('5.2.1', CompatibilityMatrix::allReleased())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDown(): void
|
protected function tearDown(): void
|
||||||
|
|||||||
Reference in New Issue
Block a user