ba06db98c5
- 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
191 lines
6.6 KiB
TypeScript
191 lines
6.6 KiB
TypeScript
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('');
|
||
});
|
||
});
|