Files
TREK/server/tests/unit/systemNotices/registry.test.ts
T
2026-10-06 21:50:59 +02:00

114 lines
4.6 KiB
TypeScript

import { RETIRED_NOTICE_IDS, SYSTEM_NOTICES } from '../../../src/systemNotices/registry.js';
import { isNoticeVersionActive } from '../../../src/systemNotices/service.js';
import fs from 'node:fs';
import path from 'node:path';
import semver from 'semver';
import { describe, it, expect } from 'vitest';
/** Collect all actionIds registered via registerNoticeAction() in client source files. */
function collectRegisteredActionIds(): Set<string> {
const clientSrc = path.resolve(__dirname, '../../../../client/src');
const ids = new Set<string>();
const queue = [clientSrc];
while (queue.length) {
const dir = queue.pop()!;
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
queue.push(full);
continue;
}
if (!entry.name.endsWith('noticeActions.ts') && !entry.name.endsWith('noticeActions.js')) continue;
const src = fs.readFileSync(full, 'utf8');
for (const m of src.matchAll(/registerNoticeAction\(\s*['"]([^'"]+)['"]/g)) {
ids.add(m[1]);
}
}
}
return ids;
}
describe('registry integrity', () => {
it('has no duplicate ids', () => {
const ids = SYSTEM_NOTICES.map((n) => n.id);
expect(new Set(ids).size).toBe(ids.length);
});
it('all action CTAs reference a registered actionId', () => {
const registeredActionIds = collectRegisteredActionIds();
const actionCtaIds = SYSTEM_NOTICES.filter((n) => n.cta?.kind === 'action').map(
(n) => (n.cta as { actionId: string }).actionId,
);
for (const id of actionCtaIds) {
expect(registeredActionIds, `actionId "${id}" not found in any client noticeActions.ts`).toContain(id);
}
});
it('all publishedAt are valid ISO dates', () => {
for (const n of SYSTEM_NOTICES) {
expect(() => new Date(n.publishedAt).toISOString()).not.toThrow();
}
});
it('minVersion and maxVersion are valid semver when set, and minVersion <= maxVersion when both set', () => {
for (const n of SYSTEM_NOTICES) {
if (n.minVersion !== undefined) {
expect(semver.valid(n.minVersion), `notice "${n.id}" has invalid minVersion "${n.minVersion}"`).not.toBeNull();
}
if (n.maxVersion !== undefined) {
expect(semver.valid(n.maxVersion), `notice "${n.id}" has invalid maxVersion "${n.maxVersion}"`).not.toBeNull();
}
if (n.minVersion && n.maxVersion) {
expect(
semver.lte(n.minVersion, n.maxVersion),
`notice "${n.id}": minVersion ${n.minVersion} > maxVersion ${n.maxVersion}`,
).toBe(true);
}
}
});
it('the release notes come back on every upgrade, with no upper bound', () => {
const release = SYSTEM_NOTICES.find((n) => n.id === 'release-notes');
expect(release).toBeDefined();
// Nothing on 3.x ships this copy, and the thank-you notice still covers it there.
expect(isNoticeVersionActive(release!, '3.4.1')).toBe(false);
// From there on every version carries it, a patch release included, so no
// install is left with nothing to show because nobody wrote a new window.
for (const version of ['4.0.0', '4.2.1', '4.3.0', '4.3.1', '4.9.9', '5.0.0', '12.4.0']) {
expect(isNoticeVersionActive(release!, version), version).toBe(true);
}
expect(release!.maxVersion).toBeUndefined();
// The window alone is not enough: a one-time dismissal would retire it for good
// the first time somebody closed it.
expect(release!.recurring).toBe('per-version');
});
it('the 4.0.0 release notice is retired and its id stays reserved', () => {
expect(SYSTEM_NOTICES.some((n) => n.id === 'release-4-0-0')).toBe(false);
expect(RETIRED_NOTICE_IDS).toContain('release-4-0-0');
for (const id of RETIRED_NOTICE_IDS) {
expect(
SYSTEM_NOTICES.some((n) => n.id === id),
id,
).toBe(false);
}
});
it('the thank-you notice hands over to the release modal at 4.0.0', () => {
const thankYou = SYSTEM_NOTICES.find((n) => n.id === 'thank-you-support');
expect(thankYou).toBeDefined();
// Both carry the same thank-you and the same two support links, so exactly
// one of them may be active at any version.
expect(isNoticeVersionActive(thankYou!, '3.4.1')).toBe(true);
expect(isNoticeVersionActive(thankYou!, '4.0.0')).toBe(false);
const release = SYSTEM_NOTICES.find((n) => n.id === 'release-notes')!;
for (const version of ['3.4.1', '4.0.0', '4.0.7', '4.1.0', '4.3.0', '5.0.0']) {
const active = [thankYou!, release].filter((n) => isNoticeVersionActive(n, version));
expect(active.length, `thank-you and release notes at ${version}`).toBe(1);
}
});
});