diff --git a/package.json b/package.json index e152a4aab2..eaa5b7655d 100644 --- a/package.json +++ b/package.json @@ -95,6 +95,7 @@ "react": "^0.13.3", "react-component-metadata": "^1.3.0", "react-hot-loader": "^1.2.8", + "react-prop-types": "^0.2.2", "react-router": "^0.13.3", "rimraf": "^2.4.2", "semver": "^5.0.1", diff --git a/src/BootstrapMixin.js b/src/BootstrapMixin.js index 98c05bd068..35f9ecb90e 100644 --- a/src/BootstrapMixin.js +++ b/src/BootstrapMixin.js @@ -1,6 +1,6 @@ import React from 'react'; import styleMaps from './styleMaps'; -import CustomPropTypes from './utils/CustomPropTypes'; +import { keyOf } from 'react-prop-types'; const BootstrapMixin = { propTypes: { @@ -8,7 +8,7 @@ const BootstrapMixin = { * bootstrap className * @private */ - bsClass: CustomPropTypes.keyOf(styleMaps.CLASSES), + bsClass: keyOf(styleMaps.CLASSES), /** * Style variants * @type {("default"|"primary"|"success"|"info"|"warning"|"danger"|"link")} @@ -18,7 +18,7 @@ const BootstrapMixin = { * Size variants * @type {("xsmall"|"small"|"medium"|"large"|"xs"|"sm"|"md"|"lg")} */ - bsSize: CustomPropTypes.keyOf(styleMaps.SIZES) + bsSize: keyOf(styleMaps.SIZES) }, getBsClassSet() { diff --git a/src/Button.js b/src/Button.js index 9dd80faf8f..e2270ebe2d 100644 --- a/src/Button.js +++ b/src/Button.js @@ -1,7 +1,7 @@ import React from 'react'; import classNames from 'classnames'; import BootstrapMixin from './BootstrapMixin'; -import CustomPropTypes from './utils/CustomPropTypes'; +import { elementType } from 'react-prop-types'; import ButtonInput from './ButtonInput'; const Button = React.createClass({ @@ -16,7 +16,7 @@ const Button = React.createClass({ /** * You can use a custom element for this component */ - componentClass: CustomPropTypes.elementType, + componentClass: elementType, href: React.PropTypes.string, target: React.PropTypes.string, /** diff --git a/src/ButtonGroup.js b/src/ButtonGroup.js index 303a61eadb..c32b7fa435 100644 --- a/src/ButtonGroup.js +++ b/src/ButtonGroup.js @@ -1,7 +1,7 @@ import React from 'react'; import classNames from 'classnames'; import BootstrapMixin from './BootstrapMixin'; -import CustomPropTypes from './utils/CustomPropTypes'; +import { all } from 'react-prop-types'; const ButtonGroup = React.createClass({ mixins: [BootstrapMixin], @@ -13,7 +13,7 @@ const ButtonGroup = React.createClass({ * Display block buttons, only useful when used with the "vertical" prop. * @type {bool} */ - block: CustomPropTypes.all([ + block: all([ React.PropTypes.bool, function(props, propName, componentName) { if (props.block && !props.vertical) { diff --git a/src/Col.js b/src/Col.js index f945fe10b5..83e6dc462d 100644 --- a/src/Col.js +++ b/src/Col.js @@ -1,7 +1,7 @@ import React from 'react'; import classNames from 'classnames'; import styleMaps from './styleMaps'; -import CustomPropTypes from './utils/CustomPropTypes'; +import { elementType } from 'react-prop-types'; const Col = React.createClass({ propTypes: { @@ -136,7 +136,7 @@ const Col = React.createClass({ /** * You can use a custom element for this component */ - componentClass: CustomPropTypes.elementType + componentClass: elementType }, getDefaultProps() { diff --git a/src/Collapse.js b/src/Collapse.js index c4a921679c..2b6152c8c4 100644 --- a/src/Collapse.js +++ b/src/Collapse.js @@ -1,7 +1,7 @@ import React from 'react'; import Transition from 'react-overlays/lib/Transition'; import domUtils from './utils/domUtils'; -import CustomPropTypes from './utils/CustomPropTypes'; +import { all } from 'react-prop-types'; import deprecationWarning from './utils/deprecationWarning'; import createChainedFunction from './utils/createChainedFunction'; @@ -145,7 +145,7 @@ Collapse.propTypes = { * duration * @private */ - duration: CustomPropTypes.all([ + duration: all([ React.PropTypes.number, (props)=> { if (props.duration != null){ diff --git a/src/Dropdown.js b/src/Dropdown.js index 3800bec484..a5b652af39 100644 --- a/src/Dropdown.js +++ b/src/Dropdown.js @@ -9,6 +9,7 @@ import CustomPropTypes from './utils/CustomPropTypes'; import createChainedFunction from './utils/createChainedFunction'; import find from 'lodash/collection/find'; import omit from 'lodash/object/omit'; +import { all, elementType, isRequiredForA11y } from 'react-prop-types'; const TOGGLE_REF = 'toggle-btn'; @@ -229,20 +230,20 @@ Dropdown.propTypes = { * @type {string|number} * @required */ - id: CustomPropTypes.isRequiredForA11y( + id: isRequiredForA11y( React.PropTypes.oneOfType([ React.PropTypes.string, React.PropTypes.number ]) ), - componentClass: CustomPropTypes.elementType, + componentClass: elementType, /** * The children of a Dropdown may be a `` or a ``. * @type {node} */ - children: CustomPropTypes.all([ + children: all([ CustomPropTypes.requiredRoles(TOGGLE_ROLE, MENU_ROLE), CustomPropTypes.exclusiveRoles(MENU_ROLE) ]), diff --git a/src/DropdownButton.js b/src/DropdownButton.js index 0bd169e2c7..610c740b31 100644 --- a/src/DropdownButton.js +++ b/src/DropdownButton.js @@ -2,7 +2,7 @@ import React from 'react'; import BootstrapMixin from './BootstrapMixin'; import Dropdown from './Dropdown'; import NavDropdown from './NavDropdown'; -import CustomPropTypes from './utils/CustomPropTypes'; +import { all } from 'react-prop-types'; import deprecationWarning from './utils/deprecationWarning'; import omit from 'lodash/object/omit'; @@ -46,7 +46,7 @@ DropdownButton.propTypes = { * @type {bool} * @deprecated Use the `NavDropdown` instead. */ - navItem: CustomPropTypes.all([ + navItem: all([ React.PropTypes.bool, function(props, propName, componentName) { if (props.navItem) { diff --git a/src/DropdownToggle.js b/src/DropdownToggle.js index 5d3b3145b7..c6e9c06ce4 100644 --- a/src/DropdownToggle.js +++ b/src/DropdownToggle.js @@ -1,7 +1,7 @@ import React from 'react'; import classNames from 'classnames'; import Button from './Button'; -import CustomPropTypes from './utils/CustomPropTypes'; +import { singlePropFrom } from 'react-prop-types'; import SafeAnchor from './SafeAnchor'; const CARET = ; @@ -29,7 +29,7 @@ export default class DropdownToggle extends React.Component { } } -const titleAndChildrenValidation = CustomPropTypes.singlePropFrom([ +const titleAndChildrenValidation = singlePropFrom([ 'title', 'children' ]); diff --git a/src/Fade.js b/src/Fade.js index 62ce1b6798..ef8677c615 100644 --- a/src/Fade.js +++ b/src/Fade.js @@ -1,6 +1,6 @@ import React from 'react'; import Transition from 'react-overlays/lib/Transition'; -import CustomPropTypes from './utils/CustomPropTypes'; +import { all } from 'react-prop-types'; import deprecationWarning from './utils/deprecationWarning'; class Fade extends React.Component { @@ -51,7 +51,7 @@ Fade.propTypes = { * duration * @private */ - duration: CustomPropTypes.all([ + duration: all([ React.PropTypes.number, (props)=> { if (props.duration != null){ @@ -95,4 +95,3 @@ Fade.defaultProps = { }; export default Fade; - diff --git a/src/Grid.js b/src/Grid.js index b0102f3ce3..ec4679b19f 100644 --- a/src/Grid.js +++ b/src/Grid.js @@ -1,6 +1,6 @@ import React from 'react'; import classNames from 'classnames'; -import CustomPropTypes from './utils/CustomPropTypes'; +import { elementType } from 'react-prop-types'; const Grid = React.createClass({ propTypes: { @@ -13,7 +13,7 @@ const Grid = React.createClass({ /** * You can use a custom element for this component */ - componentClass: CustomPropTypes.elementType + componentClass: elementType }, getDefaultProps() { diff --git a/src/Jumbotron.js b/src/Jumbotron.js index 5d5f69e658..135e6ca1db 100644 --- a/src/Jumbotron.js +++ b/src/Jumbotron.js @@ -1,13 +1,13 @@ import React from 'react'; import classNames from 'classnames'; -import CustomPropTypes from './utils/CustomPropTypes'; +import { elementType } from 'react-prop-types'; const Jumbotron = React.createClass({ propTypes: { /** * You can use a custom element for this component */ - componentClass: CustomPropTypes.elementType + componentClass: elementType }, getDefaultProps() { diff --git a/src/MenuItem.js b/src/MenuItem.js index e0823b9186..f254ea6f66 100644 --- a/src/MenuItem.js +++ b/src/MenuItem.js @@ -1,6 +1,6 @@ import React from 'react'; import classnames from 'classnames'; -import CustomPropTypes from './utils/CustomPropTypes'; +import { all } from 'react-prop-types'; import SafeAnchor from './SafeAnchor'; export default class MenuItem extends React.Component { @@ -62,7 +62,7 @@ export default class MenuItem extends React.Component { MenuItem.propTypes = { disabled: React.PropTypes.bool, - divider: CustomPropTypes.all([ + divider: all([ React.PropTypes.bool, function(props, propName, componentName) { if (props.divider && props.children) { diff --git a/src/Modal.js b/src/Modal.js index 6b8f18210e..1ffee7f709 100644 --- a/src/Modal.js +++ b/src/Modal.js @@ -5,7 +5,7 @@ import domUtils from './utils/domUtils'; import getScrollbarSize from 'dom-helpers/util/scrollbarSize'; import EventListener from './utils/EventListener'; import createChainedFunction from './utils/createChainedFunction'; -import CustomPropTypes from './utils/CustomPropTypes'; +import { elementType } from 'react-prop-types'; import Portal from 'react-overlays/lib/Portal'; import Fade from './Fade'; @@ -93,7 +93,7 @@ const Modal = React.createClass({ * A Component type that provides the modal content Markup. This is a useful prop when you want to use your own * styles and markup to create a custom modal component. */ - dialogComponent: CustomPropTypes.elementType, + dialogComponent: elementType, /** * When `true` The modal will automatically shift focus to itself when it opens, and replace it to the last focused element when it closes. diff --git a/src/Navbar.js b/src/Navbar.js index 653db896d5..787e0a4b4a 100644 --- a/src/Navbar.js +++ b/src/Navbar.js @@ -4,7 +4,7 @@ import classNames from 'classnames'; import ValidComponentChildren from './utils/ValidComponentChildren'; import createChainedFunction from './utils/createChainedFunction'; -import CustomPropTypes from './utils/CustomPropTypes'; +import { elementType } from 'react-prop-types'; const Navbar = React.createClass({ mixins: [BootstrapMixin], @@ -19,7 +19,7 @@ const Navbar = React.createClass({ /** * You can use a custom element for this component */ - componentClass: CustomPropTypes.elementType, + componentClass: elementType, brand: React.PropTypes.node, toggleButton: React.PropTypes.node, toggleNavKey: React.PropTypes.oneOfType([ diff --git a/src/Overlay.js b/src/Overlay.js index 44e8f91d83..e0758adb68 100644 --- a/src/Overlay.js +++ b/src/Overlay.js @@ -3,7 +3,7 @@ import React, { cloneElement } from 'react'; import BaseOverlay from 'react-overlays/lib/Overlay'; -import CustomPropTypes from './utils/CustomPropTypes'; +import { elementType } from 'react-prop-types'; import Fade from './Fade'; import classNames from 'classnames'; @@ -57,7 +57,7 @@ Overlay.propTypes = { */ animation: React.PropTypes.oneOfType([ React.PropTypes.bool, - CustomPropTypes.elementType + elementType ]), /** diff --git a/src/Pagination.js b/src/Pagination.js index d9e2007fc3..164f59128b 100644 --- a/src/Pagination.js +++ b/src/Pagination.js @@ -2,7 +2,7 @@ import React from 'react'; import classNames from 'classnames'; import BootstrapMixin from './BootstrapMixin'; import PaginationButton from './PaginationButton'; -import CustomPropTypes from './utils/CustomPropTypes'; +import { elementType } from 'react-prop-types'; import SafeAnchor from './SafeAnchor'; const Pagination = React.createClass({ @@ -21,7 +21,7 @@ const Pagination = React.createClass({ /** * You can use a custom element for the buttons */ - buttonComponentClass: CustomPropTypes.elementType + buttonComponentClass: elementType }, getDefaultProps() { diff --git a/src/PaginationButton.js b/src/PaginationButton.js index 917ad79fdc..0aa3336f6c 100644 --- a/src/PaginationButton.js +++ b/src/PaginationButton.js @@ -2,7 +2,7 @@ import React from 'react'; import classNames from 'classnames'; import BootstrapMixin from './BootstrapMixin'; import createSelectedEvent from './utils/createSelectedEvent'; -import CustomPropTypes from './utils/CustomPropTypes'; +import { elementType } from 'react-prop-types'; const PaginationButton = React.createClass({ mixins: [BootstrapMixin], @@ -19,7 +19,7 @@ const PaginationButton = React.createClass({ /** * You can use a custom element for this component */ - buttonComponentClass: CustomPropTypes.elementType + buttonComponentClass: elementType }, getDefaultProps() { diff --git a/src/Popover.js b/src/Popover.js index 7b8ed17f9a..a3a8b2a169 100644 --- a/src/Popover.js +++ b/src/Popover.js @@ -1,7 +1,7 @@ import React from 'react'; import classNames from 'classnames'; import BootstrapMixin from './BootstrapMixin'; -import CustomPropTypes from './utils/CustomPropTypes'; +import { isRequiredForA11y } from 'react-prop-types'; const Popover = React.createClass({ @@ -13,7 +13,7 @@ const Popover = React.createClass({ * @type {string} * @required */ - id: CustomPropTypes.isRequiredForA11y(React.PropTypes.string), + id: isRequiredForA11y(React.PropTypes.string), /** * Sets the direction the Popover is positioned towards. diff --git a/src/Portal.js b/src/Portal.js index f27c32eb5c..4fc5b0ae56 100644 --- a/src/Portal.js +++ b/src/Portal.js @@ -8,4 +8,3 @@ export default deprecationWarning.wrapper(Portal, { 'http://react-bootstrap.github.io/react-overlays/examples/#portal and ' + 'https://github.com/react-bootstrap/react-bootstrap/issues/1084' }); - diff --git a/src/Row.js b/src/Row.js index 90ccd145d2..53c35fc2bd 100644 --- a/src/Row.js +++ b/src/Row.js @@ -1,13 +1,13 @@ import React from 'react'; import classNames from 'classnames'; -import CustomPropTypes from './utils/CustomPropTypes'; +import { elementType } from 'react-prop-types'; const Row = React.createClass({ propTypes: { /** * You can use a custom element for this component */ - componentClass: CustomPropTypes.elementType + componentClass: elementType }, getDefaultProps() { diff --git a/src/Tooltip.js b/src/Tooltip.js index 2691b14abf..ab8ca3ce28 100644 --- a/src/Tooltip.js +++ b/src/Tooltip.js @@ -1,7 +1,7 @@ import React from 'react'; import classNames from 'classnames'; import BootstrapMixin from './BootstrapMixin'; -import CustomPropTypes from './utils/CustomPropTypes'; +import { isRequiredForA11y } from 'react-prop-types'; const Tooltip = React.createClass({ mixins: [BootstrapMixin], @@ -12,7 +12,7 @@ const Tooltip = React.createClass({ * @type {string} * @required */ - id: CustomPropTypes.isRequiredForA11y(React.PropTypes.string), + id: isRequiredForA11y(React.PropTypes.string), /** * Sets the direction the Tooltip is positioned towards. diff --git a/src/index.js b/src/index.js index 72e728bf4f..d5019fc219 100644 --- a/src/index.js +++ b/src/index.js @@ -79,14 +79,12 @@ import domUtils from './utils/domUtils'; import childrenValueInputValidation from './utils/childrenValueInputValidation'; import createChainedFunction from './utils/createChainedFunction'; import ValidComponentChildren from './utils/ValidComponentChildren'; -import CustomPropTypes from './utils/CustomPropTypes'; export const utils = { childrenValueInputValidation, createChainedFunction, ValidComponentChildren, - CustomPropTypes, - domUtils: createDeprecationWrapper(domUtils, 'utils/domUtils', 'npm install dom-helpers'), + domUtils: createDeprecationWrapper(domUtils, 'utils/domUtils', 'npm install dom-helpers') }; function createDeprecationWrapper(obj, deprecated, instead, link){ diff --git a/src/utils/CustomPropTypes.js b/src/utils/CustomPropTypes.js index caf3623f13..f5d7abb6e0 100644 --- a/src/utils/CustomPropTypes.js +++ b/src/utils/CustomPropTypes.js @@ -1,5 +1,3 @@ -import React from 'react'; -import warning from 'react/lib/warning'; import childrenToArray from './childrenToArray'; const ANONYMOUS = '<>'; @@ -30,30 +28,7 @@ function createChainableTypeChecker(validate) { return chainedCheckType; } -const CustomPropTypes = { - - deprecated(propType, explanation){ - return function(props, propName, componentName){ - if (props[propName] != null) { - warning(false, `"${propName}" property of "${componentName}" has been deprecated.\n${explanation}`); - } - - return propType(props, propName, componentName); - }; - }, - - isRequiredForA11y(propType){ - return function(props, propName, componentName){ - if (props[propName] == null) { - return new Error( - 'The prop `' + propName + '` is required to make ' + componentName + ' accessible ' + - 'for users using assistive technologies such as screen readers `' - ); - } - - return propType(props, propName, componentName); - }; - }, +export default { requiredRoles(...roles) { return createChainableTypeChecker( @@ -101,151 +76,5 @@ const CustomPropTypes = { } }); - }, - - /** - * Checks whether a prop provides a DOM element - * - * The element can be provided in two forms: - * - Directly passed - * - Or passed an object that has a `render` method - * - * @param props - * @param propName - * @param componentName - * @returns {Error|undefined} - */ - 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 - * - * @param props - * @param propName - * @param componentName - * @returns {Error|undefined} - */ - keyOf: createKeyOfChecker, - /** - * Checks if only one of the listed properties is in use. An error is given - * if multiple have a value - * - * @param props - * @param propName - * @param componentName - * @returns {Error|undefined} - */ - singlePropFrom: createSinglePropFromChecker, - - all -}; - -function errMsg(props, propName, componentName, msgContinuation) { - return `Invalid prop '${propName}' of value '${props[propName]}'` + - ` supplied to '${componentName}'${msgContinuation}`; -} - -function createMountableChecker() { - function validate(props, propName, componentName) { - if (typeof props[propName] !== 'object' || - typeof props[propName].render !== 'function' && props[propName].nodeType !== 1) { - return new Error( - errMsg(props, propName, componentName, - ', expected a DOM element or an object that has a `render` method') - ); - } } - - return createChainableTypeChecker(validate); -} - -function createKeyOfChecker(obj) { - function validate(props, propName, componentName) { - let propValue = props[propName]; - if (!obj.hasOwnProperty(propValue)) { - let valuesString = JSON.stringify(Object.keys(obj)); - return new Error( - errMsg(props, propName, componentName, `, expected one of ${valuesString}.`) - ); - } - } - return createChainableTypeChecker(validate); -} - -function createSinglePropFromChecker(arrOfProps) { - function validate(props, propName, componentName) { - const usedPropCount = arrOfProps - .map(listedProp => props[listedProp]) - .reduce((acc, curr) => acc + (curr !== undefined ? 1 : 0), 0); - - if (usedPropCount > 1) { - const [first, ...others] = arrOfProps; - const message = `${others.join(', ')} and ${first}`; - return new Error( - `Invalid prop '${propName}', only one of the following ` + - `may be provided: ${message}` - ); - } - } - return validate; -} - -function all(propTypes) { - if (propTypes === undefined) { - throw new Error('No validations provided'); - } - - if (!(propTypes instanceof Array)) { - throw new Error('Invalid argument must be an array'); - } - - if (propTypes.length === 0) { - throw new Error('No validations provided'); - } - - return function(props, propName, componentName) { - for(let i = 0; i < propTypes.length; i++) { - let result = propTypes[i](props, propName, componentName); - - if (result !== undefined && result !== null) { - return result; - } - } - }; -} - -function createElementTypeChecker() { - function validate(props, propName, componentName) { - let errBeginning = errMsg(props, propName, componentName, - '. Expected an Element `type`'); - - if (typeof props[propName] !== 'function') { - if (React.isValidElement(props[propName])) { - return new Error(errBeginning + ', not an actual Element'); - } - - if (typeof props[propName] !== 'string') { - return new Error(errBeginning + - ' such as a tag name or return value of React.createClass(...)'); - } - } - } - - return createChainableTypeChecker(validate); -} - -export default CustomPropTypes; +}; diff --git a/src/utils/childrenValueInputValidation.js b/src/utils/childrenValueInputValidation.js index 075a4c42b0..37f888a3a3 100644 --- a/src/utils/childrenValueInputValidation.js +++ b/src/utils/childrenValueInputValidation.js @@ -1,5 +1,5 @@ import React from 'react'; -import { singlePropFrom } from './CustomPropTypes'; +import { singlePropFrom } from 'react-prop-types'; const propList = ['children', 'value']; const typeList = [React.PropTypes.number, React.PropTypes.string]; diff --git a/src/utils/index.js b/src/utils/index.js index e84c9f5b33..9c2a55d77c 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -7,7 +7,3 @@ deprecationWarning('utils/domUtils', 'npm install dom-helpers'); export domUtils from './domUtils'; export ValidComponentChildren from './ValidComponentChildren'; - -deprecationWarning('utils/CustomPropTypes', 'npm install react-prop-types', - 'https://github.com/react-bootstrap/react-bootstrap/issues/937'); -export CustomPropTypes from './CustomPropTypes'; diff --git a/test/utils/CustomPropTypesSpec.js b/test/utils/CustomPropTypesSpec.js deleted file mode 100644 index 92e542b366..0000000000 --- a/test/utils/CustomPropTypesSpec.js +++ /dev/null @@ -1,207 +0,0 @@ -import React from 'react'; -import ReactTestUtils from 'react/lib/ReactTestUtils'; -import CustomPropTypes from '../../src/utils/CustomPropTypes'; -import {shouldWarn} from '../helpers'; - -function isChainableAndUndefinedOK(validatorUnderTest) { - it('Should validate OK with undefined or null values', function() { - assert.isUndefined(validatorUnderTest({}, 'p', 'Component')); - assert.isUndefined(validatorUnderTest({p: null}, 'p', 'Component')); - }); - - it('Should be able to chain', function() { - let err = validatorUnderTest.isRequired({}, 'p', 'Component'); - assert.instanceOf(err, Error); - assert.include(err.message, 'Required prop'); - assert.include(err.message, 'was not specified in'); - }); -} - -describe('CustomPropTypes', function() { - - describe('mountable', function () { - function validate(prop) { - return CustomPropTypes.mountable({p: prop}, 'p', 'Component'); - } - - isChainableAndUndefinedOK(CustomPropTypes.mountable); - - it('Should return error with non mountable values', function() { - let err = validate({}); - assert.instanceOf(err, Error); - assert.include(err.message, 'expected a DOM element or an object that has a `render` method'); - }); - - it('Should return undefined with mountable values', function() { - assert.isUndefined(validate(document.createElement('div'))); - assert.isUndefined(validate(document.body)); - assert.isUndefined(validate(ReactTestUtils.renderIntoDocument(
))); - }); - }); - - describe('elementType', function () { - function validate(prop) { - return CustomPropTypes.elementType({p: prop}, 'p', 'TestComponent'); - } - - isChainableAndUndefinedOK(CustomPropTypes.elementType); - - 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) { - return CustomPropTypes.keyOf(obj)({p: prop}, 'p', 'Component'); - } - - isChainableAndUndefinedOK(CustomPropTypes.keyOf(obj)); - - it('Should return error with non-key values', function() { - let err = validate('bar'); - assert.instanceOf(err, Error); - assert.include(err.message, 'expected one of ["foo"]'); - }); - - it('Should validate OK with key values', function() { - assert.isUndefined(validate('foo')); - obj.bar = 2; - assert.isUndefined(validate('bar')); - }); - }); - - describe('singlePropFrom', function () { - function validate(testProps) { - const propList = ['children', 'value']; - - return CustomPropTypes.singlePropFrom(propList)(testProps, 'value', 'Component'); - } - - it('Should validate OK if only one listed prop in used', function () { - const testProps = {value: 5}; - - assert.isUndefined(validate(testProps)); - }); - - it('Should return error if multiple of the listed properties have values', function () { - let err = validate({value: 5, children: 5}); - assert.instanceOf(err, Error); - assert.include(err.message, 'only one of the following may be provided: value and children'); - }); - }); - - describe('all', function() { - let validators; - const props = { - key: 'value' - }; - const propName = 'key'; - const componentName = 'TestComponent'; - - beforeEach(function() { - validators = [ - sinon.stub(), - sinon.stub(), - sinon.stub() - ]; - }); - - it('with no arguments provided', function() { - expect(() => { - CustomPropTypes.all(); - }).to.throw(Error, /No validations provided/); - }); - - it('with no validations provided', function() { - expect(() => { - CustomPropTypes.all([]); - }).to.throw(Error, /No validations provided/); - }); - - it('with invalid arguments provided', function() { - expect(() => { - CustomPropTypes.all(1); - }).to.throw(Error, /Invalid argument must be an array/); - }); - - it('validates each validation', function() { - const all = CustomPropTypes.all(validators); - - let result = all(props, propName, componentName); - expect(result).to.equal(undefined); - - validators.forEach(x => { - x.should.have.been.calledOnce - .and.calledWith(props, propName, componentName); - }); - }); - - it('returns first validation failure', function() { - let err = new Error('Failure'); - validators[1].returns(err); - const all = CustomPropTypes.all(validators); - - let result = all(props, propName, componentName); - expect(result).to.equal(err); - - validators[0].should.have.been.calledOnce - .and.calledWith(props, propName, componentName); - - validators[1].should.have.been.calledOnce - .and.calledWith(props, propName, componentName); - - validators[2].should.not.have.been.called; - }); - }); - - describe('isRequiredForA11y', function () { - function validate(prop) { - return CustomPropTypes.isRequiredForA11y(React.PropTypes.string)({p: prop}, 'p', 'Component'); - } - - it('Should validate OK when property is provided', function() { - let err = validate('aria-tag'); - assert.notInstanceOf(err, Error); - }); - - it('Should return custom error message when property is not provided', function() { - let err = validate(null); - assert.instanceOf(err, Error); - assert.include(err.message, 'accessible for users using assistive technologies such as screen readers'); - }); - }); - - describe('deprecated', function () { - function validate(prop) { - return CustomPropTypes.deprecated(React.PropTypes.string, 'Read more at link')({pName: prop}, 'pName', 'ComponentName'); - } - - it('Should warn about deprecation and validate OK', function() { - let err = validate('value'); - shouldWarn('"pName" property of "ComponentName" has been deprecated.\nRead more at link'); - assert.notInstanceOf(err, Error); - }); - - it('Should warn about deprecation and throw validation error when property value is not OK', function() { - let err = validate({}); - shouldWarn('"pName" property of "ComponentName" has been deprecated.\nRead more at link'); - assert.instanceOf(err, Error); - assert.include(err.message, 'Invalid undefined `pName` of type `object` supplied to `ComponentName`'); - }); - }); -});