From 1227828d73bb2db5c202fc92bd1b12807a6871d5 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Mon, 3 May 2021 17:58:47 +0200 Subject: [PATCH] fix(hash): force navigation restore on manual navigation Fix #916 --- __tests__/hash-manual-navigation.spec.ts | 80 ++++++++++++++++++++++++ src/router.ts | 37 ++++++++++- 2 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 __tests__/hash-manual-navigation.spec.ts diff --git a/__tests__/hash-manual-navigation.spec.ts b/__tests__/hash-manual-navigation.spec.ts new file mode 100644 index 000000000..5e1315c04 --- /dev/null +++ b/__tests__/hash-manual-navigation.spec.ts @@ -0,0 +1,80 @@ +import { createMemoryHistory, createRouter } from '../src' +import { tick } from './utils' + +const component = {} + +declare module '../src' { + export interface RouterHistory { + changeURL(url: string): void + } +} + +describe('hash history edge cases', () => { + it('correctly sets the url when it is manually changed but aborted with a redirect in guard', async () => { + const history = createMemoryHistory() + const router = createRouter({ + history, + routes: [ + { path: '/', component }, + { path: '/foo', component }, + ], + }) + + await router.push('/foo?step=1') + await router.push('/foo?step=2') + await router.push('/foo?step=3') + router.back() + await tick() // wait for router listener on history + expect(router.currentRoute.value.fullPath).toBe('/foo?step=2') + + // force a redirect next time + const removeListener = router.beforeEach(to => { + if (to.path === '/') { + return '/foo?step=2' + } + }) + + // const spy = jest.spyOn(history, 'go') + + history.changeURL('/') + await tick() + expect(router.currentRoute.value.fullPath).toBe('/foo?step=2') + expect(history.location).toBe('/foo?step=2') + // expect(spy).toHaveBeenCalledTimes(1) + // expect(spy).toHaveBeenCalledWith(-1) + }) + + it('correctly sets the url when it is manually changed but aborted with guard', async () => { + const history = createMemoryHistory() + const router = createRouter({ + history, + routes: [ + { path: '/', component }, + { path: '/foo', component }, + ], + }) + + await router.push('/foo?step=1') + await router.push('/foo?step=2') + await router.push('/foo?step=3') + router.back() + await tick() // wait for router listener on history + expect(router.currentRoute.value.fullPath).toBe('/foo?step=2') + + // force a redirect next time + const removeListener = router.beforeEach(to => { + if (to.path === '/') { + return false + } + }) + + // const spy = jest.spyOn(history, 'go') + + history.changeURL('/') + await tick() + expect(router.currentRoute.value.fullPath).toBe('/foo?step=2') + expect(history.location).toBe('/foo?step=2') + // expect(spy).toHaveBeenCalledTimes(1) + // expect(spy).toHaveBeenCalledWith(-1) + }) +}) diff --git a/src/router.ts b/src/router.ts index 8afc901da..3917a5eb8 100644 --- a/src/router.ts +++ b/src/router.ts @@ -13,7 +13,7 @@ import { RouteLocationOptions, MatcherLocationRaw, } from './types' -import { RouterHistory, HistoryState } from './history/common' +import { RouterHistory, HistoryState, NavigationType } from './history/common' import { ScrollPosition, getSavedScrollPosition, @@ -969,7 +969,24 @@ export function createRouter(options: RouterOptions): Router { (error as NavigationRedirectError).to, toLocation // avoid an uncaught rejection, let push call triggerError - ).catch(noop) + ) + .then(failure => { + // manual change in hash history #916 ending up in the URL not + // changing but it was changed by the manual url change, so we + // need to manually change it ourselves + if ( + isNavigationFailure( + failure, + ErrorTypes.NAVIGATION_ABORTED | + ErrorTypes.NAVIGATION_DUPLICATED + ) && + !info.delta && + info.type === NavigationType.pop + ) { + routerHistory.go(-1, false) + } + }) + .catch(noop) // avoid the then branch return Promise.reject() } @@ -989,7 +1006,21 @@ export function createRouter(options: RouterOptions): Router { ) // revert the navigation - if (failure && info.delta) routerHistory.go(-info.delta, false) + if (failure) { + if (info.delta) { + routerHistory.go(-info.delta, false) + } else if ( + info.type === NavigationType.pop && + isNavigationFailure( + failure, + ErrorTypes.NAVIGATION_ABORTED | ErrorTypes.NAVIGATION_DUPLICATED + ) + ) { + // manual change in hash history #916 + // it's like a push but lacks the information of the direction + routerHistory.go(-1, false) + } + } triggerAfterEach( toLocation as RouteLocationNormalizedLoaded,