diff --git a/.babelrc.js b/.babelrc.js index 4dd6e63..b348cb2 100644 --- a/.babelrc.js +++ b/.babelrc.js @@ -9,6 +9,7 @@ module.exports = { "@babel/flow" ], plugins: [ + "@babel/proposal-class-properties", "@babel/proposal-object-rest-spread", "transform-imports", "@babel/transform-modules-commonjs" diff --git a/README.md b/README.md index b3f4b56..ed9d7f0 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ need to handle the addition of dynamic reducers! Here's what your Redux store creation will look like: ```js -import { combineReducers, createStore, applyMiddleware } from 'redux'; +import { compose, combineReducers, createStore, applyMiddleware } from 'redux'; import thunk from 'redux-thunk'; import { combineAsyncReducers, configureRags } from 'redux-rags'; diff --git a/package-lock.json b/package-lock.json index 18af2c6..b8b1ba0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "redux-rags", - "version": "1.2.0-beta1", + "version": "1.2.1-beta2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -503,6 +503,19 @@ "@babel/types": "7.3.0" } }, + "@babel/helper-create-class-features-plugin": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.3.0.tgz", + "integrity": "sha512-DUsQNS2CGLZZ7I3W3fvh0YpPDd6BuWJlDl+qmZZpABZHza2ErE3LxtEzLJFHFC1ZwtlAXvHhbFYbtM5o5B0WBw==", + "dev": true, + "requires": { + "@babel/helper-function-name": "7.1.0", + "@babel/helper-member-expression-to-functions": "7.0.0", + "@babel/helper-optimise-call-expression": "7.0.0", + "@babel/helper-plugin-utils": "7.0.0", + "@babel/helper-replace-supers": "7.2.3" + } + }, "@babel/helper-define-map": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.1.0.tgz", @@ -741,6 +754,16 @@ "@babel/plugin-syntax-async-generators": "7.2.0" } }, + "@babel/plugin-proposal-class-properties": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.3.0.tgz", + "integrity": "sha512-wNHxLkEKTQ2ay0tnsam2z7fGZUi+05ziDJflEt3AZTP3oXLKHJp9HqhfroB/vdMvt3sda9fAbq7FsG8QPDrZBg==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "7.3.0", + "@babel/helper-plugin-utils": "7.0.0" + } + }, "@babel/plugin-proposal-json-strings": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz", @@ -7253,6 +7276,12 @@ "symbol-observable": "1.2.0" } }, + "redux-thunk": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz", + "integrity": "sha512-km6dclyFnmcvxhAcrQV2AkZmPQjzPDjgVlQtR0EQjxZPyJ0BnMf3in1ryuR8A2qU0HldVRfxYXbFSKlI3N7Slw==", + "dev": true + }, "regenerate": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", diff --git a/package.json b/package.json index 4413610..82d3949 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "redux-rags", - "version": "1.2.0", + "version": "1.2.1", "description": "Redux Reducers, Actions, and Getters. Simplified!", "main": "build/index.js", "scripts": { @@ -32,6 +32,7 @@ "devDependencies": { "@babel/cli": "^7.2.3", "@babel/core": "^7.2.2", + "@babel/plugin-proposal-class-properties": "^7.3.0", "@babel/plugin-proposal-object-rest-spread": "^7.3.1", "@babel/plugin-transform-modules-commonjs": "^7.2.0", "@babel/preset-env": "^7.3.1", @@ -49,6 +50,7 @@ "flow-bin": "^0.86.0", "jest": "^23.6.0", "redux": "^4.0.1", + "redux-thunk": "^2.3.0", "regenerator-runtime": "^0.13.1" } } diff --git a/src/combineAsyncReducers.js b/src/combineAsyncReducers.js index 07a2931..98a8555 100644 --- a/src/combineAsyncReducers.js +++ b/src/combineAsyncReducers.js @@ -10,12 +10,11 @@ const recursivelyCombineAsyncReducers = function (combineReducers: Function, asy for (let prop of Object.getOwnPropertyNames(asyncReducers)) { const subreducer = asyncReducers[prop]; if (typeof subreducer === 'object') { - recursivelyCombineAsyncReducers(combineReducers, subreducer); + reducers[prop] = recursivelyCombineAsyncReducers(combineReducers, subreducer); } else { reducers[prop] = subreducer; } } - return combineReducers(reducers); }; diff --git a/src/factory.js b/src/factory.js index 5110b37..3f6cd09 100644 --- a/src/factory.js +++ b/src/factory.js @@ -77,8 +77,8 @@ const createFactory = (injectReducer: Function) => >(config: const { name = '', load, - getInStore, loadOnlyOnce, + getInStore, getInitialState, update, partialReducer, @@ -86,16 +86,16 @@ const createFactory = (injectReducer: Function) => >(config: generatedCount += 1; const safeDataName = `${name}/${generatedCount}`; - const Getters = new function () { - const wrappedGetInitialState: () => BoilerState = createGetInitialState(getInitialState); - this.getInitialState = wrappedGetInitialState; + const wrappedGetInitialState: () => BoilerState = createGetInitialState(getInitialState); + class Getters { + static getInitialState = wrappedGetInitialState; - this.get = getInStore || + static get = getInStore || ((reduxStore: Object): BoilerState => - reduxStore[prefix][safeDataName] || this.getInitialState()); + reduxStore[prefix][safeDataName] || Getters.getInitialState()); - this.getData = (reduxStore: Object): $PropertyType, 'data'> => { - const state = this.get(reduxStore); + static getData = (reduxStore: Object): $PropertyType, 'data'> => { + const state = Getters.get(reduxStore); if (!state.hasOwnProperty('data')) { warning(`redux-rags: getData failed to find the property \'data\' on the object returned by Getters.get. This is likely caused by providing an incorrect 'getInStore' configuration option.`); @@ -103,8 +103,8 @@ const createFactory = (injectReducer: Function) => >(config: return state.data; }; - this.getMeta = (reduxStore: Object): $PropertyType, 'meta'> => { - const state = this.get(reduxStore); + static getMeta = (reduxStore: Object): $PropertyType, 'meta'> => { + const state = Getters.get(reduxStore); if (!state.hasOwnProperty('meta')) { warning(`redux-rags: getData failed to find the property \'meta\' on the object returned by Getters.get. This is likely caused by providing an incorrect 'getInStore' configuration option.`); @@ -112,11 +112,11 @@ const createFactory = (injectReducer: Function) => >(config: return state.meta; }; - this.getIsLoading = (reduxStore: Object) => { - const meta = this.getMeta(reduxStore); + static getIsLoading = (reduxStore: Object) => { + const meta = Getters.getMeta(reduxStore); return meta && meta.loading; }; - }; + } const BEGIN_LOADING = getLoadingType(name); const END_LOADING = getEndLoadingType(name); @@ -124,50 +124,50 @@ const createFactory = (injectReducer: Function) => >(config: const UPDATE_DATA = getUpdateType(name); const RESET = getResetType(name); - const Actions = new function(){ - this.beginLoading = () => ({ + class Actions { + static beginLoading = () => ({ type: BEGIN_LOADING, payload: null, }); - this.endLoading = () => ({ + static endLoading = () => ({ type: END_LOADING, payload: null, }); - this.reset = () => ({ + static reset = () => ({ type: RESET, payload: null, }); - this.errors = (errors: Object | String) => ({ + static errors = (errors: Object | String) => ({ type: ERRORS, payload: errors, }); - this.clearErrors = () => ({ + static clearErrors = () => ({ type: ERRORS, payload: null, }); - this.updateData = (data: ?T) => ({ + static updateData = (data: ?T) => ({ type: UPDATE_DATA, payload: data, }); - this.update = (...args: *) => async (dispatch, getState: () => Object) => { + static update = (...args: *) => async (dispatch, getState: () => Object) => { if (!update || typeof update !== 'function') { return; } try { const manipulated = await Promise.resolve(update(Getters.getData(getState()), ...args)); - dispatch(this.updateData(manipulated)); + dispatch(Actions.updateData(manipulated)); } catch (err) { - dispatch(this.errors(err)); + dispatch(Actions.errors(err)); } }; - this.load = (...args: G) => async ( + static load = (...args: G) => async ( dispatch: Dispatch, getState: () => Object ): Promise => { @@ -180,18 +180,18 @@ const createFactory = (injectReducer: Function) => >(config: return state.data; } } - dispatch(this.beginLoading()); + dispatch(Actions.beginLoading()); try { const data = await Promise.resolve(load(...args)); - dispatch(this.updateData(data)); + dispatch(Actions.updateData(data)); return data; } catch (err) { - dispatch(this.errors(err)); - dispatch(this.endLoading()); + dispatch(Actions.errors(err)); + dispatch(Actions.endLoading()); return null; } }; - }; + } type Interpret = ((...Iterable) => R) => R; type ExtractReturn = $Call; @@ -202,10 +202,10 @@ const createFactory = (injectReducer: Function) => >(config: | ExtractReturn | ExtractReturn | ExtractReturn; - const Subreducer = new function() { - this.partialReducer = partialReducer; + class Subreducer { + static partialReducer = partialReducer; - this.subreduce = ( + static subreduce = ( state?: BoilerState = Getters.getInitialState(), action?: GeneratedAction | * // Support other action types ) => { @@ -235,13 +235,13 @@ const createFactory = (injectReducer: Function) => >(config: case RESET: return (Getters.getInitialState(): BoilerState); default: - if (typeof this.partialReducer === 'function') { - return {...state, ...(this.partialReducer(state, action) || {})}; + if (typeof Subreducer.partialReducer === 'function') { + return {...state, ...(Subreducer.partialReducer(state, action) || {})}; } return state; } }; - }; + } if (!getInStore) { injectReducer([prefix, safeDataName], Subreducer.subreduce); diff --git a/src/factoryMap.js b/src/factoryMap.js index 532ce84..09fea99 100644 --- a/src/factoryMap.js +++ b/src/factoryMap.js @@ -34,40 +34,40 @@ let generatedCount = 0; const convertArgsToString = (...args) => JSON.stringify(args); const createFactoryMap = (injectReducer: Function) => { - const factory = createFactory(injectReducer); - return >(config: ConfigType): ReturnType => { - const { name = '', load, getInitialState } = config; - generatedCount += 1; + const factory = createFactory(injectReducer); + return >(config: ConfigType): ReturnType => { + const { name = '', load, getInitialState } = config; + generatedCount += 1; - const safeDataName = `${name}/${generatedCount}`; - const mapArgsToGenerated = {}; - const Getters = new function() { - const _getInitialStateForKey: () => BoilerState = createGetInitialState(getInitialState); + const safeDataName = `${name}/${generatedCount}`; + const mapArgsToGenerated = {}; + class Getters { + static _getInitialStateForKey: () => BoilerState = createGetInitialState(getInitialState); - this.get = (reduxStore: Object): MapState => + static get = (reduxStore: Object): MapState => reduxStore[prefix] && reduxStore[prefix][safeDataName]; - this.getWithArgs = (reduxStore, ...args) => { + static getWithArgs = (reduxStore, ...args) => { const argsKey = convertArgsToString(...args); - const state = this.get(reduxStore); + const state = Getters.get(reduxStore); if (!state || !state.hasOwnProperty(argsKey)) { - return _getInitialStateForKey(); + return Getters._getInitialStateForKey(); } return state[argsKey]; }; - this.getData = (reduxStore, ...args) => this.getWithArgs(reduxStore, ...args).data; + static getData = (reduxStore, ...args) => Getters.getWithArgs(reduxStore, ...args).data; - this.getMeta = (reduxStore, ...args) => this.getWithArgs(reduxStore, ...args).meta; + static getMeta = (reduxStore, ...args) => Getters.getWithArgs(reduxStore, ...args).meta; - this.getIsLoading = (reduxStore: Object, ...args) => { - const meta = this.getMeta(reduxStore, ...args); + static getIsLoading = (reduxStore: Object, ...args) => { + const meta = Getters.getMeta(reduxStore, ...args); return meta.loading; }; - }; + } - const Actions = new function() { - const _queryOrCreateBoilerplate = (...args) => { + class Actions { + static _queryOrCreateBoilerplate = (...args) => { const stringHash = convertArgsToString(...args); if (!mapArgsToGenerated[stringHash]) { // Need to generate everything for this. Luckily we have a generator @@ -85,10 +85,10 @@ const createFactoryMap = (injectReducer: Function) => { }; // Links to argument-less actions generated by the factory. - const _forwardActionForSubreducer = (actionName: string, { forwardArgs = false }: * = {}) => ( + static _forwardActionForSubreducer = (actionName: string, { forwardArgs = false }: * = {}) => ( ...args: Array ) => async dispatch => { - const actions = _queryOrCreateBoilerplate(...args).actions; + const actions = Actions._queryOrCreateBoilerplate(...args).actions; const action = actions[actionName]; if (forwardArgs) { return dispatch(action(...args)); // Assumed to be loading arguments. @@ -96,19 +96,19 @@ const createFactoryMap = (injectReducer: Function) => { return dispatch(action()); }; - this.load = _forwardActionForSubreducer('load', { forwardArgs: true }); + static load = Actions._forwardActionForSubreducer('load', { forwardArgs: true }); - this.reset = _forwardActionForSubreducer('reset'); + static reset = Actions._forwardActionForSubreducer('reset'); - this.clearErrors = _forwardActionForSubreducer('clearErrors'); - } + static clearErrors = Actions._forwardActionForSubreducer('clearErrors'); + } - return { - actions: Actions, - getters: Getters, - }; + return { + actions: Actions, + getters: Getters, }; }; +}; export default createFactoryMap; diff --git a/src/index.js b/src/index.js index 3a4028a..1040f55 100644 --- a/src/index.js +++ b/src/index.js @@ -12,7 +12,7 @@ function configureRags(store: *, createRootReducer: *) { injectReducer = makeReducerInjector(store, createRootReducer); ragFactory = createFactory(injectReducer); ragFactoryMap = createFactoryMap(injectReducer); -}; +} export { combineAsyncReducers, diff --git a/test/integration.test.js b/test/integration.test.js new file mode 100644 index 0000000..7401373 --- /dev/null +++ b/test/integration.test.js @@ -0,0 +1,52 @@ +// @flow + +import { compose, combineReducers, createStore, applyMiddleware } from 'redux'; +import thunk from 'redux-thunk'; + +import { combineAsyncReducers, configureRags, ragFactory, ragFactoryMap } from '../src/index'; + + +describe('integration test', () => { + // Initializing the redux store. + const createRootReducer = (dynamicReducers: Object = {}) => { + dynamicReducers = combineAsyncReducers(combineReducers, dynamicReducers); + + return combineReducers(Object.assign({}, dynamicReducers, { + // ... list your reducers below + })); + }; + + const middleware = applyMiddleware(thunk); + const store = createStore(createRootReducer(), compose(middleware)); + configureRags(store, createRootReducer); + + + it('initializes store properly', () => { + expect(store).toBeTruthy(); + }); + + it('adds a reducer to state when calling factory load', async () => { + const { actions, getters } = ragFactory({ name: 'basic-test', load: () => true }); + const state = store.getState(); + expect(state['@@redux-rags']).toBeTruthy(); + expect(getters.getData(state)).toBe(null); + await store.dispatch(actions.load()); + const nextState = store.getState(); + expect(getters.getData(nextState)).toBe(true); + }); + + it('adds a reducer to state when calling factoryMap load', async () => { + let i = 1; + const { actions, getters } = ragFactoryMap({ getInitialState: () => 0, name: 'map-test', load: (negative) => negative ? -1 * i++ : i++ }); + const state = store.getState(); + expect(getters.getData(state, true)).toBe(0); + expect(getters.getData(state, false)).toBe(0); + + await store.dispatch(actions.load(true)); + await store.dispatch(actions.load(false)); + const nextState = store.getState(); + expect(nextState['@@redux-rags/map']).toBeTruthy(); + expect(getters.getData(nextState, true)).toBe(-1); + expect(getters.getData(nextState, false)).toBe(2); + }); +});