TB layouts load through ThemeBuilderAccess (P12), live layouts (attached + enabled, Q8) need a dry run and confirm (Q9) and get the shared restore point of VisibleEdits, extracted from GlobalEdits (Q2: backup when revisions are off, P9 refusals, P14 naming). divi/post-content only in body layouts (F22), also for nested stored nodes. SaveScope::ThemeBuilderLayout sets Divi flags and clears site CSS + TB cache. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
97 lines
4.9 KiB
PHP
97 lines
4.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Compose;
|
|
|
|
use LIT\MCP\Tests\Support\DiviFixtures;
|
|
use LIT\MCP\Integrations\Divi\Compose\StructureRules;
|
|
use LIT\MCP\Integrations\Divi\BlockTree\Block;
|
|
use LIT\MCP\Tests\Support\ComposeFixture;
|
|
use PHPUnit\Framework\TestCase;
|
|
|
|
final class StructureRulesTest extends TestCase
|
|
{
|
|
/** @return array<string, array{?Block, string, ?string}> Parent, child, part of the reason (null = allowed). */
|
|
public static function placements(): array
|
|
{
|
|
$section = static fn (string $type): Block => Block::create(
|
|
'divi/section',
|
|
['module' => ['advanced' => ['type' => ['desktop' => ['value' => $type]]]]]
|
|
);
|
|
|
|
return [
|
|
'section on top' => [null, 'divi/section', null],
|
|
'module on top' => [null, 'divi/text', 'oberster Ebene'],
|
|
'unknown module' => [Block::create('divi/column'), 'divi/slider-pro', 'unbekanntes Modul'],
|
|
'row in a section' => [Block::create('divi/section'), 'divi/row', null],
|
|
'module in a section' => [Block::create('divi/section'), 'divi/text', 'Zeilen'],
|
|
'fullwidth module in a fullwidth section' => [$section('fullwidth'), 'divi/fullwidth-header', null],
|
|
'row in a fullwidth section' => [$section('fullwidth'), 'divi/row', 'fullwidth-Module'],
|
|
'anything in a specialty section' => [$section('specialty'), 'divi/row', 'Spezialabschnitte'],
|
|
'column in a row' => [Block::create('divi/row'), 'divi/column', 'Spalten entstehen mit der Zeile'],
|
|
'module in a column' => [Block::create('divi/column'), 'divi/button', null],
|
|
'row in a column (nestable)' => [Block::create('divi/column'), 'divi/row', null],
|
|
'row in a group' => [Block::create('divi/group'), 'divi/row', 'passt nicht'],
|
|
'module in a specialty column' => [
|
|
Block::create('divi/column', ['module' => ['advanced' => ['specialtyColumns' => ['desktop' => ['value' => '2']]]]]),
|
|
'divi/text',
|
|
'Spezial',
|
|
],
|
|
'module in a column holding inner rows' => [
|
|
Block::create('divi/column', [], [Block::create('divi/row-inner')]),
|
|
'divi/text',
|
|
'Spezial',
|
|
],
|
|
'group in a column' => [Block::create('divi/column'), 'divi/group', null],
|
|
'module in a group' => [Block::create('divi/group'), 'divi/text', null],
|
|
'fullwidth module in a column' => [Block::create('divi/column'), 'divi/fullwidth-header', 'passt nicht'],
|
|
'child module in a column' => [Block::create('divi/column'), 'divi/accordion-item', 'passt nicht'],
|
|
'section in a column' => [Block::create('divi/column'), 'divi/section', 'passt nicht'],
|
|
'item in its parent' => [Block::create('divi/accordion'), 'divi/accordion-item', null],
|
|
'other module in a parent' => [Block::create('divi/accordion'), 'divi/text', 'erlaubt nur divi/accordion-item'],
|
|
'child in a plain module' => [Block::create('divi/text'), 'divi/button', 'keine Kind-Module'],
|
|
'opaque parent' => [Block::parsed('divi/column', [], '<!-- wp:divi/column -->', false, true), 'divi/text', 'nur lesbar'],
|
|
'foreign parent' => [Block::create('core/group'), 'divi/text', 'nur lesbar'],
|
|
];
|
|
}
|
|
|
|
/** @dataProvider placements */
|
|
public function testWhatGoesWhere(?Block $parent, string $child, ?string $reason): void
|
|
{
|
|
$violation = ComposeFixture::rules()->violation($parent, $child);
|
|
|
|
if ($reason === null) {
|
|
self::assertNull($violation);
|
|
} else {
|
|
self::assertIsString($violation);
|
|
self::assertStringContainsString($reason, $violation);
|
|
}
|
|
}
|
|
|
|
public function testPlainModules(): void
|
|
{
|
|
$rules = ComposeFixture::rules();
|
|
|
|
self::assertTrue($rules->isPlainModule('divi/button'));
|
|
self::assertFalse($rules->isPlainModule('divi/fullwidth-header'));
|
|
self::assertFalse($rules->isPlainModule('divi/accordion-item'));
|
|
self::assertFalse($rules->isPlainModule('divi/row'));
|
|
}
|
|
|
|
/** F22: Divi renders divi/post-content only in body layouts (appearance.themeBuilderLayouts). */
|
|
public function testModulesLimitedToThemeBuilderAreas(): void
|
|
{
|
|
$catalog = DiviFixtures::catalog();
|
|
$column = Block::create('divi/column');
|
|
|
|
self::assertNull((new StructureRules($catalog, 'body'))->violation($column, 'divi/post-content'));
|
|
self::assertSame(
|
|
'divi/post-content zeigt Divi im Bereich „header“ nicht (nur body)',
|
|
(new StructureRules($catalog, 'header'))->violation($column, 'divi/post-content')
|
|
);
|
|
self::assertNull((new StructureRules($catalog))->violation($column, 'divi/post-content'), 'pages: Divi decides');
|
|
self::assertNull((new StructureRules($catalog, 'footer'))->violation($column, 'divi/text'));
|
|
}
|
|
}
|