import { describe, it, expect } from 'vitest'; import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; // @ts-expect-error plain .mjs helper shared with scripts/generate-whats-new.mjs import { extractHighlights, versionSection, buildWhatsNew } from '../scripts/lib/whats-new.mjs'; const root = resolve(import.meta.dirname, '..'); const CHANGELOG = `# aicodeman ## 1.2.0 ### Minor Changes - ### Thanks - @someone for a fix **The tile grid: six sessions side by side.** Body text with **inline bold** later. - **Split view (#560).** More text. - A bullet with no bold lead. **Behaviour change: \`Ctrl+W\` no longer closes a session.** Details. ## 1.1.0 - **Old feature.** Not for 1.2.0. `; describe('whats-new extraction', () => { it('limits itself to the requested version section', () => { const section = versionSection(CHANGELOG, '1.2.0'); expect(section).toContain('tile grid'); expect(section).not.toContain('Old feature'); expect(versionSection(CHANGELOG, '9.9.9')).toBe(''); }); it('takes bold lead-ins only, cleaned, and skips the Thanks block', () => { expect(extractHighlights(versionSection(CHANGELOG, '1.2.0'))).toEqual([ 'The tile grid: six sessions side by side', 'Split view (#560)', 'Behaviour change: `Ctrl+W` no longer closes a session', ]); }); it('caps the list', () => { const many = Array.from({ length: 20 }, (_, i) => `**Feature number ${i}.** text`).join('\n\n'); expect(extractHighlights(many)).toHaveLength(8); }); it('builds the payload the client reads', () => { expect(buildWhatsNew(CHANGELOG, '1.2.0').version).toBe('1.2.0'); }); }); describe('shipped whats-new.json', () => { it("matches the package version, so a release cannot ship the previous release's notes", () => { const pkg = JSON.parse(readFileSync(resolve(root, 'package.json'), 'utf8')); const notes = JSON.parse(readFileSync(resolve(root, 'src/web/public/whats-new.json'), 'utf8')); expect(notes.version).toBe(pkg.version); // A release written without bold lead-ins legitimately has none (the client then stays // quiet), so an empty list must not turn the release commit's CI red. expect(Array.isArray(notes.highlights)).toBe(true); }); it('is regenerated by the release script and consumed once per version by the client', () => { const pkg = readFileSync(resolve(root, 'package.json'), 'utf8'); expect(pkg).toMatch(/"version-packages": "changeset version && node scripts\/generate-whats-new\.mjs/); const ui = readFileSync(resolve(root, 'src/web/public/settings-ui.js'), 'utf8'); expect(ui).toContain("'codeman-whats-new-seen'"); expect(ui).toContain("fetch('/whats-new.json'"); expect(readFileSync(resolve(root, 'src/web/public/app.js'), 'utf8')).toContain( 'this.checkWhatsNew?.(data.version)' ); }); });