Beide Befunde sind heute beim Umzug des Vorlagenkatalogs aufgefallen,
gehoeren aber nicht dazu -- sie lagen schon vorher da und haetten bei
jedem Lauf mitgemeldet, bis sie niemand mehr liest.
pruef-struktur las Server-Routen nur in doppelten Anfuehrungszeichen.
Wer eine Route in einer Schleife registriert, schreibt sie aber als
Template:
for (const weg of ["annehmen", "ablehnen"])
router.post(`/workspace/api/vorlagen/bewerbung/${weg}`, ...)
Diese Routen fehlten in der Liste, und die Aufrufe dorthin galten als
"Schnittstelle gibt es nicht" -- obwohl sie laufen. Die Erfassung der
AUFRUFE kannte alle drei Zeichen laengst; nur die der ROUTEN nicht.
Zwei Regeln fuer dieselbe Frage, und eine davon war aelter. Gemessen:
319 statt 315 Routen, und der Fehlalarm ist weg. Die Gegenproben
schlagen weiter an ("erfundene Schnittstelle wird als fehlend
erkannt").
pruef-aufgaben-vorlagen zaehlte KARTEN und erwartete +1. Seit das
Brett gleiche Aufgaben zu einer Sammelkarte zusammenfasst, stimmt das
nicht mehr -- die Pruefung legt kurz davor sieben Vorlagen derselben
Stufe an, und die neue wanderte in eine bestehende Karte. Sie meldete
"9 -> 9" und behauptete damit, das Uebernehmen sei kaputt; drei Zeilen
weiter erkannte sie dieselbe Aufgabe als "schon uebernommen" wieder.
Jetzt zaehlt sie Aufgaben: "9 -> 10 Aufgaben in 9 Karten".
Dieselbe Stelle gab es zweimal. In pruef-modi-katalog ist sie gestern
repariert worden -- hier nicht, weil ich nach dem ersten Fund nicht
weitergesucht habe.
Co-Authored-By: Claude Opus 5 <[email protected]>
434 lines
23 KiB
JavaScript
434 lines
23 KiB
JavaScript
/* Prueft die Aufgaben-Vorlagen: vier Bereiche, vier Stufen, und den
|
|
Weg von der Vorlage zur echten Aufgabe.
|
|
|
|
WARUM DAS EINE EIGENE PRUEFUNG IST. Die Vorlagen sind Daten -- dass
|
|
sie vollstaendig sind, laesst sich in Sekunden zaehlen. Interessant
|
|
ist der Weg DANACH: Aus einer Vorlage muss eine Zeile in `aufgaben`
|
|
werden, mit Creator, Frist und Status, und sie muss in der
|
|
Aufgabenliste des Creators wirklich auftauchen. Zwischen "die Vorlage
|
|
gibt es" und "der Creator sieht die Aufgabe" liegen vier Schichten,
|
|
und jede kann still danebengehen.
|
|
|
|
DREI SORTEN GEGENPROBE, alle drei noetig:
|
|
- eine unbekannte Stufe MUSS abgelehnt werden,
|
|
- eine Uebernahme ohne Creator MUSS abgelehnt werden (eine Aufgabe
|
|
ohne Adressaten ist ein Zettel),
|
|
- ein Betreuer darf sie NICHT fuer einen fremden Creator anlegen. */
|
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
|
|
const ordner = mkdtempSync(join(tmpdir(), "ws-aufv-"));
|
|
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
|
|
/* DER PORT MUSS MEINER SEIN (nachgeruestet 19.09.2026).
|
|
Ohne diese Zeile startet der eigene Server bei belegtem Port
|
|
STILL nicht -- und alles Folgende misst gegen einen fremden
|
|
Stand. Das ist der dritte Ausgang: belegt ist etwas anderes
|
|
als kaputt. */
|
|
const PORT = await eigenerPort(import.meta, "pruef-aufgaben-vorlagen");
|
|
process.env.PORT = `${PORT}`;
|
|
process.env.SITE_ACCESS_SECRET = "lokaler-test";
|
|
process.env.SITE_ACCESS_CODE_DOGI = "DOGI-TEST-1";
|
|
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";
|
|
import { eigenerPort } from "./helfer-port.mjs";
|
|
await import("./index.js");
|
|
notbremse(240_000, "pruef-aufgaben-vorlagen");
|
|
await new Promise((r) => setTimeout(r, 700));
|
|
const BASIS = `http://127.0.0.1:${PORT}`;
|
|
|
|
let fehler = 0;
|
|
const ok = (b, t) => { console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
|
|
|
|
const { DatabaseSync } = await import("node:sqlite");
|
|
const { scryptSync, randomBytes } = await import("node:crypto");
|
|
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 idScout = anlegen("Patrick", "scout", "CODE-SCOU-0001");
|
|
const idTili = anlegen("Tili", "creator", "CODE-ANNA-0001");
|
|
const idFremd = anlegen("Fremd", "creator", "CODE-FREM-0001");
|
|
d.prepare("INSERT INTO betreuung (creator_id, betreuer_id, seit) VALUES (?,?,?)").run(idTili, idScout, jetzt);
|
|
d.close();
|
|
|
|
const keks = async (rolle, code) => {
|
|
const a = await fetch(BASIS + "/workspace/api/anmelden", {
|
|
method: "POST", headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ rolle, code }) });
|
|
return (a.headers.getSetCookie?.() || []).map((k) => k.split(";")[0]).join("; ");
|
|
};
|
|
const kScout = await keks("scout", "CODE-SCOU-0001");
|
|
const kTili = await keks("creator", "CODE-ANNA-0001");
|
|
const ruf = (pfad, k, o = {}) => fetch(BASIS + pfad, { ...o, headers: { Cookie: k, ...(o.headers || {}) } });
|
|
const senden = (pfad, k, art, koerper) => ruf(pfad, k, {
|
|
method: art, headers: { "Content-Type": "application/json" }, body: JSON.stringify(koerper || {}) });
|
|
|
|
/* ---------- 1. Die Vorlagen sind vollstaendig ---------------------------- */
|
|
console.log("\n=== Vier Bereiche, vier Stufen -- und Profi/Meister am besten bestueckt");
|
|
const alles = await (await ruf("/workspace/api/vorlagen", kScout)).json();
|
|
const V = alles.aufgaben;
|
|
ok(!!V, "die Aufgabenvorlagen werden ausgeliefert");
|
|
const BEREICHE = ["live", "content", "technik", "community"];
|
|
const STUFEN = ["anfaenger", "fort", "profi", "meister"];
|
|
let gesamt = 0;
|
|
for (const b of BEREICHE) {
|
|
const fehlend = STUFEN.filter((st) => !(V[b] || {})[st] || !V[b][st].length);
|
|
const n = STUFEN.reduce((s, st) => s + ((V[b] || {})[st] || []).length, 0);
|
|
gesamt += n;
|
|
ok(fehlend.length === 0 && n >= 8,
|
|
`${b}: ${n} Aufgaben über ${STUFEN.length - fehlend.length} von 4 Stufen`
|
|
+ (fehlend.length ? ` — fehlt: ${fehlend.join(", ")}` : ""));
|
|
}
|
|
/* KEINE FESTE GESAMTZAHL MEHR (09.09.2026).
|
|
|
|
Hier stand `gesamt === 48`. Das war richtig, solange es genau drei
|
|
Aufgaben je Bereich und Stufe gab -- und wurde in dem Moment falsch,
|
|
in dem Filipe mehr wollte: "ich will dass da noch mehr aufgeben und
|
|
moeglichkeiten sind." Eine Zahl, die bei jeder Verbesserung rot
|
|
wird, erzieht dazu, sie hochzusetzen statt hinzusehen.
|
|
|
|
Geprueft wird jetzt, was wirklich gemeint war: dass keine Stufe
|
|
leer ist und dass genug da ist. Und dazu die Regel, die aus seinem
|
|
Wunsch folgt. */
|
|
ok(gesamt >= 48, `zusammen ${gesamt} Aufgaben (mindestens 48)`);
|
|
|
|
/* PROFI UND MEISTER SIND NICHT DIE DUENNSTEN.
|
|
|
|
Filipe, 09.09.2026: "ich will dass die 2 kategorien profi und
|
|
meister ueberall noch mehr perfektionniert werden ... die muessen
|
|
richtig krass sein!!!!"
|
|
|
|
Gemessen war es genau andersherum: drei Aufgaben je Stufe, aber in
|
|
den Punktelisten daneben hatte Meister teils gar keinen Eintrag.
|
|
Das ist verkehrt: Anfaenger bleibt man Wochen, Meister Jahre -- der
|
|
oberste Rang braucht den groessten Vorrat, nicht den kleinsten.
|
|
|
|
Die Bedingung vergleicht mit FORTGESCHRITTEN statt mit einer festen
|
|
Zahl. So bleibt sie richtig, wenn irgendwann alle Stufen wachsen. */
|
|
{
|
|
const jeStufe = {};
|
|
for (const st of STUFEN) {
|
|
jeStufe[st] = BEREICHE.reduce((s, b) => s + ((V[b] || {})[st] || []).length, 0);
|
|
}
|
|
ok(jeStufe.profi >= jeStufe.fort && jeStufe.meister >= jeStufe.fort,
|
|
`Profi (${jeStufe.profi}) und Meister (${jeStufe.meister}) sind mindestens so gut `
|
|
+ `bestückt wie Fortgeschritten (${jeStufe.fort})`);
|
|
/* GEGENPROBE: Die Bedingung schlaegt an, wenn eine Stufe abfaellt.
|
|
Ohne sie hiesse "groesser gleich" nur, dass irgendwelche Zahlen
|
|
verglichen wurden. */
|
|
ok(!(1 >= jeStufe.fort), `und die Bedingung erkennt einen Rückfall (1 gegen ${jeStufe.fort})`);
|
|
/* Und jeder Bereich einzeln -- eine Summe kann eine Luecke decken. */
|
|
const duenn = BEREICHE.filter((b) => (V[b].profi || []).length < (V[b].fort || []).length
|
|
|| (V[b].meister || []).length < (V[b].fort || []).length);
|
|
ok(duenn.length === 0,
|
|
duenn.length ? `zu duenn bei: ${duenn.join(", ")}`
|
|
: `und das gilt in jedem der ${BEREICHE.length} Bereiche einzeln`);
|
|
}
|
|
|
|
/* Jede Aufgabe braucht Titel, Text, Frist und einen eindeutigen
|
|
Schluessel -- eine Aufgabe ohne Frist ist ein Wunsch. */
|
|
{
|
|
const flach = BEREICHE.flatMap((b) => STUFEN.flatMap((st) => (V[b][st] || [])));
|
|
const unvollstaendig = flach.filter((v) => !v.titel || !v.text || !v.tage || !v.schluessel);
|
|
ok(unvollstaendig.length === 0,
|
|
`jede Aufgabe hat Titel, Text, Frist und Schlüssel (${flach.length - unvollstaendig.length} von ${flach.length})`);
|
|
const schluessel = flach.map((v) => v.schluessel);
|
|
const doppelt = schluessel.filter((x, i) => schluessel.indexOf(x) !== i);
|
|
ok(doppelt.length === 0, `alle Schlüssel eindeutig${doppelt.length ? ": " + doppelt.join(", ") : ""}`);
|
|
/* DIE FRIST WAECHST NICHT DURCHGEHEND MIT DER STUFE -- und das ist
|
|
kein Mangel.
|
|
|
|
Der erste Anlauf verlangte anfaenger < fort < profi < meister und
|
|
schlug fehl: Profi kam auf 6,7 Tage, Fortgeschritten auf 7,4. Beim
|
|
Nachsehen war die Erwartung falsch, nicht die Zahl. Eine
|
|
Profi-Aufgabe ist meist eine MESSUNG -- "Upload messen und Bitrate
|
|
setzen", "Lautheit auf minus 20 bis minus 24 bringen": anspruchsvoll,
|
|
aber an einem Abend erledigt. Eine Fortgeschrittenen-Aufgabe wie
|
|
"Content-Saeulen anlegen und drei Wochen halten" braucht
|
|
zwangslaeufig Wochen, weil sie aus Wiederholung besteht.
|
|
|
|
Die Stufe sagt, was man KOENNEN muss, nicht wie lange es dauert.
|
|
|
|
Was dagegen wirklich gelten muss: Eine Anfaengeraufgabe ist an einem
|
|
Abend zu schaffen (sonst faengt niemand an), und eine Meisteraufgabe
|
|
ist es nie (sonst ist sie keine). */
|
|
const schnitt = (st) => {
|
|
const l = BEREICHE.flatMap((b) => V[b][st] || []);
|
|
return l.reduce((s, v) => s + v.tage, 0) / l.length;
|
|
};
|
|
const [sa, sf, sp, sm] = STUFEN.map(schnitt);
|
|
const anfListe = BEREICHE.flatMap((b) => V[b].anfaenger || []);
|
|
const meiListe = BEREICHE.flatMap((b) => V[b].meister || []);
|
|
const anfZuLang = anfListe.filter((v) => v.tage > 5);
|
|
const meiZuKurz = meiListe.filter((v) => v.tage < 7);
|
|
ok(anfZuLang.length === 0,
|
|
`jede Anfängeraufgabe ist in höchstens fünf Tagen zu schaffen`
|
|
+ (anfZuLang.length ? ` — zu lang: ${anfZuLang.map((v) => v.schluessel).join(", ")}` : ""));
|
|
ok(meiZuKurz.length === 0,
|
|
`keine Meisteraufgabe unter einer Woche`
|
|
+ (meiZuKurz.length ? ` — zu kurz: ${meiZuKurz.map((v) => v.schluessel).join(", ")}` : ""));
|
|
ok(sa < sm,
|
|
`Anfänger deutlich kürzer als Meister (${sa.toFixed(1)} gegen ${sm.toFixed(1)} Tage;`
|
|
+ ` fort ${sf.toFixed(1)}, profi ${sp.toFixed(1)} — dazwischen gilt keine Reihenfolge)`);
|
|
/* GEGENPROBE: Eine erfundene Anfaengeraufgabe mit 30 Tagen MUSS
|
|
auffallen -- sonst prueft die Regel oben nichts. */
|
|
ok([...anfListe, { schluessel: "test", tage: 30 }].filter((v) => v.tage > 5).length === 1,
|
|
`Gegenprobe: eine Anfängeraufgabe mit 30 Tagen fällt auf`);
|
|
}
|
|
|
|
/* ---------- 2. Aus der Vorlage wird eine echte Aufgabe -------------------- */
|
|
console.log("\n=== Von der Vorlage zur Aufgabe");
|
|
{
|
|
const vorher = (await (await ruf("/workspace/api/aufgaben", kTili)).json()).aufgaben?.length ?? 0;
|
|
const a = await senden("/workspace/api/vorlagen/uebernehmen", kScout, "POST",
|
|
{ art: "aufgabe", bereich: "technik", stufe: "anfaenger", nr: 0, creator_id: idTili });
|
|
ok(a.ok, `der Scout kann eine Aufgabe für seinen Creator übernehmen (${a.status})`);
|
|
|
|
const liste = (await (await ruf("/workspace/api/aufgaben", kTili)).json()).aufgaben || [];
|
|
ok(liste.length === vorher + 1, `sie steht in der Liste des Creators (${vorher} → ${liste.length})`);
|
|
const neu = liste.find((x) => x.titel === V.technik.anfaenger[0].titel);
|
|
ok(!!neu, `mit dem richtigen Titel ("${V.technik.anfaenger[0].titel}")`);
|
|
ok(!!neu && neu.status === "offen", `als offen angelegt (${neu?.status})`);
|
|
ok(!!neu && !!neu.frist, `und mit einer Frist (${neu?.frist})`);
|
|
/* Die Frist muss in der Zukunft liegen -- und zwar nach ORTSZEIT
|
|
gerechnet, nicht nach UTC. */
|
|
if (neu?.frist) {
|
|
const heute = new Date();
|
|
const h = `${heute.getFullYear()}-${String(heute.getMonth() + 1).padStart(2, "0")}`
|
|
+ `-${String(heute.getDate()).padStart(2, "0")}`;
|
|
ok(neu.frist > h, `die Frist liegt in der Zukunft (heute ${h}, Frist ${neu.frist})`);
|
|
}
|
|
}
|
|
|
|
/* Eine ganze Stufe auf einmal.
|
|
|
|
Hier stand `vorher + 3` -- die Zahl aus der Zeit, als jede Stufe
|
|
genau drei Aufgaben hatte. Seit dem Ausbau am 09.09.2026 sind es
|
|
sieben, und die Pruefung meldete einen Fehler, wo etwas besser
|
|
geworden war. Die erwartete Zahl kommt jetzt aus der Vorlage
|
|
selbst; damit prueft sie weiterhin genau das Richtige, naemlich
|
|
dass ALLE uebernommen werden und keine verlorengeht. */
|
|
{
|
|
const sollen = (V.community.meister || []).length;
|
|
const vorher = ((await (await ruf("/workspace/api/aufgaben", kTili)).json()).aufgaben || []).length;
|
|
const a = await senden("/workspace/api/vorlagen/uebernehmen", kScout, "POST",
|
|
{ art: "aufgaben_alle", bereich: "community", stufe: "meister", creator_id: idTili });
|
|
const nachher = ((await (await ruf("/workspace/api/aufgaben", kTili)).json()).aufgaben || []).length;
|
|
ok(a.ok && sollen > 0 && nachher === vorher + sollen,
|
|
`alle ${sollen} einer Stufe auf einmal (${vorher} → ${nachher})`);
|
|
}
|
|
|
|
/* ---------- 3. Gegenproben ------------------------------------------------ */
|
|
console.log("\n=== Gegenproben");
|
|
{
|
|
const a = await senden("/workspace/api/vorlagen/uebernehmen", kScout, "POST",
|
|
{ art: "aufgabe", bereich: "technik", stufe: "gibt-es-nicht", nr: 0, creator_id: idTili });
|
|
ok(a.status === 400, `eine unbekannte Stufe wird abgelehnt (${a.status})`);
|
|
|
|
const b = await senden("/workspace/api/vorlagen/uebernehmen", kScout, "POST",
|
|
{ art: "aufgabe", bereich: "erfunden", stufe: "anfaenger", nr: 0, creator_id: idTili });
|
|
ok(b.status === 400, `ein unbekannter Bereich wird abgelehnt (${b.status})`);
|
|
|
|
const c = await senden("/workspace/api/vorlagen/uebernehmen", kScout, "POST",
|
|
{ art: "aufgabe", bereich: "technik", stufe: "anfaenger", nr: 99, creator_id: idTili });
|
|
ok(c.status === 400, `eine Nummer, die es nicht gibt, wird abgelehnt (${c.status})`);
|
|
|
|
const e = await senden("/workspace/api/vorlagen/uebernehmen", kScout, "POST",
|
|
{ art: "aufgabe", bereich: "technik", stufe: "anfaenger", nr: 0 });
|
|
ok(e.status === 400, `ohne Creator wird abgelehnt — eine Aufgabe ohne Adressaten ist ein Zettel (${e.status})`);
|
|
|
|
const f = await senden("/workspace/api/vorlagen/uebernehmen", kScout, "POST",
|
|
{ art: "aufgabe", bereich: "technik", stufe: "anfaenger", nr: 0, creator_id: idFremd });
|
|
ok(f.status === 404, `für einen fremden Creator wird abgelehnt (${f.status})`);
|
|
|
|
/* Und der Creator selbst legt sie fuer sich an -- ohne creator_id,
|
|
der Server setzt sie. Sonst koennte er sie jemand anderem geben. */
|
|
const g = await senden("/workspace/api/vorlagen/uebernehmen", kTili, "POST",
|
|
{ art: "aufgabe", bereich: "live", stufe: "anfaenger", nr: 0, creator_id: idFremd });
|
|
const beiFremd = ((await (await ruf("/workspace/api/aufgaben", kTili)).json()).aufgaben || [])
|
|
.filter((x) => x.titel === V.live.anfaenger[0].titel).length;
|
|
ok(g.ok && beiFremd === 1,
|
|
`ein Creator legt sie für SICH an, auch wenn er jemand anderen angibt (${g.status})`);
|
|
}
|
|
|
|
/* ---------- 4. Im Browser: sieht man es, und geht es? -------------------- */
|
|
console.log("\n=== Auf der Aufgabenseite");
|
|
{
|
|
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: 1200, height: 1400 } });
|
|
const seite = await kontext.newPage();
|
|
const konsole = [];
|
|
seite.on("pageerror", (f) => konsole.push(f.message));
|
|
await seite.goto(BASIS + "/workspace/", { waitUntil: "domcontentloaded" });
|
|
await seite.click('.rolle[data-rolle="creator"]');
|
|
await seite.fill("#code", "CODE-ANNA-0001");
|
|
await seite.click("button[type=submit]");
|
|
await seite.waitForURL("**/start.html", { timeout: 20000 });
|
|
await seite.goto(BASIS + "/workspace/aufgaben.html", { waitUntil: "domcontentloaded" });
|
|
await seite.waitForTimeout(2500);
|
|
|
|
const d = await seite.evaluate(() => {
|
|
const b = document.getElementById("vorlagenbrett");
|
|
return {
|
|
sichtbar: !!b && !b.hidden,
|
|
reihen: document.querySelectorAll("#vorlagenbrett .stufenleiter").length,
|
|
karten: document.querySelectorAll("#vorlagenbrett .vorlagenbrett__karte").length,
|
|
knoepfe: document.querySelectorAll("#vorlagenbrett .vorlagenbrett__fuss .knopf").length,
|
|
/* Gestaltet oder nackt? Eine Karte ohne Kante ist keine Karte --
|
|
derselbe Fehler wie damals bei der Checkliste, wo das CSS in
|
|
der falschen Datei lag und die Pruefung trotzdem gruen war,
|
|
weil sie nur Elemente gezaehlt hat. */
|
|
gestaltet: (() => {
|
|
const k = document.querySelector("#vorlagenbrett .vorlagenbrett__karte");
|
|
if (!k) return null;
|
|
const st = getComputedStyle(k);
|
|
return parseFloat(st.borderTopWidth) >= 1 && parseFloat(st.paddingLeft) >= 8;
|
|
})(),
|
|
kopftext: document.querySelector("#vorlagenbrett .vorlagenbrett__unter")?.textContent,
|
|
ueber: document.documentElement.scrollWidth - document.documentElement.clientWidth,
|
|
};
|
|
});
|
|
ok(d.sichtbar, "der Block ist da");
|
|
|
|
/* ZUGEKLAPPT ALS VORGABE (09.09.2026).
|
|
|
|
Gemessen auf dem Handy: Der Block fuellte anderthalb Bildschirme,
|
|
BEVOR die erste eigene Aufgabe kam -- und nach dem Ausbau von Profi
|
|
und Meister am selben Tag noch mehr. Vorlagen holt man selten, das
|
|
Brett benutzt man taeglich.
|
|
|
|
Geprueft wird, dass die Karten GAR NICHT GEBAUT werden, nicht nur
|
|
dass sie versteckt sind: Achtzig Karten bei jedem Neuzeichnen
|
|
anzulegen, die niemand sieht, waere Arbeit fuer nichts. */
|
|
ok(d.karten === 0 && d.reihen === 0,
|
|
`zugeklappt steht nur die Zeile da (${d.karten} Karten, ${d.reihen} Reihen)`);
|
|
ok(/\d+ fertige Aufgaben/.test(d.kopftext || ""),
|
|
`und sagt, wie viel darin liegt ("${d.kopftext}")`);
|
|
|
|
/* Aufmachen, und alles ist da. */
|
|
await seite.click("#vorlagenbrett .vorlagenbrett__kopf");
|
|
await seite.waitForTimeout(600);
|
|
const d2 = await seite.evaluate(() => ({
|
|
reihen: document.querySelectorAll("#vorlagenbrett .stufenleiter").length,
|
|
karten: document.querySelectorAll("#vorlagenbrett .vorlagenbrett__karte").length,
|
|
knoepfe: document.querySelectorAll("#vorlagenbrett .vorlagenbrett__fuss .knopf").length,
|
|
auf: document.getElementById("vorlagenbrett").dataset.auf,
|
|
/* ERST JETZT MESSBAR. Zugeklappt gibt es keine Karte, und
|
|
getComputedStyle auf `null` liefert nichts -- die Messung von
|
|
vorhin war deshalb leer und der Haken darunter rot, ohne dass
|
|
etwas kaputt war. Eine Pruefung muss ihre Voraussetzung haben,
|
|
bevor sie misst. */
|
|
gestaltet: (() => {
|
|
const k = document.querySelector("#vorlagenbrett .vorlagenbrett__karte");
|
|
if (!k) return null;
|
|
const st = getComputedStyle(k);
|
|
return parseFloat(st.borderTopWidth) >= 1 && parseFloat(st.paddingLeft) >= 8;
|
|
})(),
|
|
ueber: document.documentElement.scrollWidth - document.documentElement.clientWidth,
|
|
}));
|
|
Object.assign(d, d2);
|
|
ok(d.auf === "ja", "ein Klick auf den Kopf macht ihn auf");
|
|
ok(d.reihen === 2, `zwei Knopfreihen: Bereich und Stufe (${d.reihen})`);
|
|
ok(d.karten === 3, `drei Aufgaben je Feld (${d.karten})`);
|
|
ok(d.knoepfe === 3, `jede hat einen Übernehmen-Knopf (${d.knoepfe})`);
|
|
ok(d.gestaltet === true, "die Karten sind gestaltet, nicht nackt");
|
|
ok(d.ueber <= 0, `nichts steht seitlich über (${d.ueber}px)`);
|
|
|
|
/* Der Bereich laesst sich wechseln, und die Karten wechseln mit. */
|
|
const vorher = await seite.textContent("#vorlagenbrett .vorlagenbrett__titel");
|
|
await seite.click("#vorlagenbrett .stufenleiter >> nth=0 >> .stufenleiter__knopf >> nth=2");
|
|
await seite.waitForTimeout(500);
|
|
const nachher = await seite.textContent("#vorlagenbrett .vorlagenbrett__titel");
|
|
ok(vorher !== nachher, `ein anderer Bereich zeigt andere Aufgaben ("${vorher}" → "${nachher}")`);
|
|
|
|
/* Und der Weg bis zum Brett.
|
|
|
|
GEZAEHLT WERDEN AUFGABEN, NICHT KARTEN (berichtigt 23.09.2026).
|
|
|
|
Seit dem Brett-Umbau vom selben Tag fasst das Brett gleiche
|
|
Aufgaben zu EINER Karte zusammen. Eine neue Aufgabe erhoeht die
|
|
Kartenzahl also nur dann, wenn es noch keine gleichnamige gibt --
|
|
und die Pruefung legt kurz vorher sieben Vorlagen derselben Stufe
|
|
an. Sie meldete "9 -> 9" und behauptete damit, das Uebernehmen sei
|
|
kaputt; es war die Zaehlweise. Die Aufgabe war jede Zeit da, drei
|
|
Zeilen weiter wird sie sogar als "schon übernommen" wiedererkannt.
|
|
|
|
Dieselbe Stelle gab es ein zweites Mal in pruef-modi-katalog. Dort
|
|
ist sie am 23.09. repariert worden -- hier nicht, weil ich nach
|
|
dem ersten Fund nicht weitergesucht habe. Jede Karte weist ihre
|
|
Aufgaben aus: data-id (eine) oder data-ids (mehrere). */
|
|
const zaehle = () => seite.evaluate(() =>
|
|
[...document.querySelectorAll("#brett .karte")]
|
|
.reduce((n, k) => n + (k.dataset.ids || k.dataset.id || "")
|
|
.split(",").filter(Boolean).length, 0));
|
|
const zaehleKarten = () => seite.evaluate(() =>
|
|
document.querySelectorAll("#brett .karte").length);
|
|
const brettVorher = await zaehle();
|
|
await seite.click("#vorlagenbrett .vorlagenbrett__fuss .knopf");
|
|
await seite.waitForTimeout(2500);
|
|
const brettNachher = await zaehle();
|
|
ok(brettNachher === brettVorher + 1,
|
|
`Übernehmen legt eine Aufgabe auf dem Brett an (${brettVorher} → ${brettNachher}`
|
|
+ ` Aufgaben in ${await zaehleKarten()} Karten)`);
|
|
|
|
/* ---- WAS SCHON GEHOLT WURDE, STEHT DA (09.09.2026) -----------------
|
|
Vorher konnte man dieselbe Vorlage zweimal uebernehmen und bekam
|
|
die Aufgabe zweimal auf das Brett -- ohne jeden Hinweis. Bei
|
|
achtzig Vorlagen weiss niemand auswendig, was er letzten Monat
|
|
schon geholt hat. */
|
|
{
|
|
const m = await seite.evaluate(() => {
|
|
const k = [...document.querySelectorAll("#vorlagenbrett .vorlagenbrett__karte")];
|
|
return { markiert: k.filter((x) => x.dataset.schon === "ja").length,
|
|
gesamt: k.length,
|
|
text: document.querySelector("#vorlagenbrett .vorlagenbrett__schon")?.textContent,
|
|
knopf: k.find((x) => x.dataset.schon === "ja")
|
|
?.querySelector(".knopf")?.textContent,
|
|
stand: document.querySelector("#vorlagenbrett .vorlagenbrett__stand")?.textContent };
|
|
});
|
|
ok(m.markiert === 1,
|
|
`genau die übernommene Vorlage ist markiert (${m.markiert} von ${m.gesamt})`);
|
|
ok(/schon übernommen/.test(m.text || ""), `mit Zustand dabei ("${m.text}")`);
|
|
ok(m.knopf === "Nochmal", `und der Knopf heißt anders ("${m.knopf}")`);
|
|
ok(/\d+ von \d+/.test(m.stand || ""), `darunter steht der Stand ("${m.stand}")`);
|
|
|
|
/* GEGENPROBE: Die übrigen sind NICHT markiert. Ohne sie hiesse
|
|
"eine ist markiert" auch dann ok, wenn alle es waeren. */
|
|
ok(m.gesamt - m.markiert >= 1,
|
|
`die übrigen ${m.gesamt - m.markiert} sind nicht markiert`);
|
|
}
|
|
|
|
/* Und der Zustand des Blocks überlebt ein Neuladen -- wer Vorlagen
|
|
holt, holt meistens mehrere. */
|
|
await seite.reload({ waitUntil: "domcontentloaded" });
|
|
await seite.waitForTimeout(2200);
|
|
const nochAuf = await seite.evaluate(() =>
|
|
document.getElementById("vorlagenbrett")?.dataset.auf);
|
|
ok(nochAuf === "ja", `nach dem Neuladen ist er noch offen (${nochAuf})`);
|
|
|
|
ok(konsole.length === 0, `keine Fehler in der Konsole${konsole.length ? ": " + konsole[0] : ""}`);
|
|
await browser.close();
|
|
}
|
|
|
|
console.log(fehler ? `\n${fehler} Prüfung(en) fehlgeschlagen.` : "\nAlles in Ordnung.");
|
|
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
|
|
process.exit(fehler ? 1 : 0);
|