ZWEI FEHLER, DIE FILIPE GESEHEN HAT.
1. DAS PROFILBILD LAG UEBER DER HALBEN SEITE. Als Patrick sein Bild
hochlud, zog sich ein roter Balken quer ueber die Startseite.
Ursache: Die CSS-Regel .wer__bild hat GEFEHLT. Das Element wurde in
kopf.js erzeugt, die Regel dazu nie geschrieben -- und ein <img> ohne
Groessenangabe nimmt seine natuerliche Groesse an, bei einem Handyfoto
also mehrere tausend Pixel. Dazu fehlte overflow: hidden am Traeger.
WARUM KEINE PRUEFUNG DAS GEFUNDEN HAT: Sie lud ein 1x1-Pixel-PNG hoch.
Klein, schnell, von Hand gebaut -- und voellig unfaehig, irgendetwas
zu ueberdecken. Die Pruefung war gruen, der Fehler war da, und gesehen
hat ihn der Nutzer. Sie arbeitet jetzt mit einem 1200x1200-Bild, also
in der Groesse, die wirklich hochgeladen wird, und misst danach: Bleibt
das Bild in seinem 28-px-Feld, ragt es irgendwo heraus, laeuft die
Seite ueber. Gegenprobe gemacht -- ohne die Regel meldet sie
1200x1200 in 28x28 und 918 px Ueberlauf.
2. ZWEI PERSONEN AUF EINEM BILDSCHIRM. Auf der Profilseite stand oben
"Profil: SpongBobSchwammKopf" und mittendrin "MEIN PROFIL: Dogfather".
Niemand konnte sagen, welche Angabe zu wem gehoert.
Es sind auch wirklich zwei verschiedene Dinge:
MEIN STECKBRIEF gehoert MIR -- Bild, ein Satz ueber mich, meine
Kanaele. Fuehre ich selbst.
CREATOR-PROFILE die BETREUUNGSAKTE eines anderen Menschen --
Ziele, 90-Tage-Plan, interne Notizen. Fuehren die
Betreuer.
Scouts, Manager und DogFather haben jetzt zwei getrennte Kacheln und
eine eigene Seite (steckbrief.html, serverseitig geschuetzt). Ein
Creator behaelt beides zusammen -- er hat nur eine Seite und sieht
dort ausschliesslich sich selbst. Die Logik liegt in einer eigenen
Datei statt hinten an profil.js: Sie gehoert der angemeldeten Person,
nicht der Akte.
ACHTZEHN KACHELN, ACHTZEHN FARBEN. Die neue Kachel haette sich ihren
Farbton mit den Creator-Profilen geteilt -- zwei Nachbarn in derselben
Farbe. Statt eine Farbe dazuzuerfinden, wurde tools/kachel-farben.mjs
fuer 18 Winkel neu gerechnet, samt der neuen Nachbarschaft im Raster.
Der kleinste Abstand zweier Nachbarn liegt weiterhin bei 100 Grad.
Gefunden hat das die Startseitenpruefung ("17 Farben auf 18 Kacheln").
Die Trennung wird jetzt fuer JEDE Rolle geprueft: welche Kacheln sie
sieht, dass auf der Akte kein fremder Steckbrief steht, und dass die
eigene Seite die richtige Person zeigt. Eine alte Pruefung, die das
Gegenteil verlangte, wurde ersetzt statt stehen gelassen.
Co-Authored-By: Claude Opus 5 <[email protected]>
150 lines
8.1 KiB
JavaScript
150 lines
8.1 KiB
JavaScript
/* Kurzpruefung: Steht die Kopfleiste auf dem Handy ueber -- und zwar
|
|
auf ALLEN Seiten oder nur auf der neuen? Beantwortet die Frage, ob
|
|
ich einen eigenen Fehler gebaut oder einen alten gefunden habe. */
|
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
|
|
const ordner = mkdtempSync(join(tmpdir(), "ws-kopf-"));
|
|
process.env.WORKSPACE_DB = join(ordner, "pruef.sqlite");
|
|
process.env.PORT = "4178";
|
|
process.env.SITE_ACCESS_SECRET = "lokaler-test";
|
|
process.env.SITE_ACCESS_CODE_DOGI = "DOGI-TEST-1";
|
|
process.env.SITE_ACCESS_CODE_VANVAN = "VANVAN-TEST-1";
|
|
process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
|
|
|
|
const express = (await import("express")).default;
|
|
const echteCookie = express.response.cookie;
|
|
express.response.cookie = function (n, w, o) {
|
|
return echteCookie.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:4178";
|
|
setTimeout(() => { console.log("ABBRUCH"); process.exit(1); }, 150_000).unref?.();
|
|
|
|
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");
|
|
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("Test-DogFather", "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();
|
|
|
|
/* Alle Seiten, die eine angemeldete Person zu sehen bekommt. Geprueft
|
|
wird auf beiden Groessen, weil die geteilten Klassen (.kopf-zeile,
|
|
.inhalt--breit, .leise) am 31.08.2026 von aufgaben.css nach start.css
|
|
gewandert sind -- faellt dabei etwas herunter, sieht man es hier. */
|
|
const SEITEN = ["start.html", "uebersicht.html", "aufgaben.html", "kalender.html",
|
|
"personen.html", "profil.html", "steckbrief.html", "wissen.html", "dateien.html",
|
|
"calls.html", "startcheck.html", "report.html", "scouting.html",
|
|
"automation.html", "content.html", "bereich.html?b=live"];
|
|
|
|
let fehler = 0;
|
|
for (const [breite, hoehe, wie] of [[1440, 1000, "Computer"], [390, 844, "Handy"]]) {
|
|
console.log(`\n=== ${wie} (${breite}px)`);
|
|
const kontext = await browser.newContext({ viewport: { width: breite, height: hoehe } });
|
|
const seite = await kontext.newPage();
|
|
const konsole = [];
|
|
seite.on("console", (m) => { if (m.type() === "error") konsole.push(m.text()); });
|
|
seite.on("pageerror", (e) => konsole.push("pageerror: " + e.message));
|
|
|
|
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 of SEITEN) {
|
|
konsole.length = 0;
|
|
await seite.goto(BASIS + "/workspace/" + s, { waitUntil: "networkidle" });
|
|
await seite.waitForTimeout(300);
|
|
const r = await seite.evaluate(() => ({
|
|
ueber: document.documentElement.scrollWidth - document.documentElement.clientWidth,
|
|
schuld: [...document.querySelectorAll("body *")]
|
|
.filter((e) => e.getBoundingClientRect().right > window.innerWidth + 1)
|
|
.map((e) => (e.tagName + "." + e.className).slice(0, 40)).slice(0, 4),
|
|
/* Die verschobenen Klassen muessen weiterhin greifen. .inhalt--breit
|
|
setzt 1240px -- kommt nur 880px an, ist die Regel verloren. */
|
|
breit: document.querySelector(".inhalt--breit")
|
|
? getComputedStyle(document.querySelector(".inhalt--breit")).maxWidth : null,
|
|
kopfzeile: document.querySelector(".kopf-zeile")
|
|
? getComputedStyle(document.querySelector(".kopf-zeile")).display : null,
|
|
abmelden: (() => {
|
|
const a = document.querySelector(".abmelden")?.getBoundingClientRect();
|
|
return a ? a.right <= window.innerWidth && a.left >= 0 : null;
|
|
})(),
|
|
/* Der Schriftzug oben links wird von kopf.js in Rang aufgeteilt.
|
|
Auf den Unterseiten ist der vordere Teil ein VERWEIS zurück zur
|
|
Startseite -- der muss die Aufteilung überleben, sonst ist der
|
|
Rückweg weg. Genau das wäre beim Zerlegen von Textknoten leicht
|
|
zu übersehen. */
|
|
markeHaupt: document.querySelector(".kopfleiste .marke__haupt")?.textContent
|
|
.replace(/ /g, " ") || null,
|
|
markeLeise: document.querySelector(".kopfleiste .marke__leise")?.textContent
|
|
.replace(/ /g, " ") || null,
|
|
markeRaute: document.querySelectorAll(".kopfleiste .marke__raute").length,
|
|
/* Ist der vordere Teil auf einer Unterseite noch ein klickbarer
|
|
Verweis auf die Startseite? */
|
|
rueckweg: document.querySelector(".kopfleiste .marke__haupt")?.tagName === "A"
|
|
? document.querySelector(".kopfleiste .marke__haupt").getAttribute("href") : null,
|
|
/* Jede Unterseite trägt Zeichen und Farbton ihrer Kachel. Beides
|
|
kommt aus bereiche.js, derselben Liste wie die Kachel -- fehlt
|
|
eins davon, ist die Zuordnung gerissen und die Seite sieht
|
|
wieder aus wie jede andere. */
|
|
ton: document.body.dataset.ton || null,
|
|
/* Jede Seite hat ihre eigene Bühne aus Filipes Bildern. Geprüft
|
|
wird nicht das Merkmal, sondern das TATSÄCHLICH geladene Bild --
|
|
ein data-buehne ohne passende CSS-Regel wäre still wirkungslos
|
|
und niemand würde es merken. */
|
|
buehne: document.body.dataset.buehne || null,
|
|
buehneBild: (getComputedStyle(document.body, "::before").backgroundImage
|
|
.match(/buehne-([a-z]+)-(breit|schmal)/) || [])[1] || null,
|
|
kopfZeichen: document.querySelectorAll(".kopf-zeichen__svg path").length,
|
|
kopfZeichenFarbe: document.querySelector(".kopf-zeichen__svg")
|
|
? getComputedStyle(document.querySelector(".kopf-zeichen__svg")).stroke : null,
|
|
wasserzeichen: document.querySelectorAll(".kopf-wasserzeichen__svg path").length,
|
|
}));
|
|
const istStart = s === "start.html";
|
|
const markeGut = r.markeHaupt && r.markeLeise && r.markeRaute === 1
|
|
&& (istStart || r.rueckweg === "start.html");
|
|
/* Auf der Startseite gibt es keinen Seitenkopf -- dort sind die
|
|
Kacheln selbst der Kopf. Überall sonst ist er Pflicht. */
|
|
const kopfGut = istStart
|
|
|| (r.ton !== null && r.kopfZeichen > 0 && r.wasserzeichen > 0);
|
|
/* Die Bühne muss gesetzt sein UND als Bild wirklich ankommen. */
|
|
const buehneGut = istStart
|
|
? r.buehneBild === "studio"
|
|
: (r.buehne !== null && r.buehneBild === r.buehne);
|
|
const gut = r.ueber <= 0 && konsole.length === 0 && r.abmelden !== false
|
|
&& markeGut && kopfGut && buehneGut
|
|
&& (r.breit === null || r.breit === "1240px")
|
|
&& (r.kopfzeile === null || r.kopfzeile === "flex");
|
|
if (!gut) fehler++;
|
|
console.log(` ${gut ? "ok " : "FEHL"} ${String(s).padEnd(20)}`
|
|
+ `über=${String(r.ueber).padStart(3)}px abmelden=${r.abmelden}`
|
|
+ ` marke=${r.markeHaupt ? `"${r.markeHaupt}"◆"${r.markeLeise}"` : "FEHLT"}`
|
|
+ `${istStart ? "" : ` zurück=${r.rueckweg || "FEHLT"}`}`
|
|
+ `${istStart ? "" : ` ton=${r.ton || "FEHLT"} zeichen=${r.kopfZeichen}/${r.wasserzeichen}`}`
|
|
+ ` bühne=${r.buehneBild || "FEHLT"}${r.buehne && r.buehneBild !== r.buehne ? ` (gewollt: ${r.buehne})` : ""}`
|
|
+ ` breit=${r.breit}`
|
|
+ (konsole.length ? " KONSOLE: " + konsole.join(" | ") : "")
|
|
+ (r.schuld.length ? " -> " + r.schuld.join(" | ") : ""));
|
|
}
|
|
await kontext.close();
|
|
}
|
|
await browser.close();
|
|
console.log(`\n${fehler === 0 ? "Alles in Ordnung." : fehler + " Seite(n) fehlerhaft."}`);
|
|
try { rmSync(ordner, { recursive: true, force: true }); } catch {}
|
|
process.exit(fehler === 0 ? 0 : 1);
|