Skip to content
This repository has been archived by the owner on Sep 1, 2024. It is now read-only.

Commit

Permalink
Re-add exact type checker (#87)
Browse files Browse the repository at this point in the history
* Add exact type checker from react native

* Add shim for exact in factoryWithThrowingShims

This needs to be kept in sync with the list in factoryWithTypeCheckers

* Polyfill Object.assign with object-assign package
  • Loading branch information
aweary authored Aug 17, 2017
1 parent ab5994f commit 9057907
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 4 deletions.
107 changes: 107 additions & 0 deletions __tests__/PropTypesDevelopmentReact15.js
Original file line number Diff line number Diff line change
Expand Up @@ -1062,6 +1062,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
3 changes: 2 additions & 1 deletion factoryWithThrowingShims.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ module.exports = function() {
objectOf: getShim,
oneOf: getShim,
oneOfType: getShim,
shape: getShim
shape: getShim,
exact: getShim
};

ReactPropTypes.checkPropTypes = emptyFunction;
Expand Down
34 changes: 33 additions & 1 deletion factoryWithTypeCheckers.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
var emptyFunction = require('fbjs/lib/emptyFunction');
var invariant = require('fbjs/lib/invariant');
var warning = require('fbjs/lib/warning');
var assign = require('object-assign');

var ReactPropTypesSecret = require('./lib/ReactPropTypesSecret');
var checkPropTypes = require('./checkPropTypes');
Expand Down Expand Up @@ -110,7 +111,8 @@ module.exports = function(isValidElement, throwOnDirectAccess) {
objectOf: createObjectOfTypeChecker,
oneOf: createEnumTypeChecker,
oneOfType: createUnionTypeChecker,
shape: createShapeTypeChecker
shape: createShapeTypeChecker,
exact: createStrictShapeTypeChecker,
};

/**
Expand Down Expand Up @@ -379,6 +381,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 = 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
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
"homepage": "https://facebook.github.io/react/",
"dependencies": {
"fbjs": "^0.8.9",
"loose-envify": "^1.3.1"
"loose-envify": "^1.3.1",
"object-assign": "^4.1.1"
},
"scripts": {
"test": "jest",
Expand Down
2 changes: 1 addition & 1 deletion yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2027,7 +2027,7 @@ oauth-sign@~0.8.1:
version "0.8.2"
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"

object-assign@^4.1.0:
object-assign@^4.1.0, object-assign@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"

Expand Down

0 comments on commit 9057907

Please sign in to comment.