Skip to content

Commit

Permalink
Add exact type checker from react native (#41)
Browse files Browse the repository at this point in the history
  • Loading branch information
jamiebuilds authored and aweary committed Jun 6, 2017
1 parent f2cf875 commit 8265af5
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 1 deletion.
107 changes: 107 additions & 0 deletions __tests__/PropTypesDevelopmentReact15.js
Expand Up @@ -1045,6 +1045,113 @@ describe('PropTypesDevelopmentReact15', () => {
});
});

describe('Exact Types', () => {
it('should warn for non objects', () => {
typeCheckFail(
PropTypes.exact({}),
'some string',
'Invalid prop `testProp` of type `string` supplied to ' +
'`testComponent`, expected `object`.',
);
typeCheckFail(
PropTypes.exact({}),
['array'],
'Invalid prop `testProp` of type `array` supplied to ' +
'`testComponent`, expected `object`.',
);
});

it('should not warn for empty values', () => {
typeCheckPass(PropTypes.exact({}), undefined);
typeCheckPass(PropTypes.exact({}), null);
typeCheckPass(PropTypes.exact({}), {});
});

it('should not warn for an empty object', () => {
typeCheckPass(PropTypes.exact({}).isRequired, {});
});

it('should warn for non specified types', () => {
typeCheckFail(
PropTypes.exact({}),
{key: 1},
'Warning: Failed prop type: Invalid prop `testProp` key `key` supplied to `testComponent`.' +
'\nBad object: {' +
'\n \"key\": 1' +
'\n}' +
'\nValid keys: []'
);
});

it('should not warn for valid types', () => {
typeCheckPass(PropTypes.exact({key: PropTypes.number}), {key: 1});
});

it('should warn for required valid types', () => {
typeCheckFail(
PropTypes.exact({key: PropTypes.number.isRequired}),
{},
'The prop `testProp.key` is marked as required in `testComponent`, ' +
'but its value is `undefined`.',
);
});

it('should warn for the first required type', () => {
typeCheckFail(
PropTypes.exact({
key: PropTypes.number.isRequired,
secondKey: PropTypes.number.isRequired,
}),
{},
'The prop `testProp.key` is marked as required in `testComponent`, ' +
'but its value is `undefined`.',
);
});

it('should warn for invalid key types', () => {
typeCheckFail(
PropTypes.exact({key: PropTypes.number}),
{key: 'abc'},
'Invalid prop `testProp.key` of type `string` supplied to `testComponent`, ' +
'expected `number`.',
);
});

it('should be implicitly optional and not warn without values', () => {
typeCheckPass(
PropTypes.exact(PropTypes.exact({key: PropTypes.number})),
null,
);
typeCheckPass(
PropTypes.exact(PropTypes.exact({key: PropTypes.number})),
undefined,
);
});

it('should warn for missing required values', () => {
typeCheckFailRequiredValues(
PropTypes.exact({key: PropTypes.number}).isRequired,
);
});

it('should warn if called manually in development', () => {
spyOn(console, 'error');
expectWarningInDevelopment(PropTypes.exact({}), 'some string');
expectWarningInDevelopment(PropTypes.exact({foo: PropTypes.number}), {
foo: 42,
});
expectWarningInDevelopment(
PropTypes.exact({key: PropTypes.number}).isRequired,
null,
);
expectWarningInDevelopment(
PropTypes.exact({key: PropTypes.number}).isRequired,
undefined,
);
expectWarningInDevelopment(PropTypes.element, <div />);
});
});

describe('Symbol Type', () => {
it('should warn for non-symbol', () => {
typeCheckFail(
Expand Down
33 changes: 32 additions & 1 deletion factoryWithTypeCheckers.js
Expand Up @@ -110,7 +110,8 @@ module.exports = function(isValidElement, throwOnDirectAccess) {
objectOf: createObjectOfTypeChecker,
oneOf: createEnumTypeChecker,
oneOfType: createUnionTypeChecker,
shape: createShapeTypeChecker
shape: createShapeTypeChecker,
exact: createStrictShapeTypeChecker,
};

/**
Expand Down Expand Up @@ -379,6 +380,36 @@ module.exports = function(isValidElement, throwOnDirectAccess) {
return createChainableTypeChecker(validate);
}

function createStrictShapeTypeChecker(shapeTypes) {
function validate(props, propName, componentName, location, propFullName) {
var propValue = props[propName];
var propType = getPropType(propValue);
if (propType !== 'object') {
return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type `' + propType + '` ' + ('supplied to `' + componentName + '`, expected `object`.'));
}
// We need to check all keys in case some are required but missing from
// props.
var allKeys = Object.assign({}, props[propName], shapeTypes);
for (var key in allKeys) {
var checker = shapeTypes[key];
if (!checker) {
return new PropTypeError(
'Invalid ' + location + ' `' + propFullName + '` key `' + key + '` supplied to `' + componentName + '`.' +
'\nBad object: ' + JSON.stringify(props[propName], null, ' ') +
'\nValid keys: ' + JSON.stringify(Object.keys(shapeTypes), null, ' ')
);
}
var error = checker(propValue, key, componentName, location, propFullName + '.' + key, ReactPropTypesSecret);
if (error) {
return error;
}
}
return null;
}

return createChainableTypeChecker(validate);
}

function isNode(propValue) {
switch (typeof propValue) {
case 'number':
Expand Down

0 comments on commit 8265af5

Please sign in to comment.