RouteLocationNormalized,
} from '../src/types'
import { createMemoryHistory } from '../src'
-import { mount } from './mount'
+import { mount, tick } from './mount'
import { ref, markNonReactive } from 'vue'
-import { tick } from './utils'
const locations: Record<
string,
router.resolve.mockReturnValueOnce(resolvedLocation)
const { app, el } = mount(router as any, {
template: `<RouterLink :to="to">a link</RouterLink>`,
- components: { RouterLink },
+ components: { RouterLink } as any,
setup() {
const to = ref(propsData.to)
RouteLocationNormalized,
} from '../src/types'
import { ref, markNonReactive } from 'vue'
-import { mount } from './mount'
+import { mount, tick } from './mount'
const routes: Record<string, RouteLocationNormalized> = {
root: {
meta: {},
matched: [{ components: { default: components.Home }, path: '/' }],
},
+ foo: {
+ fullPath: '/foo',
+ name: undefined,
+ path: '/foo',
+ query: {},
+ params: {},
+ hash: '',
+ meta: {},
+ matched: [{ components: { default: components.Foo }, path: '/foo' }],
+ },
nested: {
fullPath: '/a',
name: undefined,
it('displays nothing when route is unmatched', () => {
const { el } = factory(START_LOCATION_NORMALIZED)
// NOTE: I wonder if this will stay stable in future releases
- expect(el.innerHTML).toBe(`<!--fragment-0-start--><!--fragment-0-end-->`)
+ expect(el.childElementCount).toBe(0)
})
- // TODO: nested routes
- it.skip('displays nested views', () => {
+ it('displays nested views', () => {
const { el } = factory(routes.nested)
expect(el.innerHTML).toBe(`<div><h2>Nested</h2><div>Foo</div></div>`)
})
- it.skip('displays deeply nested views', () => {
+ it('displays deeply nested views', () => {
const { el } = factory(routes.nestedNested)
expect(el.innerHTML).toBe(
`<div><h2>Nested</h2><div><h2>Nested</h2><div>Foo</div></div></div>`
)
})
+
+ it('renders when the location changes', async () => {
+ const { el, router } = factory(routes.root)
+ expect(el.innerHTML).toBe(`<div>Home</div>`)
+ router.currentRoute.value = routes.foo
+ await tick()
+ expect(el.innerHTML).toBe(`<div>Foo</div>`)
+ })
})
-import { Component, createApp } from 'vue'
+import { Component, createApp, nextTick } from 'vue'
import * as runtimeDom from '@vue/runtime-dom'
import { compile } from '@vue/compiler-dom'
import { Router } from '../src'
router: Router,
Component: Component & {
template: string
+ components?: Record<string, Component>
},
rootProps = {}
) {
const app = createApp()
app.provide('router', router)
+ const { template, components, ...ComponentWithoutTemplate } = Component
+
+ // @ts-ignore
+ ComponentWithoutTemplate.components = {}
+ for (const componentName in components) {
+ app.component(componentName, components[componentName])
+ }
+
const rootEl = document.createElement('div')
document.body.appendChild(rootEl)
- const { template, ...ComponentWithoutTemplate } = Component
const codegen = compile(template, {
mode: 'function',
hoistStatic: true,
return { app, el: rootEl }
}
+
+export const tick = () =>
+ new Promise(resolve => {
+ nextTick(resolve)
+ })
import { JSDOM, ConstructorOptions } from 'jsdom'
import { NavigationGuard, RouteRecord, MatchedRouteRecord } from '../src/types'
-import { h } from '@vue/runtime-core'
+import { h, resolveComponent } from '@vue/runtime-core'
export const tick = (time?: number) =>
new Promise(resolve => {
Foo: { render: () => h('div', {}, 'Foo') },
Bar: { render: () => h('div', {}, 'Bar') },
Nested: {
- render: () => h('div', {}, [h('h2', {}, 'Nested'), h('RouterView')]),
+ render: () => {
+ const RouterView = resolveComponent('RouterView')
+ return h('div', {}, [
+ h('h2', {}, 'Nested'),
+ RouterView ? h(RouterView as any) : [],
+ ])
+ },
},
}
-import { h, FunctionalComponent, inject } from '@vue/runtime-core'
-import { Router } from '../'
-
-interface Props {
- name: string
-}
-
-const View: FunctionalComponent<Props> = (props, { slots, attrs }) => {
- const router = inject<Router>('router')!
-
- const route = router.currentRoute.value
-
- let depth = 0
-
- // TODO: support nested router-views
- const matched = route.matched[depth]
-
- // render empty node if no matched route
- if (!matched) return []
-
- const component = matched.components[props.name || 'default']
-
- // TODO: remove any
- // const children = typeof slots.default === 'function' ? slots.default() : []
- return h(component as any, attrs, slots.default)
-}
-
-// View.props =
-// View.name = 'RouterView'
+import {
+ h,
+ inject,
+ provide,
+ defineComponent,
+ PropType,
+ computed,
+ Component,
+} from '@vue/runtime-core'
+
+const View = defineComponent({
+ name: 'RouterView',
+ props: {
+ name: {
+ type: String as PropType<string>,
+ default: 'default',
+ },
+ },
+
+ setup(props, { attrs }) {
+ const router = inject('router')
+ const depth: number = inject('routerViewDepth', 0)
+ provide('routerViewDepth', depth + 1)
+
+ const ViewComponent = computed<Component | void>(() => {
+ const matched = router.currentRoute.value.matched[depth]
+
+ if (!matched) return null
+
+ return matched.components[props.name]
+ })
+
+ return () =>
+ ViewComponent.value ? h(ViewComponent.value as any, attrs) : []
+ },
+})
export default View
declare module '@vue/runtime-core' {
function inject(name: 'router'): Router
function inject(name: 'route'): RouteLocationNormalized
+ function inject(name: 'routerViewDepth'): number
}
// @ts-ignore: we are not importing it so it complains