Randlose Abschnitte, volle Bildschirmhoehe, Vollbild-Hero
CMS quality / php (8.2) (push) Canceled after 0s
CMS quality / php (8.4) (push) Canceled after 0s
CMS quality / browser (push) Canceled after 0s
CMS quality / mysql (push) Canceled after 0s

Umuts Frage: "Haben wir bilder usw als volles bild startseite? 100% hoehe
usw?" Die Teile gab es, die Kombination nicht - und zwei Sachen fehlten
wirklich.

Abschnittsbreiten: neu "randlos" (ohne Begrenzung, ohne Rand, von Kante zu
Kante) und "breit" (1600 px) neben normal und schmal. Vorher war jeder Inhalt
auf 1180 px begrenzt; ein Bild von Kante zu Kante war nicht moeglich.

Hoeheneinheiten: neu dvh, svh, lvh. 100vh ist auf dem Handy hoeher als der
Bildschirm - es meint die Ansicht ohne Adressleiste, und der untere Rand
verschwindet darunter. 100dvh ist das, was jemand meint, der "volle Hoehe"
sagt.

Und eine Vorlage "Vollbild-Hero", die die ganze Kombination auf einen Klick
setzt: randlos, 100dvh (mobil 80dvh), Inhalt senkrecht zentriert,
Hintergrundbild formatfuellend, dunkle Ueberlagerung damit der Text lesbar
bleibt, Ueberschrift, Text, Button. Die Teile waren alle da, aber diese
Kombination zu finden war eine Recherche.

328 PHP-Tests / 1329 Assertions, 17 JS-Modelltests. Geprueft: vier Breiten
angenommen und eine erfundene abgewiesen, sieben Hoehenangaben mit dvh/svh/lvh
und zwei ungueltige, ein randloser Abschnitt mit formatfuellendem Bild, und
die Hero-Vorlage Eigenschaft fuer Eigenschaft.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
kantarciandClaude Opus 5 committed 2026-09-12 20:52:48 +02:00
1 parent 464f52b147
commit 67b85751cf
11 files changed
+165 -7

No files matched your search

+77
View File
@@ -1776,6 +1776,83 @@ final class CmsTest extends TestCase
]);
}
/** @return iterable<string, array{string, bool}> */
public static function sectionWidths(): iterable
{
yield 'normal' => ['normal', true];
yield 'narrow' => ['narrow', true];
yield 'wide' => ['wide', true];
yield 'edge to edge' => ['full', true];
yield 'invented' => ['riesig', false];
}
#[DataProvider('sectionWidths')]
public function testASectionCanRunEdgeToEdge(string $width, bool $accepted): void
{
$document = DemoContent::starter('Breite');
$document['blocks'][0]['layout'] = ['tone' => 'paper', 'width' => $width];
if (!$accepted) {
$this->expectException(InvalidDocument::class);
$this->pages->saveDraft('page-1', $document, 0);
return;
}
$this->pages->publish('page-1', $this->pages->saveDraft('page-1', $document, 0), 1);
self::assertStringContainsString('width-'.$width, $this->request('/')->getContent());
}
/** @return iterable<string, array{string, bool}> */
public static function heights(): iterable
{
// 100vh is wrong on a phone - it means the viewport without the address bar. The unit
// people actually want is dvh, so it has to be allowed.
yield 'dynamic viewport height' => ['100dvh', true];
yield 'small viewport height' => ['100svh', true];
yield 'large viewport height' => ['100lvh', true];
yield 'classic viewport height' => ['100vh', true];
yield 'pixels' => ['640px', true];
yield 'invented unit' => ['100qh', false];
yield 'no unit' => ['100', false];
}
#[DataProvider('heights')]
public function testAFullHeightSectionIsPossibleWithTheUnitThatWorksOnPhones(string $height, bool $accepted): void
{
$document = DemoContent::starter('Hoch');
$document['schemaVersion'] = 10;
$document['blocks'][0]['style'] = ['desktop' => ['minHeight' => $height, 'display' => 'flex', 'alignItems' => 'center']];
if (!$accepted) {
$this->expectException(InvalidDocument::class);
$this->pages->saveDraft('page-1', $document, 0);
return;
}
$this->pages->publish('page-1', $this->pages->saveDraft('page-1', $document, 0), 1);
$html = $this->request('/')->getContent();
self::assertStringContainsString('min-height:'.$height, $html);
// Full height alone would put the content at the top; the flex properties centre it.
self::assertStringContainsString('display:flex;align-items:center', $html);
}
public function testAFullBleedImageFillsTheSection(): void
{
$document = DemoContent::starter('Bildfläche');
$document['schemaVersion'] = 15;
$document['blocks'][0]['layout'] = ['tone' => 'paper', 'width' => 'full'];
$document['blocks'][0]['style'] = ['desktop' => ['padding' => ['top' => '0', 'bottom' => '0']]];
$document['blocks'][0]['children'][] = [
'id' => 'flaeche', 'type' => 'image',
'props' => ['src' => '/assets/image-placeholder.svg', 'alt' => 'Weite Landschaft', 'decorative' => false, 'width' => 2400, 'height' => 1200],
'style' => ['desktop' => ['width' => '100%', 'objectFit' => 'cover', 'imageHeight' => '100dvh']],
];
$this->pages->publish('page-1', $this->pages->saveDraft('page-1', $document, 0), 1);
$html = $this->request('/')->getContent();
self::assertStringContainsString('width-full', $html);
self::assertStringContainsString('#flaeche{width:100%}', $html);
// The height and the crop belong on the image itself, not on its figure.
self::assertStringContainsString('#flaeche img{object-fit:cover;height:100dvh}', $html);
}
/** @return iterable<string, array{string, array<string, mixed>, list<string>}> */
public static function newBlocks(): iterable
{
+23
View File
@@ -302,3 +302,26 @@ test('a row arrives with its columns, inside the selected container', () => {
for (let i = 0; i < 4; i++) history.undo();
assert.deepEqual(history.document, fixture());
});
test('the hero preset assembles a full screen section that centres its content', () => {
const history = new EditorHistory(fixture());
const id = history.change(doc => insertPreset(doc, 'hero', 's1'));
const hero = locate(history.document.blocks, id).block;
assert.equal(hero.type, 'section');
assert.equal(hero.layout.width, 'full', 'Edge to edge, or it is not full screen.');
// dvh, not vh: on a phone 100vh is taller than the screen.
assert.equal(hero.style.desktop.minHeight, '100dvh');
assert.equal(hero.style.mobile.minHeight, '80dvh');
assert.equal(hero.style.desktop.display, 'flex');
assert.equal(hero.style.desktop.alignItems, 'center');
assert.equal(hero.style.desktop.backgroundSize, 'cover');
assert.ok(hero.style.desktop.overlayOpacity > 0, 'An overlay, so text stays readable on a photo.');
assert.deepEqual(hero.children.map(child => child.type), ['heading', 'text', 'button']);
assert.equal(hero.children[0].props.level, 1);
// Free styles need schema v10.
assert.ok(history.document.schemaVersion >= 10);
history.undo();
assert.deepEqual(history.document, fixture());
});