]> git.ipfire.org Git - thirdparty/vuejs/router.git/commitdiff
feat(view): handle nested views
authorEduardo San Martin Morote <posva13@gmail.com>
Fri, 24 Jan 2020 15:20:21 +0000 (16:20 +0100)
committerEduardo San Martin Morote <posva13@gmail.com>
Fri, 24 Jan 2020 15:20:21 +0000 (16:20 +0100)
__tests__/RouterLink.spec.ts
__tests__/RouterView.spec.ts
__tests__/mount.ts
__tests__/utils.ts
src/components/View.ts
src/index.ts

index 2054a232809fd113c1fdda8a333efb25874e627e..5d544c6ba293338ae24c3fafd522a2fded691df3 100644 (file)
@@ -9,9 +9,8 @@ import {
   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,
@@ -71,7 +70,7 @@ describe('RouterLink', () => {
     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)
 
index 5ef34ad2550b65dd777396d6357fccdf0353334a..001af68faaa0083825240a871483481da31353e9 100644 (file)
@@ -8,7 +8,7 @@ import {
   RouteLocationNormalized,
 } from '../src/types'
 import { ref, markNonReactive } from 'vue'
-import { mount } from './mount'
+import { mount, tick } from './mount'
 
 const routes: Record<string, RouteLocationNormalized> = {
   root: {
@@ -21,6 +21,16 @@ const routes: Record<string, RouteLocationNormalized> = {
     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,
@@ -97,19 +107,26 @@ describe('RouterView', () => {
   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>`)
+  })
 })
index ca296bfb5c5632c892068d85e00432941d9e12d2..3034a71140ae0102ba3bbd794128a9d686b97b39 100644 (file)
@@ -1,4 +1,4 @@
-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'
@@ -7,16 +7,24 @@ export function mount(
   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,
@@ -32,3 +40,8 @@ export function mount(
 
   return { app, el: rootEl }
 }
+
+export const tick = () =>
+  new Promise(resolve => {
+    nextTick(resolve)
+  })
index 9d7d50e087a0a09725e5560b65bb7bc3ffadd82f..37668e55fbce48ef6f22d2bb6e572e615bd01bc3 100644 (file)
@@ -1,6 +1,6 @@
 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 => {
@@ -49,7 +49,13 @@ export const components = {
   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) : [],
+      ])
+    },
   },
 }
 
index c25207d3b9c1699986bd9bda444ae96da06ed720..00096c733e439ed995112ab87796cfbadc6c5829 100644 (file)
@@ -1,31 +1,38 @@
-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
index 943a7a9d1fda1c09e0c5bafa4eab175be129b484..e0968148c3531cf224b15eee32528d129ac8c0d8 100644 (file)
@@ -13,6 +13,7 @@ import {
 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