Fassung 1.4.3: Update ohne Git, Verwaltung auf dem Telefon, eigene Symbole
Vier Wuensche und ein Fund beim Nachmessen. - Farbwaehler im Designbereich. Das Textfeld bleibt massgeblich, damit "@akzent" ein Verweis auf die Palette bleibt und nicht beim ersten Klick zu einem festen Hexwert erstarrt (ADR 066). - Die Verwaltung auf dem Telefon: das Menue klappt unter 760 px zu und nennt den Bereich, in dem man steht; Ziele 44 px, kein Skript. - Eigene SVG-Symbole statt Unicode-Zeichen - ein Telefon ersetzt die sonst durch Emoji in fremdem Stil. Zwei Eintraege trugen dabei denselben Pfad; ein Test findet beides (ADR 067). - Update aus dem Releasearchiv: der Weg fuer Installationen ohne Git. Zwei Schritte, weil ein Prozess, der sich selbst ausgetauscht hat, mit dem alten Code weiterarbeitet. var/, .env, hochgeladene Schriften und das erzeugte Buendel bleiben unberuehrt; public/ wird dazu Datei fuer Datei zusammengefuehrt. Geprueft ohne Netz gegen eine Releaseseite im Speicher (ADR 068). Beim Nachmessen der echten Startseite statt einer Testseite: das groesste Element ueber der Falz ist dort kein <img>, sondern der Hintergrund des ersten Abschnitts - und der steht nur im Stylesheet. Drei Vorabverweise mit denselben Geraetegrenzen wie der Compiler holen genau die Variante, die die Regel auch nimmt. LCP 1,3 s -> 1,1 s. Dabei gefunden: dasselbe Bild wurde je Geraeteregel neu abgefragt - Startseite 19 -> 15 Abfragen (ADR 069). Geprueft: 428 Tests auf PHP 8.2 und 8.4, Editor-, Browser- und Linkpruefung, Lighthouse auf drei echten Seiten (alles ausser der Leistung der Startseite 100). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ea3dd4ddf2
commit
d6d476177a
26 files changed
+1740
-26
No files matched your search
@@ -1077,6 +1077,30 @@ final class CmsTest extends TestCase
|
||||
}
|
||||
}
|
||||
|
||||
public function testTheAdministrationDrawsItsOwnIconsInsteadOfBorrowingCharacters(): void
|
||||
{
|
||||
// Umut: „Machst du auch gleich eigene Icons statt die Web-Dinger zu nutzen – auf dem
|
||||
// Smartphone kommen sonst Emojis.“ Genau so ist es: ein Telefon ersetzt ein Zeichen wie
|
||||
// das Zahnrad durch ein buntes Emoji in fremdem Stil, und die Navigation sieht aus wie
|
||||
// zusammengewürfelt. Eigene Pfade sind eine Linienstärke, eine Farbe und kein Request.
|
||||
$shell = (string) file_get_contents(__DIR__.'/../templates/admin/shell.html.twig');
|
||||
self::assertDoesNotMatchRegularExpression(
|
||||
'/[\\x{2190}-\\x{2BFF}\\x{1F300}-\\x{1FAFF}]/u',
|
||||
$shell,
|
||||
'In der Navigation steht ein Zeichen, das ein Telefon zum Emoji macht.',
|
||||
);
|
||||
self::assertStringContainsString('admin/icon.html.twig', $shell);
|
||||
|
||||
$icons = (string) file_get_contents(__DIR__.'/../templates/admin/icon.html.twig');
|
||||
// Jedes verwendete Symbol muss es geben - sonst zeichnet die Vorlage stillschweigend das
|
||||
// Ersatzsymbol, und zwei Einträge sehen gleich aus.
|
||||
foreach (['pages', 'partials', 'menus', 'media', 'forms', 'statistics', 'theme', 'settings', 'system', 'users', 'external'] as $name) {
|
||||
self::assertStringContainsString($name.": 'M", $icons, $name.' fehlt in der Symbolsammlung.');
|
||||
}
|
||||
preg_match_all("/'(M[^']+)'/", $icons, $pfade);
|
||||
self::assertSame(count($pfade[1]), count(array_unique($pfade[1])), 'Zwei Symbole tragen denselben Pfad.');
|
||||
}
|
||||
|
||||
public function testNoStylesheetHasADanglingSelector(): void
|
||||
{
|
||||
// Gefunden, als der Einwilligungsdialog plötzlich ungestaltet unter dem Fuß stand: beim
|
||||
@@ -2913,6 +2937,71 @@ final class CmsTest extends TestCase
|
||||
self::assertStringNotContainsString('fetchpriority', $second);
|
||||
}
|
||||
|
||||
public function testTheBackgroundOfTheFirstSectionIsFetchedBeforeTheStylesheetFindsIt(): void
|
||||
{
|
||||
// Auf der Startseite des Piloten ist das größte Element über der Falz kein `<img>`, sondern
|
||||
// der Hintergrund des ersten Abschnitts. Ein Hintergrundbild steht nur im Stylesheet, und
|
||||
// das kommt nach dem HTML - der Browser erfährt also erst vom Bild, wenn er das CSS
|
||||
// geparst hat. Gemessen: 1,3 s statt 0,9 s bis zum größten Element.
|
||||
$media = new MediaLibrary($this->db, $this->path.'-media');
|
||||
$datei = tempnam(sys_get_temp_dir(), 'kant-hero-');
|
||||
$limit = ini_get('memory_limit');
|
||||
ini_set('memory_limit', '512M');
|
||||
|
||||
try {
|
||||
$bild = imagecreatetruecolor(2600, 1600);
|
||||
imagejpeg($bild, $datei, 80);
|
||||
imagedestroy($bild);
|
||||
$item = $media->upload(new UploadedFile($datei, 'hero.jpg', test: true));
|
||||
|
||||
$version = static fn (Connection $db): int => (int) $db->fetchOne('SELECT version FROM page WHERE id = ?', ['page-1']);
|
||||
$ohne = DemoContent::starter('Ohne Hintergrund');
|
||||
$entwurf = $this->pages->saveDraft('page-1', $ohne, $version($this->db));
|
||||
$this->pages->publish('page-1', $entwurf, $version($this->db));
|
||||
$sparsam = (int) $this->request('/')->headers->get('X-Kant-Queries');
|
||||
|
||||
$mit = DemoContent::starter('Mit Hintergrund');
|
||||
$mit['schemaVersion'] = max(10, $mit['schemaVersion'] ?? 10);
|
||||
$mit['blocks'][0]['style']['desktop']['backgroundImage'] = $item['id'];
|
||||
$entwurf = $this->pages->saveDraft('page-1', $mit, $version($this->db));
|
||||
$this->pages->publish('page-1', $entwurf, $version($this->db));
|
||||
$antwort = $this->request('/');
|
||||
$html = (string) $antwort->getContent();
|
||||
|
||||
// Drei Verweise, einer je Gerät - und genau einer trifft zu.
|
||||
preg_match_all('/<link rel="preload" as="image" href="([^"]+)" media="([^"]+)"[^>]*>/', $html, $treffer, PREG_SET_ORDER);
|
||||
self::assertCount(3, $treffer, 'Der Hintergrund des ersten Abschnitts wird nicht vorab geholt.');
|
||||
self::assertSame(
|
||||
['(min-width:1025px)', '(min-width:768px) and (max-width:1024px)', '(max-width:767px)'],
|
||||
array_column($treffer, 2),
|
||||
);
|
||||
self::assertSame(
|
||||
['/media/'.$item['id'].'/2400', '/media/'.$item['id'].'/1600', '/media/'.$item['id'].'/960'],
|
||||
array_column($treffer, 1),
|
||||
'Jedes Gerät bekommt die Variante, die es auch ohne Vorabholen bekäme.',
|
||||
);
|
||||
foreach ($treffer as $verweis) {
|
||||
// Der Punkt der Übung: Vorabverweis und Regel müssen dieselbe Datei meinen. Sonst
|
||||
// lädt der Browser zwei Bilder statt einem, und das Vorabholen kostet mehr als es
|
||||
// bringt.
|
||||
self::assertStringContainsString('url("'.$verweis[1].'")', $html, 'Der Vorabverweis meint eine andere Datei als die Regel.');
|
||||
self::assertStringContainsString('fetchpriority="high"', $verweis[0]);
|
||||
}
|
||||
|
||||
// Dasselbe Bild wird während einer Ausgabe mehrfach gesucht: je Gerät eine Regel, dazu
|
||||
// der Kopf. Gefragt wird trotzdem nur einmal.
|
||||
$mitBild = (int) $antwort->headers->get('X-Kant-Queries');
|
||||
self::assertLessThanOrEqual($sparsam + 1, $mitBild, sprintf(
|
||||
'Ein Hintergrundbild kostet eine Abfrage, nicht vier (%d statt %d).',
|
||||
$mitBild,
|
||||
$sparsam,
|
||||
));
|
||||
} finally {
|
||||
@unlink($datei);
|
||||
ini_set('memory_limit', (string) $limit);
|
||||
}
|
||||
}
|
||||
|
||||
public function testAPageDoesNotAskTheDatabaseOverAndOver(): void
|
||||
{
|
||||
$this->pages->publish('page-1', $this->pages->saveDraft('page-1', DemoContent::pages()['home'], 0), 1);
|
||||
|
||||
Reference in new issue
Block a user