diff --git a/src/utils/CustomPropTypes.js b/src/utils/CustomPropTypes.js index bc402d3635..7818015b86 100644 --- a/src/utils/CustomPropTypes.js +++ b/src/utils/CustomPropTypes.js @@ -1,3 +1,5 @@ +import React from 'react'; + const ANONYMOUS = '<>'; const CustomPropTypes = { @@ -15,6 +17,20 @@ const CustomPropTypes = { */ mountable: createMountableChecker(), + /** + * Checks whether a prop provides a type of element. + * + * The type of element can be provided in two forms: + * - tag name (string) + * - a return value of React.createClass(...) + * + * @param props + * @param propName + * @param componentName + * @returns {Error|undefined} + */ + elementType: createElementTypeChecker(), + /** * Checks whether a prop matches a key of an associated object * @@ -135,4 +151,24 @@ function all(propTypes) { }; } +function createElementTypeChecker() { + function validate(props, propName, componentName) { + let errMsg = `Invalid prop '${propName}' specified in '${componentName}'.` + + ' Expected an Element `type`'; + + if (typeof props[propName] !== 'function') { + if (React.isValidElement(props[propName])) { + return new Error(errMsg + ', not an actual Element'); + } + + if (typeof props[propName] !== 'string') { + return new Error(errMsg + + ' such as a tag name or return value of React.createClass(...)'); + } + } + } + + return createChainableTypeChecker(validate); +} + export default CustomPropTypes; diff --git a/test/utils/CustomPropTypesSpec.js b/test/utils/CustomPropTypesSpec.js index 9e4e78de07..8fce1e25f8 100644 --- a/test/utils/CustomPropTypesSpec.js +++ b/test/utils/CustomPropTypesSpec.js @@ -26,6 +26,34 @@ describe('CustomPropTypes', function() { }); }); + describe('elementType', function () { + function validate(prop) { + return CustomPropTypes.elementType({p: prop}, 'p', 'TestComponent'); + } + + it('Should validate OK with undifined or null values', function() { + assert.isUndefined(validate()); + assert.isUndefined(validate(null)); + }); + + it('Should validate OK with elementType values', function() { + assert.isUndefined(validate('span')); + assert.isUndefined(validate(function(){})); + }); + + it('Should return error with not a string or function values', function() { + let err = validate({}); + assert.instanceOf(err, Error); + assert.include(err.message, 'Expected an Element `type` such as a tag name or return value of React.createClass(...)'); + }); + + it('Should return error with react element', function() { + let err = validate(React.createElement('span')); + assert.instanceOf(err, Error); + assert.include(err.message, 'Expected an Element `type`, not an actual Element'); + }); + }); + describe('keyOf', function () { let obj = {'foo': 1}; function validate(prop) {