]> git.ipfire.org Git - thirdparty/vuejs/router.git/commitdiff
feat: basic beforeRouteUpdate
authorEduardo San Martin Morote <posva13@gmail.com>
Thu, 2 May 2019 13:58:59 +0000 (15:58 +0200)
committerEduardo San Martin Morote <posva13@gmail.com>
Thu, 2 May 2019 13:58:59 +0000 (15:58 +0200)
__tests__/guards/component-beforeRouteUpdate.spec.js [new file with mode: 0644]
src/router.ts
src/types/index.ts

diff --git a/__tests__/guards/component-beforeRouteUpdate.spec.js b/__tests__/guards/component-beforeRouteUpdate.spec.js
new file mode 100644 (file)
index 0000000..c3c15e3
--- /dev/null
@@ -0,0 +1,93 @@
+// @ts-check
+require('../helper')
+const expect = require('expect')
+const { HTML5History } = require('../../src/history/html5')
+const { Router } = require('../../src/router')
+const fakePromise = require('faked-promise')
+const { NAVIGATION_TYPES, createDom } = require('../utils')
+
+/**
+ * @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 beforeRouteUpdate = jest.fn()
+/** @type {import('../../src/types').RouteRecord[]} */
+const routes = [
+  { path: '/', component: Home },
+  { path: '/foo', component: Foo },
+  {
+    path: '/guard/:go',
+    component: {
+      ...Foo,
+      beforeRouteUpdate,
+    },
+  },
+]
+
+beforeEach(() => {
+  beforeRouteUpdate.mockReset()
+})
+
+describe('beforeRouteUpdate', () => {
+  beforeAll(() => {
+    createDom()
+  })
+
+  NAVIGATION_TYPES.forEach(navigationMethod => {
+    describe(navigationMethod, () => {
+      it('calls beforeRouteUpdate guards when changing params', async () => {
+        const router = createRouter({ routes })
+        beforeRouteUpdate.mockImplementationOnce((to, from, next) => {
+          next()
+        })
+        await router[navigationMethod]('/guard/valid')
+        // not called on initial navigation
+        expect(beforeRouteUpdate).not.toHaveBeenCalled()
+        await router[navigationMethod]('/guard/other')
+        expect(beforeRouteUpdate).toHaveBeenCalledTimes(1)
+      })
+
+      it('resolves async components before guarding', async () => {
+        const spy = jest.fn((to, from, next) => next())
+        const component = {
+          template: `<div></div>`,
+          beforeRouteUpdate: spy,
+        }
+        const router = createRouter({
+          routes: [
+            ...routes,
+            { path: '/async/:a', component: () => Promise.resolve(component) },
+          ],
+        })
+        await router[navigationMethod]('/async/a')
+        expect(spy).not.toHaveBeenCalled()
+        await router[navigationMethod]('/async/b')
+        expect(spy).toHaveBeenCalledTimes(1)
+      })
+
+      it('waits before navigating', async () => {
+        const [promise, resolve] = fakePromise()
+        const router = createRouter({ routes })
+        beforeRouteUpdate.mockImplementationOnce(async (to, from, next) => {
+          await promise
+          next()
+        })
+        await router[navigationMethod]('/guard/one')
+        const p = router[navigationMethod]('/guard/foo')
+        expect(router.currentRoute.fullPath).toBe('/guard/one')
+        resolve()
+        await p
+        expect(router.currentRoute.fullPath).toBe('/guard/foo')
+      })
+    })
+  })
+})
index 0c4922ef544c1bfc6bca67fa47b76844a00edb8a..69043c2e2f6ae775ea6b7854ab2176976040b411 100644 (file)
@@ -141,6 +141,37 @@ export class Router {
       }
     }
 
+    // check in components beforeRouteUpdate
+    guards = []
+    await Promise.all(
+      to.matched.map(async record => {
+        // TODO: cache async routes per record
+        // TODO: handle components version. Probably repfactor in extractComponentGuards
+        if ('component' in record) {
+          const { component } = record
+          const resolvedComponent = await (typeof component === 'function'
+            ? component()
+            : component)
+          // trigger on reused views
+          // TODO: should we avoid triggering if no param changed on this route?
+          if (
+            resolvedComponent.beforeRouteUpdate &&
+            from.matched.indexOf(record) > -1
+          ) {
+            // TODO: handle the next callback
+            guards.push(
+              guardToPromiseFn(resolvedComponent.beforeRouteUpdate, to, from)
+            )
+          }
+        }
+      })
+    )
+
+    // run the queue of per route beforeEnter guards
+    for (const guard of guards) {
+      await guard()
+    }
+
     // check the route beforeEnter
     // TODO: check children. Should we also check reused routes guards
     guards = []
@@ -167,9 +198,10 @@ export class Router {
           const resolvedComponent = await (typeof component === 'function'
             ? component()
             : component)
+          // do not trigger beforeRouteEnter on reused views
           if (
             resolvedComponent.beforeRouteEnter &&
-            from.matched.indexOf(record)
+            from.matched.indexOf(record) < 0
           ) {
             // TODO: handle the next callback
             guards.push(
index c170af8fcd26b55a7b6f03db53379eaa264b1f9a..cff6a64a349051d6fbf42389231d3e0cbf341820 100644 (file)
@@ -73,9 +73,13 @@ export interface RouteComponentInterface {
    * Guard called whenever the route that renders this component has changed but
    * it is reused for the new route. This allows you to guard for changes in params,
    * the query or the hash.
+   * @param to RouteLocation we are navigating to
+   * @param from RouteLocation we are navigating from
+   * @param next function to validate, cancel or modify (by redirectering) the navigation
    */
   beforeRouteUpdate?: NavigationGuard<void>
 }
+
 // TODO: have a real type with augmented properties
 // export type RouteComponent = TODO & RouteComponentInterface
 export type RouteComponent = {