import { describe, expect, it, vi } from 'vitest' import { clearNavigationRecoveryAttempt, isStaleRouteAssetError, recoverNavigationError, type NavigationRecoveryDependencies, } from './navigationRecovery' function memoryStorage() { const values = new Map() return { getItem: (key: string) => values.get(key) ?? null, setItem: (key: string, value: string) => values.set(key, value), removeItem: (key: string) => values.delete(key), } } function dependencies(overrides: Partial = {}) { return { storage: memoryStorage(), now: () => 1_000, hasUnsavedWork: () => false, confirmDiscard: vi.fn(async () => true), discardUnsavedWork: vi.fn(), navigate: vi.fn(), showError: vi.fn(), ...overrides, } satisfies NavigationRecoveryDependencies } describe('navigationRecovery', () => { it.each([ 'Failed to fetch dynamically imported module: /assets/page.js', 'Importing a module script failed', 'ChunkLoadError: Loading chunk 42 failed', 'Loading CSS chunk page failed', ])('recognizes stale deployed route assets: %s', (message) => { expect(isStaleRouteAssetError(new TypeError(message))).toBe(true) }) it('navigates once to the intended route after a stale lazy chunk fails', async () => { const deps = dependencies() await expect(recoverNavigationError( new TypeError('Failed to fetch dynamically imported module'), '/overseas-warehouse/inventory', deps, )).resolves.toBe(true) expect(deps.discardUnsavedWork).toHaveBeenCalledOnce() expect(deps.navigate).toHaveBeenCalledWith('/overseas-warehouse/inventory') expect(deps.showError).not.toHaveBeenCalled() }) it('asks before discarding dirty work and stays put when the user cancels', async () => { const deps = dependencies({ hasUnsavedWork: () => true, confirmDiscard: vi.fn(async () => false) }) await recoverNavigationError(new Error('Importing a module script failed'), '/system/users', deps) expect(deps.confirmDiscard).toHaveBeenCalledOnce() expect(deps.discardUnsavedWork).not.toHaveBeenCalled() expect(deps.navigate).not.toHaveBeenCalled() }) it('prevents reload loops and clears the recovery marker after navigation succeeds', async () => { const storage = memoryStorage() const deps = dependencies({ storage }) const error = new Error('ChunkLoadError: Loading chunk page failed') await recoverNavigationError(error, '/system/users', deps) await recoverNavigationError(error, '/system/users', deps) expect(deps.navigate).toHaveBeenCalledOnce() expect(deps.showError).toHaveBeenCalledWith('页面资源加载失败,请稍后重试或手动刷新') clearNavigationRecoveryAttempt(storage) await recoverNavigationError(error, '/system/users', deps) expect(deps.navigate).toHaveBeenCalledTimes(2) }) it('reports ordinary router failures without reloading the application', async () => { const deps = dependencies() await expect(recoverNavigationError(new Error('Unexpected guard failure'), '/customers', deps)).resolves.toBe(false) expect(deps.navigate).not.toHaveBeenCalled() expect(deps.showError).toHaveBeenCalledWith('页面切换失败,请重试') }) })