Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
135 lines
5.3 KiB
PHP
135 lines
5.3 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Design;
|
||
|
||
use Brain\Monkey\Functions;
|
||
use LIT\MCP\Abilities\Annotations;
|
||
use LIT\MCP\Integrations\Divi\Design\DiviThemeOptionsGetAbility;
|
||
use LIT\MCP\Integrations\Divi\Design\ThemeOptionBook;
|
||
use LIT\MCP\Integrations\Divi\Design\ThemeOptionsAdapter;
|
||
use LIT\MCP\Integrations\Divi\Design\ThemeOptionSpec;
|
||
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||
use LIT\MCP\Tests\Unit\TestCase;
|
||
use Mockery;
|
||
|
||
final class DiviThemeOptionsGetAbilityTest extends TestCase
|
||
{
|
||
protected function setUp(): void
|
||
{
|
||
parent::setUp();
|
||
Functions\when('current_user_can')->justReturn(true);
|
||
}
|
||
|
||
private static function ability(): DiviThemeOptionsGetAbility
|
||
{
|
||
$stored = ThemeOptionsAreaTest::stored();
|
||
$writes = [];
|
||
|
||
return new DiviThemeOptionsGetAbility(ThemeOptionsAreaTest::adapter($stored, $writes));
|
||
}
|
||
|
||
/**
|
||
* @param array<string, mixed> $result
|
||
* @return array<string, array<string, mixed>> key => row
|
||
*/
|
||
private static function byKey(array $result): array
|
||
{
|
||
return array_column($result['options'], null, 'key');
|
||
}
|
||
|
||
public function testSchemaAndMeta(): void
|
||
{
|
||
$ability = self::ability();
|
||
$schema = $ability->inputSchema();
|
||
|
||
self::assertSame('lit/divi-theme-options-get', $ability->name());
|
||
self::assertFalse($ability->isDirect());
|
||
self::assertSame(Annotations::read(), $ability->annotations());
|
||
self::assertSame([DiviFeature::ThemeOptionRead], $ability->diviFeatures([]));
|
||
self::assertSame(ThemeOptionSpec::KEYS, $schema['properties']['keys']['items']['enum']);
|
||
self::assertFalse($schema['additionalProperties']);
|
||
self::assertSame(['options', 'design_rev', 'notes'], $ability->outputSchema()['required']);
|
||
self::assertStringContainsString('lit/divi-theme-options-update', $ability->description());
|
||
}
|
||
|
||
/** Reading needs what lit/divi-design-system needs (edit_posts) – checked before Divi is asked. */
|
||
public function testNeedsEditPosts(): void
|
||
{
|
||
Functions\when('current_user_can')->justReturn(false);
|
||
$options = Mockery::mock(ThemeOptionsAdapter::class);
|
||
$options->shouldNotReceive('read');
|
||
$ability = new DiviThemeOptionsGetAbility($options);
|
||
|
||
self::assertToolError($ability->permission([]), 'forbidden');
|
||
self::assertToolError($ability->execute([]), 'forbidden');
|
||
}
|
||
|
||
public function testListsEveryOptionWithValueEffectiveValueAndDefault(): void
|
||
{
|
||
$result = self::ability()->execute([]);
|
||
|
||
self::assertSame(ThemeOptionSpec::KEYS, array_column($result['options'], 'key'));
|
||
self::assertSame(ThemeOptionBook::fromDivi(ThemeOptionsAreaTest::stored())->rev(), $result['design_rev']);
|
||
$rows = self::byKey($result);
|
||
$size = ThemeOptionSpec::definition('body_font_size');
|
||
$expected = ['key' => 'body_font_size', 'label' => $size['label'], 'type' => 'int', 'value' => 16,
|
||
'effective' => 16, 'default' => ThemeOptionSpec::effective('body_font_size', []), 'is_default' => false,
|
||
'min' => $size['min'], 'max' => $size['max'], 'step' => $size['step']];
|
||
$actual = $rows['body_font_size'];
|
||
ksort($expected);
|
||
ksort($actual);
|
||
self::assertSame($expected, $actual);
|
||
self::assertSame('Open Sans', $rows['body_font']['value']);
|
||
}
|
||
|
||
/** Ruling W3: an unset tablet size inherits the desktop size – as effective value and as its default. */
|
||
public function testUnsetSizesInheritTheLargerSize(): void
|
||
{
|
||
$tablet = self::byKey(self::ability()->execute([]))['tablet_body_font_size'];
|
||
|
||
self::assertNull($tablet['value']);
|
||
self::assertTrue($tablet['is_default']);
|
||
self::assertSame(16, $tablet['effective']);
|
||
self::assertSame(16, $tablet['default']);
|
||
self::assertSame('body_font_size', $tablet['inherits']);
|
||
}
|
||
|
||
public function testChoicesAreListed(): void
|
||
{
|
||
$rows = self::byKey(self::ability()->execute([]));
|
||
|
||
self::assertSame(
|
||
ThemeOptionSpec::definition('body_header_style')['choices'],
|
||
$rows['body_header_style']['choices']
|
||
);
|
||
self::assertArrayNotHasKey('choices', $rows['body_font']);
|
||
}
|
||
|
||
public function testKeysNarrowTheAnswerInTheFixedOrder(): void
|
||
{
|
||
$result = self::ability()->execute(['keys' => ['row_padding', 'body_font']]);
|
||
|
||
self::assertSame(['body_font', 'row_padding'], array_column($result['options'], 'key'));
|
||
}
|
||
|
||
public function testUnknownKeysAreRefused(): void
|
||
{
|
||
$error = self::ability()->execute(['keys' => ['accent_color', 'body_font', 5]]);
|
||
|
||
self::assertToolError($error, 'validation_failed');
|
||
self::assertSame(['keys[0]', 'keys[2]'], array_keys(self::errorFields($error)));
|
||
self::assertToolError(self::ability()->execute(['keys' => 'body_font']), 'validation_failed');
|
||
}
|
||
|
||
/** G3, A8: the main colors are not theme options here; the notes point to lit/divi-colors-set. */
|
||
public function testNotesPointToTheColorTool(): void
|
||
{
|
||
$notes = implode(' ', self::ability()->execute([])['notes']);
|
||
|
||
self::assertStringContainsString('lit/divi-colors-set', $notes);
|
||
self::assertStringContainsString('null', $notes);
|
||
}
|
||
}
|