Filipe, 22.09.2026: "ersetze die zentrale durch, Die IrrenAnstalt.
genau so geschrieben bitte. und alles was treff heißt oder wo treff
steht soll durch Rudel ersetzt werden bitte."
Die Schreibweise "IrrenAnstalt" mit grossem A in der Mitte ist so
gewollt. Das steht als Hinweis daneben, damit sie beim naechsten Mal
niemand "korrigiert".
WAS UMBENANNT WURDE: alles, was jemand LIEST -- Kachelnamen,
Ueberschriften, Markenzeilen, Saetze, Meldungen, Aufgabenvorlagen.
116 Zeilen in 42 Dateien.
WAS BLEIBT: Adressen (treff-regeln.html), Bezeichner (TREFF_ROLLEN),
Datenbankwerte (bereich = 'treff'), Abfrageteile (b=treff). Eine
Adresse umzubenennen bricht jedes Lesezeichen, und ein Datenbankwert
umzuschreiben waere eine Umstellung ohne Gegenwert. Dieselbe
Entscheidung wie heute frueh bei Dogi-Media, wo material.html auch
material.html geblieben ist.
DREI GRAMMATIKFEHLER IM EIGENEN ENTWURF, alle vor dem Ausliefern
gefunden -- ein blindes Ersetzen reicht hier nicht:
1. "der Treff" ist maennlich, "das Rudel" saechlich. Ohne Tabelle
waere ueberall "Der Rudel" gestanden. (Und "Treffer" waere zu
"Rudeler" geworden, "Treffen" zu "Rudelen" -- deshalb greift die
Regel nur bei Wortgrenze und nie vor einem Kleinbuchstaben.)
2. BINDESTRICH-ZUSAMMENSETZUNGEN. In "der Treff-Chat" gehoert der
Artikel zu "Chat", nicht zu "Treff". Der erste Durchlauf machte
daraus "das Rudel-Chat", "das Rudel-Kacheln" und "ein Rudel-Raum".
Jetzt greift die Artikelregel nur, wenn das Wort allein steht.
3. GESCHUETZTE LEERZEICHEN. In den Markenzeilen steht
`Der Treff`, damit die zwei Woerter nicht umbrechen. Das
Muster hat daran vorbeigegriffen: "Der Rudel", auf fuenf
Seiten. Jetzt wird das Trennzeichen mitgefasst und unveraendert
wieder eingesetzt.
Gefunden wurden alle drei, weil jede einzelne der 116 Zeilen vor dem
Schreiben als ALT/NEU ausgegeben und gelesen wurde -- und danach
gezielt nach falschen Artikeln gesucht ("den Rudel", "der Rudel",
"einen Rudel"). Uebrig blieben sechs Treffer, und die sind alle
richtig: "einen Rudel-Raum", "der Rudel-Chat", "den Rudel-Regeln" --
Zusammensetzungen, bei denen der Artikel zum letzten Wort gehoert.
UND ZWEI PRUEFUNGEN, die die Umbenennung nicht mitbekommen haetten:
`/Treff/.test(...)` und `/Treff/i.test(...)`. Ein regulaerer Ausdruck
ist keine Zeichenkette -- sie haetten ab sofort nach einem Wort
gesucht, das die Seite nicht mehr sagt, und waeren still rot geworden,
ohne dass etwas kaputt ist.
Geprueft, alle gruen: pruef-treff, pruef-treffchat,
pruef-deutsche-texte, pruef-uebernahme, pruef-crew-adresse,
pruef-willkommen, pruef-wege-nach-draussen, pruef-vorlagen,
pruef-start-ansicht, pruef-struktur.
Co-Authored-By: Claude Opus 5 <[email protected]>
361 lines
17 KiB
JavaScript
361 lines
17 KiB
JavaScript
/* =====================================================================
|
|
Aus einer Idee wird Arbeit (15.09.2026)
|
|
|
|
Stufe 9: „Idee → Aufgabe". Das ganze Haus steht auf dem Satz *„Was
|
|
hier nicht steht, passiert nicht."* Ein Ideenbrett, aus dem keine
|
|
Aufgabe werden kann, ist der eine Ort, an dem er nicht gilt.
|
|
|
|
---------------------------------------------------------------------
|
|
DREI FRAGEN, UND ALLE DREI HABEN EINE GEGENPROBE
|
|
|
|
1. Wird aus einer Idee eine Aufgabe? ... und steht wirklich alles
|
|
Richtige darin?
|
|
2. Genau EINMAL? ... und was passiert beim
|
|
zweiten Versuch?
|
|
3. Aus JEDEM Brett? ... nein — und genau das ist
|
|
der Punkt: Ein Beitrag
|
|
aus dem Treff gehört
|
|
einem Menschen aus der
|
|
Community.
|
|
|
|
Dazu die Frage, die das Loeschkonzept aufwirft: Was passiert mit der
|
|
Aufgabe, wenn die Idee geloescht wird? (Sie bleibt — mit `SET NULL`.)
|
|
|
|
Aufrufen mit: node server/pruef-uebernahme.mjs
|
|
===================================================================== */
|
|
|
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { request as httpAnfrage } from "node:http";
|
|
import { eigenerPort } from "./helfer-port.mjs";
|
|
|
|
const PORT = await eigenerPort(import.meta, "pruef-uebernahme");
|
|
|
|
const ordner = mkdtempSync(join(tmpdir(), "ws-ueb-"));
|
|
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
|
|
process.env.PORT = String(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 }); };
|
|
import { notbremse } from "./helfer-notbremse.mjs";
|
|
await import("./index.js");
|
|
notbremse(180_000, "pruef-uebernahme");
|
|
await new Promise((r) => setTimeout(r, 700));
|
|
|
|
process.on("uncaughtException", (f) => { console.error("ABSTURZ:", f); process.exit(7); });
|
|
process.on("unhandledRejection", (f) => { console.error("ABSTURZ (Versprechen):", f); process.exit(7); });
|
|
|
|
let fehler = 0, geprueft = 0;
|
|
const melde = (t) => console.log(t);
|
|
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
|
|
|
|
const CREW = "crew.dogfather-universe.com";
|
|
const HAUS = "workspace.dogfather-universe.com";
|
|
|
|
const { DatabaseSync } = await import("node:sqlite");
|
|
const { scryptSync, randomBytes, createHmac } = await import("node:crypto");
|
|
const modul = await import("./workspace-uebernahme.js");
|
|
const { BEREICHE } = await import("./workspace-bereiche.js");
|
|
/* Welche Bretter dem Treff gehoeren, sagt workspace.js -- nicht
|
|
ein Merkmal, das ich mir an BEREICHE vorgestellt habe. Der erste
|
|
Anlauf fragte nach `BEREICHE[b].treff` und fand null Bretter: eine
|
|
Gegenprobe, die nichts gegenprueft. */
|
|
const { TREFF_BRETTER } = await import("./workspace.js");
|
|
|
|
const oeffne = () => new DatabaseSync(process.env.WORKSPACE_DB);
|
|
const jetzt = new Date().toISOString();
|
|
|
|
let idDogi, idLuna, idIdee, idTreff, idIdeeHoch;
|
|
{
|
|
const d = oeffne();
|
|
const schluessel = randomBytes(32).toString("hex");
|
|
d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)"
|
|
+ " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING")
|
|
.run("code_kennung_schluessel", schluessel, jetzt);
|
|
const 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, code_kennung, aktiv, erstellt)"
|
|
+ " VALUES (?,?,?,?,?,?,1,?)")
|
|
.run(name, rolle, hash, salt, 32768, createHmac("sha256", schluessel).update(code).digest("hex"), jetzt);
|
|
return d.prepare("SELECT last_insert_rowid() AS id").get().id;
|
|
};
|
|
idDogi = anlegen("Filipe", "admin", "CODE-DOGI-0001");
|
|
idLuna = anlegen("Luna", "creator", "CODE-CREA-0001");
|
|
anlegen("Kessi", "gast", "CODE-GAST-0001");
|
|
|
|
const rein = d.prepare(`INSERT INTO eintraege
|
|
(bereich, art, titel, text, datum, dringlichkeit, status, creator_id, erstellt, erstellt_von)
|
|
VALUES (?,?,?,?,?,?,'offen',?,?,?)`);
|
|
rein.run("ideen", "content", "Reihe über Technik", "Drei Teile, je fünf Minuten.",
|
|
jetzt.slice(0, 10), "mittel", idLuna, jetzt, idDogi);
|
|
idIdee = d.prepare("SELECT last_insert_rowid() AS id").get().id;
|
|
rein.run("ideen", "content", "Dringende Idee", "Muss diese Woche.",
|
|
jetzt.slice(0, 10), "hoch", null, jetzt, idDogi);
|
|
idIdeeHoch = d.prepare("SELECT last_insert_rowid() AS id").get().id;
|
|
d.prepare(`INSERT INTO eintraege (bereich, art, titel, text, datum, erstellt, erstellt_von)
|
|
VALUES ('treff','hallo','Beitrag aus dem Rudel','Hallo zusammen',?,?,?)`)
|
|
.run(jetzt.slice(0, 10), jetzt, idDogi);
|
|
idTreff = d.prepare("SELECT last_insert_rowid() AS id").get().id;
|
|
d.close();
|
|
}
|
|
|
|
function ruf(pfad, host, methode, kopf = {}, koerper = null) {
|
|
return new Promise((fertig, schief) => {
|
|
const a = httpAnfrage({ host: "127.0.0.1", port: PORT, path: pfad, method: methode,
|
|
headers: { Host: host, ...(koerper ? { "Content-Type": "application/json" } : {}), ...kopf } },
|
|
(antwort) => {
|
|
let text = "";
|
|
antwort.on("data", (s) => { text += s; });
|
|
antwort.on("end", () => fertig({ code: antwort.statusCode, text, kopf: antwort.headers }));
|
|
});
|
|
a.on("error", schief);
|
|
if (koerper) a.write(JSON.stringify(koerper));
|
|
a.end();
|
|
});
|
|
}
|
|
const json = (a) => { try { return JSON.parse(a.text); } catch { return {}; } };
|
|
async function anmelden(rolle, code, host) {
|
|
const r = await ruf("/workspace/api/anmelden", host, "POST", {},
|
|
rolle === "gast" ? { rolle, code, alter_ok: true } : { rolle, code });
|
|
return [].concat(r.kopf?.["set-cookie"] || []).map((c) => c.split(";")[0]).join("; ");
|
|
}
|
|
const kDogi = await anmelden("admin", "CODE-DOGI-0001", HAUS);
|
|
const kGast = await anmelden("gast", "CODE-GAST-0001", CREW);
|
|
ok(!!kDogi && !!kGast, "DogFather und ein Community-Mitglied sind angemeldet");
|
|
|
|
/* =======================================================================
|
|
1. DIE LISTE DER BRETTER IST NICHT ERFUNDEN
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 1. Aus welchen Brettern ===");
|
|
{
|
|
const echte = Object.keys(BEREICHE);
|
|
ok(echte.length > 5, `${echte.length} Bretter gibt es im Haus`);
|
|
|
|
const erfunden = modul.AUS_BRETTERN.filter((b) => !echte.includes(b));
|
|
ok(erfunden.length === 0,
|
|
erfunden.length ? `GIBT ES NICHT: ${erfunden.join(", ")}`
|
|
: `alle ${modul.AUS_BRETTERN.length} genannten Bretter gibt es wirklich`);
|
|
|
|
/* DIE TREFF-BRETTER STEHEN NICHT DRIN, und das ist der Punkt. */
|
|
const treffBretter = TREFF_BRETTER.filter((b) => echte.includes(b));
|
|
ok(treffBretter.length > 0, `${treffBretter.length} Bretter gehören dem Rudel`);
|
|
const drin = treffBretter.filter((b) => modul.AUS_BRETTERN.includes(b));
|
|
ok(drin.length === 0,
|
|
drin.length ? `TREFF-BRETT DABEI: ${drin.join(", ")}`
|
|
: "keines davon ist dabei — was jemand aus der Community schreibt, gehört ihm");
|
|
}
|
|
|
|
/* =======================================================================
|
|
2. AUS EINER IDEE WIRD EINE AUFGABE
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 2. Die Übernahme ===");
|
|
{
|
|
const a = await ruf(`/workspace/api/eintrag/${idIdee}/zur-aufgabe`, HAUS, "POST",
|
|
{ cookie: kDogi }, {});
|
|
ok(a.code === 201, `aus der Idee wird eine Aufgabe (${a.code})`);
|
|
const neu = json(a);
|
|
ok(Number.isInteger(neu.aufgabe_id), `mit einer Nummer (${neu.aufgabe_id})`);
|
|
|
|
const d = oeffne();
|
|
const auf = d.prepare("SELECT * FROM aufgaben WHERE id = ?").get(neu.aufgabe_id);
|
|
const idee = d.prepare("SELECT * FROM eintraege WHERE id = ?").get(idIdee);
|
|
d.close();
|
|
|
|
ok(auf.titel === "Reihe über Technik", `der Titel wandert mit (${auf.titel})`);
|
|
ok(/fünf Minuten/.test(auf.beschreibung || ""), "der Text auch");
|
|
ok(auf.status === "offen", `sie beginnt offen (${auf.status})`);
|
|
ok(auf.aus_eintrag_id === idIdee, `und weiß, woher sie kam (${auf.aus_eintrag_id})`);
|
|
ok(auf.creator_id === idLuna,
|
|
`der Creator-Bezug bleibt erhalten (${auf.creator_id === idLuna ? "Luna" : auf.creator_id})`);
|
|
ok(auf.verantwortlich_id === idDogi,
|
|
"zuständig ist, wer übernommen hat — nicht, wer die Idee aufgeschrieben hat");
|
|
/* WAS ABSICHTLICH NICHT MITWANDERT. */
|
|
ok(auf.frist === null, "eine Frist wird NICHT erfunden");
|
|
|
|
/* DIE IDEE BLEIBT STEHEN. Sie wird nicht verschoben und nicht
|
|
geloescht -- wer nachsieht, was aus einem Gedanken geworden ist,
|
|
soll den Gedanken noch finden. */
|
|
ok(!!idee && idee.bereich === "ideen",
|
|
`und die Idee steht unverändert im Brett (${idee?.bereich})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
3. GENAU EINMAL
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 3. Genau einmal ===");
|
|
{
|
|
const zweite = await ruf(`/workspace/api/eintrag/${idIdee}/zur-aufgabe`, HAUS, "POST",
|
|
{ cookie: kDogi }, {});
|
|
ok(zweite.code === 409, `ein zweiter Versuch wird abgelehnt (${zweite.code})`);
|
|
ok(json(zweite).fehler === "schon_uebernommen", "mit einem Grund, den die Seite versteht");
|
|
ok(Number.isInteger(json(zweite).aufgabe_id),
|
|
`und dem Verweis auf die vorhandene Aufgabe (${json(zweite).aufgabe_id})`);
|
|
|
|
const d = oeffne();
|
|
const wieviele = d.prepare(
|
|
"SELECT COUNT(*) AS n FROM aufgaben WHERE aus_eintrag_id = ?").get(idIdee).n;
|
|
d.close();
|
|
ok(wieviele === 1, `es gibt genau eine Aufgabe dazu (${wieviele})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
4. NICHT AUS JEDEM BRETT
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 4. Nicht aus dem Rudel ===");
|
|
{
|
|
const a = await ruf(`/workspace/api/eintrag/${idTreff}/zur-aufgabe`, HAUS, "POST",
|
|
{ cookie: kDogi }, {});
|
|
ok(a.code === 400, `aus einem Rudel-Beitrag wird keine Aufgabe (${a.code})`);
|
|
ok(/keine Aufgabe/.test(json(a).fehler || ""), `und die Absage sagt warum (${json(a).fehler})`);
|
|
|
|
const d = oeffne();
|
|
const n = d.prepare("SELECT COUNT(*) AS n FROM aufgaben WHERE aus_eintrag_id = ?").get(idTreff).n;
|
|
d.close();
|
|
ok(n === 0, "es ist auch wirklich keine entstanden");
|
|
|
|
const erfunden = await ruf("/workspace/api/eintrag/999999/zur-aufgabe", HAUS, "POST",
|
|
{ cookie: kDogi }, {});
|
|
ok(erfunden.code === 404, `eine erfundene Nummer ergibt 404 (${erfunden.code})`);
|
|
|
|
const ohne = await ruf(`/workspace/api/eintrag/${idIdeeHoch}/zur-aufgabe`, HAUS, "POST", {}, {});
|
|
ok(ohne.code === 401, `ohne Anmeldung gar nichts (${ohne.code})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
5. DIE DRINGLICHKEIT WIRD ZUR PRIORITÄT
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 5. Dringlichkeit ===");
|
|
{
|
|
const a = await ruf(`/workspace/api/eintrag/${idIdeeHoch}/zur-aufgabe`, HAUS, "POST",
|
|
{ cookie: kDogi }, {});
|
|
ok(a.code === 201, `die dringende Idee wird übernommen (${a.code})`);
|
|
ok(json(a).prioritaet === "hoch", `und behält ihre Dringlichkeit (${json(a).prioritaet})`);
|
|
|
|
/* GEGENPROBE -- ohne sie beweist die Zeile darueber nichts: Die
|
|
andere Idee stand auf "mittel" und muss dort geblieben sein. */
|
|
const d = oeffne();
|
|
const andere = d.prepare(
|
|
"SELECT prioritaet FROM aufgaben WHERE aus_eintrag_id = ?").get(idIdee);
|
|
d.close();
|
|
ok(andere?.prioritaet === "mittel",
|
|
`die andere steht weiterhin auf mittel (${andere?.prioritaet}) — es wird nicht alles „hoch“`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
6. WAS DAS BRETT DAZU SAGT
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 6. Die Liste am Brett ===");
|
|
{
|
|
const a = json(await ruf("/workspace/api/bereich/ideen/uebernahmen", HAUS, "GET", { cookie: kDogi }));
|
|
ok(a.moeglich === true, "das Ideenbrett kennt Übernahmen");
|
|
ok(Object.keys(a.uebernahmen || {}).length === 2,
|
|
`zwei Einträge sind übernommen (${Object.keys(a.uebernahmen || {}).join(", ")})`);
|
|
ok(a.uebernahmen[idIdee]?.status === "offen",
|
|
`und der Stand der Aufgabe steht dabei (${a.uebernahmen[idIdee]?.status})`);
|
|
|
|
/* GEGENPROBE: Ein Treff-Brett sagt „nicht moeglich" -- daran
|
|
entscheidet die Oberflaeche, ob der Knopf ueberhaupt hingehoert. */
|
|
const treff = json(await ruf("/workspace/api/bereich/treff/uebernahmen", HAUS, "GET", { cookie: kDogi }));
|
|
ok(treff.moeglich === false, "ein Rudel-Brett kennt keine");
|
|
ok(Object.keys(treff.uebernahmen || {}).length === 0, "und liefert auch keine");
|
|
}
|
|
|
|
/* =======================================================================
|
|
7. WIRD DIE IDEE GELÖSCHT, BLEIBT DIE ARBEIT
|
|
|
|
Die Frage, die das Loeschkonzept aufwirft. `ON DELETE SET NULL` und
|
|
nicht CASCADE: Die Aufgabe ist inzwischen etwas Eigenes.
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 7. Wenn die Idee verschwindet ===");
|
|
{
|
|
const d = oeffne();
|
|
const vorher = d.prepare("SELECT id FROM aufgaben WHERE aus_eintrag_id = ?").get(idIdee);
|
|
d.prepare("PRAGMA foreign_keys = ON").run();
|
|
d.prepare("DELETE FROM eintraege WHERE id = ?").run(idIdee);
|
|
const nachher = d.prepare("SELECT id, aus_eintrag_id, titel FROM aufgaben WHERE id = ?")
|
|
.get(vorher.id);
|
|
d.close();
|
|
|
|
ok(!!nachher, "die Aufgabe gibt es noch, obwohl die Idee weg ist");
|
|
ok(nachher.aus_eintrag_id === null,
|
|
`und der Verweis steht auf null statt ins Leere (${nachher.aus_eintrag_id})`);
|
|
ok(nachher.titel === "Reihe über Technik", "mit unverändertem Titel");
|
|
}
|
|
|
|
/* =======================================================================
|
|
8. DER KNOPF STEHT IM BROWSER — UND WIRKT
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 8. Im Browser ===");
|
|
{
|
|
const { chromium } = await import(
|
|
"file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
|
|
const browser = await chromium.launch();
|
|
const kontext = await browser.newContext({ viewport: { width: 1280, height: 900 } });
|
|
const seite = await kontext.newPage();
|
|
const konsole = [];
|
|
seite.on("console", (m) => { if (m.type() === "error" && !/favicon|ERR_/i.test(m.text())) konsole.push(m.text()); });
|
|
seite.on("pageerror", (e) => konsole.push("Absturz: " + e.message));
|
|
|
|
/* Eine frische Idee, damit es etwas zu uebernehmen gibt. */
|
|
const d = oeffne();
|
|
d.prepare(`INSERT INTO eintraege
|
|
(bereich, art, titel, text, datum, dringlichkeit, status, erstellt, erstellt_von)
|
|
VALUES ('ideen','content','BILDSCHIRM-IDEE','Zum Anklicken.',?,'mittel','offen',?,?)`)
|
|
.run(jetzt.slice(0, 10), jetzt, idDogi);
|
|
d.close();
|
|
|
|
await seite.goto(`http://127.0.0.1:${PORT}/workspace/`, { waitUntil: "domcontentloaded" });
|
|
await seite.click('.rolle[data-rolle="admin"]');
|
|
await seite.fill("#code", "CODE-DOGI-0001");
|
|
await seite.click("button[type=submit]");
|
|
await seite.waitForURL("**/start.html", { timeout: 20000 });
|
|
await seite.goto(`http://127.0.0.1:${PORT}/workspace/bereich.html?b=ideen`,
|
|
{ waitUntil: "networkidle" });
|
|
await seite.waitForTimeout(900);
|
|
|
|
const knoepfe = await seite.evaluate(() =>
|
|
[...document.querySelectorAll("button")].filter((b) => /zur Aufgabe/i.test(b.textContent)).length);
|
|
ok(knoepfe >= 1, `${knoepfe} Knopf/Knöpfe „zur Aufgabe“ auf dem Ideenbrett`);
|
|
|
|
const verweise = await seite.evaluate(() =>
|
|
document.querySelectorAll(".eintrag__aufgabe").length);
|
|
ok(verweise >= 1,
|
|
`${verweise} Einträge zeigen stattdessen den Verweis auf ihre Aufgabe`);
|
|
|
|
/* DER KNOPF MUSS WIRKEN -- gemessen an der Datenbank, nicht am Text. */
|
|
const vorher = (() => { const dd = oeffne();
|
|
const n = dd.prepare("SELECT COUNT(*) AS n FROM aufgaben").get().n; dd.close(); return n; })();
|
|
await seite.evaluate(() => {
|
|
const b = [...document.querySelectorAll("button")].find((x) => /zur Aufgabe/i.test(x.textContent));
|
|
b?.click();
|
|
});
|
|
await seite.waitForTimeout(1200);
|
|
const nachher = (() => { const dd = oeffne();
|
|
const n = dd.prepare("SELECT COUNT(*) AS n FROM aufgaben").get().n; dd.close(); return n; })();
|
|
ok(nachher === vorher + 1, `nach dem Klick gibt es eine Aufgabe mehr (${vorher} → ${nachher})`);
|
|
|
|
ok(konsole.length === 0,
|
|
konsole.length ? "Fehler im Browser: " + konsole[0].slice(0, 90) : "keine Fehler im Browser");
|
|
await browser.close();
|
|
}
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
melde("");
|
|
melde(`${geprueft} Pruefungen, ${fehler} Fehler`);
|
|
melde(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG");
|
|
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Aufraeumrest */ }
|
|
process.exit(fehler ? 1 : 0);
|