From 67f08d6a6a7f9cfa09ee8aea713b90eae056dc5b Mon Sep 17 00:00:00 2001 From: Giancarlo Anemone Date: Tue, 5 Dec 2017 11:52:27 -0800 Subject: [PATCH] Export mock wrapped with provider https://github.com/fusionjs/fusion-plugin-rpc-redux-react/pull/21 --- README.md | 21 +- package.json | 6 +- src/__tests__/__browser__/index.js | 599 ++++++++++++++++++++++++++++- src/__tests__/__node__/index.js | 13 + src/index.js | 5 +- yarn.lock | 80 +++- 6 files changed, 713 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 44f657a..f01339a 100644 --- a/README.md +++ b/README.md @@ -138,7 +138,7 @@ In addition to defining action/reducer pairs, the `incrementReactor` HOC also ma Reactors typically need to be used in conjunction with `connect` from `react-redux`, in order to map state to React. -Below is an example of consuming the state and RPC methods that are made available from the Redux store and the RPC plugin. +Below is an example of consuming the state and RPC methods that are made available from the Redux store and the RPC plugin. ```js // src/components/example.js @@ -171,7 +171,7 @@ export default hoc(Example); Redux colocates all valid actions in a respective "slot" in the state tree, and colocates the structuring of the state tree via helpers such as `combineReducers`. This means that a reducer can be unit tested by simply calling the reducer with one of the valid actions, without having any effect on any other state that might exist in the app. The downside is that if an action needs to modify multiple "slots" in the state tree, it can be tedious to find all transformations pertaining to any given action. -Another point worth mentioning is that with traditional reducers, it's possible to refactor the state tree in such a way that doesn't make any changes to reducers or components (albeit it does require changing the reducer composition chain as well as all relevant `mapStateToProps` functions). +Another point worth mentioning is that with traditional reducers, it's possible to refactor the state tree in such a way that doesn't make any changes to reducers or components (albeit it does require changing the reducer composition chain as well as all relevant `mapStateToProps` functions). Reactors, on the other hand, colocate a single reducer to a single action, so all state transformations pertaining to any given action are handled by a single function. This comes at the cost of flexibility: it's no longer possible to refactor the shape of the state tree without changing every affectd reducer, and it's also possible to affect unrelated parts of the state tree, for example missing properties due to an overly conservative object assignment. @@ -206,3 +206,20 @@ const NewComponent = withRPCReactor('rpcId', { transformParams(params) => ({}), // optional })(Component); ``` + +### Testing + +The package also exports a mock rpc plugin which can be useful for testing. For example: + +```js +import {mock as MockRPC} from 'fusion-plugin-rpc-redux-react'; +app.plugin(mock, { + handlers: { + getUser: (args) => { + return { + mock: 'data', + } + } + } +}); +``` diff --git a/package.json b/package.json index fb4d78a..82b2d3f 100644 --- a/package.json +++ b/package.json @@ -46,11 +46,15 @@ "fusion-core": "^0.2.5", "fusion-react": "^0.1.10", "fusion-react-async": "0.1.4", + "fusion-test-utils": "^0.2.1", "nyc": "^11.3.0", - "prettier": "1.8.2", + "prettier": "1.9.0", "react": "^16.2.0", "react-dom": "16.2.0", + "react-redux": "^5.0.6", "react-test-renderer": "^16.2.0", + "redux": "^3.7.2", + "redux-reactors": "^1.0.3", "tape-cup": "^4.7.1", "unitest": "^1.1.0" }, diff --git a/src/__tests__/__browser__/index.js b/src/__tests__/__browser__/index.js index b4b7e50..6794a29 100644 --- a/src/__tests__/__browser__/index.js +++ b/src/__tests__/__browser__/index.js @@ -1,5 +1,602 @@ +/* eslint-env browser */ import test from 'tape-cup'; +import Plugin from '../../plugin'; +import React from 'react'; +import {withRPCRedux, withRPCReactor} from '../../hoc'; +import {prepared} from 'fusion-react-async'; +import {createStore} from 'redux'; +import {Provider, connect} from 'react-redux'; +import {compose} from 'redux'; +import App from 'fusion-react'; +import {render} from 'fusion-test-utils'; +import {mock as RPCPluginMock} from '../../index'; +import {reactorEnhancer} from 'redux-reactors'; -test('noop', t => { +test('browser plugin integration test withRPCRedux', async t => { + const EventEmitter = { + of() {}, + }; + const fetch = (url, options) => { + t.equal(url, '/api/test', 'fetches to expected url'); + t.deepLooseEqual( + JSON.parse(options.body), + {hello: 'world'}, + 'sends correct body' + ); + t.equal(options.method, 'POST', 'makes POST request'); + return Promise.resolve( + new Response( + JSON.stringify({ + status: 'success', + data: { + a: 'b', + }, + }) + ) + ); + }; + + const expectedActions = [ + {type: '@@redux/INIT'}, + {type: 'TEST_START', payload: {hello: 'world'}}, + {type: 'TEST_SUCCESS', payload: {a: 'b'}}, + ]; + const store = createStore((state, action) => { + t.deepLooseEqual( + action, + expectedActions.shift(), + 'dispatches expected action' + ); + return {}; + }); + + const Component = props => { + t.equal(typeof props.test, 'function', 'passes correct prop to component'); + return React.createElement('span', null, 'hello world'); + }; + + const withTest = withRPCRedux('test')( + prepared(props => props.test({hello: 'world'}))(Component) + ); + + const element = React.createElement( + Provider, + {store}, + React.createElement(withTest) + ); + const app = new App(element); + app.plugin(Plugin, {EventEmitter, fetch}); + await render(app, '/'); + t.equal(expectedActions.length, 0, 'dispatches all actions'); + t.end(); +}); + +test('browser plugin integration test withRPCRedux - failure', async t => { + const EventEmitter = { + of() {}, + }; + const fetch = (url, options) => { + t.equal(url, '/api/test', 'fetches to expected url'); + t.deepLooseEqual( + JSON.parse(options.body), + {hello: 'world'}, + 'sends correct body' + ); + t.equal(options.method, 'POST', 'makes POST request'); + return Promise.resolve( + new Response( + JSON.stringify({ + status: 'failure', + data: { + message: 'message', + code: 'code', + meta: {hello: 'world'}, + }, + }) + ) + ); + }; + + const expectedActions = [ + {type: '@@redux/INIT'}, + {type: 'TEST_START', payload: {hello: 'world'}}, + { + type: 'TEST_FAILURE', + payload: {message: 'message', code: 'code', meta: {hello: 'world'}}, + }, + ]; + const store = createStore((state, action) => { + t.deepLooseEqual( + action, + expectedActions.shift(), + 'dispatches expected action' + ); + return {}; + }); + + const Component = props => { + t.equal(typeof props.test, 'function', 'passes correct prop to component'); + return React.createElement('span', null, 'hello world'); + }; + + const withTest = withRPCRedux('test')( + prepared(props => props.test({hello: 'world'}))(Component) + ); + + const element = React.createElement( + Provider, + {store}, + React.createElement(withTest) + ); + const app = new App(element); + app.plugin(Plugin, {EventEmitter, fetch}); + await render(app, '/'); + t.equal(expectedActions.length, 0, 'dispatches all actions'); + t.end(); +}); + +test('browser mock integration test withRPCRedux', async t => { + const handlers = { + test(args) { + t.deepLooseEqual( + args, + {hello: 'world'}, + 'passes correct args to handler' + ); + return Promise.resolve({a: 'b'}); + }, + }; + const expectedActions = [ + {type: '@@redux/INIT'}, + {type: 'TEST_START', payload: {hello: 'world'}}, + {type: 'TEST_SUCCESS', payload: {a: 'b'}}, + ]; + const store = createStore((state, action) => { + t.deepLooseEqual( + action, + expectedActions.shift(), + 'dispatches expected actions' + ); + return {}; + }); + + const Component = props => { + t.equal(typeof props.test, 'function', 'passes correct prop to component'); + return React.createElement('span', null, 'hello world'); + }; + + const withTest = withRPCRedux('test')( + prepared(props => props.test({hello: 'world'}))(Component) + ); + + const element = React.createElement( + Provider, + {store}, + React.createElement(withTest) + ); + const app = new App(element); + app.plugin(RPCPluginMock, {handlers}); + await render(app, '/'); + t.equal(expectedActions.length, 0, 'dispatches all actions'); + t.end(); +}); + +test('browser mock integration test withRPCRedux - failure', async t => { + const e = new Error('message'); + e.code = 'code'; + e.meta = {hello: 'world'}; + const handlers = { + test(args) { + t.deepLooseEqual( + args, + {hello: 'world'}, + 'passes correct args to handler' + ); + return Promise.reject(e); + }, + }; + const expectedActions = [ + {type: '@@redux/INIT'}, + {type: 'TEST_START', payload: {hello: 'world'}}, + { + type: 'TEST_FAILURE', + payload: {message: 'message', code: 'code', meta: {hello: 'world'}}, + }, + ]; + const store = createStore((state, action) => { + t.deepLooseEqual( + action, + expectedActions.shift(), + 'dispatches expected actions' + ); + return {}; + }); + + const Component = props => { + t.equal(typeof props.test, 'function', 'passes correct prop to component'); + return React.createElement('span', null, 'hello world'); + }; + + const withTest = withRPCRedux('test')( + prepared(props => props.test({hello: 'world'}))(Component) + ); + + const element = React.createElement( + Provider, + {store}, + React.createElement(withTest) + ); + const app = new App(element); + app.plugin(RPCPluginMock, {handlers}); + await render(app, '/'); + t.equal(expectedActions.length, 0, 'dispatches all actions'); + t.end(); +}); + +test('browser plugin integration test withRPCReactor', async t => { + const EventEmitter = { + of() {}, + }; + const fetch = (url, options) => { + t.equal(url, '/api/test', 'fetches to expected url'); + t.deepLooseEqual( + JSON.parse(options.body), + {hello: 'world'}, + 'sends correct body' + ); + t.equal(options.method, 'POST', 'makes POST request'); + return Promise.resolve( + new Response( + JSON.stringify({ + status: 'success', + data: { + a: 'b', + }, + }) + ) + ); + }; + + const expectedActions = [{type: '@@redux/INIT'}]; + const store = createStore( + (state, action) => { + t.deepLooseEqual( + action, + expectedActions.shift(), + 'dispatches expected action' + ); + return {}; + }, + {}, + reactorEnhancer + ); + + const Component = props => { + t.equal(typeof props.test, 'function', 'passes correct prop to component'); + t.equal(props.loading, false); + return React.createElement('span', null, 'hello world'); + }; + + const flags = {start: false, success: false}; + const hoc = compose( + withRPCReactor('test', { + start: (state, action) => { + t.equal(action.type, 'TEST_START', 'dispatches start action'); + t.deepLooseEqual( + action.payload, + {hello: 'world'}, + 'dispatches start with correct payload' + ); + flags.start = true; + return { + loading: true, + }; + }, + success: (state, action) => { + flags.success = true; + t.equal(action.type, 'TEST_SUCCESS', 'dispatches success action'); + t.deepLooseEqual( + action.payload, + {a: 'b'}, + 'dispatches success with correct payload' + ); + return { + loading: false, + }; + }, + failure: () => { + t.fail('should not call failure'); + return {}; + }, + }), + prepared(props => props.test({hello: 'world'})), + connect(s => s) + ); + + const element = React.createElement( + Provider, + {store}, + React.createElement(hoc(Component)) + ); + const app = new App(element); + app.plugin(Plugin, {EventEmitter, fetch}); + await render(app, '/'); + t.equal(expectedActions.length, 0, 'dispatches all actions'); + t.equal(flags.start, true, 'dispatches start action'); + t.equal(flags.success, true, 'dispatches success action'); + t.end(); +}); + +test('browser mock plugin integration test withRPCReactor', async t => { + const handlers = { + test(args) { + t.deepLooseEqual( + args, + {hello: 'world'}, + 'passes correct args to handler' + ); + return Promise.resolve({a: 'b'}); + }, + }; + + const expectedActions = [{type: '@@redux/INIT'}]; + const store = createStore( + (state, action) => { + t.deepLooseEqual( + action, + expectedActions.shift(), + 'dispatches expected action' + ); + return {}; + }, + {}, + reactorEnhancer + ); + + const Component = props => { + t.equal(typeof props.test, 'function', 'passes correct prop to component'); + t.equal(props.loading, false); + return React.createElement('span', null, 'hello world'); + }; + + const flags = {start: false, success: false}; + const hoc = compose( + withRPCReactor('test', { + start: (state, action) => { + t.equal(action.type, 'TEST_START', 'dispatches start action'); + t.deepLooseEqual( + action.payload, + {hello: 'world'}, + 'dispatches start with correct payload' + ); + flags.start = true; + return { + loading: true, + }; + }, + success: (state, action) => { + flags.success = true; + t.equal(action.type, 'TEST_SUCCESS', 'dispatches success action'); + t.deepLooseEqual( + action.payload, + {a: 'b'}, + 'dispatches success with correct payload' + ); + return { + loading: false, + }; + }, + failure: () => { + t.fail('should not call failure'); + return {}; + }, + }), + prepared(props => props.test({hello: 'world'})), + connect(s => s) + ); + + const element = React.createElement( + Provider, + {store}, + React.createElement(hoc(Component)) + ); + const app = new App(element); + app.plugin(RPCPluginMock, {handlers}); + await render(app, '/'); + t.equal(expectedActions.length, 0, 'dispatches all actions'); + t.equal(flags.start, true, 'dispatches start action'); + t.equal(flags.success, true, 'dispatches success action'); + t.end(); +}); + +test('browser plugin integration test withRPCReactor - failure', async t => { + const e = new Error('Some failure'); + e.code = 'ERR_CODE'; + e.meta = {error: 'meta'}; + const handlers = { + test(args) { + t.deepLooseEqual( + args, + {hello: 'world'}, + 'passes correct args to handler' + ); + return Promise.reject(e); + }, + }; + + const expectedActions = [{type: '@@redux/INIT'}]; + const store = createStore( + (state, action) => { + t.deepLooseEqual( + action, + expectedActions.shift(), + 'dispatches expected action' + ); + return {}; + }, + {}, + reactorEnhancer + ); + + const Component = props => { + t.equal(typeof props.test, 'function', 'passes correct prop to component'); + t.equal(props.loading, false); + return React.createElement('span', null, 'hello world'); + }; + + const flags = {start: false, failure: false}; + const hoc = compose( + withRPCReactor('test', { + start: (state, action) => { + t.equal(action.type, 'TEST_START', 'dispatches start action'); + t.deepLooseEqual( + action.payload, + {hello: 'world'}, + 'dispatches start with correct payload' + ); + flags.start = true; + return { + loading: true, + }; + }, + success: () => { + t.fail('should not call success'); + return {}; + }, + failure: (state, action) => { + flags.failure = true; + t.equal(action.type, 'TEST_FAILURE', 'dispatches failure action'); + t.deepLooseEqual( + action.payload, + { + message: e.message, + code: e.code, + meta: e.meta, + }, + 'dispatches failure with correct payload' + ); + return { + loading: false, + }; + }, + }), + prepared(props => props.test({hello: 'world'})), + connect(s => s) + ); + + const element = React.createElement( + Provider, + {store}, + React.createElement(hoc(Component)) + ); + const app = new App(element); + app.plugin(RPCPluginMock, {handlers}); + await render(app, '/'); + t.equal(expectedActions.length, 0, 'dispatches all actions'); + t.equal(flags.start, true, 'dispatches start action'); + t.equal(flags.failure, true, 'dispatches failure action'); + t.end(); +}); + +test('browser plugin integration test withRPCReactor - failure', async t => { + const e = new Error('Some failure'); + e.code = 'ERR_CODE'; + e.meta = {error: 'meta'}; + const EventEmitter = { + of() {}, + }; + const fetch = (url, options) => { + t.equal(url, '/api/test', 'fetches to expected url'); + t.deepLooseEqual( + JSON.parse(options.body), + {hello: 'world'}, + 'sends correct body' + ); + t.equal(options.method, 'POST', 'makes POST request'); + return Promise.resolve( + new Response( + JSON.stringify({ + status: 'failure', + data: { + message: e.message, + code: e.code, + meta: e.meta, + }, + }) + ) + ); + }; + + const expectedActions = [{type: '@@redux/INIT'}]; + const store = createStore( + (state, action) => { + t.deepLooseEqual( + action, + expectedActions.shift(), + 'dispatches expected action' + ); + return {}; + }, + {}, + reactorEnhancer + ); + + const Component = props => { + t.equal(typeof props.test, 'function', 'passes correct prop to component'); + t.equal(props.loading, false); + return React.createElement('span', null, 'hello world'); + }; + + const flags = {start: false, failure: false}; + const hoc = compose( + withRPCReactor('test', { + start: (state, action) => { + t.equal(action.type, 'TEST_START', 'dispatches start action'); + t.deepLooseEqual( + action.payload, + {hello: 'world'}, + 'dispatches start with correct payload' + ); + flags.start = true; + return { + loading: true, + }; + }, + success: () => { + t.fail('should not call success'); + return {}; + }, + failure: (state, action) => { + flags.failure = true; + t.equal(action.type, 'TEST_FAILURE', 'dispatches failure action'); + t.deepLooseEqual( + action.payload, + { + message: e.message, + code: e.code, + meta: e.meta, + }, + 'dispatches failure with correct payload' + ); + return { + loading: false, + }; + }, + }), + prepared(props => props.test({hello: 'world'})), + connect(s => s) + ); + + const element = React.createElement( + Provider, + {store}, + React.createElement(hoc(Component)) + ); + const app = new App(element); + app.plugin(Plugin, {EventEmitter, fetch}); + await render(app, '/'); + t.equal(expectedActions.length, 0, 'dispatches all actions'); + t.equal(flags.start, true, 'dispatches start action'); + t.equal(flags.failure, true, 'dispatches failure action'); t.end(); }); diff --git a/src/__tests__/__node__/index.js b/src/__tests__/__node__/index.js index b7fca73..6081aa4 100644 --- a/src/__tests__/__node__/index.js +++ b/src/__tests__/__node__/index.js @@ -1,6 +1,7 @@ import React from 'react'; import test from 'tape-cup'; import Plugin from '../../plugin'; +import {mock} from '../../index'; import ShallowRenderer from 'react-test-renderer/shallow'; import {withRPCRedux, withRPCReactor} from '../../hoc'; @@ -16,6 +17,18 @@ test('plugin', t => { t.end(); }); +test('mock plugin', t => { + t.equals(typeof mock, 'function'); + const handlers = {test() {}}; + const EventEmitter = { + of() {}, + }; + const RPCRedux = mock({handlers, EventEmitter}); + const mockCtx = {headers: {}}; + t.equal(typeof RPCRedux.of(mockCtx).request, 'function'); + t.end(); +}); + test('withRPCRedux hoc', t => { function Test() {} t.equals(typeof withRPCRedux, 'function'); diff --git a/src/index.js b/src/index.js index 30595b7..6cf1575 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,9 @@ import plugin from './plugin'; +import {ProviderPlugin} from 'fusion-react'; export {createRPCReducer} from 'fusion-rpc-redux'; -export {mock} from 'fusion-plugin-rpc'; +import {mock as RPCMock} from 'fusion-plugin-rpc'; export {withRPCRedux, withRPCReactor} from './hoc'; export default plugin; + +export const mock = ProviderPlugin.create('rpc', RPCMock); diff --git a/yarn.lock b/yarn.lock index 5d23995..5acd121 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2118,6 +2118,13 @@ fusion-rpc-redux@^0.3.3: dependencies: redux-reactors "^1.0.3" +fusion-test-utils@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fusion-test-utils/-/fusion-test-utils-0.2.1.tgz#b8bde8bf0facaf3b9038d0dfb4bd5e96a30b7ed4" + dependencies: + koa "^2.4.1" + node-mocks-http "^1.6.6" + gauge@~2.7.3: version "2.7.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" @@ -2353,6 +2360,10 @@ hoek@4.x.x: version "4.2.0" resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d" +hoist-non-react-statics@^2.2.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0" + home-path@^1.0.1: version "1.0.5" resolved "https://registry.yarnpkg.com/home-path/-/home-path-1.0.5.tgz#788b29815b12d53bacf575648476e6f9041d133f" @@ -2479,7 +2490,7 @@ interpret@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.4.tgz#820cdd588b868ffb191a809506d6c9c8f212b1b0" -invariant@^2.2.0, invariant@^2.2.2: +invariant@^2.0.0, invariant@^2.2.0, invariant@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" dependencies: @@ -2902,6 +2913,35 @@ koa@^2.3.0: type-is "^1.5.5" vary "^1.0.0" +koa@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/koa/-/koa-2.4.1.tgz#d449cfb970a7e9da571f699eda40bb9e32eb1484" + dependencies: + accepts "^1.2.2" + content-disposition "~0.5.0" + content-type "^1.0.0" + cookies "~0.7.0" + debug "*" + delegates "^1.0.0" + depd "^1.1.0" + destroy "^1.0.3" + error-inject "~1.0.0" + escape-html "~1.0.1" + fresh "^0.5.2" + http-assert "^1.1.0" + http-errors "^1.2.8" + is-generator-function "^1.0.3" + koa-compose "^4.0.0" + koa-convert "^1.2.0" + koa-is-json "^1.0.0" + mime-types "^2.0.7" + on-finished "^2.1.0" + only "0.0.2" + parseurl "^1.3.0" + statuses "^1.2.0" + type-is "^1.5.5" + vary "^1.0.0" + lazy-cache@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" @@ -2963,7 +3003,11 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" -lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0: +lodash-es@^4.2.0, lodash-es@^4.2.1: + version "4.17.4" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.4.tgz#dcc1d7552e150a0640073ba9cb31d70f032950e7" + +lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" @@ -3624,9 +3668,9 @@ preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" -prettier@1.8.2: - version "1.8.2" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.8.2.tgz#bff83e7fd573933c607875e5ba3abbdffb96aeb8" +prettier@1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.9.0.tgz#1a7205bdb6126b30cf8c0a7b2b86997162e1ee3e" pretty-bytes@^1.0.2: version "1.0.4" @@ -3664,7 +3708,7 @@ promise@^7.1.1: dependencies: asap "~2.0.3" -prop-types@^15.5.8, prop-types@^15.6.0: +prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.0: version "15.6.0" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856" dependencies: @@ -3765,6 +3809,17 @@ react-dom@16.2.0: object-assign "^4.1.1" prop-types "^15.6.0" +react-redux@^5.0.6: + version "5.0.6" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.6.tgz#23ed3a4f986359d68b5212eaaa681e60d6574946" + dependencies: + hoist-non-react-statics "^2.2.1" + invariant "^2.0.0" + lodash "^4.2.0" + lodash-es "^4.2.0" + loose-envify "^1.1.0" + prop-types "^15.5.10" + react-test-renderer@^16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.2.0.tgz#bddf259a6b8fcd8555f012afc8eacc238872a211" @@ -3853,6 +3908,15 @@ redux-reactors@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/redux-reactors/-/redux-reactors-1.0.3.tgz#b04d3465bf045549b7b48c45eb8481b679a30e62" +redux@^3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/redux/-/redux-3.7.2.tgz#06b73123215901d25d065be342eb026bc1c8537b" + dependencies: + lodash "^4.2.1" + lodash-es "^4.2.1" + loose-envify "^1.1.0" + symbol-observable "^1.0.3" + regenerate-unicode-properties@^5.1.1: version "5.1.3" resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-5.1.3.tgz#54f5891543468f36f2274b67c6bc4c033c27b308" @@ -4361,6 +4425,10 @@ supports-color@^4.0.0, supports-color@^4.2.1: dependencies: has-flag "^2.0.0" +symbol-observable@^1.0.3: + version "1.1.0" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.1.0.tgz#5c68fd8d54115d9dfb72a84720549222e8db9b32" + table@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/table/-/table-4.0.2.tgz#a33447375391e766ad34d3486e6e2aedc84d2e36"