Better warnings for nested propTypes

`arrayOf`, `shape` and `objectOf` warnings now display the full path of
the invalid key.
This commit is contained in:
Morhaus
2015-04-24 11:44:05 +02:00
parent 05b98ac70c
commit 12a43d4eee
3 changed files with 68 additions and 39 deletions

View File

@@ -48,22 +48,22 @@ describe('ReactLink', function() {
typeCheckFail(
LinkPropTypes.link(React.PropTypes.any),
{},
'Required prop `value` was not specified in `testComponent`.'
'Required prop `testProp.value` was not specified in `testComponent`.'
);
typeCheckFail(
LinkPropTypes.link(React.PropTypes.any),
{value: 123},
'Required prop `requestChange` was not specified in `testComponent`.'
'Required prop `testProp.requestChange` was not specified in `testComponent`.'
);
typeCheckFail(
LinkPropTypes.link(React.PropTypes.any),
{requestChange: emptyFunction},
'Required prop `value` was not specified in `testComponent`.'
'Required prop `testProp.value` was not specified in `testComponent`.'
);
typeCheckFail(
LinkPropTypes.link(React.PropTypes.any),
{value: null, requestChange: null},
'Required prop `value` was not specified in `testComponent`.'
'Required prop `testProp.value` was not specified in `testComponent`.'
);
});
@@ -104,7 +104,7 @@ describe('ReactLink', function() {
typeCheckFail(
LinkPropTypes.link(React.PropTypes.string),
{value: 123, requestChange: emptyFunction},
'Invalid prop `value` of type `number` supplied to `testComponent`,' +
'Invalid prop `testProp.value` of type `number` supplied to `testComponent`,' +
' expected `string`.'
);
});
@@ -148,7 +148,7 @@ describe('ReactLink', function() {
typeCheckFail(
LinkPropTypes.link(React.PropTypes.oneOfType([React.PropTypes.number])),
{value: 'imastring', requestChange: emptyFunction},
'Invalid prop `value` supplied to `testComponent`.'
'Invalid prop `testProp.value` supplied to `testComponent`.'
);
});
});

View File

@@ -86,19 +86,27 @@ var ReactPropTypes = {
};
function createChainableTypeChecker(validate) {
function checkType(isRequired, props, propName, componentName, location) {
function checkType(
isRequired,
props,
propName,
componentName,
location,
propFullName
) {
componentName = componentName || ANONYMOUS;
propFullName = propFullName || propName;
if (props[propName] == null) {
var locationName = ReactPropTypeLocationNames[location];
if (isRequired) {
return new Error(
`Required ${locationName} \`${propName}\` was not specified in ` +
`Required ${locationName} \`${propFullName}\` was not specified in ` +
`\`${componentName}\`.`
);
}
return null;
} else {
return validate(props, propName, componentName, location);
return validate(props, propName, componentName, location, propFullName);
}
}
@@ -109,7 +117,7 @@ function createChainableTypeChecker(validate) {
}
function createPrimitiveTypeChecker(expectedType) {
function validate(props, propName, componentName, location) {
function validate(props, propName, componentName, location, propFullName) {
var propValue = props[propName];
var propType = getPropType(propValue);
if (propType !== expectedType) {
@@ -120,8 +128,9 @@ function createPrimitiveTypeChecker(expectedType) {
var preciseType = getPreciseType(propValue);
return new Error(
`Invalid ${locationName} \`${propName}\` of type \`${preciseType}\` ` +
`supplied to \`${componentName}\`, expected \`${expectedType}\`.`
`Invalid ${locationName} \`${propFullName}\` of type ` +
`\`${preciseType}\` supplied to \`${componentName}\`, expected ` +
`\`${expectedType}\`.`
);
}
return null;
@@ -134,18 +143,24 @@ function createAnyTypeChecker() {
}
function createArrayOfTypeChecker(typeChecker) {
function validate(props, propName, componentName, location) {
function validate(props, propName, componentName, location, propFullName) {
var propValue = props[propName];
if (!Array.isArray(propValue)) {
var locationName = ReactPropTypeLocationNames[location];
var propType = getPropType(propValue);
return new Error(
`Invalid ${locationName} \`${propName}\` of type ` +
`Invalid ${locationName} \`${propFullName}\` of type ` +
`\`${propType}\` supplied to \`${componentName}\`, expected an array.`
);
}
for (var i = 0; i < propValue.length; i++) {
var error = typeChecker(propValue, i, componentName, location);
var error = typeChecker(
propValue,
i,
componentName,
location,
`${propFullName}[${i}]`
);
if (error instanceof Error) {
return error;
}
@@ -156,11 +171,11 @@ function createArrayOfTypeChecker(typeChecker) {
}
function createElementTypeChecker() {
function validate(props, propName, componentName, location) {
function validate(props, propName, componentName, location, propFullName) {
if (!ReactElement.isValidElement(props[propName])) {
var locationName = ReactPropTypeLocationNames[location];
return new Error(
`Invalid ${locationName} \`${propName}\` supplied to ` +
`Invalid ${locationName} \`${propFullName}\` supplied to ` +
`\`${componentName}\`, expected a single ReactElement.`
);
}
@@ -170,12 +185,12 @@ function createElementTypeChecker() {
}
function createInstanceTypeChecker(expectedClass) {
function validate(props, propName, componentName, location) {
function validate(props, propName, componentName, location, propFullName) {
if (!(props[propName] instanceof expectedClass)) {
var locationName = ReactPropTypeLocationNames[location];
var expectedClassName = expectedClass.name || ANONYMOUS;
return new Error(
`Invalid ${locationName} \`${propName}\` supplied to ` +
`Invalid ${locationName} \`${propFullName}\` supplied to ` +
`\`${componentName}\`, expected instance of \`${expectedClassName}\`.`
);
}
@@ -185,7 +200,7 @@ function createInstanceTypeChecker(expectedClass) {
}
function createEnumTypeChecker(expectedValues) {
function validate(props, propName, componentName, location) {
function validate(props, propName, componentName, location, propFullName) {
var propValue = props[propName];
for (var i = 0; i < expectedValues.length; i++) {
if (propValue === expectedValues[i]) {
@@ -196,7 +211,7 @@ function createEnumTypeChecker(expectedValues) {
var locationName = ReactPropTypeLocationNames[location];
var valuesString = JSON.stringify(expectedValues);
return new Error(
`Invalid ${locationName} \`${propName}\` of value \`${propValue}\` ` +
`Invalid ${locationName} \`${propFullName}\` of value \`${propValue}\` ` +
`supplied to \`${componentName}\`, expected one of ${valuesString}.`
);
}
@@ -204,19 +219,25 @@ function createEnumTypeChecker(expectedValues) {
}
function createObjectOfTypeChecker(typeChecker) {
function validate(props, propName, componentName, location) {
function validate(props, propName, componentName, location, propFullName) {
var propValue = props[propName];
var propType = getPropType(propValue);
if (propType !== 'object') {
var locationName = ReactPropTypeLocationNames[location];
return new Error(
`Invalid ${locationName} \`${propName}\` of type ` +
`Invalid ${locationName} \`${propFullName}\` of type ` +
`\`${propType}\` supplied to \`${componentName}\`, expected an object.`
);
}
for (var key in propValue) {
if (propValue.hasOwnProperty(key)) {
var error = typeChecker(propValue, key, componentName, location);
var error = typeChecker(
propValue,
key,
componentName,
location,
`${propFullName}.${key}`
);
if (error instanceof Error) {
return error;
}
@@ -228,17 +249,19 @@ function createObjectOfTypeChecker(typeChecker) {
}
function createUnionTypeChecker(arrayOfTypeCheckers) {
function validate(props, propName, componentName, location) {
function validate(props, propName, componentName, location, propFullName) {
for (var i = 0; i < arrayOfTypeCheckers.length; i++) {
var checker = arrayOfTypeCheckers[i];
if (checker(props, propName, componentName, location) == null) {
if (
checker(props, propName, componentName, location, propFullName) == null
) {
return null;
}
}
var locationName = ReactPropTypeLocationNames[location];
return new Error(
`Invalid ${locationName} \`${propName}\` supplied to ` +
`Invalid ${locationName} \`${propFullName}\` supplied to ` +
`\`${componentName}\`.`
);
}
@@ -246,11 +269,11 @@ function createUnionTypeChecker(arrayOfTypeCheckers) {
}
function createNodeChecker() {
function validate(props, propName, componentName, location) {
function validate(props, propName, componentName, location, propFullName) {
if (!isNode(props[propName])) {
var locationName = ReactPropTypeLocationNames[location];
return new Error(
`Invalid ${locationName} \`${propName}\` supplied to ` +
`Invalid ${locationName} \`${propFullName}\` supplied to ` +
`\`${componentName}\`, expected a ReactNode.`
);
}
@@ -260,13 +283,13 @@ function createNodeChecker() {
}
function createShapeTypeChecker(shapeTypes) {
function validate(props, propName, componentName, location) {
function validate(props, propName, componentName, location, propFullName) {
var propValue = props[propName];
var propType = getPropType(propValue);
if (propType !== 'object') {
var locationName = ReactPropTypeLocationNames[location];
return new Error(
`Invalid ${locationName} \`${propName}\` of type \`${propType}\` ` +
`Invalid ${locationName} \`${propFullName}\` of type \`${propType}\` ` +
`supplied to \`${componentName}\`, expected \`object\`.`
);
}
@@ -275,7 +298,13 @@ function createShapeTypeChecker(shapeTypes) {
if (!checker) {
continue;
}
var error = checker(propValue, key, componentName, location);
var error = checker(
propValue,
key,
componentName,
location,
`${propFullName}.${key}`
);
if (error) {
return error;
}

View File

@@ -161,7 +161,7 @@ describe('ReactPropTypes', function() {
typeCheckFail(
PropTypes.arrayOf(PropTypes.number),
[1, 2, 'b'],
'Invalid prop `2` of type `string` supplied to `testComponent`, ' +
'Invalid prop `testProp[2]` of type `string` supplied to `testComponent`, ' +
'expected `number`.'
);
});
@@ -173,7 +173,7 @@ describe('ReactPropTypes', function() {
typeCheckFail(
PropTypes.arrayOf(PropTypes.instanceOf(Thing)),
[new Thing(), 'xyz'],
'Invalid prop `1` supplied to `testComponent`, expected instance of `' +
'Invalid prop `testProp[1]` supplied to `testComponent`, expected instance of `' +
name + '`.'
);
});
@@ -458,7 +458,7 @@ describe('ReactPropTypes', function() {
typeCheckFail(
PropTypes.objectOf(PropTypes.number),
{a: 1, b: 2, c: 'b'},
'Invalid prop `c` of type `string` supplied to `testComponent`, ' +
'Invalid prop `testProp.c` of type `string` supplied to `testComponent`, ' +
'expected `number`.'
);
});
@@ -470,7 +470,7 @@ describe('ReactPropTypes', function() {
typeCheckFail(
PropTypes.objectOf(PropTypes.instanceOf(Thing)),
{a: new Thing(), b: 'xyz'},
'Invalid prop `b` supplied to `testComponent`, expected instance of `' +
'Invalid prop `testProp.b` supplied to `testComponent`, expected instance of `' +
name + '`.'
);
});
@@ -668,7 +668,7 @@ describe('ReactPropTypes', function() {
typeCheckFail(
PropTypes.shape({key: PropTypes.number.isRequired}),
{},
'Required prop `key` was not specified in `testComponent`.'
'Required prop `testProp.key` was not specified in `testComponent`.'
);
});
@@ -679,14 +679,14 @@ describe('ReactPropTypes', function() {
secondKey: PropTypes.number.isRequired
}),
{},
'Required prop `key` was not specified in `testComponent`.'
'Required prop `testProp.key` was not specified in `testComponent`.'
);
});
it("should warn for invalid key types", function() {
typeCheckFail(PropTypes.shape({key: PropTypes.number}),
{key: 'abc'},
'Invalid prop `key` of type `string` supplied to `testComponent`, ' +
'Invalid prop `testProp.key` of type `string` supplied to `testComponent`, ' +
'expected `number`.'
);
});