feat: Fähigkeiten color-write/variable-write; GlobalColorsAdapter und GlobalVariablesAdapter (Manifest, Signatur-Referenzen) und DesignRights::colors()/variables()
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
d73144ab36
commit
9b7c3c1d3b
@@ -7,6 +7,7 @@ namespace LIT\MCP\Tests\Unit\Integrations\Divi\Design;
|
||||
use Brain\Monkey\Functions;
|
||||
use LIT\MCP\Integrations\Divi\Design\DesignRights;
|
||||
use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter;
|
||||
use LIT\MCP\Integrations\Divi\Design\GlobalVariablesAdapter;
|
||||
use LIT\MCP\Tests\Unit\TestCase;
|
||||
use Mockery;
|
||||
|
||||
@@ -26,4 +27,50 @@ final class DesignRightsTest extends TestCase
|
||||
$refusal = self::assertToolError(DesignRights::presets($writer), 'forbidden');
|
||||
self::assertStringContainsString('edit_theme_options', $refusal['message']);
|
||||
}
|
||||
|
||||
public function testColorsNeedEditThemeOptionsAndTheVisualBuilder(): void
|
||||
{
|
||||
$writer = Mockery::mock(DesignSystemWriter::class);
|
||||
$writer->allows('canUseVisualBuilder')->andReturn(true, false);
|
||||
Functions\when('current_user_can')->alias(static fn (string $cap): bool => $cap === 'edit_theme_options');
|
||||
|
||||
self::assertNull(DesignRights::colors($writer));
|
||||
$refusal = self::assertToolError(DesignRights::colors($writer), 'forbidden');
|
||||
self::assertStringContainsString('keine Farben', $refusal['message']);
|
||||
|
||||
Functions\when('current_user_can')->justReturn(false);
|
||||
$refusal = self::assertToolError(DesignRights::colors($writer), 'forbidden');
|
||||
self::assertStringContainsString('Farben ändern erfordert', $refusal['message']);
|
||||
self::assertStringContainsString('edit_theme_options', $refusal['message']);
|
||||
}
|
||||
|
||||
public function testVariablesAlsoNeedTheVariablesManager(): void
|
||||
{
|
||||
$writer = Mockery::mock(DesignSystemWriter::class);
|
||||
$writer->allows('canUseVisualBuilder')->andReturn(true);
|
||||
$variables = Mockery::mock(GlobalVariablesAdapter::class);
|
||||
$variables->allows('canManage')->andReturn(true, false);
|
||||
Functions\when('current_user_can')->alias(static fn (string $cap): bool => $cap === 'edit_theme_options');
|
||||
|
||||
self::assertNull(DesignRights::variables($writer, $variables));
|
||||
$refusal = self::assertToolError(DesignRights::variables($writer, $variables), 'forbidden');
|
||||
self::assertStringContainsString('Variablen-Manager', $refusal['message']);
|
||||
self::assertStringContainsString('Rollen-Editor', $refusal['hint']);
|
||||
}
|
||||
|
||||
public function testVariablesAskTheThemeRightAndTheVisualBuilderFirst(): void
|
||||
{
|
||||
$writer = Mockery::mock(DesignSystemWriter::class);
|
||||
$writer->allows('canUseVisualBuilder')->andReturn(false);
|
||||
$variables = Mockery::mock(GlobalVariablesAdapter::class);
|
||||
$variables->allows('canManage')->andReturn(true);
|
||||
Functions\when('current_user_can')->justReturn(true);
|
||||
|
||||
$refusal = self::assertToolError(DesignRights::variables($writer, $variables), 'forbidden');
|
||||
self::assertStringContainsString('keine Variablen', $refusal['message']);
|
||||
|
||||
Functions\when('current_user_can')->justReturn(false);
|
||||
$refusal = self::assertToolError(DesignRights::variables($writer, $variables), 'forbidden');
|
||||
self::assertStringContainsString('Variablen ändern erfordert', $refusal['message']);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Design;
|
||||
|
||||
use Brain\Monkey\Functions;
|
||||
use LIT\MCP\Integrations\Divi\Design\GlobalColorsAdapter;
|
||||
use LIT\MCP\Integrations\Divi\Release\Call;
|
||||
use LIT\MCP\Integrations\Divi\Release\DiviAdapters;
|
||||
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||
use LIT\MCP\Integrations\Divi\Release\DiviLocked;
|
||||
use LIT\MCP\Integrations\Divi\Release\Manifest;
|
||||
use LIT\MCP\Integrations\Divi\Release\ReleaseGate;
|
||||
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;
|
||||
use Mockery;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Divi's GlobalData is replaced by a Mockery alias; the tests that need it run in their own process (an alias
|
||||
* defines the class for the rest of the process).
|
||||
*/
|
||||
final class GlobalColorsAdapterTest extends TestCase
|
||||
{
|
||||
private const GLOBAL_DATA = 'ET\\Builder\\Packages\\GlobalData\\GlobalData';
|
||||
|
||||
public function testIsARegisteredDesignAdapterForColorWrite(): void
|
||||
{
|
||||
$manifest = GlobalColorsAdapter::manifest();
|
||||
|
||||
self::assertContains(GlobalColorsAdapter::class, DiviAdapters::CLASSES);
|
||||
self::assertSame(Manifest::DESIGN, $manifest->area);
|
||||
self::assertSame(
|
||||
['GlobalData::get_global_colors()', 'GlobalData::sanitize_global_colors_data()',
|
||||
'GlobalData::set_global_colors()'],
|
||||
array_map(static fn (Call $call): string => $call->label(), $manifest->calls)
|
||||
);
|
||||
self::assertSame([0, 1, 2], array_map(static fn (Call $call): ?int => $call->args, $manifest->calls));
|
||||
self::assertSame(['et_divi'], $manifest->keys);
|
||||
self::assertSame([DiviFeature::ColorWrite], $manifest->features);
|
||||
}
|
||||
|
||||
public function testWritesNeedAReleasedDivi(): void
|
||||
{
|
||||
ReleaseGate::useResolver(
|
||||
static fn (): ReleaseStatus => ReleaseMatrices::uniform(ReleaseState::Untested, '5.16.0')
|
||||
);
|
||||
|
||||
$this->expectException(DiviLocked::class);
|
||||
|
||||
(new GlobalColorsAdapter())->save(['gcid-abc' => ['color' => '#fff']], false);
|
||||
}
|
||||
|
||||
/** @runInSeparateProcess */
|
||||
public function testReadLaysTheStoredCustomizerValuesOverDivisCache(): void
|
||||
{
|
||||
Mockery::mock('alias:' . self::GLOBAL_DATA)->allows('get_global_colors')->andReturn([
|
||||
'gcid-primary-color' => ['color' => '#2ea3f2', 'folder' => 'customizer', 'status' => 'active'],
|
||||
'gcid-secondary-color' => ['color' => '#2ea3f2', 'folder' => 'customizer', 'status' => 'active'],
|
||||
'gcid-body-color' => ['color' => '#666666', 'folder' => 'customizer', 'status' => 'active'],
|
||||
'gcid-abc1234567' => ['color' => '#123456', 'folder' => '', 'status' => 'active'],
|
||||
]);
|
||||
Functions\when('get_option')->alias(static fn (string $name): mixed => $name === 'et_divi'
|
||||
? ['accent_color' => '#ff0000', 'secondary_accent_color' => '', 'font_color' => 7, 'link_color' => '#00f']
|
||||
: false);
|
||||
|
||||
$colors = (new GlobalColorsAdapter())->read();
|
||||
|
||||
self::assertSame('#ff0000', $colors['gcid-primary-color']['color'], 'stored option wins over the cache');
|
||||
self::assertSame('#2ea3f2', $colors['gcid-secondary-color']['color'], 'an empty option is not laid over');
|
||||
self::assertSame('#666666', $colors['gcid-body-color']['color'], 'a non-string option is not laid over');
|
||||
self::assertArrayNotHasKey('gcid-link-color', $colors, 'no entry is invented');
|
||||
self::assertSame('#123456', $colors['gcid-abc1234567']['color']);
|
||||
}
|
||||
|
||||
/** @runInSeparateProcess */
|
||||
public function testReadWithoutThemeOptionsIsDivisAnswer(): void
|
||||
{
|
||||
$divi = ['gcid-primary-color' => ['color' => '#2ea3f2', 'folder' => 'customizer']];
|
||||
Mockery::mock('alias:' . self::GLOBAL_DATA)->allows('get_global_colors')->andReturn($divi);
|
||||
Functions\when('get_option')->justReturn(false);
|
||||
|
||||
self::assertSame($divi, (new GlobalColorsAdapter())->read());
|
||||
}
|
||||
|
||||
/** @runInSeparateProcess */
|
||||
public function testSaveSanitizesAndMergesOrReplaces(): void
|
||||
{
|
||||
$entries = ['gcid-abc1234567' => ['color' => '#123456', 'status' => 'active', 'usedInPosts' => []]];
|
||||
$clean = ['gcid-abc1234567' => ['color' => '#123456', 'status' => 'active', 'usedInPosts' => []]];
|
||||
$divi = Mockery::mock('alias:' . self::GLOBAL_DATA);
|
||||
$divi->expects('sanitize_global_colors_data')->twice()->with($entries)->andReturn($clean);
|
||||
// Normal write: merge ("already sanitized" = true); restore: replace all (false).
|
||||
$divi->expects('set_global_colors')->once()->with($clean, true)->ordered();
|
||||
$divi->expects('set_global_colors')->once()->with($clean, false)->ordered();
|
||||
|
||||
(new GlobalColorsAdapter())->save($entries, false);
|
||||
(new GlobalColorsAdapter())->save($entries, true);
|
||||
}
|
||||
|
||||
/** @runInSeparateProcess */
|
||||
public function testSaveRefusesWhenDiviDropsAnEntry(): void
|
||||
{
|
||||
$divi = Mockery::mock('alias:' . self::GLOBAL_DATA);
|
||||
$divi->allows('sanitize_global_colors_data')->andReturn(['gcid-abc1234567' => ['color' => '#123456']]);
|
||||
$divi->expects('set_global_colors')->never();
|
||||
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage('global colors refused: Divi dropped gcid-empty00000');
|
||||
|
||||
(new GlobalColorsAdapter())->save([
|
||||
'gcid-abc1234567' => ['color' => '#123456'],
|
||||
'gcid-empty00000' => ['color' => ''],
|
||||
], false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Design;
|
||||
|
||||
use Brain\Monkey\Functions;
|
||||
use LIT\MCP\Integrations\Divi\Design\GlobalVariablesAdapter;
|
||||
use LIT\MCP\Integrations\Divi\Release\Call;
|
||||
use LIT\MCP\Integrations\Divi\Release\DiviAdapters;
|
||||
use LIT\MCP\Integrations\Divi\Release\DiviFeature;
|
||||
use LIT\MCP\Integrations\Divi\Release\DiviLocked;
|
||||
use LIT\MCP\Integrations\Divi\Release\Manifest;
|
||||
use LIT\MCP\Integrations\Divi\Release\ReleaseGate;
|
||||
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;
|
||||
use Mockery;
|
||||
use RuntimeException;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Divi's GlobalData is replaced by a Mockery alias and et_pb_is_allowed() by Brain Monkey; those tests run in their
|
||||
* own process (both define a symbol for the rest of the process).
|
||||
*/
|
||||
final class GlobalVariablesAdapterTest extends TestCase
|
||||
{
|
||||
private const GLOBAL_DATA = 'ET\\Builder\\Packages\\GlobalData\\GlobalData';
|
||||
|
||||
public function testIsARegisteredDesignAdapterForVariableWrite(): void
|
||||
{
|
||||
$manifest = GlobalVariablesAdapter::manifest();
|
||||
|
||||
self::assertContains(GlobalVariablesAdapter::class, DiviAdapters::CLASSES);
|
||||
self::assertSame(Manifest::DESIGN, $manifest->area);
|
||||
self::assertSame(
|
||||
['GlobalData::get_global_variables()', 'GlobalData::set_global_variables()', 'et_pb_is_allowed()'],
|
||||
array_map(static fn (Call $call): string => $call->label(), $manifest->calls)
|
||||
);
|
||||
self::assertSame([0, 2, 1], array_map(static fn (Call $call): ?int => $call->args, $manifest->calls));
|
||||
self::assertSame([], $manifest->keys);
|
||||
self::assertSame([DiviFeature::VariableWrite], $manifest->features);
|
||||
}
|
||||
|
||||
public function testWritesNeedAReleasedDivi(): void
|
||||
{
|
||||
ReleaseGate::useResolver(
|
||||
static fn (): ReleaseStatus => ReleaseMatrices::uniform(ReleaseState::Untested, '5.16.0')
|
||||
);
|
||||
|
||||
$this->expectException(DiviLocked::class);
|
||||
|
||||
(new GlobalVariablesAdapter())->save([], []);
|
||||
}
|
||||
|
||||
public function testWithoutDiviNobodyManagesVariables(): void
|
||||
{
|
||||
// Unit tests have no et_pb_is_allowed(): a missing Divi function never means "allowed".
|
||||
self::assertFalse((new GlobalVariablesAdapter())->canManage());
|
||||
}
|
||||
|
||||
/** @runInSeparateProcess */
|
||||
public function testCanManageAsksTheVariablesManagerRight(): void
|
||||
{
|
||||
Functions\when('et_pb_is_allowed')->alias(static fn (string $cap): bool => $cap === 'variables_manager');
|
||||
|
||||
self::assertTrue((new GlobalVariablesAdapter())->canManage());
|
||||
}
|
||||
|
||||
/** @runInSeparateProcess */
|
||||
public function testReadReturnsTheStoredForm(): void
|
||||
{
|
||||
$actions = ['allowedActions' => ['editLabel' => true, 'editValue' => true, 'remove' => true]];
|
||||
$space = ['label' => 'Abstand', 'value' => '24px', 'order' => '1', 'status' => 'active'];
|
||||
$wave = ['label' => 'Welle', 'value' => 'linear-gradient(#fff,#000)', 'order' => '1', 'status' => 'active'];
|
||||
Mockery::mock('alias:' . self::GLOBAL_DATA)->allows('get_global_variables')->andReturn([
|
||||
'numbers' => (object) ['gvid-space00001' => $space + $actions],
|
||||
'strings' => (object) [],
|
||||
'colors' => (object) ['gcid-x' => ['label' => 'X'] + $actions],
|
||||
'fonts' => (object) [
|
||||
'--et_global_heading_font' => ['label' => 'Überschrift', 'value' => 'Open Sans'] + $actions,
|
||||
'gvid-font000001' => (object) (['label' => 'Fließtext', 'value' => 'Inter'] + $actions),
|
||||
],
|
||||
'gradients' => (object) ['gvid-wave000001' => $wave + $actions],
|
||||
]);
|
||||
|
||||
self::assertSame(
|
||||
[
|
||||
'numbers' => ['gvid-space00001' => $space],
|
||||
'fonts' => ['gvid-font000001' => ['label' => 'Fließtext', 'value' => 'Inter']],
|
||||
'gradients' => ['gvid-wave000001' => $wave],
|
||||
],
|
||||
(new GlobalVariablesAdapter())->read()
|
||||
);
|
||||
}
|
||||
|
||||
/** @runInSeparateProcess */
|
||||
public function testSavePassesThePlannedInventoryAndThrowsOnARefusal(): void
|
||||
{
|
||||
$data = ['numbers' => ['gvid-space00001' => ['label' => 'Abstand', 'value' => '32px']]];
|
||||
$expected = ['numbers' => ['gvid-space00001' => ['label' => 'Abstand', 'value' => '24px']]];
|
||||
Mockery::mock('alias:' . self::GLOBAL_DATA)->expects('set_global_variables')->twice()->with($data, $expected)
|
||||
->andReturn(null, new WP_Error('global_variables_stale', 'Global variables changed.'));
|
||||
|
||||
(new GlobalVariablesAdapter())->save($data, $expected);
|
||||
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage('global variables refused: global_variables_stale Global variables changed.');
|
||||
(new GlobalVariablesAdapter())->save($data, $expected);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,8 @@ namespace LIT\MCP\Tests\Unit\Integrations\Divi\Release;
|
||||
|
||||
use LIT\MCP\Integrations\Divi\Design\DesignSystemReader;
|
||||
use LIT\MCP\Integrations\Divi\Design\DesignSystemWriter;
|
||||
use LIT\MCP\Integrations\Divi\Design\GlobalColorsAdapter;
|
||||
use LIT\MCP\Integrations\Divi\Design\GlobalVariablesAdapter;
|
||||
use LIT\MCP\Integrations\Divi\Library\DiviLibraryStore;
|
||||
use LIT\MCP\Integrations\Divi\Library\DiviRoles;
|
||||
use LIT\MCP\Integrations\Divi\Pages\DiviPages;
|
||||
@@ -70,6 +72,16 @@ final class DiviFeatureAssignmentTest extends TestCase
|
||||
'UserRole::can_current_user_use_visual_builder()' => ['preset-write'],
|
||||
'Nachbau GlobalPresetController::sync()' => ['preset-write'],
|
||||
],
|
||||
GlobalColorsAdapter::class => [
|
||||
'GlobalData::get_global_colors()' => ['color-write'],
|
||||
'GlobalData::sanitize_global_colors_data()' => ['color-write'],
|
||||
'GlobalData::set_global_colors()' => ['color-write'],
|
||||
],
|
||||
GlobalVariablesAdapter::class => [
|
||||
'GlobalData::get_global_variables()' => ['variable-write'],
|
||||
'GlobalData::set_global_variables()' => ['variable-write'],
|
||||
'et_pb_is_allowed()' => ['variable-write'],
|
||||
],
|
||||
DiviModules::class => [
|
||||
'ModuleRegistration::get_all_core_modules_metadata()' => ['catalog'],
|
||||
'ModuleRegistration::get_default_attrs()' => ['catalog'],
|
||||
@@ -131,7 +143,7 @@ final class DiviFeatureAssignmentTest extends TestCase
|
||||
}
|
||||
|
||||
self::assertSame(self::EXPECTED, $actual);
|
||||
self::assertSame(array_keys(self::EXPECTED), DiviAdapters::CLASSES, 'all eight adapters, in report order');
|
||||
self::assertSame(array_keys(self::EXPECTED), DiviAdapters::CLASSES, 'all ten adapters, in report order');
|
||||
}
|
||||
|
||||
public function testReadingAdaptersCarryNoWriteFeature(): void
|
||||
|
||||
@@ -9,7 +9,7 @@ use PHPUnit\Framework\TestCase;
|
||||
|
||||
final class DiviFeatureTest extends TestCase
|
||||
{
|
||||
public function testTheElevenFeaturesAndTheirValues(): void
|
||||
public function testTheThirteenFeaturesAndTheirValues(): void
|
||||
{
|
||||
self::assertSame(
|
||||
[
|
||||
@@ -19,6 +19,8 @@ final class DiviFeatureTest extends TestCase
|
||||
'DynamicSources' => 'dynamic-sources',
|
||||
'DesignRead' => 'design-read',
|
||||
'PresetWrite' => 'preset-write',
|
||||
'ColorWrite' => 'color-write',
|
||||
'VariableWrite' => 'variable-write',
|
||||
'LibraryRead' => 'library-read',
|
||||
'LibraryWrite' => 'library-write',
|
||||
'Roles' => 'roles',
|
||||
@@ -32,14 +34,17 @@ final class DiviFeatureTest extends TestCase
|
||||
);
|
||||
}
|
||||
|
||||
public function testOnlyTheFourWriteFeaturesWrite(): void
|
||||
public function testOnlyTheSixWriteFeaturesWrite(): void
|
||||
{
|
||||
$writing = array_values(array_map(
|
||||
static fn (DiviFeature $feature): string => $feature->value,
|
||||
array_filter(DiviFeature::cases(), static fn (DiviFeature $feature): bool => $feature->isWrite())
|
||||
));
|
||||
|
||||
self::assertSame(['page-write', 'preset-write', 'library-write', 'tb-write'], $writing);
|
||||
self::assertSame(
|
||||
['page-write', 'preset-write', 'color-write', 'variable-write', 'library-write', 'tb-write'],
|
||||
$writing
|
||||
);
|
||||
}
|
||||
|
||||
public function testEveryFeatureHasAUniqueGermanLabel(): void
|
||||
@@ -49,6 +54,8 @@ final class DiviFeatureTest extends TestCase
|
||||
self::assertCount(count(DiviFeature::cases()), array_unique($labels));
|
||||
self::assertSame('Seiten schreiben', DiviFeature::PageWrite->label());
|
||||
self::assertSame('Theme Builder lesen', DiviFeature::ThemeBuilderRead->label());
|
||||
self::assertSame('Farben schreiben', DiviFeature::ColorWrite->label());
|
||||
self::assertSame('Variablen schreiben', DiviFeature::VariableWrite->label());
|
||||
foreach ($labels as $label) {
|
||||
self::assertNotSame('', trim($label));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user