Files
DogFatherGitandClaude Opus 5 2f3b8630c7 Messungen am Telefon: 37 bekommen ihren Finger
Gestern Mittag gemessen: 39 `newContext`-Aufrufe nehmen ihre Breite
aus einer Variablen und setzen kein `hasTouch`. Ohne das meldet der
Browser `pointer: fine`, und KEINE Regel aus `@media (pointer:
coarse)` greift — dort stehen im ganzen Haus die 44-Pixel-
Beruehrziele, die ausgeblendeten Tastenkuerzel und die
eingeklappte Reiterleiste.

Was das anrichtet, war an pruef-breiten zu sehen: drei Befunde auf
320, 390 und 430 Pixeln, die mit dem Finger allesamt verschwanden —
Messfehler, keine Fehler.

37 DAVON HABEN IHN JETZT. Nur `hasTouch`, nicht `isMobile`: Gefragt
ist genau die eine Sache, um die es geht. `isMobile` waere eine
zweite Aenderung in derselben Zeile, und wenn danach etwas anders
aussieht, wuesste niemand, welche von beiden es war.

ZWEI BLEIBEN STEHEN, beide in pruef-grosscheck.mjs. Die Aenderung
dort waere ein Zweizeiler; sie zu pruefen hiesse, 206 Seiten ueber
vier Rollen laufen zu lassen, und das braucht Filipes Zusage. Eine
Aenderung, die ich nicht pruefen darf, liefere ich nicht aus.
Grundlinie in pruef-fingermass steht deshalb auf 2.

JEDE EINZELN NACHGELAUFEN — 37 Laeufe:

  34 gruen, darunter pruef-handy 186, pruef-material 159,
  pruef-start-ansicht 160, pruef-kalender 141, pruef-erwaehnung 129,
  pruef-bewerbung-aufgaben 163, pruef-neue-seiten 109

  3 mit Befunden, ALLE DREI VORBESTEHEND (Gegenprobe: alter Stand
  derselben Datei, gleicher Lauf, gleiche Zahl):
    pruef-browser        3  (WebKit startet auf diesem Rechner nicht)
    pruef-chat-anhaenge  2
    pruef-crew-wand-bild 3

EINE ZEITBOMBE GEFUNDEN UND ENTSCHAERFT

pruef-dabei-optik meldete „Haekchen: 0 -> 0" — aber nur, wenn vier
andere Pruefungen gleichzeitig liefen. Allein: „0 -> 1", gruen.

Dort stand `waitForTimeout(250)` mit der Begruendung „250 ms sind
reichlich ueber den 160" (der Dauer der Blende). Auf einem Rechner,
auf dem nebenher vier Browser messen, sind sie es nicht. Die
Pruefung war damit gruen, solange nichts anderes lief, und rot im
Gesamtlauf — also genau dann, wenn niemand sie einzeln nachstellen
kann.

Eine Wartezeit ist eine Annahme ueber den Rechner. Gewartet wird
jetzt auf das, worauf es ankommt: dass das Haekchen da ist. Laeuft
die Frist ab, faellt die Pruefung mit dem ECHTEN Wert um und nicht
mit einem Messfehler. Gegenprobe: unter derselben vierfachen Last,
die sie vorher rot gemacht hat, jetzt gruen.

UND DIE GRUNDLINIE WIEDER STRENG. Ich hatte sie kurz auf
„hoechstens" gestellt — damit haette ein Rueckgang stillschweigend
Platz fuer die naechste Suende gedeckt. Genau davor warnt der Kopf
derselben Datei bei der anderen Grundlinie, und ich habe es eine
Stunde spaeter selbst falsch gemacht.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-10-01 13:37:05 +02:00

501 lines
22 KiB
JavaScript

/* =====================================================================
pruef-textform.mjs — FETT, KURSIV, UNTERSTRICHEN, DURCHGESTRICHEN
=====================================================================
Filipes Wunsch vom 21.09.2026: "dan will ich dass die leute auch
immer die art von text aussuchen können, fettgedrückt, unterstrichen
und so alles. das überall sei es im chat jeder für sich oder sei es
wenn die modis oder rechte hand oder dogfather eine aufgabe oder
sachen posten."
WAS HIER GEPRUEFT WIRD -- und warum gerade das:
(1) DASS ES UEBERALL IST. Nicht ueber eine gepflegte Seitenliste,
sondern abgeleitet: Wessen Skript die Gestaltung benutzt, dessen
Seite muss sie auch laden. Und umgekehrt -- wer sie laedt und
nicht benutzt, schleppt sie umsonst mit. Eine Liste, die niemand
pflegt, kann nicht veralten.
(2) DIE REGELN -- im echten Browser, an der echten ausgelieferten
Datei. Ein Nachbau in Node wuerde beweisen, dass mein Nachbau
funktioniert, nicht dass der Chat es tut.
(3) DASS ECHTE ELEMENTE ENTSTEHEN. `<strong>` im Baum, nicht
"**fett**" als Text und nicht "&lt;strong&gt;" als Zeichenkette.
Alle drei sehen im Quelltext aehnlich aus und sind voellig
verschieden.
(4) DIE SICHERHEITSPROBE. Wer Text zu Auszeichnung macht, ist einen
Tippfehler von `innerHTML` entfernt. `textform.js` baut mit
`createTextNode`, also kann nichts passieren -- aber das ist eine
Eigenschaft des Codes von heute, keine des Codes von morgen.
Deshalb wird es GEMESSEN: Eine Nachricht mit `<b>` und
`<img onerror=...>` muss als Text dastehen, sichtbar, unwirksam.
(5) DIE GEGENPROBEN. Ohne sie wuesste niemand, ob diese Pruefung
ueberhaupt "nicht in Ordnung" sagen kann.
===== Was hier ABSICHTLICH NICHT geprueft wird =====
Das Aussehen der Leiste (Farben, Abstaende) -- das macht
pruef-chat-optik an echten Bildpunkten, und zwei Pruefungen fuer
dieselbe Frage altern verschieden.
===================================================================== */
import { mkdtempSync, rmSync, readFileSync, readdirSync, appendFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
const ordner = mkdtempSync(join(tmpdir(), "ws-textform-"));
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
import { notbremse } from "./helfer-notbremse.mjs";
const { eigenerPort } = await import("./helfer-port.mjs");
const PORT = await eigenerPort(import.meta, "die Textgestaltungspruefung");
process.env.PORT = `${PORT}`;
process.env.SITE_ACCESS_SECRET = "lokaler-test";
process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
const express = (await import("express")).default;
const ec = express.response.cookie;
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
await import("./index.js");
notbremse(420_000, "pruef-textform");
await new Promise((r) => setTimeout(r, 1000));
const BASIS = `http://127.0.0.1:${PORT}`;
const PROTOKOLL = join(process.cwd(), "pruef-textform-lauf.txt");
try { rmSync(PROTOKOLL, { force: true }); } catch { /* gab es noch nicht */ }
const melde = (z) => {
console.log(z);
try { appendFileSync(PROTOKOLL, z + "\n"); } catch { /* Zugabe */ }
};
let fehler = 0;
const ok = (b, t) => { melde((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
melde("\n===== DIE TEXTGESTALTUNG =====");
/* =======================================================================
1. WER SIE BENUTZT, LAEDT SIE AUCH -- UND UMGEKEHRT
Abgeleitet, nicht abgeschrieben. Beide Richtungen, weil nur eine
davon die halbe Sicherung waere: Eine Seite ohne das Modul stuerzt
ab; eine Seite mit unbenutztem Modul kostet jeden Besucher Bytes.
======================================================================= */
melde("\n=== Jede Seite, die sie braucht, hat sie ===");
{
const { ohneKommentar } = await import("./helfer-ohne-kommentar.mjs");
const wHtml = new URL("../workspace/", import.meta.url);
const wJs = new URL("../workspace/assets/js/", import.meta.url);
/* Welche Skripte benutzen die Gestaltung wirklich? OHNE Kommentare --
in calls.js steht das Wort "Textformen" in einem Erklaertext, und
eine Pruefung, die daran anschlaegt, treibt einen dazu, die
Erklaerung zu loeschen. */
const nutzer = new Set();
for (const datei of readdirSync(wJs).filter((f) => f.endsWith(".js"))) {
const roh = readFileSync(new URL(datei, wJs), "utf8");
if (datei === "textform.js") continue; /* sie selbst zaehlt nicht */
if (/\bTextform\b/.test(ohneKommentar(roh, ".js"))) nutzer.add(datei);
}
ok(nutzer.size >= 3,
`${nutzer.size} Skripte benutzen die Gestaltung (${[...nutzer].sort().join(", ")})`);
let mitBedarf = 0, ohneBedarf = 0, fehlend = [], ueberfluessig = [];
const seiten = readdirSync(wHtml).filter((f) => f.endsWith(".html"));
for (const seite of seiten) {
const html = readFileSync(new URL(seite, wHtml), "utf8");
const skripte = [...html.matchAll(/<script[^>]+src="[^"]*assets\/js\/([a-z0-9-]+\.js)/g)]
.map((m) => m[1]);
const braucht = skripte.some((s) => nutzer.has(s));
const laedt = /assets\/js\/textform\.js/.test(html);
if (braucht) {
mitBedarf++;
if (!laedt) fehlend.push(seite);
} else {
ohneBedarf++;
if (laedt) ueberfluessig.push(seite);
}
}
/* DIE ZAHLEN GEHOEREN IN DIE BEDINGUNG. Faende die Ableitung
keine einzige Seite mit Bedarf, waere unten alles gruen und nichts
geprueft -- genau der Fehler, der in diesem Haus schon zweimal
unbemerkt blieb. */
ok(mitBedarf >= 3 && ohneBedarf >= 5,
`${mitBedarf} Seiten brauchen sie, ${ohneBedarf} nicht (${seiten.length} insgesamt)`);
ok(fehlend.length === 0,
fehlend.length === 0
? "jede Seite mit Bedarf laedt textform.js"
: `diese Seiten brauchen sie und laden sie nicht: ${fehlend.join(", ")}`);
ok(ueberfluessig.length === 0,
ueberfluessig.length === 0
? "und keine schleppt sie umsonst mit"
: `laden sie ohne Bedarf: ${ueberfluessig.join(", ")}`);
}
/* =======================================================================
2. VORBEREITUNG FUER ALLES WEITERE
======================================================================= */
const { DatabaseSync } = await import("node:sqlite");
const { scryptSync, randomBytes } = await import("node:crypto");
await fetch(BASIS + "/workspace/api/anmelden", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ rolle: "admin", code: "x" }) }).catch(() => {});
for (let i = 0; i < 60; i++) {
try {
const p = new DatabaseSync(process.env.WORKSPACE_DB);
try { p.prepare("SELECT 1 FROM personen LIMIT 1").get(); break; } finally { p.close(); }
} catch { await new Promise((r) => setTimeout(r, 250)); }
}
const d = new DatabaseSync(process.env.WORKSPACE_DB);
const jetzt = new Date().toISOString();
function anlegen(name, rolle, code) {
const salt = randomBytes(16).toString("hex");
const hash = scryptSync(code, salt, 64, { N: 32768, r: 8, p: 1, maxmem: 96 * 1024 * 1024 }).toString("hex");
d.prepare("INSERT INTO personen (name, rolle, code_hash, code_salt, code_n, aktiv, erstellt) VALUES (?,?,?,?,?,1,?)")
.run(name, rolle, hash, salt, 32768, jetzt);
return d.prepare("SELECT last_insert_rowid() AS id").get().id;
}
anlegen("Filipe", "admin", "CODE-DOGI-0001");
const idRieke = anlegen("Rieke", "hand", "CODE-HAND-0001");
d.close();
const pw = await import("file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
const browser = await pw.chromium.launch();
/* JEDER SEITENFEHLER WIRD MITGESCHRIEBEN.
Beim ersten Lauf meldete diese Pruefung "die Leiste steht nicht am
Feld" -- richtig, aber zwei Schritte vom Grund entfernt. Der Grund
war ein `ReferenceError: $ is not defined`, den niemand sah: Die
Seite laedt, sieht normal aus und kann nur eine Sache nicht mehr.
Ein Skriptfehler ist immer ein Befund, auch wenn man nicht sofort
weiss, was er anrichtet. */
const seitenFehler = [];
async function alsRolle(rolle, code, wohin, breite = 1280) {
const ctx = await browser.newContext({ viewport: { width: breite, height: 900 }, hasTouch: breite <= 860 });
const seite = await ctx.newPage();
seite.on("pageerror", (e) => seitenFehler.push(`${wohin}: ${String(e).slice(0, 120)}`));
await seite.goto(BASIS + "/workspace/", { waitUntil: "networkidle" });
await seite.click(`.rolle[data-rolle="${rolle}"]`);
await seite.fill("#code", code);
await seite.click("button[type=submit]");
await seite.waitForURL("**/start.html", { timeout: 25000 });
await seite.goto(`${BASIS}/workspace/${wohin}`, { waitUntil: "networkidle" });
await seite.waitForTimeout(900);
return { ctx, seite };
}
const chef = await alsRolle("admin", "CODE-DOGI-0001", "chat.html");
/* =======================================================================
3. DIE REGELN -- an der echten Datei, im echten Browser
Dreizehn Faelle, und die Haelfte davon sind die, die NICHT gestaltet
werden duerfen. Ein Zerleger, der alles auszeichnet, ist schlimmer
als keiner: Dann heisst jeder Dateiname plotzlich anders, als er
heisst.
======================================================================= */
melde("\n=== Die Regeln ===");
{
const proben = await chef.seite.evaluate(() => {
const f = window.Textform;
if (!f) return null;
/* Ein Stueck wird zu "text|stile" verdichtet, damit sich der
Vergleich unten in einer Zeile lesen laesst. */
const kurz = (t) => f.teile(t)
.map((s) => s.text + "|" + Object.keys(s.stile).sort().join("+"))
.join(" // ");
return {
fett: kurz("**fett**"),
kursiv: kurz("ein _wort_ hier"),
unter: kurz("ein __wort__ hier"),
durch: kurz("~~weg~~"),
dateiname: kurz("datei_name_hier"),
erwaehnung: kurz("@max_muster"),
verschachtelt: kurz("**fett _und_ kursiv**"),
rechnung: kurz("2 * 3 * 4"),
nackt: kurz("**"),
gewoehnlich: kurz("ganz gewoehnlich"),
zweimal: kurz("**a** und **b**"),
mittendrin: kurz("a_b_c"),
leer: kurz(""),
};
});
ok(!!proben, "die Gestaltung ist auf der Chatseite geladen");
if (proben) {
const soll = {
fett: "fett|fett",
kursiv: "ein |" + " // " + "wort|kursiv" + " // " + " hier|",
unter: "ein |" + " // " + "wort|unter" + " // " + " hier|",
durch: "weg|durch",
dateiname: "datei_name_hier|",
erwaehnung: "@max_muster|",
verschachtelt: "fett |fett // und|fett+kursiv // kursiv|fett",
rechnung: "2 * 3 * 4|",
nackt: "**|",
gewoehnlich: "ganz gewoehnlich|",
zweimal: "a|fett // und |" + " // " + "b|fett",
mittendrin: "a_b_c|",
leer: "",
};
let stimmt = 0;
for (const name of Object.keys(soll)) {
const gut = proben[name] === soll[name];
if (gut) stimmt++;
ok(gut, gut
? `${name}: ${JSON.stringify(proben[name])}`
: `${name}: ${JSON.stringify(proben[name])} statt ${JSON.stringify(soll[name])}`);
}
ok(stimmt === 13, `${stimmt} von 13 Regelfaellen stimmen`);
}
}
/* =======================================================================
4. EINE NACHRICHT WIRD ZU ECHTEN ELEMENTEN
Der Weg, den ein Mensch geht: Nachricht abschicken, Nachricht
ansehen. Nicht die Funktion aufrufen -- die habe ich oben schon
geprueft.
======================================================================= */
melde("\n=== Im Chat wird daraus wirklich Auszeichnung ===");
let raumId = null;
{
raumId = await chef.seite.evaluate(async (id) => {
const a = await fetch("/workspace/api/chat/direkt", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ mit: id }),
});
const j = await a.json().catch(() => ({}));
return j.raum_id ?? null;
}, idRieke);
ok(Number.isInteger(raumId), `ein Gespraech mit Rieke steht (Raum ${raumId})`);
const SATZ = "So geht das: **fett**, _kursiv_, __unterstrichen__ und "
+ "~~durchgestrichen~~. Und datei_name_hier bleibt normal.";
await chef.seite.evaluate(async ([id, text]) => {
await fetch(`/workspace/api/chat/raeume/${id}/nachrichten`, {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text }),
});
}, [raumId, SATZ]);
await chef.seite.goto(`${BASIS}/workspace/chat.html?raum=${raumId}`, { waitUntil: "networkidle" });
await chef.seite.waitForTimeout(1400);
const bild = await chef.seite.evaluate(() => {
const p = document.querySelector(".chat-nachricht__text");
if (!p) return null;
const holen = (tag) => [...p.querySelectorAll(tag)].map((e) => e.textContent);
return {
sichtbar: p.textContent,
fett: holen("strong"),
kursiv: holen("em"),
unter: holen("u"),
durch: holen("s"),
};
});
ok(!!bild, "die Nachricht steht auf dem Bildschirm");
if (bild) {
/* DIE VIER AUSZEICHNUNGEN SIND ECHTE ELEMENTE, keine Sternchen. */
ok(bild.fett.join() === "fett", `<strong> traegt "${bild.fett.join()}"`);
ok(bild.kursiv.join() === "kursiv", `<em> traegt "${bild.kursiv.join()}"`);
ok(bild.unter.join() === "unterstrichen", `<u> traegt "${bild.unter.join()}"`);
ok(bild.durch.join() === "durchgestrichen", `<s> traegt "${bild.durch.join()}"`);
/* UND DIE ZEICHEN SIND WEG. Stuenden sie noch da, waere oben
trotzdem alles gruen -- die Elemente gaebe es ja. */
ok(!bild.sichtbar.includes("**") && !bild.sichtbar.includes("~~")
&& !bild.sichtbar.includes("__"),
"und die Sternchen und Striche stehen nicht mehr im Text");
/* GEGENPROBE IM SATZ SELBST: Der Dateiname bleibt, wie er ist. */
ok(bild.sichtbar.includes("datei_name_hier"),
"datei_name_hier steht unveraendert da");
ok(!bild.kursiv.includes("name"),
"und ist NICHT kursiv geworden");
}
}
/* =======================================================================
5. WAS WIE HTML AUSSIEHT, BLEIBT TEXT
Die wichtigste Zeile dieser Datei. `textform.js` baut heute mit
`createTextNode` -- das ist sicher, aber es ist eine Eigenschaft von
heute. Wer morgen aus Bequemlichkeit `innerHTML` schreibt, faellt
hier auf, und nicht erst, wenn jemand es ausnutzt.
======================================================================= */
melde("\n=== Sicherheitsprobe: nichts wird zu HTML ===");
{
const BOES = "<b>nicht fett</b> <img src=x onerror=\"window.__geknackt=1\"> "
+ "**aber das schon**";
await chef.seite.evaluate(async ([id, text]) => {
await fetch(`/workspace/api/chat/raeume/${id}/nachrichten`, {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text }),
});
}, [raumId, BOES]);
await chef.seite.waitForTimeout(1500);
const lage = await chef.seite.evaluate(() => {
const alle = [...document.querySelectorAll(".chat-nachricht__text")];
const p = alle[alle.length - 1];
return {
text: p ? p.textContent : null,
bTags: p ? p.querySelectorAll("b").length : -1,
bilder: p ? p.querySelectorAll("img").length : -1,
fett: p ? [...p.querySelectorAll("strong")].map((e) => e.textContent).join() : null,
geknackt: !!window.__geknackt,
};
});
ok(lage.text !== null, "die zweite Nachricht ist da");
ok(lage.text.includes("<b>nicht fett</b>"),
"der HTML-Schnipsel steht als TEXT auf dem Bildschirm");
ok(lage.bTags === 0, `kein <b>-Element entstanden (${lage.bTags})`);
ok(lage.bilder === 0, `kein <img>-Element entstanden (${lage.bilder})`);
ok(lage.geknackt === false, "und nichts davon wurde ausgefuehrt");
/* GEGENPROBE: In derselben Nachricht wird das ECHTE Zeichen sehr wohl
ausgewertet -- sonst waere oben nur bewiesen, dass gar nichts
gestaltet wird. */
ok(lage.fett === "aber das schon",
"und die echte Auszeichnung in derselben Nachricht wirkt trotzdem");
}
/* =======================================================================
6. DIE LEISTE -- da, wo Menschen schreiben
======================================================================= */
melde("\n=== Die Leiste ueber dem Schreibfeld ===");
{
const leiste = await chef.seite.evaluate(() => {
const k = document.querySelector(".textform");
if (!k) return null;
return {
schilder: [...k.querySelectorAll("button")].map((b) => b.textContent),
titel: [...k.querySelectorAll("button")].map((b) => b.title),
benannt: k.getAttribute("aria-label"),
};
});
ok(!!leiste, "im Chat steht eine Leiste");
if (leiste) {
ok(leiste.schilder.join("") === "FKUS",
`vier Knoepfe: ${leiste.schilder.join(" ")}`);
ok(leiste.titel.every((t) => t && t.length > 3),
"jeder sagt im Titel, was er tut");
ok(leiste.benannt === "Textgestaltung",
"und die Gruppe ist fuer Vorleseprogramme benannt");
}
/* SIE TUT AUCH ETWAS. Ein Knopf, der nur dasteht, ist kein Knopf. */
const umgelegt = await chef.seite.evaluate(() => {
const feld = document.getElementById("text");
if (!feld) return null;
feld.value = "hallo welt";
feld.setSelectionRange(0, 5);
feld.focus();
document.querySelector(".textform__knopf--fett").click();
const nachher = feld.value;
/* Noch einmal derselbe Knopf -- das muss es wieder wegnehmen. */
feld.setSelectionRange(2, 7);
document.querySelector(".textform__knopf--fett").click();
return { einmal: nachher, zweimal: feld.value };
});
ok(umgelegt && umgelegt.einmal === "**hallo** welt",
`der F-Knopf macht daraus ${JSON.stringify(umgelegt && umgelegt.einmal)}`);
ok(umgelegt && umgelegt.zweimal === "hallo welt",
"und noch einmal gedrueckt nimmt er es wieder weg");
}
/* =======================================================================
7. UND AUF DEN ANDEREN SEITEN
Filipes Wunsch nennt ausdruecklich "wenn die modis oder rechte hand
oder dogfather eine aufgabe oder sachen posten" -- das ist das
Aufgabenbrett und sind die Bretter, nicht nur der Chat.
======================================================================= */
melde("\n=== Auch beim Aufgaben- und Brettschreiben ===");
{
for (const [seite, oeffnen, feld] of [
["aufgaben.html", "#neu-auf", "#f-text"],
["bereich.html?b=ideen", "#neu-auf", "#f-text"],
]) {
const p = await alsRolle("admin", "CODE-DOGI-0001", seite);
try {
/* Das Formular aufklappen -- solange es zu ist, gibt es das Feld
noch gar nicht, und die Leiste koennte gar nicht dort sein. */
const auf = await p.seite.$(oeffnen);
if (auf) { await auf.click(); await p.seite.waitForTimeout(700); }
const lage = await p.seite.evaluate((sel) => {
const f = document.querySelector(sel);
if (!f) return { feld: false };
const l = f.parentElement && f.parentElement.querySelector(".textform");
return {
feld: true,
leiste: !!l,
knoepfe: l ? [...l.querySelectorAll("button")].map((b) => b.textContent).join("") : "",
};
}, feld);
ok(lage.feld, `${seite}: das Schreibfeld ist da`);
ok(lage.leiste === true, `${seite}: und die Leiste steht daran`);
ok(lage.knoepfe === "FKUS", `${seite}: mit denselben vier Knoepfen`);
} finally {
await p.seite.waitForTimeout(150);
await p.ctx.close();
}
}
}
/* =======================================================================
8. GEGENPROBEN -- kann diese Pruefung ueberhaupt rot werden?
======================================================================= */
melde("\n=== Gegenproben ===");
{
/* (a) Ohne die Gestaltung bliebe der Text roh. Statt die Datei zu
entfernen (das faende nur heraus, dass dann gar nichts geht),
wird der Zerleger fuer einen Augenblick abgeschaltet -- genau
wie es der Rueckfall in chat.js vorsieht. */
const ohne = await chef.seite.evaluate(() => {
const echt = window.Textform;
window.Textform = null;
try {
const p = document.createElement("p");
/* der Rueckfallweg aus chat.js, woertlich */
const teilchen = window.Textform
? window.Textform.teile("**fett**")
: [{ text: "**fett**", stile: {} }];
for (const t of teilchen) p.append(document.createTextNode(t.text));
return { text: p.textContent, fett: p.querySelectorAll("strong").length };
} finally { window.Textform = echt; }
});
ok(ohne.text === "**fett**" && ohne.fett === 0,
"ohne die Gestaltung stuenden die Sternchen da -- die Probe kann also anschlagen");
/* (b) Und die Seitenableitung: ein erfundenes Skript, das die
Gestaltung benutzt, muss eine Seite ohne Modul auffliegen
lassen. Sonst waere Abschnitt 1 nur zufaellig gruen. */
const { ohneKommentar } = await import("./helfer-ohne-kommentar.mjs");
const erfunden = "/* Textform steht hier nur im Kommentar */\nvar x = 1;";
const echtNutzer = "var y = window.Textform.zeichne('a');";
ok(/\bTextform\b/.test(ohneKommentar(erfunden, ".js")) === false,
"ein Skript, das die Gestaltung nur im Kommentar nennt, gilt nicht als Nutzer");
ok(/\bTextform\b/.test(ohneKommentar(echtNutzer, ".js")) === true,
"eines, das sie wirklich aufruft, schon");
}
melde("\n=== Kein Skript ist unterwegs gestolpert ===");
ok(seitenFehler.length === 0, seitenFehler.length === 0
? "keine der besuchten Seiten meldet einen Skriptfehler"
: `Skriptfehler: ${seitenFehler.join(" | ")}`);
await chef.ctx.close();
await browser.close();
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Zugabe */ }
melde(`\n ${fehler === 0 ? "ALLES IN ORDNUNG" : `${fehler} FEHLER`}`);
process.exit(fehler ? 1 : 0);