fix: Task 6 review round 1 - admin label overlap, heading default level, structure deny-list, row widths (S41)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01At3V6SjwbMe6eMzVGW3Hs7
This commit is contained in:
2026-09-26 15:26:12 +02:00
co-authored by Claude Opus 5.5
parent 22bb1528db
commit 909ac82594
7 changed files with 294 additions and 11 deletions
@@ -231,6 +231,72 @@ final class ComposerTest extends TestCase
self::assertSame([], $separate['errors'], 'different paths, and the default heading level, may be combined');
}
/** Ruling S41: an admin label given in attrs is kept (no auto label over it); label + attrs on it is an error. */
public function testAdminLabelInAttrs(): void
{
$kept = ComposeFixture::composer()->compose([[
'type' => 'heading',
'text' => 'x',
'attrs' => ['module.meta.adminLabel' => ['desktop' => 'Mein Label', 'tablet' => 'Tablet']],
]], Block::create('divi/column'), 'nodes', 'Hero');
self::assertSame([], $kept['errors']);
self::assertSame(
['desktop' => ['value' => 'Mein Label'], 'tablet' => ['value' => 'Tablet']],
$kept['blocks'][0]->attrs()['module']['meta']['adminLabel']
);
$both = ComposeFixture::composer()->compose([[
'type' => 'heading',
'text' => 'x',
'label' => 'B',
'attrs' => ['module.meta.adminLabel' => ['desktop' => 'A', 'tablet' => 'T']],
]], Block::create('divi/column'), 'nodes', 'Hero');
self::assertStringContainsString('label', $both['errors']['nodes[0].attrs.module.meta.adminLabel']);
$update = ComposeFixture::composer()->update(Block::create('divi/heading'), [
'label' => 'B',
'attrs' => ['module.meta.adminLabel' => ['tablet' => 'T']],
], 'changes[0]');
self::assertStringContainsString('label', $update['errors']['changes[0].attrs.module.meta.adminLabel']);
}
/** Ruling S41: the default heading level survives attrs on the same font value. */
public function testDefaultHeadingLevelIsMergedIntoAttrs(): void
{
$result = ComposeFixture::composer()->compose([[
'type' => 'heading',
'text' => 'x',
'attrs' => ['title.decoration.font.font' => ['desktop' => ['color' => '#111111']]],
]], Block::create('divi/column'), 'nodes');
self::assertSame([], $result['errors']);
self::assertSame(
['headingLevel' => 'h2', 'color' => '#111111'],
$result['blocks'][0]->attrs()['title']['decoration']['font']['font']['desktop']['value']
);
$explicit = ComposeFixture::composer()->compose([[
'type' => 'heading',
'text' => 'x',
'attrs' => ['title.decoration.font.font' => ['desktop' => ['headingLevel' => 'h3']]],
]], Block::create('divi/column'), 'nodes');
self::assertSame('h3', $explicit['blocks'][0]->attrs()['title']['decoration']['font']['font']['desktop']['value']['headingLevel']);
}
/** Ruling S41: column widths on update follow the rules of "columns". */
public function testRowWidthsAreChecked(): void
{
$composer = ComposeFixture::composer();
self::assertNull($composer->rowWidthsError(['8_24', '16_24']));
self::assertNull($composer->rowWidthsError(['1_5', '1_5', '1_5', '1_5', '1_5']));
self::assertNotNull($composer->rowWidthsError(['12_24', '16_24']));
self::assertNotNull($composer->rowWidthsError(['1_13', '12_13']));
self::assertNotNull($composer->rowWidthsError(['x', '24_24']));
self::assertNotNull($composer->rowWidthsError([]));
}
/** Ruling S13: labels go through the choke point; a label that fails is an error, never stored raw. */
public function testLabelsAreCleanedOrReported(): void
{