Kampagnen-Kacheln: gegliederte Texte, Geschenke mit Preis, aufklappbar
Filipe, mit einem Bildschirmfoto des Regelblocks: "man soll die titel
in den texten besser erkennen und sehen was zu was passt. ich will
auch dass man sieht welche geschenke bei dem event zaehlen und so.
ich will alle informationen die moeglich sind wenn man den link
hochlaedt. ... auch wenn man teilt soll es einfach perfekt sein. man
soll alle infos sehen die man braucht. die kacheln von den events
soll auch nicht so lang sein, die soll man aufklappen koennen. genau
wie eine trennung fuer heute, letzte woche letzten monat und so."
DER FEHLER LAG NICHT IM TEXT, SONDERN IN SEINER ANZEIGE. Im Feld
stand die ganze Zeit eine Gliederung -- Hinweise mit "* ", eine
Ueberschrift mit Untertitel hinter einem Gedankenstrich, Absaetze
ohne Zeichen, Aufzaehlungen mit "- ". Die Kachel hat daraus mit
punkteAusText() EINE flache Liste gemacht: jede Zeile derselbe
Strich, alle gleich gross.
gliederung() liest die Gliederung, die schon dasteht, statt
den Text umzuschreiben. Die beiden Kampagnen, die
heute in der Datenbank liegen, sehen dadurch
sofort richtig aus -- ohne Umstellung.
Geschenke kommen aus der gelesenen LISTE, nicht aus dem
Text, und stehen OHNE Aufklappen auf der Kachel.
Aufklappen alles Uebrige hinter einem Druck: gemessen 555 px
weniger je Kachel bei Gipfelstuermer.
Gruppen je Spalte laeuft / kommt / vorbei, Vergangenes
nach diese Woche, dieser Monat, aelter.
NEU GELESEN (stand in allen vier gespeicherten Seiten und wurde nie
angesehen): die Rangliste mit ihren Gruppen ("Superstars" bis
"5. Liga", bei Glow Up "Team Glow"/"Team Shine"), die Beschreibung je
Missionsgruppe, der Steckbrief (Kurznummer, Markt, Sprache, Zone),
der zweite Bildbaustein, und Ueberschriften INNERHALB der Klapptexte
-- "WELCHE LIGA BIN ICH?" hat jetzt sechs eingerueckte Unterpunkte
statt zwoelf gleichrangiger Zeilen.
EVENT_TEXT_MAX 6000 -> 20000. Der Regeltext von Gipfelstuermer ist
7679 Zeichen lang; es fielen bisher jedes Mal rund 1600 Zeichen
echter Inhalt weg. Die Zahl steht jetzt an EINER Stelle.
TEILEN: Was die KAMPAGNE sagt, geht hinaus (Aufgaben, Geschenke,
Regeln) -- wer WIR sind, nicht (Name, Rolle, Haus, Kampagnennummer,
Steckbrief). Und keine Hinweiszeile: "Die Preise konnten nicht
gelesen werden -- bitte nachtragen" ist eine Notiz an DogFather und
hat auf einer Seite, die er in Discord postet, nichts zu suchen.
BERICHTIGT, WEIL NACHGEMESSEN:
- Number(null) ist 0 und 0 ist endlich -- "Feuerwerk" bekam an drei
Stellen "0 Coins" angeschrieben, also eine falsche Preisangabe.
- Ein Kommentar im Leser behauptete, der Wertungszeitraum weiche
vom Kampagnenzeitraum ab. Nachgemessen ist er in allen drei
Seiten mit Rangliste auf die Millisekunde gleich; ich hatte zwei
verschiedene Kampagnen verglichen.
- bild-kampagne.mjs verlangte "jede Kachel traegt den Ton ihrer
Seite" und war damit seit dem 07.10. rot, weil an dem Tag bewusst
das Gegenteil entschieden wurde (Zustandston, Seitenkante). Nicht
aufgefallen, weil nach dem Lauf nur die Zahl der MESSUNGEN notiert
wurde, nicht die der FEHLER.
- .kv-seite > .eintrag-karte wird .kv-seite .eintrag-karte: Die
Karten haengen durch die Gruppen eine Ebene tiefer. Das waere der
fuenfte Direktkind-Selektor gewesen, der nach einem Umbau still
ausfaellt -- diesmal vorher gesehen.
GEPRUEFT: 186 (lesen, +58), 148 (Route/Teilen, +25), 158 (Kachel,
+64), 67 (Agentur, +5), 67 Messungen (Bild, +14), dazu struktur,
css-klassen, zeichen, deutsche-texte, tippziele, lesbarkeit und
haus-trennung (107, unveraendert). Sechs Gegenproben: alle sechs
Sabotagen werden bemerkt, alle Dateien byte-gleich wiederhergestellt.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -149,7 +149,8 @@ for (const [name, s] of Object.entries(SEITEN)) {
|
||||
|
||||
const {
|
||||
leseKampagne, regelnText, aufgabenText, tagInZone, textAusHtml,
|
||||
KampagneUnlesbar, datenAusSeite,
|
||||
KampagneUnlesbar, datenAusSeite, gliederung, deutschesDatum, zeitOderNull,
|
||||
geruestVon, bausteine,
|
||||
} = await import("./kampagne-lesen.mjs");
|
||||
|
||||
/* =====================================================================
|
||||
@@ -394,9 +395,50 @@ melde("=== 4. Was in event_aufgaben und event_regeln landet ===");
|
||||
ok(r.includes("1. Liga") && r.includes("2. Liga") && r.includes("5. Liga"),
|
||||
"die Ligen sind durchnummeriert (1. bis 5.)");
|
||||
{
|
||||
/* `### ` DAVOR SEIT DEM 07.10.2026 -- und das ist der Zweck.
|
||||
|
||||
Hier stand `=== "3. Liga"`. Auf der Seite ist ein
|
||||
Listenpunkt, dessen GANZER Inhalt hervorgehoben ist, eine
|
||||
Überschrift mit Nummer: Darunter steht jeweils die Bedingung
|
||||
für diese Liga. Flach ausgegeben standen zwölf
|
||||
gleichrangige Zeilen da, und die Frage „ab wie vielen
|
||||
Diamanten bin ich in der 2. Liga" war daran nicht mehr zu
|
||||
beantworten -- obwohl die Antwort danebenstand.
|
||||
|
||||
DIE NUMMER BLEIBT DABEI (`<ol start>`), und das
|
||||
` ` hinter `</strong>` darf nicht stören -- die fünfte
|
||||
Liga hat eines, die ersten vier nicht. */
|
||||
const rohHtml = "<ol start=\"3\"><li><strong>Liga </strong></li></ol>";
|
||||
ok(textAusHtml(rohHtml) === "3. Liga",
|
||||
ok(textAusHtml(rohHtml) === "### 3. Liga",
|
||||
`Gegenprobe am Baustein selbst: ${JSON.stringify(textAusHtml(rohHtml))}`);
|
||||
const mitNbsp = "<ol start=\"5\"><li><strong>Liga </strong> </li></ol>";
|
||||
ok(textAusHtml(mitNbsp) === "### 5. Liga",
|
||||
`auch mit geschütztem Leerzeichen dahinter: ${JSON.stringify(textAusHtml(mitNbsp))}`);
|
||||
/* GEGENPROBE: Ein Listenpunkt mit gewöhnlichem Text wird KEINE
|
||||
Überschrift. Ohne sie wäre unbelegt, dass die Hervorhebung
|
||||
entscheidet und nicht einfach jede Zeile eine Überschrift
|
||||
wird. */
|
||||
const schlicht = "<ol start=\"2\"><li>Nur Text, nicht fett</li></ol>";
|
||||
ok(textAusHtml(schlicht) === "2. Nur Text, nicht fett",
|
||||
`ein gewöhnlicher Punkt bleibt ein Punkt: ${JSON.stringify(textAusHtml(schlicht))}`);
|
||||
/* Und ein Absatz, dessen ganzer Inhalt hervorgehoben ist,
|
||||
ebenfalls -- so stehen die Ligennamen auf der Seite. */
|
||||
const absatz = "<p style=\"text-align: center;\"><strong>Superstars</strong></p>"
|
||||
+ "<p>Du kannst teilnehmen, wenn ...</p>";
|
||||
/* MIT LEERZEILE DAZWISCHEN -- so soll es sein. Die
|
||||
Überschrift bekommt ihren eigenen Umbruch, der Absatz
|
||||
seinen; `gliederung()` wirft leere Zeilen ohnehin weg, und
|
||||
im Rohtext, den DogFather beim Bearbeiten sieht, liest sich
|
||||
das sauber. Meine Erwartung von EINEM Umbruch war
|
||||
geraten. */
|
||||
ok(textAusHtml(absatz) === "### Superstars\n\nDu kannst teilnehmen, wenn ...",
|
||||
`ein Absatz nur aus Hervorhebung wird Überschrift: ${JSON.stringify(textAusHtml(absatz))}`);
|
||||
/* GEGENPROBE dazu: Eine Frage MIT hervorgehobenem Wort ist auch
|
||||
der Sache nach eine Überschrift -- aber das zu behaupten wäre
|
||||
geraten. Sie bleibt ein Absatz. */
|
||||
const gemischt = "<p>Um was geht es bei <strong>Gipfelstürmer?</strong></p>";
|
||||
ok(textAusHtml(gemischt) === "Um was geht es bei Gipfelstürmer?",
|
||||
`eine Frage mit fettem Wort bleibt ein Absatz: ${JSON.stringify(textAusHtml(gemischt))}`);
|
||||
ok(rohHtml.replace(/<[^>]+>/g, "").trim() === "Liga ",
|
||||
"Gegenprobe: bloßes Markenentfernen verliert die Nummer");
|
||||
}
|
||||
@@ -831,6 +873,327 @@ melde("=== 12. Gleiche Eingabe, gleiches Ergebnis ===");
|
||||
`${gelesen} der fünf Seiten lesen sich, ${geworfen} wirft (die kaputte)`);
|
||||
}
|
||||
|
||||
/* =====================================================================
|
||||
13. DIE RANGLISTE, DER STECKBRIEF UND DIE MISSIONSGRUPPEN
|
||||
|
||||
Drei Bausteine, die in allen vier gespeicherten Seiten dastanden
|
||||
und bis zum 07.10.2026 nie gelesen wurden. Filipe: „ich will alle
|
||||
informationen die möglich sind wenn man den link hochlädt."
|
||||
===================================================================== */
|
||||
melde("");
|
||||
melde("=== 13. Rangliste, Steckbrief, Missionsgruppen ===");
|
||||
|
||||
{
|
||||
const g = leseKampagne(seite.gipfel);
|
||||
const r = g.kuer.rangliste;
|
||||
ok(!!r, "die Rangliste wird gelesen");
|
||||
|
||||
/* DER WERTUNGSZEITRAUM IST NICHT DER KAMPAGNENZEITRAUM -- genau
|
||||
deshalb lohnt das Lesen. Bei „Gipfelstürmer" sind beide gleich,
|
||||
bei „LIVE Glow Up" nicht; die Gegenprobe steht unten. */
|
||||
ok(r.von_tag === "2026-10-04" && r.bis_tag === "2026-10-07",
|
||||
`gewertet wird ${r.von_tag} bis ${r.bis_tag}`);
|
||||
ok(r.reiter.length === 6
|
||||
&& r.reiter[0] === "Superstars" && r.reiter[5] === "5. Liga",
|
||||
`sechs Gruppen, aus dem Wörterbuch aufgelöst: ${JSON.stringify(r.reiter)}`);
|
||||
ok(r.punkteVerborgen === false,
|
||||
`die Punktzahl ist hier sichtbar (${r.punkteVerborgen})`);
|
||||
ok(typeof r.beschreibung === "string" && r.beschreibung.length > 0,
|
||||
`und die Wertung ist beschriftet: ${JSON.stringify(r.beschreibung)}`);
|
||||
|
||||
/* Der Steckbrief: nur Felder, die man ohne Deutung zeigen kann. */
|
||||
const s = g.kuer.steckbrief;
|
||||
ok(s.kurznummer === "1140116740", `Kurznummer ${s.kurznummer}`);
|
||||
ok(s.region === "EU-TTP2", `Markt ${s.region}`);
|
||||
ok(s.sprache === "de-DE", `Sprache ${s.sprache}`);
|
||||
ok(s.zeitzone === "Europe/Berlin", `Zeitzone ${s.zeitzone}`);
|
||||
/* LEER IST NICHT „—": `union_name` steht in allen vier gemessenen
|
||||
Seiten leer und wird deshalb zu null. */
|
||||
ok(s.verbund === null, `der leere Verbund wird null, nicht "" (${JSON.stringify(s.verbund)})`);
|
||||
/* ANMELDUNG UND ERGEBNIS NUR BEI ABWEICHUNG. Sie sind hier gleich
|
||||
dem Kampagnenzeitraum; sie trotzdem zu zeigen hieße, dieselbe
|
||||
Zahl dreimal hinzuschreiben. */
|
||||
ok(s.anmeldung_ab === null && s.ergebnis_bis === null,
|
||||
"Anmeldung und Ergebnis fallen weg, weil sie nichts Neues sagen");
|
||||
/* GEGENPROBE: Die Rohwerte sind sehr wohl da -- sonst wäre
|
||||
„fällt weg" nicht von „wurde nie gelesen" zu unterscheiden. */
|
||||
const rohAktion = datenAusSeite(seite.gipfel).globalData.activityInfo;
|
||||
ok(zeitOderNull(rohAktion.warm_up_start_time) === g.auszug.start_ms,
|
||||
`Gegenprobe: warm_up_start_time steht da und ist gleich dem Beginn `
|
||||
+ `(${rohAktion.warm_up_start_time})`);
|
||||
|
||||
/* Kein Rohfeld, das niemand lesen kann, rutscht in den
|
||||
Steckbrief -- die stehen weiter im Auszug, nicht auf der
|
||||
Kachel. */
|
||||
ok(!Object.keys(s).some((k) => /activity_type|kind_flag|platform|sdlc|biz/.test(k)),
|
||||
`nur erklärbare Felder im Steckbrief: ${JSON.stringify(Object.keys(s))}`);
|
||||
|
||||
/* ---- Die zweite Vorlage: Missionsgruppen mit Beschreibung ---- */
|
||||
const h = leseKampagne(seite.aufgabengruppe);
|
||||
const gr = h.kuer.aufgabenGruppen;
|
||||
/* ZWEI, NICHT VIER -- und das ist der Zweck der Entdopplung.
|
||||
|
||||
In `taskItemList` stehen VIER Einträge; die Vorlage legt je
|
||||
Reiter zwei an (einen für Creator, einen für Zuschauer), und
|
||||
beide zeigen auf denselben Wörterbucheintrag. Nachgemessen:
|
||||
die Platzhalter `..._7_...` und `..._8_...` enden auf
|
||||
derselben Prüfsumme, also auf demselben Text. Zweimal
|
||||
derselbe Haken wäre ein Haken an zwei Stellen.
|
||||
|
||||
Meine Erwartung von vier war die ROHZAHL, nicht das
|
||||
Ergebnis. Gemessen wird deshalb beides: vier hinein, zwei
|
||||
heraus -- sonst wäre „zwei" auch dann grün, wenn die Hälfte
|
||||
gar nicht gelesen würde. */
|
||||
/* GEZÄHLT WIRD AM BAUSTEIN, nicht im Text gesucht (berichtigt
|
||||
07.10.2026). Mein erster Versuch suchte den Platzhalternamen
|
||||
`taskFormData_displayInfo_title` im ganzen Schema und fand ihn
|
||||
ACHTMAL -- er steht dort noch an anderen Stellen. Eine Zahl aus
|
||||
einer Textsuche ist keine Messung der Liste; gezählt wird
|
||||
`taskItemList` selbst, also genau das, was der Leser liest. */
|
||||
const rohTeile = bausteine(geruestVon(datenAusSeite(seite.aufgabengruppe)).geruest);
|
||||
const rohListe = (rohTeile.get("live_task_group") || [])[0]?.props?.taskItemList || [];
|
||||
ok(rohListe.length === 4, `vier Einträge stehen in der Seite (${rohListe.length})`);
|
||||
ok(gr.length === 2, `und zwei verschiedene Missionsgruppen bleiben (${gr.length})`);
|
||||
ok(new Set(gr.map((x) => x.titel)).size === gr.length,
|
||||
`keine zwei mit demselben Titel: ${JSON.stringify(gr.map((x) => x.titel))}`);
|
||||
ok(gr.every((x) => typeof x.beschreibung === "string" && x.beschreibung.length > 3),
|
||||
`jede mit eigener Beschreibung: ${JSON.stringify(gr.map((x) => x.beschreibung.slice(0, 28)))}`);
|
||||
ok(gr.every((x) => /^[0-9]{4}-[0-9]{2}-[0-9]{2}$/.test(x.von_tag || "")),
|
||||
`und mit eigenem Zeitraum: ${JSON.stringify(gr.map((x) => `${x.von_tag}..${x.bis_tag}`))}`);
|
||||
|
||||
/* DIE HAKENLISTE IST ENTDOPPELT, DIE GRUPPENLISTE DECKUNGSGLEICH.
|
||||
Die Vorlage legt je Reiter zwei Einträge an (Creator und
|
||||
Zuschauer); zweimal derselbe Haken wäre ein Haken an zwei
|
||||
Stellen. */
|
||||
ok(h.kuer.aufgaben.length === gr.length
|
||||
&& h.kuer.aufgaben.every((a, i) => a === gr[i].titel),
|
||||
`Aufgaben und Gruppen passen Zeile für Zeile zusammen `
|
||||
+ `(${h.kuer.aufgaben.length} / ${gr.length})`);
|
||||
|
||||
/* ---- WAS DIE RANGLISTE WIRKLICH BEITRÄGT ------------------
|
||||
|
||||
Hier stand „bei LIVE Glow Up beginnt die Wertung später als
|
||||
die Kampagne". Das ist nachgemessen FALSCH: In allen drei
|
||||
Seiten mit Rangliste ist der Wertungszeitraum auf die
|
||||
Millisekunde gleich dem Kampagnenzeitraum. Ich hatte zwei
|
||||
verschiedene Kampagnen verglichen. Die ausführliche
|
||||
Berichtigung steht bei `rangBaustein` in kampagne-lesen.mjs.
|
||||
|
||||
GEMESSEN WIRD JETZT, WAS STIMMT -- und zwar beides: dass die
|
||||
Zeiträume hier gleich sind (damit niemand wieder etwas
|
||||
anderes behauptet) UND dass der Baustein trotzdem etwas
|
||||
beiträgt, das sonst nirgends steht: die Gruppen. */
|
||||
const gleich = [seite.gipfel, seite.ohnePreise, seite.aufgabengruppe]
|
||||
.map((x) => leseKampagne(x))
|
||||
.filter((x) => x.kuer.rangliste)
|
||||
.map((x) => x.kuer.rangliste.von === x.auszug.start_ms
|
||||
&& x.kuer.rangliste.bis === x.auszug.ende_ms);
|
||||
ok(gleich.length === 3 && gleich.every(Boolean),
|
||||
`in allen ${gleich.length} Seiten mit Rangliste ist die Wertung `
|
||||
+ "gleich dem Kampagnenzeitraum -- gemessen, nicht angenommen");
|
||||
/* DAS IST DER GEWINN: zwei Gruppen hier, sechs bei
|
||||
Gipfelstürmer. „Gegen wen trete ich an" stand vorher
|
||||
nirgends auf der Kachel. */
|
||||
ok(h.kuer.rangliste.reiter.length === 2
|
||||
&& g.kuer.rangliste.reiter.length === 6,
|
||||
`die Gruppen unterscheiden sich sehr wohl: `
|
||||
+ `${JSON.stringify(h.kuer.rangliste.reiter)} gegen `
|
||||
+ `${g.kuer.rangliste.reiter.length} Ligen`);
|
||||
/* UND IM REGELTEXT STEHT ES AUCH. Ohne diese Zeile wäre
|
||||
geprüft, dass gelesen wird -- nicht, dass es ankommt. */
|
||||
const rt = regelnText(h.kuer, h.hinweise);
|
||||
ok(/## RANGLISTE/.test(rt) && /Gruppen: /.test(rt),
|
||||
"und die Rangliste steht als eigener Abschnitt im Regeltext");
|
||||
|
||||
/* Der zweite Bildbaustein. */
|
||||
ok(h.auszug.weitere_bilder.length === 2,
|
||||
`zwei weitere Bilder aus live_banner (${h.auszug.weitere_bilder.length})`);
|
||||
ok(h.auszug.weitere_bilder.every((u) => /^https:\/\//.test(u)),
|
||||
"und beide mit absoluter Adresse");
|
||||
/* GEGENPROBE: Geholt wird trotzdem nur EINES -- das Banner bleibt
|
||||
das aus live_top_image. */
|
||||
ok(h.kuer.bannerUrl !== h.auszug.weitere_bilder[0],
|
||||
"das Banner kommt weiterhin aus live_top_image, nicht von dort");
|
||||
|
||||
/* Und das deutsche Datum. */
|
||||
ok(deutschesDatum("2026-11-04") === "04.11.2026",
|
||||
`deutschesDatum: ${deutschesDatum("2026-11-04")}`);
|
||||
ok(deutschesDatum("Unsinn") === null, "und Unsinn gibt nichts");
|
||||
}
|
||||
|
||||
/* =====================================================================
|
||||
14. DIE GLIEDERUNG DES REGELTEXTES
|
||||
|
||||
Filipe, mit Bildschirmfoto: „man soll die titel in den texten
|
||||
besser erkennen und sehen was zu was passt."
|
||||
|
||||
Geprüft wird hier die REGEL, nicht die Anzeige -- was auf dem
|
||||
Bildschirm daraus wird, misst pruef-eventkarte.mjs, Abschnitt 4b.
|
||||
===================================================================== */
|
||||
melde("");
|
||||
melde("=== 14. Der Regeltext, gegliedert ===");
|
||||
|
||||
{
|
||||
/* ZEICHENGLEICH MIT DEM, WAS AM 07.10.2026 WIRKLICH IN DER
|
||||
DATENBANK STAND (Eintrag 89, „LIVE Glow Up"). Erfundene
|
||||
Testdaten hätten genau das verdeckt, worum es geht. */
|
||||
const echt = [
|
||||
"* Diese Kampagne hat keinen Einleitungstext — der Inhalt steht in den Klapptexten.",
|
||||
"* Diese Kampagne hat keine Preisliste auf der Seite.",
|
||||
"",
|
||||
"SAMMLE DEIN FEUERWERK — Erhalte doppelte Punkte für die Rangliste",
|
||||
"· Feuerwerk (Sammle Feuerwerk in deinem LIVE!)",
|
||||
"",
|
||||
"INFOS ZUR KAMPAGNE",
|
||||
"LIVE Glow Up ist eine exklusive 4-wöchige Wachstum Challenge.",
|
||||
"",
|
||||
"KAMPAGNENZEITPLAN",
|
||||
"· Woche 1: 5. - 11.10.",
|
||||
"",
|
||||
"TEILNAHME",
|
||||
"Länder: DE, CH, AT, LI, LU",
|
||||
].join("\n");
|
||||
|
||||
const ab = gliederung(echt, {
|
||||
geschenke: [{ name: "Feuerwerk", preis: null, hinweis: "Sammle Feuerwerk in deinem LIVE!" }],
|
||||
});
|
||||
const titel = ab.map((a) => a.titel);
|
||||
ok(ab.length === 5,
|
||||
`fünf Abschnitte aus dem echten Text (${ab.length}): ${JSON.stringify(titel)}`);
|
||||
|
||||
/* ---- Die Überschrift wird an der Großschreibung erkannt ---- */
|
||||
ok(titel[1] === "SAMMLE DEIN FEUERWERK",
|
||||
`die Überschrift ohne ihren Untertitel: ${JSON.stringify(titel[1])}`);
|
||||
ok(ab[1].unter === "Erhalte doppelte Punkte für die Rangliste",
|
||||
`der Untertitel getrennt dahinter: ${JSON.stringify(ab[1].unter)}`);
|
||||
|
||||
/* ---- Die vier Zeilensorten ---------------------------------- */
|
||||
const arten = ab.flatMap((a) => a.zeilen.map((z) => z.art));
|
||||
ok(new Set(arten).size >= 3 && arten.includes("hinweis")
|
||||
&& arten.includes("geschenk") && arten.includes("punkt") && arten.includes("text"),
|
||||
`alle vier Zeilensorten kommen vor: ${JSON.stringify([...new Set(arten)])}`);
|
||||
|
||||
/* ---- Die Hinweise stehen für sich, nicht zwischen den Regeln - */
|
||||
ok(ab[0].titel === null && ab[0].art === "hinweise" && ab[0].zeilen.length === 2,
|
||||
`die zwei Hinweise bilden den ersten, titellosen Abschnitt`);
|
||||
ok(!ab.slice(1).some((a) => a.zeilen.some((z) => z.art === "hinweis")),
|
||||
"und keiner steckt in einem anderen Abschnitt -- das war der Mangel");
|
||||
|
||||
/* ---- Das Geschenk kommt AUS DER LISTE, nicht aus dem Text --- */
|
||||
const gabe = ab[1].zeilen.find((z) => z.art === "geschenk");
|
||||
ok(gabe?.text === "Feuerwerk", `der Name: ${JSON.stringify(gabe?.text)}`);
|
||||
/* `Number(null)` IST 0 -- und „0 Coins" wäre eine Preisangabe,
|
||||
und zwar eine falsche. Dieser Fehler war am 07.10.2026 wirklich
|
||||
da, an drei Stellen. */
|
||||
ok(gabe && gabe.coins === null,
|
||||
`ohne Preis bleibt ohne Preis, nicht 0: ${JSON.stringify(gabe?.coins)}`);
|
||||
ok(gabe?.zusatz === "Sammle Feuerwerk in deinem LIVE!",
|
||||
`stattdessen steht da, wie man es bekommt: ${JSON.stringify(gabe?.zusatz)}`);
|
||||
ok(ab[1].art === "geschenke",
|
||||
`der Abschnitt mit einem Geschenk ist ein Geschenkabschnitt (${ab[1].art})`);
|
||||
|
||||
/* GEGENPROBE: Ohne Geschenkliste ist dieselbe Zeile ein
|
||||
gewöhnlicher Punkt. Sonst wäre unbelegt, dass die LISTE
|
||||
entscheidet. */
|
||||
const ohne = gliederung(echt);
|
||||
const z1 = ohne[1].zeilen.find((z) => z.text.startsWith("Feuerwerk"));
|
||||
ok(z1?.art === "punkt",
|
||||
`ohne Liste ist es ein Punkt, kein Geschenk (${z1?.art})`);
|
||||
|
||||
/* Und der Coinpreis aus dem TEXT greift, wo es keine Liste gibt --
|
||||
für von Hand getippte Zeilen. */
|
||||
const vonHand = gliederung("## GESCHENKE\n· Rose — 1 Coin\n· Löwe — 29999 Coins");
|
||||
const rose = vonHand[0].zeilen.find((z) => z.text === "Rose");
|
||||
const loewe = vonHand[0].zeilen.find((z) => z.text === "Löwe");
|
||||
ok(rose?.coins === 1 && loewe?.coins === 29999,
|
||||
`aus dem Text gelesen: ${JSON.stringify([rose?.coins, loewe?.coins])}`);
|
||||
|
||||
/* DIE LÄNGSTEN NAMEN ZUERST: „Rose" und „Rose Gold" in derselben
|
||||
Kampagne -- ein Vergleich von vorne träfe bei „Rose Gold" den
|
||||
Eintrag „Rose" und schriebe dessen Preis an. */
|
||||
const zwei = gliederung("## GESCHENKE\n· Rose Gold\n· Rose", {
|
||||
geschenke: [{ name: "Rose", preis: 10 }, { name: "Rose Gold", preis: 500 }],
|
||||
});
|
||||
const gold = zwei[0].zeilen.find((z) => z.text === "Rose Gold");
|
||||
ok(gold?.coins === 500,
|
||||
`„Rose Gold" bekommt 500, nicht die 10 von „Rose" (${gold?.coins})`);
|
||||
|
||||
/* ---- Die Gegenprobe zur Überschriftenerkennung -------------- */
|
||||
/* Eine Zeile aus nichts als Ländercodes IST großgeschrieben und
|
||||
würde zur Überschrift. `regelnText` schreibt deshalb „Länder:
|
||||
..." -- die Kleinbuchstaben im Wort davor sind die Sicherung. */
|
||||
ok(ab[4].zeilen[0].art === "text",
|
||||
`„Länder: DE, CH, ..." bleibt ein Absatz (${ab[4].zeilen[0].art})`);
|
||||
const nackt = gliederung("DE, CH, AT");
|
||||
ok(nackt[0]?.titel === "DE, CH, AT" || nackt.length === 0,
|
||||
"ohne Wort davor wäre es tatsächlich eine Überschrift -- "
|
||||
+ "deshalb schreibt regelnText nie nackte Codes");
|
||||
|
||||
/* Keine Überschrift ohne Buchstaben, keine mit Satzzeichen am
|
||||
Ende, keine über 80 Zeichen. */
|
||||
const proben = [
|
||||
["12.10.", "eine Zahl ist keine Überschrift"],
|
||||
["DAS IST EIN GESCHRIENER SATZ.", "ein Satz mit Punkt am Ende auch nicht"],
|
||||
["Gemischte Schreibweise", "und Kleinbuchstaben erst recht nicht"],
|
||||
["A".repeat(90), "und nichts über 80 Zeichen"],
|
||||
];
|
||||
for (const [zeile, was] of proben) {
|
||||
const p = gliederung(zeile);
|
||||
ok(p.length === 1 && p[0].titel === null, `${was}: ${JSON.stringify(zeile.slice(0, 20))}`);
|
||||
}
|
||||
|
||||
/* ---- Zwei Stufen ------------------------------------------- */
|
||||
const stufen = gliederung("## OBEN\nText.\n### Unten\nMehr Text.");
|
||||
ok(stufen.length === 2 && stufen[0].stufe === 2 && stufen[1].stufe === 3,
|
||||
`## ist Stufe 2, ### ist Stufe 3: ${JSON.stringify(stufen.map((a) => [a.titel, a.stufe]))}`);
|
||||
|
||||
/* EINE ÜBERSCHRIFT ÜBER LAUTER UNTERABSCHNITTEN BLEIBT STEHEN.
|
||||
Das war beim ersten Lauf ein echter Verlust: „WELCHE LIGA BIN
|
||||
ICH?" hat keine eigene Zeile, nur sechs Unterabschnitte -- und
|
||||
verschwand deshalb. */
|
||||
const ueber = gliederung("## WELCHE LIGA BIN ICH?\n### Superstars\nBedingung.");
|
||||
ok(ueber.length === 2 && ueber[0].titel === "WELCHE LIGA BIN ICH?",
|
||||
`die Überschrift über Unterabschnitten bleibt: ${JSON.stringify(ueber.map((a) => a.titel))}`);
|
||||
/* GEGENPROBE: Eine Überschrift über NICHTS fällt weg -- sie
|
||||
entsteht, wenn jemand den Inhalt löscht und den Titel stehen
|
||||
lässt, und sieht aus wie ein Ladefehler. */
|
||||
ok(gliederung("## LEER").length === 0,
|
||||
"eine Überschrift über nichts fällt weg");
|
||||
/* Und sie ist kein Hinweisblock -- `every()` auf einem leeren
|
||||
Feld ist `true` (CLAUDE.md, 01.09.2026). */
|
||||
ok(ueber[0].art !== "hinweise",
|
||||
`der leere Abschnitt ist kein stiller Hinweiskasten (${ueber[0].art})`);
|
||||
|
||||
/* ---- Die Frage mit Antwort darunter ------------------------ */
|
||||
const frage = gliederung("## INFO\nWie geht das?\nSo geht das.\nHast du Fragen?");
|
||||
const fz = frage[0].zeilen;
|
||||
ok(fz[0].art === "frage", `eine Frage MIT Antwort darunter: ${JSON.stringify(fz[0])}`);
|
||||
/* GEGENPROBE: Eine Frage, auf die NICHTS folgt, ist keine
|
||||
Überschrift, sondern eine Frage. */
|
||||
ok(fz[2].art === "text",
|
||||
`die letzte Zeile bleibt ein Absatz: ${JSON.stringify(fz[2])}`);
|
||||
|
||||
/* ---- Nichts geht verloren ---------------------------------- */
|
||||
/* DIE WICHTIGSTE ZEILE DIESES ABSCHNITTS: Die Gliederung ist eine
|
||||
Heuristik. Sie darf sich irren -- aber sie darf nichts
|
||||
verschlucken. Gezählt wird gegen den Eingabetext. */
|
||||
const eingabe = echt.split(/\r?\n/).map((x) => x.trim()).filter(Boolean);
|
||||
const ausgabe = ab.flatMap((a) => [
|
||||
...(a.titel ? [1] : []), ...a.zeilen.map(() => 1),
|
||||
]).length;
|
||||
ok(ausgabe === eingabe.length,
|
||||
`${eingabe.length} Zeilen hinein, ${ausgabe} Überschriften und Zeilen heraus`);
|
||||
|
||||
/* Und am echten, langen Text dasselbe. */
|
||||
const lang = regelnText(leseKampagne(seite.gipfel).kuer, []);
|
||||
const langAb = gliederung(lang, { geschenke: leseKampagne(seite.gipfel).kuer.geschenke });
|
||||
const langEin = lang.split(/\r?\n/).map((x) => x.trim()).filter(Boolean).length;
|
||||
const langAus = langAb.reduce((s, a) => s + (a.titel ? 1 : 0) + a.zeilen.length, 0);
|
||||
ok(langAus === langEin,
|
||||
`am echten Regeltext ebenfalls: ${langEin} hinein, ${langAus} heraus `
|
||||
+ `(${langAb.length} Abschnitte)`);
|
||||
}
|
||||
|
||||
melde("");
|
||||
melde(`${geprueft} Pruefungen, ${fehler} Fehler`);
|
||||
melde(fehler ? "NICHT IN ORDNUNG" : "ALLES IN ORDNUNG");
|
||||
|
||||
Reference in New Issue
Block a user