Reuses NewDraft for insert and rollback (P6), requires edit_global_library for scope global (P7), refuses opaque and global roots before wrapping (P11) and checks the node with StoredNodeGuard. The source page is never written. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
114 lines
4.9 KiB
PHP
114 lines
4.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Library;
|
|
|
|
use LIT\MCP\Integrations\Divi\BlockTree\Block;
|
|
use LIT\MCP\Integrations\Divi\BlockTree\LibraryContent;
|
|
use LIT\MCP\Integrations\Divi\BlockTree\NodeIndex;
|
|
use LIT\MCP\Integrations\Divi\BlockTree\Parser;
|
|
use LIT\MCP\Integrations\Divi\Library\LibraryItemSpec;
|
|
use LIT\MCP\Tests\Support\DiviFixtures;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
final class LibraryItemSpecTest extends TestCase
|
|
{
|
|
private static function pageNode(string $id): Block
|
|
{
|
|
return NodeIndex::build((new Parser())->parse(DiviFixtures::markup('stage3/page--globale-instanzen.html')))
|
|
->find($id);
|
|
}
|
|
|
|
private static function libraryRoot(string $file): Block
|
|
{
|
|
return LibraryContent::roots(LibraryContent::load(new Parser(), DiviFixtures::markup('stage3/' . $file)))[0];
|
|
}
|
|
|
|
/** @return array<string, array{string, bool}> Fixture role => [node source, global]. */
|
|
public static function builderEntries(): array
|
|
{
|
|
return [
|
|
'library--library-abschnitt' => ['page:s1', false],
|
|
'library--library-zeile' => ['page:s1.r0', false],
|
|
'library--library-modul' => ['page:s1.r0.c0.m0', false],
|
|
'library-global--globaler-abschnitt' => ['library:library-global--globaler-abschnitt.html', true],
|
|
'library-global--globales-modul' => ['library:library-global--globales-modul.html', true],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Terms and meta as Divi's own "add to library" wrote them (fixtures stage3, JSON of each entry),
|
|
* without Divi's broken _et_pb_row_layout ("[object Object]", finding 5).
|
|
*
|
|
* @dataProvider builderEntries
|
|
*/
|
|
public function testTermsAndMetaMatchTheBuilderEntries(string $source, bool $global): void
|
|
{
|
|
[$kind, $ref] = explode(':', $source, 2);
|
|
$node = $kind === 'page' ? self::pageNode($ref) : self::libraryRoot($ref);
|
|
$role = (string) $this->dataName();
|
|
$json = json_decode(DiviFixtures::markup('stage3/' . $role . '.json'), true);
|
|
|
|
$spec = LibraryItemSpec::forNode($node, DiviFixtures::catalog(), 'Titel', $global, 'page');
|
|
|
|
self::assertInstanceOf(LibraryItemSpec::class, $spec);
|
|
$expectedTerms = [];
|
|
foreach (['scope', 'layout_type', 'module_width'] as $taxonomy) {
|
|
$expectedTerms[$taxonomy] = array_column($json['terms'][$taxonomy], 'slug');
|
|
}
|
|
self::assertEquals($expectedTerms, $spec->terms());
|
|
$expectedMeta = array_map(static fn (array $values): string => $values[0], $json['meta']);
|
|
unset($expectedMeta['_et_pb_row_layout']);
|
|
self::assertEquals($expectedMeta, $spec->meta());
|
|
}
|
|
|
|
public function testRefusesInstancesColumnsAndGlobalsInsideGlobals(): void
|
|
{
|
|
$catalog = DiviFixtures::catalog();
|
|
|
|
self::assertStringContainsString(
|
|
'bereits ein globales Modul (Bibliothekseintrag 1058)',
|
|
(string) LibraryItemSpec::forNode(self::pageNode('g0'), $catalog, 'T', false, 'page')
|
|
);
|
|
self::assertStringContainsString(
|
|
'nur Abschnitte, Zeilen und Module',
|
|
(string) LibraryItemSpec::forNode(self::pageNode('s1.r0.c0'), $catalog, 'T', false, 'page')
|
|
);
|
|
self::assertStringContainsString(
|
|
'keine globalen Module',
|
|
(string) LibraryItemSpec::forNode(self::pageNode('s0'), $catalog, 'T', true, 'page')
|
|
);
|
|
self::assertInstanceOf(LibraryItemSpec::class, LibraryItemSpec::forNode(self::pageNode('s0'), $catalog, 'T', false, 'page'));
|
|
}
|
|
|
|
/** Ruling P11: opaque roots and instances without a readable id are refused before wrapping. */
|
|
public function testRefusesOpaqueRootsAndUnreadableInstances(): void
|
|
{
|
|
$catalog = DiviFixtures::catalog();
|
|
$document = (new Parser())->parse(
|
|
'<!-- wp:divi/section {"module":kaputt} --><!-- /wp:divi/section -->'
|
|
. '<!-- wp:divi/global-layout {"globalModule":["x"],"blockName":"divi/section"} /-->'
|
|
);
|
|
[$opaque, $instance] = $document->children();
|
|
self::assertTrue($opaque->isOpaque());
|
|
|
|
self::assertStringContainsString('nicht lesbar', (string) LibraryItemSpec::forNode($opaque, $catalog, 'T', false, 'page'));
|
|
self::assertStringContainsString(
|
|
'globales Modul',
|
|
(string) LibraryItemSpec::forNode($instance, $catalog, 'T', false, 'page')
|
|
);
|
|
}
|
|
|
|
public function testFullwidthAndTerms(): void
|
|
{
|
|
$section = Block::create('divi/section', ['module' => ['advanced' => ['type' => ['desktop' => ['value' => 'fullwidth']]]]]);
|
|
|
|
$spec = LibraryItemSpec::forNode($section, DiviFixtures::catalog(), 'Kopf', false, 'page');
|
|
|
|
self::assertSame(['fullwidth'], $spec->terms()['module_width']);
|
|
self::assertSame([26], $spec->withTerms([26], [])->terms()['layout_category']);
|
|
self::assertArrayNotHasKey('layout_tag', $spec->withTerms([26], [])->terms());
|
|
}
|
|
}
|