fix: Variablen-Detach meldet verbleibende eingebettete Verweise und lehnt Werte mit Verweis, Markup oder leerem Inhalt ab

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Jan-Niclas Loosen
2026-10-19 14:19:41 +02:00
co-authored by Claude Sonnet 5.5
parent e951cc1353
commit 62f8465e60
9 changed files with 303 additions and 35 deletions
@@ -88,10 +88,10 @@ final class DiviDesignDetachAbilityTest extends TestCase
}
/** Reads variables() (the Variablen-Verwaltung is allowed unless $manage is false), never saves. */
private static function variableAdapter(bool $manage = true): GlobalVariablesAdapter&MockInterface
private static function variableAdapter(bool $manage = true, string $radius = '8px'): GlobalVariablesAdapter&MockInterface
{
$variables = Mockery::mock(GlobalVariablesAdapter::class);
$variables->allows('read')->andReturn(self::variables());
$variables->allows('read')->andReturn(self::variables($radius));
$variables->allows('canManage')->andReturn($manage);
$variables->expects('save')->never();
@@ -99,11 +99,11 @@ final class DiviDesignDetachAbilityTest extends TestCase
}
/** @return array<string, array<string, array<string, mixed>>> Global variables in Divi's stored form. */
private static function variables(): array
private static function variables(string $radius = '8px'): array
{
return [
'numbers' => [self::RADIUS => [
'id' => self::RADIUS, 'label' => 'Eckenradius', 'value' => '8px', 'order' => '1',
'id' => self::RADIUS, 'label' => 'Eckenradius', 'value' => $radius, 'order' => '1',
'status' => 'active', 'lastUpdated' => '2026-10-01T10:00:00.000Z', 'variableType' => 'numbers',
]],
// Archived or not does not matter for a detach (V9).
@@ -294,6 +294,74 @@ final class DiviDesignDetachAbilityTest extends TestCase
);
}
/** @return array<string, array{string, bool}> */
public static function unsafeValues(): array
{
return [
'empty' => ['', true],
'marker' => ['$variable({"type":"content"})$', true],
'end marker' => ['x)$', true],
'markup without unfiltered_html' => ['<b>8px</b>', false],
];
}
/** @dataProvider unsafeValues */
public function testAValueThatMustNotBeInlinedRefusesTheCall(string $value, bool $unfiltered): void
{
$ability = $this->ability(
self::untouched(),
null,
null,
null,
null,
$unfiltered,
self::variableAdapter(true, $value)
);
$error = self::assertToolError($ability->execute(['variable' => 'Eckenradius']), 'unsupported');
self::assertStringContainsString('Divi-Builder', $error['hint']);
}
public function testMarkupInTheValueIsAllowedWithUnfilteredHtml(): void
{
$pages = Mockery::mock(PageRewriter::class);
$pages->expects('preview')->andReturn(PageRewriteReportTest::preview(1));
$ability = $this->ability($pages, null, null, null, null, true, self::variableAdapter(true, '<b>8px</b>'));
self::assertToolError($ability->execute(['variable' => 'Eckenradius']), 'confirm_required');
}
public function testReferencesLeftOnPagesNeverGetTheDoneHint(): void
{
$preview = PageRewriteReportTest::preview(0);
$preview['skipped'] = [['post_id' => 12, 'reason' => 's0.r0.c0.m0: Die Variable steht hier eingebettet']];
$pages = Mockery::mock(PageRewriter::class);
$pages->expects('preview')->andReturn($preview);
$result = $this->ability($pages)->execute(['variable' => 'Eckenradius']);
self::assertStringNotContainsString('lit/divi-variables-set', $result['summary']);
self::assertStringNotContainsString('Keine Verweise', $result['summary']);
self::assertSame($preview['skipped'], $result['skipped']);
}
public function testEmbeddedReferencesInPresetsAreReportedAndWithholdTheDoneHint(): void
{
$stored = PresetBookTest::stored();
$stored['module']['divi/button']['items']['aaaaaaaaaa']['attrs'] = ['button' => ['decoration' => [
'border' => ['desktop' => ['value' => ['radius' => 'Ab ' . VariableDetachRewriteTest::ref(self::RADIUS) . ' x']]],
]]];
$pages = Mockery::mock(PageRewriter::class);
$pages->expects('preview')->andReturn(PageRewriteReportTest::preview(0));
$result = $this->ability($pages, self::readOnlyWriter($stored))->execute(['variable' => 'Eckenradius']);
self::assertStringNotContainsString('lit/divi-variables-set', $result['summary']);
self::assertStringContainsString('In 1 Presets steht die Variable eingebettet', $result['warnings'][0]);
self::assertStringContainsString('CTA primär', $result['warnings'][0]);
}
public function testVariablePostIdsSkipThePresetStep(): void
{
$pages = Mockery::mock(PageRewriter::class);