mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-19 21:31:46 +00:00
293506217e
Server-side notice registry with per-user condition evaluation (firstLogin, existingUserBeforeVersion, addonEnabled, dateWindow, role, custom). Notices are sorted by priority then severity, filtered against dismissals stored in a new user_notice_dismissals table, and served via GET /api/system-notices/active + POST /api/system-notices/:id/dismiss. Client renders notices through a host component that partitions by display type (modal / banner / toast). The modal renderer supports multi-page pagination with directional slide transitions, keyboard navigation, and correct dismiss-all semantics on CTA / X / ESC. Dismissals are optimistic with a single background retry. Includes 3.0.0 upgrade notices (v3-photos, v3-journey, v3-features), onboarding welcome modal, and full i18n coverage across 15 languages. The /journey route is addon-gated on both client and server. Also includes: unit + integration test suites, registry integrity test that validates action CTA IDs against client source, and technical documentation in docs/system-notices.md.
49 lines
1.7 KiB
TypeScript
49 lines
1.7 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { SYSTEM_NOTICES } from '../../../src/systemNotices/registry.js';
|
|
|
|
/** 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();
|
|
}
|
|
});
|
|
});
|