}
const Foo = createTestComponent('Foo')
+const Bar = createTestComponent('Bar')
const One = createTestComponent('One')
const Two = createTestComponent('Two')
const Aux = createTestComponent('Aux')
path: '/f/:id',
component: Foo,
},
+ // TODO: test that the onBeforeRouteUpdate isn't kept
+ {
+ path: '/b/:id',
+ component: Bar,
+ },
{
path: '/named-one',
components: {
--- /dev/null
+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()
+ },
+}
--- /dev/null
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="UTF-8" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <meta http-equiv="X-UA-Compatible" content="ie=edge" />
+ <title>Vue Router e2e tests - Suspense</title>
+ <!-- TODO: replace with local imports for promises and anything else needed -->
+ <script src="https://polyfill.io/v3/polyfill.min.js?features=default%2Ces2015"></script>
+ </head>
+ <body>
+ <a href="/"><< Back to Homepage</a>
+ <hr />
+
+ <main id="app"></main>
+ </body>
+</html>
--- /dev/null
+import {
+ createRouter,
+ createWebHistory,
+ onBeforeRouteUpdate,
+ onBeforeRouteLeave,
+} from '../../src'
+import { createApp, ref, reactive, defineComponent } from 'vue'
+
+const Home = defineComponent({
+ template: `
+ <div>
+ <h2>Home</h2>
+ </div>
+ `,
+})
+
+const logs = ref<string[]>([])
+
+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: `<div id="${key}">${key}</div>`,
+
+ 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: `
+ <div id="app">
+ <h1>Suspense</h1>
+
+ <pre>
+route: {{ $route.fullPath }}
+enters: {{ state.enter }}
+updates: {{ state.update }}
+leaves: {{ state.leave }}
+ </pre>
+
+ <pre id="logs">{{ logs.join('\\n') }}</pre>
+
+ <button id="resetLogs" @click="logs = []">Reset Logs</button>
+
+ <ul>
+ <li><router-link to="/">/</router-link></li>
+ <li><router-link to="/foo">/foo</router-link></li>
+ <li><router-link to="/foo-async">/foo-async</router-link></li>
+ <li><router-link id="update-query" :to="{ query: { n: (Number($route.query.n) || 0) + 1 }}" v-slot="{ route }">{{ route.fullPath }}</router-link></li>
+ </ul>
+
+ <router-view v-slot="{ Component }" >
+ <Suspense>
+ <component :is="Component" class="view" />
+ </Suspense>
+ </router-view>
+ </div>
+ `,
+ setup() {
+ return { state, logs }
+ },
+})
+
+app.use(router)
+
+// @ts-ignore
+window.r = router
+
+app.mount('#app')
// 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
+ }
}
}