From 9e7afa3d2ff23d5c708a88e02f0fa642a1d3cda5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Hegdahl?= Date: Fri, 26 Oct 2018 22:58:46 +0200 Subject: [PATCH] [New] Add type check for validator for 'shape' and 'exact' Fixes #220. --- __tests__/PropTypesDevelopmentReact15.js | 25 +++++++++++++++++++ .../PropTypesDevelopmentStandalone-test.js | 25 +++++++++++++++++++ factoryWithTypeCheckers.js | 14 +++++++++-- 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/__tests__/PropTypesDevelopmentReact15.js b/__tests__/PropTypesDevelopmentReact15.js index 8eeff8d..252eb93 100644 --- a/__tests__/PropTypesDevelopmentReact15.js +++ b/__tests__/PropTypesDevelopmentReact15.js @@ -105,6 +105,15 @@ function expectWarningInDevelopment(declaration, value) { console.error.calls.reset(); } +function expectInvalidValidatorWarning(declaration, type) { + PropTypes.checkPropTypes({ foo: declaration }, { foo: {} }, 'prop', 'testComponent', null); + expect(console.error.calls.argsFor(0)[0]).toEqual( + 'Warning: Failed prop type: testComponent: prop type `foo.bar` is invalid; ' + + 'it must be a function, usually from the `prop-types` package, but received `' + type + '`.' + ); + console.error.calls.reset(); +} + describe('PropTypesDevelopmentReact15', () => { beforeEach(() => { resetWarningCache(); @@ -224,6 +233,22 @@ describe('PropTypesDevelopmentReact15', () => { ); expect(returnValue).toBe(undefined); }); + + it('should warn for invalid validators inside shape', () => { + spyOn(console, 'error'); + expectInvalidValidatorWarning(PropTypes.shape({ bar: PropTypes.invalid_type }), 'undefined'); + expectInvalidValidatorWarning(PropTypes.shape({ bar: true }), 'boolean'); + expectInvalidValidatorWarning(PropTypes.shape({ bar: 'true' }), 'string'); + expectInvalidValidatorWarning(PropTypes.shape({ bar: null }), 'null'); + }); + + it('should warn for invalid validators inside exact', () => { + spyOn(console, 'error'); + expectInvalidValidatorWarning(PropTypes.exact({ bar: PropTypes.invalid_type }), 'undefined'); + expectInvalidValidatorWarning(PropTypes.exact({ bar: true }), 'boolean'); + expectInvalidValidatorWarning(PropTypes.exact({ bar: 'true' }), 'string'); + expectInvalidValidatorWarning(PropTypes.exact({ bar: null }), 'null'); + }); }); describe('resetWarningCache', () => { diff --git a/__tests__/PropTypesDevelopmentStandalone-test.js b/__tests__/PropTypesDevelopmentStandalone-test.js index 8e9b2b7..d16feec 100644 --- a/__tests__/PropTypesDevelopmentStandalone-test.js +++ b/__tests__/PropTypesDevelopmentStandalone-test.js @@ -102,6 +102,15 @@ function expectThrowsInDevelopment(declaration, value) { ); } +function expectInvalidValidatorWarning(declaration, type) { + PropTypes.checkPropTypes({ foo: declaration }, { foo: {} }, 'prop', 'testComponent', null); + expect(console.error.calls.argsFor(0)[0]).toEqual( + 'Warning: Failed prop type: testComponent: prop type `foo.bar` is invalid; ' + + 'it must be a function, usually from the `prop-types` package, but received `' + type + '`.' + ); + console.error.calls.reset(); +} + describe('PropTypesDevelopmentStandalone', () => { beforeEach(() => { resetWarningCache(); @@ -221,6 +230,22 @@ describe('PropTypesDevelopmentStandalone', () => { ); expect(returnValue).toBe(undefined); }); + + it('should warn for invalid validators inside shape', () => { + spyOn(console, 'error'); + expectInvalidValidatorWarning(PropTypes.shape({ bar: PropTypes.invalid_type }), 'undefined'); + expectInvalidValidatorWarning(PropTypes.shape({ bar: true }), 'boolean'); + expectInvalidValidatorWarning(PropTypes.shape({ bar: 'true' }), 'string'); + expectInvalidValidatorWarning(PropTypes.shape({ bar: null }), 'null'); + }); + + it('should warn for invalid validators inside exact', () => { + spyOn(console, 'error'); + expectInvalidValidatorWarning(PropTypes.exact({ bar: PropTypes.invalid_type }), 'undefined'); + expectInvalidValidatorWarning(PropTypes.exact({ bar: true }), 'boolean'); + expectInvalidValidatorWarning(PropTypes.exact({ bar: 'true' }), 'string'); + expectInvalidValidatorWarning(PropTypes.exact({ bar: null }), 'null'); + }); }); describe('resetWarningCache', () => { diff --git a/factoryWithTypeCheckers.js b/factoryWithTypeCheckers.js index 3711f0b..45f2256 100644 --- a/factoryWithTypeCheckers.js +++ b/factoryWithTypeCheckers.js @@ -400,6 +400,13 @@ module.exports = function(isValidElement, throwOnDirectAccess) { return createChainableTypeChecker(validate); } + function invalidValidatorError(componentName, location, propFullName, key, type) { + return new PropTypeError( + (componentName || 'React class') + ': ' + location + ' type `' + propFullName + '.' + key + '` is invalid; ' + + 'it must be a function, usually from the `prop-types` package, but received `' + type + '`.' + ); + } + function createShapeTypeChecker(shapeTypes) { function validate(props, propName, componentName, location, propFullName) { var propValue = props[propName]; @@ -409,8 +416,8 @@ module.exports = function(isValidElement, throwOnDirectAccess) { } for (var key in shapeTypes) { var checker = shapeTypes[key]; - if (!checker) { - continue; + if (typeof checker !== 'function') { + return invalidValidatorError(componentName, location, propFullName, key, getPreciseType(checker)); } var error = checker(propValue, key, componentName, location, propFullName + '.' + key, ReactPropTypesSecret); if (error) { @@ -434,6 +441,9 @@ module.exports = function(isValidElement, throwOnDirectAccess) { var allKeys = assign({}, props[propName], shapeTypes); for (var key in allKeys) { var checker = shapeTypes[key]; + if (has(shapeTypes, key) && typeof checker !== 'function') { + return invalidValidatorError(componentName, location, propFullName, key, getPreciseType(checker)); + } if (!checker) { return new PropTypeError( 'Invalid ' + location + ' `' + propFullName + '` key `' + key + '` supplied to `' + componentName + '`.' +