]> git.ipfire.org Git - thirdparty/vuejs/router.git/commitdiff
feat: add per component beforeRouteEnter
authorEduardo San Martin Morote <posva13@gmail.com>
Wed, 1 May 2019 13:23:59 +0000 (15:23 +0200)
committerEduardo San Martin Morote <posva13@gmail.com>
Wed, 1 May 2019 13:23:59 +0000 (15:23 +0200)
__tests__/per-component-before-guards.spec.js [new file with mode: 0644]
__tests__/per-route-before-guards.spec.js [moved from __tests__/per-router-before-guards.spec.js with 60% similarity]
explorations/html5.ts
src/router.ts
src/types/index.ts

diff --git a/__tests__/per-component-before-guards.spec.js b/__tests__/per-component-before-guards.spec.js
new file mode 100644 (file)
index 0000000..2c215cd
--- /dev/null
@@ -0,0 +1,83 @@
+// @ts-check
+require('./helper')
+const expect = require('expect')
+const { HTML5History } = require('../src/history/html5')
+const { Router } = require('../src/router')
+const { JSDOM } = require('jsdom')
+const fakePromise = require('faked-promise')
+
+const tick = () => new Promise(resolve => process.nextTick(resolve))
+
+/**
+ * @param {Partial<import('../src/router').RouterOptions> & { routes: import('../src/types').RouteRecord[]}} options
+ */
+function createRouter(options) {
+  return new Router({
+    history: new HTML5History(),
+    ...options,
+  })
+}
+
+const Home = { template: `<div>Home</div>` }
+const Foo = { template: `<div>Foo</div>` }
+
+const beforeRouteEnter = jest.fn()
+/** @type {import('../src/types').RouteRecord[]} */
+const routes = [
+  { path: '/', component: Home },
+  { path: '/foo', component: Foo },
+  {
+    path: '/guard/:n',
+    component: {
+      ...Foo,
+      beforeRouteEnter,
+    },
+  },
+]
+
+beforeEach(() => {
+  beforeRouteEnter.mockReset()
+})
+
+describe('navigation guards', () => {
+  beforeAll(() => {
+    // TODO: move to utils for tests that need DOM
+    const dom = new JSDOM(
+      `<!DOCTYPE html><html><head></head><body></body></html>`,
+      {
+        url: 'https://example.org/',
+        referrer: 'https://example.com/',
+        contentType: 'text/html',
+      }
+    )
+
+    // @ts-ignore
+    global.window = dom.window
+  })
+
+  it('calls beforeRouteEnter guards on push', async () => {
+    const router = createRouter({ routes })
+    beforeRouteEnter.mockImplementationOnce((to, from, next) => {
+      if (to.params.n !== 'valid') return next(false)
+      next()
+    })
+    await router.push('/guard/valid')
+    expect(beforeRouteEnter).toHaveBeenCalledTimes(1)
+  })
+
+  it.skip('calls beforeEnter guards on replace', () => {})
+
+  it.skip('waits before navigating', async () => {
+    const [promise, resolve] = fakePromise()
+    const router = createRouter({ routes })
+    beforeRouteEnter.mockImplementationOnce(async (to, from, next) => {
+      await promise
+      next()
+    })
+    const p = router.push('/foo')
+    expect(router.currentRoute.fullPath).toBe('/')
+    resolve()
+    await p
+    expect(router.currentRoute.fullPath).toBe('/foo')
+  })
+})
similarity index 60%
rename from __tests__/per-router-before-guards.spec.js
rename to __tests__/per-route-before-guards.spec.js
index 3647791409b3e19940ea4079cd4f4da37404b1bb..1422c7088300389b970cd3dc608b18b66fe63e0e 100644 (file)
@@ -21,10 +21,16 @@ function createRouter(options) {
 const Home = { template: `<div>Home</div>` }
 const Foo = { template: `<div>Foo</div>` }
 
+const beforeEnter = jest.fn()
 /** @type {import('../src/types').RouteRecord[]} */
 const routes = [
   { path: '/', component: Home },
   { path: '/foo', component: Foo },
+  {
+    path: '/guard/:n',
+    component: Foo,
+    beforeEnter,
+  },
 ]
 
 describe('navigation guards', () => {
@@ -44,23 +50,13 @@ describe('navigation guards', () => {
   })
 
   it('calls beforeEnter guards on push', async () => {
-    const spy = jest.fn()
-    const router = createRouter({
-      routes: [
-        ...routes,
-        {
-          path: '/guard/:n',
-          component: Foo,
-          beforeEnter: spy,
-        },
-      ],
-    })
-    spy.mockImplementationOnce((to, from, next) => {
+    const router = createRouter({ routes })
+    beforeEnter.mockImplementationOnce((to, from, next) => {
       if (to.params.n !== 'valid') return next(false)
       next()
     })
     await router.push('/guard/valid')
-    expect(spy).toHaveBeenCalledTimes(1)
+    expect(beforeEnter).toHaveBeenCalledTimes(1)
   })
 
   it.skip('calls beforeEnter guards on replace', () => {})
@@ -68,7 +64,7 @@ describe('navigation guards', () => {
   it('waits before navigating', async () => {
     const [promise, resolve] = fakePromise()
     const router = createRouter({ routes })
-    router.beforeEach(async (to, from, next) => {
+    beforeEnter.mockImplementationOnce(async (to, from, next) => {
       await promise
       next()
     })
@@ -78,34 +74,4 @@ describe('navigation guards', () => {
     await p
     expect(router.currentRoute.fullPath).toBe('/foo')
   })
-
-  it('waits in the right order', async () => {
-    const [p1, r1] = fakePromise()
-    const [p2, r2] = fakePromise()
-    const router = createRouter({ routes })
-    const guard1 = jest.fn(async (to, from, next) => {
-      await p1
-      next()
-    })
-    router.beforeEach(guard1)
-    const guard2 = jest.fn(async (to, from, next) => {
-      await p2
-      next()
-    })
-    router.beforeEach(guard2)
-    let navigation = router.push('/foo')
-    expect(router.currentRoute.fullPath).toBe('/')
-    expect(guard1).toHaveBeenCalled()
-    expect(guard2).not.toHaveBeenCalled()
-    r1()
-    // wait until the guard is called
-    await tick()
-    await tick()
-    expect(guard2).toHaveBeenCalled()
-    r2()
-    expect(router.currentRoute.fullPath).toBe('/')
-    await navigation
-    expect(guard2).toHaveBeenCalled()
-    expect(router.currentRoute.fullPath).toBe('/foo')
-  })
 })
index 132c3c7262c16d8e858fd4c448bbd32f45f26296..0028a17d3fbc1bf0206b4e3160592f03e48eac0a 100644 (file)
@@ -1,6 +1,8 @@
 import { Router, HTML5History } from '../src'
 
-const component = null
+const component = {
+  template: `<div>A component</div>`,
+}
 
 const r = new Router({
   history: new HTML5History(),
index cf560e65b04d970d9983015024c3d47ddfc19fdd..466cb3c023bdc33dc98b9555328866ea3894a4fb 100644 (file)
@@ -103,7 +103,24 @@ export class Router {
         guards.push(guardToPromiseFn(record.beforeEnter, to, from))
     }
 
-    // run the queue of guards
+    // run the queue of per route beforeEnter guards
+    for (const guard of guards) {
+      await guard()
+    }
+
+    // check in-component beforeRouteEnter
+    guards = []
+    await Promise.all(
+      to.matched.map(async ({ component }) => {
+        // TODO: handle async routes
+        if (component.beforeRouteEnter) {
+          // TODO: handle the next callback
+          guards.push(guardToPromiseFn(component.beforeRouteEnter, to, from))
+        }
+      })
+    )
+
+    // run the queue of per route beforeEnter guards
     for (const guard of guards) {
       await guard()
     }
index 52cf760eb7d16543e058066c58f1b3a3e63e9e9c..d8c326fabdc4ffe2b63e459866f365a76e7c735e 100644 (file)
@@ -52,13 +52,22 @@ export interface RouteLocationNormalized
 //   params: ReturnType<PT>
 // }
 
+export interface RouteComponentInterface {
+  beforeRouteEnter?: NavigationGuard
+}
+// TODO: have a real type with augmented properties
+// export type RouteComponent = TODO & RouteComponentInterface
+export type RouteComponent = {
+  template?: string
+} & RouteComponentInterface
+
 // NOTE not sure the whole PropsTransformer thing can be usefull
 // since in callbacks we don't know where we are coming from
 // and I don't thin it's possible to filter out the route
 // by any means
 export interface RouteRecord {
   path: string // | RegExp
-  component: TODO
+  component: RouteComponent
   name?: string
   beforeEnter?: NavigationGuard
   // props: PT