feat: {{link:ID}} placeholder via Divi's post_link_url dynamic content

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-27 00:32:59 +02:00
co-authored by Claude Opus 5.5
parent 404f0f34b9
commit 788943e809
11 changed files with 304 additions and 21 deletions
@@ -5,6 +5,11 @@ declare(strict_types=1);
namespace LIT\MCP\Tests\Unit\Integrations\Divi\Compose;
use InvalidArgumentException;
use LIT\MCP\Integrations\Divi\Compose\Placeholders;
use LIT\MCP\Integrations\Divi\Compose\ShortformWriter;
use LIT\MCP\Integrations\Divi\Compose\ValueCleaner;
use LIT\MCP\Integrations\Divi\Compose\VariableRef;
use LIT\MCP\Integrations\Divi\Pages\DynamicContentMap;
use LIT\MCP\Tests\Support\ComposeFixture;
use LIT\MCP\Tests\Support\DiviFixtures;
use PHPUnit\Framework\TestCase;
@@ -352,5 +357,73 @@ final class ShortformWriterTest extends TestCase
self::assertStringContainsString('gcid-a1b2', $color['attrs']['divider']['advanced']['line']['desktop']['value']['color']);
}
}
}
/** E13a: {{link:ID}} becomes Divi's post_link_url_<type> in URL fields – and only there. */
public function testInternalLinkOnlyInUrlFields(): void
{
$writer = self::linkWriter();
$button = $writer->apply('button', [], ['text' => 'Kontakt', 'url' => '{{link:839}}'], 'n');
self::assertSame([], $button['errors']);
self::assertSame(
'$variable({"type":"content","value":{"name":"post_link_url_page","settings":{"post_id":"839"}}})$',
$button['attrs']['button']['innerContent']['desktop']['value']['linkUrl']
);
$text = $writer->apply('button', [], ['text' => '{{link:839}}'], 'n');
self::assertStringContainsString('nur in Link- und URL-Feldern', $text['errors']['n.text']);
// A post type the registry offers no link option for (fixture: page only): unknown name.
$post = $writer->apply('button', [], ['url' => '{{link:840}}'], 'n');
self::assertStringContainsString('post_link_url_post', $post['errors']['n.url']);
}
/**
* The full form goes through the same choke point: URL fields only, no before/after, and the
* target must be readable (select_post, ruling S37 / preflight §4) – no path around ValueCleaner.
*/
public function testInternalLinkFullFormIsCheckedLikeThePlaceholder(): void
{
$writer = self::linkWriter();
$link = static fn (string $settings): string =>
'$variable({"type":"content","value":{"name":"post_link_url_page","settings":' . $settings . '}})$';
$valid = $writer->apply('button', [], ['url' => $link('{"post_id":839}')], 'n');
self::assertSame([], $valid['errors']);
self::assertSame($link('{"post_id":839}'), $valid['attrs']['button']['innerContent']['desktop']['value']['linkUrl']);
$unreadable = $writer->apply('button', [], ['url' => $link('{"post_id":"666"}')], 'n');
self::assertStringContainsString('lesbaren Eintrags', $unreadable['errors']['n.url']);
$slug = $writer->apply('button', [], ['url' => $link('{"post_id":"kontakt"}')], 'n');
self::assertStringContainsString('lesbaren Eintrags', $slug['errors']['n.url']);
$extra = $writer->apply('button', [], ['url' => $link('{"post_id":"839","before":"<b>x</b>"}')], 'n');
self::assertArrayHasKey('n.url', $extra['errors']);
self::assertSame([], $extra['attrs']);
$inText = $writer->apply('heading', [], ['text' => $link('{"post_id":"839"}')], 'n');
self::assertStringContainsString('nur in Link- und URL-Feldern', $inText['errors']['n.text']);
// A control-split placeholder is the placeholder (S35) and resolves the same way.
$split = $writer->apply('button', [], ['url' => '{' . chr(0) . '{link:839}}'], 'n');
self::assertSame([], $split['errors']);
self::assertSame(
$link('{"post_id":"839"}'),
$split['attrs']['button']['innerContent']['desktop']['value']['linkUrl']
);
}
/** ShortformWriter with the live registry map (knows post_link_url_page) and the test link targets. */
private static function linkWriter(): ShortformWriter
{
$design = DiviFixtures::designSystem();
$cleaner = new ValueCleaner(
DiviFixtures::catalog(),
new VariableRef($design, DynamicContentMap::fromOptions(DiviFixtures::dynamicContentRegistry())),
ComposeFixture::sanitizer()
);
return new ShortformWriter(new Placeholders($design, ComposeFixture::links()), ComposeFixture::media(), $cleaner);
}
}