Files
WP-LIT-MCP/tests/Unit/Integrations/Divi/Design/DiviPresetsAssignAbilityTest.php
T

245 lines
9.7 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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('refresh');
$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 testEmptyStringAssignmentPointsToDetachToo(): void
{
$ability = $this->ability(self::untouched());
$empty = $ability->execute(['module' => 'button', 'preset' => '', 'confirm' => true]);
self::assertToolError($empty, 'validation_failed');
self::assertStringContainsString('lit/divi-design-detach', self::errorFields($empty)['preset']);
$group = $ability->execute(['module' => 'text', 'group_presets' => ['font-body' => ''], 'confirm' => true]);
self::assertToolError($group, 'validation_failed');
self::assertStringContainsString(
'lit/divi-design-detach',
self::errorFields($group)['group_presets.font-body']
);
}
public function testInputSchemaForbidsEmptyPresetStrings(): void
{
$schema = $this->ability(self::untouched())->inputSchema()['properties']['preset'];
self::assertSame(1, $schema['minLength']);
}
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(PresetAssignment::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());
}
}