diff --git a/server/mess-reaktion.mjs b/server/mess-reaktion.mjs index 367cc7e2..ceec9ca6 100644 --- a/server/mess-reaktion.mjs +++ b/server/mess-reaktion.mjs @@ -685,6 +685,172 @@ if (zuschauer.konsole.length) console.log(" KONSOLE:", zuschauer.konsole.slice Abzeichen UND ein zweiter Knopf dazugekommen. Genau so ist am 28.09. die Regie 197 px uebergelaufen: Die Rechnung ging auf, die Wirklichkeit nicht. */ +/* ---- Die Regieleiste ueber dem Saal ----------------------------- + + Filipe, 28.09.2026: „ich haette das gerne oben also nicht in dieser + kachel sondern ueber der kachel wo die videos laufen werden." + + GEMESSEN WIRD, WAS SIE KOSTET UND WAS SIE KANN. Eine Leiste am + oberen Rand nimmt Hoehe -- und zwar dem Video, um das es geht. + Zwei Zeilen statt einer waeren rund 90 Pixel, und das sieht man + auf dem Bildschirmfoto nicht, weil beides „eine Leiste" ist. */ +console.log(""); +console.log(" --- Die Regieleiste ---"); +{ + const leiste = await host.seite.evaluate(() => { + const l = document.getElementById("regieleiste"); + const r = document.getElementById("reiter"); + const kino = document.getElementById("teil-live"); + if (!l || !r || !kino) return null; + const lr = l.getBoundingClientRect(); + const kr = kino.getBoundingClientRect(); + const knoepfe = [...r.querySelectorAll(".reiter__knopf")]; + return { + hoch: Math.round(lr.height), + oben: Math.round(lr.top), + /* STEHT SIE WIRKLICH UEBER DEM KINO? Das ist die Frage, nicht + ob sie irgendwo existiert. */ + ueberDemKino: lr.bottom <= kr.top + 1, + knoepfe: knoepfe.length, + /* EINE ZEILE ODER ZWEI? Gemessen an den Oberkanten -- wenn + zwei Knoepfe verschieden hoch sitzen, bricht sie um. */ + zeilen: new Set(knoepfe.map((k) => Math.round(k.getBoundingClientRect().top))).size, + kleinster: Math.min(...knoepfe.map((k) => Math.round(k.getBoundingClientRect().height))), + rollt: r.scrollWidth > r.clientWidth, + /* Genau EIN Register in der Tabulatorfolge. */ + imTabulator: knoepfe.filter((k) => k.tabIndex === 0).length, + marke: (() => { + const m = document.getElementById("reiter-marke"); + if (!m || m.hidden) return null; + const mr = m.getBoundingClientRect(); + const a = knoepfe.find((k) => k.getAttribute("aria-selected") === "true"); + const ar = a.getBoundingClientRect(); + return { + breit: Math.round(mr.width), + /* Sitzt sie unter dem gewaehlten Register? */ + versatz: Math.round(Math.abs(mr.left - ar.left)), + gleichBreit: Math.abs(mr.width - ar.width) < 2, + }; + })(), + }; + }); + + if (!leiste) { + console.log(" ACHTUNG: Die Regieleiste gibt es nicht."); + ausgang = 1; + } else { + console.log(` ${leiste.knoepfe} Register in ${leiste.zeilen} Zeile(n), ` + + `Leiste ${leiste.hoch} px hoch bei y=${leiste.oben}, ` + + `kleinstes Ziel ${leiste.kleinster} px, rollt=${leiste.rollt}`); + console.log(` Ueber dem Kino: ${leiste.ueberDemKino} · ` + + `im Tabulator: ${leiste.imTabulator} · ` + + `Marke ${leiste.marke ? `${leiste.marke.breit} px, Versatz ${leiste.marke.versatz} px` : "(keine)"}`); + + if (!leiste.ueberDemKino) { + console.log(" ACHTUNG: Die Leiste steht nicht ueber dem Kino."); + ausgang = 1; + } + if (leiste.zeilen !== 1) { + console.log(" ACHTUNG: Die Register brechen um -- das kostet das Video Hoehe."); + ausgang = 1; + } + if (leiste.kleinster < 44) { + console.log(" ACHTUNG: Ein Register ist kleiner als 44 px."); + ausgang = 1; + } + if (leiste.imTabulator !== 1) { + console.log(" ACHTUNG: Der Tabulator wandert nicht -- sieben Schritte bis zum Inhalt."); + ausgang = 1; + } + if (!leiste.marke || leiste.marke.versatz > 2 || !leiste.marke.gleichBreit) { + console.log(" ACHTUNG: Die Marke sitzt nicht unter dem gewaehlten Register."); + ausgang = 1; + } + } + + /* ---- DIE TASTATUR MUSS TUN, WAS `role=tablist` VERSPRICHT ---- + Ein Versprechen im Markup, das das Programm nicht einloest, ist + schlimmer als gar keines: Der Vorleser sagt etwas an, was nicht + stimmt. Also wird es gedrueckt, nicht gelesen. */ + await host.seite.evaluate(() => { + document.querySelector('#reiter .reiter__knopf[data-tafel="sendung"]').focus(); + }); + const tasten = []; + for (const [taste, wohin] of [["ArrowRight", "rechts"], ["ArrowRight", "rechts"], + ["ArrowLeft", "links"], ["End", "ans Ende"], ["Home", "an den Anfang"]]) { + await host.seite.keyboard.press(taste); + await host.seite.waitForTimeout(220); + const wo = await host.seite.evaluate(() => { + const a = document.activeElement; + const g = [...document.querySelectorAll("#reiter .reiter__knopf")] + .find((k) => k.getAttribute("aria-selected") === "true"); + const t = [...document.querySelectorAll(".pult-tafel")].find((x) => !x.hidden); + return { + fokus: a?.dataset?.tafel || "(?)", + gewaehlt: g?.dataset?.tafel || "(?)", + tafel: t?.dataset?.tafel || "(keine)", + }; + }); + tasten.push(`${wohin}->${wo.fokus}`); + /* FOKUS, AUSWAHL UND OFFENE TAFEL MUESSEN DASSELBE SAGEN. + Laufen sie auseinander, zeigt die Regie eine andere Tafel als + das hervorgehobene Register -- und man stellt etwas ein, von + dem man glaubt, es gehoere woandershin. */ + if (wo.fokus !== wo.gewaehlt || wo.gewaehlt !== wo.tafel) { + console.log(` ACHTUNG: Fokus ${wo.fokus}, gewaehlt ${wo.gewaehlt}, ` + + `offen ${wo.tafel} -- das passt nicht zusammen.`); + ausgang = 1; + } + } + console.log(` Tastatur: ${tasten.join(" · ")}`); + + /* ---- EIN REGISTER MACHT DIE ZUGEKLAPPTE REGIE AUF ---- + Seit die Leiste oben steht, kann das Pult zu sein, waehrend man + ein Register waehlt. Ohne das waere der Knopf einer, bei dem + sichtbar nichts passiert. */ + await host.seite.click("#pult-griff").catch(() => { }); + await host.seite.waitForTimeout(400); + const zu = await host.seite.evaluate(() => + document.getElementById("pult").dataset.auf); + await host.seite.click('#reiter .reiter__knopf[data-tafel="ton"]').catch(() => { }); + await host.seite.waitForTimeout(500); + const danach = await host.seite.evaluate(() => ({ + auf: document.getElementById("pult").dataset.auf, + tafel: [...document.querySelectorAll(".pult-tafel")].find((x) => !x.hidden)?.dataset.tafel, + sichtbar: (() => { + const t = document.getElementById("tafel-ton"); + const r = t.getBoundingClientRect(); + return r.height > 0 && r.top < window.innerHeight; + })(), + })); + console.log(` Regie zugeklappt (auf=${zu}), Register „Ton" gedrueckt ` + + `-> auf=${danach.auf}, offen=${danach.tafel}, sichtbar=${danach.sichtbar}`); + if (zu !== "nein" || danach.auf !== "ja" || !danach.sichtbar) { + console.log(" ACHTUNG: Ein Register macht die zugeklappte Regie nicht auf."); + ausgang = 1; + } + await bild(host.seite, "20-regieleiste"); + + /* ---- WAS SIE DEM VIDEO KOSTET ---- + Die eigentliche Frage bei einer Leiste am oberen Rand. */ + const platz = await host.seite.evaluate(() => { + const l = document.getElementById("regieleiste").getBoundingClientRect(); + const k = document.getElementById("teil-live").getBoundingClientRect(); + const s2 = document.getElementById("saal").getBoundingClientRect(); + return { + leiste: Math.round(l.height), kino: Math.round(k.height), saal: Math.round(s2.height), + ueber: Math.max(0, document.documentElement.scrollHeight + - document.documentElement.clientHeight), + }; + }); + console.log(` Saal ${platz.saal} px = Leiste ${platz.leiste} + Kino ${platz.kino} ` + + `+ Regie, senkrecht ueber ${platz.ueber} px`); + if (platz.ueber > 6 || platz.kino < 200) { + console.log(" ACHTUNG: Die Leiste nimmt dem Bild zu viel oder die Seite laeuft ueber."); + ausgang = 1; + } +} + /* ---- Die Gestaltung der Spendenkarten --------------------------- Filipe: „mach paar fertige und so dass ich auch hochladen kann. @@ -785,7 +951,7 @@ const messeKarte = async (erwartet) => { Messung bestaetigte eine Aenderung, die gar nicht angekommen ist. */ await host.seite.waitForFunction( - (soll) => [...document.querySelectorAll(".spendenkarte")].some((k2) => + (soll) => [...document.querySelectorAll('.spendenkarte[data-da="ja"]')].some((k2) => k2.querySelector(".spendenkarte__wer")?.textContent === "Probe" && Math.abs(parseFloat(getComputedStyle(k2).fontSize) - soll) < 0.5), erwartet, { timeout: 16000 }, @@ -798,7 +964,12 @@ const messeKarte = async (erwartet) => { die Irre gefuehrt. */ await host.seite.waitForTimeout(700); return host.seite.evaluate(() => { - const k2 = [...document.querySelectorAll(".spendenkarte")] + /* `data-da="ja"` HEISST: GANZ DA. Beim Hereinkommen UND beim + Verschwinden steht die Karte auf `translateX(-28px) scale(.96)` + -- gemessen wurden dadurch 413 statt 430 Pixel (genau das + 0,96-fache) und ein linker Rand von -1. Beides war kein + Fehler der Karte, sondern ein Blick zur falschen Zeit. */ + const k2 = [...document.querySelectorAll('.spendenkarte[data-da="ja"]')] .find((x) => x.querySelector(".spendenkarte__wer")?.textContent === "Probe"); if (!k2) return null; const r = k2.getBoundingClientRect(); @@ -886,7 +1057,7 @@ if (!kGross || !kKlein) { /* ---- Und die Farbe? ---- */ const farbe = await host.seite.evaluate(() => { - const k2 = [...document.querySelectorAll(".spendenkarte")] + const k2 = [...document.querySelectorAll('.spendenkarte[data-da="ja"]')] .find((x) => x.querySelector(".spendenkarte__wer")?.textContent === "Probe"); return k2 ? getComputedStyle(k2).getPropertyValue("--sp-farbe").trim() : ""; }); @@ -1396,6 +1567,65 @@ await gast2.kontext.close(); await zuschauer.kontext.close(); { + /* ==== DIE REGIELEISTE AM HANDY ================================== + Sieben Register auf 390 Pixel passen nicht nebeneinander -- sie + muss rollen. Gemessen wird beides: dass sie rollt, und dass sie + trotzdem nur EINE Zeile hoch ist. Umbrechen waere die stille + Alternative, und die kostet das Video 44 Pixel. */ + { + const chef = await alsWer("admin", 390, 844); + await chef.seite.goto(`${BASIS}/workspace/reaktion.html`, { waitUntil: "domcontentloaded" }); + await chef.seite.waitForTimeout(2600); + const l = await chef.seite.evaluate(() => { + const r = document.getElementById("reiter"); + const le = document.getElementById("regieleiste"); + if (!r || le.hidden) return null; + const k = [...r.querySelectorAll(".reiter__knopf")]; + const rr = r.getBoundingClientRect(); + return { + hoch: Math.round(le.getBoundingClientRect().height), + zeilen: new Set(k.map((x) => Math.round(x.getBoundingClientRect().top))).size, + rollt: r.scrollWidth > r.clientWidth + 1, + sichtbar: k.filter((x) => { + const q = x.getBoundingClientRect(); + return q.left >= rr.left - 1 && q.right <= rr.right + 1; + }).length, + schild: !!document.querySelector(".regieleiste__schild")?.offsetParent, + ueber: Math.max(0, document.documentElement.scrollWidth + - document.documentElement.clientWidth), + }; + }); + if (!l) { + console.log(" ACHTUNG: Am Handy gibt es die Regieleiste nicht."); + ausgang = 1; + } else { + console.log(` Handy-Regie: ${l.hoch} px, ${l.zeilen} Zeile(n), rollt=${l.rollt}, ` + + `${l.sichtbar} von 7 im Bild, Schild=${l.schild}, seitlich ${l.ueber} px`); + if (l.zeilen !== 1 || !l.rollt || l.ueber > 0 || l.schild) { + console.log(" ACHTUNG: Die Regieleiste am Handy bricht um, rollt nicht " + + "oder schiebt die Seite breiter."); + ausgang = 1; + } + } + /* WAS BLEIBT VOM BILD, WENN DIE REGIE OFFEN IST? + Am Handy ist das die eigentliche Frage. Ein Kinosaal von 131 + Pixeln ist kein Bild mehr, sondern ein Streifen -- und man + merkt es erst, wenn man mitten in der Sendung etwas einstellen + will und nicht mehr sieht, worueber man redet. */ + const platzHandy = await chef.seite.evaluate(() => { + const g = (id) => Math.round(document.getElementById(id).getBoundingClientRect().height); + return { saal: g("saal"), leiste: g("regieleiste"), kino: g("teil-live"), pult: g("pult") }; + }); + console.log(` Handy offen: Saal ${platzHandy.saal} = Leiste ${platzHandy.leiste} ` + + `+ Kino ${platzHandy.kino} + Regie ${platzHandy.pult}`); + if (platzHandy.kino < 190) { + console.log(" ACHTUNG: Bei offener Regie bleibt vom Bild fast nichts."); + ausgang = 1; + } + await bild(chef.seite, "21-regieleiste-handy"); + await chef.kontext.close(); + } + const handy = await alsWer("gast2", 390, 844); await handy.seite.goto(`${BASIS}/workspace/reaktion.html`, { waitUntil: "networkidle" }); await handy.seite.waitForTimeout(4000); diff --git a/server/pruef-reaktion.mjs b/server/pruef-reaktion.mjs index 7d958a58..89a89176 100644 --- a/server/pruef-reaktion.mjs +++ b/server/pruef-reaktion.mjs @@ -1552,6 +1552,106 @@ melde("=== 17. Tempo, Anordnung und das zweite Video ==="); } } + +/* ======================================================================= + 18. DIE REGIELEISTE UEBER DEM SAAL + + Filipe, 28.09.2026: „ich haette das gerne oben also nicht in dieser + kachel sondern ueber der kachel wo die videos laufen werden." + + Geprueft wird hier, was sich aus dem Quelltext beweisen laesst -- + Lage, Verdrahtung, Versprechen. Wie hoch sie ist und ob die Marke + sitzt, misst `mess-reaktion` im Browser; das kann kein Quelltext. + ======================================================================= */ +melde(""); +melde("=== 18. Die Regieleiste ==="); +{ + const html = readFileSync(join(HIER, "..", "workspace", "reaktion.html"), "utf8"); + const css = readFileSync(join(HIER, "..", "workspace", "assets", "css", "reaktion.css"), "utf8"); + const js = readFileSync(join(HIER, "..", "workspace", "assets", "js", "reaktion.js"), "utf8"); + + /* ---- SIE STEHT UEBER DEM SAAL, NICHT DARIN ---- + Die Reihenfolge im Dokument ist die Aussage. Waere sie wieder im + Pult, stuende sie weiter unten -- und niemandem fiele es auf, + solange sie „irgendwo" da ist. */ + const iLeiste = html.indexOf('id="regieleiste"'); + const iKino = html.indexOf('id="teil-live"'); + const iPult = html.indexOf('id="pult"'); + ok(iLeiste > 0, "es gibt die Regieleiste"); + ok(iLeiste < iKino, `sie steht vor dem Kino (${iLeiste} < ${iKino})`); + ok(iLeiste < iPult, "und vor dem Pult"); + ok(html.indexOf('id="reiter"') < iPult, + "die Register stehen nicht mehr im Pult"); + + /* ---- JEDES REGISTER ZEIGT AUF EINE TAFEL, DIE ES GIBT ---- + `aria-controls` ist ein Versprechen an Hilfsmittel. Zeigt es ins + Leere, sagt der Vorleser eine Tafel an, die niemand oeffnen + kann -- und gemerkt haette es nie jemand. */ + const reiter = [...html.matchAll(/data-tafel="([a-z]+)"[^>]*aria-controls="([^"]+)"/g)] + .concat([...html.matchAll(/aria-controls="([^"]+)"[^>]*data-tafel="([a-z]+)"/g)] + .map((m) => [m[0], m[2], m[1]])); + ok(reiter.length >= 7, `${reiter.length} Register gefunden`); + const ohneTafel = reiter.filter(([, , ziel]) => !html.includes(`id="${ziel}"`)); + ok(ohneTafel.length === 0, + ohneTafel.length + ? `ZEIGT INS LEERE: ${ohneTafel.map((x) => x[2]).join(", ")}` + : "jedes Register zeigt auf eine Tafel, die es gibt"); + ok(!html.includes('id="tafel-gibtesnicht"'), + "Gegenprobe: eine erfundene Tafel gibt es nicht"); + + /* ---- DAS VERSPRECHEN VON `role=tablist` ---- + Steht die Rolle im Markup und loest das Programm sie nicht ein, + sagt ein Vorleser etwas an, was nicht stimmt. Das ist schlimmer + als gar keine Rolle. */ + ok(/id="reiter"[^>]*role="tablist"/.test(html), "die Leiste ist eine Registerleiste"); + /* Die Anfuehrungszeichen bleiben draussen -- im Quelltext steht + `'Home'`, in dieser Pruefung stand zuerst `"Home"`, und daran + ist sie gescheitert. Eine Pruefung, die die Schreibweise der + Anfuehrungszeichen mitprueft, prueft die falsche Sache. */ + for (const taste of ["ArrowRight", "ArrowLeft", "Home", "End"]) { + ok(js.includes(taste), `das Programm kennt „${taste}"`); + } + ok(/tabIndex\s*=\s*gewaehlt \? 0 : -1/.test(js), + "genau ein Register liegt in der Tabulatorfolge"); + + /* ---- EIN REGISTER MACHT DIE ZUGEKLAPPTE REGIE AUF ---- + Seit die Leiste oben steht, kann das Pult zu sein. Ohne diese + Zeile waere jedes Register bei zugeklappter Regie ein Knopf, + bei dem sichtbar nichts passiert. */ + ok(/pult\.dataset\.auf = 'ja'/.test(js) && /reiterWechseln/.test(js), + "ein Register klappt die Regie auf"); + + /* ---- DIE MARKE UND IHRE ZWEITE ANZEIGE ---- + Was gerechnet wird, braucht etwas daneben, das nicht rechnet. */ + ok(html.includes('id="reiter-marke"'), "es gibt die gleitende Marke"); + ok(/offsetLeft/.test(js) && /offsetWidth/.test(js) && /offsetHeight/.test(js), + "sie wird aus gemessenen Werten gesetzt, nicht gerechnet"); + const knopfRegel = css.match(/\.reiter__knopf\[aria-selected="true"\] \{[^}]*\}/); + ok(!!knopfRegel && /color:/.test(knopfRegel[0]), + "das gewaehlte Register traegt ausserdem eine eigene Farbe"); + + /* ---- SIE GEHOERT NICHT IN DEN STREAM ---- + Der Buehnenmodus zeigt nur, was Zuschauer sehen sollen. Eine + Regieleiste im Stream waere die Bedienung im Bild. */ + ok(/body\[data-nur="buehne"\][^{]*\.regieleiste/.test(css.replace(/\s+/g, " ")), + "im Buehnenmodus ist sie weg"); + + /* ---- NUR EINE STELLE SAGT, WIE HOCH DIE ZEILEN SIND ---- + Bis zum 28.09. standen zwei Angaben in derselben Datei; die + spaetere gewann und gab der Leiste null Pixel. Zwei Regeln fuer + dieselbe Frage sind keine Sicherheit. */ + const zeilen = [...css.matchAll(/^\s*\.saal[^{]*\{[^}]*grid-template-rows/gm)] + .concat([...css.matchAll(/^\.saal \{ grid-template-rows/gm)]); + ok(zeilen.length === 1, + zeilen.length === 1 + ? "genau eine Regel bestimmt die Zeilen des Saals" + : `${zeilen.length} Regeln bestimmen die Zeilen -- eine davon wird ignoriert`); + + /* ---- UND SIE GEHOERT NUR DENEN, DIE SENDEN ---- */ + ok(/leiste\.hidden = !lage\.ich\.host/.test(js), + "nur wer die Sendung fuehrt, sieht sie"); +} + melde(""); melde(`${geprueft} Pruefungen, ${fehler} Fehler`); melde(fehler === 0 ? "ALLES IN ORDNUNG" : "NICHT IN ORDNUNG"); diff --git a/workspace/anruf-probe.html b/workspace/anruf-probe.html index 23f4c42f..f5a5d74f 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`. --> - - + + - +