Files
WP-LIT-MCP/tests/Unit/Integrations/Divi/Compose/LoopContextTest.php
T

186 lines
8.8 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\Compose;
use LIT\MCP\Integrations\Divi\BlockTree\NodeIndex;
use LIT\MCP\Integrations\Divi\BlockTree\Parser;
use LIT\MCP\Integrations\Divi\Compose\LoopContext;
use LIT\MCP\Integrations\Divi\Compose\LoopScope;
use PHPUnit\Framework\TestCase;
/** Ruling X1: the nearest ancestor-or-self with desktop.value.enable === "on" decides (LoopUtils.php:4720). */
final class LoopContextTest extends TestCase
{
/**
* Block attributes with module.advanced.loop.
*
* @param mixed $loop The value of module.advanced.loop
* @return array<string, mixed>
*/
private static function attrs(mixed $loop): array
{
return ['module' => ['advanced' => ['loop' => $loop]], 'builderVersion' => '5.15.1'];
}
/**
* @param array<string, mixed> $value desktop.value of the loop
* @return array<string, mixed>
*/
private static function loop(array $value): array
{
return self::attrs(['desktop' => ['value' => $value]]);
}
public function testOnlyTheStringOnAtDesktopValueEnablesALoop(): void
{
self::assertTrue(LoopContext::enabled(self::loop(['enable' => 'on'])));
$off = [
'off' => self::loop(['enable' => 'off']),
'true' => self::loop(['enable' => true]),
'int 1' => self::loop(['enable' => 1]),
'"1"' => self::loop(['enable' => '1']),
'"true"' => self::loop(['enable' => 'true']),
'"ON"' => self::loop(['enable' => 'ON']),
'missing enable' => self::loop(['queryType' => 'post_types']),
'tablet only' => self::attrs(['tablet' => ['value' => ['enable' => 'on']]]),
'phone only' => self::attrs(['phone' => ['value' => ['enable' => 'on']]]),
'hover only' => self::attrs(['desktop' => ['hover' => ['enable' => 'on']]]),
'sticky only' => self::attrs(['desktop' => ['sticky' => ['enable' => 'on']]]),
'no loop' => ['builderVersion' => '5.15.1'],
'loop is a string' => self::attrs('on'),
'desktop is a string' => self::attrs(['desktop' => 'on']),
'value is a string' => self::attrs(['desktop' => ['value' => 'on']]),
'value is a list' => self::attrs(['desktop' => ['value' => ['on']]]),
'empty JSON object' => self::attrs(new \stdClass()),
'advanced is a string' => ['module' => ['advanced' => 'loop']],
];
foreach ($off as $case => $attrs) {
self::assertFalse(LoopContext::enabled($attrs), $case);
self::assertNull(LoopContext::ownScope($attrs), $case);
}
}
public function testOwnScopeReadsTheQueryTypeWithPostTypesAsDefault(): void
{
self::assertSame('post_types', LoopContext::ownScope(self::loop(['enable' => 'on']))?->queryType());
self::assertSame(LoopScope::POST, LoopContext::ownScope(self::loop(['enable' => 'on']))?->kind());
self::assertSame(
'post_types',
LoopContext::ownScope(self::loop(['enable' => 'on', 'queryType' => null]))?->queryType()
);
self::assertSame(
LoopScope::TERM,
LoopContext::ownScope(self::loop(['enable' => 'on', 'queryType' => 'post_taxonomies']))?->kind()
);
self::assertSame(
LoopScope::USER,
LoopContext::ownScope(self::loop(['enable' => 'on', 'queryType' => 'user_roles']))?->kind()
);
self::assertSame(
LoopScope::MENU,
LoopContext::ownScope(self::loop(['enable' => 'on', 'queryType' => 'menus']))?->kind()
);
}
public function testAnUnknownOrMalformedQueryTypeIsALoopWithoutKind(): void
{
$repeater = LoopContext::ownScope(self::loop(['enable' => 'on', 'queryType' => 'repeater_team']));
self::assertNotNull($repeater);
self::assertFalse($repeater->active());
self::assertSame('repeater_team', $repeater->queryType());
// sanitize_key() turns a non-string into "" – no post_types default (LoopUtils.php:730-732 isset()).
$array = LoopContext::ownScope(self::loop(['enable' => 'on', 'queryType' => ['post_types']]));
self::assertNotNull($array);
self::assertFalse($array->active());
self::assertSame('', $array->queryType());
}
public function testTheInnermostEnabledLoopOfTheChainWins(): void
{
$posts = self::loop(['enable' => 'on', 'queryType' => 'post_types']);
$terms = self::loop(['enable' => 'on', 'queryType' => 'post_taxonomies']);
$plain = ['builderVersion' => '5.15.1'];
$disabled = self::loop(['enable' => 'off', 'queryType' => 'user_roles']);
self::assertSame(LoopScope::TERM, LoopContext::fromChain([$plain, $posts, $terms, $plain])->kind());
self::assertSame(LoopScope::POST, LoopContext::fromChain([$terms, $posts])->kind(), 'post loop inside terms');
self::assertSame(LoopScope::POST, LoopContext::fromChain([$posts, $disabled, $plain])->kind());
self::assertSame(LoopScope::POST, LoopContext::fromChain([$posts])->kind(), 'the node itself');
self::assertNull(LoopContext::fromChain([$plain, $disabled])->queryType());
self::assertNull(LoopContext::fromChain([])->queryType());
// An unsupported inner loop still hides the outer one: Divi resolves the inner loop first.
$repeater = self::loop(['enable' => 'on', 'queryType' => 'repeater_team']);
$inner = LoopContext::fromChain([$posts, $repeater, $plain]);
self::assertFalse($inner->active());
self::assertSame('repeater_team', $inner->queryType());
}
private static function page(): NodeIndex
{
$loop = static fn (string $queryType): string => '{"module":{"advanced":{"loop":{"desktop":{"value":'
. '{"enable":"on","loopId":"loop-lit4etest1","queryType":"' . $queryType . '"}}}}},'
. '"builderVersion":"5.15.1"}';
$off = '{"module":{"advanced":{"loop":{"desktop":{"value":{"enable":"off","queryType":"menus"}},'
. '"tablet":{"value":{"enable":"on"}}}}},"builderVersion":"5.15.1"}';
$text = '<!-- wp:divi/text {"builderVersion":"5.15.1"} /-->';
return NodeIndex::build((new Parser())->parse(
'<!-- wp:divi/placeholder -->'
. '<!-- wp:divi/section {"builderVersion":"5.15.1"} --><!-- wp:divi/row {"builderVersion":"5.15.1"} -->'
// s0.r0.c0: a post loop, a term loop group inside it, a disabled loop group inside that.
. '<!-- wp:divi/column ' . $loop('post_types') . ' -->' . $text
. '<!-- wp:divi/group ' . $loop('post_taxonomies') . ' -->' . $text
. '<!-- wp:divi/group ' . $off . ' -->' . $text . '<!-- /wp:divi/group -->'
. '<!-- /wp:divi/group -->'
. '<!-- /wp:divi/column -->'
// s0.r0.c1: no loop; a global module instance claiming a loop (opaque: not read).
. '<!-- wp:divi/column {"builderVersion":"5.15.1"} -->' . $text
. '<!-- wp:divi/text {"globalModule":"812","module":{"advanced":{"loop":{"desktop":{"value":'
. '{"enable":"on"}}}}},"builderVersion":"5.15.1"} /-->'
. '<!-- /wp:divi/column -->'
. '<!-- /wp:divi/row --><!-- /wp:divi/section -->'
// s1: the section itself is a user loop.
. '<!-- wp:divi/section ' . $loop('user_roles') . ' --><!-- wp:divi/row {"builderVersion":"5.15.1"} -->'
. '<!-- wp:divi/column {"builderVersion":"5.15.1"} -->' . $text . '<!-- /wp:divi/column -->'
. '<!-- /wp:divi/row --><!-- /wp:divi/section -->'
. '<!-- /wp:divi/placeholder -->'
));
}
public function testTheTreeWalksAncestorsOrSelfByPathId(): void
{
$index = self::page();
// The ids the cases below rely on.
self::assertSame('divi/group', $index->find('s0.r0.c0.m1.m1')?->name());
self::assertTrue($index->find('s0.r0.c1.g0')?->isOpaque());
$kinds = [
's0' => null,
's0.r0' => null,
's0.r0.c0' => LoopScope::POST,
's0.r0.c0.m0' => LoopScope::POST,
's0.r0.c0.m1' => LoopScope::TERM,
's0.r0.c0.m1.m0' => LoopScope::TERM,
's0.r0.c0.m1.m1' => LoopScope::TERM,
's0.r0.c0.m1.m1.m0' => LoopScope::TERM,
's0.r0.c1' => null,
's0.r0.c1.m0' => null,
's0.r0.c1.g0' => null,
's1' => LoopScope::USER,
's1.r0.c0.m0' => LoopScope::USER,
'nope' => null,
's9.r0' => null,
];
foreach ($kinds as $id => $kind) {
self::assertSame($kind, LoopContext::of($index, $id)->kind(), $id);
}
self::assertSame('post_taxonomies', LoopContext::of($index, 's0.r0.c0.m1.m1.m0')->queryType());
self::assertNull(LoopContext::of($index, 's0.r0.c1.g0')->queryType());
}
}