Skip to content

Commit

Permalink
feat(store): split immutibility checks in state and action checks (#1894
Browse files Browse the repository at this point in the history
)
  • Loading branch information
timdeschryver authored and brandonroberts committed May 28, 2019
1 parent 2fb8d67 commit c59c211
Show file tree
Hide file tree
Showing 17 changed files with 359 additions and 288 deletions.

This file was deleted.

77 changes: 64 additions & 13 deletions modules/store/spec/meta-reducers/immutability_reducer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,55 +3,106 @@ import { immutabilityCheckMetaReducer } from '../../src/meta-reducers';
describe('immutabilityCheckMetaReducer:', () => {
describe('actions:', () => {
it('should not throw if left untouched', () => {
expect(() => invokeReducer((action: any) => action)).not.toThrow();
expect(() => invokeActionReducer((state: any) => state)).not.toThrow();
});

it('should throw when mutating an action', () => {
expect(() =>
invokeReducer((action: any) => {
invokeActionReducer((state: any, action: any) => {
action.foo = '123';
return state;
})
).toThrow();
expect(() =>
invokeReducer((action: any) => {
invokeActionReducer((state: any, action: any) => {
action.numbers.push(4);
return state;
})
).toThrow();
});

function invokeReducer(reduce: Function) {
immutabilityCheckMetaReducer((state, action) => {
reduce(action);
it('should throw when mutating action outside of reducer', () => {
let dispatchedAction: any;
invokeActionReducer((state: any, action: any) => {
dispatchedAction = action;
return state;
});

expect(() => {
dispatchedAction.foo = '123';
}).toThrow();
});

it('should not throw when check is off', () => {
expect(() =>
invokeActionReducer((state: any, action: any) => {
action.foo = '123';
return state;
}, false)
).not.toThrow();
});

function invokeActionReducer(reduce: Function, checkIsOn = true) {
immutabilityCheckMetaReducer((state, action) => reduce(state, action), {
action: checkIsOn,
state: false,
})({}, { type: 'invoke', numbers: [1, 2, 3], fun: function() {} });
}
});

describe('state:', () => {
it('should not throw if left untouched', () => {
expect(() =>
invokeReducer((state: any) => ({ ...state, foo: 'bar' }))
invokeStateReducer((state: any) => ({ ...state, foo: 'bar' }))
).not.toThrow();
});

it('should throw when mutating state', () => {
expect(() =>
invokeReducer((state: any) => {
invokeStateReducer((state: any) => {
state.foo = '123';
return state;
})
).toThrow();
expect(() =>
invokeReducer((state: any) => {
invokeStateReducer((state: any) => {
state.numbers.push(4);
return state;
})
).toThrow();
});

function invokeReducer(reduce: Function) {
immutabilityCheckMetaReducer(state => reduce(state))(
{ numbers: [1, 2, 3] },
{ type: 'invoke' }
it('should throw when mutating state outside of reducer', () => {
const nextState = invokeStateReducer((state: any) => state);
expect(() => {
nextState.foo = '123';
}).toThrow();
});

it('should not throw when check is off', () => {
expect(() =>
invokeStateReducer((state: any) => {
state.foo = '123';
return state;
}, false)
).not.toThrow();
});

function invokeStateReducer(reduce: Function, checkIsOn = true) {
const reducer = immutabilityCheckMetaReducer(
(state, action) => {
if (action.type === 'init') return state;
return reduce(state, action);
},
{
state: checkIsOn,
action: false,
}
);

// dispatch init noop action because it's the next state that is frozen
const state = reducer({ numbers: [1, 2, 3] }, { type: 'init' });
return reducer(state, { type: 'invoke' });
}
});
});
111 changes: 111 additions & 0 deletions modules/store/spec/meta-reducers/serialization_reducer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { serializationCheckMetaReducer } from '../../src/meta-reducers';

describe('serializationCheckMetaReducer:', () => {
const serializables: Record<string, any> = {
number: { value: 4 },
boolean: { value: true },
string: { value: 'foobar' },
array: { value: [1, 2, 3] },
object: { value: {} },
nested: { value: { number: 7, array: ['n', 'g', 'r', 'x'] } },
null: { value: null },
undefined: { value: undefined },
};

const unSerializables: Record<string, any> = {
date: { value: new Date() },
map: { value: new Map() },
set: { value: new Set() },
class: { value: new class {}() },
function: { value: () => {} },
};

describe('serializable:', () => {
Object.keys(serializables).forEach(key => {
it(`action with ${key} should not throw`, () => {
expect(() =>
invokeActionReducer({ type: 'valid', payload: serializables[key] })
).not.toThrow();
});

it(`state with ${key} should not throw`, () => {
expect(() => invokeStateReducer(serializables[key])).not.toThrow();
});
});
});

describe('unserializable:', () => {
Object.keys(unSerializables).forEach(key => {
it(`action with ${key} should throw`, () => {
expect(() =>
invokeActionReducer({ type: 'valid', payload: unSerializables[key] })
).toThrow();
});

it(`state with ${key} should throw`, () => {
expect(() => invokeStateReducer(unSerializables[key])).toThrow();
});
});
});

describe('actions: ', () => {
it('should not throw if check is off', () => {
expect(() =>
invokeActionReducer({ type: 'valid', payload: unSerializables }, false)
);
});

it('should log the path that is not serializable', () => {
expect(() =>
invokeActionReducer({
type: 'valid',
payload: { foo: { bar: unSerializables['date'] } },
})
).toThrowError(
/Detected unserializable action at "payload.foo.bar.value"/
);
});
});

describe('state: ', () => {
it('should not throw if check is off', () => {
expect(() => invokeStateReducer(unSerializables, false)).not.toThrow();
});

it('should log the path that is not serializable', () => {
expect(() =>
invokeStateReducer({
foo: { bar: unSerializables['date'] },
})
).toThrowError(/Detected unserializable state at "foo.bar.value"/);
});

it('should not throw if state is null', () => {
expect(() => invokeStateReducer(null)).toThrowError(
/Detected unserializable state at "root"/
);
});

it('should not throw if state is undefined', () => {
expect(() => invokeStateReducer(undefined)).toThrowError(
/Detected unserializable state at "root"/
);
});
});

function invokeActionReducer(action: any, checkIsOn = true) {
serializationCheckMetaReducer(state => state, {
action: checkIsOn,
state: false,
})(undefined, action);
}

function invokeStateReducer(nextState?: any, checkIsOn = true) {
serializationCheckMetaReducer(() => nextState, {
state: checkIsOn,
action: false,
})(undefined, {
type: 'invokeReducer',
});
}
});

This file was deleted.

82 changes: 0 additions & 82 deletions modules/store/spec/meta-reducers/utils.spec.ts

This file was deleted.

Loading

0 comments on commit c59c211

Please sign in to comment.