From f1432aee9d10bb5ce6bd3b4b1d9d5d444a9a703c Mon Sep 17 00:00:00 2001 From: Gregory Beaver Date: Tue, 18 Apr 2017 22:30:33 -0500 Subject: [PATCH] prepare for initial release under new name --- package.json | 8 +-- src/DisplaysChildren.jsx | 3 +- src/Link.jsx | 20 +++++-- src/Route.jsx | 3 +- src/Routes.jsx | 3 +- src/Toggle.jsx | 19 +------ src/helpers.js | 10 ++-- src/index.js | 12 ++++- test/Link.test.js | 31 +++++++++-- test/Route.test.js | 2 +- test/Routes.test.jsx | 9 ++-- test/Toggle.test.js | 31 ----------- test/actions.test.js | 2 +- test/enhanced.test.js | 2 +- test/helpers.test.js | 72 +++++++++++++++++++++++++ test/index.test.js | 14 ++++- test/middleware.test.js | 1 + test/reducer.test.js | 2 +- test/selectors.test.js | 2 +- test/test_helper.jsx | 113 ++------------------------------------- 20 files changed, 169 insertions(+), 190 deletions(-) diff --git a/package.json b/package.json index b6d2d8e..2bd3b05 100644 --- a/package.json +++ b/package.json @@ -34,10 +34,10 @@ "dependencies": { "history": "^4.5.1", "invariant": "^2.2.2", - "react": "^15.4.2", - "react-dom": "^15.4.2", + "prop-types": "^15.5.8", + "react": "^15.5.4", + "react-dom": "^15.5.4", "redux": "^3.6.0", - "redux-saga": "^0.14.2", "route-parser": "0.0.5" }, "devDependencies": { @@ -84,7 +84,7 @@ "lolex": "^1.5.2", "mocha": "^3.2.0", "raw-loader": "^0.5.1", - "react-redux": "^5.0.2", + "react-redux": "^5.0.4", "rimraf": "^2.5.4", "selenium-webdriver": "^3.0.1", "should": "^11.1.2", diff --git a/src/DisplaysChildren.jsx b/src/DisplaysChildren.jsx index dedf6b1..e23ebec 100644 --- a/src/DisplaysChildren.jsx +++ b/src/DisplaysChildren.jsx @@ -1,4 +1,5 @@ -import React, { Component, PropTypes } from 'react' +import React, { Component } from 'react' +import PropTypes from 'prop-types' export default class DisplaysChildren extends Component { static propTypes = { diff --git a/src/Link.jsx b/src/Link.jsx index 3efae03..c6817e5 100644 --- a/src/Link.jsx +++ b/src/Link.jsx @@ -1,4 +1,5 @@ -import React, { PropTypes, Component } from 'react' +import React, { Component } from 'react' +import PropTypes from 'prop-types' import RouteParser from 'route-parser' import invariant from 'invariant' @@ -64,15 +65,26 @@ class Link extends Component { const { '@@__routes': unused, dispatch, href, replace, to, route, onClick, ...props // eslint-disable-line no-unused-vars } = this.props + const validProps = [ + 'download', 'hrefLang', 'referrerPolicy', 'rel', 'target', 'type', + 'id', 'accessKey', 'className', 'contentEditable', 'contextMenu', 'dir', 'draggable', + 'hidden', 'itemID', 'itemProp', 'itemRef', 'itemScope', 'itemType', 'lang', + 'spellCheck', 'style', 'tabIndex', 'title' + ] + const aProps = Object.keys(props).reduce((newProps, key) => { + if (validProps.includes(key)) newProps[key] = props[key] // eslint-disable-line + if (key.slice(0, 5) === 'data-') newProps[key] = props[key] // eslint-disable-line + return newProps + }, {}) invariant(!href, 'href should not be passed to Link, use "to," "replace" or "route" (passed "%s")', href) - let landing = replace || to + let landing = replace || to || '' if (this.route) { landing = this.route.reverse(props) } else if (landing.pathname) { landing = `${landing.pathname}${'' + landing.search}${'' + landing.hash}` // eslint-disable-line prefer-template } return ( - + {this.props.children} ) @@ -83,7 +95,7 @@ export { Link } export const Placeholder = () => { throw new Error('call connectLink with the connect function from react-redux to ' + - 'initialize Link (see https://github.com/cellog/react-redux-saga-router/issues/1)') + 'initialize Link (see https://github.com/cellog/ion-router/issues/1)') } let ConnectedLink = null diff --git a/src/Route.jsx b/src/Route.jsx index 6ba3c72..b818fcd 100644 --- a/src/Route.jsx +++ b/src/Route.jsx @@ -1,4 +1,5 @@ -import React, { Children, Component, PropTypes } from 'react' +import React, { Children, Component } from 'react' +import PropTypes from 'prop-types' export function fake() { return {} diff --git a/src/Routes.jsx b/src/Routes.jsx index ebd985f..66366d7 100644 --- a/src/Routes.jsx +++ b/src/Routes.jsx @@ -1,4 +1,5 @@ -import React, { PropTypes, Component, Children } from 'react' +import React, { Component, Children } from 'react' +import PropTypes from 'prop-types' import * as actions from './actions' import { onServer } from '.' diff --git a/src/Toggle.jsx b/src/Toggle.jsx index 6aa557e..72a9a66 100644 --- a/src/Toggle.jsx +++ b/src/Toggle.jsx @@ -1,4 +1,5 @@ -import React, { PropTypes } from 'react' +import React from 'react' +import PropTypes from 'prop-types' import DisplaysChildren from './DisplaysChildren' @@ -107,21 +108,5 @@ export default (isActive, loaded = () => true, componentLoadingMap = {}, debug = names[item] = componentLoadingMap[item] } }) - - Toggle.propTypes = { - [names.component]: PropTypes.oneOfType([PropTypes.element, PropTypes.func]), - [names.loadingComponent]: PropTypes.oneOfType([PropTypes.element, PropTypes.func]), - loading: (props, propName) => { - if (Object.hasOwnProperty.call(props, 'loading') && !Object.hasOwnProperty.call(props, 'loadingComponent')) { - const name = props.component.displayName || props.component.name || 'Component' - if (!componentLoadingMap.loadingComponent) { - console.warn(`${propName} in Toggle:${name} should be loadingComponent for ion-router`) // eslint-disable-line - } - } - return null - }, - [names.else]: PropTypes.oneOfType([PropTypes.element, PropTypes.func]), - children: PropTypes.any - } return Toggle } diff --git a/src/helpers.js b/src/helpers.js index def57db..8ee7efc 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -7,11 +7,6 @@ import * as enhancers from './enhancers' export const filter = (enhancedRoutes, path) => name => enhancedRoutes[name]['@parser'].match(path) export const diff = (main, second) => main.filter(name => second.indexOf(name) === -1) -export function template(s, params) { - return s.exitParams instanceof Function ? - { ...s.exitParams(params) } : { ...s.exitParams } -} - export function changed(oldItems, newItems) { return Object.keys({ ...newItems, ...oldItems }) .filter(key => !Object.prototype.hasOwnProperty.call(oldItems, key) || @@ -98,6 +93,11 @@ export function updateState(s, params, state) { } } +export function template(s, params) { + return s.exitParams instanceof Function ? + { ...s.exitParams(params) } : { ...s.exitParams } +} + export const exitRoute = (state, enhanced, name) => { const s = enhanced[name] const params = s.params diff --git a/src/index.js b/src/index.js index d49e5ee..a21bd3c 100644 --- a/src/index.js +++ b/src/index.js @@ -48,7 +48,7 @@ export function routingReducer(reducers = state => ({ ...(state || {}) })) { return (state, action) => { if (!state) { return { - ...routerReducer(), + routing: routerReducer(), ...(reducers() || {}) } } @@ -58,8 +58,16 @@ export function routingReducer(reducers = state => ({ ...(state || {}) })) { ...state, routing: routerReducer() } + } else { + const routing = routerReducer(newState.routing, action) + if (routing !== newState.routing) { + newState = { + ...newState, + routing + } + } } - return reducers(routerReducer(newState, action), action) + return reducers(newState, action) } } diff --git a/test/Link.test.js b/test/Link.test.js index 821efa3..42d7290 100644 --- a/test/Link.test.js +++ b/test/Link.test.js @@ -6,7 +6,7 @@ import { setEnhancedRoutes } from '../src' import * as enhancers from '../src/enhancers' import { renderComponent, connect } from './test_helper' -describe('react-redux-saga-router Link', () => { +describe('Link', () => { it('dispatches replace', () => { const dispatch = sinon.spy() const component = renderComponent(Link, { dispatch, replace: '/hi' }) @@ -31,9 +31,9 @@ describe('react-redux-saga-router Link', () => { }) it('renders placeholder', () => { expect(() => { - renderComponent(ConnectLink, { to: '/hi' }, {}, true) + renderComponent(ConnectLink, { dispatch: () => null, to: '/hi' }, {}, true) }).throws('call connectLink with the connect function from react-redux to ' + - 'initialize Link (see https://github.com/cellog/react-redux-saga-router/issues/1)') + 'initialize Link (see https://github.com/cellog/ion-router/issues/1)') }) it('connectLink', () => { const spy1 = sinon.spy() @@ -56,14 +56,14 @@ describe('react-redux-saga-router Link', () => { it('dispatches actions when initialized', () => { const spy = sinon.spy() connectLink(connect) - const [component, , log] = renderComponent(ConnectLink, { to: '/hi', onClick: spy }, {}, true) + const [component, , log] = renderComponent(ConnectLink, { dispatch: () => null, to: '/hi', onClick: spy }, {}, true) component.find('a').trigger('click') expect(log).eqls([push('/hi')]) expect(spy.called).is.true }) it('errors (in dev) on href passed in', () => { connectLink(connect) - expect(() => renderComponent(ConnectLink, { href: '/hi' }, {}, true)) + expect(() => renderComponent(ConnectLink, { dispatch: () => null, href: '/hi' }, {}, true)) .throws('href should not be passed to Link, use "to," "replace" or "route" (passed "/hi")') }) describe('generates the correct path when route option is used', () => { @@ -150,4 +150,25 @@ describe('react-redux-saga-router Link', () => { expect(component.find('a').props('href')).eqls('/there/baby') }) }) + it('only valid props are passed to the a tag', () => { + const component = renderComponent(Link, { + ...[ + 'download', 'hrefLang', 'referrerPolicy', 'rel', 'target', 'type', + 'id', 'accessKey', 'className', 'contentEditable', 'contextMenu', 'dir', 'draggable', + 'hidden', 'itemID', 'itemProp', 'itemRef', 'itemScope', 'itemType', 'lang', + 'spellCheck', 'style', 'tabIndex', 'title' + ].reduce((coll, item) => ({ ...coll, [item]: {} }), {}), + 'data-hi': 'there', + foo: 'bar', + to: 'hi', + dispatch: () => null, + }) + expect(Object.keys(component.find('a').props())).eqls([ + 'href', 'onClick', 'download', 'hrefLang', 'referrerPolicy', 'rel', 'target', 'type', + 'id', 'accessKey', 'className', 'contentEditable', 'contextMenu', 'dir', 'draggable', + 'hidden', 'itemID', 'itemProp', 'itemRef', 'itemScope', 'itemType', 'lang', + 'spellCheck', 'style', 'tabIndex', 'title', + 'data-hi', 'children' + ]) + }) }) diff --git a/test/Route.test.js b/test/Route.test.js index 167b48c..d0f602a 100644 --- a/test/Route.test.js +++ b/test/Route.test.js @@ -6,7 +6,7 @@ import { setEnhancedRoutes } from '../src' import * as enhancers from '../src/enhancers' import { renderComponent, connect } from './test_helper' -describe('react-redux-saga-router Route', () => { +describe('Route', () => { const paramsFromState = state => ({ id: state.ensembleTypes.selectedEnsembleType ? (state.ensembleTypes.selectedEnsembleType === true ? 'new' : state.ensembleTypes.selectedEnsembleType) : diff --git a/test/Routes.test.jsx b/test/Routes.test.jsx index 69753cc..a9d2974 100644 --- a/test/Routes.test.jsx +++ b/test/Routes.test.jsx @@ -4,11 +4,11 @@ import * as actions from '../src/actions' import { setServer, onServer } from '../src' import { renderComponent, connect } from './test_helper' -describe('react-redux-saga-router Routes', () => { +describe('Routes', () => { let component, store, log // eslint-disable-line - function make(props = {}, Comp = ConnectedRoutes, state = {}) { + function make(props = {}, Comp = ConnectedRoutes, state = {}, mount = false) { connectRoutes(connect) - const info = renderComponent(Comp, props, state, true) + const info = renderComponent(Comp, props, state, true, false, mount) component = info[0] store = info[1] log = info[2] @@ -54,13 +54,14 @@ describe('react-redux-saga-router Routes', () => { } ) - make({ thing: true }, R) + make({ thing: true }, R, {}, true) component.props({ thing: false }) expect(log).eqls([ actions.batchRoutes([{ name: 'foo', path: '/bar' }]), actions.batchRemoveRoutes([{ name: 'foo', path: '/bar' }]) ]) + component.unmount() }) it('passes in routes from state', () => { const Thing = () =>
diff --git a/test/Toggle.test.js b/test/Toggle.test.js index 508d92d..74c7c09 100644 --- a/test/Toggle.test.js +++ b/test/Toggle.test.js @@ -186,35 +186,4 @@ describe('Toggle', () => { }) }) }) - describe('propTypes loading', () => { - let warn - beforeEach(() => { - connectToggle(connect) - warn = sinon.stub(console, 'warn') - }) - afterEach(() => { - warn.restore() - }) - it('loading proptype validation', () => { - const R = Toggle(() => true, () => true) - const F = () => null - const container = renderComponent(R, { // eslint-disable-line - loading: F, - component: F - }) - expect(warn.called).is.true - expect(warn.args[0]).eqls(['loading in Toggle:F should be loadingComponent for ion-router']) - }) - it('no error if componentMap is enabled', () => { - const R = Toggle(() => true, () => true, { - loadingComponent: 'foo' - }) - const F = () => null - const container = renderComponent(R, { // eslint-disable-line - loading: F, - component: F - }) - expect(warn.called).is.false - }) - }) }) diff --git a/test/actions.test.js b/test/actions.test.js index 9a4dc91..7ec85e0 100644 --- a/test/actions.test.js +++ b/test/actions.test.js @@ -2,7 +2,7 @@ import * as actions from '../src/actions' import * as types from '../src/types' -describe('react-redux-saga-router actions', () => { +describe('actions', () => { it('push', () => { expect(actions.push('/hi')).eqls({ type: types.ACTION, diff --git a/test/enhanced.test.js b/test/enhanced.test.js index e5f417e..e27440e 100644 --- a/test/enhanced.test.js +++ b/test/enhanced.test.js @@ -1,7 +1,7 @@ import RouteParser from 'route-parser' import * as enhance from '../src/enhancers' -describe('react-redux-saga-router enhanced route store', () => { +describe('enhanced route store', () => { it('fake', () => { expect(enhance.fake('hi')).eqls({}) }) diff --git a/test/helpers.test.js b/test/helpers.test.js index efb6b3b..7260a88 100644 --- a/test/helpers.test.js +++ b/test/helpers.test.js @@ -108,4 +108,76 @@ describe('helper functions', () => { ] }) }) + it('getStateUpdates', () => { + expect(helpers.getStateUpdates({ + state: { + a: 1, + b: 2, + c: 3, + }, + updateState: { + a: a => ({ type: 'a', a }), + b: b => ({ type: 'b', b }), + } + }, { + a: 2, + b: 2, + c: 5, + })).eqls([ + { type: 'a', a: 2 }, + ]) + }) + it('exitRoute', () => { + expect(helpers.exitRoute({ + routing: reducer(), + }, { + a: { + params: { + hi: 5 + }, + state: { + hi: 5, + }, + parent: 'b', + exitParams: { + hi: undefined + }, + stateFromParams: params => params, + updateState: { + hi: hi => ({ type: 'hi', hi }) + } + }, + b: { + params: { + there: 6 + }, + state: { + there: 6 + }, + parent: 'c', + exitParams: { + there: undefined + }, + stateFromParams: params => params, + updateState: { + there: there => ({ type: 'there', there }) + } + }, + c: { + params: { + booboo: 1 + }, + state: { + booboo: 1 + }, + exitParams: { + booboo: undefined + }, + stateFromParams: params => params, + updateState: { + booboo: booboo => ({ type: 'booboo', booboo }) + } + } + }, 'a')) + }) }) diff --git a/test/index.test.js b/test/index.test.js index 0ec4f7b..3225f1f 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -113,6 +113,7 @@ describe('ion-router', () => { parent: undefined, } }) + index.synchronousMakeRoutes([]) }) it('setServer', () => { expect(index.options.server).is.false @@ -129,7 +130,9 @@ describe('ion-router', () => { return state } const metareducer = index.routingReducer(fakeReducer) - expect(metareducer()).eqls(reducer()) + expect(metareducer()).eqls({ + routing: reducer() + }) expect(spy.called).is.true expect(spy.args[0]).eqls([undefined, undefined]) }) @@ -151,7 +154,14 @@ describe('ion-router', () => { }, undefined]) }) it('works even with no reducer', () => { - expect(index.routingReducer()()).eqls(reducer()) + expect(index.routingReducer()()).eqls({ + routing: reducer() + }) + }) + it('normal flow', () => { + const state = index.routingReducer()() + console.log(state) + expect(index.routingReducer()(state, { type: 'hi' })).eqls(state) }) }) describe('main', () => { diff --git a/test/middleware.test.js b/test/middleware.test.js index 0ef1445..cc70641 100644 --- a/test/middleware.test.js +++ b/test/middleware.test.js @@ -80,6 +80,7 @@ describe('middleware', () => { actions.route('/hi'), { type: 'foo' } ]) + createMiddleware() // coverage of default options }) it('calls an action handler', () => { const spy = sinon.spy() diff --git a/test/reducer.test.js b/test/reducer.test.js index 868750a..bf4cedd 100644 --- a/test/reducer.test.js +++ b/test/reducer.test.js @@ -1,7 +1,7 @@ import reducer from '../src/reducer' import * as actions from '../src/actions' -describe('ion-router reducer', () => { +describe('reducer', () => { it('ROUTE', () => { const state = { ...reducer() } expect(reducer(state, actions.route({ diff --git a/test/selectors.test.js b/test/selectors.test.js index 7a02752..c574e07 100644 --- a/test/selectors.test.js +++ b/test/selectors.test.js @@ -1,6 +1,6 @@ import * as selectors from '../src/selectors' -describe('react-redux-saga-router selectors', () => { +describe('selectors', () => { it('matchedRoute', () => { const state = { routing: { diff --git a/test/test_helper.jsx b/test/test_helper.jsx index 6389b69..21b4ddb 100644 --- a/test/test_helper.jsx +++ b/test/test_helper.jsx @@ -1,7 +1,4 @@ import React, { Component } from 'react' -import createSagaMiddleware, { runSaga } from 'redux-saga' -import * as effects from 'redux-saga/effects' -import * as utils from 'redux-saga/utils' import teaspoon from 'teaspoon' import { Provider, connect } from 'react-redux' import { createStore, combineReducers, applyMiddleware } from 'redux' @@ -9,122 +6,25 @@ import reducer from '../src/reducer' const fakeWeekReducer = (state = 1) => state -export const tests = Object.keys(utils.asEffect).reduce((effects, name) => ({ - ...effects, - [name]: effect => utils.is.notUndef(utils.asEffect[name](effect)) -}), { - isPromise: effect => utils.is.promise(effect), - isHelper: effect => utils.is.helper(effect), - isIterator: effect => utils.is.iterator(effect), - takeEvery: effect => utils.is.helper(effect), - takeLatest: effect => utils.is.helper(effect), - throttle: effect => utils.is.helper(effect), - takem: effect => utils.is.notUndef(utils.asEffect.take(effect)) && effect.TAKE.maybe, - apply: () => false, - spawn: effect => utils.is.notUndef(utils.asEffect.fork(effect)) && effect.FORK.detached, - exception: effect => effect instanceof Error, - endSaga: effect => Object.hasOwnProperty.call(effect, 'effect') && effect.effect === undefined, -}) - function sagaStore(state, reducers = { routing: reducer, week: fakeWeekReducer }, middleware = []) { const log = [] const logger = store => next => action => { // eslint-disable-line log.push(action) return next(action) } - const monitor = { - } - const sagaMiddleware = createSagaMiddleware({ sagaMonitor: monitor }) const store = createStore(combineReducers(reducers), - state, applyMiddleware(sagaMiddleware, ...middleware, logger)) + state, applyMiddleware(...middleware, logger)) return { log, store, - sagaMiddleware, - monitor, - } -} - -export const effectNames = - Object.keys(utils.asEffect).reduce((effects, name) => ({ - ...effects, - [name]: name, - }), { - isPromise: 'promise', - isHelper: 'helper', - isIterator: 'iterator', - takeEvery: 'takeEvery', - takeLatest: 'takeLatest', - throttle: 'throttle', - takem: 'takem', - apply: 'apply', - spawn: 'spawn', - exception: 'exception', - endSaga: 'endSaga', - }) - -export const effectName = effect => - effectNames[Object.keys(tests).map(test => tests[test](effect) && test).filter(t => t)[0]] - -export const toEffectAction = effect => ({ - type: effectName(effect), - effect -}) - -export const START = { type: '###@@@start' } - -export function testSaga(state = undefined, - reducers = { routing: reducer, week: fakeWeekReducer }) { - const { sagaMiddleware, store, monitor } = sagaStore(state, { - ...reducers, - routing: reducer - }) - return (sagas, test) => { - const cbs = [] - sagaMiddleware.run(function*() { - yield effects.take('###@@@start') - yield sagas.map(args => effects.fork(function *f() { - try { - yield effects.call(...args) - } catch (e) { - cbs.forEach(cb => cb(e)) - } - })) - }) - const vars = { - log: [] - } - monitor.effectTriggered = (info) => { - const effect = info.effect || info - if (effect && !effect.length) { - vars.log.push(toEffectAction(effect)) - cbs.forEach((cb) => { - cb(toEffectAction(effect)) - }) - } - } - runSaga(test(), { - subscribe: (callback) => { - vars.log.forEach(effect => callback(effect)) - cbs.push(callback) - return () => { - cbs.splice(cbs.indexOf(callback), 1) - } - }, - dispatch: (output) => { - store.dispatch(output) - }, - getState: () => vars.log - }) } } function renderComponent(ComponentClass, props = {}, state = {}, returnStore = false, - sagaStore = false) { + sagaStore = false, intoDocument = false) { let store let log - const sagaMiddleware = createSagaMiddleware() if (!sagaStore) { log = [] const logger = store => next => action => { // eslint-disable-line @@ -133,7 +33,7 @@ function renderComponent(ComponentClass, props = {}, state = {}, returnStore = f } store = createStore(combineReducers({ routing: reducer, week: fakeWeekReducer }), - state, applyMiddleware(logger, sagaMiddleware)) + state, applyMiddleware(logger)) } class Tester extends Component { @@ -156,13 +56,10 @@ function renderComponent(ComponentClass, props = {}, state = {}, returnStore = f } const componentInstance = teaspoon( - ).render() + ).render(intoDocument) const ret = componentInstance if (returnStore) { - if (sagaStore) { - return [ret, sagaStore.store, sagaStore.log, sagaStore.sagaMiddleware] - } - return [ret, store, log, sagaMiddleware] + return [ret, store, log] } return ret }