Prime message audio and silence duplicate OS notification sound
This commit is contained in:
@@ -14,8 +14,8 @@ window.localStorage = localStorage;
|
||||
|
||||
const root = fileURLToPath(new URL('../', import.meta.url));
|
||||
const jiti = createJiti(import.meta.url, { alias: { '@': `${root}src` } });
|
||||
const freshSound = () => createJiti(import.meta.url, { alias: { '@': `${root}src` }, moduleCache: false }).import('../src/lib/notifications/sound.ts');
|
||||
const source = (path) => readFile(new URL(`../${path}`, import.meta.url), 'utf8');
|
||||
let soundAudio;
|
||||
|
||||
test('display brand changes while the Tinode client identifier stays MeetingIM', async () => {
|
||||
const { BASE_APP_NAME, APP_NAME } = await jiti.import('../src/lib/config.ts');
|
||||
@@ -52,7 +52,7 @@ test('official image and supplied sound are copied byte for byte and used for ic
|
||||
});
|
||||
|
||||
test('sound is lazy, rewinds on repeat, and ignores sync and async playback failures', async () => {
|
||||
const sound = await jiti.import('../src/lib/notifications/sound.ts');
|
||||
const sound = await freshSound();
|
||||
assert.equal(typeof sound.playMessageNotificationSound, 'function');
|
||||
const played = [];
|
||||
globalThis.Audio = class {
|
||||
@@ -63,7 +63,6 @@ test('sound is lazy, rewinds on repeat, and ignores sync and async playback fail
|
||||
sound.playMessageNotificationSound();
|
||||
sound.playMessageNotificationSound();
|
||||
assert.equal(played.length, 1);
|
||||
soundAudio = played[0];
|
||||
assert.equal(played[0].src, '/message-notification.mp3');
|
||||
assert.equal(played[0].currentTime, 0);
|
||||
played[0].play = () => { throw new Error('blocked'); };
|
||||
@@ -73,18 +72,76 @@ test('sound is lazy, rewinds on repeat, and ignores sync and async playback fail
|
||||
await new Promise((resolve) => setImmediate(resolve));
|
||||
});
|
||||
|
||||
test('first gesture silently primes one audio element for later message playback', async () => {
|
||||
const handlers = new Map();
|
||||
window.addEventListener = (type, handler) => handlers.set(type, handler);
|
||||
window.removeEventListener = (type) => handlers.delete(type);
|
||||
const instances = [];
|
||||
globalThis.Audio = class {
|
||||
constructor(src) { this.src = src; this.currentTime = 8; this.muted = false; this.plays = []; this.pauses = 0; instances.push(this); }
|
||||
play() { this.plays.push(this.muted); return Promise.resolve(); }
|
||||
pause() { this.pauses++; }
|
||||
};
|
||||
const sound = await freshSound();
|
||||
assert.equal(instances.length, 0);
|
||||
assert.equal(typeof handlers.get('pointerdown'), 'function');
|
||||
assert.equal(typeof handlers.get('keydown'), 'function');
|
||||
const prime = handlers.get('pointerdown')();
|
||||
assert.equal(instances.length, 1);
|
||||
assert.equal(instances[0].muted, true);
|
||||
assert.deepEqual(instances[0].plays, [true]);
|
||||
await prime;
|
||||
assert.equal(instances[0].pauses, 1);
|
||||
assert.equal(instances[0].currentTime, 0);
|
||||
assert.equal(instances[0].muted, false);
|
||||
assert.equal(handlers.size, 0);
|
||||
sound.playMessageNotificationSound();
|
||||
assert.equal(instances.length, 1);
|
||||
assert.deepEqual(instances[0].plays, [true, false]);
|
||||
});
|
||||
|
||||
test('failed gesture prime unmutes and retries on the next gesture', async () => {
|
||||
const handlers = new Map();
|
||||
window.addEventListener = (type, handler) => handlers.set(type, handler);
|
||||
window.removeEventListener = (type) => handlers.delete(type);
|
||||
let attempts = 0;
|
||||
let pauses = 0;
|
||||
let instance;
|
||||
globalThis.Audio = class {
|
||||
constructor() { instance = this; }
|
||||
currentTime = 4;
|
||||
muted = false;
|
||||
play() { return ++attempts === 1 ? Promise.reject(new Error('blocked')) : Promise.resolve(); }
|
||||
pause() { pauses++; }
|
||||
};
|
||||
const sound = await freshSound();
|
||||
assert.equal(typeof handlers.get('pointerdown'), 'function');
|
||||
await handlers.get('pointerdown')();
|
||||
assert.equal(attempts, 1);
|
||||
assert.equal(instance.muted, false);
|
||||
assert.equal(handlers.size, 2);
|
||||
await handlers.get('keydown')();
|
||||
assert.equal(attempts, 2);
|
||||
assert.equal(pauses, 1);
|
||||
assert.equal(instance.muted, false);
|
||||
assert.equal(instance.currentTime, 0);
|
||||
assert.equal(handlers.size, 0);
|
||||
sound.playMessageNotificationSound();
|
||||
assert.equal(attempts, 3);
|
||||
});
|
||||
|
||||
test('incoming alerts gate own, visible and muted messages, but sound needs no notification permission', async () => {
|
||||
let plays = 0;
|
||||
globalThis.Audio = class { currentTime = 0; play() { plays++; return Promise.resolve(); } };
|
||||
const { notifyIncomingMessage } = await jiti.import('../src/lib/notifications/push.ts');
|
||||
assert.equal(typeof notifyIncomingMessage, 'function');
|
||||
const { useSessionStore } = await jiti.import('../src/stores/session.ts');
|
||||
const { setActiveConversationTopic } = await jiti.import('../src/lib/tinode/topics.ts');
|
||||
useSessionStore.setState({ myUserId: 'me' });
|
||||
let plays = 0;
|
||||
soundAudio.play = () => { plays++; return Promise.resolve(); };
|
||||
let notifications = 0;
|
||||
const notifications = [];
|
||||
globalThis.Notification = class {
|
||||
static permission = 'default';
|
||||
constructor() { notifications++; }
|
||||
constructor(title, options) { notifications.push({ title, options }); }
|
||||
};
|
||||
const contact = { topic: 'p2p-test', public: { fn: '朋友' }, private: {} };
|
||||
notifyIncomingMessage('p2p-test', { from: 'me' }, contact);
|
||||
@@ -97,16 +154,17 @@ test('incoming alerts gate own, visible and muted messages, but sound needs no n
|
||||
assert.equal(plays, 0);
|
||||
notifyIncomingMessage('p2p-test', { from: 'peer' }, contact);
|
||||
assert.equal(plays, 1);
|
||||
assert.equal(notifications, 0);
|
||||
assert.equal(notifications.length, 0);
|
||||
delete globalThis.Notification;
|
||||
notifyIncomingMessage('p2p-test', { from: 'peer' }, contact);
|
||||
assert.equal(plays, 2);
|
||||
assert.equal(notifications, 0);
|
||||
assert.equal(notifications.length, 0);
|
||||
globalThis.Notification = class {
|
||||
static permission = 'granted';
|
||||
constructor() { notifications++; }
|
||||
constructor(title, options) { notifications.push({ title, options }); }
|
||||
};
|
||||
notifyIncomingMessage('p2p-test', { from: 'peer' }, contact);
|
||||
assert.equal(plays, 3);
|
||||
assert.equal(notifications, 1);
|
||||
assert.equal(notifications.length, 1);
|
||||
assert.equal(notifications[0].options.silent, true);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user