Seitenbreite wirkt, vier Menueplaetze, Seiten ohne Kopfbereich
Drei Fragen, drei unterschiedliche Antworten. Die globale Seitenbreite war einstellbar und wirkte nur in eine Richtung: site.css schrieb width:min(1180px,...), also schluckte die 1180 jeden groesseren Wert aus dem Designbereich. Schmaler ging, breiter nicht, ohne jede Meldung. Die Zahl kommt jetzt aus dem Theme. Das ist der dritte Fehler dieser Art an einem Tag - nach der Rahmenstaerke und der Bildrundung -, und alle drei hatten dieselbe Ursache: CSS, das nie gegen eine Erwartung geprueft wurde. Menueplaetze und Menueart waren vermischt. Wo ein Menue steht, bestimmt der Block und war immer frei. Welche Eintraege es zeigt, bestimmt der Platz - davon gab es zwei, jetzt vier (Haupt, zweite, Fusszeile, Rechtliches). Wie es aussieht, war gar nicht einstellbar: jetzt waagerecht, untereinander oder aufklappbar als Burger. Der Burger ist ein details-Element, denn ein Menue, das ein Skript zum Oeffnen braucht, geht auf einer Seite ohne Skript nicht auf. "Seiten ohne Menue" war nicht moeglich - der Kopfbereich war ein Dokument fuer alle Seiten. Jetzt waehlt jede Seite: Standard, ein anderer Bereich, oder keiner. Dafuer musste die Regel "es gibt genau einen Kopf- und Fussbereich" fallen; sie war richtig, solange niemand waehlen konnte, und wurde falsch, sobald man waehlen kann. Ein bestehender Test, der diese Regel zusicherte, ist deshalb umgeschrieben und prueft jetzt die neue. Gespeichert wird die Wahl, nicht das Ergebnis: leer heisst Standard, none heisst keiner, sonst die Adresse. Ein Verweis auf einen geloeschten Bereich faellt auf den Standard zurueck, statt die Seite stillschweigend ohne Navigation auszuliefern. Neu im Ausbauplan: ein einzelnes install.php, das das Releasearchiv herunterlaedt und entpackt, damit niemand tausende Dateien per FTP hochladen muss. Mit den Regeln, die darueber entscheiden, ob das eine Hilfe oder eine Einladung ist: Adresse und SHA-256 stehen in der Datei und nicht im Formular, HTTPS mit Zertifikatspruefung, Schutz gegen ZIP-Slip, Selbstloeschung danach. 336 PHP-Tests / 1354 Assertions, 17 JS-Modelltests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
67b85751cf
commit
af3589c95c
22 files changed
+357
-26
No files matched your search
+106
-4
@@ -1513,13 +1513,18 @@ final class CmsTest extends TestCase
|
||||
// Nothing was written, so the section still holds its original block.
|
||||
self::assertSame('section', $this->partials->find($id)['document']['blocks'][0]['type']);
|
||||
|
||||
// Only one header and one footer.
|
||||
// A second header is allowed now that pages can choose one - it keeps its own address,
|
||||
// while the first one stays "default" and is what a page without a choice gets.
|
||||
$this->partials->create('header', 'landing', 'Kopf für Landingpages', ['schemaVersion' => 11, 'title' => 'x', 'description' => 'x', 'blocks' => []]);
|
||||
self::assertSame(['default' => 'Kopfbereich', 'landing' => 'Kopf für Landingpages'], $this->partials->choices('header'));
|
||||
self::assertSame('Kopf für Landingpages', $this->partials->findByKind('header', 'landing')['name']);
|
||||
// The same address twice stays refused.
|
||||
try {
|
||||
$this->partials->create('header', 'zweiter', 'Zweiter Kopf', ['schemaVersion' => 11, 'title' => 'x', 'description' => 'x', 'blocks' => []]);
|
||||
self::fail('Expected a second header to be refused.');
|
||||
$this->partials->create('header', 'landing', 'Noch einer', ['schemaVersion' => 11, 'title' => 'x', 'description' => 'x', 'blocks' => []]);
|
||||
self::fail('Expected a duplicate address to be refused.');
|
||||
} catch (InvalidPage) {
|
||||
}
|
||||
// And they cannot be deleted, only emptied.
|
||||
// The standard area cannot be deleted, only emptied.
|
||||
$this->expectException(InvalidPage::class);
|
||||
$this->partials->delete($this->partials->findByKind('header')['id']);
|
||||
}
|
||||
@@ -1776,6 +1781,103 @@ final class CmsTest extends TestCase
|
||||
]);
|
||||
}
|
||||
|
||||
public function testAPageCanUseAnotherHeaderOrNoneAtAll(): void
|
||||
{
|
||||
// The seeded areas plus a second header for landing pages.
|
||||
$this->partials->create('header', 'landing', 'Landing-Kopf', [
|
||||
'schemaVersion' => 11, 'title' => 'Landing', 'description' => 'x',
|
||||
'blocks' => [['id' => 'landing-text', 'type' => 'text', 'props' => ['text' => 'Nur ein Logo']]],
|
||||
]);
|
||||
$this->pages->publish('page-1', $this->pages->saveDraft('page-1', DemoContent::pages()['home'], 0), 1);
|
||||
|
||||
// By default the standard header is used.
|
||||
$html = $this->request('/')->getContent();
|
||||
self::assertStringContainsString('area-header-name', $html);
|
||||
self::assertStringNotContainsString('landing-text', $html);
|
||||
|
||||
// The page can ask for the other one.
|
||||
$version = (int) $this->db->fetchOne('SELECT version FROM page WHERE id = ?', ['page-1']);
|
||||
$this->pages->saveAreas('page-1', 'landing', null, $version);
|
||||
$html = $this->request('/')->getContent();
|
||||
self::assertStringContainsString('landing-text', $html);
|
||||
self::assertStringNotContainsString('area-header-name', $html);
|
||||
|
||||
// Or for none at all - a landing page without navigation.
|
||||
$this->pages->saveAreas('page-1', 'none', 'none', $version + 1);
|
||||
$html = $this->request('/')->getContent();
|
||||
self::assertStringNotContainsString('site-header', $html);
|
||||
self::assertStringNotContainsString('site-footer', $html);
|
||||
self::assertStringContainsString('Deine Inhalte', $html, 'The page itself is still there.');
|
||||
|
||||
// An address that no longer exists falls back to the standard area instead of silently
|
||||
// losing the header.
|
||||
$this->pages->saveAreas('page-1', 'weg', null, $version + 2);
|
||||
self::assertStringContainsString('area-header-name', $this->request('/')->getContent());
|
||||
|
||||
// A stale version is refused, and an invalid address too.
|
||||
$this->expectException(StaleWrite::class);
|
||||
$this->pages->saveAreas('page-1', null, null, $version);
|
||||
}
|
||||
|
||||
public function testAnInvalidAreaAddressIsRefused(): void
|
||||
{
|
||||
$this->expectException(InvalidPage::class);
|
||||
$this->pages->saveAreas('page-1', 'Kein Slug!', null, 0);
|
||||
}
|
||||
|
||||
/** @return iterable<string, array{string, bool}> */
|
||||
public static function menuLayouts(): iterable
|
||||
{
|
||||
yield 'horizontal' => ['horizontal', true];
|
||||
yield 'vertical' => ['vertical', true];
|
||||
yield 'burger without a script' => ['burger', true];
|
||||
yield 'invented' => ['mega', false];
|
||||
}
|
||||
|
||||
#[DataProvider('menuLayouts')]
|
||||
public function testAMenuCanBeLaidOutInThreeWays(string $layout, bool $accepted): void
|
||||
{
|
||||
$document = DemoContent::starter('Mit Menü');
|
||||
$document['schemaVersion'] = 16;
|
||||
$document['blocks'][0]['children'][] = ['id' => 'nav', 'type' => 'menu', 'props' => ['slot' => 'header', 'layout' => $layout]];
|
||||
if (!$accepted) {
|
||||
$this->expectException(InvalidDocument::class);
|
||||
$this->pages->saveDraft('page-1', $document, 0);
|
||||
|
||||
return;
|
||||
}
|
||||
$this->menus->save('header', 'Hauptnavigation', [['label' => 'Start', 'type' => 'page', 'level' => 1, 'pageId' => 'page-1', 'position' => 10]], 0);
|
||||
$this->pages->publish('page-1', $this->pages->saveDraft('page-1', $document, 0), 1);
|
||||
$html = $this->request('/')->getContent();
|
||||
self::assertStringContainsString('class="block-menu menu-'.$layout, $html);
|
||||
// The burger opens with a details element, so it works on a page that runs no script.
|
||||
self::assertSame('burger' === $layout, str_contains($html, 'menu-burger'));
|
||||
self::assertStringContainsString("script-src 'none'", (string) $this->request('/')->headers->get('Content-Security-Policy'));
|
||||
}
|
||||
|
||||
public function testAMenuLayoutNeedsSchemaSixteen(): void
|
||||
{
|
||||
$document = DemoContent::starter('Zu alt');
|
||||
$document['schemaVersion'] = 15;
|
||||
$document['blocks'][0]['children'][] = ['id' => 'nav', 'type' => 'menu', 'props' => ['slot' => 'header', 'layout' => 'burger']];
|
||||
$this->expectException(InvalidDocument::class);
|
||||
$this->pages->saveDraft('page-1', $document, 0);
|
||||
}
|
||||
|
||||
public function testTheGlobalContentWidthCanMakeThePageWiderNotOnlyNarrower(): void
|
||||
{
|
||||
$this->pages->publish('page-1', $this->pages->saveDraft('page-1', DemoContent::pages()['home'], 0), 1);
|
||||
// The stylesheet takes the number from the theme instead of a hard 1180px, which capped
|
||||
// every larger value and made the setting look broken.
|
||||
$css = (string) file_get_contents(__DIR__.'/../public/assets/site.css');
|
||||
self::assertStringContainsString('.shell{width:min(var(--k-container,1180px),calc(100% - 64px))', $css);
|
||||
|
||||
$theme = $this->themes->load();
|
||||
$this->themes->save($theme['colors'], $theme['fonts'], [...$theme['base'], 'containerWidth' => '1440px']);
|
||||
$html = $this->request('/')->getContent();
|
||||
self::assertStringContainsString('--k-container:1440px', $html);
|
||||
}
|
||||
|
||||
/** @return iterable<string, array{string, bool}> */
|
||||
public static function sectionWidths(): iterable
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user