diff --git a/__tests__/pure-utils.js b/__tests__/pure-utils.js index 5e23d2ac..2d138da5 100644 --- a/__tests__/pure-utils.js +++ b/__tests__/pure-utils.js @@ -10,7 +10,7 @@ import pathToAction from '../src/pure-utils/pathToAction' import actionToPath from '../src/pure-utils/actionToPath' import changePageTitle from '../src/pure-utils/changePageTitle' -import { NOT_FOUND } from '../src/index' +import { NOT_FOUND, addRoutes } from '../src/index' beforeEach(() => { window.SSRtest = false @@ -510,6 +510,159 @@ describe('confirmLeave()', () => { expect(history.location.pathname).toEqual('/first') }) + it('can block leaving after addRoutes if confirmLeave was missing on init', () => { + const firstRouteName = 'FIRST' + + const firstRouteOptions = { + path: '/first' + } + + const routesMap = { + [firstRouteName]: firstRouteOptions, + SECOND: '/second' + } + + const displayConfirmLeave = jest.fn() + const options = { displayConfirmLeave } + const { store, history } = setupAll('/first', options, { routesMap }) + const confirmLeave = jest.fn((state, action) => 'blocked') + + store.dispatch( + addRoutes({ + [firstRouteName]: { + ...firstRouteOptions, + confirmLeave + } + }) + ) + + store.dispatch({ type: 'SECOND' }) + + const { type } = store.getState().location + expect(type).toEqual(firstRouteName) + expect(displayConfirmLeave).toBeCalled() + expect(history.location.pathname).toEqual('/first') + expect(confirmLeave).toBeCalled() + }) + + it('can block leaving after addRoutes if confirmLeave was passing on init', () => { + const firstRouteName = 'FIRST' + + const firstRouteOptions = { + path: '/first' + } + + const prevConfirmLeave = jest.fn((state, action) => undefined) + const nextConfirmLeave = jest.fn((state, action) => 'blocked') + + const routesMap = { + [firstRouteName]: { + ...firstRouteOptions, + confirmLeave: prevConfirmLeave + }, + SECOND: '/second' + } + + const displayConfirmLeave = jest.fn() + const options = { displayConfirmLeave } + const { store, history } = setupAll('/first', options, { routesMap }) + + store.dispatch( + addRoutes({ + [firstRouteName]: { + ...firstRouteOptions, + confirmLeave: nextConfirmLeave + } + }) + ) + + store.dispatch({ type: 'SECOND' }) + + const { type } = store.getState().location + expect(type).toEqual(firstRouteName) + expect(displayConfirmLeave).toBeCalled() + expect(history.location.pathname).toEqual('/first') + expect(prevConfirmLeave).not.toBeCalled() + expect(nextConfirmLeave).toBeCalled() + }) + + it('can unblock leaving after addRoutes removing blocking confirmLeave', () => { + const firstRouteName = 'FIRST' + + const firstRouteOptions = { + path: '/first' + } + + const confirmLeave = jest.fn((state, action) => 'blocked') + + const routesMap = { + [firstRouteName]: { + ...firstRouteOptions, + confirmLeave + }, + SECOND: '/second' + } + + const displayConfirmLeave = jest.fn() + const options = { displayConfirmLeave } + const { store, history } = setupAll('/first', options, { routesMap }) + + store.dispatch( + addRoutes({ + [firstRouteName]: firstRouteOptions + }) + ) + + store.dispatch({ type: 'SECOND' }) + + const { type } = store.getState().location + expect(type).toEqual('SECOND') + expect(displayConfirmLeave).not.toBeCalled() + expect(history.location.pathname).toEqual('/second') + expect(confirmLeave).not.toBeCalled() + }) + + it('can unblock leaving after addRoutes if confirmLeave was blocking on init', () => { + const firstRouteName = 'FIRST' + + const firstRouteOptions = { + path: '/first' + } + + const prevConfirmLeave = jest.fn((state, action) => 'blocked') + const nextConfirmLeave = jest.fn((state, action) => undefined) + + const routesMap = { + [firstRouteName]: { + ...firstRouteOptions, + confirmLeave: prevConfirmLeave + }, + SECOND: '/second' + } + + const displayConfirmLeave = jest.fn() + const options = { displayConfirmLeave } + const { store, history } = setupAll('/first', options, { routesMap }) + + store.dispatch( + addRoutes({ + [firstRouteName]: { + ...firstRouteOptions, + confirmLeave: nextConfirmLeave + } + }) + ) + + store.dispatch({ type: 'SECOND' }) + + const { type } = store.getState().location + expect(type).toEqual('SECOND') + expect(displayConfirmLeave).not.toBeCalled() + expect(history.location.pathname).toEqual('/second') + expect(prevConfirmLeave).not.toBeCalled() + expect(nextConfirmLeave).toBeCalled() + }) + it('can leave throws (React Native where window.confirm does not exist)', () => { global.confirm = undefined diff --git a/src/connectRoutes.js b/src/connectRoutes.js index 3ff0b205..d753e12d 100644 --- a/src/connectRoutes.js +++ b/src/connectRoutes.js @@ -16,6 +16,7 @@ import pathnamePlusSearch from './pure-utils/pathnamePlusSearch' import canUseDom from './pure-utils/canUseDom' import { + clearBlocking, createConfirm, confirmUI, setDisplayConfirmLeave, @@ -230,8 +231,32 @@ export default (routesMap: RoutesMap = {}, options: Options = {}) => { // code-splitting functionliaty to add routes after store is initially configured if (action.type === ADD_ROUTES) { + const { type } = selectLocationState(store.getState()) + const route = routesMap[type] + routesMap = { ...routesMap, ...action.payload.routes } - return next(action) + + const result = next(action) + const nextRoute = routesMap[type] + + if (route !== nextRoute) { + if (_confirm !== null) { + clearBlocking() + } + + if (typeof nextRoute === 'object' && nextRoute.confirmLeave) { + _confirm = createConfirm( + nextRoute.confirmLeave, + store, + selectLocationState, + history, + querySerializer, + () => (_confirm = null) + ) + } + } + + return result } // navigation transformation specific to React Navigation @@ -571,7 +596,7 @@ export default (routesMap: RoutesMap = {}, options: Options = {}) => { _selectLocationState = selectLocationState let _initialDispatch - let _confirm + let _confirm = null _updateScroll = (performedByUser: boolean = true) => { if (scrollBehavior) { diff --git a/src/pure-utils/confirmLeave.js b/src/pure-utils/confirmLeave.js index e295b284..631af9ce 100644 --- a/src/pure-utils/confirmLeave.js +++ b/src/pure-utils/confirmLeave.js @@ -18,7 +18,7 @@ let _unblock let _removeConfirmBlocking let _displayConfirmLeave -const clearBlocking = () => { +export const clearBlocking = () => { _unblock && _unblock() _removeConfirmBlocking && _removeConfirmBlocking() }