diff --git a/__tests__/RouterView.spec.ts b/__tests__/RouterView.spec.ts index b88b57c06..17dd39f77 100644 --- a/__tests__/RouterView.spec.ts +++ b/__tests__/RouterView.spec.ts @@ -6,8 +6,16 @@ import { components, RouteLocationNormalizedLoose } from './utils' import { START_LOCATION_NORMALIZED } from '../src/types' import { ref, markNonReactive } from 'vue' import { mount, tick } from './mount' +import { mockWarn } from 'jest-mock-warn' -const routes: Record = { +// to have autocompletion +function createRoutes>( + routes: T +): T { + return routes +} + +const routes = createRoutes({ root: { fullPath: '/', name: undefined, @@ -65,12 +73,30 @@ const routes: Record = { meta: {}, matched: [{ components: { foo: components.Foo }, path: '/' }], }, -} + withParams: { + fullPath: '/users/3', + name: undefined, + path: '/users/3', + query: {}, + params: { id: '1' }, + hash: '', + meta: {}, + matched: [ + { + components: { default: components.User }, + path: '/users/:id', + props: true, + }, + ], + }, +}) describe('RouterView', () => { + mockWarn() + function factory(route: RouteLocationNormalizedLoose, props: any = {}) { const router = { - currentRoute: ref(markNonReactive(route)), + currentRoute: ref(markNonReactive({ ...route })), } const { app, el } = mount( @@ -104,6 +130,7 @@ describe('RouterView', () => { it('displays nothing when route is unmatched', () => { const { el } = factory(START_LOCATION_NORMALIZED as any) // NOTE: I wonder if this will stay stable in future releases + expect('Router').not.toHaveBeenWarned() expect(el.childElementCount).toBe(0) }) @@ -126,4 +153,24 @@ describe('RouterView', () => { await tick() expect(el.innerHTML).toBe(`
Foo
`) }) + + it('does not pass params as props by default', async () => { + let noPropsWithParams = { + ...routes.withParams, + matched: [{ ...routes.withParams.matched[0], props: false }], + } + const { el, router } = factory(noPropsWithParams) + expect(el.innerHTML).toBe(`
User: default
`) + router.currentRoute.value = { ...noPropsWithParams, params: { id: '4' } } + await tick() + expect(el.innerHTML).toBe(`
User: default
`) + }) + + it('passes params as props with props: true', async () => { + const { el, router } = factory(routes.withParams) + expect(el.innerHTML).toBe(`
User: 1
`) + router.currentRoute.value = { ...routes.withParams, params: { id: '4' } } + await tick() + expect(el.innerHTML).toBe(`
User: 4
`) + }) }) diff --git a/__tests__/utils.ts b/__tests__/utils.ts index 1b88eeb52..0a0b718c1 100644 --- a/__tests__/utils.ts +++ b/__tests__/utils.ts @@ -4,8 +4,9 @@ import { RouteRecordMultipleViews, MatcherLocationNormalized, RouteLocationNormalized, + RouteRecordCommon, } from '../src/types' -import { h, resolveComponent } from 'vue' +import { h, resolveComponent, defineComponent } from 'vue' export const tick = (time?: number) => new Promise(resolve => { @@ -22,6 +23,7 @@ export interface RouteRecordViewLoose 'path' | 'name' | 'components' | 'children' | 'meta' | 'beforeEnter' > { leaveGuards?: any + props?: RouteRecordCommon['props'] aliasOf: RouteRecordViewLoose | undefined } @@ -83,6 +85,16 @@ export const components = { Home: { render: () => h('div', {}, 'Home') }, Foo: { render: () => h('div', {}, 'Foo') }, Bar: { render: () => h('div', {}, 'Bar') }, + User: defineComponent({ + props: { + id: { + default: 'default', + }, + }, + render() { + return h('div', {}, 'User: ' + this.id) + }, + }), Nested: { render: () => { const RouterView = resolveComponent('RouterView') diff --git a/src/components/View.ts b/src/components/View.ts index 564f488be..2016759c1 100644 --- a/src/components/View.ts +++ b/src/components/View.ts @@ -36,11 +36,17 @@ export const View = defineComponent({ () => matchedRoute.value && matchedRoute.value.components[props.name] ) + const propsData = computed(() => { + if (!matchedRoute.value.props) return {} + + return route.value.params + }) + provide(matchedRouteKey, matchedRoute) return () => { return ViewComponent.value - ? h(ViewComponent.value as any, { ...attrs }) + ? h(ViewComponent.value as any, { ...propsData.value, ...attrs }) : null } }, diff --git a/src/matcher/index.ts b/src/matcher/index.ts index 1246aa926..4d919ca07 100644 --- a/src/matcher/index.ts +++ b/src/matcher/index.ts @@ -269,6 +269,7 @@ export function normalizeRouteRecord( children: (record as any).children || [], name: record.name, beforeEnter, + props: record.props || false, meta: record.meta || {}, leaveGuards: [], aliasOf: undefined, diff --git a/src/matcher/types.ts b/src/matcher/types.ts index d29c8423e..34153c368 100644 --- a/src/matcher/types.ts +++ b/src/matcher/types.ts @@ -1,4 +1,8 @@ -import { RouteRecordMultipleViews, NavigationGuard } from '../types' +import { + RouteRecordMultipleViews, + NavigationGuard, + RouteRecordCommon, +} from '../types' // normalize component/components into components and make every property always present export interface RouteRecordNormalized { @@ -7,6 +11,7 @@ export interface RouteRecordNormalized { components: RouteRecordMultipleViews['components'] children: Exclude meta: Exclude + props: Exclude beforeEnter: RouteRecordMultipleViews['beforeEnter'] leaveGuards: NavigationGuard[] aliasOf: RouteRecordNormalized | undefined diff --git a/src/types/index.ts b/src/types/index.ts index 4c46b8881..d9238df65 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -118,6 +118,7 @@ export interface RouteRecordCommon { path: string alias?: string | string[] name?: string + props?: boolean // TODO: beforeEnter has no effect with redirect, move and test beforeEnter?: NavigationGuard | NavigationGuard[] meta?: Record @@ -166,9 +167,10 @@ export const START_LOCATION_NORMALIZED: RouteLocationNormalized = markNonReactiv ) // make matched non enumerable for easy printing -Object.defineProperty(START_LOCATION_NORMALIZED, 'matched', { - enumerable: false, -}) +// NOTE: commented for tests at RouterView.spec +// Object.defineProperty(START_LOCATION_NORMALIZED, 'matched', { +// enumerable: false, +// }) // Matcher types // the matcher doesn't care about query and hash