Eine neue Form fuer jedes Modul -- und der Kalender zeigt nur noch, was einen angeht
Filipe: "DU HAST WIEDER EINE KLEINE AENDERUNG UEBERALL GEMACHT ANSTATT
EINE RIESEN AENDERUNG." Er hatte recht, und der Grund war jedes Mal
derselbe: Ich habe das MATERIAL getauscht (Mattglas, Leuchtschiene,
Verlauf) und die FORM gelassen. Ein abgerundetes Rechteck bleibt ein
abgerundetes Rechteck -- und die Silhouette ist das Einzige, was man aus
fuenf Metern erkennt.
Jetzt ist die Form eine andere: abgeschnittene Ecke oben links
(clip-path, echte Silhouette), ein Kantenlicht in der Kategoriefarbe
darauf, Eckwinkel unten rechts, ein feines Raster statt Koernung.
35 Bauteile auf allen 18 Seiten, in einer eigenen Datei (module.css).
DREI DINGE, DIE DABEI SCHIEFGINGEN UND JETZT ABGESICHERT SIND
1. Die Regeln standen in :where() -- Spezifitaet null, also gewann jede
aeltere .kachel::before-Regel. Jetzt :is().
2. module.css stand an DRITTER Stelle im Ladeweg. Auf Calls,
Automationen, Bereich und Dateien hat sie damit gar nichts bewirkt:
Die Seitendateien setzen dort selbst border-radius und box-shadow und
kommen spaeter. Genau das ergab wieder "ueberall ein bisschen". Sie
wird jetzt als LETZTE geladen, geprueft auf allen 18 Seiten.
3. Die Klassenliste steht siebenmal in der Datei (CSS kennt keine
Variable fuer Selektoren). Eine vergessene Kopie faellt niemandem
auf -- pruef-css-klassen vergleicht sie deshalb alle, mit Gegenprobe.
DER KALENDER: NUR NOCH, WAS EINEN ANGEHT
Filipe: "calls oder termine soll jeder nur sehen die er selber macht
oder jeden betrifft." Das gilt auch fuer DogFather, und das ist die
eigentliche Aenderung -- er bekam bisher 1=1. "Jeden betrifft" heisst:
ohne Gegenueber und ohne Teilnehmerliste. Wer niemanden eintraegt, meint
alle. Die Rollenfrage entfaellt im Kalender damit vollstaendig.
SECHS PRUEFUNGEN, DIE EINE WELT GEMESSEN HABEN, DIE ES NICHT MEHR GIBT
Alle sechs wurden auf die neue Regel umgeschrieben, keine geloescht --
loeschen haette die Zahl gesenkt und den neuen Weg ungeprueft gelassen:
ics, serien, sicht, spicy, teilnehmer, tagesblick, team.
UND VIER ECHTE FUNDE, DIE DABEI HERAUSFIELEN
* pruef-workspace-seiten war seit dem Bildumbau von heute Frueh auf
ALLEN 32 Seiten rot: Sie verlangte noch das eine Motiv. Die neue
Bedingung ist schaerfer als beide alten -- das geladene Bild muss zu
dem Merkmal passen, das die Seite selbst traegt. Und die Meldung sagt
jetzt, WELCHE Bedingung gefallen ist.
* pruef-leistung-optik hat sich selbst ausgesperrt (meldete sich als
Manager an, der die Zahlen seit Filipes Anweisung nicht mehr sehen
darf) und stuerzte danach ab: 2 Pruefungen statt 56. Der Absturz hat
verdeckt, dass sie sieben Fingerziele als "zu klein" meldete -- es
waren die unsichtbaren Knoepfe der zugeklappten Woche, 0x0.
* personen.html beim Manager: Der Erklaersatz ("Codes, Sperren und das
Protokoll bleiben bei DogFather") kam nie an -- das Element gab es
nicht, und das && davor hat den Fehlgriff verschluckt. Die Liste stand
ausserdem dauerhaft auf aria-busy="true": fuer einen Screenreader lud
die Seite fuer immer.
* pruef-schranke hielt personen.html noch fuer DogFather-only. Die Seite
wechselt jetzt die Erwartung, statt aus der Pruefung zu verschwinden:
Seite auf fuer die Leitung, Verwaltungsdaten dahinter weiterhin zu.
Die Anmeldeseite wurde nicht angefasst (nur ihr Versionsstempel).
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
+123
-90
@@ -137,9 +137,16 @@ async function alsRolle(rolle, code, wohin = "leistung.html", groesse = { width:
|
||||
1. Die Seite ist überhaupt erreichbar — und über die Kachel
|
||||
======================================================================= */
|
||||
melde("\n=== Der Weg zur Seite ===");
|
||||
const man = await alsRolle("manager", "CODE-MANA-0001", "start.html");
|
||||
/* SEIT FILIPES ANWEISUNG SIEHT NUR DOGFATHER DIE ZAHLEN.
|
||||
Hier stand "manager", und bis zur Umstellung war das richtig. Seit
|
||||
die Kachel und die Seite auf DogFather beschraenkt sind, hat sich
|
||||
diese Pruefung selbst ausgesperrt: Sie meldete sieben Fehler, von
|
||||
denen keiner ein Mangel war -- sie sah nur eine Seite, die sie nicht
|
||||
mehr sehen darf. Gemessen wird jetzt als DogFather; dass der Manager
|
||||
NICHT hineinkommt, steht als eigene Gegenprobe darunter. */
|
||||
const chef = await alsRolle("admin", "CODE-DOGI-0001", "start.html");
|
||||
{
|
||||
const kachel = await man.seite.$$eval(".kachel", (k) =>
|
||||
const kachel = await chef.seite.$$eval(".kachel", (k) =>
|
||||
k.map((x) => ({
|
||||
name: x.querySelector(".kachel__name")?.textContent.trim(),
|
||||
/* Das href sitzt am .kachel__link INNEN, nicht am <li> aussen --
|
||||
@@ -156,13 +163,28 @@ const man = await alsRolle("manager", "CODE-MANA-0001", "start.html");
|
||||
|
||||
/* GEGENPROBE zum Selektor: Ein Name, den es nicht gibt, darf NICHT
|
||||
gefunden werden -- sonst prüfte der Block oben nichts. */
|
||||
const erfunden = await man.seite.$$eval(".kachel", (k) =>
|
||||
const erfunden = await chef.seite.$$eval(".kachel", (k) =>
|
||||
k.some((x) => x.querySelector(".kachel__name")?.textContent.trim() === "Zahlenzauber"));
|
||||
ok(!erfunden, "eine erfundene Kachel wird nicht gefunden");
|
||||
|
||||
await man.seite.goto(`${BASIS}/workspace/leistung.html`, { waitUntil: "networkidle" });
|
||||
await man.seite.waitForTimeout(1100);
|
||||
const titel = await man.seite.$eval(".titel", (e) => e.textContent.trim());
|
||||
/* GEGENPROBE ZUR REGEL SELBST: Der Manager darf sie NICHT sehen und
|
||||
NICHT hineinkommen. Ohne diese beiden Zeilen bewiese der Block
|
||||
oben nur, dass DogFather etwas sieht -- nicht, dass sonst niemand
|
||||
es sieht. Und genau das war die Anweisung. */
|
||||
const mgr = await alsRolle("manager", "CODE-MANA-0001", "start.html");
|
||||
const beiIhm = await mgr.seite.$$eval(".kachel", (k) =>
|
||||
k.map((x) => x.querySelector(".kachel__name")?.textContent.trim()));
|
||||
ok(beiIhm.length > 3 && !beiIhm.includes("Zahlen"),
|
||||
`beim Manager fehlt sie (${beiIhm.length} Kacheln, keine davon "Zahlen")`);
|
||||
|
||||
const versuch = await mgr.seite.evaluate(async (b) =>
|
||||
(await fetch(b + "/workspace/leistung.html", { redirect: "manual" })).status, BASIS);
|
||||
ok(versuch !== 200, `und die Seite selbst laesst ihn nicht herein (HTTP ${versuch})`);
|
||||
await mgr.ctx.close();
|
||||
|
||||
await chef.seite.goto(`${BASIS}/workspace/leistung.html`, { waitUntil: "networkidle" });
|
||||
await chef.seite.waitForTimeout(1100);
|
||||
const titel = await chef.seite.$eval(".titel", (e) => e.textContent.trim());
|
||||
ok(titel.length > 0, `die Seite ist offen: "${titel}"`);
|
||||
}
|
||||
|
||||
@@ -171,14 +193,14 @@ const man = await alsRolle("manager", "CODE-MANA-0001", "start.html");
|
||||
======================================================================= */
|
||||
melde("\n=== Einen Tag eintragen ===");
|
||||
{
|
||||
const vorher = await man.seite.$$eval(".zahlkarte__wert", (w) => w.map((x) => x.textContent));
|
||||
const vorher = await chef.seite.$$eval(".zahlkarte__wert", (w) => w.map((x) => x.textContent));
|
||||
ok(vorher.length >= 5,
|
||||
`die Wochenkarten stehen da (${vorher.length} Werte) — sonst prüft alles Folgende nichts`);
|
||||
|
||||
/* Die laufende Woche ist leer: Diamanten müssen 0 sein. Steht hier
|
||||
schon etwas, ist der Bestand oben falsch aufgebaut und die Prüfung
|
||||
misst etwas anderes als sie behauptet. */
|
||||
const diaVorher = await man.seite.$$eval(".zahlkarte", (k) => {
|
||||
const diaVorher = await chef.seite.$$eval(".zahlkarte", (k) => {
|
||||
const t = k.find((x) => x.querySelector(".zahlkarte__name")?.textContent === "Diamanten");
|
||||
return t?.querySelector(".zahlkarte__wert")?.textContent;
|
||||
});
|
||||
@@ -186,19 +208,19 @@ melde("\n=== Einen Tag eintragen ===");
|
||||
|
||||
/* Der erste "eintragen"-Knopf ist der von HEUTE (die Liste läuft
|
||||
rückwärts). */
|
||||
const knoepfe = await man.seite.$$(".tagzeile__knopf");
|
||||
const knoepfe = await chef.seite.$$(".tagzeile__knopf");
|
||||
ok(knoepfe.length === 14, `es gibt 14 Tageszeilen mit Knopf (${knoepfe.length})`);
|
||||
await knoepfe[0].click();
|
||||
await man.seite.waitForSelector("#tag-dialog[open]", { timeout: 5000 });
|
||||
await chef.seite.waitForSelector("#tag-dialog[open]", { timeout: 5000 });
|
||||
|
||||
await man.seite.fill("#f-diamanten", "1250");
|
||||
await man.seite.fill("#f-dauer", "120");
|
||||
await man.seite.fill("#f-verweildauer", "260");
|
||||
await man.seite.fill("#f-notiz", "PK gegen Mia");
|
||||
await man.seite.click("#tag-speichern");
|
||||
await man.seite.waitForTimeout(1400);
|
||||
await chef.seite.fill("#f-diamanten", "1250");
|
||||
await chef.seite.fill("#f-dauer", "120");
|
||||
await chef.seite.fill("#f-verweildauer", "260");
|
||||
await chef.seite.fill("#f-notiz", "PK gegen Mia");
|
||||
await chef.seite.click("#tag-speichern");
|
||||
await chef.seite.waitForTimeout(1400);
|
||||
|
||||
const dia = await man.seite.$$eval(".zahlkarte", (k) => {
|
||||
const dia = await chef.seite.$$eval(".zahlkarte", (k) => {
|
||||
const t = k.find((x) => x.querySelector(".zahlkarte__name")?.textContent === "Diamanten");
|
||||
return t?.querySelector(".zahlkarte__wert")?.textContent;
|
||||
});
|
||||
@@ -206,7 +228,7 @@ melde("\n=== Einen Tag eintragen ===");
|
||||
|
||||
/* DER VERGLEICH. Ohne ihn ist die Zahl eine Behauptung -- das ist die
|
||||
Regel, wegen der dieses Modul überhaupt so gebaut ist. */
|
||||
const wandel = await man.seite.$$eval(".zahlkarte", (k) => {
|
||||
const wandel = await chef.seite.$$eval(".zahlkarte", (k) => {
|
||||
const t = k.find((x) => x.querySelector(".zahlkarte__name")?.textContent === "Diamanten");
|
||||
const w = t?.querySelector(".zahlkarte__wandel");
|
||||
return { text: w?.textContent || "", richtung: w?.dataset.richtung || "" };
|
||||
@@ -227,7 +249,7 @@ melde("\n=== Einen Tag eintragen ===");
|
||||
|
||||
/* Und die Farbe muss der Richtung folgen -- sonst steht ein Minus in
|
||||
Grün da und niemand merkt es. */
|
||||
const farbe = await man.seite.$$eval(".zahlkarte", (k) => {
|
||||
const farbe = await chef.seite.$$eval(".zahlkarte", (k) => {
|
||||
const t = k.find((x) => x.querySelector(".zahlkarte__name")?.textContent === "Diamanten");
|
||||
return getComputedStyle(t.querySelector(".zahlkarte__wandel")).color;
|
||||
});
|
||||
@@ -236,12 +258,12 @@ melde("\n=== Einen Tag eintragen ===");
|
||||
`und die Farbe passt dazu (${farbe})`);
|
||||
|
||||
/* Die Notiz ist der Kontext, ohne den eine Zahl in die Irre führt. */
|
||||
const notiz = await man.seite.$$eval(".tagzeile__notiz", (n) => n.map((x) => x.textContent));
|
||||
const notiz = await chef.seite.$$eval(".tagzeile__notiz", (n) => n.map((x) => x.textContent));
|
||||
ok(notiz.includes("PK gegen Mia"), `die Notiz steht in der Tageszeile (${notiz.length} Notizen)`);
|
||||
|
||||
/* GEGENPROBE: Ein Wert, der nie eingetragen wurde, darf nicht
|
||||
dastehen. */
|
||||
const erfunden = await man.seite.$$eval(".zahlkarte__wert", (w) =>
|
||||
const erfunden = await chef.seite.$$eval(".zahlkarte__wert", (w) =>
|
||||
w.some((x) => x.textContent === "9.999"));
|
||||
ok(!erfunden, "ein nie eingetragener Wert erscheint nicht");
|
||||
}
|
||||
@@ -257,7 +279,7 @@ melde("\n=== Die kleine Verlaufslinie ===");
|
||||
und der Browser zeichnete wortlos gar nichts. Ein SVG mit
|
||||
points="NaN,NaN" ist im DOM vorhanden und auf dem Bildschirm
|
||||
unsichtbar. Deshalb werden die PUNKTE gemessen. */
|
||||
const linien = await man.seite.$$eval(".zahlkarte__linie polyline", (ls) =>
|
||||
const linien = await chef.seite.$$eval(".zahlkarte__linie polyline", (ls) =>
|
||||
ls.map((l) => l.getAttribute("points") || ""));
|
||||
ok(linien.length >= 3, `${linien.length} Verlaufslinien sind gezeichnet`);
|
||||
|
||||
@@ -272,20 +294,20 @@ melde("\n=== Die kleine Verlaufslinie ===");
|
||||
`jede Linie hat mindestens drei Punkte (${zuKurz.length} zu kurz)`);
|
||||
|
||||
/* Auch der Endpunkt-Kreis. */
|
||||
const kreise = await man.seite.$$eval(".zahlkarte__linie circle", (cs) =>
|
||||
const kreise = await chef.seite.$$eval(".zahlkarte__linie circle", (cs) =>
|
||||
cs.map((c) => `${c.getAttribute("cx")},${c.getAttribute("cy")}`));
|
||||
ok(kreise.length === linien.length && !kreise.some((k) => /NaN/.test(k)),
|
||||
`jede Linie hat einen Endpunkt (${kreise.length})`);
|
||||
|
||||
/* GEGENPROBE zum Selektor: Nach dem Entfernen darf nichts mehr
|
||||
gefunden werden. */
|
||||
const nachher = await man.seite.evaluate(() => {
|
||||
const nachher = await chef.seite.evaluate(() => {
|
||||
document.querySelectorAll(".zahlkarte__linie").forEach((e) => e.remove());
|
||||
return document.querySelectorAll(".zahlkarte__linie polyline").length;
|
||||
});
|
||||
ok(nachher === 0, "der Selektor trifft wirklich die Linien (nach dem Entfernen 0)");
|
||||
await man.seite.reload({ waitUntil: "networkidle" });
|
||||
await man.seite.waitForTimeout(1200);
|
||||
await chef.seite.reload({ waitUntil: "networkidle" });
|
||||
await chef.seite.waitForTimeout(1200);
|
||||
}
|
||||
|
||||
/* =======================================================================
|
||||
@@ -293,17 +315,17 @@ melde("\n=== Die kleine Verlaufslinie ===");
|
||||
======================================================================= */
|
||||
melde("\n=== Ziele ===");
|
||||
{
|
||||
const leerText = await man.seite.$eval("#zielbalken", (e) => e.textContent.trim());
|
||||
const leerText = await chef.seite.$eval("#zielbalken", (e) => e.textContent.trim());
|
||||
ok(/keine Ziele/i.test(leerText), `ohne Ziel steht ein Satz da: "${leerText.slice(0, 60)}…"`);
|
||||
|
||||
await man.seite.click("#ziele-aendern");
|
||||
await man.seite.waitForSelector("#ziele-dialog[open]", { timeout: 5000 });
|
||||
await man.seite.fill("#z-diamanten", "2500");
|
||||
await man.seite.fill("#z-tage", "5");
|
||||
await man.seite.click("#ziele-speichern");
|
||||
await man.seite.waitForTimeout(1300);
|
||||
await chef.seite.click("#ziele-aendern");
|
||||
await chef.seite.waitForSelector("#ziele-dialog[open]", { timeout: 5000 });
|
||||
await chef.seite.fill("#z-diamanten", "2500");
|
||||
await chef.seite.fill("#z-tage", "5");
|
||||
await chef.seite.click("#ziele-speichern");
|
||||
await chef.seite.waitForTimeout(1300);
|
||||
|
||||
const balken = await man.seite.$$eval(".zielbalken__zeile", (zs) =>
|
||||
const balken = await chef.seite.$$eval(".zielbalken__zeile", (zs) =>
|
||||
zs.map((z) => ({
|
||||
name: z.querySelector(".zielbalken__name")?.textContent,
|
||||
zahl: z.querySelector(".zielbalken__zahl")?.textContent,
|
||||
@@ -316,12 +338,12 @@ melde("\n=== Ziele ===");
|
||||
ok(dia?.breite === "50%", `der Balken ist zur Hälfte gefüllt (${dia?.breite})`);
|
||||
|
||||
/* KEIN BALKEN ÜBER 100 % -- er hinge sonst aus dem Kasten. */
|
||||
await man.seite.click("#ziele-aendern");
|
||||
await man.seite.waitForSelector("#ziele-dialog[open]", { timeout: 5000 });
|
||||
await man.seite.fill("#z-diamanten", "500");
|
||||
await man.seite.click("#ziele-speichern");
|
||||
await man.seite.waitForTimeout(1300);
|
||||
const voll = await man.seite.$$eval(".zielbalken__zeile", (zs) => {
|
||||
await chef.seite.click("#ziele-aendern");
|
||||
await chef.seite.waitForSelector("#ziele-dialog[open]", { timeout: 5000 });
|
||||
await chef.seite.fill("#z-diamanten", "500");
|
||||
await chef.seite.click("#ziele-speichern");
|
||||
await chef.seite.waitForTimeout(1300);
|
||||
const voll = await chef.seite.$$eval(".zielbalken__zeile", (zs) => {
|
||||
const z = zs.find((x) => x.querySelector(".zielbalken__name")?.textContent === "Diamanten");
|
||||
return {
|
||||
breite: z?.querySelector(".zielbalken__fuell")?.style.width,
|
||||
@@ -346,23 +368,23 @@ melde("\n=== Aus Datei einlesen ===");
|
||||
"kein-datum;500;60;100",
|
||||
].join("\n");
|
||||
|
||||
await man.seite.click("#import-oeffnen");
|
||||
await man.seite.waitForSelector("#import-dialog[open]", { timeout: 5000 });
|
||||
await chef.seite.click("#import-oeffnen");
|
||||
await chef.seite.waitForSelector("#import-dialog[open]", { timeout: 5000 });
|
||||
|
||||
const gesperrt = await man.seite.$eval("#import-los", (b) => b.disabled);
|
||||
const gesperrt = await chef.seite.$eval("#import-los", (b) => b.disabled);
|
||||
ok(gesperrt, "der Übernehmen-Knopf ist gesperrt, solange keine Datei da ist");
|
||||
|
||||
await man.seite.setInputFiles("#datei", {
|
||||
await chef.seite.setInputFiles("#datei", {
|
||||
name: "export.csv", mimeType: "text/csv", buffer: Buffer.from(csv, "utf8"),
|
||||
});
|
||||
await man.seite.waitForSelector("#vorschau:not([hidden])", { timeout: 8000 });
|
||||
await chef.seite.waitForSelector("#vorschau:not([hidden])", { timeout: 8000 });
|
||||
|
||||
const zahlen = await man.seite.$$eval(".import-zahl", (zs) =>
|
||||
const zahlen = await chef.seite.$$eval(".import-zahl", (zs) =>
|
||||
zs.map((z) => `${z.querySelector(".import-zahl__wert")?.textContent} ${z.querySelector(".import-zahl__name")?.textContent}`));
|
||||
ok(zahlen.some((z) => /^2 neu/.test(z)), `die Vorschau zählt: ${zahlen.join(" · ")}`);
|
||||
ok(zahlen.some((z) => /^1 unlesbar/.test(z)), "die unlesbare Zeile wird gemeldet, nicht verschluckt");
|
||||
|
||||
const spalten = await man.seite.$eval(".import-spalten", (e) => e.textContent);
|
||||
const spalten = await chef.seite.$eval(".import-spalten", (e) => e.textContent);
|
||||
ok(/Diamanten/.test(spalten) && /LIVE-Dauer/.test(spalten),
|
||||
`die erkannten Spalten stehen da: "${spalten}"`);
|
||||
|
||||
@@ -370,22 +392,22 @@ melde("\n=== Aus Datei einlesen ===");
|
||||
geschrieben worden sein. Eine Vorschau, die schon schreibt, ist
|
||||
keine Vorschau. */
|
||||
const vorAntwort = await fetch(`${BASIS}/workspace/api/leistung/${idLuna}/tage?tage=7`, {
|
||||
headers: { cookie: (await man.ctx.cookies()).map((c) => `${c.name}=${c.value}`).join("; ") },
|
||||
headers: { cookie: (await chef.ctx.cookies()).map((c) => `${c.name}=${c.value}`).join("; ") },
|
||||
});
|
||||
const vorTage = (await vorAntwort.json()).tage || [];
|
||||
const schonDa = vorTage.some((t) => t.tag === tagVor(3));
|
||||
ok(!schonDa, "vor dem Bestätigen steht noch nichts in der Datenbank");
|
||||
|
||||
await man.seite.click("#import-los");
|
||||
await man.seite.waitForTimeout(1600);
|
||||
await chef.seite.click("#import-los");
|
||||
await chef.seite.waitForTimeout(1600);
|
||||
|
||||
const nachTage = await man.seite.$$eval(".tagzeile", (zs) =>
|
||||
const nachTage = await chef.seite.$$eval(".tagzeile", (zs) =>
|
||||
zs.filter((z) => z.dataset.quelle === "import").length);
|
||||
ok(nachTage === 2, `nach dem Bestätigen sind 2 Zeilen aus dem Import da (${nachTage})`);
|
||||
|
||||
/* DER TAUSENDERPUNKT. "1.480" darf nicht als 1,48 ankommen -- Faktor
|
||||
tausend daneben, und die Zahl sieht trotzdem plausibel aus. */
|
||||
const werte = await man.seite.$$eval(".tagzeile", (zs) =>
|
||||
const werte = await chef.seite.$$eval(".tagzeile", (zs) =>
|
||||
zs.filter((z) => z.dataset.quelle === "import")
|
||||
.map((z) => z.querySelector(".tagzeile__wertzahl")?.textContent));
|
||||
ok(werte.includes("1.480"), `"1.480" ist als 1480 angekommen (${werte.join(", ")})`);
|
||||
@@ -397,26 +419,23 @@ melde("\n=== Aus Datei einlesen ===");
|
||||
6. Der Creator sieht seine Zahlen — und darf sie nicht eintragen
|
||||
======================================================================= */
|
||||
melde("\n=== Was der Creator sieht ===");
|
||||
const luna = await alsRolle("creator", "CODE-CREA-0001");
|
||||
const luna = await alsRolle("creator", "CODE-CREA-0001", "start.html");
|
||||
{
|
||||
const karten = await luna.seite.$$eval(".zahlkarte__wert", (w) => w.map((x) => x.textContent));
|
||||
ok(karten.length >= 5, `Luna sieht ihre Zahlen (${karten.length} Werte)`);
|
||||
/* 1250 von Hand + 1480 + 970 aus dem Import = 3700. Die Zahl steht
|
||||
hier ausgerechnet und nicht abgeschrieben: So fällt auf, wenn der
|
||||
Import etwas dazuzählt oder verschluckt. */
|
||||
ok(karten.includes("3.700"),
|
||||
`darunter ihre Diamanten, 1250+1480+970 (${karten.slice(0, 4).join(", ")})`);
|
||||
/* HIER STAND DAS GEGENTEIL, UND ES WAR RICHTIG -- bis Filipe die
|
||||
Regel geändert hat: "nimm die kategorie zahlen bei jedem weg, nur
|
||||
die rolle dogfather soll die sehen." Vorher sah Luna ihre eigenen
|
||||
Zahlen (nur ohne Eintragen-Knopf), jetzt kommt sie gar nicht mehr
|
||||
hinein. Die Prüfung misst deshalb nicht mehr, WAS sie dort sieht,
|
||||
sondern DASS sie nicht hineinkommt -- und dass die Schranke am
|
||||
Server sitzt, nicht bloß in der Oberfläche. */
|
||||
const keineKachel = await luna.seite.$$eval(".kachel", (k) =>
|
||||
k.map((x) => x.querySelector(".kachel__name")?.textContent.trim()));
|
||||
ok(keineKachel.length > 2 && !keineKachel.includes("Zahlen"),
|
||||
`bei Luna fehlt die Kachel "Zahlen" (${keineKachel.length} Kacheln)`);
|
||||
|
||||
const knoepfe = await luna.seite.$$(".tagzeile__knopf");
|
||||
ok(knoepfe.length === 0, `sie hat keinen Eintragen-Knopf (${knoepfe.length})`);
|
||||
|
||||
const zieleKnopf = await luna.seite.$eval("#ziele-aendern", (b) => b.hidden);
|
||||
ok(zieleKnopf, "und kann auch keine Ziele setzen");
|
||||
|
||||
/* Kein Auswahlfeld: Sie hat nur sich selbst, und eine Liste mit einem
|
||||
Eintrag ist eine Frage ohne Anlass. */
|
||||
const wahl = await luna.seite.$eval("#creator-wahl", (e) => e.hidden);
|
||||
ok(wahl, "es gibt kein Creator-Auswahlfeld für sie");
|
||||
const rein = await luna.seite.evaluate(async () =>
|
||||
(await fetch("/workspace/leistung.html", { credentials: "same-origin" })).redirected);
|
||||
ok(rein === true, "und die Seite selbst weist sie ab (Umleitung)");
|
||||
|
||||
/* DIE SCHRANKE SITZT AM SERVER, nicht in der Oberfläche. Ein
|
||||
versteckter Knopf ist keine Sicherung. */
|
||||
@@ -439,7 +458,7 @@ melde("\n=== Lesbar und bedienbar ===");
|
||||
/* Kleiner als 12 px liest niemand auf einem Bildschirm, an dem er
|
||||
jeden Tag sitzt. Zweimal ist mir das schon durchgerutscht (10,56
|
||||
und 10,88 px) -- deshalb wird es gemessen, nicht angesehen. */
|
||||
const klein = await man.seite.$$eval(
|
||||
const klein = await chef.seite.$$eval(
|
||||
".zahlkarte *, .zielbalken *, .tagzeile *, .import-vorschau *",
|
||||
(es) => es
|
||||
.filter((e) => e.textContent.trim() && !e.children.length)
|
||||
@@ -451,19 +470,33 @@ melde("\n=== Lesbar und bedienbar ===");
|
||||
/* Die ZAHL der geprüften Elemente gehört in die Bedingung, nicht nur
|
||||
in den Meldetext. Eine leere Auswahl wäre sonst grün, ohne etwas
|
||||
angesehen zu haben. */
|
||||
const gezaehlt = await man.seite.$$eval(
|
||||
const gezaehlt = await chef.seite.$$eval(
|
||||
".zahlkarte *, .zielbalken *, .tagzeile *",
|
||||
(es) => es.filter((e) => e.textContent.trim() && !e.children.length).length);
|
||||
ok(gezaehlt > 40, `und es wurden wirklich Elemente gemessen (${gezaehlt})`);
|
||||
|
||||
/* HANDY: Der Eintragen-Knopf muss mit dem Daumen zu treffen sein. */
|
||||
const handy = await alsRolle("manager", "CODE-MANA-0001", "leistung.html",
|
||||
const handy = await alsRolle("admin", "CODE-DOGI-0001", "leistung.html",
|
||||
{ width: 390, height: 844 });
|
||||
const masse = await handy.seite.$$eval(".tagzeile__knopf", (bs) =>
|
||||
bs.map((b) => { const r = b.getBoundingClientRect(); return { h: Math.round(r.height), w: Math.round(r.width) }; }));
|
||||
/* NUR SICHTBARE KNÖPFE. Die Seite zeigt zwei Wochen, und die zweite
|
||||
ist zugeklappt -- ihre Knöpfe messen 0×0. Genau die meldete die
|
||||
Prüfung als "zu klein": ein Fingerziel, das niemand sehen kann,
|
||||
ist kein zu kleines Fingerziel, sondern gar keins. Sieben
|
||||
angebliche Mängel, kein einziger echter. Deshalb wird die Zahl der
|
||||
wirklich sichtbaren mitgeführt und in die Bedingung genommen --
|
||||
wäre plötzlich keiner mehr sichtbar, muss das auffallen und darf
|
||||
nicht als "alle groß genug" durchgehen. */
|
||||
const masse = await handy.seite.$$eval(".tagzeile__knopf", (bs) => bs
|
||||
.filter((b) => b.checkVisibility ? b.checkVisibility() : b.offsetParent !== null)
|
||||
.map((b) => { const r = b.getBoundingClientRect(); return { h: Math.round(r.height), w: Math.round(r.width) }; })
|
||||
.filter((m) => m.w > 0 && m.h > 0));
|
||||
const zuKlein = masse.filter((m) => m.h < 38 || m.w < 60);
|
||||
ok(masse.length > 0 && zuKlein.length === 0,
|
||||
`alle ${masse.length} Eintragen-Knöpfe sind groß genug (${zuKlein.length} zu klein)`);
|
||||
/* Die Maße der Ausreißer gehören in die Meldung. "7 zu klein" sagt
|
||||
nur, DASS etwas nicht stimmt; "38×44" sagt, WAS -- und erspart
|
||||
einen zweiten Lauf, nur um nachzusehen. */
|
||||
ok(masse.length >= 7 && zuKlein.length === 0,
|
||||
`alle ${masse.length} Eintragen-Knöpfe sind groß genug (${zuKlein.length} zu klein`
|
||||
+ `${zuKlein.length ? ": " + zuKlein.slice(0, 4).map((m) => `${m.w}×${m.h}`).join(", ") : ""})`);
|
||||
|
||||
/* NICHTS DARF SEITLICH HERAUSHÄNGEN. Waagerechtes Scrollen auf dem
|
||||
Handy ist der sicherste Weg, eine Seite unbenutzbar zu machen. */
|
||||
@@ -494,10 +527,10 @@ melde("\n=== Lesbar und bedienbar ===");
|
||||
======================================================================= */
|
||||
melde("\n=== Dashboard ===");
|
||||
{
|
||||
await man.seite.goto(`${BASIS}/workspace/uebersicht.html`, { waitUntil: "networkidle" });
|
||||
await man.seite.waitForTimeout(1600);
|
||||
await chef.seite.goto(`${BASIS}/workspace/uebersicht.html`, { waitUntil: "networkidle" });
|
||||
await chef.seite.waitForTimeout(1600);
|
||||
|
||||
const zahlen = await man.seite.$$eval(".kzahlen", (zs) =>
|
||||
const zahlen = await chef.seite.$$eval(".kzahlen", (zs) =>
|
||||
zs.map((z) => ({
|
||||
ziel: z.getAttribute("href"),
|
||||
felder: [...z.querySelectorAll(".kzahlen__feld")].map((f) =>
|
||||
@@ -511,21 +544,21 @@ melde("\n=== Dashboard ===");
|
||||
|
||||
/* DIE FRÜHWARNUNG. Der Bestand oben fällt über drei Wochen ab und
|
||||
hat diese Woche weniger LIVE-Tage -- da MUSS etwas stehen. */
|
||||
const warnung = await man.seite.$eval("#fruehwarnung", (e) => e.hidden);
|
||||
const warnung = await chef.seite.$eval("#fruehwarnung", (e) => e.hidden);
|
||||
ok(!warnung, "die Frühwarnung ist sichtbar");
|
||||
|
||||
const signale = await man.seite.$$eval(".warnsignal__text", (ts) => ts.map((t) => t.textContent));
|
||||
const signale = await chef.seite.$$eval(".warnsignal__text", (ts) => ts.map((t) => t.textContent));
|
||||
ok(signale.length > 0, `${signale.length} Signale: ${signale.join(" | ").slice(0, 140)}`);
|
||||
|
||||
/* KEINE PUNKTZAHL. Genau das war die Entscheidung: sechs benannte
|
||||
Beobachtungen statt eines Werts, dem man nicht widersprechen kann. */
|
||||
const punktzahl = await man.seite.evaluate(() =>
|
||||
const punktzahl = await chef.seite.evaluate(() =>
|
||||
/risiko|score|punkte?zahl/i.test(document.getElementById("fruehwarnung")?.textContent || ""));
|
||||
ok(!punktzahl, "und nirgends steht eine Risiko-Punktzahl");
|
||||
|
||||
/* Der Weg zum Gespräch. Ein Hinweis ohne Weg dahin ist nur ein
|
||||
schlechtes Gewissen. */
|
||||
const wege = await man.seite.$$eval(".warnkarte__weg", (ws) => ws.map((w) => w.getAttribute("href")));
|
||||
const wege = await chef.seite.$$eval(".warnkarte__weg", (ws) => ws.map((w) => w.getAttribute("href")));
|
||||
ok(wege.length > 0 && wege.every((w) => /^chat\.html\?mit=\d+$/.test(w)),
|
||||
`jede Warnkarte führt ins Gespräch (${wege.join(", ")})`);
|
||||
|
||||
@@ -533,11 +566,11 @@ melde("\n=== Dashboard ===");
|
||||
Fläche mit eigenem CSS. Beim ersten Lauf stand .kzahlen__name auf
|
||||
.66 rem = 10,56 px; genau derselbe Wert, der mir schon zweimal
|
||||
durchgerutscht ist, weil ihn niemand misst. */
|
||||
const kleinDash = await man.seite.$$eval(".kzahlen *, .warnkarte *", (es) => es
|
||||
const kleinDash = await chef.seite.$$eval(".kzahlen *, .warnkarte *", (es) => es
|
||||
.filter((e) => e.textContent.trim() && !e.children.length)
|
||||
.map((e) => ({ t: e.textContent.trim().slice(0, 24), px: parseFloat(getComputedStyle(e).fontSize) }))
|
||||
.filter((x) => x.px < 12));
|
||||
const gezaehltDash = await man.seite.$$eval(".kzahlen *, .warnkarte *",
|
||||
const gezaehltDash = await chef.seite.$$eval(".kzahlen *, .warnkarte *",
|
||||
(es) => es.filter((e) => e.textContent.trim() && !e.children.length).length);
|
||||
ok(gezaehltDash > 8 && kleinDash.length === 0,
|
||||
`im Dashboard keine Schrift unter 12 px (${gezaehltDash} gemessen, ${kleinDash.length} zu klein`
|
||||
@@ -546,9 +579,9 @@ melde("\n=== Dashboard ===");
|
||||
/* Und dieser Weg muss WIRKLICH funktionieren. ?mit= war bis zum
|
||||
06.09.2026 gar nicht vorgesehen -- ein Link, der auf einer leeren
|
||||
Gesprächsliste endet, ist schlimmer als keiner. */
|
||||
await man.seite.goto(`${BASIS}/workspace/${wege[0]}`, { waitUntil: "networkidle" });
|
||||
await man.seite.waitForTimeout(1800);
|
||||
const offen = await man.seite.$eval("#verlauf-titel", (e) => e.textContent.trim());
|
||||
await chef.seite.goto(`${BASIS}/workspace/${wege[0]}`, { waitUntil: "networkidle" });
|
||||
await chef.seite.waitForTimeout(1800);
|
||||
const offen = await chef.seite.$eval("#verlauf-titel", (e) => e.textContent.trim());
|
||||
ok(offen === "Luna", `der Link öffnet das Gespräch mit "${offen}"`);
|
||||
}
|
||||
|
||||
@@ -573,7 +606,7 @@ melde("\n=== Was der Creator NICHT sieht ===");
|
||||
}
|
||||
|
||||
await luna.ctx.close();
|
||||
await man.ctx.close();
|
||||
await chef.ctx.close();
|
||||
await browser.close();
|
||||
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user