Filipe, zum vierten Mal: "wenn ich in eine kategorie rein gehe und dan zurueck geh die hauptseite immer wieder ganz hoch ... ohne dass die seite hoch scrollt ODER NEU LAEDT." DAS "ODER NEU LAEDT" WAR DER HINWEIS, und ich habe ihn zweimal ueberlesen. kopf.js laedt die Seite selbst neu, sobald der Browser sie aus seinem Rueckwaerts-Speicher holt -- damit keine veralteten Zahlen dastehen. Nach einem Neuladen heisst die Navigationsart aber "reload", nicht "back_forward". UND DIE EIGENTLICHE BOSHEIT stand in einer Zeile, die ich selbst geschrieben habe: Wurde eine Ankunft nicht als Zurueck erkannt, wurde die gemerkte Stelle GELOESCHT. Ein einziges Neuladen reichte -- danach half auch das naechste Zurueckgehen nicht mehr. Deshalb "geht es immer noch nicht", obwohl ich es dreimal fuer behoben hielt. WARUM ES IN JEDER MESSUNG FUNKTIONIERT HAT: kopf.js haelt einen Ereignisstrom offen, und der sperrt den Rueckwaerts-Speicher aus. `persisted` bleibt hier immer false, die Neulade-Zeile lief nie. Auf einem echten Handy greift er sehr wohl. Man muss den Weg gehen, den der Mensch geht -- und wenn man ihn nicht nachstellen kann, baut man gegen ALLE Wege robust statt gegen einen. FUENF AENDERUNGEN: Die Stelle wird LAUFEND gemerkt (gedrosselt auf 250 ms), nicht nur beim Klicken und Verlassen. Deckt auch die Glocke, eine Benachrichtigung und einen Absturz des Reiters ab. Nichts wird mehr geloescht. Die Stelle verfaellt von selbst nach einer Stunde. Wiederhergestellt wird jetzt auch bei "reload" und bei "navigate mit Herkunft aus diesem Haus" -- nicht nur bei "back_forward". Vor dem Neuladen aus dem Rueckwaerts-Speicher wird die Stelle samt Merker festgehalten. Der Pfeil in der Kopfleiste setzt den Merker jetzt fuer BEIDE seiner Wege. Er nimmt history.back() nur, wenn `document.referrer` da ist -- sonst location.assign(), und das ist fuer den Browser ein Hingehen. Hier stand "der braucht nichts"; fuer den zweiten Weg stimmte das nie. Ein Neuanfang faengt weiterhin oben an: keine Herkunft, kein Merker -- also vom Startbildschirm, aus einer Benachrichtigung, ueber die Adresszeile. Beim Bauen fast hineingelaufen: `START` steht in einem anderen Block derselben Datei und waere an der neuen Stelle ein Absturz gewesen -- dieselbe Falle wie bei `$` am 21.09. Jetzt gibt der erste Block ihn einmal bekannt, statt ihn abzuschreiben. Geprueft: pruef-stelle 15/0 (war 9) -- Brotkrume (navigate), Browser-Zurueck (back_forward), NEULADEN (reload), Neuanfang oben, und dass eine fremde Ankunft die Stelle nicht wegwirft. Dazu pruef-sprung 43/0, pruef-start-ansicht 153/0, pruef-css-klassen 30/0.
321 lines
14 KiB
JavaScript
321 lines
14 KiB
JavaScript
/* =====================================================================
|
|
pruef-stelle.mjs — Bleibt man, wo man war? (21.09.2026)
|
|
|
|
Filipe, zum vierten Mal: "muss ich jedes mal wenn ich von einer seite
|
|
zurück scrolle immer wieder runter scrollen um weiter zu schauen und
|
|
das nervt, wieso bleibt es nicht sofort da wo ich dran war anstatt
|
|
dass ich immer wieder ganz runter scrollen muss."
|
|
|
|
---------------------------------------------------------------------
|
|
WARUM ES DREIMAL ALS BEHOBEN GALT UND NIE BEHOBEN WAR
|
|
|
|
Die Wiederherstellung in kopf.js gibt es seit dem 20.09. Sie ist
|
|
sorgfaeltig gebaut -- sie merkt sich die Stelle je Adresse, wartet,
|
|
bis die nachgeladene Seite hoch genug ist, und hoert auf, sobald der
|
|
Mensch selbst scrollt.
|
|
|
|
Sie lief nur fast nie. Denn sie lief NUR bei
|
|
`navigation.type === "back_forward"`, also beim Zurueck-Knopf des
|
|
Browsers. Im Haus geht man aber anders zurueck: ueber die Kopfzeile,
|
|
und dort steht ein ganz normales `<a href="start.html">`. Fuer den
|
|
Browser ist das eine Fahrt VORWAERTS. Die Bedingung war falsch, und
|
|
der `else`-Zweig loeschte die eben gemerkte Stelle sogar noch.
|
|
|
|
IM KOPF DERSELBEN DATEI STAND DIE LOESUNG ALS ABSICHT: "Ein eigener
|
|
Merker faengt den Fall ab, dass der Zurueck-Knopf der Kopfleiste
|
|
benutzt wurde." Gebaut wurde er nie. Ein Kommentar, der eine Absicht
|
|
beschreibt, erfuellt sie nicht.
|
|
|
|
UND DESHALB HAT ES IN JEDEM TEST FUNKTIONIERT: Wer mit
|
|
`page.goBack()` prueft, prueft den einen Weg, den niemand geht.
|
|
Gemessen am 21.09.: ueber `goBack()` sauber wiederhergestellt,
|
|
ueber den Link in der Kopfzeile oben gelandet. Man muss den Weg
|
|
gehen, den der Mensch geht.
|
|
|
|
---------------------------------------------------------------------
|
|
WAS HIER GEMESSEN WIRD
|
|
|
|
1. Ueber den Link in der Kopfzeile zurueck -> Stelle bleibt
|
|
2. Ueber den Zurueck-Knopf des Browsers -> Stelle bleibt
|
|
3. GEGENPROBE: Eine Seite frisch oeffnen -> faengt oben an
|
|
|
|
Die dritte ist nicht Zierde. Ohne sie waere die Pruefung auch dann
|
|
gruen, wenn die Seite IMMER an die alte Stelle springt -- und das
|
|
waere schlimmer als das Problem: Wer eine Kachel antippt, will oben
|
|
anfangen.
|
|
|
|
Aufrufen mit: node server/pruef-stelle.mjs
|
|
===================================================================== */
|
|
|
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { request as httpAnfrage } from "node:http";
|
|
import { eigenerPort } from "./helfer-port.mjs";
|
|
|
|
const PORT = await eigenerPort(import.meta, "pruef-stelle");
|
|
const ordner = mkdtempSync(join(tmpdir(), "ws-stelle-"));
|
|
process.env.WORKSPACE_DB = join(ordner, "workspace.db");
|
|
process.env.PORT = String(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 });
|
|
};
|
|
import { notbremse } from "./helfer-notbremse.mjs";
|
|
await import("./index.js");
|
|
notbremse(180_000, "pruef-stelle");
|
|
await new Promise((r) => setTimeout(r, 900));
|
|
|
|
process.on("uncaughtException", (f) => { console.error("ABSTURZ:", f); process.exit(7); });
|
|
|
|
let fehler = 0, geprueft = 0;
|
|
const melde = (t) => console.log(t);
|
|
const ok = (b, t) => { geprueft++; console.log((b ? " ok " : " FEHL ") + t); if (!b) fehler++; };
|
|
|
|
const CREW = "crew.dogfather-universe.com";
|
|
|
|
function roh(pfad, koerper) {
|
|
return new Promise((fertig, schief) => {
|
|
const a = httpAnfrage({
|
|
host: "127.0.0.1", port: PORT, path: pfad, method: koerper ? "POST" : "GET",
|
|
headers: { Host: CREW, ...(koerper ? { "Content-Type": "application/json" } : {}) },
|
|
}, (antwort) => {
|
|
let text = "";
|
|
antwort.on("data", (s) => { text += s; });
|
|
antwort.on("end", () => fertig({ code: antwort.statusCode, text, kopf: antwort.headers }));
|
|
});
|
|
a.on("error", schief);
|
|
if (koerper) a.write(JSON.stringify(koerper));
|
|
a.end();
|
|
});
|
|
}
|
|
|
|
const { DatabaseSync } = await import("node:sqlite");
|
|
const { scryptSync, randomBytes } = await import("node:crypto");
|
|
{
|
|
const d = new DatabaseSync(process.env.WORKSPACE_DB);
|
|
const salz = randomBytes(16).toString("hex");
|
|
const hash = scryptSync("CODE-DOGI-0001", salz, 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, salz, 32768, new Date().toISOString());
|
|
d.close();
|
|
}
|
|
|
|
let an = await roh("/workspace/api/anmelden", { rolle: "admin", code: "CODE-DOGI-0001" });
|
|
if (an.code === 400 && an.text.includes("alter_offen")) {
|
|
an = await roh("/workspace/api/anmelden", { rolle: "admin", code: "CODE-DOGI-0001", alter_ok: true });
|
|
}
|
|
const keks = [].concat(an.kopf["set-cookie"] || []).map((z) => z.split(";")[0]).join("; ");
|
|
ok(!!keks, "angemeldet");
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
let pw = null;
|
|
try {
|
|
pw = await import("file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
|
|
} catch { /* dritter Ausgang */ }
|
|
|
|
if (!pw) {
|
|
melde(" -- Playwright ist nicht da -- im Browser wurde NICHT nachgesehen.");
|
|
melde(" Das ist kein Befund, aber auch kein bestandener Test.");
|
|
melde(`\n${geprueft} Pruefungen, ${fehler} Fehler`);
|
|
process.exit(fehler ? 1 : 0);
|
|
}
|
|
|
|
const browser = await pw.chromium.launch();
|
|
|
|
/** Scrollt auf der Startseite nach unten, oeffnet ein Brett und kommt
|
|
* auf dem uebergebenen Weg zurueck. Gibt zurueck, wo man landet. */
|
|
async function hinUndZurueck(wieZurueck) {
|
|
const ctx = await browser.newContext({ viewport: { width: 1280, height: 800 } });
|
|
await ctx.addCookies(keks.split("; ").filter(Boolean).map((z) => {
|
|
const [name, ...rest] = z.split("=");
|
|
return { name, value: rest.join("="), domain: "127.0.0.1", path: "/" };
|
|
}));
|
|
const seite = await ctx.newPage();
|
|
try {
|
|
await seite.goto(`http://127.0.0.1:${PORT}/workspace/start.html`, { waitUntil: "networkidle" });
|
|
await seite.waitForTimeout(1700);
|
|
const hoch = await seite.evaluate(() => document.documentElement.scrollHeight);
|
|
/* DIE HOEHE GEHOERT IN DIE BEDINGUNG. Auf einer Seite, die kuerzer
|
|
ist als das Fenster, kann man gar nicht scrollen -- dann misst
|
|
alles Folgende nichts. */
|
|
if (hoch < 1600) return { zuKurz: hoch };
|
|
await seite.evaluate(() => window.scrollTo(0, 1200));
|
|
await seite.waitForTimeout(400);
|
|
const vor = await seite.evaluate(() => Math.round(window.scrollY));
|
|
|
|
await seite.goto(`http://127.0.0.1:${PORT}/workspace/bereich.html?b=highlight`,
|
|
{ waitUntil: "networkidle" });
|
|
await seite.waitForTimeout(1400);
|
|
|
|
if (wieZurueck === "link") {
|
|
/* Ueber die Kopfzeile -- der Weg, den Filipe im Video geht.
|
|
`evaluate` statt `click`, weil der Link je nach Breite hinter
|
|
dem Knopf liegen kann; das Ereignis ist dasselbe. */
|
|
const gabEs = await seite.evaluate(() => {
|
|
const a = document.querySelector("a.zurueck");
|
|
if (!a) return false;
|
|
a.click();
|
|
return true;
|
|
});
|
|
if (!gabEs) return { keinLink: true };
|
|
await seite.waitForNavigation({ waitUntil: "networkidle" }).catch(() => {});
|
|
} else {
|
|
await seite.goBack({ waitUntil: "load" });
|
|
if (wieZurueck === "neuladen") {
|
|
/* WARUM AUSGERECHNET DAS (21.09.2026): kopf.js laedt die Seite
|
|
selbst neu, sobald der Browser sie aus seinem Rueckwaerts-
|
|
Speicher holt -- damit keine veralteten Zahlen dastehen. Nach
|
|
einem Neuladen heisst die Navigationsart aber "reload", nicht
|
|
"back_forward". Die Wiederherstellung sprang deshalb nicht an
|
|
und LOESCHTE die gemerkte Stelle sogar.
|
|
|
|
Hier wird nie `persisted` erreicht, weil kopf.js einen
|
|
Ereignisstrom offenhaelt und der den Rueckwaerts-Speicher
|
|
aussperrt -- genau deshalb war jede Messung gruen und Filipes
|
|
Handy nicht. Das Neuladen wird deshalb von Hand ausgeloest:
|
|
Der ZUSTAND danach ist derselbe, und nur auf den kommt es an. */
|
|
await seite.waitForTimeout(2200);
|
|
await seite.reload({ waitUntil: "load" });
|
|
}
|
|
}
|
|
await seite.waitForTimeout(3600);
|
|
const nach = await seite.evaluate(() => ({
|
|
y: Math.round(window.scrollY),
|
|
art: performance.getEntriesByType("navigation")[0]?.type,
|
|
wo: location.pathname,
|
|
}));
|
|
return { vor, ...nach };
|
|
} finally {
|
|
await ctx.close();
|
|
}
|
|
}
|
|
|
|
try {
|
|
melde("\n=== 1. Zurueck ueber die Kopfzeile ===");
|
|
{
|
|
const r = await hinUndZurueck("link");
|
|
ok(!r.zuKurz && !r.keinLink,
|
|
r.zuKurz ? `die Startseite ist nur ${r.zuKurz}px hoch -- nichts zu messen`
|
|
: r.keinLink ? "es gibt keinen Zurueck-Link in der Kopfzeile"
|
|
: "der Weg ueber die Kopfzeile ist da");
|
|
if (!r.zuKurz && !r.keinLink) {
|
|
ok(r.wo === "/workspace/start.html", `man landet auf der Startseite (${r.wo})`);
|
|
/* DASS ES EBEN KEIN "back_forward" IST, ist der Kern der Sache --
|
|
und wird deshalb ausdruecklich festgehalten. Waere es eines,
|
|
pruefte dieser Abschnitt denselben Weg wie der naechste. */
|
|
ok(r.art !== "back_forward",
|
|
`und zwar als gewoehnliche Fahrt vorwaerts (${r.art}) -- genau der Fall, `
|
|
+ "der bisher nicht wiederhergestellt wurde");
|
|
ok(r.y >= r.vor - 80,
|
|
`die Stelle bleibt: ${r.vor}px hin, ${r.y}px zurueck`);
|
|
}
|
|
}
|
|
|
|
melde("\n=== 2. Zurueck ueber den Browser ===");
|
|
{
|
|
const r = await hinUndZurueck("back");
|
|
ok(!r.zuKurz, r.zuKurz ? `zu kurz (${r.zuKurz}px)` : "die Seite ist hoch genug");
|
|
if (!r.zuKurz) {
|
|
ok(r.art === "back_forward", `der Browser meldet ein Zurueck (${r.art})`);
|
|
ok(r.y >= r.vor - 80, `die Stelle bleibt: ${r.vor}px hin, ${r.y}px zurueck`);
|
|
}
|
|
}
|
|
|
|
melde("\n=== 3. Gegenprobe: frisch geoeffnet faengt oben an ===");
|
|
{
|
|
/* Ohne diese Zeile waere alles oben auch dann gruen, wenn die
|
|
Seite IMMER an die alte Stelle springt. Das waere schlimmer als
|
|
das Problem: Wer eine Kachel antippt, will oben anfangen. */
|
|
const ctx = await browser.newContext({ viewport: { width: 1280, height: 800 } });
|
|
await ctx.addCookies(keks.split("; ").filter(Boolean).map((z) => {
|
|
const [name, ...rest] = z.split("=");
|
|
return { name, value: rest.join("="), domain: "127.0.0.1", path: "/" };
|
|
}));
|
|
const seite = await ctx.newPage();
|
|
try {
|
|
await seite.goto(`http://127.0.0.1:${PORT}/workspace/start.html`, { waitUntil: "networkidle" });
|
|
await seite.waitForTimeout(1500);
|
|
await seite.evaluate(() => window.scrollTo(0, 1200));
|
|
await seite.waitForTimeout(400);
|
|
/* Neu aufrufen, ohne ueber einen Zurueck-Weg zu kommen. */
|
|
await seite.goto(`http://127.0.0.1:${PORT}/workspace/start.html`, { waitUntil: "networkidle" });
|
|
await seite.waitForTimeout(2600);
|
|
const y = await seite.evaluate(() => Math.round(window.scrollY));
|
|
ok(y < 80, `frisch geoeffnet steht die Seite oben (${y}px)`);
|
|
} finally {
|
|
await ctx.close();
|
|
}
|
|
}
|
|
melde("\n=== 4. Zurueck, und dann laedt die Seite neu ===");
|
|
{
|
|
const r = await hinUndZurueck("neuladen");
|
|
ok(!r.zuKurz, r.zuKurz ? `zu kurz (${r.zuKurz}px)` : "die Seite ist hoch genug");
|
|
if (!r.zuKurz) {
|
|
ok(r.art === "reload",
|
|
`die Navigationsart ist wirklich ein Neuladen (${r.art}) -- sonst `
|
|
+ "pruefte dieser Abschnitt denselben Weg wie Abschnitt 2");
|
|
ok(r.y >= r.vor - 80,
|
|
`und die Stelle ueberlebt es: ${r.vor}px hin, ${r.y}px zurueck`);
|
|
}
|
|
}
|
|
|
|
melde("\n=== 5. Eine fremde Ankunft darf die Stelle nicht wegwerfen ===");
|
|
{
|
|
/* DER EIGENTLICHE FEHLER war nicht, dass nicht wiederhergestellt
|
|
wurde -- sondern dass die gemerkte Stelle dabei GELOESCHT wurde.
|
|
Ein einziges Neuladen reichte, und danach half auch das naechste
|
|
Zurueckgehen nicht mehr. Genau das wird hier gemessen. */
|
|
const ctx = await browser.newContext({ viewport: { width: 1280, height: 800 } });
|
|
await ctx.addCookies(keks.split("; ").filter(Boolean).map((z) => {
|
|
const [name, ...rest] = z.split("=");
|
|
return { name, value: rest.join("="), domain: "127.0.0.1", path: "/" };
|
|
}));
|
|
try {
|
|
const seite = await ctx.newPage();
|
|
await seite.goto(`http://127.0.0.1:${PORT}/workspace/start.html`,
|
|
{ waitUntil: "networkidle" });
|
|
await seite.waitForTimeout(1800);
|
|
const hoch = await seite.evaluate(() => document.documentElement.scrollHeight);
|
|
const ziel = Math.min(900, Math.max(200, hoch - 700));
|
|
await seite.evaluate((y) => window.scrollTo(0, y), ziel);
|
|
await seite.waitForTimeout(700);
|
|
const gemerkt = await seite.evaluate(() => {
|
|
try { return sessionStorage.getItem("ws-stelle:/workspace/start.html"); }
|
|
catch (e) { return null; }
|
|
});
|
|
ok(!!gemerkt, `beim Scrollen wird laufend gemerkt (${gemerkt})`);
|
|
|
|
/* Eine Ankunft OHNE Herkunft -- der Neuanfang. */
|
|
await seite.goto(`http://127.0.0.1:${PORT}/workspace/start.html`,
|
|
{ waitUntil: "load" });
|
|
await seite.waitForTimeout(2600);
|
|
const oben = await seite.evaluate(() => Math.round(window.scrollY));
|
|
ok(oben <= 80, `ein Neuanfang faengt oben an (${oben}px)`);
|
|
|
|
const nochDa = await seite.evaluate(() => {
|
|
try { return sessionStorage.getItem("ws-stelle:/workspace/start.html"); }
|
|
catch (e) { return null; }
|
|
});
|
|
ok(!!nochDa, nochDa
|
|
? "und die gemerkte Stelle ist trotzdem noch da"
|
|
: "GELOESCHT -- das naechste Zurueckgehen haette nichts mehr");
|
|
} finally {
|
|
await ctx.close();
|
|
}
|
|
}
|
|
} finally {
|
|
await browser.close();
|
|
}
|
|
|
|
melde(`\n${geprueft} Pruefungen, ${fehler} Fehler`);
|
|
melde(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG");
|
|
try { rmSync(ordner, { recursive: true, force: true }); } catch { /* Aufraeumrest */ }
|
|
process.exit(fehler ? 1 : 0);
|