Files
picpeak/backend/__tests__/utils/whatsNew.test.js
T
Luca 500cf8522e feat(updates): "What's New" highlights after update + pre-update teaser
Surfaces release highlights to admins, sourced from the GitHub release notes
(no AI at runtime). Bullets are written once per release in CI via GitHub Models
(see docs/ci/whatsnew-highlights.yml) into a <!-- whatsnew --> block; the app
reads that block and falls back to the changelog's "### Features" for releases
without it — so it works against today's releases immediately.

- backend utils/whatsNew.parseWhatsNew(body): curated block else Features
  section, strips scope/PR-links, de-dups, caps at 8 (tested).
- GET  /admin/system/updates/whatsnew: highlights for every version moved
  through since the per-instance marker (whatsnew_last_seen_version); fresh
  installs self-anchor silently. Best-effort, never errors.
- POST /admin/system/updates/whatsnew/seen: advance the marker (per-instance).
- /admin/system/updates also returns latestHighlights for the teaser.
- Frontend: WhatsNewBanner (green bar -> modal with "Full changelog" link) on
  the dashboard via adminService; UpdateNotification shows a "New features
  include:" teaser. i18n de/en. No migration (uses app_settings).
2026-06-30 02:31:13 +02:00

55 lines
1.8 KiB
JavaScript

const { parseWhatsNew } = require('../../src/utils/whatsNew');
describe('parseWhatsNew', () => {
it('prefers the curated <!-- whatsnew --> block', () => {
const body = [
'<!-- whatsnew -->',
'- Invoice drafts in list',
'- Bank transfer payments',
'<!-- /whatsnew -->',
'',
'### Features',
'* **invoices:** something long that should be ignored ([#1](http://x))',
].join('\n');
expect(parseWhatsNew(body)).toEqual(['Invoice drafts in list', 'Bank transfer payments']);
});
it('falls back to the Features section, stripping scope + commit links', () => {
const body = [
'## [3.73.0-beta.0](http://x) (2026-06-29)',
'',
'### Features',
'',
'* **dashboard:** revenue tile toggles 365 days ([d1c9e02](http://c))',
'* **invoices:** surface monthly drafts in the Bills list ([e457656](http://c))',
'',
'### Bug Fixes',
'',
'* **invoices:** add bank transfer ([e96ef4c](http://c))',
].join('\n');
expect(parseWhatsNew(body)).toEqual([
'revenue tile toggles 365 days',
'surface monthly drafts in the Bills list',
]);
});
it('excludes Bug Fixes from the fallback', () => {
const body = '### Features\n* **a:** feature one\n### Bug Fixes\n* **b:** fix one';
expect(parseWhatsNew(body)).toEqual(['feature one']);
});
it('caps at 8 bullets and de-dups', () => {
const lines = Array.from({ length: 12 }, (_, i) => `- bullet ${i % 9}`);
const body = `<!-- whatsnew -->\n${lines.join('\n')}\n<!-- /whatsnew -->`;
const out = parseWhatsNew(body);
expect(out.length).toBe(8);
expect(new Set(out).size).toBe(8);
});
it('returns [] for empty / non-string input', () => {
expect(parseWhatsNew('')).toEqual([]);
expect(parseWhatsNew(null)).toEqual([]);
expect(parseWhatsNew(undefined)).toEqual([]);
});
});