Files
tessera-ctl/apps/web/src/lib/changelog.test.ts
T
schalli ba06db98c5 feat(quick-260916-dcz): CHANGELOG.md, Seite "Was ist neu" mit Kanalfilter, Versionszeile als Link, Bauzeit-Einbettung
- CHANGELOG.md (Wurzel): Unveroeffentlicht (bwo + dyv + diese Seite, 8 Punkte) und 1.0.0 – 2026-09-15 (11 Punkte aus den Handbuechern), Alltagssprache, echte Umlaute
- next.config.ts liest CHANGELOG.md zur Bauzeit nach env.TESSERA_CHANGELOG_MD; .dockerignore-Ausnahme !CHANGELOG.md und COPY-Zeile im Web-Dockerfile
- lib/changelog.ts: filterChangelogForChannel (live ohne Unveroeffentlicht, beta/dev mit "Noch nicht freigegeben (Beta)", leerer Abschnitt ausgeblendet), 10 Tests
- Server-Seite /changelog (MDEditor.Markdown + rehype-sanitize, Hinweis auf Beta, Leer-Text), 3 Tests; Versionszeile ist Link mit aria-label, 2 Tests
- de/en: sidebar.whatsNew und Namensraum changelog; Web-Suite 49/309, tsc 0

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
2026-09-16 11:20:22 +02:00

191 lines
6.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { afterEach, describe, expect, it, vi } from 'vitest';
import { filterChangelogForChannel } from './changelog';
/**
* changelog.test — Kanalregel fuer die Seite "Was ist neu" (quick-260916-dcz).
*
* `filterChangelogForChannel` ist eine reine Funktion ueber dem Markdown-Text
* von CHANGELOG.md: auf `live` verschwindet der Abschnitt "Unveröffentlicht",
* auf `beta`/`dev` bleibt er mit der Ueberschrift "Noch nicht freigegeben
* (Beta)"; H1 und Vorspann entfallen immer. Test 1 ist die Falsifizierung (a)
* des Plans: er wird rot, sobald die Funktion den Abschnitt nicht entfernt.
* Tests 9/10 pruefen die Bauzeit-Einbettung ueber `TESSERA_CHANGELOG_MD`
* (Muster: app-version.test.ts — stubEnv + resetModules + dynamischer Import).
*/
const SAMPLE = [
'# Änderungen an Tessera',
'',
'Vorspann: die neueste Version steht oben.',
'',
'## Unveröffentlicht',
'',
'### Geändert',
'',
'- Punkt U1 (noch nicht freigegeben)',
'- Punkt U2 (noch nicht freigegeben)',
'',
'## 1.0.0 – 2026-09-15',
'',
'### Neu',
'',
'- Punkt A der Version 1.0.0',
'- Punkt B der Version 1.0.0',
'',
'## 0.9.0 – 2026-09-01',
'',
'### Neu',
'',
'- Punkt C der Version 0.9.0',
'',
].join('\n');
afterEach(() => {
vi.unstubAllEnvs();
});
describe('filterChangelogForChannel (quick-260916-dcz)', () => {
it('Test 1 (live): entfernt den Abschnitt Unveröffentlicht samt Punkten, behaelt die Versionen in Reihenfolge', () => {
const result = filterChangelogForChannel(SAMPLE, 'live');
expect(result.hasUnreleased).toBe(false);
expect(result.markdown).not.toContain('## Unveröffentlicht');
expect(result.markdown).not.toContain('Punkt U1');
expect(result.markdown).not.toContain('Punkt U2');
expect(result.markdown).toContain('## 1.0.0 – 2026-09-15');
expect(result.markdown).toContain('## 0.9.0 – 2026-09-01');
expect(result.markdown).toContain('- Punkt A der Version 1.0.0');
expect(result.markdown).toContain('- Punkt C der Version 0.9.0');
expect(result.markdown.indexOf('## 1.0.0')).toBeLessThan(result.markdown.indexOf('## 0.9.0'));
});
it('Test 2 (beta): ersetzt die Ueberschrift durch "Noch nicht freigegeben (Beta)", Punkte bleiben', () => {
const result = filterChangelogForChannel(SAMPLE, 'beta');
expect(result.hasUnreleased).toBe(true);
expect(result.markdown).toContain('## Noch nicht freigegeben (Beta)');
expect(result.markdown).not.toContain('## Unveröffentlicht');
expect(result.markdown).toContain('- Punkt U1');
expect(result.markdown).toContain('- Punkt U2');
expect(result.markdown).toContain('## 1.0.0 – 2026-09-15');
});
it('Test 3 (dev): verhaelt sich wie beta', () => {
const result = filterChangelogForChannel(SAMPLE, 'dev');
expect(result.hasUnreleased).toBe(true);
expect(result.markdown).toContain('## Noch nicht freigegeben (Beta)');
expect(result.markdown).not.toContain('## Unveröffentlicht');
expect(result.markdown).toContain('- Punkt U1');
});
it('Test 4 (eigenes Label): options.unreleasedHeading bestimmt die Ueberschrift', () => {
const result = filterChangelogForChannel(SAMPLE, 'beta', {
unreleasedHeading: 'Not yet released (beta)',
});
expect(result.markdown).toContain('## Not yet released (beta)');
expect(result.markdown).not.toContain('## Noch nicht freigegeben (Beta)');
});
it('Test 5 (leerer Abschnitt, beta): Unveröffentlicht ohne Listenpunkt wird ausgeblendet', () => {
const empty = [
'# Titel',
'',
'## Unveröffentlicht',
'',
'### Neu',
'',
'## 1.0.0 – 2026-09-15',
'',
'- Punkt A der Version 1.0.0',
'',
].join('\n');
const result = filterChangelogForChannel(empty, 'beta');
expect(result.hasUnreleased).toBe(false);
expect(result.markdown).not.toContain('Unveröffentlicht');
expect(result.markdown).not.toContain('Noch nicht freigegeben');
expect(result.markdown).toContain('## 1.0.0 – 2026-09-15');
expect(result.markdown).toContain('- Punkt A der Version 1.0.0');
});
it('Test 6 (kein Abschnitt): ohne Unveröffentlicht bleiben die Versionsabschnitte unveraendert', () => {
const noUnreleased = [
'# Titel',
'',
'## 1.0.0 – 2026-09-15',
'',
'### Neu',
'',
'- Punkt A der Version 1.0.0',
'',
'## 0.9.0 – 2026-09-01',
'',
'- Punkt C der Version 0.9.0',
'',
].join('\n');
const expected = [
'## 1.0.0 – 2026-09-15',
'',
'### Neu',
'',
'- Punkt A der Version 1.0.0',
'',
'## 0.9.0 – 2026-09-01',
'',
'- Punkt C der Version 0.9.0',
'',
].join('\n');
const beta = filterChangelogForChannel(noUnreleased, 'beta');
const live = filterChangelogForChannel(noUnreleased, 'live');
expect(beta.hasUnreleased).toBe(false);
expect(live.hasUnreleased).toBe(false);
expect(beta.markdown).toBe(expected);
expect(live.markdown).toBe(expected);
});
it('Test 7 (Hierarchie): H1 und Vorspann fehlen, Ergebnis beginnt mit "## ", "### " bleibt', () => {
const result = filterChangelogForChannel(SAMPLE, 'beta');
expect(result.markdown).not.toContain('# Änderungen an Tessera');
expect(result.markdown).not.toContain('Vorspann:');
expect(result.markdown.trim().startsWith('## ')).toBe(true);
expect(result.markdown).toContain('### Geändert');
expect(result.markdown).toContain('### Neu');
});
it('Test 8 (CRLF): Windows-Zeilenenden werden normalisiert, live entfernt den Abschnitt ebenfalls', () => {
const crlf = SAMPLE.replace(/\n/g, '\r\n');
const result = filterChangelogForChannel(crlf, 'live');
expect(result.hasUnreleased).toBe(false);
expect(result.markdown).not.toContain('\r');
expect(result.markdown).not.toContain('Unveröffentlicht');
expect(result.markdown).toContain('## 1.0.0 – 2026-09-15');
});
it('Test 9 (Einbettung): changelogMarkdown ist exakt der Wert von TESSERA_CHANGELOG_MD', async () => {
const text = '# T\n\n## 1.0.0 – 2026-01-01\n\n- x';
vi.stubEnv('TESSERA_CHANGELOG_MD', text);
vi.resetModules();
const mod = await import('./changelog');
expect(mod.changelogMarkdown).toBe(text);
});
it('Test 10 (ohne Variable): changelogMarkdown ist ein leerer String', async () => {
vi.stubEnv('TESSERA_CHANGELOG_MD', '');
vi.resetModules();
const empty = await import('./changelog');
expect(empty.changelogMarkdown).toBe('');
vi.stubEnv('TESSERA_CHANGELOG_MD', undefined);
vi.resetModules();
const unset = await import('./changelog');
expect(unset.changelogMarkdown).toBe('');
});
});