fix(object): retain deletions to properties/array elements
This commit is contained in:
parent
cb4f155a29
commit
09e5b9c922
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)', () => {
|
||||||
|
|
|
@ -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', () => {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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` +
|
||||||
|
|
Loading…
Reference in New Issue