diff --git a/README.md b/README.md index cf2ece4..15a677f 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,18 @@ Vitruvius extends redux's `combineReducers` to allow developers to include a `buildInitialState` method on their reducer. This allows for the passing of locals to build the initial state that wouldn't normally be available to a reducer when setting its initial state. For instance, one could pass some data -from the request object. Below is an example of a reducer implementing a -`buildInitialState` method and an example of virtuvius being implemented. +from the request object. + +## Installation + +``` +$ npm install --save @americanexpress/vitruvius +``` + +## Implementation + +Below is an example of a reducer implementing a `buildInitialState` method and +an example of vitruvius being implemented. ```js import { Map } from 'immutable'; @@ -26,8 +36,13 @@ export default function reducer(state = buildInitialState(), action) { reducer.buildInitialState = buildInitialState; ``` +> **TIP**: To extend `combineReducers` from `redux-immutable` instead of `redux` +import from `vitruvius/immutable`. + ```js -const reducer = virtuvius({ +import vitruvius from 'vitruvius'; + +const reducer = vitruvius({ stuff: stuffReducer, things: thingsReducer, ...otherReducers, @@ -36,6 +51,7 @@ const reducer = virtuvius({ const store = createStore(reducer, reducer.buildInitialState(locals), enhancer); ``` + ## Contributing We welcome Your interest in the American Express Open Source Community on Github. Any Contributor to any Open Source Project managed by the American Express Open diff --git a/__tests__/__snapshots__/collectBuiltState.spec.js.snap b/__tests__/__snapshots__/collectBuiltState.spec.js.snap new file mode 100644 index 0000000..3679a0c --- /dev/null +++ b/__tests__/__snapshots__/collectBuiltState.spec.js.snap @@ -0,0 +1,29 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`collectBuiltState should build a mutable state 1`] = ` +Object { + "other": Immutable.Map { + baz: "test", +}, + "stuff": Immutable.Map { + foo: "test", +}, + "things": Immutable.Map { + bar: "test", +}, +} +`; + +exports[`collectBuiltState should build an immutable state 1`] = ` +Immutable.Map { + stuff: Immutable.Map { + foo: "test", + }, + things: Immutable.Map { + bar: "test", + }, + other: Immutable.Map { + baz: "test", + }, +} +`; diff --git a/__tests__/__snapshots__/immutable.spec.js.snap b/__tests__/__snapshots__/immutable.spec.js.snap new file mode 100644 index 0000000..e1acd5b --- /dev/null +++ b/__tests__/__snapshots__/immutable.spec.js.snap @@ -0,0 +1,81 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`vitruviusImmutable should build the expected state for a flat tree of reducers 1`] = ` +Immutable.Map { + stuff: Immutable.Map { + foo: "test", + }, + things: Immutable.Map { + bar: "test", + }, + other: Immutable.Map { + baz: "test", + }, +} +`; + +exports[`vitruviusImmutable should build the expected state for a nested tree of reducers 1`] = ` +Immutable.Map { + stuff: Immutable.Map { + foo: "test", + }, + nested: Immutable.Map { + things: Immutable.Map { + bar: "test", + }, + other: Immutable.Map { + baz: "test", + }, + }, +} +`; + +exports[`vitruviusImmutable should handle a mix of reducers having and not having buildInitialState method 1`] = ` +Immutable.Map { + stuff: Immutable.Map { + foo: "test", + }, + things: Immutable.Map { + bar: "test", + }, + other: Immutable.Map { + baz: "test", + }, +} +`; + +exports[`vitruviusImmutable should handle all reducers having buildInitialState method 1`] = ` +Immutable.Map { + stuff: Immutable.Map { + foo: "test", + }, + things: Immutable.Map { + bar: "test", + }, + other: Immutable.Map { + baz: "test", + }, +} +`; + +exports[`vitruviusImmutable should handle no reducers having buildInitialState method 1`] = ` +Immutable.Map { +} +`; + +exports[`vitruviusImmutable should return an initialState that is acceptable to redux's createStore 1`] = ` +Immutable.Map { + stuff: Immutable.Map { + foo: "test", + }, + things: Immutable.Map { + bar: "test", + }, + other: Immutable.Map { + baz: "test", + }, + static: Immutable.Map { + static: true, + }, +} +`; diff --git a/__tests__/__snapshots__/index.spec.js.snap b/__tests__/__snapshots__/index.spec.js.snap new file mode 100644 index 0000000..582dbbc --- /dev/null +++ b/__tests__/__snapshots__/index.spec.js.snap @@ -0,0 +1,78 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`vitruvius should build the expected state for a flat tree of reducers 1`] = ` +Object { + "other": Immutable.Map { + baz: "test", +}, + "stuff": Immutable.Map { + foo: "test", +}, + "things": Immutable.Map { + bar: "test", +}, +} +`; + +exports[`vitruvius should build the expected state for a nested tree of reducers 1`] = ` +Object { + "nested": Object { + "other": Immutable.Map { + baz: "test", +}, + "things": Immutable.Map { + bar: "test", +}, + }, + "stuff": Immutable.Map { + foo: "test", +}, +} +`; + +exports[`vitruvius should handle a mix of reducers having and not having buildInitialState method 1`] = ` +Object { + "other": Immutable.Map { + baz: "test", +}, + "stuff": Immutable.Map { + foo: "test", +}, + "things": Immutable.Map { + bar: "test", +}, +} +`; + +exports[`vitruvius should handle all reducers having buildInitialState method 1`] = ` +Object { + "other": Immutable.Map { + baz: "test", +}, + "stuff": Immutable.Map { + foo: "test", +}, + "things": Immutable.Map { + bar: "test", +}, +} +`; + +exports[`vitruvius should handle no reducers having buildInitialState method 1`] = `Object {}`; + +exports[`vitruvius should return an initialState that is acceptable to redux's createStore 1`] = ` +Object { + "other": Immutable.Map { + baz: "test", +}, + "static": Immutable.Map { + static: true, +}, + "stuff": Immutable.Map { + foo: "test", +}, + "things": Immutable.Map { + bar: "test", +}, +} +`; diff --git a/__tests__/collectBuiltState.spec.js b/__tests__/collectBuiltState.spec.js new file mode 100644 index 0000000..380d5f4 --- /dev/null +++ b/__tests__/collectBuiltState.spec.js @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2017 American Express Travel Related Services Company, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +import { Map } from 'immutable'; +import collectBuiltState from '../src/collectBuiltState'; +import { + stuffReducer, + thingsReducer, + otherReducer, + staticReducer, +} from './fixtures'; + +describe('collectBuiltState', () => { + const locals = { data: 'test' }; + + it('should build an immutable state', () => { + const result = collectBuiltState({ + reducers: { + stuff: stuffReducer, + things: thingsReducer, + other: otherReducer, + static: staticReducer, + }, + locals, + defaultState: new Map(), + }); + expect(result).toMatchSnapshot(); + }); + + it('should build a mutable state', () => { + const result = collectBuiltState({ + reducers: { + stuff: stuffReducer, + things: thingsReducer, + other: otherReducer, + static: staticReducer, + }, + locals, + defaultState: {}, + }); + expect(result).toMatchSnapshot(); + }); +}); diff --git a/__tests__/fixtures.js b/__tests__/fixtures.js index 1298815..a027fc4 100644 --- a/__tests__/fixtures.js +++ b/__tests__/fixtures.js @@ -13,6 +13,7 @@ */ import { Map } from 'immutable'; +import { createStore } from 'redux'; export const SOME_ACTION = 'SOME_ACTION'; @@ -63,3 +64,73 @@ export function staticReducer(state = new Map({ static: true }), action) { return state; } } + +export function runVitruviusTests(describeSpec, vitruvius) { + return describe(describeSpec, () => { + const locals = { data: 'test' }; + + it('should handle all reducers having buildInitialState method', () => { + const reducer = vitruvius({ + stuff: stuffReducer, + things: thingsReducer, + other: otherReducer, + }); + const actual = reducer.buildInitialState(locals); + expect(actual).toMatchSnapshot(); + }); + + it('should handle no reducers having buildInitialState method', () => { + const reducer = vitruvius({ + static: staticReducer, + }); + const actual = reducer.buildInitialState(locals); + expect(actual).toMatchSnapshot(); + }); + + it('should handle a mix of reducers having and not having buildInitialState method', () => { + const reducer = vitruvius({ + stuff: stuffReducer, + things: thingsReducer, + other: otherReducer, + static: staticReducer, + }); + const actual = reducer.buildInitialState(locals); + expect(actual).toMatchSnapshot(); + }); + + it('should build the expected state for a flat tree of reducers', () => { + const reducer = vitruvius({ + stuff: stuffReducer, + things: thingsReducer, + other: otherReducer, + static: staticReducer, + }); + const actual = reducer.buildInitialState(locals); + expect(actual).toMatchSnapshot(); + }); + + it('should build the expected state for a nested tree of reducers', () => { + const reducer = vitruvius({ + stuff: stuffReducer, + nested: vitruvius({ + things: thingsReducer, + other: otherReducer, + }), + }); + const actual = reducer.buildInitialState(locals); + expect(actual).toMatchSnapshot(); + }); + + it('should return an initialState that is acceptable to redux\'s createStore', () => { + const reducer = vitruvius({ + stuff: stuffReducer, + things: thingsReducer, + other: otherReducer, + static: staticReducer, + }); + const store = createStore(reducer, reducer.buildInitialState(locals)); + const actual = store.getState(); + expect(actual).toMatchSnapshot(); + }); + }); +} diff --git a/__tests__/immutable.spec.js b/__tests__/immutable.spec.js new file mode 100644 index 0000000..715d961 --- /dev/null +++ b/__tests__/immutable.spec.js @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2017 American Express Travel Related Services Company, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +import vitruviusImmutable from '../src/immutable'; +import vitruviusImmutableExport from '../immutable'; +import { runVitruviusTests } from './fixtures'; + +jest.mock('../lib/immutable', () => require('../src/immutable'), { virtual: true }); + +runVitruviusTests('vitruviusImmutable', vitruviusImmutable); + +describe('vitruviusImmutable export', () => { + it('should export vitruviusImmutable', () => { + expect(vitruviusImmutableExport.name).toBe('vitruviusImmutable'); + }); +}); diff --git a/__tests__/index.spec.js b/__tests__/index.spec.js index d7f501c..fe9fbd1 100644 --- a/__tests__/index.spec.js +++ b/__tests__/index.spec.js @@ -12,108 +12,7 @@ * the License. */ -import { Map } from 'immutable'; -import { createStore } from 'redux'; import vitruvius from '../src'; -import { - stuffReducer, - thingsReducer, - otherReducer, - staticReducer, -} from './fixtures'; +import { runVitruviusTests } from './fixtures'; -describe('vitruvius', () => { - const locals = { data: 'test' }; - - it('should handle all reducers having buildInitialState method', () => { - const reducer = vitruvius({ - stuff: stuffReducer, - things: thingsReducer, - other: otherReducer, - }); - const actual = reducer.buildInitialState(locals); - const expected = { - stuff: new Map({ foo: locals.data }), - things: new Map({ bar: locals.data }), - other: new Map({ baz: locals.data }), - }; - expect(actual).toEqual(expected); - }); - - it('should handle no reducers having buildInitialState method', () => { - const reducer = vitruvius({ - static: staticReducer, - }); - const actual = reducer.buildInitialState(locals); - expect(actual).toEqual({}); - }); - - it('should handle a mix of reducers having and not having buildInitialState method', () => { - const reducer = vitruvius({ - stuff: stuffReducer, - things: thingsReducer, - other: otherReducer, - static: staticReducer, - }); - const actual = reducer.buildInitialState(locals); - const expected = { - stuff: new Map({ foo: locals.data }), - things: new Map({ bar: locals.data }), - other: new Map({ baz: locals.data }), - }; - expect(actual).toEqual(expected); - }); - - it('should build the expected state for a flat tree of reducers', () => { - const reducer = vitruvius({ - stuff: stuffReducer, - things: thingsReducer, - other: otherReducer, - static: staticReducer, - }); - const actual = reducer.buildInitialState(locals); - const expected = { - stuff: new Map({ foo: locals.data }), - things: new Map({ bar: locals.data }), - other: new Map({ baz: locals.data }), - }; - expect(actual).toEqual(expected); - }); - - it('should build the expected state for a nested tree of reducers', () => { - const reducer = vitruvius({ - stuff: stuffReducer, - nested: vitruvius({ - things: thingsReducer, - other: otherReducer, - }), - }); - const actual = reducer.buildInitialState(locals); - const expected = { - stuff: new Map({ foo: locals.data }), - nested: { - things: new Map({ bar: locals.data }), - other: new Map({ baz: locals.data }), - }, - }; - expect(actual).toEqual(expected); - }); - - it('should return an initialState that is acceptable to redux\'s createStore', () => { - const reducer = vitruvius({ - stuff: stuffReducer, - things: thingsReducer, - other: otherReducer, - static: staticReducer, - }); - const store = createStore(reducer, reducer.buildInitialState(locals)); - const actual = store.getState(); - const expected = { - stuff: new Map({ foo: locals.data }), - things: new Map({ bar: locals.data }), - other: new Map({ baz: locals.data }), - static: new Map({ static: true }), - }; - expect(actual).toEqual(expected); - }); -}); +runVitruviusTests('vitruvius', vitruvius); diff --git a/immutable.js b/immutable.js new file mode 100644 index 0000000..1c3c03c --- /dev/null +++ b/immutable.js @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2017 American Express Travel Related Services Company, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +// Required file is only included in build +// eslint-disable-next-line import/no-unresolved +module.exports = require('./lib/immutable'); diff --git a/package.json b/package.json index 61dd01a..cf242d8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@americanexpress/vitruvius", - "version": "1.1.2", + "version": "1.2.0", "description": "Add buildInitialState method to your reducers", "main": "lib/index.js", "scripts": { @@ -30,25 +30,24 @@ "keywords": [ "redux", "reducer", + "immutable", "initial state" ], "author": "Jimmy King (https://github.com/jking90)", "license": "Apache-2.0", "devDependencies": { - "amex-jest-preset": "^3.0.0", - "babel-cli": "^6.24.0", - "babel-preset-amex": "^1.0.0", - "eslint": "^3.18.0", - "eslint-config-amex": "^4.0.0", - "immutable": "^3.8.1", - "jest": "^19.0.2", - "redux": "^3.6.0", + "amex-jest-preset": "^3.3.0", + "babel-cli": "^6.24.1", + "babel-preset-amex": "^1.0.1", + "eslint": "^3.19.0", + "eslint-config-amex": "^6.0.0", + "jest": "^20.0.4", "rimraf": "^2.6.1" }, "dependencies": { - "postinstall-build": "^3.0.0" - }, - "peerDependencies": { - "redux": "^3.6.0" + "immutable": "^3.8.1", + "postinstall-build": "^5.0.0", + "redux": "^3.6.0", + "redux-immutable": "^4.0.0" } } diff --git a/src/collectBuiltState.js b/src/collectBuiltState.js new file mode 100644 index 0000000..056bd87 --- /dev/null +++ b/src/collectBuiltState.js @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2017 American Express Travel Related Services Company, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +export default function collectBuiltState({ reducers, locals, defaultState }) { + let builtState = defaultState; + + Object.keys(reducers).forEach((key) => { + const reducer = reducers[key]; + if (typeof reducer === 'function' && typeof reducer.buildInitialState === 'function') { + if (typeof builtState.set === 'function') { + builtState = builtState.set(key, reducer.buildInitialState(locals)); + } else { + builtState[key] = reducer.buildInitialState(locals); + } + } + }); + + return builtState; +} diff --git a/src/immutable.js b/src/immutable.js new file mode 100644 index 0000000..c42e679 --- /dev/null +++ b/src/immutable.js @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2017 American Express Travel Related Services Company, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +import { combineReducers } from 'redux-immutable'; +import { Map } from 'immutable'; +import collectBuiltState from './collectBuiltState'; + +export default function vitruviusImmutable(reducers, getDefaultState = Map) { + const combined = combineReducers(reducers, getDefaultState); + + combined.buildInitialState = function buildInitialState(locals) { + return collectBuiltState({ reducers, locals, defaultState: getDefaultState() }); + }; + + return combined; +} diff --git a/src/index.js b/src/index.js index c3777eb..72d2f36 100644 --- a/src/index.js +++ b/src/index.js @@ -13,21 +13,13 @@ */ import { combineReducers } from 'redux'; +import collectBuiltState from './collectBuiltState'; export default function vitruvius(reducers) { const combined = combineReducers(reducers); combined.buildInitialState = function buildInitialState(locals) { - const builtState = {}; - - Object.keys(reducers).forEach((key) => { - const reducer = reducers[key]; - if (typeof reducer === 'function' && typeof reducer.buildInitialState === 'function') { - builtState[key] = reducer.buildInitialState(locals); - } - }); - - return builtState; + return collectBuiltState({ reducers, locals, defaultState: {} }); }; return combined;