Skip to content

Commit

Permalink
Implement reducer wrapper
Browse files Browse the repository at this point in the history
  • Loading branch information
miqmago committed May 19, 2016
1 parent 422100c commit 0230daa
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 98 deletions.
1 change: 1 addition & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"extends": "airbnb",
"rules": {
"indent": [2, 4, { "SwitchCase": 1, "VariableDeclarator": {"var": 2, "let": 2, "const": 3} }],
"no-restricted-syntax": 0,
"react/jsx-indent-props": [2, 4],
"react/jsx-closing-bracket-location": [2, {"selfClosing": false}]
}
Expand Down
68 changes: 58 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ At this development stage, it only works with [immutable-js](https://facebook.gi

```javascript
import { assert } from 'chai';
import { updateImmutableState } from 'path-reducer';
import { pathReducer } from 'path-reducer';
import { Immutable } from 'immutable';
import { createStore } from 'redux';

Expand All @@ -25,11 +25,10 @@ const defaultState = Immutable.fromJS({
}
});
const reducer = (state = defaultState, action) => {
const nextState = updateImmutableState(state, action);
// Operate with nextState here
return nextState;
return state;
};
const store = createStore(reducer);
const store = createStore(pathReducer(reducer));
const action = {
type: 'updateElementInObject',
meta: ['bar', 'boo'],
Expand All @@ -50,8 +49,61 @@ assert.equal(store.getState(), {

# API

## pathReducer(reducer)
This is a reducer wrapper that makes your life easier:

```javascript
const reducer = (state ) defaultState, action) {
// The state here is already parsed
return state;
};
const store = createStore(pathReducer(reducer));
```

The `pathReducer` function will look for an array in `meta` and an object/array in `payload`.
If this conditions are met, it will try to update the path specified into the supplied state.

**[SEE CASES.md](https://github.com/appfeel/path-reducer/blob/master/CASES.md)** for an extended list of expample cases.


## updateImmutableState(state, action)
This is just a simple reducer. It expects an FSA action:
This is just a simple reducer. It allows you to setup which parts of your reducer will be updated with path:

```javascript
import { updateImmutableState } from 'path-reducer';
import { combineReducers, createStore } from 'redux';

const somePartOfTheTree = (state = someDefaultState, action) => {
switch (action.type) {
case 'PATH':
const nextState = updateImmutableState(state, action);
// Operate with nextState here
return nextState;
default:
return state;
}
};

const anotherPartOfTheTree = (state = anotherDefaultState, action) => {
switch (action.type) {
case 'NO_PATH':
const nextState = Object.assign({}, state);
// ... Operate the state
return nextState;
default:
return state;
}
};

const wholeTree = combineReducers({
somePartOfTheTree,
anotherPartOfTheTree,
});

const store = createStore(wholeTree);
```

It expects an FSA action:

```json
{
Expand All @@ -61,13 +113,9 @@ This is just a simple reducer. It expects an FSA action:
}
```

The reducer function will look for an array in `meta` and an object/array in `payload`.
When this conditions are met, it will try to update the path specified into the supplied state.

**[SEE CASES.md](https://github.com/appfeel/path-reducer/blob/master/CASES.md)** for an extended list of expample cases.

*Note* the path must be relative to the suplied path, not to the state path. If you are updating a sub-tree of the state, you should add the corresponding part of the path to the action:


```javascript
const reducer = (state = defaultState, action) => {
const newAction = {
Expand Down
20 changes: 5 additions & 15 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ export function getObjectValueByPath(obj, path) {

export function updateImmutableState(state, action) {
if (Object.prototype.toString.call(action.meta) !== '[object Array]'
|| action.payload === undefined) {
|| action.payload === undefined
|| !isImmutable(state)) {
return state;
}

Expand Down Expand Up @@ -101,18 +102,7 @@ export function updateImmutableState(state, action) {
return nextState;
}

export const pathReducer = (store) => (next) => (action) => {
// Check if we have path and payload in the action
if (action.meta === undefined
|| Object.prototype.toString.call(action.meta) !== '[object Array]'
|| action.payload === undefined) {
return next(action);
}

const state = store.getState();
// Check if it's immutable-js
if (!isImmutable(state)) {
return next(action);
}
return next(action);
export const pathReducer = (reducer) => (state, action) => {
const nextState = updateImmutableState(state, action);
return reducer(nextState, action);
};
2 changes: 1 addition & 1 deletion sonar-project.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
sonar.projectKey=appfeel:path-reducer
# this is the name displayed in the SonarQube UI
sonar.projectName=Path reducer
sonar.projectVersion=0.1.2
sonar.projectVersion=0.1.3

# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.
# Since SonarQube 4.2, this property is optional if sonar.modules is set.
Expand Down
15 changes: 7 additions & 8 deletions test/generateDoc.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@ import { describe, it } from 'mocha';
import { assert } from 'chai';
import fs from 'fs';
import Immutable from 'immutable';
import { createStore, applyMiddleware } from 'redux';
import { pathReducer } from '../';
import { createStore } from 'redux';
import { executeCbs } from './helpers';
import { defaultState as objDefaultState, actions as objActions } from './objectState/index';
import { defaultState as arrDefaultState, actions as arrActions } from './arrayState/index';
Expand All @@ -12,13 +11,13 @@ import { updateImmutableState } from '../';
const subscribedCbs = [];
const immObjDefaultState = Immutable.fromJS(objDefaultState); // Immutable state
const immArrDefaultState = Immutable.fromJS(arrDefaultState); // Immutable state
const fakeObjReducer = (state = immObjDefaultState, action = {}) => {
const objReducer = (state = immObjDefaultState, action = {}) => {
executeCbs(subscribedCbs, state, action);
// We return original state in order to not mutate it,
// So every test is done over the same initial state
return state;
};
const fakeArrReducer = (state = immArrDefaultState, action = {}) => {
const arrReducer = (state = immArrDefaultState, action = {}) => {
executeCbs(subscribedCbs, state, action);
// We return original state in order to not mutate it,
// So every test is done over the same initial state
Expand Down Expand Up @@ -65,8 +64,8 @@ function logStates(initialStateStr) {
};
}

function generateDocType(type, actions, fakeReducer) {
const store = createStore(fakeReducer, applyMiddleware(pathReducer));
function generateDocType(type, actions, reducer) {
const store = createStore(reducer);
let action;

log += `## ${type} store state\n\n`;
Expand Down Expand Up @@ -115,8 +114,8 @@ export default function generateDoc() {
generateDocIndex('Object', objActions);
generateDocIndex('Array', arrActions);
log += '\n';
generateDocType('Object', objActions, fakeObjReducer);
generateDocType('Array', arrActions, fakeArrReducer);
generateDocType('Object', objActions, objReducer);
generateDocType('Array', arrActions, arrReducer);
ws.write(head);
ws.write(log);
ws.end();
Expand Down
91 changes: 47 additions & 44 deletions test/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,52 +11,55 @@ export const executeCbs = (subscribedCbs, state, action) => {
}
};

export const createReducerTest = (store, subscribedCbs, defaultState) =>
(testedAction) => (done) => {
// The functions that will be executed in our fakeReducer
subscribedCbs.push(updateImmutableState);
// Our test function
subscribedCbs.push((nextState) => {
if (!testedAction.meta) {
assert.equal(nextState, defaultState);
} else {
const path = testedAction.meta.slice(0);
const stateJs = nextState.toJS();
const real = getObjectValueByPath(stateJs, path);
let expected = getObjectValueByPath(testedAction.payload, path, true);
let statePtr = nextState;
let p;
export const createReducerTest =
(store, subscribedCbs, defaultState, isUseUpdateImmutableStateReducer = true) =>
(testedAction) => (done) => {
// The functions that will be executed in our reducer
if (isUseUpdateImmutableStateReducer) {
subscribedCbs.push(updateImmutableState);
}
// Our test function
subscribedCbs.push((nextState) => {
if (!testedAction.meta || /^doNothing/i.test(testedAction.type)) {
assert.equal(JSON.stringify(nextState), JSON.stringify(defaultState));
} else {
const path = testedAction.meta.slice(0);
const stateJs = nextState.toJS();
const real = getObjectValueByPath(stateJs, path);
let expected = getObjectValueByPath(testedAction.payload, path, true);
let statePtr = nextState;
let p;

// console.log('');
// console.log('*************************');
// console.log('nextState:', nextState);
// console.log('path:', path);
// console.log('state:', stateJs);
// console.log('testedAction.payload:', testedAction.payload);
for (p = 0; p < path.length; p += 1) {
assert(statePtr.has(path[p]), `state doesn't contain [${path}]`);
if (p < path.length - 1) {
statePtr = statePtr.get(path[p]);
// console.log('');
// console.log('*************************');
// console.log('nextState:', nextState);
// console.log('path:', path);
// console.log('state:', stateJs);
// console.log('testedAction.payload:', testedAction.payload);
for (p = 0; p < path.length; p += 1) {
assert(statePtr.has(path[p]), `state doesn't contain [${path}]`);
if (p < path.length - 1) {
statePtr = statePtr.get(path[p]);
}
}
}
assert(nextState.hasIn(path), `state doesn't contain [${path}]`);
assert(nextState.hasIn(path), `state doesn't contain [${path}]`);

while (expected === undefined && path.length > 0) {
path.shift();
expected = getObjectValueByPath(testedAction.payload, path);
}
while (expected === undefined && path.length > 0) {
path.shift();
expected = getObjectValueByPath(testedAction.payload, path);
}

// console.log('real:', real);
// console.log('expected:', expected);
// console.log('');
assert.equal(
JSON.stringify(real),
JSON.stringify(expected)
);
}
done();
});
// console.log('real:', real);
// console.log('expected:', expected);
// console.log('');
assert.equal(
JSON.stringify(real),
JSON.stringify(expected)
);
}
done();
});

// Dispatch the action
store.dispatch(testedAction);
};
// Dispatch the action
store.dispatch(testedAction);
};
52 changes: 40 additions & 12 deletions test/immutableState.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
import { describe, it } from 'mocha';
import Immutable from 'immutable';
import deepFreeze from 'deep-freeze';
import { createStore, applyMiddleware } from 'redux';
import { pathReducer } from '../';
import { createStore } from 'redux';
import { createReducerTest, executeCbs } from './helpers';
import { defaultState as objDefaultState, actions as objActions } from './objectState/index';
import { defaultState as arrDefaultState, actions as arrActions } from './arrayState/index';
import { pathReducer } from '../';

const subscribedCbs = [];
const immObjDefaultState = Immutable.fromJS(objDefaultState); // Immutable state
const immArrDefaultState = Immutable.fromJS(arrDefaultState); // Immutable state
const fakeObjReducer = (state = immObjDefaultState, action = {}) => {
const objReducer = (state = immObjDefaultState, action = {}) => {
executeCbs(subscribedCbs, state, action);
// We return original state in order to not mutate it,
// So every test is done over the same initial state
return state;
};
const fakeArrReducer = (state = immArrDefaultState, action = {}) => {
const arrReducer = (state = immArrDefaultState, action = {}) => {
executeCbs(subscribedCbs, state, action);
// We return original state in order to not mutate it,
// So every test is done over the same initial state
Expand All @@ -30,32 +30,60 @@ deepFreeze(arrActions);

export default function executeTest() {
describe('Immutable object state store', () => {
const store = createStore(fakeObjReducer, applyMiddleware(pathReducer));
const store = createStore(objReducer);
const test = createReducerTest(store, subscribedCbs, immObjDefaultState);
let action;

it('store should be correctly created with pathReducer middleware', () => {
createStore(fakeObjReducer, applyMiddleware(pathReducer));
});

for (action in objActions) {
// test(..) ensures the state is defaultState before each dispatch
if (objActions.hasOwnProperty(action)) {
it(`dispatch ${objActions[action].type} action`, test(objActions[action]));
}
}
});

describe('Immutable array state store', () => {
const store = createStore(fakeArrReducer, applyMiddleware(pathReducer));
const store = createStore(arrReducer);
const test = createReducerTest(store, subscribedCbs, immArrDefaultState);
let action;

it('store should be correctly created with pathReducer middleware', () => {
createStore(fakeArrReducer, applyMiddleware(pathReducer));
for (action in arrActions) {
// test(..) ensures the state is defaultState before each dispatch
if (arrActions.hasOwnProperty(action)) {
it(`dispatch ${arrActions[action].type} action`, test(arrActions[action]));
}
}
});

describe('Immutable object state store with pathReducer wrapper', () => {
let action;

it('store should be correctly created with pathReducer wrapper', () => {
createStore(pathReducer(objReducer));
});

for (action in objActions) {
if (objActions.hasOwnProperty(action)) {
// We need to recreate store as the state is being modified after each dispatch
const store = createStore(pathReducer(objReducer));
const test = createReducerTest(store, subscribedCbs, immObjDefaultState, false);
it(`dispatch ${objActions[action].type} action`, test(objActions[action]));
}
}
});

describe('Immutable array state store with pathReducer wrapper', () => {
let action;

it('store should be correctly created with pathReducer wrapper', () => {
createStore(pathReducer(objReducer));
});

for (action in arrActions) {
if (arrActions.hasOwnProperty(action)) {
// We need to recreate store as the state is being modified after each dispatch
const store = createStore(pathReducer(arrReducer));
const test = createReducerTest(store, subscribedCbs, immArrDefaultState, false);
it(`dispatch ${arrActions[action].type} action`, test(arrActions[action]));
}
}
Expand Down
Loading

0 comments on commit 0230daa

Please sign in to comment.