diff --git a/.babelrc b/.babelrc index dc1bc4f..3c3b968 100644 --- a/.babelrc +++ b/.babelrc @@ -1,3 +1,4 @@ { - "presets": ["es2015"] -} \ No newline at end of file + "presets": ["es2015"], + "plugins": ["transform-object-assign"] +} diff --git a/karma.conf.js b/karma.conf.js new file mode 100644 index 0000000..027abcb --- /dev/null +++ b/karma.conf.js @@ -0,0 +1,63 @@ +var path = require('path'); +var webpack = require('webpack'); + +module.exports = function (config) { + + var runCoverage = process.env.COVERAGE === 'true'; + + var coverageLoaders = []; + var coverageReporters = []; + + if (runCoverage) { + coverageLoaders.push({ + test: /\.js$/, + include: path.resolve('src/'), + loader: 'isparta' + }), + + coverageReporters.push('coverage'); + } + + config.set({ + + browsers: [ 'Firefox' ], + frameworks: [ 'mocha' ], + reporters: [ 'mocha' ].concat(coverageReporters), + + files: [ + 'tests.webpack.js' + ], + + preprocessors: { + 'tests.webpack.js': [ 'webpack', 'sourcemap' ] + }, + + singleRun: true, + + webpack: { + devtool: 'inline-source-map', + module: { + preLoaders: [ + { + test: /\.js$/, + exclude: [ + path.resolve('node_modules/') + ], + loader: 'babel' + }, + ].concat(coverageLoaders) + } + }, + + webpackServer: { + noInfo: true + }, + + coverageReporter: { + reporters: [ + { type: 'text' }, + { type: 'json', subdir: 'browser-coverage', file: 'coverage.json' } + ] + } + }); +}; diff --git a/package.json b/package.json index 75ace7b..aae8dc0 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,14 @@ ], "license": "MIT", "scripts": { - "build": "mkdir -p lib && babel ./src/index.js --plugins transform-object-assign --out-file ./lib/index.js", - "test": "mocha --compilers js:babel-core/register --recursive", - "test:cov": "babel-node $(npm bin)/isparta cover $(npm bin)/_mocha -- --recursive", + "build": "mkdir -p lib && babel ./src/index.js --out-file ./lib/index.js", + "test": "npm run test:node && npm run test:browser", + "test:node": "mocha --compilers js:babel-core/register --recursive ./test/node", + "test:browser": "karma start", + "test:cov": "npm run test:cov:browser && npm run test:cov:node && npm run test:cov:report", + "test:cov:node": "babel-node $(npm bin)/isparta cover $(npm bin)/_mocha report --dir ./coverage/node-coverage -- --recursive ./test/node", + "test:cov:browser": "COVERAGE=true karma start", + "test:cov:report": "$(npm bin)/istanbul report --dir ./coverage --include **/*coverage.json html text", "prepublish": "npm run build" }, "tags": [ @@ -29,13 +34,26 @@ "devDependencies": { "babel-cli": "^6.1.2", "babel-core": "^6.2.1", + "babel-loader": "^6.2.0", "babel-plugin-transform-object-assign": "^6.0.14", "babel-preset-es2015": "^6.1.2", "expect": "^1.13.0", "history": "^1.13.1", "isparta": "^4.0.0", + "isparta-loader": "^2.0.0", + "karma": "^0.13.3", + "karma-chrome-launcher": "^0.2.0", + "karma-coverage": "^0.5.3", + "karma-firefox-launcher": "^0.1.7", + "karma-ie-launcher": "^0.2.0", + "karma-mocha": "^0.2.0", + "karma-mocha-reporter": "^1.0.4", + "karma-safari-launcher": "^0.1.1", + "karma-sourcemap-loader": "^0.3.5", + "karma-webpack": "^1.7.0", "mocha": "^2.3.4", - "redux": "^3.0.4" + "redux": "^3.0.4", + "webpack": "^1.12.9" }, "dependencies": { "deep-equal": "^1.0.1" diff --git a/test/browser/index.js b/test/browser/index.js new file mode 100644 index 0000000..22222e5 --- /dev/null +++ b/test/browser/index.js @@ -0,0 +1,5 @@ +const { createHashHistory, createHistory } = require('history'); +const createTests = require('../createTests.js'); + +createTests(createHashHistory, 'Hash History', () => window.location = '#/'); +createTests(createHistory, 'Browser History', () => window.history.replaceState(null, null, '/')); diff --git a/test/createTests.js b/test/createTests.js new file mode 100644 index 0000000..e05b9ad --- /dev/null +++ b/test/createTests.js @@ -0,0 +1,479 @@ +const expect = require('expect'); +const { pushPath, replacePath, UPDATE_PATH, routeReducer, syncReduxAndRouter } = require('../src/index'); +const { createStore, combineReducers } = require('redux'); + +function createSyncedHistoryAndStore(createHistory) { + const store = createStore(combineReducers({ + routing: routeReducer + })); + const history = createHistory(); + const unsubscribe = syncReduxAndRouter(history, store); + return { history, store, unsubscribe }; +} + +const defaultReset = () => {}; + +module.exports = function createTests(createHistory, name, reset = defaultReset) { + describe(name, () => { + + beforeEach(reset); + + describe('pushPath', () => { + it('creates actions', () => { + expect(pushPath('/foo', { bar: 'baz' })).toEqual({ + type: UPDATE_PATH, + payload: { + path: '/foo', + replace: false, + state: { bar: 'baz' }, + avoidRouterUpdate: false + } + }); + + expect(pushPath('/foo', undefined, { avoidRouterUpdate: true })).toEqual({ + type: UPDATE_PATH, + payload: { + path: '/foo', + state: undefined, + replace: false, + avoidRouterUpdate: true + } + }); + }); + }); + + describe('replacePath', () => { + it('creates actions', () => { + expect(replacePath('/foo', { bar: 'baz' })).toEqual({ + type: UPDATE_PATH, + payload: { + path: '/foo', + replace: true, + state: { bar: 'baz' }, + avoidRouterUpdate: false + } + }); + + expect(replacePath('/foo', undefined, { avoidRouterUpdate: true })).toEqual({ + type: UPDATE_PATH, + payload: { + path: '/foo', + state: undefined, + replace: true, + avoidRouterUpdate: true + } + }); + + expect(replacePath('/foo', undefined, { avoidRouterUpdate: false })).toEqual({ + type: UPDATE_PATH, + payload: { + path: '/foo', + state: undefined, + replace: true, + avoidRouterUpdate: false + } + }); + }); + }); + + describe('routeReducer', () => { + const state = { + path: '/foo', + changeId: 1 + }; + + it('updates the path', () => { + expect(routeReducer(state, { + type: UPDATE_PATH, + payload: { + path: '/bar', + replace: false + } + })).toEqual({ + path: '/bar', + replace: false, + state: undefined, + changeId: 2 + }); + }); + + it('respects replace', () => { + expect(routeReducer(state, { + type: UPDATE_PATH, + payload: { + path: '/bar', + replace: true, + avoidRouterUpdate: false + } + })).toEqual({ + path: '/bar', + replace: true, + state: undefined, + changeId: 2 + }); + }); + + it('respects `avoidRouterUpdate` flag', () => { + expect(routeReducer(state, { + type: UPDATE_PATH, + payload: { + path: '/bar', + replace: false, + avoidRouterUpdate: true + } + })).toEqual({ + path: '/bar', + replace: false, + state: undefined, + changeId: 1 + }); + }); + }); + + describe('syncReduxAndRouter', () => { + let history, store, unsubscribe; + + beforeEach(() => { + let synced = createSyncedHistoryAndStore(createHistory); + history = synced.history; + store = synced.store; + unsubscribe = synced.unsubscribe; + }); + + afterEach(() => { + unsubscribe(); + }); + + it('syncs router -> redux', () => { + expect(store.getState().routing.path).toEqual('/'); + + history.pushState(null, '/foo'); + expect(store.getState().routing.path).toEqual('/foo'); + expect(store.getState().routing.state).toBe(null); + expect(store.getState().routing.replace).toBe(false); + + history.pushState({ bar: 'baz' }, '/foo'); + expect(store.getState().routing.path).toEqual('/foo'); + expect(store.getState().routing.state).toEqual({ bar: 'baz' }); + expect(store.getState().routing.replace).toBe(true); + + history.replaceState(null, '/bar'); + expect(store.getState().routing.path).toEqual('/bar'); + expect(store.getState().routing.state).toBe(null); + expect(store.getState().routing.replace).toBe(true); + + history.pushState(null, '/bar'); + expect(store.getState().routing.path).toEqual('/bar'); + expect(store.getState().routing.state).toBe(null); + expect(store.getState().routing.replace).toBe(true); + + history.pushState(null, '/bar?query=1'); + expect(store.getState().routing.path).toEqual('/bar?query=1'); + expect(store.getState().routing.replace).toBe(false); + + history.replaceState({ bar: 'baz' }, '/bar?query=1'); + expect(store.getState().routing.path).toEqual('/bar?query=1'); + expect(store.getState().routing.state).toEqual({ bar: 'baz' }); + expect(store.getState().routing.replace).toBe(true); + + history.pushState({ bar: 'baz' }, '/bar?query=1#hash=2'); + expect(store.getState().routing.path).toEqual('/bar?query=1#hash=2'); + expect(store.getState().routing.replace).toBe(true); + }); + + it('syncs redux -> router', () => { + expect(store.getState().routing).toEqual({ + path: '/', + changeId: 1, + replace: false, + state: undefined + }); + + store.dispatch(pushPath('/foo')); + expect(store.getState().routing).toEqual({ + path: '/foo', + changeId: 2, + replace: false, + state: undefined + }); + + store.dispatch(pushPath('/foo', { bar: 'baz' })); + expect(store.getState().routing).toEqual({ + path: '/foo', + changeId: 3, + replace: false, + state: { bar: 'baz' } + }); + + store.dispatch(replacePath('/bar', { bar: 'foo' })); + expect(store.getState().routing).toEqual({ + path: '/bar', + changeId: 4, + replace: true, + state: { bar: 'foo' } + }); + + store.dispatch(pushPath('/bar')); + expect(store.getState().routing).toEqual({ + path: '/bar', + changeId: 5, + replace: false, + state: undefined + }); + + store.dispatch(pushPath('/bar?query=1')); + expect(store.getState().routing).toEqual({ + path: '/bar?query=1', + changeId: 6, + replace: false, + state: undefined + }); + + store.dispatch(pushPath('/bar?query=1#hash=2')); + expect(store.getState().routing).toEqual({ + path: '/bar?query=1#hash=2', + changeId: 7, + replace: false, + state: undefined + }); + }); + + it('updates the router even if path is the same', () => { + expect(store.getState().routing).toEqual({ + path: '/', + changeId: 1, + replace: false, + state: undefined + }); + + store.dispatch(pushPath('/foo')); + expect(store.getState().routing).toEqual({ + path: '/foo', + changeId: 2, + replace: false, + state: undefined + }); + + store.dispatch(pushPath('/foo')); + expect(store.getState().routing).toEqual({ + path: '/foo', + changeId: 3, + replace: false, + state: undefined + }); + + store.dispatch(replacePath('/foo')); + expect(store.getState().routing).toEqual({ + path: '/foo', + changeId: 4, + replace: true, + state: undefined + }); + }); + + it('does not update the router for other state changes', () => { + store.dispatch({ + type: 'RANDOM_ACTION', + payload: { + payload: { + value: 5 + } + } + }); + + expect(store.getState().routing).toEqual({ + path: '/', + changeId: 1, + replace: false, + state: undefined + }); + }); + + it('only updates the router once when dispatching from `listenBefore`', () => { + expect(store.getState().routing).toEqual({ + path: '/', + changeId: 1, + replace: false, + state: undefined + }); + + history.listenBefore(location => { + expect(location.pathname).toEqual('/foo'); + store.dispatch({ + type: 'RANDOM_ACTION', + payload: { + payload: { + value: 5 + } + } + }); + }); + + store.dispatch(pushPath('/foo')); + expect(store.getState().routing).toEqual({ + path: '/foo', + changeId: 2, + replace: false, + state: undefined + }); + }); + + it('does not unnecessarily update the store', () => { + const updates = []; + + const unsubscribeFromStore = store.subscribe(() => { + updates.push(store.getState()) + }); + + store.dispatch(pushPath('/foo')); + store.dispatch(pushPath('/foo')); + store.dispatch(pushPath('/foo', { bar: 'baz' })); + store.dispatch(replacePath('/bar')); + store.dispatch(replacePath('/bar', { bar: 'foo' })); + + unsubscribeFromStore(); + + expect(updates.length).toBe(5); + expect(updates).toEqual([ + { + routing: { + changeId: 2, + path: '/foo', + state: undefined, + replace: false + } + }, + { + routing: { + changeId: 3, + path: '/foo', + state: undefined, + replace: false + } + }, + { + routing: { + changeId: 4, + path: '/foo', + state: { bar: 'baz' }, + replace: false + } + }, + { + routing: { + changeId: 5, + path: '/bar', + state: undefined, + replace: true + } + }, + { + routing: { + changeId: 6, + path: '/bar', + state: { bar: 'foo' }, + replace: true + } + } + ]); + }); + + it('allows updating the route from within `listenBefore`', () => { + expect(store.getState().routing).toEqual({ + path: '/', + changeId: 1, + replace: false, + state: undefined + }); + + history.listenBefore(location => { + if(location.pathname === '/foo') { + expect(store.getState().routing).toEqual({ + path: '/foo', + changeId: 2, + replace: false, + state: undefined + }); + store.dispatch(pushPath('/bar')); + } + else if(location.pathname === '/replace') { + expect(store.getState().routing).toEqual({ + path: '/replace', + changeId: 4, + replace: false, + state: { bar: 'baz' } + }); + store.dispatch(replacePath('/baz', { foo: 'bar' })); + } + }); + + store.dispatch(pushPath('/foo')); + expect(store.getState().routing).toEqual({ + path: '/bar', + changeId: 3, + replace: false, + state: undefined + }); + + store.dispatch(pushPath('/replace', { bar: 'baz' })); + expect(store.getState().routing).toEqual({ + path: '/baz', + changeId: 5, + replace: true, + state: { foo: 'bar' } + }); + }) + + it('throws if "routing" key is missing with default selectRouteState', () => { + const store = createStore(combineReducers({ + notRouting: routeReducer + })); + const history = createHistory(); + expect( + () => syncReduxAndRouter(history, store) + ).toThrow(/Cannot sync router: route state does not exist/); + }); + + it('accepts custom selectRouterState', () => { + const store = createStore(combineReducers({ + notRouting: routeReducer + })); + const history = createHistory(); + syncReduxAndRouter(history, store, state => state.notRouting) + history.pushState(null, '/bar'); + expect(store.getState().notRouting.path).toEqual('/bar'); + }); + + it('returns unsubscribe to stop listening to history and store', () => { + const store = createStore(combineReducers({ + routing: routeReducer + })); + const history = createHistory(); + const unsubscribe = syncReduxAndRouter(history, store) + + history.pushState(null, '/foo'); + expect(store.getState().routing.path).toEqual('/foo'); + + store.dispatch(pushPath('/bar')); + expect(store.getState().routing).toEqual({ + path: '/bar', + changeId: 2, + replace: false, + state: undefined + }); + + unsubscribe(); + + history.pushState(null, '/foo'); + expect(store.getState().routing.path).toEqual('/bar'); + + history.listenBefore(location => { + throw new Error() + }); + expect( + () => store.dispatch(pushPath('/foo')) + ).toNotThrow(); + }); + }); + }); +} diff --git a/test/index.js b/test/index.js deleted file mode 100644 index a415502..0000000 --- a/test/index.js +++ /dev/null @@ -1,457 +0,0 @@ -const expect = require('expect'); -const { pushPath, replacePath, UPDATE_PATH, routeReducer, syncReduxAndRouter } = require('../src/index'); -const { createStore, combineReducers } = require('redux'); -const { createMemoryHistory: createHistory } = require('history'); - -function createSyncedHistoryAndStore() { - const store = createStore(combineReducers({ - routing: routeReducer - })); - const history = createHistory(); - syncReduxAndRouter(history, store); - return { history, store }; -} - -describe('pushPath', () => { - it('creates actions', () => { - expect(pushPath('/foo', { bar: 'baz' })).toEqual({ - type: UPDATE_PATH, - payload: { - path: '/foo', - replace: false, - state: { bar: 'baz' }, - avoidRouterUpdate: false - } - }); - - expect(pushPath('/foo', undefined, { avoidRouterUpdate: true })).toEqual({ - type: UPDATE_PATH, - payload: { - path: '/foo', - state: undefined, - replace: false, - avoidRouterUpdate: true - } - }); - }); -}); - -describe('replacePath', () => { - it('creates actions', () => { - expect(replacePath('/foo', { bar: 'baz' })).toEqual({ - type: UPDATE_PATH, - payload: { - path: '/foo', - replace: true, - state: { bar: 'baz' }, - avoidRouterUpdate: false - } - }); - - expect(replacePath('/foo', undefined, { avoidRouterUpdate: true })).toEqual({ - type: UPDATE_PATH, - payload: { - path: '/foo', - state: undefined, - replace: true, - avoidRouterUpdate: true - } - }); - - expect(replacePath('/foo', undefined, { avoidRouterUpdate: false })).toEqual({ - type: UPDATE_PATH, - payload: { - path: '/foo', - state: undefined, - replace: true, - avoidRouterUpdate: false - } - }); - }); -}); - -describe('routeReducer', () => { - const state = { - path: '/foo', - changeId: 1 - }; - - it('updates the path', () => { - expect(routeReducer(state, { - type: UPDATE_PATH, - payload: { - path: '/bar', - replace: false - } - })).toEqual({ - path: '/bar', - replace: false, - state: undefined, - changeId: 2 - }); - }); - - it('respects replace', () => { - expect(routeReducer(state, { - type: UPDATE_PATH, - payload: { - path: '/bar', - replace: true, - avoidRouterUpdate: false - } - })).toEqual({ - path: '/bar', - replace: true, - state: undefined, - changeId: 2 - }); - }); - - it('respects `avoidRouterUpdate` flag', () => { - expect(routeReducer(state, { - type: UPDATE_PATH, - payload: { - path: '/bar', - replace: false, - avoidRouterUpdate: true - } - })).toEqual({ - path: '/bar', - replace: false, - state: undefined, - changeId: 1 - }); - }); -}); - -describe('syncReduxAndRouter', () => { - it('syncs router -> redux', () => { - const { history, store } = createSyncedHistoryAndStore(); - expect(store.getState().routing.path).toEqual('/'); - - history.pushState(null, '/foo'); - expect(store.getState().routing.path).toEqual('/foo'); - expect(store.getState().routing.state).toBe(null); - expect(store.getState().routing.replace).toBe(false); - - history.pushState({ bar: 'baz' }, '/foo'); - expect(store.getState().routing.path).toEqual('/foo'); - expect(store.getState().routing.state).toEqual({ bar: 'baz' }); - expect(store.getState().routing.replace).toBe(true); - - history.replaceState(null, '/bar'); - expect(store.getState().routing.path).toEqual('/bar'); - expect(store.getState().routing.state).toBe(null); - expect(store.getState().routing.replace).toBe(true); - - history.pushState(null, '/bar'); - expect(store.getState().routing.path).toEqual('/bar'); - expect(store.getState().routing.state).toBe(null); - expect(store.getState().routing.replace).toBe(true); - - history.pushState(null, '/bar?query=1'); - expect(store.getState().routing.path).toEqual('/bar?query=1'); - expect(store.getState().routing.replace).toBe(false); - - history.replaceState({ bar: 'baz' }, '/bar?query=1'); - expect(store.getState().routing.path).toEqual('/bar?query=1'); - expect(store.getState().routing.state).toEqual({ bar: 'baz' }); - expect(store.getState().routing.replace).toBe(true); - - history.pushState({ bar: 'baz' }, '/bar?query=1#hash=2'); - expect(store.getState().routing.path).toEqual('/bar?query=1#hash=2'); - expect(store.getState().routing.replace).toBe(true); - }); - - it('syncs redux -> router', () => { - const { history, store } = createSyncedHistoryAndStore(); - expect(store.getState().routing).toEqual({ - path: '/', - changeId: 1, - replace: false, - state: undefined - }); - - store.dispatch(pushPath('/foo')); - expect(store.getState().routing).toEqual({ - path: '/foo', - changeId: 2, - replace: false, - state: undefined - }); - - store.dispatch(pushPath('/foo', { bar: 'baz' })); - expect(store.getState().routing).toEqual({ - path: '/foo', - changeId: 3, - replace: false, - state: { bar: 'baz' } - }); - - store.dispatch(replacePath('/bar', { bar: 'foo' })); - expect(store.getState().routing).toEqual({ - path: '/bar', - changeId: 4, - replace: true, - state: { bar: 'foo' } - }); - - store.dispatch(pushPath('/bar')); - expect(store.getState().routing).toEqual({ - path: '/bar', - changeId: 5, - replace: false, - state: undefined - }); - - store.dispatch(pushPath('/bar?query=1')); - expect(store.getState().routing).toEqual({ - path: '/bar?query=1', - changeId: 6, - replace: false, - state: undefined - }); - - store.dispatch(pushPath('/bar?query=1#hash=2')); - expect(store.getState().routing).toEqual({ - path: '/bar?query=1#hash=2', - changeId: 7, - replace: false, - state: undefined - }); - }); - - it('updates the router even if path is the same', () => { - const { history, store } = createSyncedHistoryAndStore(); - expect(store.getState().routing).toEqual({ - path: '/', - changeId: 1, - replace: false, - state: undefined - }); - - store.dispatch(pushPath('/foo')); - expect(store.getState().routing).toEqual({ - path: '/foo', - changeId: 2, - replace: false, - state: undefined - }); - - store.dispatch(pushPath('/foo')); - expect(store.getState().routing).toEqual({ - path: '/foo', - changeId: 3, - replace: false, - state: undefined - }); - - store.dispatch(replacePath('/foo')); - expect(store.getState().routing).toEqual({ - path: '/foo', - changeId: 4, - replace: true, - state: undefined - }); - }); - - it('does not update the router for other state changes', () => { - const { history, store } = createSyncedHistoryAndStore(); - store.dispatch({ - type: 'RANDOM_ACTION', - value: 5 - }); - - expect(store.getState().routing).toEqual({ - path: '/', - changeId: 1, - replace: false, - state: undefined - }); - }); - - it('only updates the router once when dispatching from `listenBefore`', () => { - const { history, store } = createSyncedHistoryAndStore(); - expect(store.getState().routing).toEqual({ - path: '/', - changeId: 1, - replace: false, - state: undefined - }); - - history.listenBefore(location => { - expect(location.pathname).toEqual('/foo'); - store.dispatch({ - type: 'RANDOM_ACTION', - value: 5 - }); - }); - - store.dispatch(pushPath('/foo')); - expect(store.getState().routing).toEqual({ - path: '/foo', - changeId: 2, - replace: false, - state: undefined - }); - }); - - it('does not unnecessarily update the store', () => { - const { history, store } = createSyncedHistoryAndStore(); - const updates = []; - - const unsubscribe = store.subscribe(() => { - updates.push(store.getState()) - }); - - store.dispatch(pushPath('/foo')); - store.dispatch(pushPath('/foo')); - store.dispatch(pushPath('/foo', { bar: 'baz' })); - store.dispatch(replacePath('/bar')); - store.dispatch(replacePath('/bar', { bar: 'foo' })); - - unsubscribe(); - - expect(updates.length).toBe(5); - expect(updates).toEqual([ - { - routing: { - changeId: 2, - path: '/foo', - state: undefined, - replace: false - } - }, - { - routing: { - changeId: 3, - path: '/foo', - state: undefined, - replace: false - } - }, - { - routing: { - changeId: 4, - path: '/foo', - state: { bar: 'baz' }, - replace: false - } - }, - { - routing: { - changeId: 5, - path: '/bar', - state: undefined, - replace: true - } - }, - { - routing: { - changeId: 6, - path: '/bar', - state: { bar: 'foo' }, - replace: true - } - } - ]); - }); - - it('allows updating the route from within `listenBefore`', () => { - const { history, store } = createSyncedHistoryAndStore(); - expect(store.getState().routing).toEqual({ - path: '/', - changeId: 1, - replace: false, - state: undefined - }); - - history.listenBefore(location => { - if(location.pathname === '/foo') { - expect(store.getState().routing).toEqual({ - path: '/foo', - changeId: 2, - replace: false, - state: undefined - }); - store.dispatch(pushPath('/bar')); - } - else if(location.pathname === '/replace') { - expect(store.getState().routing).toEqual({ - path: '/replace', - changeId: 4, - replace: false, - state: { bar: 'baz' } - }); - store.dispatch(replacePath('/baz', { foo: 'bar' })); - } - }); - - store.dispatch(pushPath('/foo')); - expect(store.getState().routing).toEqual({ - path: '/bar', - changeId: 3, - replace: false, - state: undefined - }); - - store.dispatch(pushPath('/replace', { bar: 'baz' })); - expect(store.getState().routing).toEqual({ - path: '/baz', - changeId: 5, - replace: true, - state: { foo: 'bar' } - }); - }) - - it('throws if "routing" key is missing with default selectRouteState', () => { - const store = createStore(combineReducers({ - notRouting: routeReducer - })); - const history = createHistory(); - expect( - () => syncReduxAndRouter(history, store) - ).toThrow(/Cannot sync router: route state does not exist/); - }); - - it('accepts custom selectRouterState', () => { - const store = createStore(combineReducers({ - notRouting: routeReducer - })); - const history = createHistory(); - syncReduxAndRouter(history, store, state => state.notRouting) - history.pushState(null, '/bar'); - expect(store.getState().notRouting.path).toEqual('/bar'); - }); - - it('returns unsubscribe to stop listening to history and store', () => { - const store = createStore(combineReducers({ - routing: routeReducer - })); - const history = createHistory(); - const unsubscribe = syncReduxAndRouter(history, store) - - history.pushState(null, '/foo'); - expect(store.getState().routing.path).toEqual('/foo'); - - store.dispatch(pushPath('/bar')); - expect(store.getState().routing).toEqual({ - path: '/bar', - changeId: 2, - replace: false, - state: undefined - }); - - unsubscribe(); - - history.pushState(null, '/foo'); - expect(store.getState().routing.path).toEqual('/bar'); - - history.listenBefore(location => { - throw new Error() - }); - expect( - () => store.dispatch(pushPath('/foo')) - ).toNotThrow(); - }); -}); diff --git a/test/node/index.js b/test/node/index.js new file mode 100644 index 0000000..ed6a04a --- /dev/null +++ b/test/node/index.js @@ -0,0 +1,4 @@ +const { createMemoryHistory } = require('history'); +const createTests = require('../createTests.js'); + +createTests(createMemoryHistory, 'Memory History'); diff --git a/tests.webpack.js b/tests.webpack.js new file mode 100644 index 0000000..15df5a3 --- /dev/null +++ b/tests.webpack.js @@ -0,0 +1,2 @@ +const browserContext = require.context('./test/browser', true, /\.js$/); +browserContext.keys().forEach(browserContext);