Add new array methods, and remove unnecessary validation calls

This commit is contained in:
Kai Moseley 2017-01-22 20:13:28 +00:00
parent efa441f62e
commit 1cd4cbf6bf
7 changed files with 111 additions and 16 deletions

View File

@ -99,9 +99,14 @@ describe('reducers', () => {
'toast': {
reducer: 'foo',
action: 'bar',
validate: true,
}
}, 'location')).toEqual({
'location.toast': 'foo',
'location.toast': {
reducer: 'foo',
action: 'bar',
validate: true,
},
})
});
});

View File

@ -97,7 +97,7 @@ export function createReducerBehaviors(behaviorsConfig: { [key: string]: { reduc
//the location string/name combination, so will match up 1:1.
return reduce(behaviorsConfig, (memo, behavior, name) => ({
...memo,
[`${locationString}.${name}`]: behavior.reducer,
[`${locationString}.${name}`]: behavior,
}), {});
}

View File

@ -64,6 +64,33 @@ describe('arrayReducer', () => {
});
});
describe('push', () => {
const { push } = DEFAULT_ARRAY_BEHAVIORS;
it('should push the payload onto the end of the array', () => {
expect(push.reducer([1,2,3], 4)).toEqual([1,2,3,4]);
});
});
describe('pop', () => {
const { pop } = DEFAULT_ARRAY_BEHAVIORS;
it('should remove the last element from the array', () => {
expect(pop.reducer([1,2,3])).toEqual([1,2]);
});
});
describe('unshift', () => {
const { unshift } = DEFAULT_ARRAY_BEHAVIORS;
it('should add the payload to the beginning of the array', () => {
expect(unshift.reducer([1,2,3], 4)).toEqual([4,1,2,3]);
});
});
describe('shift', () => {
const { shift } = DEFAULT_ARRAY_BEHAVIORS;
it('should remove the first element of the array', () => {
expect(shift.reducer([1,2,3])).toEqual([2,3]);
});
});
});
describe('applyValidation', () => {
@ -82,7 +109,7 @@ describe('arrayReducer', () => {
});
describe('createReducer', () => {
const arrayStructure = Types.arrayOf(Types.number());
const arrayStructure = Types.arrayOf(Types.number(), [1,2,3,4]);
const reducer = createReducer(arrayStructure, createReducerBehaviors(DEFAULT_ARRAY_BEHAVIORS, 'string'));
it('should call the correct behavior', () => {
@ -92,6 +119,12 @@ describe('arrayReducer', () => {
index: 0,
})).toEqual([4,2,3]);
});
it('do not trigger validation if not required', () => {
expect(reducer([1,2], {
type: 'string.reset',
})).toEqual([1,2,3,4]);
});
});
});

View File

@ -71,6 +71,12 @@ describe('ObjectReducer', () => {
payload: { foo: 'toast'}
})).toEqual({ foo: 'toast', bar: { baz: 5 }});
});
it('do not trigger validation if not required', () => {
expect(reducer({ foo: 'foo', bar: { baz: 6 }}, {
type: 'string.reset',
})).toEqual({ foo: 'foo', bar: { baz: 5 }});
});
});
});

View File

@ -18,6 +18,7 @@ export type ArrayReducerBehaviorsConfig = {
[key: string]: {
action?: (value: any) => any,
reducer: ArrayReducerBehavior,
validate: boolean,
}
};
export type ArrayReducerBehaviors = {
@ -69,32 +70,61 @@ export const DEFAULT_ARRAY_BEHAVIORS: ArrayReducerBehaviorsConfig = {
if (!checkIndex(index, payload, 'updateAtIndex')) return state;
if (payload === undefined) return console.warn('Undefined was passed when updating index. Update not performed') || state;
return updateAtIndex(state, payload, index);
}
},
validate: true,
},
resetAtIndex: {
reducer(state, payload, initialState, index) {
if (!checkIndex(index, payload, 'resetAtIndex')) return state;
return updateAtIndex(state, initialState, index);
}
},
validate: false,
},
removeAtIndex: {
reducer(state, payload, initialState, index) {
if (!checkIndex(index, payload, 'removeAtIndex')) return state;
return removeAtIndex(state, index);
}
},
validate: false,
},
//Whole array behaviors.
replace: {
reducer(state, payload) {
if(!isArray(payload)) return console.warn('An array must be provided when replacing an array') || state;
return payload;
}
},
validate: true,
},
reset: {
reducer(state, payload, initialState) {
return initialState;
}
},
validate: false,
},
push: {
reducer(state, payload) {
return [...state, payload];
},
validate: true,
},
pop: {
reducer(state) {
return state.slice(0, -1);
},
validate: false,
},
unshift: {
reducer(state, payload) {
return [payload, ...state];
},
validate: true,
},
shift: {
reducer(state) {
return state.slice(1);
},
validate: false,
}
};
@ -123,7 +153,12 @@ export function createReducer(arrayTypeDescription: ArrayStructureType, behavior
//Validating the payload of an array is more tricky, as we do not know ahead of time if the
//payload should be an object, primitive, or an array. However, we can still validate here based on the
//payload type passed.
return behaviors[type](state, applyValidation(arrayTypeDescription, payload), initialValue, index);
return behaviors[type].reducer(
state,
behaviors[type].validate ? applyValidation(arrayTypeDescription, payload) : payload,
initialValue,
index
);
}
}

View File

@ -11,6 +11,7 @@ import type { StructureType } from '../structure';
export type ShapeReducerAction = {
type: string,
payload: Object,
validate: boolean,
};
export type ShapeReducer = (state: Object, action: ShapeReducerAction) => Object;
export type ShapeReducerBehavior = (state: {}, payload: Object | void, initialState: {}) => Object;
@ -55,18 +56,21 @@ export const DEFAULT_SHAPE_BEHAVIORS: ShapeReducerBehaviorsConfig = {
reducer(state, payload) {
if (!isObject(payload)) return state;
return { ...state, ...payload };
}
},
validate: true,
},
reset: {
reducer(state, payload, initialState) {
return initialState;
}
},
validate: false,
},
replace: {
reducer(state, payload) {
if (!payload) return state;
return payload;
}
},
validate: true,
}
};
@ -102,7 +106,11 @@ export function createReducer(objectStructure: StructureType, behaviors: ShapeRe
//Sanitize the payload using the reducer shape, then apply the sanitized
//payload to the state using the behavior linked to this action type.
return behaviors[type](state, validateShape(objectStructure, payload), initialState);
return behaviors[type].reducer(
state,
behaviors[type].validate ? validateShape(objectStructure, payload) : payload,
initialState
);
}
}

View File

@ -17,6 +17,7 @@ export type PrimitiveReducerBehaviorsConfig = {
[key: string]: {
action?: (value: mixed) => mixed,
reducer: PrimitiveReducerBehavior,
validate: boolean,
}
};
export type PrimitiveReducerBehaviors = {
@ -52,12 +53,14 @@ export const DEFAULT_PRIMITIVE_BEHAVIORS: PrimitiveReducerBehaviorsConfig = {
reducer(state, payload) {
if (payload === undefined) return state;
return payload;
}
},
validate: true,
},
reset: {
reducer(state, payload, initialState) {
return initialState;
}
},
validate: false,
},
};
@ -76,6 +79,7 @@ export function createPrimitiveReducer(primitiveType: PrimitiveType, {
function createReducer(primitiveType: PrimitiveType, behaviors: PrimitiveReducerBehaviors): PrimitiveReducer {
//Calculate and validate the initial state of the reducer
const initialState: mixed = validatePrimitive(primitiveType, primitiveType().defaultValue);
return (state = initialState, { type, payload }: PrimitiveReducerAction) => {
//If the action type does not match any of the specified behaviors, just return the current state.
@ -83,7 +87,11 @@ function createReducer(primitiveType: PrimitiveType, behaviors: PrimitiveReducer
//Sanitize the payload using the reducer shape, then apply the sanitized
//payload to the state using the behavior linked to this action type.
return behaviors[type](state, validatePrimitive(primitiveType, payload), initialState);
return behaviors[type].reducer(
state,
behaviors[type].validate ? validatePrimitive(primitiveType, payload) : payload,
initialState
);
}
}