Files
kapteins-daagbok/client/src/services/pwaStartup.test.ts
T
elpatron 951b5b3f1c fix(pwa): Startup-Hänger nach Inaktivität stabilisieren
Verhindert Blank-Screens und Reload-Schleifen beim Wiederöffnen der PWA, indem Recovery nur bei bestätigter SW-Übernahme greift und Navigationen bevorzugt frisch aus dem Netzwerk geladen werden.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-31 15:00:51 +02:00

147 lines
4.2 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from 'vitest'
import {
forcePwaRecovery,
markReloadAttempt,
recentlyAttemptedReload,
reconcileServiceWorkerOnStartup,
reconcileVersionOnStartup
} from './pwaStartup.js'
const STALE_RECOVERY_COUNT_KEY = 'pwa_stale_recovery_count'
const STALE_RECOVERY_LAST_KEY = 'pwa_stale_recovery_last_ts'
describe('pwaStartup reload guards', () => {
beforeEach(() => {
sessionStorage.clear()
})
it('blocks repeated reload attempts within the debounce window', () => {
expect(recentlyAttemptedReload(10_000)).toBe(false)
markReloadAttempt(10_000)
expect(recentlyAttemptedReload(12_000)).toBe(true)
expect(recentlyAttemptedReload(15_000)).toBe(false)
})
})
describe('forcePwaRecovery stale counter reset', () => {
beforeEach(() => {
sessionStorage.clear()
vi.unstubAllEnvs()
vi.restoreAllMocks()
})
it('clears stale recovery counter before hard recovery reload', async () => {
vi.stubEnv('DEV', false)
sessionStorage.setItem(STALE_RECOVERY_COUNT_KEY, '2')
sessionStorage.setItem(STALE_RECOVERY_LAST_KEY, String(Date.now()))
const reload = vi.fn()
vi.stubGlobal('location', { reload })
vi.stubGlobal('caches', {
keys: vi.fn().mockResolvedValue([]),
delete: vi.fn()
})
Object.defineProperty(navigator, 'serviceWorker', {
configurable: true,
value: {
getRegistrations: vi.fn().mockResolvedValue([])
}
})
await forcePwaRecovery()
expect(sessionStorage.getItem(STALE_RECOVERY_COUNT_KEY)).toBeNull()
expect(sessionStorage.getItem(STALE_RECOVERY_LAST_KEY)).toBeNull()
expect(reload).toHaveBeenCalledOnce()
})
it('returns false when hard recovery was just attempted', async () => {
sessionStorage.setItem('pwa_hard_recovery_ts', String(Date.now()))
const result = await forcePwaRecovery()
expect(result).toBe(false)
})
})
describe('reconcileServiceWorkerOnStartup', () => {
beforeEach(() => {
sessionStorage.clear()
vi.unstubAllEnvs()
})
it('returns false in dev mode', async () => {
vi.stubEnv('DEV', true)
await expect(reconcileServiceWorkerOnStartup()).resolves.toBe(false)
})
it('returns false when no waiting worker exists', async () => {
vi.stubEnv('DEV', false)
Object.defineProperty(navigator, 'serviceWorker', {
configurable: true,
value: {
controller: {},
getRegistration: vi.fn().mockResolvedValue({
waiting: null,
installing: null,
update: vi.fn().mockResolvedValue(undefined),
addEventListener: vi.fn()
}),
addEventListener: vi.fn()
}
})
await expect(reconcileServiceWorkerOnStartup()).resolves.toBe(false)
})
it('returns false when waiting worker activation never takes over', async () => {
vi.useFakeTimers()
const postMessage = vi.fn()
const addEventListener = vi.fn()
vi.stubEnv('DEV', false)
Object.defineProperty(navigator, 'serviceWorker', {
configurable: true,
value: {
controller: { scriptURL: '/sw.js?v=1' },
getRegistration: vi.fn().mockResolvedValue({
waiting: { postMessage },
installing: null,
update: vi.fn().mockResolvedValue(undefined),
addEventListener: vi.fn()
}),
addEventListener
}
})
const reconcilePromise = reconcileServiceWorkerOnStartup()
await vi.advanceTimersByTimeAsync(4_000)
await expect(reconcilePromise).resolves.toBe(false)
expect(postMessage).toHaveBeenCalledWith({ type: 'SKIP_WAITING' })
vi.useRealTimers()
})
})
describe('reconcileVersionOnStartup', () => {
beforeEach(() => {
sessionStorage.clear()
vi.unstubAllEnvs()
vi.restoreAllMocks()
})
it('returns noop in dev mode', async () => {
vi.stubEnv('DEV', true)
await expect(reconcileVersionOnStartup()).resolves.toBe('noop')
})
it('returns noop when deployed version matches bundled version', async () => {
vi.stubEnv('DEV', false)
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
ok: true,
json: async () => ({ version: '0.1.0.57' })
}))
vi.stubGlobal('__APP_VERSION__', '0.1.0.57')
await expect(reconcileVersionOnStartup()).resolves.toBe('noop')
})
})