feat: lit/divi-dynamic-sources mit post_id + node – Schleife des Knotens und passende loop_*-Quellen (X6)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
5b52e18a31
commit
02e7c3e8ec
@@ -6,6 +6,7 @@ namespace LIT\MCP\Tests\Unit\Integrations\Divi\Pages;
|
||||
|
||||
use Brain\Monkey\Functions;
|
||||
use LIT\MCP\Integrations\Divi\Pages\DiviDynamicSourcesAbility;
|
||||
use LIT\MCP\Integrations\Divi\Pages\DiviPageLoader;
|
||||
use LIT\MCP\Integrations\Divi\Pages\DynamicContentMap;
|
||||
use LIT\MCP\Integrations\WordPress\Schema\ContentSchema;
|
||||
use LIT\MCP\Integrations\WordPress\Schema\FieldDefinition;
|
||||
@@ -16,6 +17,9 @@ use Mockery;
|
||||
|
||||
final class DiviDynamicSourcesAbilityTest extends TestCase
|
||||
{
|
||||
/** @var list<int> Post IDs Divi's registry was asked for. */
|
||||
private array $registryCalls = [];
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
@@ -51,19 +55,118 @@ final class DiviDynamicSourcesAbilityTest extends TestCase
|
||||
private function ability(
|
||||
array $deniedRoles = [],
|
||||
?string $storage = 'meta',
|
||||
?array $registry = null
|
||||
?array $registry = null,
|
||||
?DiviPageLoader $loader = null
|
||||
): DiviDynamicSourcesAbility {
|
||||
$registry ??= DiviFixtures::dynamicContentRegistry();
|
||||
|
||||
return new DiviDynamicSourcesAbility(
|
||||
$this->schema(),
|
||||
new DynamicContentMap(static fn (int $id): array => $registry),
|
||||
new DynamicContentMap(function (int $id) use ($registry): array {
|
||||
$this->registryCalls[] = $id;
|
||||
|
||||
return $registry;
|
||||
}),
|
||||
LibraryFixtures::roles($deniedRoles),
|
||||
static fn (string $postType): int => 831,
|
||||
static fn (string $postType): ?string => $storage
|
||||
static fn (string $postType): ?string => $storage,
|
||||
$loader
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ruling X6: the ability with a loader over post 7 – s0.r0.c0 runs a term loop, s0.r0.c1 none – and a
|
||||
* registry with one post and one term loop option (Divi 5.15.1 groups and fields).
|
||||
*/
|
||||
private function withLoopPage(): DiviDynamicSourcesAbility
|
||||
{
|
||||
$loop = ['module' => ['advanced' => ['loop' => ['desktop' => ['value' => [
|
||||
'enable' => 'on', 'queryType' => 'post_taxonomies',
|
||||
]]]]]];
|
||||
$text = '<!-- wp:divi/text {"builderVersion":"5.2.1"} --><!-- /wp:divi/text -->';
|
||||
$column = static fn (array $attrs): string => '<!-- wp:divi/column '
|
||||
. json_encode($attrs + ['builderVersion' => '5.2.1']) . ' -->' . $text . '<!-- /wp:divi/column -->';
|
||||
DiviFixtures::givenPost(
|
||||
'<!-- wp:divi/placeholder --><!-- wp:divi/section {"builderVersion":"5.2.1"} --><!-- wp:divi/row '
|
||||
. '{"builderVersion":"5.2.1"} -->' . $column($loop) . $column([])
|
||||
. '<!-- /wp:divi/row --><!-- /wp:divi/section --><!-- /wp:divi/placeholder -->'
|
||||
);
|
||||
$field = static fn (string $type): array => ['label' => 'x', 'type' => $type, 'default' => ''];
|
||||
$common = ['before' => $field('text'), 'after' => $field('text'), 'loop_position' => $field('text')];
|
||||
$registry = DiviFixtures::dynamicContentRegistry() + [
|
||||
'loop_post_date' => ['label' => 'Datum', 'group' => 'Loop', 'fields' => $common + [
|
||||
'date_format' => $field('select'),
|
||||
]],
|
||||
'loop_term_name' => ['label' => 'Name', 'group' => 'Loop Terms', 'fields' => $common],
|
||||
];
|
||||
|
||||
return $this->ability([], 'meta', $registry, new DiviPageLoader(DiviFixtures::schema()));
|
||||
}
|
||||
|
||||
/** Ruling X6: post_id + node add the loop sources of the node's loop kind, read from that entry. */
|
||||
public function testANodeInALoopAddsTheLoopSourcesOfItsKind(): void
|
||||
{
|
||||
$result = $this->withLoopPage()->execute(['post_type' => 'page', 'post_id' => 7, 'node' => 's0.r0.c0.m0']);
|
||||
|
||||
self::assertSame(7, $result['sample_post_id'], 'the entry itself, not a sample');
|
||||
self::assertSame([7], array_values(array_unique($this->registryCalls)));
|
||||
self::assertSame(
|
||||
['node' => 's0.r0.c0.m0', 'query_type' => 'post_taxonomies', 'label' => 'Terme'],
|
||||
$result['loop']
|
||||
);
|
||||
// The fixture registry also has the meta key option of each kind: only the term one fits, no short form.
|
||||
self::assertSame(
|
||||
['loop_term_name', 'loop_term_meta_key_manual_custom_field'],
|
||||
array_column($result['loop_sources'], 'name')
|
||||
);
|
||||
self::assertSame('{{loop:term_name}}', $result['loop_sources'][0]['placeholder']);
|
||||
self::assertNull($result['loop_sources'][1]['placeholder']);
|
||||
self::assertEquals(
|
||||
['before' => 'richtext', 'after' => 'richtext', 'loop_position' => 'text'],
|
||||
(array) $result['loop_sources'][0]['settings']
|
||||
);
|
||||
self::assertSame([], array_filter(
|
||||
array_column($result['sources'], 'name'),
|
||||
static fn (string $name): bool => str_starts_with($name, 'loop_')
|
||||
));
|
||||
}
|
||||
|
||||
public function testANodeOutsideALoopGetsNoLoopSources(): void
|
||||
{
|
||||
$result = $this->withLoopPage()->execute(['post_type' => 'page', 'post_id' => 7, 'node' => 's0.r0.c1.m0']);
|
||||
|
||||
self::assertSame(['node' => 's0.r0.c1.m0', 'query_type' => null, 'label' => null], $result['loop']);
|
||||
self::assertSame([], $result['loop_sources']);
|
||||
self::assertStringContainsString('kein Schleifen-Kontext', implode(' ', $result['notes']));
|
||||
}
|
||||
|
||||
public function testWithoutANodeTheAnswerHasNoLoopPart(): void
|
||||
{
|
||||
$result = $this->ability()->execute(['post_type' => 'page']);
|
||||
|
||||
self::assertArrayNotHasKey('loop', $result);
|
||||
self::assertArrayNotHasKey('loop_sources', $result);
|
||||
}
|
||||
|
||||
public function testPostIdAndNodeComeTogetherAndNeedTheEntry(): void
|
||||
{
|
||||
$ability = $this->withLoopPage();
|
||||
|
||||
foreach ([['post_id' => 7], ['node' => 's0']] as $half) {
|
||||
self::assertToolError($ability->permission(['post_type' => 'page'] + $half), 'validation_failed');
|
||||
self::assertToolError($ability->execute(['post_type' => 'page'] + $half), 'validation_failed');
|
||||
}
|
||||
$node = ['post_type' => 'page', 'post_id' => 7, 'node' => 's0'];
|
||||
self::assertToolError($ability->execute(['node' => 's9'] + $node), 'not_found');
|
||||
self::assertToolError($ability->execute(['node' => 'x'] + $node), 'validation_failed');
|
||||
self::assertToolError($ability->execute(['post_type' => 'post'] + $node), 'validation_failed');
|
||||
|
||||
Functions\when('current_user_can')->alias(static fn (string $cap): bool => $cap !== 'edit_post');
|
||||
self::assertToolError($ability->permission($node), 'forbidden');
|
||||
self::assertToolError($ability->execute($node), 'forbidden');
|
||||
self::assertSame([], $this->registryCalls, 'nothing is read for a refused entry');
|
||||
}
|
||||
|
||||
public function testListsDiviSourcesWithTheirPlaceholders(): void
|
||||
{
|
||||
$result = $this->ability()->execute(['post_type' => 'page']);
|
||||
|
||||
@@ -203,6 +203,14 @@ final class DiviToolFeaturesTest extends TestCase
|
||||
'lit/divi-design-restore', ['snapshot_id' => 'theme_options-20261016T100000Z-aaaaaa'],
|
||||
['theme-option-read', 'theme-option-write'],
|
||||
],
|
||||
'dynamic-sources of a node' => [
|
||||
'lit/divi-dynamic-sources', ['post_type' => 'page', 'post_id' => $page, 'node' => 's0'],
|
||||
['dynamic-sources', 'roles', 'page-read'],
|
||||
],
|
||||
'dynamic-sources of a node in a layout' => [
|
||||
'lit/divi-dynamic-sources', ['post_type' => 'et_body_layout', 'post_id' => $layout, 'node' => 's0'],
|
||||
['dynamic-sources', 'roles', 'page-read'],
|
||||
],
|
||||
'library-list' => ['lit/library-list', [], ['library-read', 'roles']],
|
||||
'library-list of one entry' => [
|
||||
'lit/library-list', ['id' => $library], ['library-read', 'roles', 'page-read', 'design-read'],
|
||||
|
||||
Reference in New Issue
Block a user