Files
werkbaum/frontend/tests/news.test.js
T
mhoennigandClaude Opus 5 36154f6f68 feat: Neuigkeiten — der Stern wandert in die Kopfzeile und bekommt ein Popup
Der „Was ist neu?"-Knopf stand im Diagramm-Kopf und war verborgen, solange es
nichts gab — er konnte also nur etwas über das offene Dokument sagen, und
meistens sagte er gar nichts. Jetzt steht er permanent in der oberen
Bedienleiste und öffnet ein Popup mit der Chronik der letzten Tage; je Tag
führt ein Link die betroffenen Knoten im Diagramm vor (dieselbe gelbe
Kranz-Ansicht wie D28, nur mit einer anderen Frage).

Zwei Quellen, jede in ihrer Rolle: docs/CHANGELOG.md sagt, WAS geschehen ist
(englisch — das Popup ist Oberfläche in neun Sprachen, die Commit-Betreffs
sind deutsch); die git-Historie des mitgelieferten Plans sagt, WELCHE Knoten
sich bewegt haben. Beides wird zur Bauzeit eingelesen (Vite-Plugin), zur
Laufzeit lädt Werkbaum weiterhin nichts nach.

Bernstein heißt ungesehen, Petrol heißt „wird gerade vorgeführt". Der
Besuchsvergleich (D28) steht als abgesetzter Abschnitt zuoberst im Popup und
trägt den „gesehen"-Knopf, den vorher der Knopf selbst war.

SPEC §9 (Neuigkeiten) und D58; 20 neue Tests, davon einer auf der
ausgelieferten CHANGELOG.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 12:50:13 +02:00

147 lines
5.8 KiB
JavaScript

import { describe, it, expect } from 'vitest';
import { readFileSync } from 'node:fs';
import { parseChangelog, changedKeys, attachKeys, parseLog }
from '../../scripts/news.mjs';
/* Neuigkeiten (D58). Geprüft ist die entscheidbare Hälfte — was aus dem
Changelog eine Notiz wird und welche Knoten sich an einem Tag bewegt haben.
Was git und Dateisystem liefern, steht in `collectNews()` und bleibt Sache
des Builds. */
const st = (...paare) => new Map(paare);
describe('parseChangelog — Notizen aus docs/CHANGELOG.md', () => {
const md = [
'# Changelog', '', 'Fließtext, der niemanden interessiert.', '- keine Notiz, kein Tag offen',
'', '## 2026-08-24', '', '- Erste Notiz', '* Zweite Notiz', 'kein Aufzählungszeichen',
'', '## 2026-08-23', '', '- Ältere Notiz', '',
].join('\n');
it('liest Tage und Notizen, neueste zuerst', () => {
expect(parseChangelog(md)).toEqual([
{date: '2026-08-24', lines: ['Erste Notiz', 'Zweite Notiz']},
{date: '2026-08-23', lines: ['Ältere Notiz']},
]);
});
/* Die Datei erklärt oben, was sie ist — das darf nicht als Notiz enden. */
it('überliest alles vor dem ersten Tag', () => {
expect(parseChangelog(md)[0].lines).not.toContain('keine Notiz, kein Tag offen');
});
it('beendet einen Tag an der nächsten Überschrift', () => {
const x = '## 2026-08-24\n- drin\n## Anhang\n- draußen\n';
expect(parseChangelog(x)).toEqual([{date: '2026-08-24', lines: ['drin']}]);
});
it('lässt einen Tag ohne Notizen weg', () => {
expect(parseChangelog('## 2026-08-24\n\n## 2026-08-23\n- da\n'))
.toEqual([{date: '2026-08-23', lines: ['da']}]);
});
it('führt zwei Abschnitte desselben Tages zusammen', () => {
expect(parseChangelog('## 2026-08-24\n- a\n\n## 2026-08-24\n- b\n')[0].lines)
.toEqual(['a', 'b']);
});
it('deckelt die Tage', () => {
const x = '## 2026-08-24\n- a\n## 2026-08-23\n- b\n';
expect(parseChangelog(x, {maxDays: 1}).map(e => e.date)).toEqual(['2026-08-24']);
});
it('verträgt Leeres', () => {
expect(parseChangelog('')).toEqual([]);
expect(parseChangelog(undefined)).toEqual([]);
});
/* Die ausgelieferte Datei selbst: Sie ist die Quelle des Popups — ist sie
unlesbar, steht das Popup leer da, ohne dass es jemand merkt. */
it('liest die echte docs/CHANGELOG.md', () => {
const echt = parseChangelog(readFileSync(new URL('../../docs/CHANGELOG.md', import.meta.url), 'utf8'));
expect(echt.length).toBeGreaterThan(3);
expect(echt[0].date).toMatch(/^\d{4}-\d{2}-\d{2}$/);
expect(echt.every(e => e.lines.length > 0)).toBe(true);
/* Absteigend sortiert — das Popup zeigt sie in dieser Reihenfolge. */
expect([...echt].sort((a, b) => a.date < b.date ? 1 : -1)).toEqual(echt);
});
});
describe('changedKeys — was sich im Plan bewegt hat', () => {
it('meldet neue Knoten und geänderten Status', () => {
const prev = st(['> A', 'geplant'], ['> B', 'fertig']);
const curr = st(['> A', 'fertig'], ['> B', 'fertig'], ['> C', 'idee']);
expect(changedKeys(prev, curr).sort()).toEqual(['> A', '> C']);
});
/* Wie beim Erstkontakt in D28: Ohne Vergleichsfassung leuchtet nichts. */
it('gibt ohne Vorfassung nichts zurück', () => {
expect(changedKeys(null, st(['> A', 'fertig']))).toEqual([]);
});
/* Ein entfernter Knoten ist im Diagramm nicht mehr da — es gäbe nichts
hervorzuheben. */
it('meldet entfernte Knoten nicht', () => {
expect(changedKeys(st(['> A', 'fertig']), st())).toEqual([]);
});
});
describe('attachKeys — Tageseinträge und Plan-Fassungen zusammenführen', () => {
const versionen = [
{date: '2026-08-22', status: st(['> A', 'geplant'])},
{date: '2026-08-23', status: st(['> A', 'fertig'])},
{date: '2026-08-24', status: st(['> A', 'prod'])},
];
it('hängt die Knoten an den Tag', () => {
const e = attachKeys([{date: '2026-08-24', lines: ['x']}], versionen);
expect(e[0]).toEqual({date: '2026-08-24', lines: ['x'], keys: ['> A']});
});
it('gibt einem Tag ohne Plan-Änderung eine leere Menge', () => {
const e = attachKeys([{date: '2026-08-25', lines: ['x']}], versionen);
expect(e[0].keys).toEqual([]);
});
/* Der Deploy-Tag (D30) oder ein vergessener Changelog-Eintrag: Der Link
führt trotzdem vor, was sich bewegt hat. */
it('legt für einen reinen Plan-Tag einen eigenen Eintrag an', () => {
const e = attachKeys([{date: '2026-08-24', lines: ['x']}], versionen);
expect(e.map(x => x.date)).toEqual(['2026-08-24', '2026-08-23']);
expect(e[1]).toEqual({date: '2026-08-23', lines: [], keys: ['> A']});
});
it('nimmt die erste Fassung nicht als Änderung', () => {
const e = attachKeys([], [versionen[0]]);
expect(e).toEqual([]);
});
it('deckelt auch nach dem Zusammenführen', () => {
const e = attachKeys([{date: '2026-08-24', lines: ['x']}], versionen, {maxDays: 1});
expect(e.map(x => x.date)).toEqual(['2026-08-24']);
});
});
describe('parseLog — git-Ausgabe zerlegen', () => {
const REC = '\x1e', F = '\x1f';
it('liest Kennung, Datum und Betreff', () => {
const raw = `${REC}abc${F}2026-08-24${F}feat: X${REC}def${F}2026-08-23${F}fix: Y`;
expect(parseLog(raw)).toEqual([
{sha: 'abc', date: '2026-08-24', subject: 'feat: X', files: []},
{sha: 'def', date: '2026-08-23', subject: 'fix: Y', files: []},
]);
});
/* Mit `--name-only` hängt git die Dateinamen hinter die Formatzeile — sie
gehören zu diesem Commit, weil der Satztrenner vorn steht. */
it('sammelt die Dateinamen aus --name-only', () => {
const raw = `${REC}abc${F}2026-08-24${F}rename\n\ndocs/examples/alt.werkbaum\n`;
expect(parseLog(raw)[0].files).toEqual(['docs/examples/alt.werkbaum']);
});
it('verträgt leere Ausgabe', () => {
expect(parseLog('')).toEqual([]);
expect(parseLog(REC + '\n')).toEqual([]);
});
});