From 601c1ddbec6223abf46d5dc0cfa3f18b056f6733 Mon Sep 17 00:00:00 2001 From: Brian Emil Hartz Date: Thu, 11 Feb 2016 17:52:57 -0700 Subject: [PATCH] add mapShape prop type --- src/ImmutablePropTypes.js | 24 +++-- src/__tests__/ImmutablePropTypes-test.js | 118 +++++++++++++++++++++++ 2 files changed, 136 insertions(+), 6 deletions(-) diff --git a/src/ImmutablePropTypes.js b/src/ImmutablePropTypes.js index 72402dc..ac16216 100644 --- a/src/ImmutablePropTypes.js +++ b/src/ImmutablePropTypes.js @@ -17,8 +17,9 @@ var ImmutablePropTypes = { stackOf: createStackOfTypeChecker, iterableOf: createIterableOfTypeChecker, recordOf: createRecordOfTypeChecker, - shape: createShapeTypeChecker, - contains: createShapeTypeChecker, + shape: createShapeChecker, + contains: createContainsChecker, + mapShape: createMapShapeChecker, // Primitive Types list: createImmutableTypeChecker('List', Immutable.List.isList), map: createImmutableTypeChecker('Map', Immutable.Map.isMap), @@ -171,23 +172,23 @@ function createRecordOfTypeChecker(recordKeys) { } // there is some irony in the fact that shapeTypes is a standard hash and not an immutable collection -function createShapeTypeChecker(shapeTypes) { +function createShapeTypeChecker(shapeTypes, immutableClassName = 'Iterable', immutableClassTypeValidator = Immutable.Iterable.isIterable) { function validate(props, propName, componentName, location) { var propValue = props[propName]; var propType = getPropType(propValue); - if (!Immutable.Iterable.isIterable(propValue)) { + if (!immutableClassTypeValidator(propValue)) { var locationName = location; return new Error( `Invalid ${locationName} \`${propName}\` of type \`${propType}\` ` + - `supplied to \`${componentName}\`, expected an Immutable.js Iterable.` + `supplied to \`${componentName}\`, expected an Immutable.js ${immutableClassName}.` ); } + var mutablePropValue = propValue.toJS(); for (var key in shapeTypes) { var checker = shapeTypes[key]; if (!checker) { continue; } - var mutablePropValue = propValue.toObject(); var error = checker(mutablePropValue, key, componentName, location); if (error) { return error; @@ -197,5 +198,16 @@ function createShapeTypeChecker(shapeTypes) { return createChainableTypeChecker(validate); } +function createShapeChecker(shapeTypes) { + return createShapeTypeChecker(shapeTypes); +} + +function createContainsChecker(shapeTypes) { + return createShapeTypeChecker(shapeTypes); +} + +function createMapShapeChecker(shapeTypes) { + return createShapeTypeChecker(shapeTypes, 'Map', Immutable.Map.isMap); +} module.exports = ImmutablePropTypes; diff --git a/src/__tests__/ImmutablePropTypes-test.js b/src/__tests__/ImmutablePropTypes-test.js index df55cb6..28e0676 100644 --- a/src/__tests__/ImmutablePropTypes-test.js +++ b/src/__tests__/ImmutablePropTypes-test.js @@ -1235,4 +1235,122 @@ describe('ImmutablePropTypes', function() { typeCheckPass(PropTypes.contains(contains), Immutable.List([1, '2'])); }); }); + + describe('MapShape Types', function() { + it('should warn for non objects', function() { + typeCheckFail( + PropTypes.mapShape({}), + 'some string', + 'Invalid prop `testProp` of type `string` supplied to ' + + '`testComponent`, expected an Immutable.js Map.' + ); + typeCheckFail( + PropTypes.mapShape({}), + ['array'], + 'Invalid prop `testProp` of type `array` supplied to ' + + '`testComponent`, expected an Immutable.js Map.' + ); + typeCheckFail( + PropTypes.mapShape({}), + {a: 1}, + 'Invalid prop `testProp` of type `object` supplied to ' + + '`testComponent`, expected an Immutable.js Map.' + ); + }); + + it('should not warn for empty values', function() { + typeCheckPass(PropTypes.mapShape({}), undefined); + typeCheckPass(PropTypes.mapShape({}), null); + typeCheckPass(PropTypes.mapShape({}), Immutable.fromJS({})); + }); + + it('should not warn for an empty Immutable object', function() { + typeCheckPass(PropTypes.mapShape({}).isRequired, Immutable.fromJS({})); + }); + + it('should not warn for non specified types', function() { + typeCheckPass(PropTypes.mapShape({}), Immutable.fromJS({key: 1})); + }); + + it('should not warn for valid types', function() { + typeCheckPass(PropTypes.mapShape({key: React.PropTypes.number}), Immutable.fromJS({key: 1})); + }); + + it('should not warn for nested valid types', function() { + typeCheckPass( + PropTypes.mapShape({ + data: React.PropTypes.arrayOf(React.PropTypes.shape({ + id: React.PropTypes.number.isRequired + })).isRequired + }), + Immutable.fromJS({data: [{id: 1}, {id: 2}]}) + ); + }); + + it('should ignore null keys', function() { + typeCheckPass(PropTypes.mapShape({key: null}), Immutable.fromJS({key: 1})); + }); + + it('should warn for required valid types', function() { + typeCheckFail( + PropTypes.mapShape({key: React.PropTypes.number.isRequired}), + Immutable.fromJS({}), + 'Required prop `key` was not specified in `testComponent`.' + ); + }); + + it('should warn for the first required type', function() { + typeCheckFail( + PropTypes.mapShape({ + key: React.PropTypes.number.isRequired, + secondKey: React.PropTypes.number.isRequired + }), + Immutable.fromJS({}), + 'Required prop `key` was not specified in `testComponent`.' + ); + }); + + it('should warn for invalid key types', function() { + typeCheckFail(PropTypes.mapShape({key: React.PropTypes.number}), + Immutable.fromJS({key: 'abc'}), + 'Invalid prop `key` of type `string` supplied to `testComponent`, ' + + 'expected `number`.' + ); + }); + + it('should be implicitly optional and not warn without values', function() { + typeCheckPass( + PropTypes.mapShape(PropTypes.mapShape({key: React.PropTypes.number})), null + ); + typeCheckPass( + PropTypes.mapShape(PropTypes.mapShape({key: React.PropTypes.number})), undefined + ); + }); + + it('should warn for missing required values', function() { + typeCheckFail( + PropTypes.mapShape({key: React.PropTypes.number}).isRequired, + null, + requiredMessage + ); + typeCheckFail( + PropTypes.mapShape({key: React.PropTypes.number}).isRequired, + undefined, + requiredMessage + ); + }); + + it('should not validate a list', function() { + var contains = { + 0: React.PropTypes.number.isRequired, + 1: React.PropTypes.string.isRequired, + 2: React.PropTypes.string + }; + typeCheckFail( + PropTypes.mapShape(contains), + Immutable.List([1, '2']), + 'Invalid prop `testProp` of type `Immutable.List` supplied to `testComponent`, expected an Immutable.js Map.' + ); + }); + }); });