Skip to content

Commit

Permalink
add mapShape prop type
Browse files Browse the repository at this point in the history
  • Loading branch information
hartzis committed Feb 12, 2016
1 parent e923003 commit 601c1dd
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 6 deletions.
24 changes: 18 additions & 6 deletions src/ImmutablePropTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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;
Expand All @@ -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;
118 changes: 118 additions & 0 deletions src/__tests__/ImmutablePropTypes-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.'
);
});
});
});

0 comments on commit 601c1dd

Please sign in to comment.