Skip to content

Commit 18dbdc2

Browse files
authored
fix: trigger redirect on popstate (vuejs#592)
1 parent 30d2d26 commit 18dbdc2

File tree

4 files changed

+74
-48
lines changed

4 files changed

+74
-48
lines changed

__tests__/warnings.spec.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ describe('warnings', () => {
1515
{ path: '/redirect', redirect: { params: { foo: 'f' } } },
1616
],
1717
})
18-
await router.push('/redirect').catch(() => {})
18+
try {
19+
await router.push('/redirect')
20+
} catch (err) {}
1921
expect('Invalid redirect found').toHaveBeenWarned()
2022
})
2123

e2e/hash/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const router = createRouter({
2323
history: createWebHashHistory('/' + __dirname + '/#/'),
2424
routes: [
2525
{ path: '/', component: Home },
26+
{ path: '/redirect', name: 'redirect', redirect: '/foo' },
2627
{ path: '/foo', component: Foo },
2728
{ path: '/bar', component: Bar },
2829
{ path: '/unicode/:id', name: 'unicode', component: Unicode },

e2e/specs/hash.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,20 @@ module.exports = {
9292

9393
.end()
9494
},
95+
96+
/** @type {import('nightwatch').NightwatchTest} */
97+
'manual hash change to trigger redirect': function (browser) {
98+
browser
99+
.url(baseURL + '/')
100+
.waitForElementPresent('#app > *', 1000)
101+
.assert.containsText('.view', 'home')
102+
103+
.execute(function () {
104+
window.___location.hash = '#/redirect'
105+
})
106+
.assert.containsText('.view', 'Foo')
107+
.assert.urlEquals(baseURL + '/foo')
108+
109+
.end()
110+
},
95111
}

src/router.ts

Lines changed: 54 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -545,24 +545,13 @@ export function createRouter(options: RouterOptions): Router {
545545
return push(assign(locationAsObject(to), { replace: true }))
546546
}
547547

548-
function pushWithRedirect(
549-
to: RouteLocationRaw | RouteLocation,
550-
redirectedFrom?: RouteLocation
551-
): Promise<NavigationFailure | void | undefined> {
552-
const targetLocation: RouteLocation = (pendingLocation = resolve(to))
553-
const from = currentRoute.value
554-
const data: HistoryState | undefined = (to as RouteLocationOptions).state
555-
const force: boolean | undefined = (to as RouteLocationOptions).force
556-
// to could be a string where `replace` is a function
557-
const replace = (to as RouteLocationOptions).replace === true
558-
559-
const lastMatched =
560-
targetLocation.matched[targetLocation.matched.length - 1]
548+
function handleRedirectRecord(to: RouteLocation): RouteLocationRaw | void {
549+
const lastMatched = to.matched[to.matched.length - 1]
561550
if (lastMatched && lastMatched.redirect) {
562551
const { redirect } = lastMatched
563552
// transform it into an object to pass the original RouteLocaleOptions
564553
let newTargetLocation = locationAsObject(
565-
typeof redirect === 'function' ? redirect(targetLocation) : redirect
554+
typeof redirect === 'function' ? redirect(to) : redirect
566555
)
567556

568557
if (
@@ -576,29 +565,42 @@ export function createRouter(options: RouterOptions): Router {
576565
null,
577566
2
578567
)}\n when navigating to "${
579-
targetLocation.fullPath
568+
to.fullPath
580569
}". A redirect must contain a name or path. This will break in production.`
581570
)
582-
return Promise.reject(new Error('Invalid redirect'))
571+
throw new Error('Invalid redirect')
583572
}
573+
574+
return assign(
575+
{
576+
query: to.query,
577+
hash: to.hash,
578+
params: to.params,
579+
},
580+
newTargetLocation
581+
)
582+
}
583+
}
584+
585+
function pushWithRedirect(
586+
to: RouteLocationRaw | RouteLocation,
587+
redirectedFrom?: RouteLocation
588+
): Promise<NavigationFailure | void | undefined> {
589+
const targetLocation: RouteLocation = (pendingLocation = resolve(to))
590+
const from = currentRoute.value
591+
const data: HistoryState | undefined = (to as RouteLocationOptions).state
592+
const force: boolean | undefined = (to as RouteLocationOptions).force
593+
// to could be a string where `replace` is a function
594+
const replace = (to as RouteLocationOptions).replace === true
595+
596+
const shouldRedirect = handleRedirectRecord(targetLocation)
597+
598+
if (shouldRedirect)
584599
return pushWithRedirect(
585-
assign(
586-
{
587-
query: targetLocation.query,
588-
hash: targetLocation.hash,
589-
params: targetLocation.params,
590-
},
591-
newTargetLocation,
592-
{
593-
state: data,
594-
force,
595-
replace,
596-
}
597-
),
600+
assign(shouldRedirect, { state: data, force, replace }),
598601
// keep original redirectedFrom if it exists
599602
redirectedFrom || targetLocation
600603
)
601-
}
602604

603605
// if it was a redirect we already called `pushWithRedirect` above
604606
const toLocation = targetLocation as RouteLocationNormalized
@@ -616,7 +618,7 @@ export function createRouter(options: RouterOptions): Router {
616618
from,
617619
from,
618620
// this is a push, the only way for it to be triggered from a
619-
// history.listen is with a redirect, which makes it become a pus
621+
// history.listen is with a redirect, which makes it become a push
620622
true,
621623
// This cannot be the first navigation because the initial ___location
622624
// cannot be manually navigated to
@@ -625,20 +627,12 @@ export function createRouter(options: RouterOptions): Router {
625627
}
626628

627629
return (failure ? Promise.resolve(failure) : navigate(toLocation, from))
628-
.catch((error: NavigationFailure | NavigationRedirectError) => {
629-
if (
630-
isNavigationFailure(
631-
error,
632-
ErrorTypes.NAVIGATION_ABORTED |
633-
ErrorTypes.NAVIGATION_CANCELLED |
634-
ErrorTypes.NAVIGATION_GUARD_REDIRECT
635-
)
636-
) {
637-
return error
638-
}
639-
// unknown error, rejects
640-
return triggerError(error)
641-
})
630+
.catch((error: NavigationFailure | NavigationRedirectError) =>
631+
isNavigationFailure(error)
632+
? error
633+
: // reject any unknown error
634+
triggerError(error)
635+
)
642636
.then((failure: NavigationFailure | NavigationRedirectError | void) => {
643637
if (failure) {
644638
if (
@@ -896,7 +890,19 @@ export function createRouter(options: RouterOptions): Router {
896890
function setupListeners() {
897891
removeHistoryListener = routerHistory.listen((to, _from, info) => {
898892
// cannot be a redirect route because it was in history
899-
const toLocation = resolve(to) as RouteLocationNormalized
893+
let toLocation = resolve(to) as RouteLocationNormalized
894+
895+
// due to dynamic routing, and to hash history with manual navigation
896+
// (manually changing the url or calling history.hash = '#/somewhere'),
897+
// there could be a redirect record in history
898+
const shouldRedirect = handleRedirectRecord(toLocation)
899+
if (shouldRedirect) {
900+
pushWithRedirect(
901+
assign(shouldRedirect, { replace: true }),
902+
toLocation
903+
).catch(noop)
904+
return
905+
}
900906

901907
pendingLocation = toLocation
902908
const from = currentRoute.value
@@ -927,9 +933,10 @@ export function createRouter(options: RouterOptions): Router {
927933
// the error is already handled by router.push we just want to avoid
928934
// logging the error
929935
pushWithRedirect(
936+
// TODO: should we force replace: true
930937
(error as NavigationRedirectError).to,
931938
toLocation
932-
// avoid an uncaught rejection
939+
// avoid an uncaught rejection, let push call triggerError
933940
).catch(noop)
934941
// avoid the then branch
935942
return Promise.reject()

0 commit comments

Comments
 (0)