feat: lit/tb-list and the tool group "Divi Theme Builder"
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace LIT\MCP\Tests\Unit\Integrations\Divi\ThemeBuilder;
|
||||
|
||||
use Brain\Monkey\Functions;
|
||||
use LIT\MCP\Integrations\Divi\ThemeBuilder\DiviThemeBuilder;
|
||||
use LIT\MCP\Integrations\Divi\ThemeBuilder\TbListAbility;
|
||||
use LIT\MCP\Integrations\Divi\ThemeBuilder\TemplateConditions;
|
||||
use LIT\MCP\Tests\Support\LibraryFixtures;
|
||||
use LIT\MCP\Tests\Support\ThemeBuilderFixtures;
|
||||
use LIT\MCP\Tests\Unit\TestCase;
|
||||
|
||||
final class TbListAbilityTest extends TestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
LibraryFixtures::given([]);
|
||||
Functions\when('get_the_title')->alias(static fn (int $id): string => '[Fixture] Layout ' . $id);
|
||||
}
|
||||
|
||||
/** @param list<string> $deniedRoles */
|
||||
private static function ability(DiviThemeBuilder $store, array $deniedRoles = [], bool $visible = true): TbListAbility
|
||||
{
|
||||
return new TbListAbility(
|
||||
ThemeBuilderFixtures::access($deniedRoles, true, $store, $visible),
|
||||
new TemplateConditions($store)
|
||||
);
|
||||
}
|
||||
|
||||
/** @param list<int> $notEditable Post IDs without edit_post. */
|
||||
private static function denyEditing(array $notEditable): void
|
||||
{
|
||||
Functions\when('current_user_can')->alias(
|
||||
static fn (string $cap, mixed ...$args): bool => ! ($cap === 'edit_post'
|
||||
&& in_array((int) ($args[0] ?? 0), $notEditable, true))
|
||||
);
|
||||
}
|
||||
|
||||
public function testListsTemplatesWithLayoutsAndLabels(): void
|
||||
{
|
||||
$store = ThemeBuilderFixtures::store([ThemeBuilderFixtures::template(1065), ThemeBuilderFixtures::template(1066)]);
|
||||
|
||||
$result = self::ability($store)->execute([]);
|
||||
|
||||
self::assertSame(1064, $result['theme_builder_id']);
|
||||
self::assertFalse($result['draft_pending']);
|
||||
self::assertSame([1065, 1066], array_column($result['templates'], 'id'));
|
||||
$template = $result['templates'][1];
|
||||
self::assertFalse($template['enabled']);
|
||||
self::assertFalse($template['live']);
|
||||
self::assertSame(ThemeBuilderFixtures::template(1066)->rev(), $template['template_rev']);
|
||||
self::assertSame(['Bestimmte Seiten: 1053'], $template['use_on_labels']);
|
||||
self::assertSame([], $template['exclude_from_labels']);
|
||||
self::assertSame('[Fixture] Layout 1062', $template['layouts']['body']['title']);
|
||||
self::assertSame([1065], $template['layouts']['header']['shared_with'], 'the global header');
|
||||
self::assertSame([], $template['layouts']['body']['shared_with']);
|
||||
self::assertSame('', $result['templates'][0]['layouts']['body']['title'], 'no layout');
|
||||
self::assertSame([], $result['detached']);
|
||||
self::assertSame([], $result['notes']);
|
||||
self::assertArrayNotHasKey('conditions', $result);
|
||||
}
|
||||
|
||||
public function testReportsDetachedAndMarkedTemplates(): void
|
||||
{
|
||||
$store = ThemeBuilderFixtures::store(
|
||||
[ThemeBuilderFixtures::template(1065)],
|
||||
0,
|
||||
[ThemeBuilderFixtures::template(1066, ['attached' => false, 'marked_unused' => '2026-09-27 10:00:00'])]
|
||||
);
|
||||
|
||||
$result = self::ability($store)->execute([]);
|
||||
|
||||
self::assertSame([1066], array_column($result['detached'], 'id'));
|
||||
self::assertSame('2026-09-27 10:00:00', $result['detached'][0]['marked_unused']);
|
||||
self::assertFalse($result['detached'][0]['attached']);
|
||||
self::assertSame([1065], $result['detached'][0]['layouts']['header']['shared_with']);
|
||||
self::assertStringContainsString('reattach', implode(' ', $result['notes']));
|
||||
self::assertSame([1066], array_column(self::ability($store)->execute(['template_id' => 1066])['detached'], 'id'));
|
||||
}
|
||||
|
||||
public function testOneTemplateTheDraftAndTheConditionCatalog(): void
|
||||
{
|
||||
$store = ThemeBuilderFixtures::store([ThemeBuilderFixtures::template(1065), ThemeBuilderFixtures::template(1066)], 999);
|
||||
|
||||
$result = self::ability($store)->execute(['template_id' => 1066, 'conditions' => true]);
|
||||
|
||||
self::assertSame([1066], array_column($result['templates'], 'id'));
|
||||
self::assertTrue($result['draft_pending']);
|
||||
self::assertStringContainsString('Entwurf', implode(' ', $result['notes']));
|
||||
self::assertContains('singular:post_type:lit_person:all', array_column($result['conditions'], 'id'));
|
||||
self::assertToolError(self::ability($store)->execute(['template_id' => 4711]), 'not_found');
|
||||
}
|
||||
|
||||
public function testNeedsTheThemeBuilderRole(): void
|
||||
{
|
||||
$ability = self::ability(ThemeBuilderFixtures::store(), ['theme_builder']);
|
||||
|
||||
self::assertToolError($ability->permission([]), 'forbidden');
|
||||
self::assertToolError($ability->execute([]), 'forbidden');
|
||||
self::assertTrue($ability->annotations()['readonly']);
|
||||
self::assertFalse($ability->isDirect());
|
||||
}
|
||||
|
||||
/** Ruling Q15: without edit_theme_options the Theme Builder is closed. */
|
||||
public function testNeedsEditThemeOptions(): void
|
||||
{
|
||||
LibraryFixtures::given([], ['edit_theme_options']);
|
||||
|
||||
self::assertToolError(self::ability(ThemeBuilderFixtures::store())->execute([]), 'forbidden');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ruling Q8/B11: templates the user may not edit do not appear anywhere – not in the lists, not in
|
||||
* shared_with; asking for one reads exactly like a missing ID (P12).
|
||||
*/
|
||||
public function testHidesTemplatesTheUserCannotEdit(): void
|
||||
{
|
||||
self::denyEditing([1065, 1067]);
|
||||
$store = ThemeBuilderFixtures::store(
|
||||
[ThemeBuilderFixtures::template(1065), ThemeBuilderFixtures::template(1066)],
|
||||
0,
|
||||
[ThemeBuilderFixtures::template(1067, ['attached' => false])]
|
||||
);
|
||||
|
||||
$result = self::ability($store)->execute([]);
|
||||
|
||||
self::assertSame([1066], array_column($result['templates'], 'id'));
|
||||
self::assertSame([], $result['detached']);
|
||||
self::assertSame([], $result['templates'][0]['layouts']['header']['shared_with']);
|
||||
self::assertSame([], $result['notes'], 'no hint about hidden detached templates');
|
||||
$encoded = (string) json_encode($result);
|
||||
self::assertStringNotContainsString('1065', $encoded);
|
||||
self::assertStringNotContainsString('1067', $encoded);
|
||||
|
||||
$hidden = self::ability($store)->execute(['template_id' => 1065]);
|
||||
$missing = self::ability($store)->execute(['template_id' => 4711]);
|
||||
self::assertToolError($hidden, 'not_found');
|
||||
self::assertSame(
|
||||
$missing->get_error_message(),
|
||||
str_replace('1065', '4711', $hidden->get_error_message())
|
||||
);
|
||||
}
|
||||
|
||||
/** Ruling Q8/B11: layout titles only for layouts the user may edit. */
|
||||
public function testLayoutTitlesOnlyForEditableLayouts(): void
|
||||
{
|
||||
self::denyEditing([1062]);
|
||||
$store = ThemeBuilderFixtures::store([ThemeBuilderFixtures::template(1066)]);
|
||||
|
||||
$layouts = self::ability($store)->execute([])['templates'][0]['layouts'];
|
||||
|
||||
self::assertSame('', $layouts['body']['title']);
|
||||
self::assertSame('[Fixture] Layout 1061', $layouts['header']['title']);
|
||||
}
|
||||
|
||||
public function testConditionLabelsNeverNameObjectTitles(): void
|
||||
{
|
||||
Functions\when('get_the_title')->justReturn('Geheime Seite');
|
||||
$store = ThemeBuilderFixtures::store([ThemeBuilderFixtures::template(1066, [
|
||||
'layouts' => [
|
||||
'header' => ['id' => 0, 'enabled' => true],
|
||||
'body' => ['id' => 0, 'enabled' => true],
|
||||
'footer' => ['id' => 0, 'enabled' => true],
|
||||
],
|
||||
'exclude_from' => ['singular:post_type:page:children:id:7', 'unbekannt:x'],
|
||||
])]);
|
||||
|
||||
$result = self::ability($store)->execute([]);
|
||||
|
||||
self::assertSame(
|
||||
['Unterseiten bestimmter Seiten: 7', 'unbekannt:x'],
|
||||
$result['templates'][0]['exclude_from_labels']
|
||||
);
|
||||
self::assertStringNotContainsString('Geheime Seite', (string) json_encode($result));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user