Quelltext des aktuellen Standes
<?php
declare(strict_types=1);
/**
* Hält die Editor-Vorlage und ihre ausgelagerten Dateien zusammen.
*
* Bedienung und Gestaltung des Seiten-Editors liegen in
* `public/assets/js/page-editor.js` und `public/assets/css/page-editor.css`.
* Diese Trennung fällt nicht auf, wenn sie bricht: Eine fehlende Einbindung
* erzeugt keinen Fehler beim Ausliefern, sondern einen Editor ohne Funktion.
* Ebenso bleibt zurückgerutschtes Inline-JS unbemerkt – es macht die Vorlage
* nur wieder unübersichtlich.
*/
return static function (): void {
$wurzel = dirname(__DIR__);
$vorlage = (string)file_get_contents($wurzel . '/templates/pages/edit.php');
$skript = (string)file_get_contents($wurzel . '/public/assets/js/page-editor.js');
$stil = (string)file_get_contents($wurzel . '/public/assets/css/page-editor.css');
// ----------------------------------------------------------------
// Die Trennung hält
// ----------------------------------------------------------------
test('Die Vorlage bindet Skript und Stil ein, statt sie mitzuführen', function () use ($vorlage) {
assertTrue(str_contains($vorlage, '/assets/js/page-editor.js'), 'Das Skript wird nicht geladen');
assertTrue(str_contains($vorlage, '/assets/css/page-editor.css'), 'Der Stil wird nicht geladen');
// Ein `<script>` ohne `src` wäre wieder Inline-Code. Erlaubt bleibt
// der JSON-Block mit den Angaben für das Skript.
preg_match_all('/<script(?![^>]*\bsrc=)(?![^>]*application\/json)[^>]*>/i', $vorlage, $inline);
assertSame(0, count($inline[0]), 'Die Vorlage führt wieder eigenes JavaScript');
assertFalse(str_contains($vorlage, '<style>'), 'Die Vorlage führt wieder eigenes CSS');
});
test('Das Skript kommt ohne serverseitigen Code aus', function () use ($skript, $stil) {
// Eine statisch ausgelieferte Datei wird nie durch PHP gereicht.
assertFalse(str_contains($skript, '<?'), 'Im Skript steht noch PHP');
assertFalse(str_contains($stil, '<?'), 'Im Stil steht noch PHP');
});
// ----------------------------------------------------------------
// Markup ruft nur auf, was es auch gibt
// ----------------------------------------------------------------
test('Jede aus dem Markup gerufene Funktion hängt am window', function () use ($vorlage, $skript) {
preg_match_all('/\bon(?:click|change|submit|input)="([a-zA-Z_][A-Za-z0-9_]*)\s*\(/', $vorlage, $treffer);
$gerufen = array_unique($treffer[1]);
assertTrue($gerufen !== [], 'Keine Aufrufe gefunden – Suchmuster passt nicht mehr');
foreach ($gerufen as $name) {
assertTrue(
str_contains($skript, 'window.' . $name . ' ='),
"Aus dem Markup gerufen, aber nicht am window: {$name}()"
);
}
});
// ----------------------------------------------------------------
// Die Angaben des Servers erreichen das Skript
// ----------------------------------------------------------------
test('Was das Skript erwartet, liefert die Vorlage auch', function () use ($vorlage, $skript) {
assertTrue(
str_contains($vorlage, 'id="page-editor-data"'),
'Der Datenblock fehlt in der Vorlage'
);
assertTrue(
str_contains($skript, "getElementById('page-editor-data')"),
'Das Skript liest den Datenblock nicht'
);
// Jeder Schlüssel, den das Skript liest, wird auch geschrieben.
foreach (['isEdit', 'tinymce', 'aiEnabled', 'canDelete', 'pageId', 'draftContent'] as $feld) {
assertTrue(str_contains($skript, 'cfg.' . $feld), "Das Skript nutzt {$feld} nicht mehr");
assertTrue(str_contains($vorlage, "'" . $feld . "'"), "Die Vorlage liefert {$feld} nicht");
}
});
test('Der Datenblock verlässt seinen eigenen Block nicht', function () use ($vorlage) {
// Ohne Maskierung könnte ein Seiteninhalt den JSON-Block schließen.
assertTrue(str_contains($vorlage, 'JSON_HEX_TAG'), 'Spitze Klammern werden nicht maskiert');
assertTrue(str_contains($vorlage, 'JSON_HEX_QUOT'), 'Anführungszeichen werden nicht maskiert');
assertTrue(str_contains($vorlage, 'JSON_HEX_AMP'), 'Kaufmanns-Und wird nicht maskiert');
});
// ----------------------------------------------------------------
// TinyMCE steht vor dem Skript, das es aufruft
// ----------------------------------------------------------------
test('TinyMCE wird geladen, bevor das Editor-Skript es benutzt', function () use ($wurzel, $vorlage, $skript) {
assertTrue(str_contains($skript, 'tinymce.init('), 'Das Skript richtet TinyMCE nicht mehr ein');
// Die Vorlage lädt TinyMCE im Rumpf, `extra_js` folgt erst danach.
$layout = (string)file_get_contents($wurzel . '/templates/layouts/main.php');
$inhalt = strpos($layout, '<?= $content ?>');
$extra = strpos($layout, 'extra_js');
assertTrue($inhalt !== false && $extra !== false, 'Das Layout hat sich geändert');
assertTrue($inhalt < $extra, 'Das Editor-Skript liefe vor TinyMCE');
assertTrue(str_contains($vorlage, 'tinymce.min.js'), 'TinyMCE wird nicht mehr geladen');
});
test('Ein Fehler in einem Abschnitt reisst die uebrigen nicht mit', function () use ($skript) {
// Vorher waren es vier getrennte Skriptbloecke; ein Fehler im einen
// liess die anderen weiterlaufen. Das muss die eine Datei nachbilden.
assertSame(
4,
preg_match_all("/abschnitt\('[^']+', function \(\) \{/", $skript),
'Nicht jeder der vier Abschnitte ist abgeschirmt'
);
assertTrue(str_contains($skript, 'console.error'), 'Ein Fehler bliebe unsichtbar');
// Die einzige Verbindung zwischen zwei Abschnitten liegt im
// gemeinsamen Geltungsbereich, nicht in einem von beiden.
assertTrue(
(bool)preg_match('/^ var openAiEditorModal = /m', $skript),
'Der KI-Dialog ist fuer den Editor-Abschnitt nicht erreichbar'
);
});
// ----------------------------------------------------------------
// Icons folgen dem Umschalter
// ----------------------------------------------------------------
test('Die Editor-Icons sind umschaltbar statt fest verdrahtet', function () use ($wurzel, $vorlage) {
// Eine Emoji-Entity im Markup bleibt Emoji, gleich welche Einstellung gilt.
preg_match_all('/&#\d{4,};/', $vorlage, $entities);
assertSame(0, count($entities[0]), 'Im Markup stehen wieder fest verdrahtete Emoji');
// Jedes verwendete Icon braucht seine Zuordnung, sonst bleibt es im
// Emoji-Modus monochrom.
$map = (string)file_get_contents($wurzel . '/src/Core/IconMap.php');
preg_match_all('/fas (fa-[a-z0-9-]+)/', $vorlage, $treffer);
$icons = array_unique($treffer[1]);
assertTrue($icons !== [], 'Keine Icons gefunden – Suchmuster passt nicht mehr');
foreach ($icons as $icon) {
assertTrue(
(bool)preg_match("/'" . preg_quote($icon, '/') . "'\s*=>/", $map),
"Icon ohne Zuordnung in IconMap.php: {$icon}"
);
}
});
};