diff --git a/src/mobx/react/Route.js b/src/mobx/react/Route.js
index dd32e4f..fad4eab 100644
--- a/src/mobx/react/Route.js
+++ b/src/mobx/react/Route.js
@@ -4,11 +4,15 @@ import {inject, observer} from 'mobx-react'
function Route (props) {
const {router, path, Component, ...otherProps} = props
- return router.route.startsWith(path) ? : null
+ if (!router.route.startsWith(path)) {
+ return null
+ }
+ if (router.vmPlugin && router.vmPlugin.vmTree[path]) {
+ otherProps.vm = router.vmPlugin.vmTree[path]
+ }
+ return
}
-export default inject('router')(observer(Route))
-
Route.propTypes = {
Component: PropTypes.func.isRequired,
router: PropTypes.shape({
@@ -17,3 +21,5 @@ Route.propTypes = {
}),
path: PropTypes.string.isRequired,
}
+
+export default inject('router')(observer(Route))
diff --git a/src/plugins/vmPlugin.js b/src/plugins/vmPlugin.js
new file mode 100644
index 0000000..776a1a4
--- /dev/null
+++ b/src/plugins/vmPlugin.js
@@ -0,0 +1,52 @@
+import {diffPaths, splitPath} from '../Router'
+
+/**
+ * @param {{router: {vmPlugin: {vmTree: *}},incomingRequest: {route: string}, currentState: {route: string}}} - beforeNav event
+ */
+export function vmPluginInstantiateVM ({router, incomingRequest, currentState}) {
+ const nodes = diffPaths(splitPath(currentState.route), splitPath(incomingRequest.route))
+ nodes.forEach((node) => {
+ const routeConfig = router.routes[node]
+ if (routeConfig.vmPlugin) {
+ const {vmClass} = routeConfig.vmPlugin
+ router.vmPlugin.vmTree[node] = new vmClass(router.app.rootStore) // eslint-disable-line
+ }
+ })
+}
+
+/**
+ * @param {{router: {vmPlugin: {vmTree: *}},incomingRequest: {route: string}, currentState: {route: string}}} - afterNav event
+ */
+export function vmPluginCleanupVM ({router, incomingRequest, currentState}) {
+ const nodes = diffPaths(splitPath(incomingRequest.route), splitPath(currentState.route))
+ const {vmTree} = router.vmPlugin
+ nodes.forEach((node) => {
+ if (vmTree[node] && typeof vmTree[node].destroyVM === 'function') {
+ vmTree[node].destroyVM()
+ }
+ })
+}
+
+/**
+ * @param {*} router
+ */
+function register (router) {
+ router.on('beforeNav', vmPluginInstantiateVM)
+ router.on('afterNav', vmPluginCleanupVM)
+
+ const unregister = () => {
+ router.off('beforeNav', vmPluginInstantiateVM)
+ router.off('afterNav', vmPluginCleanupVM)
+ delete router.vmPlugin
+ }
+
+ router.vmPlugin = {
+ vmTree: {},
+ }
+
+ return unregister
+}
+
+export default {
+ register,
+}
diff --git a/src/plugins/vmPlugin.spec.js b/src/plugins/vmPlugin.spec.js
new file mode 100644
index 0000000..0f1fce4
--- /dev/null
+++ b/src/plugins/vmPlugin.spec.js
@@ -0,0 +1,110 @@
+import vmPlugin, {vmPluginInstantiateVM, vmPluginCleanupVM} from './vmPlugin'
+
+class SomePageMockVM {
+ destroyVM = jest.fn()
+}
+
+class OtherPageMockVM {
+}
+
+const routes = {
+ '/': {
+ },
+ '/some-page': {
+ vmPlugin: {
+ vmClass: SomePageMockVM,
+ },
+ },
+ '/some-other-page': {
+ vmPlugin: {
+ vmClass: OtherPageMockVM,
+ },
+ },
+}
+
+class RouterMock {
+ route = '/'
+ params = {}
+ goTo = jest.fn().mockImplementation((request) => {
+ this.route = request.route
+ this.params = request.params
+ })
+ on = jest.fn()
+ off = jest.fn()
+ routes = routes
+ app = {
+ rootStore: {}
+ }
+}
+
+describe('vmPlugin', () => {
+ let routerMock
+ let unregister
+
+ beforeEach(() => {
+ routerMock = new RouterMock(routes)
+ unregister = vmPlugin.register(routerMock)
+ })
+
+ describe('register', () => {
+ it('should register vmPluginInstantiateVM / vmPluginCleanupVM event listeners', () => {
+ // Before nav
+ const spyArgsBeforeNav = routerMock.on.mock.calls[0]
+ expect(spyArgsBeforeNav[0]).toBe('beforeNav')
+ expect(spyArgsBeforeNav[1]).toBe(vmPluginInstantiateVM)
+ // After nav
+ const spyArgsAfterNav = routerMock.on.mock.calls[1]
+ expect(spyArgsAfterNav[0]).toBe('afterNav')
+ expect(spyArgsAfterNav[1]).toBe(vmPluginCleanupVM)
+ })
+
+ it('should return a function to unregister the plugin', () => {
+ expect(unregister).toBeInstanceOf(Function)
+ })
+
+ it('should decorate the router with the vmTree', () => {
+ const routerMock = new RouterMock()
+ expect(routerMock.vmPlugin).toBeUndefined()
+ vmPlugin.register(routerMock)
+ expect(routerMock.vmPlugin.vmTree).toBeDefined()
+ })
+ })
+
+ describe('unregister', () => {
+ it('should remove the event handlers & vmTree', () => {
+ expect(routerMock.vmPlugin.vmTree).toBeDefined()
+ unregister()
+ expect(routerMock.vmPlugin).toBeUndefined()
+
+ // Before nav
+ const spyArgsBeforeNav = routerMock.off.mock.calls[0]
+ expect(spyArgsBeforeNav[0]).toBe('beforeNav')
+ expect(spyArgsBeforeNav[1]).toBe(vmPluginInstantiateVM)
+ // After nav
+ const spyArgsAfterNav = routerMock.off.mock.calls[1]
+ expect(spyArgsAfterNav[0]).toBe('afterNav')
+ expect(spyArgsAfterNav[1]).toBe(vmPluginCleanupVM)
+ })
+ })
+
+ describe('vmPluginInstantiateVM', () => {
+ it('should instantiate the corresponding VMs', () => {
+ vmPluginInstantiateVM({
+ router: routerMock, incomingRequest: {route: '/some-page'}, currentState: {route: '/'}
+ })
+ expect(routerMock.vmPlugin.vmTree['/some-page']).toBeInstanceOf(routes['/some-page'].vmPlugin.vmClass)
+ })
+ })
+
+ describe('vmPluginCleanupVM', () => {
+ it('should cleanup VMs', () => {
+ vmPluginInstantiateVM({
+ router: routerMock, incomingRequest: {route: '/some-page'}, currentState: {route: '/'}
+ })
+ vmPluginCleanupVM({
+ router: routerMock, incomingRequest: {route: '/some-other-page'}, currentState: {route: '/some-page'}
+ })
+ expect(routerMock.vmPlugin.vmTree['/some-page'].destroyVM).toHaveBeenCalled()
+ })
+ })
+})