Filipe, 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."
VORHER GEMESSEN: Es gab genau EINE Auszeichnung im ganzen Haus --
fett, und nur auf den Brettern (`mitFett` in bereich.js). Im Chat
konnte niemand etwas hervorheben.
EINE QUELLE FUER DAS GANZE HAUS: workspace/assets/js/textform.js.
Chat, Bretter und Aufgaben zeichnen ihren Text jetzt durch denselben
Zerleger. Drei Fassungen derselben Regel waeren drei Fassungen, die
verschieden altern.
**fett** _kursiv_ __unterstrichen__ ~~durchgestrichen~~
Dazu eine Leiste ueber jedem Schreibfeld: F K U S, 44 px am Finger,
Strg+B/I/U, und ein zweiter Druck nimmt die Zeichen wieder weg.
DIE WORTGRENZEN SIND DER EIGENTLICHE BAU. Ein Zerleger, der alles
auszeichnet, ist schlimmer als keiner: `datei_name_hier` hiesse
plotzlich anders, als es heisst, und `@max_muster` ebenso. Deshalb
stehen bei den Unterstrichen Wortgrenzen -- bei Sternchen und Tilden
nicht, die kommen in Text nicht versehentlich paarweise vor.
GEBAUT MIT createTextNode, NIE innerHTML. Wer Text zu Auszeichnung
macht, ist einen Tippfehler von einer Luecke entfernt. Das ist heute
sicher; pruef-textform misst es, damit es das morgen auch ist.
EIN ECHTER FEHLER DABEI GEFUNDEN -- in meinem eigenen Einbau von
vorhin: Auf dem Aufgabenbrett stand `$('f-text')` in einem Block, der
`$` gar nicht kennt (die Datei hat drei getrennte Bloecke).
`ReferenceError: $ is not defined`, die Leiste kam dort nie an, und
die Seite sah dabei vollkommen normal aus. Derselbe Block beginnt
ausserdem mit `if (!feld) return;` auf ein Aufwand-Feld, das mit
Textgestaltung nichts zu tun hat -- die Leiste haette an einer
voellig fremden Bedingung gehangen. Jetzt ein eigener Block, ohne
fremden Helfer und ohne fremde Bedingung.
Gefunden hat es die Pruefung, weil sie die Leiste AM FELD sucht,
statt den Aufruf im Quelltext zu zaehlen. Sie sammelt seitdem auch
Skriptfehler ein -- ein ReferenceError ist immer ein Befund, auch
wenn man noch nicht weiss, was er anrichtet.
pruef-textform.mjs, 50 Aussagen:
- welche Seite das Modul braucht, wird ABGELEITET (beide
Richtungen: keine ohne, keine umsonst) -- 3 brauchen, 32 nicht
- 13 Regelfaelle im echten Browser an der echten Datei, davon
sechs, die NICHT gestaltet werden duerfen
- eine echte Nachricht wird zu <strong>/<em>/<u>/<s>
- Sicherheitsprobe: <b> und <img onerror> bleiben Text, nichts
wird ausgefuehrt -- und die echte Auszeichnung daneben wirkt
trotzdem (sonst waere nur bewiesen, dass gar nichts passiert)
- die Leiste legt Zeichen wirklich um und wieder ab
- Gegenproben fuer beide Richtungen
Nachbarn unveraendert gruen: pruef-chat-optik 45/0 (war 39 -- die
Antwortleiste kam dazu), pruef-aufgabenbrett 49/0, pruef-nachfrage
49/0, pruef-css-klassen 30/0, pruef-tippziele 11/0, pruef-meldungen
8/0.
501 lines
22 KiB
JavaScript
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 "<strong>" 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 } });
|
|
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);
|