diff --git a/server/pruef-dateien-liste.mjs b/server/pruef-dateien-liste.mjs new file mode 100644 index 00000000..1c31a647 --- /dev/null +++ b/server/pruef-dateien-liste.mjs @@ -0,0 +1,163 @@ +/* DATEIEN: DIE LISTE EINES EINTRAGS (01.10.2026) + + Filipe: "es sieht sogar noch richtig scheisse aus wenn ich da was + hochladen will." Auf dem Handy lag "Fassung 2" quer ueber dem Datum, + "1,9 MB" brach in zwei Zeilen, und am Rechner stand "SICHTBAR FUER" + zweizeilig ganz links am Rand. + + URSACHE (gemessen): .datei ist ein Raster mit den Spalten + `34px 1fr auto`. Die ersten vier Kinder sitzen richtig -- Zeichen, + Name, Knoepfe, Metazeile. Alles DANACH (Fassungs-Marke, Freigaben, + Personenwahl) wird automatisch platziert und landet in der ersten + Spalte. Die ist 34 Pixel breit. Darin steht dann "Fassung 2", und + was nicht hineinpasst, legt sich ueber den Nachbarn. + + GEMESSEN WIRD: Bekommt jeder Block unterhalb der Kopfzeile genug + Breite, und laeuft sein Inhalt ueber? Beides sind Zahlen. + + DREI AUSGAENGE: 0 in Ordnung / 1 Befunde / 2 konnte nicht arbeiten. */ +import { mkdtempSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +const ordner = mkdtempSync(join(tmpdir(), "ws-dateienliste-")); +process.env.WORKSPACE_DB = join(ordner, "workspace.db"); + +const { eigenerPort } = await import("./helfer-port.mjs"); +const PORT = await eigenerPort(import.meta, "die Dateilisten-Pruefung"); +process.env.PORT = `${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 }); }; +await import("./index.js"); +const { notbremse } = await import("./helfer-notbremse.mjs"); +notbremse(420_000, "pruef-dateien-liste"); +await new Promise((r) => setTimeout(r, 1200)); +const BASIS = `http://127.0.0.1:${PORT}`; + +let geprueft = 0, fehler = 0; +const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; }; + +/* ---- eine Person, die ablegen darf ---------------------------------- */ +const { DatabaseSync } = await import("node:sqlite"); +const { scryptSync, randomBytes } = await import("node:crypto"); +await fetch(BASIS + "/workspace/api/anmelden", { + method: "POST", headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ rolle: "admin", code: "x" }) }).catch(() => {}); +for (let i = 0; i < 60; i++) { + try { + const p = new DatabaseSync(process.env.WORKSPACE_DB); + try { p.prepare("SELECT 1 FROM personen LIMIT 1").get(); break; } finally { p.close(); } + } catch { await new Promise((r) => setTimeout(r, 250)); } +} +const d = new DatabaseSync(process.env.WORKSPACE_DB); +const salt = randomBytes(16).toString("hex"); +const hash = scryptSync("CODE-DOGI-0001", 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("Filipe", "admin", hash, salt, 32768, new Date().toISOString()); +d.close(); + +/* Eine Datei mit langem Namen -- kurze Namen verdecken genau den Fehler, + um den es hier geht. */ +const PROBE = join(ordner, "16 Events und Kampagnen \u2014 Spicy Media x Dogfather.html"); +writeFileSync(PROBE, "Probe" + "x".repeat(400000)); + +const pw = await import("file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs"); +const browser = await pw.chromium.launch(); + +const BREITEN = [1280, 900, 390]; +for (const breite of BREITEN) { + const ctx = await browser.newContext({ viewport: { width: breite, height: 1000 }, hasTouch: breite <= 860 }); + const seite = await ctx.newPage(); + const konsole = []; + seite.on("pageerror", (f) => konsole.push("Absturz: " + f.message)); + seite.on("console", (m) => { if (m.type() === "error") konsole.push("Konsole: " + m.text()); }); + + await seite.goto(BASIS + "/workspace/", { waitUntil: "networkidle" }); + 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: 25000 }); + await seite.goto(BASIS + "/workspace/dateien.html", { waitUntil: "networkidle" }); + await seite.waitForTimeout(700); + + console.log(`\n--- ${breite} px ---`); + /* ZWEIMAL dieselbe Datei: Beim zweiten Mal entsteht "Fassung 2" und + "nicht mehr aktuell" -- genau der Zustand auf Filipes Bild. Ohne das + waere die Pruefung blind fuer den Fall, der gemeldet wurde. */ + for (let n = 0; n < 2; n++) { + await seite.setInputFiles("#feld-datei", PROBE); + await seite.waitForTimeout(2500); + } + + const befund = await seite.evaluate(() => { + const eintrag = document.querySelector("#liste .datei"); + if (!eintrag) return { fehlt: true }; + const eig = eintrag.getBoundingClientRect(); + const bloecke = [...eintrag.children].map((el) => { + const r = el.getBoundingClientRect(); + return { + klasse: (el.className.baseVal ?? el.className ?? "").toString().split(" ")[0] || el.tagName, + breite: Math.round(r.width), + ueberlauf: Math.round(el.scrollWidth - el.clientWidth), + text: (el.textContent || "").trim().replace(/\s+/g, " ").slice(0, 24), + o: r.top, u: r.bottom, l: r.left, re: r.right, + }; + }); + const ueber = []; + for (let i = 0; i < bloecke.length; i++) { + for (let j = i + 1; j < bloecke.length; j++) { + const a = bloecke[i], b = bloecke[j]; + const sen = Math.min(a.u, b.u) - Math.max(a.o, b.o); + const waa = Math.min(a.re, b.re) - Math.max(a.l, b.l); + if (sen > 1 && waa > 1) ueber.push(`${a.klasse} auf ${b.klasse} (${Math.round(sen)} px)`); + } + } + return { fehlt: false, breiteEintrag: Math.round(eig.width), bloecke, ueber }; + }); + + if (befund.fehlt) { + console.log(" KONNTE NICHT ARBEITEN: Kein Eintrag in der Liste -- Upload hat nicht geklappt."); + await ctx.close(); await browser.close(); process.exit(2); + } + + ok(befund.bloecke.length >= 5, `der Eintrag hat ${befund.bloecke.length} Bloecke`); + + /* Das Zeichen DARF schmal sein -- es ist die 34-px-Spalte. Alles andere + nicht: Ein Textblock in 34 px Breite ist der Fehler selbst. */ + const schmal = befund.bloecke.filter( + (b) => b.klasse !== "datei__zeichen" && b.breite < 80); + ok(schmal.length === 0, + `kein Textblock steckt in der Zeichenspalte (${schmal.length})`); + for (const s of schmal) console.log(` "${s.text}" nur ${s.breite} px breit (${s.klasse})`); + + const laeuft = befund.bloecke.filter((b) => b.ueberlauf > 1); + ok(laeuft.length === 0, `kein Block laeuft seitlich ueber (${laeuft.length})`); + for (const l of laeuft) console.log(` "${l.text}" ragt ${l.ueberlauf} px heraus`); + + ok(befund.ueber.length === 0, `nichts liegt aufeinander (${befund.ueber.length})`); + for (const u of befund.ueber) console.log(` ${u}`); + + ok(konsole.length === 0, `keine Fehler in der Konsole${konsole.length ? " -> " + konsole.join(" | ") : ""}`); + + if (process.env.SCHUSS === "ja") { + const { mkdirSync } = await import("node:fs"); + mkdirSync(join(process.cwd(), "schuss-dateienliste"), { recursive: true }); + await seite.locator("#liste").screenshot({ + path: join(process.cwd(), "schuss-dateienliste", `liste-${breite}.png`) }); + console.log(` Bild: schuss-dateienliste/liste-${breite}.png`); + } + await ctx.close(); +} +await browser.close(); + +console.log(`\n${geprueft} Pruefungen, ${fehler} Befunde`); +if (geprueft < BREITEN.length * 5) { + console.log("ERGEBNIS: KONNTE NICHT ARBEITEN (zu wenige Messungen)"); + process.exit(2); +} +console.log(fehler ? "ERGEBNIS: NICHT IN ORDNUNG" : "ERGEBNIS: IN ORDNUNG"); +process.exit(fehler ? 1 : 0); diff --git a/server/pruef-wissen-formular.mjs b/server/pruef-wissen-formular.mjs new file mode 100644 index 00000000..9854c75a --- /dev/null +++ b/server/pruef-wissen-formular.mjs @@ -0,0 +1,191 @@ +/* WISSEN: DAS FORMULAR "NEUE ANLEITUNG" (01.10.2026) + + Filipe zum Bildschirmfoto: "erstens sieht das richtig scheisse aus". + Zu sehen war: die Kategorie-Auswahl lag quer ueber "STUFE", "GERAET", + "VEROEFFENTLICHT AM" und den Tag-Knoepfen. Text auf Text. + + URSACHE (gemessen): aufgaben.css zwingt JEDES direkte Kind von + .neu__raster in zwei Rasterzeilen -- `grid-template-rows: 1fr 44px`. + Das ist richtig und wichtig, damit alle Eingabefelder auf derselben + Linie sitzen. Die Zelle "Hauptkategorie" enthaelt aber keine 44 px + hohe Eingabe, sondern .katwahl: sechs Gruppen mit zwanzig Knoepfen. + Die passen nicht in 44 px und laufen ueber alles darunter. + + Dieselbe Falle war schon einmal da und ist im CSS dokumentiert + ("EINE ZELLE, DIE AUFKLAPPT, PASST NICHT IN EINE FESTE ZEILE") -- + damals fuer die aufklappbare Personenliste. Diese Pruefung sorgt + dafuer, dass sie nicht ein drittes Mal zuschnappt. + + GEMESSEN WIRD NICHT "sieht gut aus", sondern: Ueberlappen sich zwei + Felder des Rasters? Das ist eine Zahl, und sie ist eindeutig. + + DREI AUSGAENGE: 0 in Ordnung / 1 Befunde / 2 konnte nicht arbeiten. */ +import { mkdtempSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +const ordner = mkdtempSync(join(tmpdir(), "ws-wissenform-")); +process.env.WORKSPACE_DB = join(ordner, "workspace.db"); + +const { eigenerPort } = await import("./helfer-port.mjs"); +const PORT = await eigenerPort(import.meta, "die Wissen-Formularpruefung"); +process.env.PORT = `${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 }); }; +await import("./index.js"); +const { notbremse } = await import("./helfer-notbremse.mjs"); +notbremse(420_000, "pruef-wissen-formular"); +await new Promise((r) => setTimeout(r, 1200)); +const BASIS = `http://127.0.0.1:${PORT}`; + +let geprueft = 0, fehler = 0; +const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; }; + +/* ---- eine Person, die ablegen darf ---------------------------------- */ +const { DatabaseSync } = await import("node:sqlite"); +const { scryptSync, randomBytes } = await import("node:crypto"); +await fetch(BASIS + "/workspace/api/anmelden", { + method: "POST", headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ rolle: "admin", code: "x" }) }).catch(() => {}); +for (let i = 0; i < 60; i++) { + try { + const p = new DatabaseSync(process.env.WORKSPACE_DB); + try { p.prepare("SELECT 1 FROM personen LIMIT 1").get(); break; } finally { p.close(); } + } catch { await new Promise((r) => setTimeout(r, 250)); } +} +const d = new DatabaseSync(process.env.WORKSPACE_DB); +const salt = randomBytes(16).toString("hex"); +const hash = scryptSync("CODE-DOGI-0001", 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("Filipe", "admin", hash, salt, 32768, new Date().toISOString()); +d.close(); + +const pw = await import("file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs"); +const browser = await pw.chromium.launch(); + +/* Die Breiten sind nicht geraten: 1165 ist die aus Filipes Bildschirmfoto, + 1280 der haeufigste Notebook-Wert, 900 und 412 die beiden Umbruchstufen + des Rasters (860 und 560) von oben bzw. unten her. */ +const BREITEN = [1440, 1280, 1165, 900, 412]; + +for (const breite of BREITEN) { + const ctx = await browser.newContext({ viewport: { width: breite, height: 1000 }, hasTouch: breite <= 860 }); + const seite = await ctx.newPage(); + const konsole = []; + seite.on("pageerror", (f) => konsole.push("Absturz: " + f.message)); + seite.on("console", (m) => { if (m.type() === "error") konsole.push("Konsole: " + m.text()); }); + + await seite.goto(BASIS + "/workspace/", { waitUntil: "networkidle" }); + 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: 25000 }); + await seite.goto(BASIS + "/workspace/wissen.html", { waitUntil: "networkidle" }); + await seite.waitForTimeout(800); + + console.log(`\n--- ${breite} px ---`); + const aufKnopf = seite.locator("#neu-auf"); + if (!(await aufKnopf.count()) || !(await aufKnopf.isVisible())) { + console.log(" KONNTE NICHT ARBEITEN: Knopf \"PDF hinzufuegen\" nicht sichtbar."); + await ctx.close(); await browser.close(); process.exit(2); + } + await aufKnopf.click(); + await seite.waitForTimeout(700); + + /* Eine Kategorie waehlen: Erst dann erscheint der Beschreibungstext + unter der Auswahl -- und genau der lag auf Filipes Bild quer ueber + der naechsten Ueberschrift. Ohne Auswahl waere die Pruefung blind + fuer den schlimmeren der beiden Faelle. */ + const ersteKat = seite.locator("#n-kategorie button").first(); + if (await ersteKat.count()) { await ersteKat.click(); await seite.waitForTimeout(400); } + + const befund = await seite.evaluate(() => { + const raster = document.querySelector("#neu-form .neu__raster"); + if (!raster) return { fehlt: true }; + const kinder = [...raster.children]; + const kasten = (el) => { const r = el.getBoundingClientRect(); + return { o: r.top, u: r.bottom, l: r.left, r: r.right, h: r.height }; }; + + /* 1. Ueberlappen sich zwei Felder? */ + const ueber = []; + for (let i = 0; i < kinder.length; i++) { + for (let j = i + 1; j < kinder.length; j++) { + const a = kasten(kinder[i]), b = kasten(kinder[j]); + const sen = Math.min(a.u, b.u) - Math.max(a.o, b.o); + const waa = Math.min(a.r, b.r) - Math.max(a.l, b.l); + if (sen > 1 && waa > 1) { + ueber.push({ a: i + 1, b: j + 1, px: Math.round(sen), + at: (kinder[i].textContent || "").trim().slice(0, 28), + bt: (kinder[j].textContent || "").trim().slice(0, 28) }); + } + } + } + + /* 2. Laeuft der INHALT der Kategorie-Auswahl aus ihr heraus? + ERST FALSCH GEMESSEN (01.10.2026): Der erste Versuch verglich + den KASTEN der Auswahl mit dem der Zelle -- und meldete gruen, + waehrend das Bildschirmfoto die Ueberlappung deutlich zeigte. + Der Kasten ist naemlich brav 44 px hoch; herausgelaufen ist + sein INHALT. Eine Pruefung, die den Kasten misst, kann das + nie sehen. Gemessen wird deshalb scrollHeight gegen + clientHeight und zusaetzlich das unterste Kind. */ + const wahl = raster.querySelector(".katwahl"); + let raus = null, ueberHang = null, hoch = null; + if (wahl) { + ueberHang = Math.round(wahl.scrollHeight - wahl.clientHeight); + const w = wahl.getBoundingClientRect(); + hoch = Math.round(w.height); + let tiefstes = w.bottom; + for (const kind of wahl.children) { + const k = kind.getBoundingClientRect(); + if (k.bottom > tiefstes) tiefstes = k.bottom; + } + raus = Math.round(tiefstes - w.bottom); + } + return { fehlt: false, anzahl: kinder.length, ueber, raus, ueberHang, hoch }; + }); + + if (befund.fehlt) { + console.log(" KONNTE NICHT ARBEITEN: Das Formularraster wurde nicht gefunden."); + await ctx.close(); await browser.close(); process.exit(2); + } + + ok(befund.anzahl >= 6, `das Raster hat ${befund.anzahl} Felder`); + ok(befund.raus !== null, `die Kategorie-Auswahl ist da (${befund.hoch} px hoch)`); + ok(befund.ueberHang !== null && befund.ueberHang <= 1, + `der Inhalt passt in die Auswahl (${befund.ueberHang} px Ueberhang)`); + ok(befund.raus !== null && befund.raus <= 1, + `nichts ragt unten heraus (${befund.raus} px)`); + ok(befund.ueber.length === 0, + `kein Feld liegt auf einem anderen (${befund.ueber.length} Ueberlappungen)`); + for (const u of befund.ueber) { + console.log(` Feld ${u.a} ("${u.at}") liegt ${u.px} px auf Feld ${u.b} ("${u.bt}")`); + } + ok(konsole.length === 0, `keine Fehler in der Konsole${konsole.length ? " -> " + konsole.join(" | ") : ""}`); + + /* Zum Hinsehen, wenn eine Zahl nicht reicht: SCHUSS=ja node pruef-wissen-formular.mjs */ + if (process.env.SCHUSS === "ja") { + const { mkdirSync } = await import("node:fs"); + mkdirSync(join(process.cwd(), "schuss-wissenform"), { recursive: true }); + await seite.locator("#neu-block").screenshot({ + path: join(process.cwd(), "schuss-wissenform", `form-${breite}.png`) }); + console.log(` Bild: schuss-wissenform/form-${breite}.png`); + } + + await ctx.close(); +} +await browser.close(); + +console.log(`\n${geprueft} Pruefungen, ${fehler} Befunde`); +/* Die Zahl gehoert in die BEDINGUNG: Eine Pruefung, die nichts gemessen + hat, darf nicht gruen sein. */ +if (geprueft < BREITEN.length * 6) { + console.log("ERGEBNIS: KONNTE NICHT ARBEITEN (zu wenige Messungen)"); + process.exit(2); +} +console.log(fehler ? "ERGEBNIS: NICHT IN ORDNUNG" : "ERGEBNIS: IN ORDNUNG"); +process.exit(fehler ? 1 : 0); diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html index 09c9850c..4af532d9 100644 --- a/workspace/anruf-probe.html +++ b/workspace/anruf-probe.html @@ -16,8 +16,8 @@ verdeckt. Gefunden hat es pruef-struktur. Richtig sind `gate.css` (die Farbwerte des Hauses) und `haus.css`. --> - - + + - +