From 6a696961a42d1d39640d36a1e65e5097b50030b8 Mon Sep 17 00:00:00 2001 From: Gennadii Fairushin Date: Mon, 29 Jan 2024 08:56:02 +0000 Subject: [PATCH] fix(guards): run beforeRouteEnter with app context (#2117) Fix vuejs/router#2051 --- .../guards/navigatioGuardsInjections.spec.ts | 88 +++++++++++++++++++ packages/router/src/navigationGuards.ts | 31 ++++--- packages/router/src/router.ts | 3 +- 3 files changed, 111 insertions(+), 11 deletions(-) diff --git a/packages/router/__tests__/guards/navigatioGuardsInjections.spec.ts b/packages/router/__tests__/guards/navigatioGuardsInjections.spec.ts index a660b1c12..fe20236be 100644 --- a/packages/router/__tests__/guards/navigatioGuardsInjections.spec.ts +++ b/packages/router/__tests__/guards/navigatioGuardsInjections.spec.ts @@ -48,4 +48,92 @@ describe('inject() within navigation guards', () => { await router.isReady() }) } + + describe('in-component guards', () => { + it('beforeRouteEnter', async () => { + expect.assertions(1) + const router = createRouter({ + routes: [ + { + path: '/', + component: { + template: `
Page
`, + beforeRouteEnter() { + expect(inject('test')).toBe('hello') + }, + }, + }, + ], + }) + factory(router) + await router.isReady() + await router.push('/') + }) + + it('beforeRouteEnter + lazy load', async () => { + expect.assertions(1) + const router = createRouter({ + routes: [ + { + path: '/', + component: () => + new Promise(r => + r({ + template: `
Page
`, + beforeRouteEnter() { + expect(inject('test')).toBe('hello') + }, + }) + ), + }, + ], + }) + factory(router) + await router.isReady() + await router.push('/') + }) + + it('beforeRouteUpdate', async () => { + expect.assertions(1) + const router = createRouter({ + routes: [ + { + path: '/', + component: { + template: `
Page
`, + beforeRouteUpdate() { + expect(inject('test')).toBe('hello') + }, + }, + }, + ], + }) + factory(router) + await router.isReady() + await router.push('/') + await router.push('/#other') + }) + + it('beforeRouteLeave', async () => { + expect.assertions(1) + const router = createRouter({ + routes: [ + { path: '/', component: PageComponent }, + { + path: '/foo', + component: { + template: `
Page
`, + beforeRouteLeave() { + expect(inject('test')).toBe('hello') + }, + }, + }, + ], + }) + factory(router) + await router.isReady() + await router.push('/foo') + await router.push('/') + }) + }) }) diff --git a/packages/router/src/navigationGuards.ts b/packages/router/src/navigationGuards.ts index 148a51f27..27eac6cfc 100644 --- a/packages/router/src/navigationGuards.ts +++ b/packages/router/src/navigationGuards.ts @@ -117,14 +117,16 @@ export function guardToPromiseFn( to: RouteLocationNormalized, from: RouteLocationNormalizedLoaded, record: RouteRecordNormalized, - name: string + name: string, + runWithContext: (fn: () => T) => T ): () => Promise export function guardToPromiseFn( guard: NavigationGuard, to: RouteLocationNormalized, from: RouteLocationNormalizedLoaded, record?: RouteRecordNormalized, - name?: string + name?: string, + runWithContext: (fn: () => T) => T = fn => fn() ): () => Promise { // keep a reference to the enterCallbackArray to prevent pushing callbacks if a new navigation took place const enterCallbackArray = @@ -173,11 +175,13 @@ export function guardToPromiseFn( } // wrapping with Promise.resolve allows it to work with both async and sync guards - const guardReturn = guard.call( - record && record.instances[name!], - to, - from, - __DEV__ ? canOnlyBeCalledOnce(next, to, from) : next + const guardReturn = runWithContext(() => + guard.call( + record && record.instances[name!], + to, + from, + __DEV__ ? canOnlyBeCalledOnce(next, to, from) : next + ) ) let guardCall = Promise.resolve(guardReturn) @@ -231,7 +235,8 @@ export function extractComponentsGuards( matched: RouteRecordNormalized[], guardType: GuardType, to: RouteLocationNormalized, - from: RouteLocationNormalizedLoaded + from: RouteLocationNormalizedLoaded, + runWithContext: (fn: () => T) => T = fn => fn() ) { const guards: Array<() => Promise> = [] @@ -292,7 +297,10 @@ export function extractComponentsGuards( const options: ComponentOptions = (rawComponent as any).__vccOpts || rawComponent const guard = options[guardType] - guard && guards.push(guardToPromiseFn(guard, to, from, record, name)) + guard && + guards.push( + guardToPromiseFn(guard, to, from, record, name, runWithContext) + ) } else { // start requesting the chunk already let componentPromise: Promise< @@ -324,7 +332,10 @@ export function extractComponentsGuards( const options: ComponentOptions = (resolvedComponent as any).__vccOpts || resolvedComponent const guard = options[guardType] - return guard && guardToPromiseFn(guard, to, from, record, name)() + return ( + guard && + guardToPromiseFn(guard, to, from, record, name, runWithContext)() + ) }) ) } diff --git a/packages/router/src/router.ts b/packages/router/src/router.ts index f5cc271ee..b1ec39dfd 100644 --- a/packages/router/src/router.ts +++ b/packages/router/src/router.ts @@ -879,7 +879,8 @@ export function createRouter(options: RouterOptions): Router { enteringRecords, 'beforeRouteEnter', to, - from + from, + runWithContext ) guards.push(canceledNavigationCheck)