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": {
"jest-cli": {
"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=",
"dev": true
}

View File

@ -42,6 +42,9 @@ describe('buildStoreChunk', () => {
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')));
@ -51,7 +54,7 @@ describe('buildStoreChunk', () => {
}));
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', () => {
expect(isFunction(nonNestedChunk.selectors)).toBe(true);
@ -69,7 +72,7 @@ describe('buildStoreChunk', () => {
describe('Actions', () => {
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', () => {
expect(Object.keys(nonNestedChunk.actions)).toEqual(['replace', 'reset']);
@ -97,6 +100,14 @@ describe('buildStoreChunk', () => {
store.dispatch(chunk.actions.nested1.reset());
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)', () => {

View File

@ -65,7 +65,10 @@ describe('Validation functionality', () => {
});
it('Array should return an empty array if a non-array is passed', () => {
expect(validateArray('foo')).toEqual([]);
})
});
it('Array should allow an empty array', () => {
expect(validateArray([])).toEqual([]);
});
});
describe('Objects', () => {
@ -138,6 +141,16 @@ describe('Validation functionality', () => {
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', () => {

View File

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

View File

@ -45,6 +45,7 @@ export function validateShape(objectStructure: any, value: mixed): Object {
}
const validatedValue = getTypeValidation(valueType().type)(valueType, value);
if (validatedValue === undefined) {
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` +