import { describe, expect, it } from 'vitest'; import { parseChangelogReleases, RELEASE_NOTICE_MAX_VERSIONS, selectReleaseNotice, } from './release-notes'; /** * release-notes.test — Auswahl der Versionsabschnitte fuer das * "Was ist neu"-Fenster (quick-260925-bow, D-03/D-04). * * Eigene Markdown-Fixtures statt der echten CHANGELOG.md, damit die Tests * nicht bei jedem neuen Eintrag brechen. */ const SAMPLE = [ '# Änderungen an Tessera', '', 'Vorspann.', '', '## Unveröffentlicht', '', '### Neu', '', '- Punkt U (noch nicht freigegeben)', '', '## 1.4.0 – 2026-09-25', '', '### Behoben', '', '- Fehler F140', '', '### Neu', '', '- Funktion N140', ' mit Fortsetzungszeile', '', '### Entfernt', '', '- Entfernt E140', '', '### Geändert', '', '- Aenderung G140', '', '## 1.3.1 – 2026-09-23', '', '### Geändert', '', '- Aenderung G131', '', '### Neu', '', '', '## 1.3.0 – 2026-09-22', '', '### Neu', '', '- Funktion N130', '', '## 1.2.0 – 2026-09-17', '', '### Entfernt', '', '- Nur Entferntes E120', '', '## 1.1.0 – 2026-09-16', '', '### Behoben', '', '- Fehler F110', '', '## 1.0.0 – 2026-09-15', '', '### Neu', '', '- Funktion N100', '', ].join('\n'); function versionsOf(notice: ReturnType) { return notice?.versions.map((v) => v.version); } describe('parseChangelogReleases (quick-260925-bow)', () => { it('liefert nur freigegebene Versionen mit mindestens einer Gruppe Neu/Geändert/Behoben', () => { const releases = parseChangelogReleases(SAMPLE); expect(releases.map((r) => r.version)).toEqual(['1.4.0', '1.3.1', '1.3.0', '1.1.0', '1.0.0']); }); it('Gruppen stehen in fester Reihenfolge new, changed, fixed, unabhaengig von der Dateireihenfolge; Entfernt fehlt', () => { const v140 = parseChangelogReleases(SAMPLE).find((r) => r.version === '1.4.0'); expect(v140?.sections.map((s) => s.kind)).toEqual(['new', 'changed', 'fixed']); expect(v140?.sections[0].markdown).toBe('- Funktion N140\n mit Fortsetzungszeile'); expect(v140?.sections[1].markdown).toBe('- Aenderung G140'); expect(v140?.sections[2].markdown).toBe('- Fehler F140'); expect(JSON.stringify(v140)).not.toContain('E140'); }); it('leere Gruppen fallen weg', () => { const v131 = parseChangelogReleases(SAMPLE).find((r) => r.version === '1.3.1'); expect(v131?.sections.map((s) => s.kind)).toEqual(['changed']); }); it('„Unveröffentlicht“ wird nie ausgewählt', () => { expect(JSON.stringify(parseChangelogReleases(SAMPLE))).not.toContain('Punkt U'); }); it('CRLF wird normalisiert', () => { const releases = parseChangelogReleases(SAMPLE.replace(/\n/g, '\r\n')); expect(releases.map((r) => r.version)).toEqual(['1.4.0', '1.3.1', '1.3.0', '1.1.0', '1.0.0']); expect(releases[0].sections[0].markdown).toBe('- Funktion N140\n mit Fortsetzungszeile'); }); it('leere Eingabe ergibt eine leere Liste', () => { expect(parseChangelogReleases('')).toEqual([]); }); }); describe('selectReleaseNotice (quick-260925-bow)', () => { it('Deckel ist 3 Versionen', () => { expect(RELEASE_NOTICE_MAX_VERSIONS).toBe(3); }); it('current null oder unparsebar → null', () => { expect(selectReleaseNotice(SAMPLE, null, '1.0.0')).toBeNull(); expect(selectReleaseNotice(SAMPLE, 'dev', '1.0.0')).toBeNull(); expect(selectReleaseNotice(SAMPLE, 'abc1234', null)).toBeNull(); }); it('lastSeen null → nur der Abschnitt der laufenden Version', () => { const notice = selectReleaseNotice(SAMPLE, '1.4.0', null); expect(notice?.currentRelease).toBe('1.4.0'); expect(versionsOf(notice)).toEqual(['1.4.0']); expect(notice?.omittedCount).toBe(0); }); it('lastSeen gleich oder ueber current → null', () => { expect(selectReleaseNotice(SAMPLE, '1.4.0', '1.4.0')).toBeNull(); expect(selectReleaseNotice(SAMPLE, '1.3.1', '1.4.0')).toBeNull(); }); it('lastSeen 1.3.0, current 1.4.0 → 1.4.0 und 1.3.1, neueste zuerst, omittedCount 0', () => { const notice = selectReleaseNotice(SAMPLE, '1.4.0', '1.3.0'); expect(versionsOf(notice)).toEqual(['1.4.0', '1.3.1']); expect(notice?.omittedCount).toBe(0); }); it('lastSeen 1.0.0 bei vier passenden Versionen darueber → die drei neuesten, omittedCount 1', () => { const notice = selectReleaseNotice(SAMPLE, '1.4.0', '1.0.0'); expect(versionsOf(notice)).toEqual(['1.4.0', '1.3.1', '1.3.0']); expect(notice?.omittedCount).toBe(1); }); it('fuenf Versionen ueber lastSeen → drei gezeigt, omittedCount 2', () => { const five = [ '## 2.4.0', '### Neu', '- a', '## 2.3.0', '### Neu', '- b', '## 2.2.0', '### Neu', '- c', '## 2.1.0', '### Neu', '- d', '## 2.0.1', '### Behoben', '- e', '## 2.0.0', '### Neu', '- f', ].join('\n'); const notice = selectReleaseNotice(five, '2.4.0', '2.0.0'); expect(versionsOf(notice)).toEqual(['2.4.0', '2.3.0', '2.2.0']); expect(notice?.omittedCount).toBe(2); }); it('lastSeen unparsebar → wie null', () => { const notice = selectReleaseNotice(SAMPLE, '1.4.0', 'kaputt'); expect(versionsOf(notice)).toEqual(['1.4.0']); }); it('Versionen ueber current (Web neuer als API) werden ausgelassen', () => { const notice = selectReleaseNotice(SAMPLE, '1.3.1', '1.1.0'); expect(versionsOf(notice)).toEqual(['1.3.1', '1.3.0']); expect(notice?.currentRelease).toBe('1.3.1'); }); it('laufende Version ohne Abschnitt in der Liste → null', () => { expect(selectReleaseNotice(SAMPLE, '1.5.0', '1.3.0')).toBeNull(); expect(selectReleaseNotice(SAMPLE, '1.5.0', null)).toBeNull(); }); it('eine laufende Version nur mit „Entfernt“ zaehlt als ohne Abschnitt → null', () => { expect(selectReleaseNotice(SAMPLE, '1.2.0', '1.1.0')).toBeNull(); }); it('Versionen nur mit „Entfernt“ fallen im Bereich ganz weg', () => { const notice = selectReleaseNotice(SAMPLE, '1.3.0', '1.0.0'); expect(versionsOf(notice)).toEqual(['1.3.0', '1.1.0']); expect(notice?.omittedCount).toBe(0); }); it('sortiert numerisch absteigend statt der Dateireihenfolge zu vertrauen', () => { const shuffled = [ '## 1.9.0', '### Neu', '- neun', '## 1.10.0', '### Neu', '- zehn', '## 1.8.0', '### Neu', '- acht', ].join('\n'); const notice = selectReleaseNotice(shuffled, '1.10.0', '1.7.0'); expect(versionsOf(notice)).toEqual(['1.10.0', '1.9.0', '1.8.0']); }); it('current mit v-Praefix oder Describe-Anhang wird kanonisch ausgewertet', () => { const notice = selectReleaseNotice(SAMPLE, 'v1.4.0-3-gabc1234', null); expect(notice?.currentRelease).toBe('1.4.0'); expect(versionsOf(notice)).toEqual(['1.4.0']); }); });