From: Eduardo San Martin Morote Date: Mon, 1 Mar 2021 09:33:01 +0000 (+0100) Subject: fix(view): correctly reuse instance guards (#795) X-Git-Tag: v4.0.5~25 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d4fde599803a1be9d4823de0e406c9ce66143e2c;p=thirdparty%2Fvuejs%2Frouter.git fix(view): correctly reuse instance guards (#795) --- diff --git a/e2e/guards-instances/index.ts b/e2e/guards-instances/index.ts index b2e1f719..f8bf36e8 100644 --- a/e2e/guards-instances/index.ts +++ b/e2e/guards-instances/index.ts @@ -94,6 +94,7 @@ function createTestComponent(key: string) { } const Foo = createTestComponent('Foo') +const Bar = createTestComponent('Bar') const One = createTestComponent('One') const Two = createTestComponent('Two') const Aux = createTestComponent('Aux') @@ -111,6 +112,11 @@ const router = createRouter({ path: '/f/:id', component: Foo, }, + // TODO: test that the onBeforeRouteUpdate isn't kept + { + path: '/b/:id', + component: Bar, + }, { path: '/named-one', components: { diff --git a/e2e/specs/suspense.js b/e2e/specs/suspense.js new file mode 100644 index 00000000..cee36efe --- /dev/null +++ b/e2e/specs/suspense.js @@ -0,0 +1,39 @@ +const bsStatus = require('../browserstack-send-status') + +module.exports = { + ...bsStatus(), + + '@tags': [], + + /** @type {import('nightwatch').NightwatchTest} */ + 'suspense with guards': function (browser) { + browser + .url('http://localhost:8080/suspense/foo') + .waitForElementPresent('#app > *', 1000) + + browser + .click('li:nth-child(2) a') + .waitForElementPresent('#Foo', 1000) + .click('li:nth-child(4) a') + .click('li:nth-child(3) a') + .waitForElementPresent('#FooAsync', 1000) + .click('li:nth-child(4) a') + .click('li:nth-child(2) a') + .waitForElementPresent('#Foo', 1000) + .click('li:nth-child(4) a') + .click('li:nth-child(1) a') + .expect.element('#logs') + .text.to.equal( + [ + `Foo: setup:update /foo - /foo?n=1`, + `Foo: setup:leave /foo?n=1 - /foo-async`, + `FooAsync: setup:update /foo-async - /foo-async?n=1`, + `FooAsync: setup:leave /foo-async?n=1 - /foo`, + `Foo: setup:update /foo - /foo?n=1`, + `Foo: setup:leave /foo?n=1 - /`, + ].join('\n') + ) + + browser.end() + }, +} diff --git a/e2e/suspense/index.html b/e2e/suspense/index.html new file mode 100644 index 00000000..757fb4b3 --- /dev/null +++ b/e2e/suspense/index.html @@ -0,0 +1,17 @@ + + + + + + + Vue Router e2e tests - Suspense + + + + + << Back to Homepage +
+ +
+ + diff --git a/e2e/suspense/index.ts b/e2e/suspense/index.ts new file mode 100644 index 00000000..9ec46a77 --- /dev/null +++ b/e2e/suspense/index.ts @@ -0,0 +1,110 @@ +import { + createRouter, + createWebHistory, + onBeforeRouteUpdate, + onBeforeRouteLeave, +} from '../../src' +import { createApp, ref, reactive, defineComponent } from 'vue' + +const Home = defineComponent({ + template: ` +
+

Home

+
+ `, +}) + +const logs = ref([]) + +const state = reactive({ + enter: 0, + update: 0, + leave: 0, +}) + +const delay = (t: number) => new Promise(r => setTimeout(r, t)) + +/** + * creates a component that logs the guards + * @param name + */ +function createTestComponent(key: string, isAsync = false) { + return defineComponent({ + name: key, + template: `
${key}
`, + + setup() { + onBeforeRouteUpdate((to, from) => { + logs.value.push( + `${key}: setup:update ${from.fullPath} - ${to.fullPath}` + ) + }) + onBeforeRouteLeave((to, from) => { + logs.value.push(`${key}: setup:leave ${from.fullPath} - ${to.fullPath}`) + }) + + return isAsync ? delay(100).then(() => ({})) : {} + }, + }) +} + +const Foo = createTestComponent('Foo') +const FooAsync = createTestComponent('FooAsync', true) + +const webHistory = createWebHistory('/' + __dirname) +const router = createRouter({ + history: webHistory, + routes: [ + { path: '/', component: Home }, + { + path: '/foo', + component: Foo, + }, + { + path: '/foo-async', + component: FooAsync, + }, + ], +}) + +const app = createApp({ + template: ` +
+

Suspense

+ +
+route: {{ $route.fullPath }}
+enters: {{ state.enter }}
+updates: {{ state.update }}
+leaves: {{ state.leave }}
+      
+ +
{{ logs.join('\\n') }}
+ + + +
    +
  • /
  • +
  • /foo
  • +
  • /foo-async
  • +
  • {{ route.fullPath }}
  • +
+ + + + + + +
+ `, + setup() { + return { state, logs } + }, +}) + +app.use(router) + +// @ts-ignore +window.r = router + +app.mount('#app') diff --git a/src/RouterView.ts b/src/RouterView.ts index 883476a0..8d4ca3f3 100644 --- a/src/RouterView.ts +++ b/src/RouterView.ts @@ -73,10 +73,18 @@ export const RouterViewImpl = /*#__PURE__*/ defineComponent({ // instances when navigating to a new route to.instances[name] = instance // the component instance is reused for a different route or name so - // we copy any saved update or leave guards + // we copy any saved update or leave guards. With async setup, the + // mounting component will mount before the matchedRoute changes, + // making instance === oldInstance, so we check if guards have been + // added before. This works because we remove guards when + // unmounting/deactivating components if (from && from !== to && instance && instance === oldInstance) { - to.leaveGuards = from.leaveGuards - to.updateGuards = from.updateGuards + if (!to.leaveGuards.size) { + to.leaveGuards = from.leaveGuards + } + if (!to.updateGuards.size) { + to.updateGuards = from.updateGuards + } } }