Files
dogfather-universe/server/pruef-chat-optik.mjs
T
DogFatherGit 0a2363374c Eine Nachricht, die nicht ankommt, sieht man jetzt -- und schickt sie neu
DER KOMMENTAR STAND DA, DIE SACHE NICHT. Im Chat stand woertlich:
"NICHT STILL VERSCHWINDEN LASSEN. Wer etwas schreibt und es sieht,
glaubt, es sei angekommen." Gemessen: `markiereAlsGescheitert` setzte
`n.gescheitert = true` -- und gelesen hat das Merkmal NIEMAND, weder
das Skript noch das CSS. Eine gescheiterte Nachricht sah exakt aus wie
eine zugestellte. Im CSS stand an der Stelle eine leere Regel mit dem
Kommentar "Platzhalter, damit :has unterstuetzt bleibt".

Ein Kommentar, der eine Absicht beschreibt, erfuellt sie nicht.

Jetzt: sichtbare Kante an der Blase, blasse Darstellung solange sie
unterwegs ist, und ein Knopf "Nochmal senden" daneben. Der Satz in der
Meldezeile bat vorher ums Kopieren -- fuenf Handgriffe fuer etwas, das
die Seite in einem tun kann; den Text hat sie ja noch.

KEIN ZWEITER SENDEWEG: Der Knopf legt den Text zurueck ins Feld,
stellt das Antwortziel wieder her und ruft `abschicken`. Ein eigener
Weg waere morgen anders als dieser, und der Unterschied fiele erst
auf, wenn er zaehlt.

"ZUM NEUESTEN" STAND AUF EINER RECHNUNG VON GESTERN: `bottom: 96px` --
die Hoehe der Schreibleiste an dem Tag, an dem der Knopf gebaut wurde.
Das Textfeld waechst aber bis 160. Wer lange tippt und gleichzeitig
oben liest, hatte den Knopf HINTER der Leiste. Und darueber koennen
noch das Nachtruhe-Band und der Hochlade-Balken stehen.
Jetzt misst die Seite den ganzen Unterbau selbst -- ueber einen
Beobachter statt einer Liste von Ausloesern, denn das Band erscheint
um Mitternacht von selbst. Kommt morgen ein viertes Bauteil dazu,
rechnet es von allein mit.

NEUE PRUEFUNG FUER EINEN WEG, DEN NIE ETWAS GEPRUEFT HAT:
pruef-chat-optik faengt die Sendeanfrage jetzt einmal ab und geht den
ganzen Fehlerweg durch -- 10 Aussagen, darunter:
  der Unterschied ist auch zu SEHEN, nicht nur im Merkmal (Rand 1px)
  die Nachricht steht danach GENAU EINMAL da, nicht doppelt
  und sie ist beim Gegenueber angekommen
Der letzte ist der eigentliche: Alles davor koennte gut aussehen und
trotzdem nichts zugestellt haben.

pruef-treffchat war seit gestern rot -- 8 Fehler ueber "undefined".
Sie suchte die Kachel mit `ziel === "chat.html"`, seit dem 19.09.
heisst es `chat.html?raum=treff` (sie fuehrt direkt in den Raum). Kein
Befund ueber das Haus, sondern einer ueber sich selbst. Sie sucht
jetzt nach der SEITE und prueft statt des Wortlauts das, worauf es
ankommt: Man erkennt den Chat, und der Name kommt genau einmal vor.
110/0 statt 108 mit 8 Fehlern.

pruef-chat, -anhaenge, -kanaele, -optik, -ausbau gruen,
pruef-start-ansicht 151/0, pruef-tippziele 11/0, pruef-nachfrage 33/0,
pruef-leerzustand 13/0, pruef-meldungen 8/0, pruef-css-klassen gruen.
2026-09-20 12:44:31 +02:00

515 lines
24 KiB
JavaScript

/* CHAT — SCHREIBEN ZWEI LEUTE WIRKLICH MITEINANDER?
pruef-chat.mjs beweist, dass der Server richtig entscheidet, WER mit
wem darf. Diese Prüfung fragt das, was der Wunsch eigentlich meinte:
*"jeder kann dann eine nachricht reinschreiben, die andere person
bekommt eine benachrichtigung und kann dann antworten."*
Dafür laufen ZWEI Browser gleichzeitig. Luna schreibt, und ohne dass
irgendjemand etwas neu lädt, muss die Nachricht bei Patrick
erscheinen. Das ist der Kern -- alles andere ist Ausstattung.
WARUM ZWEI ECHTE BROWSER und nicht zwei Anfragen: Die sofortige
Zustellung hängt an einer offenen Verbindung (SSE), an einem
EventSource im Browser und daran, dass die Seite darauf richtig
reagiert. Mit fetch allein prüfte man den Server, nicht den Chat.
GEMESSEN WIRD AUSSERDEM:
* Die Zahl in der Kopfleiste -- auf einer ANDEREN Seite, denn dort
muss sie ankommen, damit jemand von der Nachricht erfährt.
* Dass die eigene Nachricht sofort dasteht, nicht erst nach der
Antwort des Servers.
* Dass "gelesen" die Zahl wieder wegnimmt.
GEGENPROBEN durchgehend. */
import { mkdtempSync, rmSync, appendFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
const ordner = mkdtempSync(join(tmpdir(), "ws-chatoptik-"));
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
import { notbremse } from "./helfer-notbremse.mjs";
import { bestaetige } from "./helfer-nachfrage.mjs";
const { portMussFreiSein } = await import("./helfer-port.mjs");
await portMussFreiSein(4317, "die Chat-Optikpruefung");
process.env.PORT = "4317";
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");
/* DIE NOTBREMSE. Muss NACH index.js stehen: Der Server haengt dort
zwei Auffangnetze ein, die einen Absturz nur protokollieren --
richtig fuer den Betrieb, fatal fuer eine Pruefung. Ohne diese
Zeile bleibt der Prozess nach einem Fehler ewig stehen, weil der
Server ihn am Leben haelt (siehe helfer-notbremse.mjs). */
notbremse(480_000, "pruef-chat-optik");
await new Promise((r) => setTimeout(r, 1000));
const BASIS = "http://127.0.0.1:4317";
const PROTOKOLL = join(process.cwd(), "pruef-chat-optik-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++; };
/* ---- Bestand ------------------------------------------------------------ */
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 idPat = anlegen("Patrick", "scout", "CODE-SCOU-0001");
const idLuna = anlegen("Luna", "creator", "CODE-CREA-0001");
d.prepare("INSERT INTO betreuung (creator_id, betreuer_id, seit) VALUES (?,?,?)").run(idLuna, idPat, jetzt);
d.close();
const pw = await import("file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
const browser = await pw.chromium.launch();
async function alsRolle(rolle, code, wohin = "chat.html", breite = 1280) {
const ctx = await browser.newContext({ viewport: { width: breite, height: 900 } });
const seite = await ctx.newPage();
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 };
}
/* =======================================================================
1. Ein Gespräch anfangen
======================================================================= */
melde("\n=== Ein Gespräch anfangen ===");
const luna = await alsRolle("creator", "CODE-CREA-0001");
{
const leer = await luna.seite.isVisible("#liste-leer");
ok(leer, "am Anfang steht da, dass es noch keine Gespräche gibt");
await luna.seite.click("#neu-oeffnen");
await luna.seite.waitForSelector("#neu-dialog[open]", { timeout: 5000 });
const knoepfe = await luna.seite.$$eval(".chat-wahl__knopf",
(b) => b.map((x) => x.textContent.trim()));
/* Luna erreicht ihren Scout und DogFather -- die Regel aus
pruef-chat, hier an der Oberfläche nachgewiesen. */
ok(knoepfe.length === 2, `${knoepfe.length} Personen zur Auswahl: ${knoepfe.join(", ")}`);
const los = await luna.seite.$eval("#neu-los", (b) => b.disabled);
ok(los, "der Knopf ist gesperrt, solange niemand gewählt ist");
await luna.seite.click('.chat-wahl__knopf[data-rolle="scout"]');
await luna.seite.waitForTimeout(200);
const frei = await luna.seite.$eval("#neu-los", (b) => !b.disabled);
const beschriftung = await luna.seite.$eval("#neu-los", (b) => b.textContent.trim());
ok(frei && beschriftung === "Schreiben",
`nach der Wahl ist er frei und heißt "${beschriftung}"`);
/* Der Gruppenname erscheint erst ab zwei -- vorher wäre er eine
Frage ohne Anlass. */
const nameVersteckt = await luna.seite.$eval("#gruppenname-zeile", (e) => e.hidden);
ok(nameVersteckt, "bei einer Person wird nicht nach einem Gruppennamen gefragt");
await luna.seite.click("#neu-los");
await luna.seite.waitForSelector("#schreiben:not([hidden])", { timeout: 8000 });
const titel = await luna.seite.$eval("#verlauf-titel", (e) => e.textContent);
ok(titel === "Patrick", `das Gespräch ist offen: "${titel}"`);
}
/* =======================================================================
2. DER KERN: schreiben, und es kommt an — ohne Neuladen
======================================================================= */
melde("\n=== Zwei Browser, eine Nachricht ===");
const pat = await alsRolle("scout", "CODE-SCOU-0001");
{
/* Patrick öffnet dasselbe Gespräch. */
await pat.seite.waitForSelector(".chat-raum__knopf", { timeout: 8000 });
await pat.seite.click(".chat-raum__knopf");
await pat.seite.waitForSelector("#schreiben:not([hidden])", { timeout: 8000 });
await pat.seite.waitForTimeout(600);
const vorher = (await pat.seite.$$(".chat-nachricht")).length;
/* Luna schreibt. */
await luna.seite.fill("#text", "Hey Patrick, kurze Frage zum Start-Check!");
await luna.seite.click("#senden");
/* SOFORT bei Luna sichtbar -- vor jeder Serverantwort. Deshalb wird
hier bewusst nur ganz kurz gewartet. */
await luna.seite.waitForTimeout(120);
const beiLuna = await luna.seite.$$eval(".chat-nachricht__text", (t) => t.map((x) => x.textContent));
ok(beiLuna.some((t) => /Start-Check/.test(t)),
"die eigene Nachricht steht sofort da, ohne auf den Server zu warten");
/* UND SIE KOMMT BEI PATRICK AN, ohne dass er etwas tut. */
const angekommen = await pat.seite.waitForFunction(
() => [...document.querySelectorAll(".chat-nachricht__text")]
.some((t) => /Start-Check/.test(t.textContent)),
{ timeout: 8000 }).then(() => true).catch(() => false);
ok(angekommen, angekommen
? "sie erscheint bei Patrick von selbst — kein Neuladen nötig"
: "sie kam bei Patrick NICHT an (der Strom liefert nicht)");
const nachher = (await pat.seite.$$(".chat-nachricht")).length;
ok(nachher === vorher + 1, `Patrick sieht ${nachher} statt ${vorher} Nachrichten`);
/* Und die Seiten sind sich einig, WER sie geschrieben hat. */
const beiPatSelbst = await pat.seite.$eval(".chat-nachricht:last-of-type",
(e) => e.dataset.selbst === "ja");
const beiLunaSelbst = await luna.seite.$eval(".chat-nachricht:last-of-type",
(e) => e.dataset.selbst === "ja");
ok(!beiPatSelbst && beiLunaSelbst,
"bei Luna steht sie rechts (eigene), bei Patrick links (fremde)");
}
/* =======================================================================
3. Und zurück
======================================================================= */
melde("\n=== Patrick antwortet ===");
{
await pat.seite.fill("#text", "Klar, schieß los!");
await pat.seite.click("#senden");
const zurueck = await luna.seite.waitForFunction(
() => [...document.querySelectorAll(".chat-nachricht__text")]
.some((t) => /schieß los/.test(t.textContent)),
{ timeout: 8000 }).then(() => true).catch(() => false);
ok(zurueck, "die Antwort erscheint bei Luna von selbst");
/* Der Verlauf steht in der richtigen Reihenfolge -- eine
Unterhaltung, die durcheinandergerät, ist unbrauchbar. */
const texte = await luna.seite.$$eval(".chat-nachricht__text", (t) => t.map((x) => x.textContent));
ok(/Start-Check/.test(texte[0]) && /schieß los/.test(texte[1]),
`Reihenfolge stimmt: "${texte.map((t) => t.slice(0, 18)).join('" → "')}"`);
}
/* =======================================================================
4. Die Zahl in der Kopfleiste — auf einer ANDEREN Seite
======================================================================= */
melde("\n=== Erfährt man davon, ohne im Chat zu sein? ===");
{
/* Patrick geht woandershin. Genau dort muss die Zahl erscheinen --
sonst merkt er von einer Nachricht nichts. */
await pat.seite.goto(`${BASIS}/workspace/aufgaben.html`, { waitUntil: "networkidle" });
await pat.seite.waitForTimeout(900);
const knopfDa = await pat.seite.$("#chat-knopf, .chat-knopf");
ok(!!knopfDa, "auf der Aufgabenseite steht ein Chat-Knopf in der Kopfleiste");
const vorher = await pat.seite.$eval(".chat-knopf__zahl",
(e) => (e.hidden ? "0" : e.textContent));
ok(vorher === "0", `vorher zeigt er nichts an (${vorher})`);
await luna.seite.fill("#text", "Wann hast du Zeit für einen Call?");
await luna.seite.click("#senden");
const erschienen = await pat.seite.waitForFunction(() => {
const z = document.querySelector(".chat-knopf__zahl");
return z && !z.hidden && z.textContent === "1";
}, { timeout: 8000 }).then(() => true).catch(() => false);
ok(erschienen, erschienen
? "die Zahl 1 erscheint von selbst in der Kopfleiste"
: "die Zahl erscheint NICHT — man erfährt nichts von der Nachricht");
/* Und der Knopf selbst trägt Farbe -- eine kleine Zahl allein
übersieht man beim Überfliegen. */
const markiert = await pat.seite.$eval(".chat-knopf", (e) => e.dataset.neu === "ja");
ok(markiert, "der Knopf ist zusätzlich hervorgehoben, nicht nur die Zahl");
}
/* =======================================================================
5. Lesen nimmt die Zahl wieder weg
======================================================================= */
melde("\n=== Gelesen ===");
{
await pat.seite.goto(`${BASIS}/workspace/chat.html`, { waitUntil: "networkidle" });
await pat.seite.waitForSelector(".chat-raum__knopf", { timeout: 8000 });
const zahlInListe = await pat.seite.$eval(".chat-raum__zahl", (e) => e.textContent)
.catch(() => null);
ok(zahlInListe === "1", `in der Gesprächsliste steht die Zahl (${zahlInListe})`);
await pat.seite.click(".chat-raum__knopf");
await pat.seite.waitForTimeout(1200);
const weg = await pat.seite.$(".chat-raum__zahl");
ok(!weg, "nach dem Öffnen ist sie weg");
/* Und sie bleibt weg -- auch nach einem Neuladen. Wäre "gelesen" nur
eine Anzeige im Browser, käme die Zahl beim nächsten Aufruf zurück. */
await pat.seite.reload({ waitUntil: "networkidle" });
await pat.seite.waitForTimeout(1000);
const wiederDa = await pat.seite.$(".chat-raum__zahl");
ok(!wiederDa, "auch nach dem Neuladen bleibt sie weg — es ist wirklich gespeichert");
}
/* =======================================================================
6. Zurücknehmen, im Bild
======================================================================= */
melde("\n=== Zurücknehmen ===");
{
await luna.seite.bringToFront();
const vorher = (await luna.seite.$$(".chat-nachricht")).length;
/* Seit 19.09.2026 ein eigener <dialog> statt confirm() -- bei einem
confirm() haette Playwright ihn nicht von selbst bestaetigt, bei
einem <dialog> muss der Knopf wirklich angeklickt werden. */
await bestaetige(luna.seite, () => luna.seite.click(
".chat-nachricht[data-selbst='ja']:last-of-type .chat-nachricht__weg-knopf"));
await luna.seite.waitForTimeout(1000);
const nachher = (await luna.seite.$$(".chat-nachricht")).length;
ok(nachher === vorher,
`die Zeile bleibt stehen (${nachher} statt ${vorher}) — kein Loch im Verlauf`);
const hinweis = await luna.seite.$(".chat-nachricht__weg");
ok(!!hinweis, "an ihrer Stelle steht 'Nachricht zurückgenommen'");
/* Und beim Gegenüber verschwindet der Text ebenfalls -- von selbst. */
const beiPat = await pat.seite.waitForFunction(
() => !![...document.querySelectorAll(".chat-nachricht__weg")].length,
{ timeout: 8000 }).then(() => true).catch(() => false);
ok(beiPat, "auch bei Patrick ist der Text weg, ohne Neuladen");
}
/* =======================================================================
GEGENPROBE — misst die Prüfung überhaupt etwas?
======================================================================= */
melde("\n=== Gegenprobe ===");
{
/* Träfe der Selektor nichts, wären alle Haken oben wertlos. */
const vorher = (await luna.seite.$$(".chat-nachricht")).length;
await luna.seite.evaluate(() => {
for (const n of document.querySelectorAll(".chat-nachricht")) n.remove();
});
const nachher = (await luna.seite.$$(".chat-nachricht")).length;
ok(vorher >= 3 && nachher === 0,
`der Selektor trifft (${vorher} gefunden, nach dem Entfernen ${nachher})`);
/* Und ein Text, der nie geschrieben wurde, darf NICHT gefunden
werden -- sonst prüfte "kam an" gar nichts. */
await luna.seite.reload({ waitUntil: "networkidle" });
await luna.seite.waitForTimeout(1200);
const erfunden = await luna.seite.$$eval(".chat-nachricht__text",
(t) => t.some((x) => /Diesen Satz gab es nie/.test(x.textContent)));
ok(!erfunden, "ein nie geschriebener Satz wird nicht gefunden");
}
/* =======================================================================
DER FUSS EINER NACHRICHT BLEIBT IN SEINER BLASE (10.09.2026)
Am Fuss jeder Nachricht stehen die Handgriffe: Zeit, antworten,
zuruecknehmen, kopieren -- und seit Kapitel 7.2 auch anheften. Mit
drei Eintraegen ging die Rechnung auf, mit vier nicht mehr:
Gemessen stand "kopieren" bei 390 px, der haeufigsten Handybreite,
18 px UEBER der Blase; bei 320 px waren es 75.
Gefunden hat das kein Testlauf, sondern ein Blick auf ein
Bildschirmfoto. Damit das beim FUENFTEN Handgriff nicht wieder
passiert, wird es ab jetzt gemessen -- und zwar an echten
Bildpunkten, nicht an der Rechnung, die zu dem Fehler gefuehrt hat.
GEPRUEFT WIRD NICHT DIE ZAHL DER ZEILEN. Ob der Fuss ein- oder
zweizeilig ist, darf sich aendern; dass etwas darin ueber den Rand
haengt, nicht. Ein `flex-wrap: wrap` ist deshalb die Loesung und
keine Schwelle: Es bricht genau dann um, wenn der Platz nicht reicht.
======================================================================= */
melde("\n=== Der Fuss bleibt in der Blase ===");
{
let gemessen = 0;
let heraus = [];
for (const breite of [320, 360, 390, 430]) {
await luna.seite.setViewportSize({ width: breite, height: 844 });
await luna.seite.waitForTimeout(350);
const m = await luna.seite.evaluate(() => {
const raus = [];
let stuecke = 0;
for (const f of document.querySelectorAll(".chat-nachricht__fuss")) {
const blase = f.closest(".chat-nachricht__blase")?.getBoundingClientRect();
if (!blase) continue;
for (const k of f.children) {
stuecke++;
const r = k.getBoundingClientRect();
if (r.width === 0) continue; /* ausgeblendet: kein Fall */
if (r.right > blase.right + 0.5 || r.left < blase.left - 0.5) {
raus.push((k.textContent || "?").trim() + " +" + Math.round(r.right - blase.right));
}
}
}
return { raus, stuecke };
});
gemessen += m.stuecke;
if (m.raus.length) heraus.push(`${breite}px: ${m.raus.join(", ")}`);
}
/* DIE ZAHL GEHOERT IN DIE BEDINGUNG, nicht nur in den Meldetext.
Findet der Selektor nichts, waere `heraus.length === 0` gruen --
und haette nichts angesehen. Genau der Fehler vom 01.09. */
ok(gemessen >= 12 && heraus.length === 0,
`${gemessen} Fusselemente auf vier Breiten gemessen, nichts haengt heraus`
+ (heraus.length ? ` -- ${heraus.join(" | ")}` : ""));
/* GEGENPROBE: Kann die Messung ueberhaupt "haengt heraus" sagen?
Ein Element wird absichtlich hinausgeschoben. Sagt sie dann
weiterhin "alles gut", misst sie gar nichts. */
const erkannt = await luna.seite.evaluate(() => {
const f = document.querySelector(".chat-nachricht__fuss");
const k = f?.lastElementChild;
if (!k) return null;
const alt = k.style.marginLeft;
k.style.marginLeft = "400px";
const blase = f.closest(".chat-nachricht__blase").getBoundingClientRect();
const draussen = k.getBoundingClientRect().right > blase.right + 0.5;
k.style.marginLeft = alt;
return draussen;
});
ok(erkannt === true, "und sie erkennt ein absichtlich hinausgeschobenes Element");
await luna.seite.setViewportSize({ width: 1280, height: 900 });
await luna.seite.waitForTimeout(300);
}
/* =====================================================================
EINE NACHRICHT, DIE NICHT ANKOMMT (20.09.2026)
Der wichtigste Satz im Code daneben lautete: „NICHT STILL
VERSCHWINDEN LASSEN. Wer etwas schreibt und es sieht, glaubt, es
sei angekommen." Gemessen am 20.09.2026 wurde er nicht eingehalten:
`markiereAlsGescheitert` setzte `n.gescheitert = true` -- und
gelesen hat das Merkmal NIEMAND, weder das Skript noch das CSS.
Eine gescheiterte Nachricht sah exakt aus wie eine zugestellte.
Ein Kommentar, der eine Absicht beschreibt, erfüllt sie nicht.
Und der Satz in der Meldezeile bat den Nutzer, die Arbeit der Seite
zu machen: „kopiere sie und versuch es erneut" -- markieren,
kopieren, Feld leeren, einfügen, senden. Fünf Handgriffe für etwas,
das die Seite in einem tun kann; den Text hat sie ja noch.
Geprüft wird deshalb der GANZE Weg, und zwar an einem echten
Fehlschlag: Die Anfrage wird unterwegs abgefangen, die Nachricht
muss sichtbar scheitern, und der Knopf daneben muss sie wirklich
zustellen.
===================================================================== */
{
melde("\n— Eine Nachricht, die nicht ankommt —");
/* Den Sendeweg EINMAL scheitern lassen. Nicht den Server abschalten:
Dann wäre auch der Ereignisstrom weg, und die Prüfung misst einen
anderen Fehler als den gemeinten. */
let abgefangen = 0;
await luna.seite.route("**/api/chat/raeume/*/nachrichten", (weg) => {
if (abgefangen === 0) { abgefangen++; return weg.abort("failed"); }
return weg.continue();
});
const TEXT = "Diese Nachricht scheitert beim ersten Versuch.";
await luna.seite.fill("#text", TEXT);
await luna.seite.press("#text", "Enter");
await luna.seite.waitForTimeout(900);
ok(abgefangen === 1, `die Anfrage wurde wirklich abgefangen (${abgefangen}x)`);
const gescheitert = await luna.seite.evaluate((t) => {
const alle = [...document.querySelectorAll(".chat-nachricht")];
const z = alle.find((e) => e.textContent.includes(t));
if (!z) return null;
return {
markiert: z.dataset.gescheitert === "ja",
knopf: !!z.querySelector(".chat-nachricht__nochmal"),
/* Sieht man den Unterschied WIRKLICH -- oder steht er nur im
Merkmal? Gemessen an echten Bildpunkten. */
rand: getComputedStyle(z.querySelector(".chat-nachricht__blase")).borderTopWidth,
};
}, TEXT);
ok(gescheitert !== null, "die Nachricht steht noch im Verlauf");
ok(gescheitert?.markiert === true, "und ist sichtbar als gescheitert markiert");
ok(gescheitert?.rand !== "0px",
`der Unterschied ist auch zu SEHEN, nicht nur im Merkmal (Rand ${gescheitert?.rand})`);
ok(gescheitert?.knopf === true, "und daneben steht „Nochmal senden“");
/* Der Satz in der Meldezeile verlangt nichts mehr vom Nutzer. */
const satz = await luna.seite.evaluate(() =>
document.getElementById("fehler")?.textContent || "");
ok(!/kopiere/i.test(satz),
`und die Meldung bittet nicht mehr ums Kopieren („${satz.slice(0, 70)}“)`);
/* ---- Und jetzt der Knopf ---------------------------------------- */
const vorher = await luna.seite.evaluate(() =>
document.querySelectorAll(".chat-nachricht").length);
await luna.seite.click(".chat-nachricht__nochmal");
await luna.seite.waitForTimeout(1200);
const danach = await luna.seite.evaluate((t) => {
const alle = [...document.querySelectorAll(".chat-nachricht")];
const treffer = alle.filter((e) => e.textContent.includes(t));
return {
anzahl: alle.length,
mitText: treffer.length,
nochGescheitert: treffer.some((e) => e.dataset.gescheitert === "ja"),
};
}, TEXT);
ok(danach.mitText === 1,
`die Nachricht steht danach GENAU EINMAL da (${danach.mitText}x) `
+ `— nicht doppelt, wie es bei einem zweiten Sendeweg passieren würde`);
ok(danach.nochGescheitert === false, "und gilt nicht mehr als gescheitert");
ok(danach.anzahl === vorher, `die Zahl der Nachrichten blieb gleich (${vorher} → ${danach.anzahl})`);
/* Und sie ist WIRKLICH beim Gegenüber angekommen -- nicht nur im
eigenen Bild. Das ist der Unterschied zwischen „sieht gut aus"
und „funktioniert". */
await pat.seite.waitForTimeout(800);
const beimAnderen = await pat.seite.evaluate((t) =>
document.body.textContent.includes(t), TEXT);
ok(beimAnderen === true, "und sie ist beim Gegenüber angekommen");
await luna.seite.unroute("**/api/chat/raeume/*/nachrichten");
}
await luna.ctx.close();
await pat.ctx.close();
await browser.close();
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
melde(`\n${fehler === 0 ? "ALLES IN ORDNUNG" : `${fehler} FEHLER`}`);
/* HIER IST process.exit() RICHTIG -- anders als in pruef-push-weg.
Diese Pruefung startet einen Express-Server, und der laeuft von
selbst weiter: offene Verbindungen, ein Zeitgeber fuer die
Sicherung, bei dieser Pruefung zusaetzlich die offenen
Ereignisstroeme des Chats. Mit `exitCode` allein wartet Node ewig
darauf, dass all das zu Ende geht -- der Lauf lief in die
Zeitueberschreitung, obwohl alle Pruefungen schon durch waren.
Der Absturz, der in pruef-push-weg gegen exit() sprach, kam von
fetch-Verbindungen mitten im Schliessen. Die gibt es hier nicht
mehr: Die Browser sind zu, bevor diese Zeile laeuft. */
process.exit(fehler ? 1 : 0);