fix(studio): Lauf-Anzeige ohne geöffneten Fall zurücksetzen

Nachtrag zu ffbdd51. _renderLauf lief nur über _stagesIdle, und das wiederum
nur beim Laden eines Falls. Schloss man den Fall oder öffnete das Studio ohne
Fall, blieb der Stand-Kasten mit den Angaben des zuletzt geöffneten Falls
stehen, beim allerersten Aufruf war er leer.

Zwei Absicherungen. Der Leerzustand setzt Fall und Frischedaten zurück und
zeichnet die Spalte einmal neu, und das Dokument bringt den Ausgangstext
bereits mit.

Sechs Prüfungen dafür.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
claude-dev
2026-08-02 19:28:39 +00:00
Ursprung ffbdd5170c
Commit f8d32b1d18
3 geänderte Dateien mit 346 neuen und 315 gelöschten Zeilen

Datei anzeigen

@@ -170,6 +170,14 @@ const Studio = {
const cols = document.getElementById('studio-cols'); const cols = document.getElementById('studio-cols');
if (cols) cols.classList.toggle('idle', on); if (cols) cols.classList.toggle('idle', on);
document.getElementById('studio-empty').style.display = on ? '' : 'none'; document.getElementById('studio-empty').style.display = on ? '' : 'none';
// Ohne Fall wuerde die Lauf-Anzeige sonst mit dem Stand des zuletzt
// geoeffneten Falls stehen bleiben. Nur beim Leerschalten neu zeichnen,
// beim Oeffnen macht das loadDetail mit den richtigen Daten.
if (on) {
this.incident = null;
this.fresh = null;
this._renderLauf();
}
}, },
// === Linke Spalte: Reiter "Fälle" / "Quellen" === // === Linke Spalte: Reiter "Fälle" / "Quellen" ===

Datei anzeigen

@@ -396,7 +396,7 @@
selben Ziel nebeneinander stehen. Die Beschriftung des Knopfes selben Ziel nebeneinander stehen. Die Beschriftung des Knopfes
richtet sich nach dem Zustand des Falls, siehe _renderLauf. --> richtet sich nach dem Zustand des Falls, siehe _renderLauf. -->
<div class="lauf-stand" id="lauf-stand"> <div class="lauf-stand" id="lauf-stand">
<div class="ls-haupt" id="ls-haupt"></div> <div class="ls-haupt" id="ls-haupt">Kein Fall geöffnet</div>
<div class="ls-neben" id="ls-neben"></div> <div class="ls-neben" id="ls-neben"></div>
<button class="ls-btn" id="ls-btn" data-stage="full" disabled <button class="ls-btn" id="ls-btn" data-stage="full" disabled
onclick="Studio.laufKnopf()"> onclick="Studio.laufKnopf()">

Datei anzeigen

@@ -1,314 +1,337 @@
/** /**
* Testet die Lauf-Anzeige der Bausteinspalte ohne Browser. * Testet die Lauf-Anzeige der Bausteinspalte ohne Browser.
* *
* Die Methoden werden aus studio.js herausgeloest und gegen ein schlankes * Die Methoden werden aus studio.js herausgeloest und gegen ein schlankes
* DOM-Double laufen gelassen. Kein Netzzugriff, keine Kosten. * DOM-Double laufen gelassen. Kein Netzzugriff, keine Kosten.
* *
* Aufruf aus dem Projektstamm: * Aufruf aus dem Projektstamm:
* node tests/test_lauf.js * node tests/test_lauf.js
*/ */
'use strict'; 'use strict';
const fs = require('fs'); const fs = require('fs');
const path = require('path'); const path = require('path');
const vm = require('vm'); const vm = require('vm');
let ok = 0; let ok = 0;
let fail = 0; let fail = 0;
function pruefe(name, bedingung, extra) { function pruefe(name, bedingung, extra) {
if (bedingung) { if (bedingung) {
ok++; ok++;
console.log(' OK ' + name); console.log(' OK ' + name);
} else { } else {
fail++; fail++;
console.log(' FEHL ' + name + ' ' console.log(' FEHL ' + name + ' '
+ (extra === undefined ? '' : JSON.stringify(extra))); + (extra === undefined ? '' : JSON.stringify(extra)));
} }
} }
// --- DOM-Double ------------------------------------------------------------ // --- DOM-Double ------------------------------------------------------------
function element(id) { function element(id) {
const klassen = new Set(); const klassen = new Set();
const el = { const el = {
id: id, id: id,
innerHTML: '', innerHTML: '',
textContent: '', textContent: '',
title: '', title: '',
hidden: false, hidden: false,
disabled: false, disabled: false,
classList: { classList: {
add: (n) => klassen.add(n), add: (n) => klassen.add(n),
remove: (n) => klassen.delete(n), remove: (n) => klassen.delete(n),
toggle: (n, an) => { if (an) klassen.add(n); else klassen.delete(n); }, toggle: (n, an) => { if (an) klassen.add(n); else klassen.delete(n); },
contains: (n) => klassen.has(n), contains: (n) => klassen.has(n),
}, },
_kinder: {}, style: {},
querySelector(sel) { _kinder: {},
const k = sel.replace('.', ''); querySelector(sel) {
if (!this._kinder[k]) this._kinder[k] = element(k); const k = sel.replace('.', '');
return this._kinder[k]; if (!this._kinder[k]) this._kinder[k] = element(k);
}, return this._kinder[k];
}; },
return el; };
} return el;
}
const IDS = ['lauf-stand', 'ls-haupt', 'ls-neben', 'ls-btn', 'ls-hinweis',
'lauf-kette', 'lauf-fortschritt', 'lauf-schritte', 'start-panel', const IDS = ['lauf-stand', 'ls-haupt', 'ls-neben', 'ls-btn', 'ls-hinweis',
'art-summary-meta', 'art-fc-meta', 'art-snap-meta', 'art-tl-meta']; 'lauf-kette', 'lauf-fortschritt', 'lauf-schritte', 'start-panel',
const elemente = {}; 'art-summary-meta', 'art-fc-meta', 'art-snap-meta', 'art-tl-meta',
const meldungen = []; 'studio-cols', 'studio-empty'];
const aufrufe = []; const elemente = {};
const meldungen = [];
const umgebung = { const aufrufe = [];
document: {
getElementById: (id) => elemente[id] || null, const umgebung = {
querySelectorAll: () => [], document: {
}, getElementById: (id) => elemente[id] || null,
UI: { querySelectorAll: () => [],
showToast: (t, a) => meldungen.push({ t, a }), },
escape: (s) => String(s === undefined || s === null ? '' : s) UI: {
.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'), showToast: (t, a) => meldungen.push({ t, a }),
}, escape: (s) => String(s === undefined || s === null ? '' : s)
CSS: { escape: (s) => s }, .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;'),
localStorage: { _d: {}, setItem(k, v) { this._d[k] = v; }, getItem(k) { return this._d[k]; } }, },
console: console, CSS: { escape: (s) => s },
}; localStorage: { _d: {}, setItem(k, v) { this._d[k] = v; }, getItem(k) { return this._d[k]; } },
umgebung.window = umgebung; console: console,
};
// --- Den Lauf-Block aus studio.js herausloesen ----------------------------- umgebung.window = umgebung;
const quelle = fs.readFileSync(
path.join(__dirname, '..', 'src', 'static', 'js', 'studio.js'), 'utf8'); // --- Den Lauf-Block aus studio.js herausloesen -----------------------------
const von = quelle.indexOf(' // === Der Lauf (Spalte 3) ==='); const quelle = fs.readFileSync(
const bis = quelle.indexOf(' async runStage(stage) {', von); path.join(__dirname, '..', 'src', 'static', 'js', 'studio.js'), 'utf8');
if (von === -1 || bis === -1) { const von = quelle.indexOf(' // === Der Lauf (Spalte 3) ===');
console.log('FEHLER: Lauf-Block in studio.js nicht gefunden'); const bis = quelle.indexOf(' async runStage(stage) {', von);
process.exit(1); if (von === -1 || bis === -1) {
} console.log('FEHLER: Lauf-Block in studio.js nicht gefunden');
process.exit(1);
const kontext = vm.createContext(umgebung); }
const Studio = vm.runInContext(
'var Studio = {\n' + quelle.slice(von, bis) + '\n' const kontext = vm.createContext(umgebung);
+ ' incident: null, fresh: null, _runningStage: null,\n' const Studio = vm.runInContext(
+ ' async runStage(s) { this._letzterStart = s; },\n' 'var Studio = {\n' + quelle.slice(von, bis) + '\n'
+ '};\nStudio;', kontext); + ' incident: null, fresh: null, _runningStage: null,\n'
+ ' async runStage(s) { this._letzterStart = s; },\n'
const LAGE = { id: 7, title: 'Ceuta', type: 'adhoc' }; // _showEmpty steht ausserhalb des herausgeloesten Blocks, deshalb hier
// wortgleich nachgebildet.
// Vier Zustaende, wie sie im Betrieb wirklich vorkommen + ' _showEmpty(on) {\n'
const FRISCH = { + ' const c = document.getElementById("studio-cols");\n'
leer: { articles: 0, summary: {}, factcheck: {}, geoparse: {} }, + ' if (c) c.classList.toggle("idle", on);\n'
veraltet: { articles: 85, summary: { exists: true, last: '2026-08-02 18:10', pending: 3 }, + ' document.getElementById("studio-empty").style.display = on ? "" : "none";\n'
factcheck: { exists: true, last: '2026-08-02 16:00', facts: 19, pending: 3 }, + ' if (on) { this.incident = null; this.fresh = null; this._renderLauf(); }\n'
geoparse: { pending: 0 }, snapshots: 4, events: 12 }, + ' },\n'
aktuell: { articles: 85, summary: { exists: true, last: '2026-08-02 18:40', pending: 0 }, + '};\nStudio;', kontext);
factcheck: { exists: true, last: '2026-08-02 18:41', facts: 19, pending: 0 },
geoparse: { pending: 0 }, snapshots: 4, events: 12 }, const LAGE = { id: 7, title: 'Ceuta', type: 'adhoc' };
halb: { articles: 85, summary: { exists: false }, factcheck: { exists: false },
geoparse: { pending: 85 } }, // Vier Zustaende, wie sie im Betrieb wirklich vorkommen
}; const FRISCH = {
leer: { articles: 0, summary: {}, factcheck: {}, geoparse: {} },
function neu(zustand, ueber) { veraltet: { articles: 85, summary: { exists: true, last: '2026-08-02 18:10', pending: 3 },
for (const id of IDS) elemente[id] = element(id); factcheck: { exists: true, last: '2026-08-02 16:00', facts: 19, pending: 3 },
Studio.incident = Object.assign({}, LAGE, ueber || {}); geoparse: { pending: 0 }, snapshots: 4, events: 12 },
Studio.fresh = FRISCH[zustand] ? JSON.parse(JSON.stringify(FRISCH[zustand])) : null; aktuell: { articles: 85, summary: { exists: true, last: '2026-08-02 18:40', pending: 0 },
Studio._runningStage = null; factcheck: { exists: true, last: '2026-08-02 18:41', facts: 19, pending: 0 },
Studio._schrittOffen = null; geoparse: { pending: 0 }, snapshots: 4, events: 12 },
Studio._letzterStart = null; halb: { articles: 85, summary: { exists: false }, factcheck: { exists: false },
meldungen.length = 0; geoparse: { pending: 85 } },
aufrufe.length = 0; };
}
function neu(zustand, ueber) {
const g = (id) => elemente[id]; for (const id of IDS) elemente[id] = element(id);
const knopfText = () => g('ls-btn').querySelector('.ls-text').textContent; Studio.incident = Object.assign({}, LAGE, ueber || {});
Studio.fresh = FRISCH[zustand] ? JSON.parse(JSON.stringify(FRISCH[zustand])) : null;
async function main() { Studio._runningStage = null;
console.log('\nA) Der Startknopf richtet sich nach dem Zustand'); Studio._schrittOffen = null;
neu('leer'); Studio._letzterStart = null;
Studio._renderLauf(); meldungen.length = 0;
pruefe('A1 leerer Fall lädt zum Start ein', knopfText() === 'Lauf starten', knopfText()); aufrufe.length = 0;
pruefe('A2 der Stand sagt warum', g('ls-haupt').textContent.includes('Noch keine Artikel'), }
g('ls-haupt').textContent);
pruefe('A3 der Kasten ist hervorgehoben', g('lauf-stand').classList.contains('betont')); const g = (id) => elemente[id];
pruefe('A4 mit einem Hinweis, was danach kommt', const knopfText = () => g('ls-btn').querySelector('.ls-text').textContent;
g('ls-hinweis').textContent.length > 10 && g('ls-hinweis').hidden === false);
async function main() {
neu('leer', { type: 'research' }); console.log('\nA) Der Startknopf richtet sich nach dem Zustand');
Studio._renderLauf(); neu('leer');
pruefe('A5 bei einer Recherche heißt es Recherche starten', Studio._renderLauf();
knopfText() === 'Recherche starten', knopfText()); pruefe('A1 leerer Fall lädt zum Start ein', knopfText() === 'Lauf starten', knopfText());
pruefe('A2 der Stand sagt warum', g('ls-haupt').textContent.includes('Noch keine Artikel'),
neu('veraltet'); g('ls-haupt').textContent);
Studio._renderLauf(); pruefe('A3 der Kasten ist hervorgehoben', g('lauf-stand').classList.contains('betont'));
pruefe('A6 bei neuen Artikeln heißt es aktualisieren', pruefe('A4 mit einem Hinweis, was danach kommt',
knopfText() === 'Jetzt aktualisieren', knopfText()); g('ls-hinweis').textContent.length > 10 && g('ls-hinweis').hidden === false);
pruefe('A7 der Stand nennt die Zahl',
g('ls-haupt').textContent.includes('3 neue Artikel'), g('ls-haupt').textContent); neu('leer', { type: 'research' });
pruefe('A8 und warnt', g('ls-haupt').classList.contains('warn')); Studio._renderLauf();
pruefe('A5 bei einer Recherche heißt es Recherche starten',
neu('aktuell'); knopfText() === 'Recherche starten', knopfText());
Studio._renderLauf();
pruefe('A9 wenn alles frisch ist heißt es erneut durchlaufen', neu('veraltet');
knopfText() === 'Erneut durchlaufen', knopfText()); Studio._renderLauf();
pruefe('A10 der Kasten ist dann ruhig', !g('lauf-stand').classList.contains('betont')); pruefe('A6 bei neuen Artikeln heißt es aktualisieren',
pruefe('A11 und der Hinweis verschwindet', g('ls-hinweis').hidden === true); knopfText() === 'Jetzt aktualisieren', knopfText());
pruefe('A12 der Bestand steht daneben', pruefe('A7 der Stand nennt die Zahl',
g('ls-neben').textContent === '85 Artikel im Bestand', g('ls-neben').textContent); g('ls-haupt').textContent.includes('3 neue Artikel'), g('ls-haupt').textContent);
pruefe('A8 und warnt', g('ls-haupt').classList.contains('warn'));
console.log('\nB) Während eines Laufs');
neu('veraltet'); neu('aktuell');
Studio._runningStage = 'full'; Studio._renderLauf();
Studio._renderLauf(); pruefe('A9 wenn alles frisch ist heißt es erneut durchlaufen',
pruefe('B1 der Knopf bricht ab', knopfText() === 'Abbrechen', knopfText()); knopfText() === 'Erneut durchlaufen', knopfText());
pruefe('B2 er ist rot abgesetzt', g('ls-btn').classList.contains('abbrechen')); pruefe('A10 der Kasten ist dann ruhig', !g('lauf-stand').classList.contains('betont'));
pruefe('B3 er bleibt klickbar', g('ls-btn').disabled === false); pruefe('A11 und der Hinweis verschwindet', g('ls-hinweis').hidden === true);
pruefe('B4 der Stand sagt, was läuft', pruefe('A12 der Bestand steht daneben',
g('ls-haupt').textContent.includes('Kompletter Lauf'), g('ls-haupt').textContent); g('ls-neben').textContent === '85 Artikel im Bestand', g('ls-neben').textContent);
neu('veraltet'); console.log('\nB) Während eines Laufs');
Studio._runningStage = 'analyze'; neu('veraltet');
Studio._renderLauf(); Studio._runningStage = 'full';
pruefe('B5 ein nicht abbrechbarer Schritt sperrt den Knopf', g('ls-btn').disabled === true); Studio._renderLauf();
pruefe('B6 und wird nicht als Abbruch angeboten', pruefe('B1 der Knopf bricht ab', knopfText() === 'Abbrechen', knopfText());
!g('ls-btn').classList.contains('abbrechen')); pruefe('B2 er ist rot abgesetzt', g('ls-btn').classList.contains('abbrechen'));
pruefe('B7 der Stand nennt den Schritt beim Ergebnisnamen', pruefe('B3 er bleibt klickbar', g('ls-btn').disabled === false);
g('ls-haupt').textContent.includes('Lagebild schreiben'), g('ls-haupt').textContent); pruefe('B4 der Stand sagt, was läuft',
g('ls-haupt').textContent.includes('Kompletter Lauf'), g('ls-haupt').textContent);
console.log('\nC) Der Knopf trifft den richtigen Schritt');
neu('veraltet'); neu('veraltet');
await Studio.laufKnopf(); Studio._runningStage = 'analyze';
pruefe('C1 ohne Lauf startet er den kompletten Lauf', Studio._letzterStart === 'full'); Studio._renderLauf();
pruefe('B5 ein nicht abbrechbarer Schritt sperrt den Knopf', g('ls-btn').disabled === true);
neu('veraltet'); pruefe('B6 und wird nicht als Abbruch angeboten',
Studio._runningStage = 'collect'; !g('ls-btn').classList.contains('abbrechen'));
await Studio.laufKnopf(); pruefe('B7 der Stand nennt den Schritt beim Ergebnisnamen',
pruefe('C2 ein laufendes Sammeln wird auch als Sammeln abgebrochen', g('ls-haupt').textContent.includes('Lagebild schreiben'), g('ls-haupt').textContent);
Studio._letzterStart === 'collect', Studio._letzterStart);
console.log('\nC) Der Knopf trifft den richtigen Schritt');
neu('veraltet'); neu('veraltet');
Studio._runningStage = 'analyze'; await Studio.laufKnopf();
await Studio.laufKnopf(); pruefe('C1 ohne Lauf startet er den kompletten Lauf', Studio._letzterStart === 'full');
pruefe('C3 ein nicht abbrechbarer Schritt löst nichts aus',
Studio._letzterStart === null, Studio._letzterStart); neu('veraltet');
pruefe('C4 und sagt das auch', meldungen.length === 1 && meldungen[0].a === 'info', meldungen); Studio._runningStage = 'collect';
await Studio.laufKnopf();
console.log('\nD) Die Kette'); pruefe('C2 ein laufendes Sammeln wird auch als Sammeln abgebrochen',
neu('aktuell'); Studio._letzterStart === 'collect', Studio._letzterStart);
Studio._renderLauf();
const kette = g('lauf-kette').innerHTML; neu('veraltet');
pruefe('D1 vier Knoten', (kette.match(/class="kn /g) || []).length === 4, Studio._runningStage = 'analyze';
(kette.match(/class="kn /g) || []).length); await Studio.laufKnopf();
pruefe('D2 drei Verbindungen', (kette.match(/kn-linie/g) || []).length === 3); pruefe('C3 ein nicht abbrechbarer Schritt löst nichts aus',
pruefe('D3 alle vier erledigt', (kette.match(/kn-fertig/g) || []).length === 4); Studio._letzterStart === null, Studio._letzterStart);
pruefe('D4 der Zähler stimmt', pruefe('C4 und sagt das auch', meldungen.length === 1 && meldungen[0].a === 'info', meldungen);
g('lauf-fortschritt').textContent === '4 von 4 Schritten auf dem neuesten Stand',
g('lauf-fortschritt').textContent); console.log('\nD) Die Kette');
neu('aktuell');
neu('veraltet'); Studio._renderLauf();
Studio._renderLauf(); const kette = g('lauf-kette').innerHTML;
pruefe('D5 bei veralteten Ergebnissen sind nicht alle grün', pruefe('D1 vier Knoten', (kette.match(/class="kn /g) || []).length === 4,
(g('lauf-kette').innerHTML.match(/kn-fertig/g) || []).length === 2, (kette.match(/class="kn /g) || []).length);
(g('lauf-kette').innerHTML.match(/kn-fertig/g) || []).length); pruefe('D2 drei Verbindungen', (kette.match(/kn-linie/g) || []).length === 3);
pruefe('D6 zwei stehen auf veraltet', pruefe('D3 alle vier erledigt', (kette.match(/kn-fertig/g) || []).length === 4);
(g('lauf-kette').innerHTML.match(/kn-veraltet/g) || []).length === 2); pruefe('D4 der Zähler stimmt',
pruefe('D7 der Zähler zählt nur die fertigen', g('lauf-fortschritt').textContent === '4 von 4 Schritten auf dem neuesten Stand',
g('lauf-fortschritt').textContent.startsWith('2 von 4'), g('lauf-fortschritt').textContent);
g('lauf-fortschritt').textContent);
neu('veraltet');
neu('leer'); Studio._renderLauf();
Studio._renderLauf(); pruefe('D5 bei veralteten Ergebnissen sind nicht alle grün',
pruefe('D8 ohne Artikel sind drei Schritte gesperrt', (g('lauf-kette').innerHTML.match(/kn-fertig/g) || []).length === 2,
(g('lauf-kette').innerHTML.match(/kn-gesperrt/g) || []).length === 3, (g('lauf-kette').innerHTML.match(/kn-fertig/g) || []).length);
(g('lauf-kette').innerHTML.match(/kn-gesperrt/g) || []).length); pruefe('D6 zwei stehen auf veraltet',
(g('lauf-kette').innerHTML.match(/kn-veraltet/g) || []).length === 2);
console.log('\nE) Die Schritte'); pruefe('D7 der Zähler zählt nur die fertigen',
neu('halb'); g('lauf-fortschritt').textContent.startsWith('2 von 4'),
Studio._renderLauf(); g('lauf-fortschritt').textContent);
const l = g('lauf-schritte').innerHTML;
pruefe('E1 vier Schritte', (l.match(/class="schritt /g) || []).length === 4, neu('leer');
(l.match(/class="schritt /g) || []).length); Studio._renderLauf();
pruefe('E2 Ergebnisnamen statt Erzeugernamen', pruefe('D8 ohne Artikel sind drei Schritte gesperrt',
l.includes('Artikel holen') && l.includes('Lagebild schreiben') (g('lauf-kette').innerHTML.match(/kn-gesperrt/g) || []).length === 3,
&& l.includes('Fakten prüfen') && l.includes('Orte erkennen')); (g('lauf-kette').innerHTML.match(/kn-gesperrt/g) || []).length);
pruefe('E3 keine Erzeugernamen mehr',
!l.includes('Geoparsing') && !l.includes('>Sammeln<') && !l.includes('>Analyse<')); console.log('\nE) Die Schritte');
pruefe('E4 der erste Schritt ist erledigt', l.includes('schritt schritt-fertig')); neu('halb');
pruefe('E5 die noch nicht erzeugten sind offen', Studio._renderLauf();
(l.match(/schritt-offen/g) || []).length === 2, (l.match(/schritt-offen/g) || []).length); const l = g('lauf-schritte').innerHTML;
pruefe('E6 die Karte meldet offene Artikel', pruefe('E1 vier Schritte', (l.match(/class="schritt /g) || []).length === 4,
l.includes('85 Artikel noch nicht verortet')); (l.match(/class="schritt /g) || []).length);
pruefe('E2 Ergebnisnamen statt Erzeugernamen',
neu('aktuell', { type: 'research' }); l.includes('Artikel holen') && l.includes('Lagebild schreiben')
Studio._renderLauf(); && l.includes('Fakten prüfen') && l.includes('Orte erkennen'));
pruefe('E7 bei einer Recherche heißt Schritt zwei Recherchebericht', pruefe('E3 keine Erzeugernamen mehr',
g('lauf-schritte').innerHTML.includes('Recherchebericht schreiben')); !l.includes('Geoparsing') && !l.includes('>Sammeln<') && !l.includes('>Analyse<'));
pruefe('E4 der erste Schritt ist erledigt', l.includes('schritt schritt-fertig'));
console.log('\nF) Kein Schritt startet etwas'); pruefe('E5 die noch nicht erzeugten sind offen',
for (const z of ['leer', 'veraltet', 'aktuell', 'halb']) { (l.match(/schritt-offen/g) || []).length === 2, (l.match(/schritt-offen/g) || []).length);
neu(z); pruefe('E6 die Karte meldet offene Artikel',
Studio._renderLauf(); l.includes('85 Artikel noch nicht verortet'));
const h = g('lauf-schritte').innerHTML;
pruefe('F ' + z + ' enthält keinen Startaufruf', neu('aktuell', { type: 'research' });
!h.includes('runStage') && !h.includes('laufKnopf')); Studio._renderLauf();
} pruefe('E7 bei einer Recherche heißt Schritt zwei Recherchebericht',
g('lauf-schritte').innerHTML.includes('Recherchebericht schreiben'));
console.log('\nG) Aufklappen erklärt nur');
neu('aktuell'); console.log('\nF) Kein Schritt startet etwas');
Studio._renderLauf(); for (const z of ['leer', 'veraltet', 'aktuell', 'halb']) {
pruefe('G1 anfangs ist nichts offen', neu(z);
!g('lauf-schritte').innerHTML.includes('schritt offen') Studio._renderLauf();
&& !g('lauf-schritte').innerHTML.includes(' offen"')); const h = g('lauf-schritte').innerHTML;
Studio.klappSchritt(1); pruefe('F ' + z + ' enthält keinen Startaufruf',
pruefe('G2 ein Klick öffnet den Schritt', !h.includes('runStage') && !h.includes('laufKnopf'));
g('lauf-schritte').innerHTML.includes(' offen"'), Studio._schrittOffen); }
pruefe('G3 und zeigt die Erklärung',
g('lauf-schritte').innerHTML.includes('Wertet alle Artikel aus')); console.log('\nG) Aufklappen erklärt nur');
Studio.klappSchritt(1); neu('aktuell');
pruefe('G4 nochmal klicken schließt ihn', Studio._schrittOffen === null); Studio._renderLauf();
Studio.klappSchritt(0); pruefe('G1 anfangs ist nichts offen',
Studio.klappSchritt(2); !g('lauf-schritte').innerHTML.includes('schritt offen')
pruefe('G5 es ist immer nur einer offen', Studio._schrittOffen === 2); && !g('lauf-schritte').innerHTML.includes(' offen"'));
Studio.klappSchritt(1);
console.log('\nH) Nebenangaben an den Ansichten'); pruefe('G2 ein Klick öffnet den Schritt',
neu('aktuell'); g('lauf-schritte').innerHTML.includes(' offen"'), Studio._schrittOffen);
Studio._renderLauf(); pruefe('G3 und zeigt die Erklärung',
pruefe('H1 Lagebild nennt die Artikelzahl', g('lauf-schritte').innerHTML.includes('Wertet alle Artikel aus'));
g('art-summary-meta').textContent.includes('85 Artikel ausgewertet'), Studio.klappSchritt(1);
g('art-summary-meta').textContent); pruefe('G4 nochmal klicken schließt ihn', Studio._schrittOffen === null);
pruefe('H2 Faktencheck nennt die Fakten', Studio.klappSchritt(0);
g('art-fc-meta').textContent.includes('19 Fakten'), g('art-fc-meta').textContent); Studio.klappSchritt(2);
pruefe('H3 frühere Berichte werden gezählt', g('art-snap-meta').textContent === '4'); pruefe('G5 es ist immer nur einer offen', Studio._schrittOffen === 2);
pruefe('H4 Ereignisse werden gezählt',
g('art-tl-meta').textContent === '12 Ereignisse', g('art-tl-meta').textContent); console.log('\nH) Nebenangaben an den Ansichten');
neu('aktuell');
neu('leer'); Studio._renderLauf();
Studio._renderLauf(); pruefe('H1 Lagebild nennt die Artikelzahl',
pruefe('H5 ohne Lauf steht noch nicht erzeugt', g('art-summary-meta').textContent.includes('85 Artikel ausgewertet'),
g('art-summary-meta').textContent === 'noch nicht erzeugt', g('art-summary-meta').textContent);
g('art-summary-meta').textContent); pruefe('H2 Faktencheck nennt die Fakten',
g('art-fc-meta').textContent.includes('19 Fakten'), g('art-fc-meta').textContent);
console.log('\nI) Ohne offenen Fall'); pruefe('H3 frühere Berichte werden gezählt', g('art-snap-meta').textContent === '4');
neu('leer'); pruefe('H4 Ereignisse werden gezählt',
Studio.incident = null; g('art-tl-meta').textContent === '12 Ereignisse', g('art-tl-meta').textContent);
Studio.fresh = null;
Studio._renderLauf(); neu('leer');
pruefe('I1 der Knopf ist gesperrt', g('ls-btn').disabled === true); Studio._renderLauf();
pruefe('I2 der Stand sagt es', g('ls-haupt').textContent === 'Kein Fall geöffnet', pruefe('H5 ohne Lauf steht noch nicht erzeugt',
g('ls-haupt').textContent); g('art-summary-meta').textContent === 'noch nicht erzeugt',
pruefe('I3 die Kette ist leer', g('lauf-kette').innerHTML === ''); g('art-summary-meta').textContent);
pruefe('I4 die Schrittliste ist leer', g('lauf-schritte').innerHTML === '');
pruefe('I5 nichts stürzt ab', true); console.log('\nI) Ohne offenen Fall');
neu('leer');
console.log('\nJ) Ohne Frischedaten'); Studio.incident = null;
neu('leer'); Studio.fresh = null;
Studio.fresh = null; Studio._renderLauf();
Studio._renderLauf(); pruefe('I1 der Knopf ist gesperrt', g('ls-btn').disabled === true);
pruefe('J1 die Anzeige bleibt bedienbar', g('ls-btn').disabled === false); pruefe('I2 der Stand sagt es', g('ls-haupt').textContent === 'Kein Fall geöffnet',
pruefe('J2 und behandelt den Fall wie leer', g('ls-haupt').textContent);
g('ls-haupt').textContent.includes('Noch keine Artikel')); pruefe('I3 die Kette ist leer', g('lauf-kette').innerHTML === '');
pruefe('I4 die Schrittliste ist leer', g('lauf-schritte').innerHTML === '');
console.log('\nErgebnis: ' + ok + ' bestanden, ' + fail + ' fehlgeschlagen'); pruefe('I5 nichts stürzt ab', true);
process.exit(fail ? 1 : 0);
} console.log('\nJ) Ohne Frischedaten');
neu('leer');
main(); Studio.fresh = null;
Studio._renderLauf();
pruefe('J1 die Anzeige bleibt bedienbar', g('ls-btn').disabled === false);
pruefe('J2 und behandelt den Fall wie leer',
g('ls-haupt').textContent.includes('Noch keine Artikel'));
console.log('\nK) Der Leerzustand räumt die Anzeige auf');
neu('aktuell');
Studio._renderLauf();
pruefe('K1 zunächst steht der Stand des Falls da',
g('ls-haupt').textContent.includes('neuesten Stand'));
Studio._showEmpty(true);
pruefe('K2 danach steht dort kein alter Stand mehr',
g('ls-haupt').textContent === 'Kein Fall geöffnet', g('ls-haupt').textContent);
pruefe('K3 die Kette ist geleert', g('lauf-kette').innerHTML === '');
pruefe('K4 die Schrittliste ist geleert', g('lauf-schritte').innerHTML === '');
pruefe('K5 der Knopf ist gesperrt', g('ls-btn').disabled === true);
pruefe('K6 der Fall ist wirklich losgelassen', Studio.incident === null);
console.log('\nErgebnis: ' + ok + ' bestanden, ' + fail + ' fehlgeschlagen');
process.exit(fail ? 1 : 0);
}
main();