Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
106 lines
3.7 KiB
PHP
106 lines
3.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace LIT\MCP\Tests\Unit\Integrations\Divi\BlockTree;
|
|
|
|
use InvalidArgumentException;
|
|
use LIT\MCP\Integrations\Divi\BlockTree\Block;
|
|
use LIT\MCP\Integrations\Divi\BlockTree\Parser;
|
|
use LIT\MCP\Integrations\Divi\BlockTree\Serializer;
|
|
use LogicException;
|
|
use PHPUnit\Framework\TestCase;
|
|
use stdClass;
|
|
|
|
final class BlockTest extends TestCase
|
|
{
|
|
/** @return array<string, array{string}> */
|
|
public static function invalidNames(): array
|
|
{
|
|
return [
|
|
'no namespace' => ['button'],
|
|
'trailing markup injection' => ['divi/x --><script>'],
|
|
'trailing newline' => ["divi/x\n"],
|
|
'uppercase' => ['Divi/Button'],
|
|
'empty' => [''],
|
|
];
|
|
}
|
|
|
|
/** @dataProvider invalidNames */
|
|
public function testCreateRejectsInvalidBlockNames(string $name): void
|
|
{
|
|
$this->expectException(InvalidArgumentException::class);
|
|
|
|
Block::create($name);
|
|
}
|
|
|
|
public function testCreateAcceptsAWellFormedName(): void
|
|
{
|
|
self::assertSame('divi/button', Block::create('divi/button')->name());
|
|
}
|
|
|
|
public function testOnlyThePlaceholderIsAPlaceholder(): void
|
|
{
|
|
self::assertTrue(Block::create('divi/placeholder')->isPlaceholder());
|
|
self::assertFalse(Block::create('divi/section')->isPlaceholder());
|
|
self::assertFalse(Block::create('core/placeholder')->isPlaceholder());
|
|
}
|
|
|
|
public function testSetAttrsRefusesOnAnOpaqueGlobalModule(): void
|
|
{
|
|
$document = (new Parser())->parse('<!-- wp:divi/section {"globalModule":"x"} --><!-- /wp:divi/section -->');
|
|
$section = $document->children()[0];
|
|
self::assertTrue($section->isOpaque());
|
|
|
|
$this->expectException(LogicException::class);
|
|
$section->setAttrs(['a' => 1]);
|
|
}
|
|
|
|
public function testSetAttrsRefusesOnABlockWithUndecodableAttrs(): void
|
|
{
|
|
$document = (new Parser())->parse('<!-- wp:divi/text {"a":} /-->');
|
|
$block = $document->children()[0];
|
|
self::assertTrue($block->isOpaque());
|
|
|
|
$this->expectException(LogicException::class);
|
|
$block->setAttrs(['a' => 1]);
|
|
}
|
|
|
|
public function testCopyDeepClonesStdClassAttributeValues(): void
|
|
{
|
|
$block = Block::create('divi/text', ['localAttrs' => new stdClass(), 'nested' => ['obj' => new stdClass()]]);
|
|
$copy = $block->copy();
|
|
|
|
self::assertNotSame($block->attrs()['localAttrs'], $copy->attrs()['localAttrs']);
|
|
self::assertEquals($block->attrs()['localAttrs'], $copy->attrs()['localAttrs']);
|
|
self::assertNotSame($block->attrs()['nested']['obj'], $copy->attrs()['nested']['obj']);
|
|
}
|
|
|
|
public function testCopyAsRegeneratesTheDelimitersAndKeepsChildrenAndText(): void
|
|
{
|
|
$document = (new Parser())->parse("<!-- wp:divi/row {\"a\":1} -->\n<!-- wp:divi/column /-->\n<!-- /wp:divi/row -->");
|
|
$row = $document->children()[0];
|
|
|
|
$copy = $row->copyAs('divi/global-layout', ['globalModule' => '5', 'blockName' => 'divi/row']);
|
|
|
|
self::assertSame(
|
|
"<!-- wp:divi/global-layout {\"globalModule\":\"5\",\"blockName\":\"divi/row\"} -->\n<!-- wp:divi/column /-->\n"
|
|
. '<!-- /wp:divi/global-layout -->',
|
|
Serializer::block($copy)
|
|
);
|
|
self::assertTrue($copy->isOpaque());
|
|
self::assertSame(
|
|
"<!-- wp:divi/row {\"a\":1} -->\n<!-- wp:divi/column /-->\n<!-- /wp:divi/row -->",
|
|
Serializer::block($row)
|
|
);
|
|
self::assertFalse($row->copyAs('divi/row', [])->isOpaque());
|
|
}
|
|
|
|
public function testCopyAsRefusesInvalidNames(): void
|
|
{
|
|
$this->expectException(InvalidArgumentException::class);
|
|
|
|
Block::create('divi/text')->copyAs('Divi Text', []);
|
|
}
|
|
}
|