From: Eduardo San Martin Morote Date: Tue, 21 Jul 2020 12:32:20 +0000 (+0200) Subject: fix(guards): skip update and leave guards of unmounted views X-Git-Tag: v4.0.0-beta.3~8 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f22e70a6d15ce9834c9eb841d9fe9547c5d21e24;p=thirdparty%2Fvuejs%2Frouter.git fix(guards): skip update and leave guards of unmounted views --- diff --git a/__tests__/guards/beforeRouteLeave.spec.ts b/__tests__/guards/beforeRouteLeave.spec.ts index 12f5c69c..ff561833 100644 --- a/__tests__/guards/beforeRouteLeave.spec.ts +++ b/__tests__/guards/beforeRouteLeave.spec.ts @@ -100,13 +100,36 @@ describe('beforeRouteLeave', () => { await router.push('/guard') expect(beforeRouteLeave).not.toHaveBeenCalled() + // simulate a mounted route component + router.currentRoute.value.matched[0].instances.default = {} as any + await router.push('/foo') expect(beforeRouteLeave).toHaveBeenCalledTimes(1) }) + it('does not call beforeRouteLeave guard if the view is not mounted', async () => { + const router = createRouter({ routes }) + beforeRouteLeave.mockImplementationOnce((to, from, next) => { + next() + }) + await router.push('/guard') + expect(beforeRouteLeave).not.toHaveBeenCalled() + + // usually we would have to simulate a mounted route component + // router.currentRoute.value.matched[0].instances.default = {} as any + + await router.push('/foo') + expect(beforeRouteLeave).not.toHaveBeenCalled() + }) + it('calls beforeRouteLeave guard on navigation between children', async () => { const router = createRouter({ routes }) await router.push({ name: 'nested-path' }) + + // simulate a mounted route component + router.currentRoute.value.matched[0].instances.default = {} as any + router.currentRoute.value.matched[1].instances.default = {} as any + resetMocks() await router.push({ name: 'nested-path-b' }) expect(nested.nestedEmpty).not.toHaveBeenCalled() @@ -147,6 +170,11 @@ describe('beforeRouteLeave', () => { next() }) + // simulate a mounted route component + router.currentRoute.value.matched[0].instances.default = {} as any + router.currentRoute.value.matched[1].instances.default = {} as any + router.currentRoute.value.matched[2].instances.default = {} as any + await router.push('/') expect(nested.parent).toHaveBeenCalledTimes(1) expect(nested.nestedNested).toHaveBeenCalledTimes(1) diff --git a/__tests__/guards/beforeRouteUpdate.spec.ts b/__tests__/guards/beforeRouteUpdate.spec.ts index 8911ee47..2b539b6e 100644 --- a/__tests__/guards/beforeRouteUpdate.spec.ts +++ b/__tests__/guards/beforeRouteUpdate.spec.ts @@ -33,10 +33,24 @@ describe('beforeRouteUpdate', () => { await router.push('/guard/valid') // not called on initial navigation expect(beforeRouteUpdate).not.toHaveBeenCalled() + // simulate a mounted route component + router.currentRoute.value.matched[0].instances.default = {} as any await router.push('/guard/other') expect(beforeRouteUpdate).toHaveBeenCalledTimes(1) }) + it('does not call beforeRouteUpdate guard if the view is not mounted', async () => { + const router = createRouter({ routes }) + beforeRouteUpdate.mockImplementationOnce(noGuard) + await router.push('/guard/valid') + // not called on initial navigation + expect(beforeRouteUpdate).not.toHaveBeenCalled() + // usually we would have to simulate a mounted route component + // router.currentRoute.value.matched[0].instances.default = {} as any + await router.push('/guard/other') + expect(beforeRouteUpdate).not.toHaveBeenCalled() + }) + it('waits before navigating', async () => { const [promise, resolve] = fakePromise() const router = createRouter({ routes }) diff --git a/__tests__/lazyLoading.spec.ts b/__tests__/lazyLoading.spec.ts index 429909c5..f28f2cc8 100644 --- a/__tests__/lazyLoading.spec.ts +++ b/__tests__/lazyLoading.spec.ts @@ -211,6 +211,9 @@ describe('Lazy Loading', () => { expect(component).toHaveBeenCalledTimes(1) expect(spy).toHaveBeenCalledTimes(0) + // simulate a mounted route component + router.currentRoute.value.matched[0].instances.default = {} as any + await router.push('/') expect(spy).toHaveBeenCalledTimes(1) }) @@ -230,6 +233,9 @@ describe('Lazy Loading', () => { expect(component).toHaveBeenCalledTimes(1) expect(spy).toHaveBeenCalledTimes(0) + // simulate a mounted route component + router.currentRoute.value.matched[0].instances.default = {} as any + await router.push('/bar') expect(spy).toHaveBeenCalledTimes(1) }) diff --git a/e2e/multi-app/index.ts b/e2e/multi-app/index.ts index 3a975f32..65a2ea74 100644 --- a/e2e/multi-app/index.ts +++ b/e2e/multi-app/index.ts @@ -44,13 +44,14 @@ let looper = [1, 2, 3] const NamedViews: RouteComponent[] = looper.map(i => ({ name: 'part-' + i, - template: `
Part ${i}. Updated {{ count }}
`, + template: `
Part ${i}. Updated {{ count }}
`, data: () => ({ count: 0 }), beforeRouteUpdate(to, from, next) { + console.log('update of', i) // @ts-ignore - // this.count++ + this.count++ next() }, })) @@ -118,7 +119,6 @@ looper.forEach((n, i) => { mountBtn.addEventListener('click', () => { let app = (apps[i] = createApp({ template: ` -
`, })) app.use(router) diff --git a/e2e/specs/multi-app.js b/e2e/specs/multi-app.js index 97b94f2d..5659b275 100644 --- a/e2e/specs/multi-app.js +++ b/e2e/specs/multi-app.js @@ -109,16 +109,16 @@ module.exports = { .assert.containsText('#app-1 .home', 'Home') // toggle multiple times .click('#app-1 li:nth-child(2) a') - .assert.containsText('#app-1 .count', '0') + .assert.containsText('#part-1 .count', '0') .click('#app-1 li:nth-child(3) a') - .assert.containsText('#app-1 .count', '1') + .assert.containsText('#part-1 .count', '1') .click('#mount2') - .assert.containsText('#app-2 .user', 'User 2') + .assert.containsText('#app-2 .user', 'User') .click('#app-1 li:nth-child(2) a') // first one keeps updating - .assert.containsText('#app-1 .count', '2') + .assert.containsText('#part-1 .count', '2') // second app only updated once - .assert.containsText('#app-2 .count', '1') + .assert.containsText('#part-2 .count', '1') .click('#mount3') }, } diff --git a/src/navigationGuards.ts b/src/navigationGuards.ts index 53bee104..74a734f7 100644 --- a/src/navigationGuards.ts +++ b/src/navigationGuards.ts @@ -200,6 +200,9 @@ export function extractComponentsGuards( rawComponent = () => promise } + // skip update and leave guards if the route component is not mounted + if (guardType !== 'beforeRouteEnter' && !record.instances[name]) continue + if (isRouteComponent(rawComponent)) { // __vccOpts is added by vue-class-component and contain the regular options let options: ComponentOptions =