fix(frontend): recover login redirect navigation

This commit is contained in:
MMEXA
2026-05-26 23:22:30 +08:00
parent 4933ae9014
commit a9e6a7d644
4 changed files with 156 additions and 15 deletions
@@ -0,0 +1,73 @@
import { describe, expect, it, vi } from 'vitest'
import { createMemoryHistory, createRouter, type Router } from 'vue-router'
import { navigateAfterLogin } from '../loginRedirect'
function createRouterMock(push: Router['push']): Router {
return { push } as Router
}
async function createDuplicatedNavigationFailure(path: string) {
const router = createRouter({
history: createMemoryHistory(),
routes: [
{
path,
component: {},
},
],
})
await router.push(path)
return router.push(path)
}
async function createAbortedNavigationFailure(path: string) {
const router = createRouter({
history: createMemoryHistory(),
routes: [
{
path,
component: {},
},
],
})
router.beforeEach(() => false)
return router.push(path)
}
describe('navigateAfterLogin', () => {
it('treats duplicated Vue Router navigation as a completed login navigation', async () => {
const push = vi.fn<Router['push']>().mockResolvedValue(await createDuplicatedNavigationFailure('/dashboard'))
const documentNavigate = vi.fn()
const result = await navigateAfterLogin(createRouterMock(push), '/dashboard', documentNavigate)
expect(push).toHaveBeenCalledWith('/dashboard')
expect(documentNavigate).not.toHaveBeenCalled()
expect(result).toBe('already-there')
})
it('falls back to document navigation when route chunk loading rejects during SPA navigation', async () => {
const push = vi.fn<Router['push']>().mockRejectedValue(new Error('Failed to fetch dynamically imported module'))
const documentNavigate = vi.fn()
const result = await navigateAfterLogin(createRouterMock(push), '/admin/dashboard', documentNavigate)
expect(push).toHaveBeenCalledWith('/admin/dashboard')
expect(documentNavigate).toHaveBeenCalledWith('/admin/dashboard')
expect(result).toBe('document')
})
it('falls back to document navigation when the router reports a real navigation failure', async () => {
const push = vi.fn<Router['push']>().mockResolvedValue(await createAbortedNavigationFailure('/dashboard'))
const documentNavigate = vi.fn()
const result = await navigateAfterLogin(createRouterMock(push), '/dashboard', documentNavigate)
expect(push).toHaveBeenCalledWith('/dashboard')
expect(documentNavigate).toHaveBeenCalledWith('/dashboard')
expect(result).toBe('document')
})
})
@@ -0,0 +1,33 @@
import { isNavigationFailure, NavigationFailureType, type Router } from 'vue-router'
export type LoginNavigationResult = 'router' | 'already-there' | 'document'
type DocumentNavigate = (targetPath: string) => void
function defaultDocumentNavigate(targetPath: string) {
window.location.assign(targetPath)
}
export async function navigateAfterLogin(
router: Router,
targetPath: string,
documentNavigate: DocumentNavigate = defaultDocumentNavigate,
): Promise<LoginNavigationResult> {
try {
const navigationFailure = await router.push(targetPath)
if (isNavigationFailure(navigationFailure, NavigationFailureType.duplicated)) {
return 'already-there'
}
if (navigationFailure) {
documentNavigate(targetPath)
return 'document'
}
return 'router'
} catch {
documentNavigate(targetPath)
return 'document'
}
}