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(''); }); });