/* Prueft die Formularzeilen auf allen Seiten.
Beanstandet am 01.09.2026: "das sieht scheisse aus, so verzogen".
Gemeint war die Zeile im Kalender -- "Art" schmal, "Beginn" breit,
"Dauer (Minuten)" zweizeilig umgebrochen, alle Felder auf
verschiedenen Hoehen.
Ursache war eine Regel, die nur fuer Kinder mit der Klasse .feld galt.
Der Kalender benutzt schlichte
ohne Klasse; die fielen hindurch,
bekamen von zwoelf Rasterspalten je EINE und wurden nur so breit, wie
ihr Inhalt sie zwang.
Geprueft wird deshalb nicht "sieht gut aus", sondern messbar:
* alle Eingabefelder einer Zeile gleich hoch
* ihre Unterkanten auf einer Linie (sonst haengt eines tiefer)
* kein Feld absurd schmal
* keine Beschriftung mehrzeilig */
import { mkdtempSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
const ordner = mkdtempSync(join(tmpdir(), "ws-form-"));
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
process.env.PORT = "4203";
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 }); };
await import("./index.js");
await new Promise((r) => setTimeout(r, 700));
const BASIS = "http://127.0.0.1:4203";
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 250_000).unref?.();
let fehler = 0;
const ok = (b, t) => { console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
await fetch(BASIS + "/workspace/api/anmelden", {
method: "POST", headers: { "Content-Type": "application/json" },
body: JSON.stringify({ rolle: "admin", code: "x" }) }).catch(() => {});
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 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 { chromium } = await import(
"file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
const browser = await chromium.launch();
/* Seite, Knopf zum Aufklappen des Formulars. */
const FORMULARE = [
["kalender.html", "#neu-oeffnen"],
["aufgaben.html", "#neu-oeffnen"],
["personen.html", "#neu-oeffnen"],
["bereich.html?b=live", "#neu-oeffnen"],
["scouting.html", "#neu-oeffnen"],
];
for (const [breite, wie] of [[1280, "Computer"], [390, "Handy"]]) {
console.log(`\n=== ${wie} (${breite}px)`);
const kontext = await browser.newContext({ viewport: { width: breite, height: 1000 } });
const seite = await kontext.newPage();
await seite.goto(BASIS + "/workspace/", { waitUntil: "domcontentloaded" });
await seite.click('.rolle[data-rolle="admin"]');
await seite.fill("#code", "CODE-DOGI-0001");
await seite.click("button[type=submit]");
await seite.waitForURL("**/start.html", { timeout: 15000 });
for (const [s, knopf] of FORMULARE) {
await seite.goto(BASIS + "/workspace/" + s, { waitUntil: "networkidle" });
await seite.waitForTimeout(700);
const da = await seite.locator(knopf).count();
if (!da) { console.log(` -- ${s}: kein Formularknopf`); continue; }
await seite.click(knopf).catch(() => {});
await seite.waitForTimeout(450);
const r = await seite.evaluate(() => {
const raster = document.querySelector(".neu__raster");
if (!raster) return null;
const felder = [...raster.children].filter((k) => k.offsetParent !== null);
const eingaben = felder
.map((f) => f.querySelector("input, select, textarea"))
.filter(Boolean)
.map((e) => e.getBoundingClientRect());
const schilder = felder
.map((f) => f.querySelector(".feld-schild"))
.filter(Boolean);
/* Mehrzeilige Beschriftung: tatsaechliche Hoehe gegen eine Zeile. */
const mehrzeilig = schilder.filter((s2) => {
const zeile = parseFloat(getComputedStyle(s2).lineHeight) || 16;
return s2.getBoundingClientRect().height > zeile * 1.6;
}).map((s2) => s2.textContent.trim());
return {
anzahl: eingaben.length,
hoehen: eingaben.map((e) => Math.round(e.height)),
/* Auf 3 px genau zusammengefasst: Unterkanten liegen durch
Teilpixel-Rundung selten exakt gleich, obwohl sie auf einer
Linie sitzen. Ein erster Anlauf meldete deshalb "3 Unterkanten"
bei drei Feldern derselben Reihe -- ein Messfehler, kein
Gestaltungsfehler. */
unterkanten: eingaben.map((e) => Math.round(e.bottom / 3) * 3),
breiten: eingaben.map((e) => Math.round(e.width)),
einzeln: felder.map((f) => { const e=f.querySelector("input, select, textarea"); if(!e) return null; const r2=e.getBoundingClientRect(); const fr=f.getBoundingClientRect(); return { id:e.id, tag:e.tagName, top:Math.round(r2.top), bottom:Math.round(r2.bottom), h:Math.round(r2.height), feldTop:Math.round(fr.top), feldH:Math.round(fr.height) }; }).filter(Boolean),
mehrzeilig,
};
});
if (!r || r.anzahl < 2) { console.log(` -- ${s}: kein Raster mit mehreren Feldern`); continue; }
const hoehenGleich = new Set(r.hoehen).size === 1;
/* Unterkanten auf einer Linie -- auf dem Handy stehen die Felder
untereinander, dort gilt das natuerlich nicht. */
const reihen = new Set(r.unterkanten).size;
const buendig = breite < 700 ? true : reihen <= Math.ceil(r.anzahl / 3);
const nichtWinzig = Math.min(...r.breiten) >= 90;
const gut = hoehenGleich && buendig && nichtWinzig && !r.mehrzeilig.length;
if (!gut) fehler++;
console.log(` ${gut ? "ok " : "FEHL"} ${String(s).padEnd(20)}`
+ `${r.anzahl} Felder Höhen=${[...new Set(r.hoehen)].join("/")} `
+ `Breiten=${Math.min(...r.breiten)}–${Math.max(...r.breiten)}`
+ (hoehenGleich ? "" : " UNGLEICH HOCH")
+ (buendig ? "" : ` NICHT BÜNDIG (Unterkanten ${r.unterkanten.join(",")})`)
+ (nichtWinzig ? "" : " ZU SCHMAL")
+ (r.mehrzeilig.length ? ` MEHRZEILIG: ${r.mehrzeilig.join(", ")}` : "")
+ (gut ? "" : " | " + r.einzeln.map((e) => `${e.id}(${e.tag}) Feld:${e.feldTop}+${e.feldH} Eingabe:${e.top}..${e.bottom}`).join(" ")));
}
await kontext.close();
}
await browser.close();
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* egal */ }
console.log(fehler ? `\n${fehler} Formular(e) fehlerhaft.` : "\nAlles in Ordnung.");
process.exit(fehler ? 1 : 0);