diff --git a/src/__tests__/reducers.test.js b/src/__tests__/reducers.test.js index f43dbc5..951e6e3 100644 --- a/src/__tests__/reducers.test.js +++ b/src/__tests__/reducers.test.js @@ -51,7 +51,7 @@ describe('reducers', () => { describe('determineReducerType', () => { it('should return the correct creator function for the default mapping', () => { - forEach(omit(Types, 'reducer'), structureType => { + forEach(omit(Types, 'reducer', 'wildcardKey'), structureType => { const returnVal = determineReducerType(Types.reducer(structureType()), { name: 'toast', locationString: 'toasty', diff --git a/src/__tests__/validatePayload.test.js b/src/__tests__/validatePayload.test.js index f4879ae..2dc7152 100644 --- a/src/__tests__/validatePayload.test.js +++ b/src/__tests__/validatePayload.test.js @@ -3,7 +3,9 @@ import { validatePrimitive, validateShape, validateArray, - getTypeValidation + getTypeValidation, + hasWildcardKey, + getValueType, } from '../validatePayload'; describe('Validation functionality', () => { @@ -91,7 +93,38 @@ describe('Validation functionality', () => { test1: {}, test2: 'bar', }); - }) + }); + + + const testObjectStructure4 = Types.shape({ + test2: Types.string(), + [Types.wildcardKey()]: Types.string(), + }); + const testObjectStructure5 = Types.shape({ + test2: Types.string(), + [Types.wildcardKey()]: Types.any(), + }); + it('Should, if a key is not specified, see if the key matches the wildcard type, and apply if true', () => { + expect(validateShape(testObjectStructure4, { test1: 'foo', test2: 'bar' })).toEqual({ + test1: 'foo', + test2: 'bar', + }); + + expect(validateShape(testObjectStructure5, { test1: 0, test2: 'bar' })).toEqual({ + test1: 0, + test2: 'bar', + }); + }); + + const testObjectStructure6 = Types.shape({ + test2: Types.string(), + [Types.wildcardKey()]: Types.string(), + }); + it('Should, if a key is not specified, and does not match the wildcardKey, strip it out', () => { + expect(validateShape(testObjectStructure6, { test1: 0, test2: 'bar' })).toEqual({ + test2: 'bar', + }); + }); }); describe('Non covered types', () => { @@ -100,4 +133,50 @@ describe('Validation functionality', () => { }); }); + describe('Has wildcard value', () => { + const testObjectStructure = Types.shape({ + test1: Types.string(), + test2: Types.number(), + [Types.wildcardKey()]: Types.any(), + }); + + const testObjectStructure2 = Types.shape({ + test1: Types.string(), + test2: Types.number(), + }); + + it('should return true if the objectStructure passed in has a wildcard key', () => { + expect(hasWildcardKey(testObjectStructure)).toBe(true); + }); + + it('should return false if no wildcard key passed in', () => { + expect(hasWildcardKey(testObjectStructure2)).toBe(false); + }); + }); + + describe('GetValueType', () => { + const testObjectStructure = Types.shape({ + test1: Types.string(), + test2: Types.number(), + [Types.wildcardKey()]: Types.number(), + }); + + const testObjectStructure2 = Types.shape({ + test1: Types.string(), + test2: Types.number(), + }); + + it('should return the correct type for a key that is present, if no wildcard present', () => { + expect(getValueType(testObjectStructure, 'test1', false)().type).toEqual(Types.string()().type); + }); + + it('should return the wildcard value if key not present and wildcard is', () => { + expect(getValueType(testObjectStructure, 'test3', true)().type).toEqual(Types.number()().type); + }); + + it('should return undefined if no wildcard or matching key', () => { + expect(getValueType(testObjectStructure, 'test3', false)).toEqual(undefined); + }); + }); + }); diff --git a/src/reducers/arrayReducer.js b/src/reducers/arrayReducer.js index bfa76b2..1bf7254 100644 --- a/src/reducers/arrayReducer.js +++ b/src/reducers/arrayReducer.js @@ -47,7 +47,7 @@ import { updateAtIndex, removeAtIndex } from '../utils/arrayUtils'; import { PROP_TYPES } from '../structure'; -function checkIndex(index: ?number, payload: any = '', behaviorName: string = ''): boolean { +function checkIndex(index: ?number, payload: any = '', behaviorName: string): boolean { if (!isNumber(index) || index === -1) { console.warn(`Index not passed to ${behaviorName} for payload ${payload}.`); return false; diff --git a/src/structure.js b/src/structure.js index f67ffef..3c87948 100644 --- a/src/structure.js +++ b/src/structure.js @@ -43,6 +43,7 @@ export const PROP_TYPES = { _shape: '_shape', _array: '_array', _any: '_any', + _wildcardKey: '_wildcardKey', }; //The types objects are used in order to build up the structure of a store chunk, and provide/accept @@ -81,4 +82,5 @@ export const Types: TypesObject = { type: PROP_TYPES._shape, structure, }), + wildcardKey: () => PROP_TYPES._wildcardKey, }; diff --git a/src/validatePayload.js b/src/validatePayload.js index 7b15b36..00dd0f4 100644 --- a/src/validatePayload.js +++ b/src/validatePayload.js @@ -12,6 +12,18 @@ type validationFunction = (structure: StructureType | PrimitiveType | ShapeStruc import reduce from 'lodash/reduce'; import isObject from 'lodash/isObject'; import { PROP_TYPES } from './structure'; +const find = require('lodash/fp/find').convert({ cap: false }); + + +export const hasWildcardKey = (objectStructure: any) => + !!find((prop, key) => key === PROP_TYPES._wildcardKey)(objectStructure().structure); + + +export const getValueType = (objectStructure: any, key: string, wildcardKeyPresent: boolean) => + wildcardKeyPresent + ? objectStructure().structure[key] || objectStructure().structure[PROP_TYPES._wildcardKey] + : objectStructure().structure[key]; + export function validateShape(objectStructure: any, value: mixed): Object { if (!isObject(value)) { @@ -19,20 +31,23 @@ export function validateShape(objectStructure: any, value: mixed): Object { return {}; } + const wildcardKeyPresent = hasWildcardKey(objectStructure); + return reduce(value, (memo, value, name) => { - const valueType = objectStructure().structure[name]; - //If the value type does not exist in the reducer structure, we don't want to include it in the payload. + const valueType = getValueType(objectStructure, name, wildcardKeyPresent); + //If the value type does not exist in the reducer structure, and there's no wildcard key, then + //we don't want to include it in the payload. //Display a console error for the developer, and skip the inclusion of this property in the payload. if (!valueType) { console.warn(`The property, ${name}, was not specified in the structure` + - ' and was stripped out of the payload. Structure: ', objectStructure().structure); + ` and was stripped out of the payload. Structure: ${ objectStructure().structure }`); return memo; } 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. It has been stripped from' + + ` match that specified in the reducer configuration ${ wildcardKeyPresent ? ', nor did it match a wildcardKey': ''}. It has been stripped from` + ' the payload'); return memo; }