Files
tessera-ctl/apps/web/src/lib/release-notes.test.ts
T
schalli cf7784e409 test(260925-bow): Auswahl, Server-Aktionen, Fenster und Host des Was-ist-neu-Fensters
- selectReleaseNotice: Bereich, Deckel 3, null, unparsebar, nur Neu/Geaendert/Behoben
- Server-Aktionen: Cookie, Antwortform, Fehler still
- Fenster: role=dialog, Fokusfalle, alle Schliesswege genau einmal
- Host: merkt erst beim Schliessen, nicht auf /change-password, StrictMode

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-25 08:48:25 +02:00

229 lines
6.8 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 { 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<typeof selectReleaseNotice>) {
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']);
});
});