cf7784e409
- 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>
229 lines
6.8 KiB
TypeScript
229 lines
6.8 KiB
TypeScript
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']);
|
||
});
|
||
});
|