mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-03 05:59:43 +02:00
Merge pull request #178 from Lint111/agent/split-notification-noise
fix(notifications): quiet lifecycle hook noise
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
|
||||
const SOURCE = readFileSync(new URL('../src/web/public/notification-manager.js', import.meta.url), 'utf8');
|
||||
|
||||
type EventPreference = {
|
||||
enabled: boolean;
|
||||
browser: boolean;
|
||||
audio: boolean;
|
||||
push: boolean;
|
||||
};
|
||||
|
||||
type NotificationPreferences = {
|
||||
enabled: boolean;
|
||||
eventTypes: Record<string, EventPreference>;
|
||||
_version: number;
|
||||
};
|
||||
|
||||
type Manager = {
|
||||
preferences: NotificationPreferences;
|
||||
notifications: unknown[];
|
||||
getStorageKey: () => string;
|
||||
normalizePreferences: (preferences: Record<string, unknown>) => NotificationPreferences;
|
||||
notify: (notification: Record<string, unknown>) => void;
|
||||
};
|
||||
|
||||
const openWindows: JSDOM[] = [];
|
||||
|
||||
function loadManager(
|
||||
saved?: Record<string, unknown>,
|
||||
device: { deviceType?: string; handheld?: boolean } = {}
|
||||
): { dom: JSDOM; manager: Manager } {
|
||||
const dom = new JSDOM(
|
||||
'<!doctype html><body><span id="notifBadge"></span><div id="notifList"></div><div id="notifEmpty"></div></body>',
|
||||
{
|
||||
url: 'http://localhost/',
|
||||
runScripts: 'outside-only',
|
||||
}
|
||||
);
|
||||
openWindows.push(dom);
|
||||
const win = dom.window as unknown as Window &
|
||||
typeof globalThis & {
|
||||
MobileDetection: {
|
||||
getDeviceType: () => string;
|
||||
isHandheldDevice?: () => boolean;
|
||||
};
|
||||
STUCK_THRESHOLD_DEFAULT_MS: number;
|
||||
GROUPING_TIMEOUT_MS: number;
|
||||
NOTIFICATION_LIST_CAP: number;
|
||||
};
|
||||
win.MobileDetection = {
|
||||
getDeviceType: () => device.deviceType ?? 'desktop',
|
||||
...(typeof device.handheld === 'boolean' ? { isHandheldDevice: () => device.handheld === true } : {}),
|
||||
};
|
||||
win.STUCK_THRESHOLD_DEFAULT_MS = 600_000;
|
||||
win.GROUPING_TIMEOUT_MS = 5_000;
|
||||
win.NOTIFICATION_LIST_CAP = 100;
|
||||
win.requestAnimationFrame = ((callback: FrameRequestCallback) => {
|
||||
callback(0);
|
||||
return 1;
|
||||
}) as typeof requestAnimationFrame;
|
||||
|
||||
if (saved) {
|
||||
win.localStorage.setItem('codeman-notification-prefs', JSON.stringify(saved));
|
||||
}
|
||||
|
||||
win.eval(`
|
||||
${SOURCE}
|
||||
window.__testNotificationManager = NotificationManager;
|
||||
`);
|
||||
const NotificationManager = (
|
||||
win as unknown as {
|
||||
__testNotificationManager: new (app: { sessions: Map<unknown, unknown> }) => Manager;
|
||||
}
|
||||
).__testNotificationManager;
|
||||
const manager = new NotificationManager({ sessions: new Map() }) as Manager;
|
||||
return { dom, manager };
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
for (const dom of openWindows.splice(0)) dom.window.close();
|
||||
});
|
||||
|
||||
describe('notification noise defaults', () => {
|
||||
it('keeps response-complete and team lifecycle drawer entries opt-in', () => {
|
||||
const { manager } = loadManager();
|
||||
expect(manager.preferences.eventTypes.stop.enabled).toBe(false);
|
||||
|
||||
for (const category of ['hook-stop', 'hook-teammate-idle', 'hook-task-completed']) {
|
||||
manager.notify({
|
||||
urgency: 'info',
|
||||
category,
|
||||
sessionId: 'session-1',
|
||||
sessionName: 'session',
|
||||
title: category,
|
||||
message: category,
|
||||
});
|
||||
}
|
||||
|
||||
expect(manager.notifications).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('migrates the old drawer-only Stop default but preserves explicit delivery', () => {
|
||||
const quietV4 = {
|
||||
enabled: true,
|
||||
eventTypes: {
|
||||
stop: { enabled: true, browser: false, audio: false, push: false },
|
||||
},
|
||||
_version: 4,
|
||||
};
|
||||
const { manager: quietManager } = loadManager(quietV4);
|
||||
expect(quietManager.preferences.eventTypes.stop.enabled).toBe(false);
|
||||
expect(quietManager.preferences._version).toBe(5);
|
||||
|
||||
const browserV4 = {
|
||||
enabled: true,
|
||||
eventTypes: {
|
||||
stop: { enabled: true, browser: true, audio: false, push: false },
|
||||
},
|
||||
_version: 4,
|
||||
};
|
||||
const { manager: browserManager } = loadManager(browserV4);
|
||||
expect(browserManager.preferences.eventTypes.stop.enabled).toBe(true);
|
||||
});
|
||||
|
||||
it('normalizes server-hydrated v4 preferences through the same quiet migration', () => {
|
||||
const { manager } = loadManager();
|
||||
manager.preferences = manager.normalizePreferences({
|
||||
enabled: true,
|
||||
eventTypes: {
|
||||
stop: { enabled: true, browser: false, audio: false, push: false },
|
||||
},
|
||||
_version: 4,
|
||||
});
|
||||
|
||||
expect(manager.preferences.eventTypes.stop.enabled).toBe(false);
|
||||
expect(manager.preferences._version).toBe(5);
|
||||
});
|
||||
|
||||
it('keeps mobile notification defaults and storage on an unfolded handheld', () => {
|
||||
const { manager } = loadManager(undefined, {
|
||||
deviceType: 'desktop',
|
||||
handheld: true,
|
||||
});
|
||||
|
||||
expect(manager.preferences.enabled).toBe(false);
|
||||
expect(manager.getStorageKey()).toBe('codeman-notification-prefs-mobile');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user