diff --git a/.gitignore b/.gitignore index eca1ea19..029eebae 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ server/node_modules/ # kollidieren und den Deploy blockieren -- genau das ist gerade passiert. server/tmp-lokal.mjs server/package-lock.json +bild-*.png diff --git a/pruef-uebersicht.mjs b/pruef-uebersicht.mjs new file mode 100644 index 00000000..00272278 --- /dev/null +++ b/pruef-uebersicht.mjs @@ -0,0 +1,245 @@ +/* ===================================================================== + pruef-uebersicht.mjs — die neue Startansicht der Verwaltung + + Zwei Dinge werden hier geprüft, und beide haben eine Vorgeschichte: + + 1. STEHEN DIE REITER WIRKLICH UNTER DEM TITEL? + Das ist der ausdrückliche Wunsch vom 23.08.2026. Es wäre bequem, + dafür das Vorhandensein einer CSS-Regel zu prüfen — das beweist + aber nichts. Am 22.08.2026 waren 37 von 37 Prüfungen grün, während + jede Aufgabenzeile im Browser verkehrt herum stand: Die Regeln + waren da, das Ergebnis war falsch. Deshalb wird hier die + tatsächliche Lage im Fenster gemessen — der Reiter MUSS weiter + unten liegen als der Titel. + + 2. FÜHREN DIE KACHELN AUCH WIRKLICH IRGENDWOHIN? + Eine Kachel, die aussieht wie ein Knopf und keiner ist, ist + schlimmer als gar kein Knopf. Also wird geklickt und geprüft, dass + danach die richtige Ansicht offen ist. + ===================================================================== */ +import { chromium } from "playwright"; +import { createServer } from "node:http"; +import { readFileSync, existsSync } from "node:fs"; +import { join, extname } from "node:path"; + +const W = process.cwd(); +const T = { ".html": "text/html", ".css": "text/css", ".js": "text/javascript", + ".png": "image/png", ".jpg": "image/jpeg", ".svg": "image/svg+xml", + ".webp": "image/webp", ".ico": "image/x-icon", ".json": "application/json" }; +const srv = createServer((q, r) => { + const p = join(W, decodeURIComponent(q.url.split("?")[0])); + if (existsSync(p) && !p.endsWith("/")) { + r.writeHead(200, { "content-type": T[extname(p)] || "text/plain" }); + return r.end(readFileSync(p)); + } + r.writeHead(404).end(""); +}); +await new Promise((r) => srv.listen(4337, r)); + +const ergebnisse = []; +function pruefe(name, ok, zusatz) { + ergebnisse.push({ name, ok }); + console.log((ok ? " ok " : " FEHL ") + name + (zusatz !== undefined ? " -> " + zusatz : "")); +} + +const vorTagen = (n) => new Date(Date.now() - n * 864e5).toISOString(); + +/* Absichtlich unbequeme Testdaten: eine Anfrage, die seit acht Tagen + liegt (muss rot werden), eine Nachricht seit drei Tagen (gelb), und + ein offener Widerruf von heute (muss trotzdem rot sein, weil eine + gesetzliche Frist läuft). Ein Datensatz, in dem alles in Ordnung ist, + würde die Warnstufen gar nicht erst auslösen. */ +const UEBERSICHT = { + ok: true, + beiMir: { + anfragen: { n: 3, aeltestes: vorTagen(8) }, + nachrichten: { n: 2, aeltestes: vorTagen(3) }, + wuensche: { n: 0, aeltestes: null }, + widerrufe: { n: 1, aeltestes: vorTagen(0) }, + projekte: 2, + }, + beiKunde: { + projekte: 1, + aufgaben: 4, + zahlungen: { n: 2, summe: 120000, aeltestes: vorTagen(5) }, + zahlungenText: "1.200,00 €", + }, + geld: { + monat: { n: 3, brutto: 250000, netto: 241000, bruttoText: "2.500,00 €", nettoText: "2.410,00 €" }, + gesamt: { n: 11, brutto: 980000, bruttoText: "9.800,00 €" }, + gebuehrenText: "90,00 €", + }, + projekte: [ + { id: "p1", nummer: "P-2026-001", titel: "Website für Musterbau", status: "bau", + wartet_auf: "dogfather", richttermin: "2026-09-15", kunde_name: "Anna Muster", + aufg_gesamt: 20, aufg_fertig: 13 }, + { id: "p2", nummer: "P-2026-002", titel: "Shop für Beispiel GmbH", status: "bau", + wartet_auf: "kunde", richttermin: null, kunde_name: "Bert Beispiel", + aufg_gesamt: 18, aufg_fertig: 4 }, + ], + verlauf: [ + { bereich: "projekt", aktion: "Aufgabe erledigt: Startseite gebaut", wer: "dogfather", erstellt_am: vorTagen(0) }, + { bereich: "zahlung", aktion: "Zahlung eingegangen", wer: "paypal", erstellt_am: vorTagen(1) }, + ], + bestand: { kunden: 5, projekte: 2, anfragenGesamt: 9 }, + paypal: { eingerichtet: true, live: true }, +}; + +const browser = await chromium.launch(); + +for (const [wo, breite, hoehe] of [["Computer", 1440, 950], ["Handy", 390, 844]]) { + const seite = await browser.newPage({ viewport: { width: breite, height: hoehe } }); + const fehlerImProtokoll = []; + seite.on("pageerror", (e) => fehlerImProtokoll.push(e.message)); + + await seite.route("**/webdesign/admin/**", (route) => { + const u = new URL(route.request().url()).pathname; + if (u.endsWith("/admin/uebersicht")) return route.fulfill({ json: UEBERSICHT }); + if (u.includes("/admin/postfach-zaehler")) return route.fulfill({ json: { ok: true, ungelesen: 2 } }); + if (u.includes("/admin/anfragen")) return route.fulfill({ json: { ok: true, anfragen: [] } }); + if (u.includes("/admin/kunden")) return route.fulfill({ json: { ok: true, kunden: [] } }); + if (u.includes("/admin/projekte")) return route.fulfill({ json: { ok: true, projekte: [] } }); + if (u.includes("/admin/zahlungen")) return route.fulfill({ json: { ok: true, zahlungen: [] } }); + if (u.includes("/admin/postfach")) return route.fulfill({ json: { ok: true, ungelesen: 2, verlaeufe: [] } }); + return route.fulfill({ json: { ok: true } }); + }); + await seite.route("**/auth/**", (r) => r.fulfill({ json: { ok: true, user: { name: "Test" } } })); + await seite.addInitScript(() => { + sessionStorage.setItem("wd-vw-token", "t"); + sessionStorage.setItem("wd-sitzung-aktiv", "1"); + }); + + await seite.goto("http://127.0.0.1:4337/webdesign/verwaltung.html"); + await seite.waitForTimeout(900); + console.log("\n=== " + wo + " ==="); + + /* ---- 1. Die Verwaltung startet in der Übersicht ---- */ + const titel = await seite.locator("#vw-titel").innerText(); + pruefe(wo + ": die Verwaltung öffnet in der Übersicht", titel.trim() === "Übersicht", titel); + pruefe(wo + ": das Cockpit ist sichtbar", + await seite.locator("#vw-ub-inhalt").isVisible()); + pruefe(wo + ": die Anfragenliste liegt NICHT darunter", + !(await seite.locator("#vw-liste").isVisible())); + + /* ---- 2. Reiter UNTER dem Titel — gemessen, nicht behauptet ---- */ + const lage = await seite.evaluate(() => { + const t = document.getElementById("vw-titel").getBoundingClientRect(); + const r = document.querySelector(".vw-tabs").getBoundingClientRect(); + return { titelUnten: t.bottom, titelLinks: t.left, reiterOben: r.top, reiterLinks: r.left }; + }); + pruefe(wo + ": die Reiter beginnen UNTERHALB des Titels", + lage.reiterOben >= lage.titelUnten - 1, + "Titel endet bei " + Math.round(lage.titelUnten) + ", Reiter beginnen bei " + Math.round(lage.reiterOben)); + /* Zusatzbedingung, sonst wäre "darunter" auch erfüllt, wenn die Reiter + weit nach rechts eingerückt stünden statt am Seitenrand zu beginnen. */ + pruefe(wo + ": die Reiter beginnen am selben linken Rand wie der Titel", + Math.abs(lage.reiterLinks - lage.titelLinks) < 3, + "Titel " + Math.round(lage.titelLinks) + " vs. Reiter " + Math.round(lage.reiterLinks)); + + /* ---- 2b. Groessenverhaeltnisse ---- + Eine Zwischenueberschrift darf nicht groesser sein als die Zahl, um + die es geht. Genau das war der Fall, solange die Regel .wd h2 die + eigene ueberstimmte: 44px Ueberschrift ueber 33px Zahl -- die Seite + las sich wie ein Plakat statt wie ein Arbeitsplatz. */ + const groessen = await seite.evaluate(() => ({ + h: parseFloat(getComputedStyle(document.querySelector(".vw-ub-h")).fontSize), + zahl: parseFloat(getComputedStyle(document.querySelector(".vw-ub-zahl")).fontSize), + })); + pruefe(wo + ": die Zwischenueberschrift ist kleiner als die Zahl darunter", + groessen.h < groessen.zahl, + "Ueberschrift " + groessen.h + "px, Zahl " + groessen.zahl + "px"); + + /* ---- 3. Inhalt der Kacheln ---- */ + const kacheln = await seite.locator("#vw-ub-beimir .vw-ub-kachel").allInnerTexts(); + pruefe(wo + ": alle fünf Kacheln 'bei mir' sind da (inkl. Widerruf)", + kacheln.length === 5, kacheln.length); + pruefe(wo + ": die drei offenen Anfragen stehen drin", + kacheln.some((k) => k.includes("3") && k.includes("offene Anfragen"))); + pruefe(wo + ": das Alter steht als TEXT dabei, nicht nur als Farbe", + kacheln.some((k) => k.includes("seit 8 Tagen")), + kacheln.find((k) => k.includes("Anfragen"))); + + /* ---- 4. Die Warnstufen greifen wirklich ---- */ + const stufen = await seite.evaluate(() => { + const raus = {}; + document.querySelectorAll("#vw-ub-beimir .vw-ub-kachel").forEach((k) => { + const t = (k.querySelector(".vw-ub-titel") || {}).textContent || ""; + raus[t] = k.className; + }); + return raus; + }); + pruefe(wo + ": 8 Tage alte Anfrage ist rot", + /vw-ub-kachel--rot/.test(stufen["offene Anfragen"] || ""), stufen["offene Anfragen"]); + pruefe(wo + ": 3 Tage alte Nachricht ist nur gelb, nicht rot", + /--warn/.test(stufen["ungelesene Nachrichten"] || "") && + !/--rot/.test(stufen["ungelesene Nachrichten"] || ""), stufen["ungelesene Nachrichten"]); + pruefe(wo + ": ein Widerruf von HEUTE ist trotzdem rot (Frist läuft)", + /--rot/.test(stufen["Widerrufe unbearbeitet"] || ""), stufen["Widerrufe unbearbeitet"]); + pruefe(wo + ": eine Null-Kachel nimmt sich zurück statt zu warnen", + /--leer/.test(stufen["Änderungswünsche zu prüfen"] || ""), + stufen["Änderungswünsche zu prüfen"]); + + /* ---- 5. Geld ---- */ + const geld = (await seite.locator("#vw-ub-geld").innerText()); + pruefe(wo + ": der Monatsumsatz steht da", geld.includes("2.500,00")); + pruefe(wo + ": die PayPal-Gebühr wird offen ausgewiesen", geld.includes("90,00")); + pruefe(wo + ": das tatsächlich angekommene Netto steht dabei", geld.includes("2.410,00")); + + /* ---- 6. Projekte mit Fortschritt ---- */ + const proj = await seite.locator("#vw-ub-projekte .vw-ub-projekt").allInnerTexts(); + pruefe(wo + ": beide laufenden Projekte erscheinen", proj.length === 2, proj.length); + pruefe(wo + ": der Fortschritt steht als Zahl da, nicht nur als Balken", + proj[0].includes("13 von 20") && proj[0].includes("65"), proj[0].replace(/\n/g, " | ")); + pruefe(wo + ": man sieht, wer am Zug ist", + proj[0].includes("du bist dran") && proj[1].includes("wartet auf Kunden")); + const balken = await seite.evaluate(() => + document.querySelector(".vw-ub-balken-voll").style.width); + pruefe(wo + ": der Balken zeigt denselben Wert wie der Text", balken === "65%", balken); + + /* ---- 7. Die Kacheln sind echte Knöpfe und führen irgendwohin ---- */ + const istKnopf = await seite.evaluate(() => + document.querySelector("#vw-ub-beimir .vw-ub-kachel").tagName); + pruefe(wo + ": eine Kachel ist ein + + @@ -648,17 +838,56 @@ -
+ +
+
Wird geladen…
+ +
+ + + + + + -
Wird geladen…
+