Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add key to error messages #284

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 23 additions & 2 deletions __tests__/PropTypesDevelopmentReact15.js
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,27 @@ describe('PropTypesDevelopmentReact15', () => {
expectInvalidValidatorWarning(PropTypes.exact({ bar: 'true' }), 'string');
expectInvalidValidatorWarning(PropTypes.exact({ bar: null }), 'null');
});

it('should output the value of the `key` prop (if it has one)', () => {
function typeCheckFailWithKey(declaration, value) {
const propTypes = { testProp: declaration };
const props = { testProp: value, key: 'abc' };
const message = getPropTypeWarningMessage(propTypes, props, 'testComponent');
expect(message).toContain('`testComponent` with key `abc`');
}
function Something() {}
typeCheckFailWithKey(PropTypes.string, false);
typeCheckFailWithKey(PropTypes.arrayOf(PropTypes.string), 2);
typeCheckFailWithKey(PropTypes.element, {});
typeCheckFailWithKey(PropTypes.elementType, {});
typeCheckFailWithKey(PropTypes.instanceOf(Something), '');
typeCheckFailWithKey(PropTypes.node, {});
typeCheckFailWithKey(PropTypes.objectOf(PropTypes.string), '');
typeCheckFailWithKey(PropTypes.oneOf([1]), '');
typeCheckFailWithKey(PropTypes.oneOfType([PropTypes.number]), '');
typeCheckFailWithKey(PropTypes.shape({ a: PropTypes.string }), false);
typeCheckFailWithKey(PropTypes.exact({ a: PropTypes.string }), false);
});
});

describe('resetWarningCache', () => {
Expand Down Expand Up @@ -1217,7 +1238,7 @@ describe('PropTypesDevelopmentReact15', () => {
typeCheckFail(
PropTypes.shape({key: PropTypes.number}),
{key: 'abc'},
'Invalid prop `testProp.key` of type `string` supplied to `testComponent`, ' +
'Invalid prop `testProp.key` of type `string` supplied to `testComponent` with key `abc`, ' +
'expected `number`.',
);
});
Expand Down Expand Up @@ -1324,7 +1345,7 @@ describe('PropTypesDevelopmentReact15', () => {
typeCheckFail(
PropTypes.exact({key: PropTypes.number}),
{key: 'abc'},
'Invalid prop `testProp.key` of type `string` supplied to `testComponent`, ' +
'Invalid prop `testProp.key` of type `string` supplied to `testComponent` with key `abc`, ' +
'expected `number`.',
);
});
Expand Down
2 changes: 1 addition & 1 deletion __tests__/PropTypesDevelopmentStandalone-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1291,7 +1291,7 @@ describe('PropTypesDevelopmentStandalone', () => {
typeCheckFail(
PropTypes.shape({key: PropTypes.number}),
{key: 'abc'},
'Invalid prop `testProp.key` of type `string` supplied to `testComponent`, ' +
'Invalid prop `testProp.key` of type `string` supplied to `testComponent` with key `abc`, ' +
'expected `number`.',
);
});
Expand Down
33 changes: 19 additions & 14 deletions factoryWithTypeCheckers.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ function emptyFunctionThatReturnsNull() {
return null;
}

// Helper for adding `key` to the error message if it exists in props.
function getKey(props) {
return (props.key && typeof props.key !== 'obejct') ? ' with key `' + props.key + '`' : '';
}

module.exports = function(isValidElement, throwOnDirectAccess) {
/* global Symbol */
var ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator;
Expand Down Expand Up @@ -209,9 +214,9 @@ module.exports = function(isValidElement, throwOnDirectAccess) {
if (props[propName] == null) {
if (isRequired) {
if (props[propName] === null) {
return new PropTypeError('The ' + location + ' `' + propFullName + '` is marked as required ' + ('in `' + componentName + '`, but its value is `null`.'));
return new PropTypeError('The ' + location + ' `' + propFullName + '` is marked as required ' + ('in `' + componentName + '`' + getKey(props) + ', but its value is `null`.'));
}
return new PropTypeError('The ' + location + ' `' + propFullName + '` is marked as required in ' + ('`' + componentName + '`, but its value is `undefined`.'));
return new PropTypeError('The ' + location + ' `' + propFullName + '` is marked as required in ' + ('`' + componentName + '`' + getKey(props) + ', but its value is `undefined`.'));
}
return null;
} else {
Expand All @@ -236,7 +241,7 @@ module.exports = function(isValidElement, throwOnDirectAccess) {
var preciseType = getPreciseType(propValue);

return new PropTypeError(
'Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + preciseType + '` supplied to `' + componentName + '`, expected ') + ('`' + expectedType + '`.'),
'Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + preciseType + '` supplied to `' + componentName + '`' + getKey(props) + ', expected ') + ('`' + expectedType + '`.'),
{expectedType: expectedType}
);
}
Expand All @@ -257,7 +262,7 @@ module.exports = function(isValidElement, throwOnDirectAccess) {
var propValue = props[propName];
if (!Array.isArray(propValue)) {
var propType = getPropType(propValue);
return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected an array.'));
return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`' + getKey(props) + ', expected an array.'));
}
for (var i = 0; i < propValue.length; i++) {
var error = typeChecker(propValue, i, componentName, location, propFullName + '[' + i + ']', ReactPropTypesSecret);
Expand All @@ -275,7 +280,7 @@ module.exports = function(isValidElement, throwOnDirectAccess) {
var propValue = props[propName];
if (!isValidElement(propValue)) {
var propType = getPropType(propValue);
return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected a single ReactElement.'));
return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`' + getKey(props) + ', expected a single ReactElement.'));
}
return null;
}
Expand All @@ -287,7 +292,7 @@ module.exports = function(isValidElement, throwOnDirectAccess) {
var propValue = props[propName];
if (!ReactIs.isValidElementType(propValue)) {
var propType = getPropType(propValue);
return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected a single ReactElement type.'));
return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`' + getKey(props) + ', expected a single ReactElement type.'));
}
return null;
}
Expand All @@ -299,7 +304,7 @@ module.exports = function(isValidElement, throwOnDirectAccess) {
if (!(props[propName] instanceof expectedClass)) {
var expectedClassName = expectedClass.name || ANONYMOUS;
var actualClassName = getClassName(props[propName]);
return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + actualClassName + '` supplied to `' + componentName + '`, expected ') + ('instance of `' + expectedClassName + '`.'));
return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + actualClassName + '` supplied to `' + componentName + '`' + getKey(props) + ', expected ') + ('instance of `' + expectedClassName + '`.'));
}
return null;
}
Expand Down Expand Up @@ -336,7 +341,7 @@ module.exports = function(isValidElement, throwOnDirectAccess) {
}
return value;
});
return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of value `' + String(propValue) + '` ' + ('supplied to `' + componentName + '`, expected one of ' + valuesString + '.'));
return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of value `' + String(propValue) + '` ' + ('supplied to `' + componentName + '`' + getKey(props) + ', expected one of ' + valuesString + '.'));
}
return createChainableTypeChecker(validate);
}
Expand All @@ -349,7 +354,7 @@ module.exports = function(isValidElement, throwOnDirectAccess) {
var propValue = props[propName];
var propType = getPropType(propValue);
if (propType !== 'object') {
return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected an object.'));
return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`' + getKey(props) + ', expected an object.'));
}
for (var key in propValue) {
if (has(propValue, key)) {
Expand Down Expand Up @@ -394,15 +399,15 @@ module.exports = function(isValidElement, throwOnDirectAccess) {
}
}
var expectedTypesMessage = (expectedTypes.length > 0) ? ', expected one of type [' + expectedTypes.join(', ') + ']': '';
return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` supplied to ' + ('`' + componentName + '`' + expectedTypesMessage + '.'));
return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` supplied to ' + ('`' + componentName + '`' + getKey(props) + expectedTypesMessage + '.'));
}
return createChainableTypeChecker(validate);
}

function createNodeChecker() {
function validate(props, propName, componentName, location, propFullName) {
if (!isNode(props[propName])) {
return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` supplied to ' + ('`' + componentName + '`, expected a ReactNode.'));
return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` supplied to ' + ('`' + componentName + '`' + getKey(props) + ', expected a ReactNode.'));
}
return null;
}
Expand All @@ -421,7 +426,7 @@ module.exports = function(isValidElement, throwOnDirectAccess) {
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`.'));
return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type `' + propType + '` ' + ('supplied to `' + componentName + '`' + getKey(props) + ', expected `object`.'));
}
for (var key in shapeTypes) {
var checker = shapeTypes[key];
Expand All @@ -443,7 +448,7 @@ module.exports = function(isValidElement, throwOnDirectAccess) {
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`.'));
return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type `' + propType + '` ' + ('supplied to `' + componentName + '`' + getKey(props) + ', expected `object`.'));
}
// We need to check all keys in case some are required but missing from
// props.
Expand All @@ -455,7 +460,7 @@ module.exports = function(isValidElement, throwOnDirectAccess) {
}
if (!checker) {
return new PropTypeError(
'Invalid ' + location + ' `' + propFullName + '` key `' + key + '` supplied to `' + componentName + '`.' +
'Invalid ' + location + ' `' + propFullName + '` key `' + key + '` supplied to `' + componentName + '`' + getKey(props) + '.' +
'\nBad object: ' + JSON.stringify(props[propName], null, ' ') +
'\nValid keys: ' + JSON.stringify(Object.keys(shapeTypes), null, ' ')
);
Expand Down