feat: lit/library-save creates library entries like Divi (E4)
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>
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
<?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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user