feat: lit/divi-presets-assign – Presets site-weit zuweisen mit Probelauf und höchstens 50 Seiten je Aufruf
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
65a207488e
commit
d4f235ae5e
@@ -0,0 +1,332 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Integrations\Divi\Design;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use LIT\MCP\Abilities\Ability;
|
||||||
|
use LIT\MCP\Abilities\Annotations;
|
||||||
|
use LIT\MCP\Abilities\Errors;
|
||||||
|
use LIT\MCP\Abilities\ToolResult;
|
||||||
|
use WP_Error;
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* lit/divi-presets-assign (spec §6.7, G2, A6): assigns a module preset stack and/or group presets to every module of
|
||||||
|
* one type – on all posts that contain it or only on post_ids. Every value passes PresetAssignment (choke point D7),
|
||||||
|
* AssignRewrite changes the blocks and the PageRewriter writes: without confirm a dry run, with confirm at most
|
||||||
|
* PageRewriter::PAGE_LIMIT posts, each with a revision; "remaining" counts the rest. The filters skip modules that
|
||||||
|
* already carry the change, so the same call continues where the last one stopped. An empty dry run is a normal
|
||||||
|
* result ("nichts zu tun"), not confirm_required: there is nothing to confirm, and a repeated call ends cleanly.
|
||||||
|
* null removes nothing here – lit/divi-design-detach does.
|
||||||
|
*/
|
||||||
|
final class DiviPresetsAssignAbility implements Ability
|
||||||
|
{
|
||||||
|
public const MAX_STACK = 10;
|
||||||
|
|
||||||
|
/** Path prefix for PresetAssignment's errors; removed again so fields name the input keys. */
|
||||||
|
private const ROOT = 'input';
|
||||||
|
|
||||||
|
private const USE_DETACH = 'null entfernt site-weit nichts; Preset-Verweise entfernt lit/divi-design-detach';
|
||||||
|
|
||||||
|
/** @param Closure(): PresetAssignment $assignment A fresh assignment (own preset book) per call. */
|
||||||
|
public function __construct(
|
||||||
|
private DesignSystemWriter $writer,
|
||||||
|
private PageRewriter $pages,
|
||||||
|
private Closure $assignment
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function name(): string
|
||||||
|
{
|
||||||
|
return 'lit/divi-presets-assign';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function label(): string
|
||||||
|
{
|
||||||
|
return 'Divi-Presets site-weit zuweisen';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function description(): string
|
||||||
|
{
|
||||||
|
return 'Weist allen Modulen eines Typs (module, z. B. "button") site-weit ein Modul-Preset (preset: Name oder '
|
||||||
|
. 'ID oder Stapel als Liste; bei gleicher Priorität gewinnt das letzte Preset im Stapel (Divi-Verhalten)) '
|
||||||
|
. 'und/oder Gruppen-Presets (group_presets) zu. Filter: post_ids, only_without_preset oder '
|
||||||
|
. 'only_with_preset; clear_overrides: true entfernt lokale Werte, die das Preset überdecken. Zuerst '
|
||||||
|
. 'Probelauf (betroffene Seiten und Module), nach Rückfrage beim Nutzer confirm: true; je Aufruf höchstens '
|
||||||
|
. '50 Seiten, jede mit Revision; remaining nennt den Rest. Mit post_ids können nur die genannten Seiten '
|
||||||
|
. 'das Schreiben blockieren, die Obergrenze für sehr viele Seiten gilt dann nicht, und eine genannte '
|
||||||
|
. 'Seite ohne Bearbeitungsrecht steht in skipped. Verweise entfernen: lit/divi-design-detach.';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function inputSchema(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'type' => 'object',
|
||||||
|
'properties' => [
|
||||||
|
'module' => ['type' => 'string', 'description' => 'Modul, z. B. "button" oder "divi/button".'],
|
||||||
|
'preset' => [
|
||||||
|
'type' => ['string', 'array'],
|
||||||
|
'items' => ['type' => 'string'],
|
||||||
|
'minItems' => 1,
|
||||||
|
'maxItems' => self::MAX_STACK,
|
||||||
|
'description' => 'Modul-Preset (Name oder ID) oder Stapel; bei gleicher Priorität gewinnt das '
|
||||||
|
. 'letzte Preset im Stapel (Divi-Verhalten).',
|
||||||
|
],
|
||||||
|
'group_presets' => [
|
||||||
|
'type' => 'object',
|
||||||
|
'description' => '{"<Gruppe>": "<Name>" | ["<Name>", …]}; Gruppe als Kurzname (font-body), voller '
|
||||||
|
. 'Name (divi/font-body) oder Attributpfad.',
|
||||||
|
],
|
||||||
|
'post_ids' => PostIdsInput::schema(),
|
||||||
|
'only_without_preset' => [
|
||||||
|
'type' => 'boolean',
|
||||||
|
'description' => 'Nur Module ohne eigenes Modul-Preset.',
|
||||||
|
],
|
||||||
|
'only_with_preset' => [
|
||||||
|
'type' => 'string',
|
||||||
|
'description' => 'Nur Module mit diesem Modul-Preset (Name oder ID); nicht zusammen mit '
|
||||||
|
. 'only_without_preset.',
|
||||||
|
],
|
||||||
|
'clear_overrides' => [
|
||||||
|
'type' => 'boolean',
|
||||||
|
'description' => 'Lokale Werte entfernen, die die zugewiesenen Presets überdecken.',
|
||||||
|
],
|
||||||
|
] + array_intersect_key(DesignChange::inputProperties(), ['confirm' => true]),
|
||||||
|
'required' => ['module'],
|
||||||
|
'additionalProperties' => false,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function outputSchema(): ?array
|
||||||
|
{
|
||||||
|
return PageRewriteReport::outputSchema();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function annotations(): array
|
||||||
|
{
|
||||||
|
return Annotations::write(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isDirect(): bool
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function permission(array $input): bool|WP_Error
|
||||||
|
{
|
||||||
|
return DesignRights::presets($this->writer) ?? true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execute(array $input): mixed
|
||||||
|
{
|
||||||
|
$refusal = DesignRights::presets($this->writer);
|
||||||
|
if ($refusal !== null) {
|
||||||
|
return $refusal;
|
||||||
|
}
|
||||||
|
$request = $this->request($input);
|
||||||
|
if ($request instanceof WP_Error) {
|
||||||
|
return $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = new ModuleUsageQuery($request['module']);
|
||||||
|
$change = 'Zuweisung an ' . $request['module'];
|
||||||
|
if (($input['confirm'] ?? false) !== true) {
|
||||||
|
return $this->dryRun($query, $request, $change);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $this->pages->apply($this->name(), $query, $request['rewrite'], $request['post_ids'], $change);
|
||||||
|
if ($result instanceof WP_Error) {
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ToolResult::ok(PageRewriteReport::savedSummary($result, $change))
|
||||||
|
->warnings($result['warnings'])
|
||||||
|
->with('saved', $result['saved'])
|
||||||
|
->with('remaining', $result['remaining'])
|
||||||
|
->with('skipped', $result['skipped'])
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array{module: string, rewrite: Closure(): NodeRewrite, post_ids: list<int>|null} $request
|
||||||
|
* @return array<string, mixed>|WP_Error
|
||||||
|
*/
|
||||||
|
private function dryRun(ModuleUsageQuery $query, array $request, string $change): array|WP_Error
|
||||||
|
{
|
||||||
|
$preview = $this->pages->preview($query, $request['rewrite'], $request['post_ids']);
|
||||||
|
if ($preview instanceof WP_Error) {
|
||||||
|
return $preview;
|
||||||
|
}
|
||||||
|
if (PageRewriteReport::isEmpty($preview)) {
|
||||||
|
return PageRewriteReport::nothingToDo($change . ' – keine Seite betroffen, nichts zu tun.', $preview);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Errors::confirmRequired(PageRewriteReport::dryRunMessage($preview, $change), $preview);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The checked call: full module name, the rewrite strategy per page and the post filter.
|
||||||
|
*
|
||||||
|
* @param array<string, mixed> $input
|
||||||
|
* @return array{module: string, rewrite: Closure(): NodeRewrite, post_ids: list<int>|null}|WP_Error
|
||||||
|
*/
|
||||||
|
private function request(array $input): array|WP_Error
|
||||||
|
{
|
||||||
|
$errors = [];
|
||||||
|
$module = self::module($input['module'] ?? null, $errors);
|
||||||
|
$spec = self::spec($input, $errors);
|
||||||
|
$postIds = self::postIds($input['post_ids'] ?? null, $errors);
|
||||||
|
$onlyWithout = self::flag($input, 'only_without_preset', $errors);
|
||||||
|
$clear = self::flag($input, 'clear_overrides', $errors);
|
||||||
|
if ($module === null || $errors !== []) {
|
||||||
|
return self::invalid($errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
$assignment = ($this->assignment)();
|
||||||
|
foreach ($assignment->apply($module, [], $spec, self::ROOT)['errors'] as $path => $problem) {
|
||||||
|
$errors[self::field($path)] = $problem;
|
||||||
|
}
|
||||||
|
$onlyWith = $this->onlyWith($module, $input, $onlyWithout, $errors);
|
||||||
|
if ($errors !== []) {
|
||||||
|
return self::invalid($errors);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'module' => $module,
|
||||||
|
'post_ids' => $postIds,
|
||||||
|
'rewrite' => static fn (): NodeRewrite => new AssignRewrite(
|
||||||
|
$module,
|
||||||
|
$assignment,
|
||||||
|
$spec,
|
||||||
|
$onlyWithout,
|
||||||
|
$onlyWith,
|
||||||
|
$clear
|
||||||
|
),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param array<string, string> $errors */
|
||||||
|
private static function module(mixed $value, array &$errors): ?string
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return PresetScope::fromInput([PresetScope::MODULE => $value], 'module')->name;
|
||||||
|
} catch (InvalidArgumentException) {
|
||||||
|
$errors['module'] = 'Modulname wie "button" oder "divi/button" erwartet';
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* preset and group_presets as given; null removes nothing site-wide (lit/divi-design-detach does).
|
||||||
|
*
|
||||||
|
* @param array<string, mixed> $input
|
||||||
|
* @param array<string, string> $errors
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
private static function spec(array $input, array &$errors): array
|
||||||
|
{
|
||||||
|
$spec = array_intersect_key($input, ['preset' => true, 'group_presets' => true]);
|
||||||
|
if ($spec === []) {
|
||||||
|
$errors['preset'] = 'preset oder group_presets angeben';
|
||||||
|
}
|
||||||
|
if (array_key_exists('preset', $spec) && $spec['preset'] === null) {
|
||||||
|
$errors['preset'] = self::USE_DETACH;
|
||||||
|
}
|
||||||
|
$groups = $spec['group_presets'] ?? null;
|
||||||
|
if ($groups === []) {
|
||||||
|
$errors['group_presets'] = 'mindestens eine Gruppe angeben';
|
||||||
|
}
|
||||||
|
foreach (is_array($groups) ? $groups : [] as $key => $value) {
|
||||||
|
if ($value === null) {
|
||||||
|
$errors['group_presets.' . $key] = self::USE_DETACH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $spec;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, string> $errors
|
||||||
|
* @return list<int>|null
|
||||||
|
*/
|
||||||
|
private static function postIds(mixed $value, array &$errors): ?array
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return PostIdsInput::parse($value);
|
||||||
|
} catch (InvalidArgumentException $e) {
|
||||||
|
$errors['post_ids'] = $e->getMessage();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $input
|
||||||
|
* @param array<string, string> $errors
|
||||||
|
*/
|
||||||
|
private static function flag(array $input, string $key, array &$errors): bool
|
||||||
|
{
|
||||||
|
$value = $input[$key] ?? false;
|
||||||
|
if (! is_bool($value)) {
|
||||||
|
$errors[$key] = 'true oder false erwartet';
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The ID of the module preset only_with_preset names; null without that filter or after noting a problem.
|
||||||
|
*
|
||||||
|
* @param array<string, mixed> $input
|
||||||
|
* @param array<string, string> $errors
|
||||||
|
*/
|
||||||
|
private function onlyWith(string $module, array $input, bool $onlyWithout, array &$errors): ?string
|
||||||
|
{
|
||||||
|
$value = $input['only_with_preset'] ?? null;
|
||||||
|
if ($value === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if ($onlyWithout) {
|
||||||
|
$errors['only_with_preset'] = 'nicht zusammen mit only_without_preset';
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$scope = new PresetScope(PresetScope::MODULE, $module);
|
||||||
|
$name = is_string($value) ? $value : '';
|
||||||
|
try {
|
||||||
|
$found = PresetBook::fromDivi($this->writer->presets())->find($scope, $name);
|
||||||
|
} catch (InvalidArgumentException $e) {
|
||||||
|
$errors['only_with_preset'] = $e->getMessage();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if ($found === null) {
|
||||||
|
$errors['only_with_preset'] = sprintf('unbekanntes %s „%s“', $scope->label(), $name);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (string) $found['id'];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function field(string $path): string
|
||||||
|
{
|
||||||
|
$prefix = self::ROOT . '.';
|
||||||
|
|
||||||
|
return str_starts_with($path, $prefix) ? substr($path, strlen($prefix)) : $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param array<string, string> $errors */
|
||||||
|
private static function invalid(array $errors): WP_Error
|
||||||
|
{
|
||||||
|
return Errors::invalidInput('Ungültige Angaben; nichts wurde geändert.', $errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Integrations\Divi\Design;
|
||||||
|
|
||||||
|
use LIT\MCP\Abilities\ToolResult;
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The words and the output schema around the PageRewriter (spec §6.6, A6), shared by lit/divi-presets-assign and
|
||||||
|
* lit/divi-design-detach: when a dry run has nothing to do, the dry run message with the next step, the summary of a
|
||||||
|
* confirmed call with "remaining". $preview is a PageRewriter::preview() result, $result an apply() result. Pure PHP.
|
||||||
|
*/
|
||||||
|
final class PageRewriteReport
|
||||||
|
{
|
||||||
|
/** @param array<string, mixed> $preview */
|
||||||
|
public static function isEmpty(array $preview): bool
|
||||||
|
{
|
||||||
|
return $preview['pages_count'] === 0 && $preview['blocked_ids'] === [] && $preview['hidden_count'] === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param array<string, mixed> $preview */
|
||||||
|
public static function dryRunMessage(array $preview, string $change): string
|
||||||
|
{
|
||||||
|
$message = sprintf(
|
||||||
|
'%s – betroffen (Seiten: %d, Module: %d). Nichts wurde gespeichert (Probelauf). Nach Rückfrage beim '
|
||||||
|
. 'Nutzer mit confirm: true erneut aufrufen; je Aufruf werden höchstens %d Seiten geschrieben.',
|
||||||
|
$change,
|
||||||
|
$preview['pages_count'],
|
||||||
|
$preview['nodes_count'],
|
||||||
|
PageRewriter::PAGE_LIMIT
|
||||||
|
);
|
||||||
|
if ($preview['blocked_ids'] !== []) {
|
||||||
|
$message .= ' Seiten unter blocked_ids darf dieses Konto nicht bearbeiten; mit confirm würde nichts '
|
||||||
|
. 'gespeichert.';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $message;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param array<string, mixed> $result */
|
||||||
|
public static function savedSummary(array $result, string $change): string
|
||||||
|
{
|
||||||
|
$summary = sprintf(
|
||||||
|
'%s – gespeichert (Seiten: %d, Module: %d).',
|
||||||
|
$change,
|
||||||
|
count($result['saved']),
|
||||||
|
array_sum(array_column($result['saved'], 'nodes'))
|
||||||
|
);
|
||||||
|
if ($result['remaining'] > 0) {
|
||||||
|
return $summary . sprintf(
|
||||||
|
' Noch %d Seiten offen: mit denselben Angaben erneut mit confirm: true aufrufen.',
|
||||||
|
$result['remaining']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if ($result['skipped'] !== []) {
|
||||||
|
return $summary . sprintf(' Übersprungen: %d (siehe skipped).', count($result['skipped']));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $summary . ' Alle Fundstellen sind erledigt.';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, mixed> $preview
|
||||||
|
* @param list<string> $warnings
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public static function nothingToDo(string $summary, array $preview, array $warnings = []): array
|
||||||
|
{
|
||||||
|
return ToolResult::ok($summary)
|
||||||
|
->warnings($warnings)
|
||||||
|
->with('saved', [])
|
||||||
|
->with('remaining', 0)
|
||||||
|
->with('skipped', $preview['skipped'])
|
||||||
|
->toArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, array<string, mixed>> $extra Further optional output properties.
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public static function outputSchema(array $extra = []): array
|
||||||
|
{
|
||||||
|
return ToolResult::schema([
|
||||||
|
'saved' => [
|
||||||
|
'type' => 'array',
|
||||||
|
'items' => [
|
||||||
|
'type' => 'object',
|
||||||
|
'properties' => [
|
||||||
|
'post_id' => ['type' => 'integer'],
|
||||||
|
'revision_id' => ['type' => ['integer', 'null']],
|
||||||
|
'nodes' => ['type' => 'integer'],
|
||||||
|
],
|
||||||
|
'required' => ['post_id', 'revision_id', 'nodes'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'remaining' => ['type' => 'integer'],
|
||||||
|
'skipped' => [
|
||||||
|
'type' => 'array',
|
||||||
|
'items' => [
|
||||||
|
'type' => 'object',
|
||||||
|
'properties' => ['post_id' => ['type' => 'integer'], 'reason' => ['type' => 'string']],
|
||||||
|
'required' => ['post_id', 'reason'],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
] + $extra, ['saved', 'remaining', 'skipped']);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Integrations\Divi\Design;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
defined('ABSPATH') || exit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The post filter "post_ids" of the site-wide preset tools (lit/divi-presets-assign, lit/divi-design-detach):
|
||||||
|
* 1 to PageRewriter::PAGE_LIMIT positive post IDs – as many as one confirmed call writes. Pure PHP.
|
||||||
|
*/
|
||||||
|
final class PostIdsInput
|
||||||
|
{
|
||||||
|
public const MAX = PageRewriter::PAGE_LIMIT;
|
||||||
|
|
||||||
|
/** @return array<string, mixed> */
|
||||||
|
public static function schema(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'type' => 'array',
|
||||||
|
'items' => ['type' => 'integer', 'minimum' => 1],
|
||||||
|
'minItems' => 1,
|
||||||
|
'maxItems' => self::MAX,
|
||||||
|
'description' => 'Nur diese Beiträge (IDs); ohne Angabe alle Fundstellen.',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<int>|null null without a filter; duplicates removed.
|
||||||
|
* @throws InvalidArgumentException For anything but a list of 1 to MAX positive integers.
|
||||||
|
*/
|
||||||
|
public static function parse(mixed $value): ?array
|
||||||
|
{
|
||||||
|
if ($value === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (! is_array($value) || ! array_is_list($value) || $value === [] || count($value) > self::MAX) {
|
||||||
|
throw self::invalid();
|
||||||
|
}
|
||||||
|
foreach ($value as $id) {
|
||||||
|
if (! is_int($id) || $id < 1) {
|
||||||
|
throw self::invalid();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_values(array_unique($value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function invalid(): InvalidArgumentException
|
||||||
|
{
|
||||||
|
return new InvalidArgumentException(sprintf(
|
||||||
|
'Liste von 1 bis %d Beitrags-IDs (positive ganze Zahlen) erwartet',
|
||||||
|
self::MAX
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,9 +15,11 @@ use LIT\MCP\Integrations\Divi\Design\DesignRights;
|
|||||||
use LIT\MCP\Integrations\Divi\Design\DiviPresetGetAbility;
|
use LIT\MCP\Integrations\Divi\Design\DiviPresetGetAbility;
|
||||||
use LIT\MCP\Integrations\Divi\Design\DiviDesignRestoreAbility;
|
use LIT\MCP\Integrations\Divi\Design\DiviDesignRestoreAbility;
|
||||||
use LIT\MCP\Integrations\Divi\Design\DiviDesignSnapshotsAbility;
|
use LIT\MCP\Integrations\Divi\Design\DiviDesignSnapshotsAbility;
|
||||||
|
use LIT\MCP\Integrations\Divi\Design\DiviPresetsAssignAbility;
|
||||||
use LIT\MCP\Integrations\Divi\Design\DiviPresetsDeleteAbility;
|
use LIT\MCP\Integrations\Divi\Design\DiviPresetsDeleteAbility;
|
||||||
use LIT\MCP\Integrations\Divi\Design\DiviPresetsSetAbility;
|
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\PresetComposer;
|
use LIT\MCP\Integrations\Divi\Design\PresetComposer;
|
||||||
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;
|
||||||
@@ -185,6 +187,11 @@ final class DiviIntegration implements Integration
|
|||||||
new SnapshotStore(),
|
new SnapshotStore(),
|
||||||
DesignRights::unfilteredHtml()
|
DesignRights::unfilteredHtml()
|
||||||
),
|
),
|
||||||
|
new DiviPresetsAssignAbility(
|
||||||
|
$this->designWriter,
|
||||||
|
$this->pageRewriter($loader),
|
||||||
|
$this->presetAssignment(...)
|
||||||
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -284,6 +291,12 @@ final class DiviIntegration implements Integration
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** A fresh PresetAssignment (own preset book) per lit/divi-presets-assign call, wired like the Composer's (D7). */
|
||||||
|
private function presetAssignment(): PresetAssignment
|
||||||
|
{
|
||||||
|
return $this->composition()->presets();
|
||||||
|
}
|
||||||
|
|
||||||
private function loader(): DiviPageLoader
|
private function loader(): DiviPageLoader
|
||||||
{
|
{
|
||||||
return new DiviPageLoader($this->schema, new Parser(), $this->access, $this->themeBuilderAccess);
|
return new DiviPageLoader($this->schema, new Parser(), $this->access, $this->themeBuilderAccess);
|
||||||
|
|||||||
@@ -0,0 +1,220 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Design;
|
||||||
|
|
||||||
|
use Brain\Monkey\Functions;
|
||||||
|
use Closure;
|
||||||
|
use LIT\MCP\Integrations\Divi\Design\AssignRewrite;
|
||||||
|
use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter;
|
||||||
|
use LIT\MCP\Integrations\Divi\Design\DiviPresetsAssignAbility;
|
||||||
|
use LIT\MCP\Integrations\Divi\Design\ModuleUsageQuery;
|
||||||
|
use LIT\MCP\Integrations\Divi\Design\PageRewriter;
|
||||||
|
use LIT\MCP\Integrations\Divi\Design\PresetAssignment;
|
||||||
|
use LIT\MCP\Integrations\Divi\Design\PresetBook;
|
||||||
|
use LIT\MCP\Tests\Unit\TestCase;
|
||||||
|
use Mockery;
|
||||||
|
use Mockery\MockInterface;
|
||||||
|
use ReflectionProperty;
|
||||||
|
|
||||||
|
final class DiviPresetsAssignAbilityTest extends TestCase
|
||||||
|
{
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
Functions\when('current_user_can')->justReturn(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function ability(PageRewriter $pages): DiviPresetsAssignAbility
|
||||||
|
{
|
||||||
|
$writer = Mockery::mock(DesignSystemWriter::class);
|
||||||
|
$writer->allows('presets')->andReturn(PresetBookTest::stored());
|
||||||
|
$writer->allows('canUseVisualBuilder')->andReturn(true);
|
||||||
|
|
||||||
|
return new DiviPresetsAssignAbility(
|
||||||
|
$writer,
|
||||||
|
$pages,
|
||||||
|
static fn (): PresetAssignment => new PresetAssignment(
|
||||||
|
static fn (): PresetBook => PresetBook::fromDivi(PresetBookTest::stored()),
|
||||||
|
static fn (string $module): array => $module === 'divi/text'
|
||||||
|
? ['content.decoration.bodyFont' => 'divi/font-body']
|
||||||
|
: [],
|
||||||
|
static fn (string $id): bool => preg_match('/^[0-9a-z]{10}$/', $id) === 1
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A PageRewriter that must not be asked at all. */
|
||||||
|
private static function untouched(): PageRewriter&MockInterface
|
||||||
|
{
|
||||||
|
$pages = Mockery::mock(PageRewriter::class);
|
||||||
|
$pages->expects('preview')->never();
|
||||||
|
$pages->expects('apply')->never();
|
||||||
|
|
||||||
|
return $pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function moduleOf(ModuleUsageQuery $query): string
|
||||||
|
{
|
||||||
|
return (new ReflectionProperty(ModuleUsageQuery::class, 'module'))->getValue($query);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function onlyWithOf(AssignRewrite $rewrite): ?string
|
||||||
|
{
|
||||||
|
return (new ReflectionProperty(AssignRewrite::class, 'onlyWithPresetId'))->getValue($rewrite);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNeedsEditThemeOptionsAndTheVisualBuilder(): void
|
||||||
|
{
|
||||||
|
$ability = $this->ability(self::untouched());
|
||||||
|
Functions\when('current_user_can')->justReturn(false);
|
||||||
|
$input = ['module' => 'button', 'preset' => 'CTA primär'];
|
||||||
|
|
||||||
|
self::assertToolError($ability->permission($input), 'forbidden');
|
||||||
|
self::assertToolError($ability->execute($input), 'forbidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUnknownPresetIsRefusedBeforeAnyPageIsRead(): void
|
||||||
|
{
|
||||||
|
$result = $this->ability(self::untouched())->execute(['module' => 'button', 'preset' => 'Fehlt']);
|
||||||
|
|
||||||
|
self::assertToolError($result, 'validation_failed');
|
||||||
|
self::assertArrayHasKey('preset', self::errorFields($result));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOnlyWithAndOnlyWithoutPresetExcludeEachOther(): void
|
||||||
|
{
|
||||||
|
$result = $this->ability(self::untouched())->execute([
|
||||||
|
'module' => 'button',
|
||||||
|
'preset' => 'CTA primär',
|
||||||
|
'only_without_preset' => true,
|
||||||
|
'only_with_preset' => 'aaaaaaaaaa',
|
||||||
|
]);
|
||||||
|
|
||||||
|
self::assertToolError($result, 'validation_failed');
|
||||||
|
self::assertSame(
|
||||||
|
['only_with_preset' => 'nicht zusammen mit only_without_preset'],
|
||||||
|
self::errorFields($result)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNullAndMissingAssignmentPointToDetach(): void
|
||||||
|
{
|
||||||
|
$ability = $this->ability(self::untouched());
|
||||||
|
|
||||||
|
$null = $ability->execute(['module' => 'button', 'preset' => null]);
|
||||||
|
self::assertToolError($null, 'validation_failed');
|
||||||
|
self::assertStringContainsString('lit/divi-design-detach', self::errorFields($null)['preset']);
|
||||||
|
|
||||||
|
$groupNull = $ability->execute(['module' => 'text', 'group_presets' => ['font-body' => null]]);
|
||||||
|
self::assertStringContainsString(
|
||||||
|
'lit/divi-design-detach',
|
||||||
|
self::errorFields($groupNull)['group_presets.font-body']
|
||||||
|
);
|
||||||
|
|
||||||
|
$missing = $ability->execute(['module' => 'button']);
|
||||||
|
self::assertSame('preset oder group_presets angeben', self::errorFields($missing)['preset']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testBadModuleFiltersAndPostIdsAreCollected(): void
|
||||||
|
{
|
||||||
|
$ability = $this->ability(self::untouched());
|
||||||
|
|
||||||
|
$fields = self::errorFields($ability->execute([
|
||||||
|
'module' => 'Button!',
|
||||||
|
'preset' => 'CTA primär',
|
||||||
|
'post_ids' => [0],
|
||||||
|
'clear_overrides' => 'ja',
|
||||||
|
]));
|
||||||
|
self::assertSame(['module', 'post_ids', 'clear_overrides'], array_keys($fields));
|
||||||
|
|
||||||
|
$unknown = self::errorFields($ability->execute([
|
||||||
|
'module' => 'button',
|
||||||
|
'preset' => 'CTA primär',
|
||||||
|
'only_with_preset' => 'Fehlt',
|
||||||
|
]));
|
||||||
|
self::assertSame('unbekanntes Modul-Preset divi/button „Fehlt“', $unknown['only_with_preset']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDryRunListsThePagesAndWritesNothing(): void
|
||||||
|
{
|
||||||
|
$pages = Mockery::mock(PageRewriter::class);
|
||||||
|
$pages->expects('preview')
|
||||||
|
->with(
|
||||||
|
Mockery::on(static fn (ModuleUsageQuery $query): bool => self::moduleOf($query) === 'divi/button'),
|
||||||
|
Mockery::on(static fn (Closure $rewrite): bool => $rewrite() instanceof AssignRewrite),
|
||||||
|
null
|
||||||
|
)
|
||||||
|
->andReturn(PageRewriteReportTest::preview(2));
|
||||||
|
$pages->expects('apply')->never();
|
||||||
|
|
||||||
|
$dry = self::assertToolError(
|
||||||
|
$this->ability($pages)->execute(['module' => 'button', 'preset' => ['CTA primär', 'cccccccccc']]),
|
||||||
|
'confirm_required'
|
||||||
|
);
|
||||||
|
|
||||||
|
self::assertSame([12, 13], array_column($dry['pages'], 'post_id'));
|
||||||
|
self::assertSame(2, $dry['pages_count']);
|
||||||
|
self::assertStringStartsWith('Zuweisung an divi/button – betroffen (Seiten: 2, Module: 2).', $dry['message']);
|
||||||
|
self::assertStringContainsString('confirm: true', $dry['message']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEmptyDryRunIsNothingToDo(): void
|
||||||
|
{
|
||||||
|
$pages = Mockery::mock(PageRewriter::class);
|
||||||
|
$pages->expects('preview')->andReturn(PageRewriteReportTest::preview(0));
|
||||||
|
$pages->expects('apply')->never();
|
||||||
|
|
||||||
|
$result = $this->ability($pages)->execute(['module' => 'button', 'preset' => 'CTA primär']);
|
||||||
|
|
||||||
|
self::assertTrue($result['ok']);
|
||||||
|
self::assertSame('Zuweisung an divi/button – keine Seite betroffen, nichts zu tun.', $result['summary']);
|
||||||
|
self::assertSame([[], 0], [$result['saved'], $result['remaining']]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testConfirmWritesAndReportsWhatRemains(): void
|
||||||
|
{
|
||||||
|
$pages = Mockery::mock(PageRewriter::class);
|
||||||
|
$pages->expects('preview')->never();
|
||||||
|
$pages->expects('apply')
|
||||||
|
->with(
|
||||||
|
'lit/divi-presets-assign',
|
||||||
|
Mockery::on(static fn (ModuleUsageQuery $query): bool => self::moduleOf($query) === 'divi/button'),
|
||||||
|
Mockery::on(static fn (Closure $rewrite): bool => self::onlyWithOf($rewrite()) === 'aaaaaaaaaa'),
|
||||||
|
[12, 13],
|
||||||
|
'Zuweisung an divi/button'
|
||||||
|
)
|
||||||
|
->andReturn(PageRewriteReportTest::applied(2, 3));
|
||||||
|
|
||||||
|
$result = $this->ability($pages)->execute([
|
||||||
|
'module' => 'divi/button',
|
||||||
|
'preset' => ['bbbbbbbbbb', 'cccccccccc'],
|
||||||
|
'only_with_preset' => 'CTA primär',
|
||||||
|
'post_ids' => [12, 13],
|
||||||
|
'confirm' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
|
self::assertTrue($result['ok']);
|
||||||
|
self::assertSame(3, $result['remaining']);
|
||||||
|
self::assertSame([900, 901], array_column($result['saved'], 'revision_id'));
|
||||||
|
self::assertStringContainsString('Noch 3 Seiten offen', $result['summary']);
|
||||||
|
self::assertStringContainsString('confirm: true', $result['summary']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSchemaAndMeta(): void
|
||||||
|
{
|
||||||
|
$ability = $this->ability(self::untouched());
|
||||||
|
$schema = $ability->inputSchema();
|
||||||
|
|
||||||
|
self::assertSame('lit/divi-presets-assign', $ability->name());
|
||||||
|
self::assertFalse($ability->isDirect());
|
||||||
|
self::assertSame(['readonly' => false, 'destructive' => false, 'idempotent' => true], $ability->annotations());
|
||||||
|
self::assertSame(['module'], $schema['required']);
|
||||||
|
self::assertFalse($schema['additionalProperties']);
|
||||||
|
self::assertSame(DiviPresetsAssignAbility::MAX_STACK, $schema['properties']['preset']['maxItems']);
|
||||||
|
self::assertArrayNotHasKey('design_rev', $schema['properties']);
|
||||||
|
self::assertSame(['saved', 'remaining', 'skipped'], array_slice($ability->outputSchema()['required'], -3));
|
||||||
|
self::assertStringContainsString('gewinnt das letzte Preset im Stapel', $ability->description());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,116 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Design;
|
||||||
|
|
||||||
|
use LIT\MCP\Integrations\Divi\Design\PageRewriteReport;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
final class PageRewriteReportTest extends TestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* A PageRewriter::preview() result: $pages listed pages (IDs from 12) with one module each.
|
||||||
|
*
|
||||||
|
* @param list<int> $blocked
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public static function preview(int $pages, array $blocked = []): array
|
||||||
|
{
|
||||||
|
$listed = [];
|
||||||
|
for ($i = 0; $i < $pages; ++$i) {
|
||||||
|
$listed[] = [
|
||||||
|
'post_id' => 12 + $i,
|
||||||
|
'title' => 'Seite ' . ($i + 1),
|
||||||
|
'type' => 'page',
|
||||||
|
'status' => 'publish',
|
||||||
|
'nodes' => [['id' => 's0.r0.c0.m0', 'module' => 'divi/button', 'label' => 'Button']],
|
||||||
|
'warnings' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'pages' => $listed,
|
||||||
|
'pages_count' => $pages,
|
||||||
|
'nodes_count' => $pages,
|
||||||
|
'skipped' => [],
|
||||||
|
'blocked_ids' => $blocked,
|
||||||
|
'hidden_count' => 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A PageRewriter::apply() result: $saved pages (IDs from 12) with two modules each.
|
||||||
|
*
|
||||||
|
* @param list<array{post_id: int, reason: string}> $skipped
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public static function applied(int $saved, int $remaining, array $skipped = []): array
|
||||||
|
{
|
||||||
|
$rows = [];
|
||||||
|
for ($i = 0; $i < $saved; ++$i) {
|
||||||
|
$rows[] = ['post_id' => 12 + $i, 'revision_id' => 900 + $i, 'nodes' => 2];
|
||||||
|
}
|
||||||
|
|
||||||
|
return ['saved' => $rows, 'remaining' => $remaining, 'warnings' => [], 'skipped' => $skipped];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testEmptyMeansNothingToWriteNothingBlockedNothingHidden(): void
|
||||||
|
{
|
||||||
|
self::assertTrue(PageRewriteReport::isEmpty(self::preview(0)));
|
||||||
|
self::assertFalse(PageRewriteReport::isEmpty(self::preview(1)));
|
||||||
|
self::assertFalse(PageRewriteReport::isEmpty(self::preview(0, [7])));
|
||||||
|
self::assertFalse(PageRewriteReport::isEmpty(['hidden_count' => 1] + self::preview(0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testDryRunMessageNamesCountsTheLimitAndBlockedPages(): void
|
||||||
|
{
|
||||||
|
$message = PageRewriteReport::dryRunMessage(self::preview(2), 'Zuweisung an divi/button');
|
||||||
|
|
||||||
|
self::assertStringStartsWith('Zuweisung an divi/button – betroffen (Seiten: 2, Module: 2).', $message);
|
||||||
|
self::assertStringContainsString('Nichts wurde gespeichert (Probelauf).', $message);
|
||||||
|
self::assertStringContainsString('confirm: true', $message);
|
||||||
|
self::assertStringContainsString('höchstens 50 Seiten', $message);
|
||||||
|
self::assertStringNotContainsString('blocked_ids', $message);
|
||||||
|
self::assertStringContainsString('blocked_ids', PageRewriteReport::dryRunMessage(self::preview(1, [7]), 'X'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSavedSummaryAsksForAnotherCallWhilePagesRemain(): void
|
||||||
|
{
|
||||||
|
self::assertSame(
|
||||||
|
'X – gespeichert (Seiten: 2, Module: 4). Noch 3 Seiten offen: mit denselben Angaben erneut mit '
|
||||||
|
. 'confirm: true aufrufen.',
|
||||||
|
PageRewriteReport::savedSummary(self::applied(2, 3), 'X')
|
||||||
|
);
|
||||||
|
self::assertSame(
|
||||||
|
'X – gespeichert (Seiten: 1, Module: 2). Alle Fundstellen sind erledigt.',
|
||||||
|
PageRewriteReport::savedSummary(self::applied(1, 0), 'X')
|
||||||
|
);
|
||||||
|
self::assertSame(
|
||||||
|
'X – gespeichert (Seiten: 0, Module: 0). Übersprungen: 1 (siehe skipped).',
|
||||||
|
PageRewriteReport::savedSummary(self::applied(0, 0, [['post_id' => 5, 'reason' => 'global']]), 'X')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testNothingToDoIsAToolResultWithTheSkippedPages(): void
|
||||||
|
{
|
||||||
|
$preview = ['skipped' => [['post_id' => 5, 'reason' => 'global']]] + self::preview(0);
|
||||||
|
|
||||||
|
$result = PageRewriteReport::nothingToDo('Nichts zu tun.', $preview, ['Hinweis']);
|
||||||
|
|
||||||
|
self::assertTrue($result['ok']);
|
||||||
|
self::assertSame('Nichts zu tun.', $result['summary']);
|
||||||
|
self::assertSame(['Hinweis'], $result['warnings']);
|
||||||
|
self::assertSame([[], 0], [$result['saved'], $result['remaining']]);
|
||||||
|
self::assertSame(5, $result['skipped'][0]['post_id']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testOutputSchemaRequiresTheRewriteKeys(): void
|
||||||
|
{
|
||||||
|
$schema = PageRewriteReport::outputSchema(['diff' => ['type' => 'array']]);
|
||||||
|
|
||||||
|
self::assertSame(['saved', 'remaining', 'skipped'], array_slice($schema['required'], -3));
|
||||||
|
self::assertSame('integer', $schema['properties']['remaining']['type']);
|
||||||
|
self::assertSame(['type' => 'array'], $schema['properties']['diff']);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Design;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use LIT\MCP\Integrations\Divi\Design\PageRewriter;
|
||||||
|
use LIT\MCP\Integrations\Divi\Design\PostIdsInput;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
final class PostIdsInputTest extends TestCase
|
||||||
|
{
|
||||||
|
public function testParsesAListOfPositiveIdsWithoutDuplicates(): void
|
||||||
|
{
|
||||||
|
self::assertNull(PostIdsInput::parse(null));
|
||||||
|
self::assertSame([12, 13], PostIdsInput::parse([12, 13, 12]));
|
||||||
|
self::assertSame(PageRewriter::PAGE_LIMIT, PostIdsInput::MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @dataProvider invalidInput */
|
||||||
|
public function testRefusesEverythingElse(mixed $value): void
|
||||||
|
{
|
||||||
|
$this->expectException(InvalidArgumentException::class);
|
||||||
|
$this->expectExceptionMessage('Liste von 1 bis 50 Beitrags-IDs (positive ganze Zahlen) erwartet');
|
||||||
|
|
||||||
|
PostIdsInput::parse($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return array<string, array{mixed}> */
|
||||||
|
public static function invalidInput(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'leer' => [[]],
|
||||||
|
'null als ID' => [[0]],
|
||||||
|
'negativ' => [[-3]],
|
||||||
|
'Text' => [['12']],
|
||||||
|
'Objekt' => [['a' => 1]],
|
||||||
|
'zu viele' => [range(1, 51)],
|
||||||
|
'keine Liste' => [12],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testSchemaMatchesTheParser(): void
|
||||||
|
{
|
||||||
|
$schema = PostIdsInput::schema();
|
||||||
|
|
||||||
|
self::assertSame('array', $schema['type']);
|
||||||
|
self::assertSame(1, $schema['minItems']);
|
||||||
|
self::assertSame(50, $schema['maxItems']);
|
||||||
|
self::assertSame(['type' => 'integer', 'minimum' => 1], $schema['items']);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,6 +36,7 @@ final class DiviIntegrationTest extends TestCase
|
|||||||
'lit/divi-presets-delete',
|
'lit/divi-presets-delete',
|
||||||
'lit/divi-design-snapshots',
|
'lit/divi-design-snapshots',
|
||||||
'lit/divi-design-restore',
|
'lit/divi-design-restore',
|
||||||
|
'lit/divi-presets-assign',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function testProvidesTheDiviAbilitiesOnlyWithDivi5(): void
|
public function testProvidesTheDiviAbilitiesOnlyWithDivi5(): void
|
||||||
|
|||||||
Reference in New Issue
Block a user