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

Commit 9057907

Browse files
authored
Re-add exact type checker (#87)
* 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
1 parent ab5994f commit 9057907

5 files changed

+145
-4
lines changed

__tests__/PropTypesDevelopmentReact15.js

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1062,6 +1062,113 @@ describe('PropTypesDevelopmentReact15', () => {
10621062
});
10631063
});
10641064

1065+
describe('Exact Types', () => {
1066+
it('should warn for non objects', () => {
1067+
typeCheckFail(
1068+
PropTypes.exact({}),
1069+
'some string',
1070+
'Invalid prop `testProp` of type `string` supplied to ' +
1071+
'`testComponent`, expected `object`.',
1072+
);
1073+
typeCheckFail(
1074+
PropTypes.exact({}),
1075+
['array'],
1076+
'Invalid prop `testProp` of type `array` supplied to ' +
1077+
'`testComponent`, expected `object`.',
1078+
);
1079+
});
1080+
1081+
it('should not warn for empty values', () => {
1082+
typeCheckPass(PropTypes.exact({}), undefined);
1083+
typeCheckPass(PropTypes.exact({}), null);
1084+
typeCheckPass(PropTypes.exact({}), {});
1085+
});
1086+
1087+
it('should not warn for an empty object', () => {
1088+
typeCheckPass(PropTypes.exact({}).isRequired, {});
1089+
});
1090+
1091+
it('should warn for non specified types', () => {
1092+
typeCheckFail(
1093+
PropTypes.exact({}),
1094+
{key: 1},
1095+
'Warning: Failed prop type: Invalid prop `testProp` key `key` supplied to `testComponent`.' +
1096+
'\nBad object: {' +
1097+
'\n \"key\": 1' +
1098+
'\n}' +
1099+
'\nValid keys: []'
1100+
);
1101+
});
1102+
1103+
it('should not warn for valid types', () => {
1104+
typeCheckPass(PropTypes.exact({key: PropTypes.number}), {key: 1});
1105+
});
1106+
1107+
it('should warn for required valid types', () => {
1108+
typeCheckFail(
1109+
PropTypes.exact({key: PropTypes.number.isRequired}),
1110+
{},
1111+
'The prop `testProp.key` is marked as required in `testComponent`, ' +
1112+
'but its value is `undefined`.',
1113+
);
1114+
});
1115+
1116+
it('should warn for the first required type', () => {
1117+
typeCheckFail(
1118+
PropTypes.exact({
1119+
key: PropTypes.number.isRequired,
1120+
secondKey: PropTypes.number.isRequired,
1121+
}),
1122+
{},
1123+
'The prop `testProp.key` is marked as required in `testComponent`, ' +
1124+
'but its value is `undefined`.',
1125+
);
1126+
});
1127+
1128+
it('should warn for invalid key types', () => {
1129+
typeCheckFail(
1130+
PropTypes.exact({key: PropTypes.number}),
1131+
{key: 'abc'},
1132+
'Invalid prop `testProp.key` of type `string` supplied to `testComponent`, ' +
1133+
'expected `number`.',
1134+
);
1135+
});
1136+
1137+
it('should be implicitly optional and not warn without values', () => {
1138+
typeCheckPass(
1139+
PropTypes.exact(PropTypes.exact({key: PropTypes.number})),
1140+
null,
1141+
);
1142+
typeCheckPass(
1143+
PropTypes.exact(PropTypes.exact({key: PropTypes.number})),
1144+
undefined,
1145+
);
1146+
});
1147+
1148+
it('should warn for missing required values', () => {
1149+
typeCheckFailRequiredValues(
1150+
PropTypes.exact({key: PropTypes.number}).isRequired,
1151+
);
1152+
});
1153+
1154+
it('should warn if called manually in development', () => {
1155+
spyOn(console, 'error');
1156+
expectWarningInDevelopment(PropTypes.exact({}), 'some string');
1157+
expectWarningInDevelopment(PropTypes.exact({foo: PropTypes.number}), {
1158+
foo: 42,
1159+
});
1160+
expectWarningInDevelopment(
1161+
PropTypes.exact({key: PropTypes.number}).isRequired,
1162+
null,
1163+
);
1164+
expectWarningInDevelopment(
1165+
PropTypes.exact({key: PropTypes.number}).isRequired,
1166+
undefined,
1167+
);
1168+
expectWarningInDevelopment(PropTypes.element, <div />);
1169+
});
1170+
});
1171+
10651172
describe('Symbol Type', () => {
10661173
it('should warn for non-symbol', () => {
10671174
typeCheckFail(

factoryWithThrowingShims.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ module.exports = function() {
4949
objectOf: getShim,
5050
oneOf: getShim,
5151
oneOfType: getShim,
52-
shape: getShim
52+
shape: getShim,
53+
exact: getShim
5354
};
5455

5556
ReactPropTypes.checkPropTypes = emptyFunction;

factoryWithTypeCheckers.js

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
var emptyFunction = require('fbjs/lib/emptyFunction');
1313
var invariant = require('fbjs/lib/invariant');
1414
var warning = require('fbjs/lib/warning');
15+
var assign = require('object-assign');
1516

1617
var ReactPropTypesSecret = require('./lib/ReactPropTypesSecret');
1718
var checkPropTypes = require('./checkPropTypes');
@@ -110,7 +111,8 @@ module.exports = function(isValidElement, throwOnDirectAccess) {
110111
objectOf: createObjectOfTypeChecker,
111112
oneOf: createEnumTypeChecker,
112113
oneOfType: createUnionTypeChecker,
113-
shape: createShapeTypeChecker
114+
shape: createShapeTypeChecker,
115+
exact: createStrictShapeTypeChecker,
114116
};
115117

116118
/**
@@ -379,6 +381,36 @@ module.exports = function(isValidElement, throwOnDirectAccess) {
379381
return createChainableTypeChecker(validate);
380382
}
381383

384+
function createStrictShapeTypeChecker(shapeTypes) {
385+
function validate(props, propName, componentName, location, propFullName) {
386+
var propValue = props[propName];
387+
var propType = getPropType(propValue);
388+
if (propType !== 'object') {
389+
return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type `' + propType + '` ' + ('supplied to `' + componentName + '`, expected `object`.'));
390+
}
391+
// We need to check all keys in case some are required but missing from
392+
// props.
393+
var allKeys = assign({}, props[propName], shapeTypes);
394+
for (var key in allKeys) {
395+
var checker = shapeTypes[key];
396+
if (!checker) {
397+
return new PropTypeError(
398+
'Invalid ' + location + ' `' + propFullName + '` key `' + key + '` supplied to `' + componentName + '`.' +
399+
'\nBad object: ' + JSON.stringify(props[propName], null, ' ') +
400+
'\nValid keys: ' + JSON.stringify(Object.keys(shapeTypes), null, ' ')
401+
);
402+
}
403+
var error = checker(propValue, key, componentName, location, propFullName + '.' + key, ReactPropTypesSecret);
404+
if (error) {
405+
return error;
406+
}
407+
}
408+
return null;
409+
}
410+
411+
return createChainableTypeChecker(validate);
412+
}
413+
382414
function isNode(propValue) {
383415
switch (typeof propValue) {
384416
case 'number':

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727
"homepage": "https://facebook.github.io/react/",
2828
"dependencies": {
2929
"fbjs": "^0.8.9",
30-
"loose-envify": "^1.3.1"
30+
"loose-envify": "^1.3.1",
31+
"object-assign": "^4.1.1"
3132
},
3233
"scripts": {
3334
"test": "jest",

yarn.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2027,7 +2027,7 @@ oauth-sign@~0.8.1:
20272027
version "0.8.2"
20282028
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
20292029

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

0 commit comments

Comments
 (0)