Filipe: "mach das bitte viel krasser und viel geiler. die aufgaben die
wir verteilen sollen da auch zu sehen sein und so. viel
uebersichtlicher, viel moderner. nicht so banal und einfach."
VORHER: Name, Rolle, ein flacher Balken und drei Zahlenkaesten. Auf
seinem Bildschirmfoto standen in fuenf von sechs Kacheln exakt
dieselben drei Zahlen (0 / 68 / 0) -- achtzehn Kaesten fuer eine
einzige Auskunft. Und die Aufgaben, die er eine Handbreit darunter
verteilt, kamen auf der Kachel derselben Person gar nicht vor.
JETZT drei Zonen in der Reihenfolge, in der man fragt:
KOPF Ring mit dem Anteil in der Mitte, Name, Rolle.
AUFTRAG Wie viele offen, wie viele ueberfaellig, und die naechste
mit Namen und Frist ("seit gestern ueberfaellig").
FUSS "46 von 68 angesehen" -- und "verschieden gesehen" nur
dann, wenn es dort etwas gibt.
Der Ring rechnet mit stroke-dasharray auf einem Kreis vom Umfang 100:
der Anteil in Prozent ist damit buchstaeblich die Strichlaenge. Keine
Ampelfarbe -- was "genug" ist, haengt davon ab, wie lange jemand dabei
ist. Gewarnt wird nur an einem Massstab, der nicht geraten ist: einer
ueberschrittenen Frist.
Die Aufgabenzeile wird aus derselben Liste gerechnet wie das
Verteil-Band und der Katalog (alleAufgabenV) und in aufgabenHolen()
nachgezogen -- an EINER Stelle, damit es keine gibt, die es vergisst.
GITTER STATT FLIESSREIHE: Bei sieben Leuten stand die letzte Kachel
allein in ihrer Zeile und wuchs auf 1160 px neben 379 px der anderen.
Eine Person sah dreimal so wichtig aus, weil die Teamgroesse ungerade
ist.
Drei Funde nebenbei, alle durch die neuen Messungen:
- pruef-schritt verlangte seit dem 23.09. einen Sprung, der an dem
Tag ABSICHTLICH entfernt wurde. Sie war seither rot, ohne dass
etwas kaputt war. Neu gefasst auf den Sprung, den es noch gibt:
den Weg von aussen ueber "?zeigen=person-...".
- Und der war kaputt. Gesprungen wurde, waehrend in der Karte nur
"wird geladen ..." stand -- die Seite war zu kurz zum Scrollen,
der Browser klemmte bei 0 ab. Wer der Talentseite folgte, landete
oben auf der Liste. Gesprungen wird jetzt, wenn die Karte steht.
- Auf demselben Weg rief karte() das Vorlagenbrett, bevor es
eingerichtet war ("zeigen() vor einrichten()"). Das Einrichten ist
vorgezogen. Ueber "?zeigen=" ging vorher KEINE Pruefung.
Gemessen: pruef-entwicklung-kacheln 31 (vorher 16), pruef-schritt 68
(vorher 65), pruef-entwicklung 48, pruef-modi-katalog 133,
pruef-bewerbung-aufgaben 101, pruef-zuteilung, pruef-css-klassen --
alle gruen. Schriftgroessen unter 11,5 px: 40 statt 42.
Neu: server/mess-entwicklung-kacheln.mjs. Die Pruefung braucht einen
kargen Bestand und zeigt deshalb den Sonderfall (alles auf null); diese
Datei legt sieben Leute mit verschiedenen Staenden und Aufgaben an und
macht Bilder fuer 1280 und 390 px. Sie prueft nichts.
Co-Authored-By: Claude Opus 5 <[email protected]>
638 lines
31 KiB
JavaScript
638 lines
31 KiB
JavaScript
/* =====================================================================
|
|
Aus einer Beobachtung wird ein Schritt (15.09.2026)
|
|
|
|
Filipe: „ich will dass du die 2 kategorien perfektionnierst […] es
|
|
soll so einfach wie möglich sein für jeden."
|
|
|
|
---------------------------------------------------------------------
|
|
WAS GEFEHLT HAT
|
|
|
|
Die Entwicklungskarte konnte genau eines: festhalten, dass etwas
|
|
hakt — mit Anlass, das war schon richtig. Danach passierte nichts.
|
|
Beim nächsten Öffnen stand dieselbe Beobachtung da, nur älter.
|
|
|
|
Ein Entwicklungsplan wirkt dann, wenn er an einer ECHTEN Aufgabe
|
|
hängt. Also wird aus „da hakt es" eine Aufgabe auf dem Brett — und
|
|
wenn sie erledigt ist, fragt die Karte dieselbe Frage noch einmal.
|
|
|
|
---------------------------------------------------------------------
|
|
UND GENAU DA SIND DIE RIEGEL
|
|
|
|
Aus „läuft" einen Schritt machen? (darf nicht)
|
|
Aus der Beobachtung eines anderen? (darf nicht)
|
|
Zweimal zum selben Punkt? (darf nicht)
|
|
Ein Modi für sich selbst? (darf nicht)
|
|
Steht der Anlass hinterher im Protokoll? (darf nicht)
|
|
|
|
Aufrufen mit: node server/pruef-schritt.mjs
|
|
===================================================================== */
|
|
|
|
import { mkdtempSync, rmSync, readFileSync } 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-schritt");
|
|
|
|
const ordner = mkdtempSync(join(tmpdir(), "ws-schritt-"));
|
|
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(240_000, "pruef-schritt");
|
|
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 kat = await import("./workspace-entwicklungs-punkte.js");
|
|
const ent = await import("./workspace-entwicklung.js");
|
|
|
|
const oeffne = () => new DatabaseSync(process.env.WORKSPACE_DB);
|
|
const jetzt = new Date().toISOString();
|
|
|
|
let idDogi, idHand, idFrida;
|
|
{
|
|
const d = oeffne();
|
|
const s = randomBytes(32).toString("hex");
|
|
d.prepare("INSERT INTO einstellungen (schluessel, wert, geaendert, von)"
|
|
+ " VALUES (?,?,?,NULL) ON CONFLICT(schluessel) DO NOTHING")
|
|
.run("code_kennung_schluessel", s, 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", s).update(code).digest("hex"), jetzt);
|
|
return d.prepare("SELECT last_insert_rowid() AS id").get().id;
|
|
};
|
|
idDogi = anlegen("Filipe", "admin", "CODE-DOGI-0001");
|
|
idHand = anlegen("Rieke", "hand", "CODE-HAND-0001");
|
|
idFrida = anlegen("Frida", "modi", "CODE-MODI-0001");
|
|
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, code });
|
|
return [].concat(r.kopf?.["set-cookie"] || []).map((c) => c.split(";")[0]).join("; ");
|
|
}
|
|
const kDogi = await anmelden("admin", "CODE-DOGI-0001", HAUS);
|
|
const kHand = await anmelden("hand", "CODE-HAND-0001", CREW);
|
|
const kFrida = await anmelden("modi", "CODE-MODI-0001", CREW);
|
|
ok(!!kDogi && !!kHand && !!kFrida, "DogFather, die rechte Hand und ein Modi sind angemeldet");
|
|
|
|
/* Zwei Punkte, die andere setzen dürfen. */
|
|
const P1 = kat.ENTWICKLUNG_FREMD[0].schluessel;
|
|
const P2 = kat.ENTWICKLUNG_FREMD[1].schluessel;
|
|
const SELBST = kat.ENTWICKLUNG_SELBST[0].schluessel;
|
|
|
|
/* DIE ADRESSE GEHÖRT ZUM KEKS.
|
|
|
|
Ein Zugang ist an das Haus gebunden, über das er angemeldet wurde --
|
|
die rechte Hand arbeitet auf crew., DogFather auf workspace. Mein
|
|
erster Entwurf schickte beide über dieselbe Adresse; die rechte Hand
|
|
bekam daraufhin 401, und WEIL sie nichts setzen konnte, ging ein
|
|
zweiter Test ("aus der Beobachtung eines anderen wird kein Schritt")
|
|
aus dem falschen Grund durch. Ein grüner Haken über einer Leere. */
|
|
const wo = (keks) => (keks === kDogi ? HAUS : CREW);
|
|
const setzen = (keks, punkt, stufe, anlass) =>
|
|
ruf(`/workspace/api/entwicklung/person/${idFrida}/${punkt}`, wo(keks),
|
|
"PUT", { cookie: keks }, { stufe, anlass });
|
|
const schritt = (keks, punkt, person = idFrida) =>
|
|
ruf(`/workspace/api/entwicklung/person/${person}/${punkt}/schritt`, wo(keks),
|
|
"POST", { cookie: keks }, {});
|
|
const karte = async (keks = kDogi) => json(await ruf(
|
|
`/workspace/api/entwicklung/person/${idFrida}`, wo(keks), "GET", { cookie: keks }));
|
|
const punktVon = (k, s) => k.bloecke?.flatMap((b) => b.punkte).find((p) => p.schluessel === s);
|
|
|
|
/* =======================================================================
|
|
1. EIN SCHRITT ENTSTEHT NUR AUS EINEM „DA HAKT ES"
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 1. Nur aus „Da hakt es“ ===");
|
|
{
|
|
const l = await setzen(kDogi, P1, "laeuft");
|
|
ok(l.code === 200, `„Läuft“ ist gesetzt (${l.code})`);
|
|
const ohne = await schritt(kDogi, P1);
|
|
ok(ohne.code === 400 && json(ohne).fehler === "kein_hakt",
|
|
`daraus wird kein Schritt (${ohne.code}, ${json(ohne).fehler})`);
|
|
|
|
/* Gar nichts gesetzt: auch nicht. */
|
|
const nichts = await schritt(kDogi, P2);
|
|
ok(nichts.code === 400 && json(nichts).fehler === "kein_hakt",
|
|
"und aus gar nichts erst recht nicht");
|
|
|
|
const h = await setzen(kDogi, P1, "hakt", "Am Dienstag zweimal hintereinander gesehen.");
|
|
ok(h.code === 200, `mit „Da hakt es“ und Anlass geht es (${h.code})`);
|
|
const ja = await schritt(kDogi, P1);
|
|
ok(ja.code === 201, `und daraus wird ein Schritt (${ja.code})`);
|
|
ok(json(ja).titel === kat.ENTWICKLUNG_FREMD[0].titel,
|
|
`er heißt wie der Punkt: „${json(ja).titel}“`);
|
|
ok(/^\d{4}-\d{2}-\d{2}$/.test(json(ja).frist || ""),
|
|
`mit einer Frist in ${ent.SCHRITT_TAGE} Tagen (${json(ja).frist})`);
|
|
}
|
|
|
|
/* =======================================================================
|
|
2. ES IST EINE ECHTE AUFGABE — keine Notiz
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 2. Eine echte Aufgabe ===");
|
|
{
|
|
const d = oeffne();
|
|
const a = d.prepare("SELECT * FROM aufgaben WHERE aus_punkt = ?").get(P1);
|
|
d.close();
|
|
ok(!!a, "sie steht in der Aufgabentabelle, nicht in einer eigenen Liste");
|
|
ok(a.verantwortlich_id === idFrida, "Frida ist verantwortlich, nicht DogFather");
|
|
ok(a.erstellt_von === idDogi, "und DogFather hat sie angelegt");
|
|
ok(a.status === "offen", `sie ist offen (${a.status})`);
|
|
/* DER ANLASS WANDERT MIT -- ohne ihn stuende auf dem Brett ein Satz
|
|
ohne Grund, und die Aufgabe waere ein Vorwurf. */
|
|
ok(/Am Dienstag zweimal/.test(a.beschreibung || ""),
|
|
"der Anlass steht in der Beschreibung");
|
|
|
|
/* UND SIE STEHT WIRKLICH AUF DEM BRETT, nicht nur in der Tabelle. */
|
|
const brett = json(await ruf("/workspace/api/aufgaben", HAUS, "GET", { cookie: kDogi }));
|
|
const drauf = (brett.aufgaben || []).find((x) => x.id === a.id);
|
|
ok(!!drauf, "und sie taucht auf dem Aufgabenbrett auf");
|
|
|
|
/* AUCH FRIDA SIEHT SIE -- eine Aufgabe, die nur die Leitung sieht,
|
|
waere wieder eine Notiz. */
|
|
const ihres = json(await ruf("/workspace/api/aufgaben", CREW, "GET", { cookie: kFrida }));
|
|
ok((ihres.aufgaben || []).some((x) => x.id === a.id),
|
|
"Frida sieht sie ebenfalls — sonst wäre sie keine Aufgabe");
|
|
}
|
|
|
|
/* =======================================================================
|
|
3. DIE KARTE ZEIGT IHN
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 3. Die Karte zeigt ihn ===");
|
|
{
|
|
const k = await karte();
|
|
const p = punktVon(k, P1);
|
|
ok(!!p?.schritt, "an dem Punkt hängt der Schritt");
|
|
ok(p.schritt.status === "offen", `er läuft (${p.schritt.status})`);
|
|
ok(punktVon(k, P2)?.schritt === null, "an einem Punkt ohne Schritt hängt nichts");
|
|
|
|
/* ZWEIMAL ZUM SELBEN PUNKT WAERE ZWEIMAL DIESELBE ERINNERUNG -- und
|
|
die zweite macht die erste wertlos. */
|
|
const nochmal = await schritt(kDogi, P1);
|
|
ok(nochmal.code === 400 && json(nochmal).fehler === "schon_da",
|
|
`ein zweiter Schritt zum selben Punkt: abgelehnt (${json(nochmal).fehler})`);
|
|
ok(json(nochmal).aufgabe_id === p.schritt.id,
|
|
"und die Absage nennt den, der schon läuft");
|
|
}
|
|
|
|
/* =======================================================================
|
|
4. WER DARF — UND WER NICHT
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 4. Wer darf ===");
|
|
{
|
|
/* DIE RECHTE HAND FÜHRT DAS TEAM MIT -- sie darf. */
|
|
await setzen(kHand, P2, "hakt", "Ist mir im Stream am Freitag aufgefallen.");
|
|
const hand = await schritt(kHand, P2);
|
|
ok(hand.code === 201, `die rechte Hand darf auch (${hand.code})`);
|
|
|
|
/* EIN MODI NICHT -- nicht für sich und nicht für andere. */
|
|
const modi = await ruf(`/workspace/api/entwicklung/person/${idFrida}/${P1}/schritt`,
|
|
CREW, "POST", { cookie: kFrida }, {});
|
|
ok(json(modi).fehler === "nicht_gefunden", "mit der Auskunft „gibt es nicht“");
|
|
ok(modi.code === 404, `ein Modi bekommt 404 — nicht „verboten“ (${modi.code})`);
|
|
|
|
/* AUS DER BEOBACHTUNG EINES ANDEREN wird kein Schritt: DogFather hat
|
|
bei P2 nichts gesetzt, die rechte Hand schon. */
|
|
const fremd = await schritt(kDogi, P2);
|
|
ok(fremd.code === 400 && json(fremd).fehler === "kein_hakt",
|
|
"und aus der Beobachtung eines anderen auch nicht");
|
|
|
|
/* EIN PUNKT AUS BLOCK 5 SIEHT VON AUSSEN AUS WIE EIN ERFUNDENER. */
|
|
const geheim = await schritt(kDogi, SELBST);
|
|
ok(geheim.code === 404, `ein Punkt aus „Wie geht's dir?“ gibt 404 (${geheim.code})`);
|
|
const quatsch = await schritt(kDogi, "e-gibt-es-nicht");
|
|
ok(quatsch.code === 404, "ein erfundener Punkt ebenfalls");
|
|
}
|
|
|
|
/* =======================================================================
|
|
5. IM PROTOKOLL STEHT, DASS — NICHT WORAN
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 5. Das Protokoll ===");
|
|
{
|
|
const d = oeffne();
|
|
const zeilen = d.prepare(
|
|
"SELECT detail FROM protokoll WHERE aktion = 'entwicklung_schritt'").all();
|
|
d.close();
|
|
ok(zeilen.length === 2, `${zeilen.length} Schritte stehen im Protokoll`);
|
|
ok(zeilen.every((z) => /Frida/.test(z.detail || "")),
|
|
"mit Punkt und Name — das ist der Vorgang");
|
|
/* WORAN JEMAND ETWAS GEMERKT HAT, GEHÖRT NICHT INS PROTOKOLL. Es
|
|
liest sich anders, wenn es zwischen Anmeldungen steht. */
|
|
ok(!zeilen.some((z) => /Dienstag|Freitag/.test(z.detail || "")),
|
|
"aber ohne den Anlass");
|
|
}
|
|
|
|
/* =======================================================================
|
|
6. UND DANN SCHLIESST SICH DER KREIS
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 6. Der Kreis ===");
|
|
{
|
|
const d = oeffne();
|
|
const a = d.prepare("SELECT id FROM aufgaben WHERE aus_punkt = ?").get(P1);
|
|
d.close();
|
|
const fertig = await ruf(`/workspace/api/aufgaben/${a.id}`, HAUS, "PATCH",
|
|
{ cookie: kDogi }, { status: "erledigt" });
|
|
ok(fertig.code === 200, `die Aufgabe wird erledigt (${fertig.code})`);
|
|
|
|
const k = await karte();
|
|
const p = punktVon(k, P1);
|
|
ok(p.schritt?.status === "erledigt", `die Karte sieht das (${p.schritt?.status})`);
|
|
ok(!!p.schritt?.erledigt_am, "mit Datum");
|
|
|
|
/* UND DANACH GEHT ES WIEDER -- der Kreis ist ein Kreis, keine
|
|
Einbahnstrasse. Waere das gesperrt, koennte man denselben Punkt
|
|
nie wieder angehen. */
|
|
const zweite = await schritt(kDogi, P1);
|
|
ok(zweite.code === 201, `zum selben Punkt geht danach ein neuer Schritt (${zweite.code})`);
|
|
const k2 = await karte();
|
|
ok(punktVon(k2, P1).schritt?.id === json(zweite).id,
|
|
"und die Karte zeigt den NEUEN, nicht den alten");
|
|
}
|
|
|
|
/* =======================================================================
|
|
7. DER VERWEIS AUFS BRETT FÜHRT WIRKLICH HIN
|
|
======================================================================= */
|
|
melde("");
|
|
melde("=== 7. Der Verweis ===");
|
|
{
|
|
/* `aufgaben.html#a12` stand an drei Stellen im Haus und wurde von
|
|
keiner gelesen -- das Brett kannte nur `?zeigen=`. Wer draufdrueckte,
|
|
landete auf dem Brett und suchte von Hand. */
|
|
const brett = readFileSync(new URL("../workspace/assets/js/aufgaben.js", import.meta.url), "utf8");
|
|
ok(/data-id=/.test(brett), "die Karten auf dem Brett tragen ihre Nummer");
|
|
ok(/\^#a/.test(brett), "und das Brett liest `#a<nummer>` aus der Adresse");
|
|
|
|
const e = readFileSync(new URL("../workspace/assets/js/entwicklung.js", import.meta.url), "utf8");
|
|
ok(/aufgaben\.html#a/.test(e), "die Entwicklungskarte verweist in dieser Schreibweise");
|
|
const b = readFileSync(new URL("../workspace/assets/js/bereich.js", import.meta.url), "utf8");
|
|
const r = readFileSync(new URL("../workspace/assets/js/report.js", import.meta.url), "utf8");
|
|
ok(/aufgaben\.html#a/.test(b) && /aufgaben\.html#a/.test(r),
|
|
"die beiden anderen Stellen auch — sie funktionieren jetzt mit");
|
|
}
|
|
|
|
/* =======================================================================
|
|
8. IM BROWSER
|
|
======================================================================= */
|
|
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: 1100 } });
|
|
await kontext.addCookies(kDogi.split("; ").filter(Boolean).map((teil) => {
|
|
const i = teil.indexOf("=");
|
|
return { name: teil.slice(0, i), value: teil.slice(i + 1), domain: "127.0.0.1", path: "/" };
|
|
}));
|
|
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));
|
|
|
|
await seite.goto(`http://127.0.0.1:${PORT}/workspace/entwicklung.html`, { waitUntil: "networkidle" });
|
|
await seite.waitForTimeout(800);
|
|
/* Erst die Person öffnen -- ohne Karte gibt es keine Punkte. */
|
|
/* FRIDAS KARTE, nicht die erste in der Liste. Die steht dort nach
|
|
der Hierarchie des Hauses -- also die rechte Hand -- und die hat
|
|
keinen Schritt. Mein erster Entwurf klickte blind auf die erste
|
|
und maß danach eine leere Karte. */
|
|
await seite.click('.e-person:has-text("Frida")');
|
|
await seite.waitForTimeout(900);
|
|
|
|
/* ===== DIE KATEGORIEN KLAPPEN AUF UND ZU (15.09.2026) ==============
|
|
|
|
Filipe: „ich will auch dass man die kategorien von den fragen, also
|
|
die liste auf und zu drücken kann mit einem button."
|
|
|
|
Geprüft wird nicht, dass es einen Knopf GIBT -- sondern dass er
|
|
etwas tut, und dass zugeklappt trotzdem dasteht, was drin ist.
|
|
================================================================ */
|
|
const bloecke = await seite.evaluate(() => {
|
|
const b = [...document.querySelectorAll(".e-block")];
|
|
return {
|
|
anzahl: b.length,
|
|
offen: b.filter((x) => x.dataset.offen === "ja").length,
|
|
schalter: document.querySelectorAll(".e-block__schalter").length,
|
|
staende: [...document.querySelectorAll(".e-block__stand")].map((x) => x.textContent.trim()),
|
|
hakt: [...document.querySelectorAll(".e-block__hakt")]
|
|
.filter((x) => !x.hidden).map((x) => x.textContent.trim()),
|
|
versteckt: b.filter((x) => x.querySelector(".e-block__koerper")?.hidden).length,
|
|
alleKnopf: document.querySelector(".e-bloecke__alle")?.textContent.trim() || "",
|
|
};
|
|
});
|
|
/* DIE ZAHL KOMMT AUS DEM KATALOG, nicht aus dieser Datei.
|
|
|
|
Hier stand `=== 4`. Am 17.09.2026 kamen zwei Bloecke dazu ("Clips,
|
|
Schnitt und Kommentare" und "Waehrend der Stream laeuft"), und die
|
|
Zeile war ab da rot, ohne dass irgendetwas kaputt war -- eine feste
|
|
Zahl ist eine Rechnung von gestern. Was die Zeile eigentlich
|
|
behauptet, ist: Die Karte zeigt ALLE Bloecke. */
|
|
const bloeckeErwartet = (await import("./workspace-entwicklungs-punkte.js"))
|
|
.ENTWICKLUNG_BLOECKE.filter((b) => !b.nurSelbst).length;
|
|
ok(bloecke.anzahl === bloeckeErwartet,
|
|
`${bloecke.anzahl} von ${bloeckeErwartet} Kategorien stehen auf der Karte`);
|
|
ok(bloecke.schalter === bloecke.anzahl, "jede hat einen Knopf zum Auf- und Zuklappen");
|
|
/* GENAU EINE STEHT OFFEN -- die erste, in der noch etwas fehlt. Alle
|
|
zu hieße jedes Mal suchen, alle auf wäre der Zustand von vorher. */
|
|
ok(bloecke.offen === 1, `genau eine steht offen (${bloecke.offen})`);
|
|
ok(bloecke.versteckt === bloecke.anzahl - 1,
|
|
`die anderen ${bloecke.versteckt} sind wirklich zugeklappt`);
|
|
/* ZUGEKLAPPT MUSS SIE TROTZDEM ETWAS SAGEN. */
|
|
ok(bloecke.staende.length === bloecke.anzahl
|
|
&& bloecke.staende.every((x) => x.includes(' / ')),
|
|
`jeder Kopf sagt, wie weit er ist (${bloecke.staende.join(", ")})`);
|
|
/* SICHTBAR, nicht bloß vorhanden. Seit dem 15.09. steht der Hinweis
|
|
immer im Baum (damit er nachgezogen werden kann, ohne den Kopf neu
|
|
zu bauen) -- „es gibt ihn“ wäre damit über vier versteckten grün. */
|
|
ok(bloecke.hakt.length === 1 && /hakt/.test(bloecke.hakt[0]),
|
|
`und NUR wo etwas hakt, steht es im Kopf (${bloecke.hakt.join(", ") || "nirgends"})`);
|
|
ok(/Alle aufklappen/.test(bloecke.alleKnopf),
|
|
`daneben einer für alle auf einmal („${bloecke.alleKnopf}“)`);
|
|
|
|
/* UND JETZT WIRKLICH DRÜCKEN. Ein Knopf, den niemand betätigt hat,
|
|
ist kein geprüfter Knopf. */
|
|
await seite.click(".e-block:not([data-offen='ja']) .e-block__schalter");
|
|
await seite.waitForTimeout(250);
|
|
const nachKlick = await seite.evaluate(() => ({
|
|
offen: document.querySelectorAll('.e-block[data-offen="ja"]').length,
|
|
ausgeklappt: [...document.querySelectorAll(".e-block__schalter")]
|
|
.filter((x) => x.getAttribute("aria-expanded") === "true").length,
|
|
}));
|
|
ok(nachKlick.offen === 2, `nach einem Klick stehen zwei offen (${nachKlick.offen})`);
|
|
ok(nachKlick.ausgeklappt === 2,
|
|
"und ein Vorleseprogramm erfährt dasselbe (aria-expanded)");
|
|
|
|
await seite.click(".e-bloecke__alle");
|
|
await seite.waitForTimeout(400);
|
|
const alle = await seite.evaluate(() => ({
|
|
offen: document.querySelectorAll('.e-block[data-offen="ja"]').length,
|
|
knopf: document.querySelector(".e-bloecke__alle")?.textContent.trim() || "",
|
|
}));
|
|
ok(alle.offen === bloeckeErwartet,
|
|
`„Alle aufklappen“ öffnet wirklich alle (${alle.offen} von ${bloeckeErwartet})`);
|
|
ok(/Alle zuklappen/.test(alle.knopf),
|
|
`und der Knopf heißt danach anders („${alle.knopf}“)`);
|
|
|
|
const stand = await seite.evaluate(() => ({
|
|
punkte: document.querySelectorAll(".e-punkt").length,
|
|
laeuft: document.querySelectorAll('.e-schritt[data-lage="laeuft"]').length,
|
|
knoepfe: document.querySelectorAll(".e-schritt__knopf").length,
|
|
leere: document.querySelectorAll(".e-schritt:not([data-lage])").length,
|
|
verweis: document.querySelector(".e-schritt__hin")?.getAttribute("href") || "",
|
|
}));
|
|
ok(stand.punkte > 20, `${stand.punkte} Punkte stehen auf der Karte`);
|
|
ok(stand.laeuft >= 1, `${stand.laeuft} Schritt läuft sichtbar`);
|
|
ok(/aufgaben\.html#a\d+/.test(stand.verweis),
|
|
`und verweist auf die Aufgabe (${stand.verweis})`);
|
|
/* KEIN KNOPF, WO ER NICHTS TUN KANN. Bei über zwanzig Punkten wären
|
|
zwanzig Knöpfe, die fast alle „geht nicht" antworten, schlimmer
|
|
als keiner. */
|
|
ok(stand.knoepfe === 0,
|
|
"an den übrigen Punkten steht kein Knopf — dort hakt nichts");
|
|
ok(stand.leere > 20, `und dort steht auch kein leerer Kasten (${stand.leere})`);
|
|
|
|
/* DER SPRUNG: die Adresse mit der Nummer, und das Brett zeigt genau
|
|
die eine Aufgabe. */
|
|
const d = oeffne();
|
|
const a = d.prepare("SELECT id FROM aufgaben WHERE aus_punkt IS NOT NULL ORDER BY id DESC").get();
|
|
d.close();
|
|
await seite.goto(`http://127.0.0.1:${PORT}/workspace/aufgaben.html#a${a.id}`,
|
|
{ waitUntil: "networkidle" });
|
|
await seite.waitForTimeout(1100);
|
|
const sprung = await seite.evaluate(() => ({
|
|
leiste: document.querySelector(".ziel-leiste__text")?.textContent || "",
|
|
marke: document.querySelector(".ziel-leiste__marke")?.textContent || "",
|
|
}));
|
|
ok(/Diese eine Aufgabe/.test(sprung.leiste),
|
|
`das Brett sagt, was es zeigt: „${sprung.leiste}“`);
|
|
ok(sprung.marke === "1", `und es ist genau eine (${sprung.marke})`);
|
|
|
|
/* =====================================================================
|
|
ZWEI FEHLER, DIE FILIPE GEMELDET HAT (15.09.2026)
|
|
|
|
Ein Fehler, der nur behoben ist, ist beim naechsten Umbau wieder da.
|
|
Beide werden deshalb nachgestellt, nicht beschrieben.
|
|
===================================================================== */
|
|
|
|
/* ZURÜCK AUF DIE ENTWICKLUNGSSEITE. Der Abschnitt davor ist zum
|
|
Aufgabenbrett gesprungen -- mein erster Entwurf maß danach auf dem
|
|
Brett weiter und fand dort weder Punkte noch eine Karte. Gemeldet
|
|
hat das die Prüfung selbst: „Kachel und Karte nennen dieselbe Zahl
|
|
(„“ / „“)" -- zwei leere Texte sind gleich und beweisen nichts.
|
|
Genau dafür steht die Zahl in der Bedingung daneben. */
|
|
await seite.goto(`http://127.0.0.1:${PORT}/workspace/entwicklung.html`,
|
|
{ waitUntil: "networkidle" });
|
|
await seite.waitForTimeout(800);
|
|
await seite.click('.e-person:has-text("Frida")');
|
|
await seite.waitForTimeout(1000);
|
|
await seite.click(".e-bloecke__alle");
|
|
await seite.waitForTimeout(400);
|
|
|
|
/* (1) und (2) IN EINEM DURCHGANG -- sie gehören zusammen.
|
|
|
|
Filipe hat beides am selben Klick gemerkt: Die Seite sprang hoch,
|
|
UND die Zahl auf der Kachel blieb stehen. Also wird auch beides am
|
|
selben Klick gemessen: Vorher die Zahl und die Höhe merken, EINMAL
|
|
drücken, beides nachsehen. */
|
|
await seite.evaluate(() => window.scrollTo({ top: 600, behavior: "instant" }));
|
|
await seite.waitForTimeout(250);
|
|
const vorDemKlick = await seite.evaluate(() => {
|
|
const name = document.querySelector("#karte .e-karte__name")?.textContent.trim() || "";
|
|
const kachel = [...document.querySelectorAll(".e-person")]
|
|
.find((x) => x.querySelector(".e-person__name")?.textContent.trim() === name);
|
|
return {
|
|
name,
|
|
y: Math.round(window.scrollY),
|
|
/* WO DIE ZAHL AUF DER KACHEL STEHT -- zum dritten Mal woanders.
|
|
|
|
Bis 17.09.2026 ein Satz ("2 von 68 angesehen"), danach ein
|
|
Bilanz-Kasten, seit dem 24.09.2026 die Ziffer vor "von 68
|
|
angesehen" plus der Ring darueber.
|
|
|
|
Das ist kein Zufall und kein Pech: Wer eine Kachel umbaut,
|
|
verschiebt die Zahl. Die Pruefung muss es merken -- und sie
|
|
merkt es nur, weil sie die Zahl VERGLEICHT statt sie bloss zu
|
|
lesen. Beim Umbau am 17.09. las genau dieser Selektor
|
|
monatelang einen leeren String, ohne dass etwas kaputt war.
|
|
|
|
DER RING WIRD MITGELESEN. Er ist seit dem 24.09. die groessere
|
|
der beiden Darstellungen; bewegte sich nur die Ziffer, stuende
|
|
ein Ring bei 3 % ueber einer 46 -- und das faellt niemandem
|
|
auf, weil man das eine sieht und das andere liest. */
|
|
kachel: kachel?.querySelector(".e-person__stand-zahl")?.textContent.trim() || "",
|
|
bogen: kachel?.querySelector(".e-ring__bogen")
|
|
?.getAttribute("stroke-dasharray") || "",
|
|
kopfZahl: (document.querySelector("#karte .e-karte__stand")?.textContent
|
|
.match(/(\d+) von/) || [])[1] || "",
|
|
kopf: document.querySelector("#karte .e-karte__stand")?.textContent.trim() || "",
|
|
};
|
|
});
|
|
ok(vorDemKlick.name === "Frida", `Fridas Karte ist offen (${vorDemKlick.name})`);
|
|
/* Die Kachel nennt jetzt eine nackte Ziffer, keinen Satz. */
|
|
const zahlAus = (x) => { const n = Number(x); return Number.isFinite(n) && x !== "" ? n : -1; };
|
|
ok(zahlAus(vorDemKlick.kachel) >= 0,
|
|
`vorher steht auf ihrer Kachel „${vorDemKlick.kachel}“`);
|
|
|
|
/* Ein Punkt, der noch NICHT gesetzt ist -- genau der löst den
|
|
Neuaufbau aus, und genau da sprang die Seite. */
|
|
const gedrueckt = await seite.evaluate(() => {
|
|
const p = [...document.querySelectorAll(".e-punkt")]
|
|
.find((x) => !x.dataset.stufe && x.offsetParent);
|
|
if (!p) return false;
|
|
p.querySelector('.e-wahl__knopf[data-stufe="laeuft"]')?.click();
|
|
return true;
|
|
});
|
|
ok(gedrueckt, "ein noch nicht gesetzter Punkt wird angeklickt");
|
|
await seite.waitForTimeout(1400);
|
|
|
|
const nachDemKlick = await seite.evaluate(() => {
|
|
const name = document.querySelector("#karte .e-karte__name")?.textContent.trim() || "";
|
|
const kachel = [...document.querySelectorAll(".e-person")]
|
|
.find((x) => x.querySelector(".e-person__name")?.textContent.trim() === name);
|
|
return {
|
|
y: Math.round(window.scrollY),
|
|
/* WO DIE ZAHL AUF DER KACHEL STEHT -- zum dritten Mal woanders.
|
|
|
|
Bis 17.09.2026 ein Satz ("2 von 68 angesehen"), danach ein
|
|
Bilanz-Kasten, seit dem 24.09.2026 die Ziffer vor "von 68
|
|
angesehen" plus der Ring darueber.
|
|
|
|
Das ist kein Zufall und kein Pech: Wer eine Kachel umbaut,
|
|
verschiebt die Zahl. Die Pruefung muss es merken -- und sie
|
|
merkt es nur, weil sie die Zahl VERGLEICHT statt sie bloss zu
|
|
lesen. Beim Umbau am 17.09. las genau dieser Selektor
|
|
monatelang einen leeren String, ohne dass etwas kaputt war.
|
|
|
|
DER RING WIRD MITGELESEN. Er ist seit dem 24.09. die groessere
|
|
der beiden Darstellungen; bewegte sich nur die Ziffer, stuende
|
|
ein Ring bei 3 % ueber einer 46 -- und das faellt niemandem
|
|
auf, weil man das eine sieht und das andere liest. */
|
|
kachel: kachel?.querySelector(".e-person__stand-zahl")?.textContent.trim() || "",
|
|
bogen: kachel?.querySelector(".e-ring__bogen")
|
|
?.getAttribute("stroke-dasharray") || "",
|
|
kopfZahl: (document.querySelector("#karte .e-karte__stand")?.textContent
|
|
.match(/(\d+) von/) || [])[1] || "",
|
|
kopf: document.querySelector("#karte .e-karte__stand")?.textContent.trim() || "",
|
|
};
|
|
});
|
|
|
|
/* (1) „immer wenn ich auf was drücke dan geht das fenster wieder hoch." */
|
|
ok(Math.abs(nachDemKlick.y - vorDemKlick.y) <= 4,
|
|
`die Seite bleibt stehen, wo sie stand (${vorDemKlick.y} -> ${nachDemKlick.y})`);
|
|
|
|
/* (2) „wieso steht bei vanvan 0 von 28 obwohl ich alle durch habe." */
|
|
ok(zahlAus(nachDemKlick.kachel) === zahlAus(vorDemKlick.kachel) + 1,
|
|
`die Kachel zählt sofort mit (${vorDemKlick.kachel} -> ${nachDemKlick.kachel})`);
|
|
ok(nachDemKlick.kachel === nachDemKlick.kopfZahl,
|
|
`und nennt dieselbe Zahl wie die Karte (${nachDemKlick.kachel} / ${nachDemKlick.kopfZahl})`);
|
|
/* UND DER RING WANDERT MIT. Seine Laenge ist der Anteil in Prozent
|
|
(Umfang 100) -- er muss also groesser werden, wenn die Ziffer es
|
|
wird. Ein Ring, der stehenbleibt, waere dieselbe zweite Wahrheit
|
|
wie die Zahl von vorhin, nur als Bild statt als Text. */
|
|
const bogenAus = (x) => Number(String(x).split(" ")[0]);
|
|
ok(bogenAus(nachDemKlick.bogen) > bogenAus(vorDemKlick.bogen),
|
|
`und der Ring wird voller (${vorDemKlick.bogen} -> ${nachDemKlick.bogen})`);
|
|
ok(zahlAus(nachDemKlick.kachel) > 0,
|
|
"es ist eine echte Zahl, keine Null — sonst wäre die Gleichheit wertlos");
|
|
|
|
/* =====================================================================
|
|
GEGENPROBE ZUM SPRUNG -- neu gefasst am 24.09.2026
|
|
=====================================================================
|
|
|
|
Sie muss es geben: Ohne sie hiesse "die Seite bleibt stehen"
|
|
vielleicht nur, dass ueberhaupt nichts mehr scrollt, und die
|
|
Pruefung darueber waere wertlos.
|
|
|
|
HIER STAND BIS HEUTE DER FALSCHE FALL. Sie klickte eine Kachel an
|
|
und verlangte einen Sprung zur Karte -- richtig bis zum
|
|
23.09.2026. An dem Tag wurde die Seite nach dem Arbeitsablauf
|
|
umgeraeumt: Zwischen Kachel und Karte liegen seither das
|
|
Verteil-Formular, der Katalog und die Aufgabenliste. Ein Sprung
|
|
zur Karte wuerde also ueber genau die drei Dinge hinwegspringen,
|
|
die man nach der Wahl zuerst braucht. Der Sprung wurde deshalb
|
|
ABSICHTLICH entfernt (siehe entwicklung.js, "KEIN SPRUNG MEHR").
|
|
|
|
Die Gegenprobe blieb stehen und war ab da rot -- nicht, weil
|
|
etwas kaputt war, sondern weil sie eine Zusage einforderte, die
|
|
zurueckgenommen worden ist. Gefunden beim Kachelumbau am
|
|
24.09.2026, also einen Tag spaeter; waere sie gruen gewesen,
|
|
haette es niemand bemerkt.
|
|
|
|
DER SPRUNG, DEN ES NOCH GIBT, ist der von aussen: Wer der
|
|
Talentseite folgt, kommt mit "?zeigen=person-<nr>" und will genau
|
|
diese eine Karte sehen. Daran laesst sich beweisen, dass die Seite
|
|
sehr wohl springen KANN -- und zwar an der einzigen Stelle, an der
|
|
sie es noch soll. */
|
|
const werDenn = await seite.evaluate(() =>
|
|
document.querySelector("#personen .e-person")?.dataset.person || "");
|
|
ok(werDenn !== "", `eine Kachel zum Anspringen gefunden (Nr. ${werDenn})`);
|
|
await seite.goto(`http://127.0.0.1:${PORT}/workspace/entwicklung.html?zeigen=person-${werDenn}`,
|
|
{ waitUntil: "networkidle" });
|
|
await seite.waitForSelector("#personen .e-person", { timeout: 15_000 });
|
|
await seite.waitForTimeout(1600);
|
|
const vonAussen = await seite.evaluate(() => ({
|
|
y: Math.round(window.scrollY),
|
|
karteOffen: !document.getElementById("karte")?.hidden,
|
|
wer: document.querySelector('#personen .e-person[aria-pressed="true"]')
|
|
?.dataset.person || "",
|
|
}));
|
|
ok(vonAussen.y > 0,
|
|
`von aussen verlinkt springt sie sehr wohl (scrollY ${vonAussen.y})`);
|
|
ok(vonAussen.karteOffen && vonAussen.wer === werDenn,
|
|
`und zwar zu genau dieser Karte (offen: ${vonAussen.karteOffen}, `
|
|
+ `gewaehlt: ${vonAussen.wer})`);
|
|
|
|
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);
|