fix(object): retain deletions to properties/array elements

This commit is contained in:
Kai Moseley 2017-08-08 16:10:41 +01:00
parent cb4f155a29
commit 09e5b9c922
5 changed files with 38 additions and 14 deletions

2
package-lock.json generated
View File

@ -2002,7 +2002,7 @@
"dependencies": { "dependencies": {
"jest-cli": { "jest-cli": {
"version": "20.0.4", "version": "20.0.4",
"resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-20.0.4.tgz", "resolved": "https://npm.onfido.co.uk:443/jest-cli/-/jest-cli-20.0.4.tgz",
"integrity": "sha1-5TKxnYiuW8bEF+iwWTpv6VSx3JM=", "integrity": "sha1-5TKxnYiuW8bEF+iwWTpv6VSx3JM=",
"dev": true "dev": true
} }

View File

@ -42,6 +42,9 @@ describe('buildStoreChunk', () => {
innerNested3: Types.reducer(Types.string('baz')), innerNested3: Types.reducer(Types.string('baz')),
}), }),
}), }),
nested5: Types.reducer(Types.shape({
arrayExample: Types.arrayOf(Types.string()),
})),
}); });
const nonNestedChunk = buildStoreChunk('example2', Types.reducer(Types.string('foo'))); const nonNestedChunk = buildStoreChunk('example2', Types.reducer(Types.string('foo')));
@ -51,7 +54,7 @@ describe('buildStoreChunk', () => {
})); }));
it('Selectors object has the correct top level structure for a nested chunk', () => { it('Selectors object has the correct top level structure for a nested chunk', () => {
expect(Object.keys(chunk.selectors)).toEqual(['nested1', 'nested2', 'nested3', 'nested4']); expect(Object.keys(chunk.selectors)).toEqual(['nested1', 'nested2', 'nested3', 'nested4', 'nested5']);
}); });
it('Selectors object is a function for a non-nested chunk', () => { it('Selectors object is a function for a non-nested chunk', () => {
expect(isFunction(nonNestedChunk.selectors)).toBe(true); expect(isFunction(nonNestedChunk.selectors)).toBe(true);
@ -69,7 +72,7 @@ describe('buildStoreChunk', () => {
describe('Actions', () => { describe('Actions', () => {
it('Actions object has the correct top level structure for a nested chunk', () => { it('Actions object has the correct top level structure for a nested chunk', () => {
expect(Object.keys(chunk.actions)).toEqual(['nested1', 'nested2', 'nested3', 'nested4']); expect(Object.keys(chunk.actions)).toEqual(['nested1', 'nested2', 'nested3', 'nested4', 'nested5']);
}); });
it('Actions object has the correct top level structure for a non nested chunk', () => { it('Actions object has the correct top level structure for a non nested chunk', () => {
expect(Object.keys(nonNestedChunk.actions)).toEqual(['replace', 'reset']); expect(Object.keys(nonNestedChunk.actions)).toEqual(['replace', 'reset']);
@ -97,6 +100,14 @@ describe('buildStoreChunk', () => {
store.dispatch(chunk.actions.nested1.reset()); store.dispatch(chunk.actions.nested1.reset());
expect(chunk.selectors.nested1(store.getState())).toEqual('foo'); expect(chunk.selectors.nested1(store.getState())).toEqual('foo');
}); });
it('Dispatching an empty array property should replace existing array', () => {
store.dispatch(chunk.actions.nested5.replace({ arrayExample: ['2'] }));
store.dispatch(chunk.actions.nested5.update({ arrayExample: [] }));
expect(chunk.selectors.nested5(store.getState())).toEqual({
arrayExample: [],
});
});
}); });
describe('Combined actions and selectors (non nested chunk)', () => { describe('Combined actions and selectors (non nested chunk)', () => {

View File

@ -65,7 +65,10 @@ describe('Validation functionality', () => {
}); });
it('Array should return an empty array if a non-array is passed', () => { it('Array should return an empty array if a non-array is passed', () => {
expect(validateArray('foo')).toEqual([]); expect(validateArray('foo')).toEqual([]);
}) });
it('Array should allow an empty array', () => {
expect(validateArray([])).toEqual([]);
});
}); });
describe('Objects', () => { describe('Objects', () => {
@ -138,6 +141,16 @@ describe('Validation functionality', () => {
test2: 'bar', test2: 'bar',
}); });
}); });
const testObjectStructure7 = Types.shape({
test1: Types.arrayOf(Types.string()),
});
it('Should allow an empty array to be passed for an array property', () => {
expect(validateShape(testObjectStructure7, { test1: [] })).toEqual({
test1: [],
});
});
}); });
describe('Non covered types', () => { describe('Non covered types', () => {

View File

@ -40,7 +40,6 @@ export type ShapeReducerOptions = {
//============================== //==============================
import isObject from 'lodash/isObject'; import isObject from 'lodash/isObject';
import omit from 'lodash/omit'; import omit from 'lodash/omit';
import merge from 'lodash/fp/merge';
import { validateShape } from '../validatePayload'; import { validateShape } from '../validatePayload';
import { createReducerBehaviors } from '../reducers'; import { createReducerBehaviors } from '../reducers';
import { PROP_TYPES } from '../structure'; import { PROP_TYPES } from '../structure';
@ -119,16 +118,16 @@ export function createReducer(objectStructure: StructureType, behaviors: ShapeRe
if (matchedBehaviors.length) { if (matchedBehaviors.length) {
//Sanitize the payload using the reducer shape, then apply the sanitized //Sanitize the payload using the reducer shape, then apply the sanitized
//payload to the state using the behavior linked to this action type. //payload to the state using the behavior linked to this action type.
return reduce((interimState, matchedBehavior) => merge( return reduce((interimState, matchedBehavior) => ({
interimState, ...interimState,
behaviors[matchedBehavior.type].reducer( ...behaviors[matchedBehavior.type].reducer(
interimState, interimState,
behaviors[matchedBehavior.type].validate behaviors[matchedBehavior.type].validate
? validateShape(objectStructure, matchedBehavior.payload) ? validateShape(objectStructure, matchedBehavior.payload)
: matchedBehavior.payload, : matchedBehavior.payload,
initialState initialState
) )
), state)(matchedBehaviors); }), state)(matchedBehaviors);
} }
return state; return state;

View File

@ -45,6 +45,7 @@ export function validateShape(objectStructure: any, value: mixed): Object {
} }
const validatedValue = getTypeValidation(valueType().type)(valueType, value); const validatedValue = getTypeValidation(valueType().type)(valueType, value);
if (validatedValue === undefined) { if (validatedValue === undefined) {
console.warn(`The property, ${name}, was populated with a type ${ typeof value } which does not` + console.warn(`The property, ${name}, was populated with a type ${ typeof value } which does not` +
` match that specified in the reducer configuration ${ wildcardKeyPresent ? ', nor did it match a wildcardKey': ''}. It has been stripped from` + ` match that specified in the reducer configuration ${ wildcardKeyPresent ? ', nor did it match a wildcardKey': ''}. It has been stripped from` +