diff --git a/tests/Unit/RemoteNotifications.test.tsx b/tests/Unit/RemoteNotifications.test.tsx index 727e475df..2a9506538 100644 --- a/tests/Unit/RemoteNotifications.test.tsx +++ b/tests/Unit/RemoteNotifications.test.tsx @@ -79,8 +79,16 @@ beforeEach(() => { }, }); vi.stubGlobal('document', { addEventListener() {}, removeEventListener() {} }); - let lock = Promise.resolve(); - vi.stubGlobal('navigator', { locks: { request: (_name: string, run: () => Promise) => (lock = lock.then(run)) } }); + const locks = new Map>(); + vi.stubGlobal('navigator', { locks: { request: (name: string, options: LockOptions | (() => Promise), run?: () => Promise) => { + const callback = typeof options === 'function' ? options : () => { + options.signal?.throwIfAborted(); + return run!(); + }; + const lock = (locks.get(name) ?? Promise.resolve()).then(callback, callback); + locks.set(name, lock); + return lock; + } } }); }); afterEach(() => { @@ -378,10 +386,17 @@ test.each([true, false])('background inbox polling alerts without consuming unre const stop = watchRemoteAlerts('me'); try { - await vi.advanceTimersByTimeAsync(0); + await vi.advanceTimersByTimeAsync(59_999); + assert.deepEqual(requestedBaselines, []); + await vi.advanceTimersByTimeAsync(1); + assert.deepEqual(requestedBaselines, [true]); assert.equal(shown.length, 0); messages = [conversation(31, { kind: 'org' })]; - await vi.advanceTimersByTimeAsync(15_000); + await vi.advanceTimersByTimeAsync(59_999); + assert.deepEqual(requestedBaselines, [true]); + assert.equal(shown.length, 0); + assert.equal(play.mock.calls.length, 0); + await vi.advanceTimersByTimeAsync(1); assert.equal(shown.length, Number(banners)); assert.equal(play.mock.calls.length, 1); assert.equal(chatSnapshot().unread, 1); @@ -391,22 +406,22 @@ test.each([true, false])('background inbox polling alerts without consuming unre unread = 4; messages = [conversation(32, { kind: 'org', ahead: 4 })]; - await vi.advanceTimersByTimeAsync(15_000); + await vi.advanceTimersByTimeAsync(60_000); assert.equal(pageTitle('Settings', page), '(6) Settings - /dev/null'); assert.equal(shown.length, 2 * Number(banners)); assert.equal(play.mock.calls.length, 2); unread = 97; - await vi.advanceTimersByTimeAsync(15_000); + await vi.advanceTimersByTimeAsync(60_000); assert.equal(pageTitle('Settings', page), '(99) Settings - /dev/null'); unread = 98; - await vi.advanceTimersByTimeAsync(15_000); + await vi.advanceTimersByTimeAsync(60_000); assert.equal(pageTitle('', page), '(99+) /dev/null'); unread = 0; messages = [conversation(32, { kind: 'org', ahead: 0 })]; - await vi.advanceTimersByTimeAsync(15_000); + await vi.advanceTimersByTimeAsync(60_000); assert.equal(pageTitle('', page), '(2) /dev/null'); page.props.auth.unreadNotificationsCount = 0; assert.equal(pageTitle('Remotes', page), 'Remotes - /dev/null'); @@ -563,7 +578,7 @@ test('native broadcasts only acknowledge the receiving remote, without extra sou test('restarting alerts during an inbox request waits for its own baseline without replaying old messages', async () => { vi.useFakeTimers(); Object.assign(window, { setTimeout, clearTimeout }); - Object.assign(document, { hidden: true, cookie: '' }); + Object.assign(document, { hidden: false, hasFocus: () => false, cookie: '' }); let resolveOld: ((response: Response) => void) | undefined; let id = 80; const requestedBaselines: boolean[] = []; @@ -610,9 +625,9 @@ test('an inbox response from before a successful mute cannot alert or replay aft }); const stop = watchRemoteAlerts('me'); try { - await vi.advanceTimersByTimeAsync(0); + await vi.advanceTimersByTimeAsync(60_000); delayInbox = true; - await vi.advanceTimersByTimeAsync(15_000); + await vi.advanceTimersByTimeAsync(60_000); assert.ok(resolveInbox); await setConversationMute(target, 'forever'); messages = [conversation(70, { id: 'mute-race', kind: 'org' })]; @@ -623,10 +638,10 @@ test('an inbox response from before a successful mute cannot alert or replay aft delayInbox = false; await setConversationMute(target, 'off'); - await vi.advanceTimersByTimeAsync(15_000); + await vi.advanceTimersByTimeAsync(60_000); assert.equal(shown.length, 0); messages = [conversation(71, { id: 'mute-race', kind: 'org' })]; - await vi.advanceTimersByTimeAsync(15_000); + await vi.advanceTimersByTimeAsync(60_000); assert.equal(shown.length, 1); assert.equal(play.mock.calls.length, 1); } finally { @@ -642,8 +657,11 @@ test('muting while an alert waits for the cross-tab lock suppresses it', async ( const target = { kind: 'org' as const, org: { id: 'lock-mute', name: 'Team' } }; let messages: ConversationSummary[] = []; let releaseAlert: (() => Promise) | undefined; - vi.stubGlobal('navigator', { locks: { request: (_name: string, notify: () => Promise) => { - releaseAlert = notify; + vi.stubGlobal('navigator', { locks: { request: (_name: string, options: LockOptions | (() => Promise), run?: () => Promise) => { + if (typeof options !== 'function') return run!(); + return new Promise((resolve) => { + releaseAlert = async () => { await options(); resolve(); }; + }); } } }); vi.stubGlobal('fetch', async (_url: string, options?: RequestInit) => { if (options?.method === 'PUT') return Response.json({ muted_until_at: '9999-12-31T00:00:00Z' }); @@ -651,9 +669,9 @@ test('muting while an alert waits for the cross-tab lock suppresses it', async ( }); const stop = watchRemoteAlerts('me'); try { - await vi.advanceTimersByTimeAsync(0); + await vi.advanceTimersByTimeAsync(60_000); messages = [conversation(75, { id: 'lock-mute', kind: 'org' })]; - await vi.advanceTimersByTimeAsync(15_000); + await vi.advanceTimersByTimeAsync(60_000); assert.ok(releaseAlert); await setConversationMute(target, 'forever'); await releaseAlert(); @@ -686,17 +704,17 @@ test('popping an unread stash without opening its thread does not replay old mes }); const stop = watchRemoteAlerts('me'); try { - await vi.advanceTimersByTimeAsync(0); + await vi.advanceTimersByTimeAsync(60_000); await loadTab('stashed'); assert.equal(await setConversationArchived(target, false), true); - await vi.advanceTimersByTimeAsync(15_000); + await vi.advanceTimersByTimeAsync(60_000); assert.equal(chatSnapshot().threads['org:old-stash'], undefined); assert.equal(chatSnapshot().unread, 1); assert.equal(shown.length, 0); assert.equal(play.mock.calls.length, 0); id = 81; - await vi.advanceTimersByTimeAsync(15_000); + await vi.advanceTimersByTimeAsync(60_000); assert.equal(shown.length, 1); assert.equal(play.mock.calls.length, 1); } finally { @@ -744,7 +762,7 @@ test.each(['blur', 'hidden', 'leave'] as const)('viewing one remote allows alert assert.equal(shown.length, 2); await vi.advanceTimersByTimeAsync(10_000); id = 94; - await vi.advanceTimersByTimeAsync(15_000); + await vi.advanceTimersByTimeAsync(release === 'hidden' ? 60_000 : 15_000); assert.equal(shown.length, 3); assert.equal(play.mock.calls.length, 2); } finally { @@ -816,9 +834,9 @@ test('alerts from recent activity even when older pins occupy the visible first }); const stop = watchRemoteAlerts('me'); try { - await vi.advanceTimersByTimeAsync(0); + await vi.advanceTimersByTimeAsync(60_000); incoming = conversation(101, { id: 'recent-unpinned', kind: 'org' }); - await vi.advanceTimersByTimeAsync(15_000); + await vi.advanceTimersByTimeAsync(60_000); assert.equal(shown.length, 1); assert.equal(play.mock.calls.length, 1); assert.equal(chatSnapshot().conversations?.[0].id, 'old-pinned');