Skip to content

Commit

Permalink
fix(guards): skip update and leave guards of unmounted views
Browse files Browse the repository at this point in the history
  • Loading branch information
posva committed Jul 21, 2020
1 parent 41bffda commit f22e70a
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 9 deletions.
28 changes: 28 additions & 0 deletions __tests__/guards/beforeRouteLeave.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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)
Expand Down
14 changes: 14 additions & 0 deletions __tests__/guards/beforeRouteUpdate.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 })
Expand Down
6 changes: 6 additions & 0 deletions __tests__/lazyLoading.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
Expand All @@ -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)
})
Expand Down
7 changes: 3 additions & 4 deletions e2e/multi-app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,14 @@ let looper = [1, 2, 3]
const NamedViews: RouteComponent[] = looper.map(i => ({
name: 'part-' + i,

template: `<div class="named part-${i}">Part ${i}. Updated <span class="count">{{ count }}</span></div>`,
template: `<div class="named" id="part-${i}">Part ${i}. Updated <span class="count">{{ count }}</span></div>`,

data: () => ({ count: 0 }),

beforeRouteUpdate(to, from, next) {
console.log('update of', i)
// @ts-ignore
// this.count++
this.count++
next()
},
}))
Expand Down Expand Up @@ -118,7 +119,6 @@ looper.forEach((n, i) => {
mountBtn.addEventListener('click', () => {
let app = (apps[i] = createApp({
template: `
<div id="app-${i}">
<ul>
<li><router-link to="/">Home</router-link></li>
<li><router-link to="/users/1">User 1</router-link></li>
Expand All @@ -127,7 +127,6 @@ looper.forEach((n, i) => {
<router-view></router-view>
<router-view name="part-${n}"></router-view>
</div>
`,
}))
app.use(router)
Expand Down
10 changes: 5 additions & 5 deletions e2e/specs/multi-app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
},
}
3 changes: 3 additions & 0 deletions src/navigationGuards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down

0 comments on commit f22e70a

Please sign in to comment.