{
components: { default: components.Home },
instances: {},
- enterCallbacks: [],
+ enterCallbacks: {},
path: '/',
props,
},
{
components: { default: components.Foo },
instances: {},
- enterCallbacks: [],
+ enterCallbacks: {},
path: '/foo',
props,
},
{
components: { default: components.Nested },
instances: {},
- enterCallbacks: [],
+ enterCallbacks: {},
path: '/',
props,
},
{
components: { default: components.Foo },
instances: {},
- enterCallbacks: [],
+ enterCallbacks: {},
path: 'a',
props,
},
{
components: { default: components.Nested },
instances: {},
- enterCallbacks: [],
+ enterCallbacks: {},
path: '/',
props,
},
{
components: { default: components.Nested },
instances: {},
- enterCallbacks: [],
+ enterCallbacks: {},
path: 'a',
props,
},
{
components: { default: components.Foo },
instances: {},
- enterCallbacks: [],
+ enterCallbacks: {},
path: 'b',
props,
},
{
components: { foo: components.Foo },
instances: {},
- enterCallbacks: [],
+ enterCallbacks: {},
path: '/',
props,
},
components: { default: components.User },
instances: {},
- enterCallbacks: [],
+ enterCallbacks: {},
path: '/users/:id',
props: { default: true },
},
components: { default: components.WithProps },
instances: {},
- enterCallbacks: [],
+ enterCallbacks: {},
path: '/props/:id',
props: { default: { id: 'foo', other: 'fixed' } },
},
components: { default: components.WithProps },
instances: {},
- enterCallbacks: [],
+ enterCallbacks: {},
path: '/props/:id',
props: {
default: (to: RouteLocationNormalized) => ({
{
components: { default: components.User },
instances: {},
- enterCallbacks: [],
+ enterCallbacks: {},
path: '/users/:id',
props,
},
--- /dev/null
+/**
+ * @jest-environment jsdom
+ */
+import { defineComponent, h } from 'vue'
+import { mount } from '../mount'
+import {
+ createRouter,
+ RouterView,
+ createMemoryHistory,
+ RouterOptions,
+} from '../../src'
+
+const nextCallbacks = {
+ Default: jest.fn(),
+ Other: jest.fn(),
+}
+const Default = defineComponent({
+ beforeRouteEnter(to, from, next) {
+ next(nextCallbacks.Default)
+ },
+ name: 'Default',
+ setup() {
+ return () => h('div', 'Default content')
+ },
+})
+
+const Other = defineComponent({
+ beforeRouteEnter(to, from, next) {
+ next(nextCallbacks.Other)
+ },
+ name: 'Other',
+ setup() {
+ return () => h('div', 'Other content')
+ },
+})
+
+const Third = defineComponent({
+ name: 'Third',
+ setup() {
+ return () => h('div', 'Third content')
+ },
+})
+
+beforeEach(() => {
+ for (const key in nextCallbacks) {
+ nextCallbacks[key as keyof typeof nextCallbacks].mockClear()
+ }
+})
+
+describe('beforeRouteEnter next callback', () => {
+ async function factory(options: Partial<RouterOptions>) {
+ const history = createMemoryHistory()
+ const router = createRouter({
+ history,
+ routes: [],
+ ...options,
+ })
+
+ const wrapper = await mount(
+ {
+ template: `
+ <div>
+ <router-view/>
+ <router-view name="other"/>
+ </div>
+ `,
+ components: { RouterView },
+ },
+ { router }
+ )
+
+ return { wrapper, router }
+ }
+
+ it('calls each beforeRouteEnter callback once', async () => {
+ const { router } = await factory({
+ routes: [
+ {
+ path: '/:p(.*)',
+ components: {
+ default: Default,
+ other: Other,
+ third: Third,
+ },
+ },
+ ],
+ })
+
+ await router.isReady()
+
+ expect(nextCallbacks.Default).toHaveBeenCalledTimes(1)
+ expect(nextCallbacks.Other).toHaveBeenCalledTimes(1)
+ })
+})
import * as runtimeDom from '@vue/runtime-dom'
import { RouteLocationNormalizedLoose } from './utils'
import { routeLocationKey } from '../src/injectionSymbols'
+import { Router } from '../src'
export interface MountOptions {
propsData: Record<string, any>
provide: Record<string | symbol, any>
components: ComponentOptions['components']
slots: Record<string, string>
+ router?: Router
}
interface Wrapper {
return rootEl.querySelector(selector)
}
+ if (options.router) app.use(options.router)
+
app.mount(rootEl)
activeWrapperRemovers.push(() => {
> {
leaveGuards?: any
instances: Record<string, any>
- enterCallbacks: Function[]
+ enterCallbacks: Record<string, Function[]>
props: Record<string, _RouteRecordProps>
aliasOf: RouteRecordViewLoose | undefined
}
const currentName = props.name
const onVnodeMounted = () => {
matchedRoute.instances[currentName] = viewRef.value
- matchedRoute.enterCallbacks.forEach(callback =>
+ ;(matchedRoute.enterCallbacks[currentName] || []).forEach(callback =>
callback(viewRef.value!)
)
}
instances: {},
leaveGuards: [],
updateGuards: [],
- enterCallbacks: [],
+ enterCallbacks: {},
components:
'components' in record
? record.components || {}
beforeEnter: RouteRecordMultipleViews['beforeEnter']
leaveGuards: NavigationGuard[]
updateGuards: NavigationGuard[]
- enterCallbacks: NavigationGuardNextCallback[]
+ enterCallbacks: Record<string, NavigationGuardNextCallback[]>
// having the instances on the record mean beforeRouteUpdate and
// beforeRouteLeave guards can only be invoked with the latest mounted app
// instance if there are multiple application instances rendering the same
NavigationFailure,
NavigationRedirectError,
} from './errors'
-import { ComponentPublicInstance, ComponentOptions } from 'vue'
+import { ComponentOptions } from 'vue'
import { inject, getCurrentInstance, warn } from 'vue'
import { matchedRouteKey } from './injectionSymbols'
import { RouteRecordNormalized } from './matcher/types'
)
}
+export function guardToPromiseFn(
+ guard: NavigationGuard,
+ to: RouteLocationNormalized,
+ from: RouteLocationNormalizedLoaded
+): () => Promise<void>
export function guardToPromiseFn(
guard: NavigationGuard,
to: RouteLocationNormalized,
from: RouteLocationNormalizedLoaded,
- instance?: ComponentPublicInstance | undefined | null,
- record?: RouteRecordNormalized
+ record: RouteRecordNormalized,
+ name: string
+): () => Promise<void>
+export function guardToPromiseFn(
+ guard: NavigationGuard,
+ to: RouteLocationNormalized,
+ from: RouteLocationNormalizedLoaded,
+ record?: RouteRecordNormalized,
+ name?: string
): () => Promise<void> {
// keep a reference to the enterCallbackArray to prevent pushing callbacks if a new navigation took place
- const enterCallbackArray = record && record.enterCallbacks
+ const enterCallbackArray =
+ record &&
+ // name is defined if record is because of the function overload
+ (record.enterCallbacks[name!] = record.enterCallbacks[name!] || [])
+
return () =>
new Promise((resolve, reject) => {
const next: NavigationGuardNext = (
)
} else {
if (
- record &&
- record.enterCallbacks === enterCallbackArray &&
+ enterCallbackArray &&
+ // since enterCallbackArray is truthy, both record and name also are
+ record!.enterCallbacks[name!] === enterCallbackArray &&
typeof valid === 'function'
)
enterCallbackArray.push(valid)
// wrapping with Promise.resolve allows it to work with both async and sync guards
Promise.resolve(
guard.call(
- instance,
+ record && record.instances[name!],
to,
from,
__DEV__ ? canOnlyBeCalledOnce(next, to, from) : next
let options: ComponentOptions =
(rawComponent as any).__vccOpts || rawComponent
const guard = options[guardType]
- guard &&
- guards.push(
- guardToPromiseFn(guard, to, from, record.instances[name], record)
- )
+ guard && guards.push(guardToPromiseFn(guard, to, from, record, name))
} else {
// start requesting the chunk already
let componentPromise: Promise<RouteComponent | null> = (rawComponent as Lazy<
record.components[name] = resolvedComponent
// @ts-ignore: the options types are not propagated to Component
const guard: NavigationGuard = resolvedComponent[guardType]
- return (
- guard &&
- guardToPromiseFn(
- guard,
- to,
- from,
- record.instances[name],
- record
- )()
- )
+ return guard && guardToPromiseFn(guard, to, from, record, name)()
})
)
}
// NOTE: at this point to.matched is normalized and does not contain any () => Promise<Component>
// clear existing enterCallbacks, these are added by extractComponentsGuards
- to.matched.forEach(record => (record.enterCallbacks = []))
+ to.matched.forEach(record => (record.enterCallbacks = {}))
// check in-component beforeRouteEnter
guards = extractComponentsGuards(
record.leaveGuards = []
// free the references
record.instances = {}
+ record.enterCallbacks = {}
}
// only consider as push if it's not the first navigation