import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { disableSitePush, sitePushDisabled, syncSitePush } from './push-client'; describe('notification pause and resume', () => { const unsubscribe = vi.fn(); const getSubscription = vi.fn(); const subscribe = vi.fn(); const fetchMock = vi.fn(); let local: Map; let session: Map; const storage = (values: Map) => ({ getItem: (key: string) => values.get(key) ?? null, setItem: (key: string, value: string) => { values.set(key, value); }, removeItem: (key: string) => { values.delete(key); }, }); beforeEach(() => { vi.resetAllMocks(); local = new Map(); session = new Map(); vi.stubGlobal('localStorage', storage(local)); vi.stubGlobal('sessionStorage', storage(session)); vi.stubGlobal('window', new EventTarget()); const registration = { pushManager: { getSubscription, subscribe } }; vi.stubGlobal('navigator', { serviceWorker: { getRegistration: vi.fn().mockResolvedValue(registration), register: vi.fn().mockResolvedValue(registration), } }); vi.stubGlobal('fetch', fetchMock); unsubscribe.mockResolvedValue(true); getSubscription.mockResolvedValue({ unsubscribe, toJSON: () => ({ endpoint: 'endpoint' }) }); fetchMock.mockResolvedValue({ ok: true, json: async () => ({ publicKey: 'key', canAdmin: false }) }); }); afterEach(() => vi.unstubAllGlobals()); it('unsubscribes and prevents automatic registration while paused', async () => { session.set('site-push-synced-user', '1'); await disableSitePush('user'); expect(unsubscribe).toHaveBeenCalledOnce(); expect(sitePushDisabled()).toBe(true); expect(session.has('site-push-synced-user')).toBe(false); await syncSitePush('user'); expect(fetchMock).not.toHaveBeenCalled(); }); it('registers both notification features when explicitly resuming', async () => { await disableSitePush('user'); getSubscription.mockResolvedValue(null); subscribe.mockResolvedValue({ toJSON: () => ({ endpoint: 'new-endpoint' }) }); await syncSitePush('user', true); expect(subscribe).toHaveBeenCalledOnce(); expect(fetchMock).toHaveBeenCalledWith('/api/comments/push-subscription', expect.objectContaining({ method: 'POST' })); expect(fetchMock).toHaveBeenCalledWith('/api/commission/push-subscription', expect.objectContaining({ method: 'POST' })); }); it('does not report a successful pause when unsubscribe fails', async () => { unsubscribe.mockResolvedValue(false); await expect(disableSitePush('user')).rejects.toThrow('push-unsubscribe-failed'); expect(sitePushDisabled()).toBe(false); }); it('waits for an in-flight sync before unsubscribing', async () => { const syncing = syncSitePush('user'); const pausing = disableSitePush('user'); await Promise.all([syncing, pausing]); expect(sitePushDisabled()).toBe(true); expect(unsubscribe.mock.invocationCallOrder[0]).toBeGreaterThan(fetchMock.mock.invocationCallOrder.at(-1)!); }); });