feat(notifications): add configurable browser reminders
This commit is contained in:
1 parent
349ac4fc87
commit
cf0327fcaf
14 files changed
+217
-21
No files matched your search
+21
-4
@@ -50,11 +50,28 @@ assert.doesNotMatch(assets.indexHtml, /setTimeout\(applyLanguage\s*,/);
|
||||
assert.doesNotMatch(assets.addonsJs, /(?<![\w.])applyLanguage\s*\(/, "add-on actions must not call a missing global locale function");
|
||||
assert.match(assets.indexHtml, /window\.renderIcons=renderIcons;window\.renderAll=renderAll/, "shared renderers are explicitly exposed to locale scripts");
|
||||
const shippedScripts = [...assets.indexHtml.matchAll(/<script>([\s\S]*?)<\/script>/g)].map(match => match[1]);
|
||||
assert.equal(shippedScripts.length, 16, "the complete desktop script chain is present in the served HTML");
|
||||
assert.equal(shippedScripts.length, 17, "the complete desktop script chain is present in the served HTML");
|
||||
assert.doesNotMatch(assets.indexHtml, /<script\s+src=/, "the desktop cannot silently fall back when an app script fails to load");
|
||||
assert.ok(assets.indexHtml.indexOf('window.systemApp =') < assets.indexHtml.lastIndexOf("window.init()"), "locale renderers run before desktop initialization");
|
||||
for (const script of shippedScripts) new vm.Script(script);
|
||||
for (const script of [assets.i18nJs, assets.dataActionsJs, assets.terminalAppJs, assets.dbAdminJs, assets.tasksJs, assets.calendarJs, assets.i18nRenderersJs, assets.desktopPersonalizationJs, assets.desktopWidgetsJs, assets.browserAppJs, assets.addonsJs]) new vm.Script(script);
|
||||
for (const script of [assets.i18nJs, assets.dataActionsJs, assets.terminalAppJs, assets.dbAdminJs, assets.tasksJs, assets.calendarJs, assets.notificationsJs, assets.i18nRenderersJs, assets.desktopPersonalizationJs, assets.desktopWidgetsJs, assets.browserAppJs, assets.addonsJs]) new vm.Script(script);
|
||||
const reminderStorage = new Map(), delivered = [];
|
||||
reminderStorage.set('lifeos_notifications_v1', JSON.stringify(Object.fromEntries(['memory', 'tasks', 'goals'].map(kind => [kind, { enabled: true, time: '12:00' }]))));
|
||||
const reminderContext = vm.createContext({
|
||||
document: { hidden: false, addEventListener() {}, createElement: () => ({}), head: { append() {} } },
|
||||
localStorage: { getItem: key => reminderStorage.get(key) ?? null, setItem: (key, value) => reminderStorage.set(key, value) },
|
||||
navigator: { serviceWorker: { getRegistration: async () => ({ showNotification: async (title, options) => delivered.push(options.tag) }), addEventListener() {} } },
|
||||
Notification: { permission: 'granted', requestPermission: async () => 'granted' }, isSecureContext: true,
|
||||
LifeI18n: { t: key => key }, mems: () => [], sys: () => ({ tasksBoard: { tasks: [{ dueDate: '2026-09-28', done: false }], goals: [{ title: 'Study', targetDate: '2026-10-01', done: false }] } }),
|
||||
URL, location: { href: 'https://life-os.test/' }, history: { replaceState() {} }, setInterval() {}, console, Date
|
||||
});
|
||||
reminderContext.window = reminderContext;
|
||||
reminderContext.addEventListener = () => {};
|
||||
vm.runInContext(assets.notificationsJs, reminderContext);
|
||||
await reminderContext.LifeOSNotifications.check(new Date(2026, 8, 28, 12, 1));
|
||||
assert.deepEqual(delivered, ['lifeos-memory-2026-09-28', 'lifeos-tasks-2026-09-28', 'lifeos-goals-2026-09-28']);
|
||||
await reminderContext.LifeOSNotifications.check(new Date(2026, 8, 28, 12, 2));
|
||||
assert.equal(delivered.length, 3, 'a reminder is sent only once per day');
|
||||
const adminNode = { innerHTML: "", querySelector: () => ({ addEventListener() {} }) };
|
||||
const adminContext = vm.createContext({
|
||||
document: { createElement: () => ({ textContent: "" }), head: { append() {} }, querySelector: () => adminNode },
|
||||
@@ -186,7 +203,7 @@ assert.ok(command('date').message.length > 8);
|
||||
assert.ok(command('time').message.includes(':'));
|
||||
assert.ok(command('datetime').message.length > 12);
|
||||
assert.equal(command('uptime').message, 'terminal.uptime');
|
||||
assert.equal(command('version').message, 'Life OS v1.14.2');
|
||||
assert.equal(command('version').message, 'Life OS v1.15.0');
|
||||
assert.equal(command('status').message, 'terminal.status');
|
||||
assert.match(command('apps').message, /calendar/);
|
||||
assert.equal(command('echo "hello Life OS"').message, 'hello Life OS');
|
||||
@@ -284,7 +301,7 @@ const uiContext = vm.createContext({
|
||||
...Object.fromEntries(["icons", "tasks", "launcher", "calendar", "desktop", "boot", "modal"].map(id => [id, uiNodes.get(`#${id}`)]))
|
||||
});
|
||||
uiContext.window = uiContext;
|
||||
for (const index of [0, 2, 3, 4, 5, 9, 10]) vm.runInContext(shippedScripts[index], uiContext);
|
||||
for (const index of [0, 2, 3, 4, 5, 10, 11]) vm.runInContext(shippedScripts[index], uiContext);
|
||||
vm.runInContext("windows.system={classList:{contains(){return false}}}", uiContext);
|
||||
uiContext.setLanguage("zh");
|
||||
assert.equal(uiContext.LifeI18n.language(), "zh");
|
||||
|
||||
Reference in new issue
Block a user