From dfec02351fd0cc9c222643c27853122ea12dc821 Mon Sep 17 00:00:00 2001 From: Matthew Smith Date: Fri, 22 May 2015 14:58:17 -0600 Subject: [PATCH] [added] CustomPropType.all to allow multiple validations This will allow us to have multiple validations applied on a single prop. --- karma.conf.js | 3 +- package.json | 4 +- src/utils/CustomPropTypes.js | 31 ++++++++++- test/.eslintrc | 1 + test/{ => utils}/CustomPropTypesSpec.js | 68 ++++++++++++++++++++++++- 5 files changed, 101 insertions(+), 6 deletions(-) rename test/{ => utils}/CustomPropTypesSpec.js (55%) diff --git a/karma.conf.js b/karma.conf.js index fc0e6c6a4c..b85ff0b313 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -13,7 +13,8 @@ module.exports = function (config) { frameworks: [ 'mocha', 'chai', - 'sinon' + 'sinon', + 'sinon-chai' ], files: [ diff --git a/package.json b/package.json index 4b6b6e219f..9bc23811e6 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "karma-mocha-reporter": "^1.0.2", "karma-phantomjs-launcher": "^0.1.4", "karma-sinon": "^1.0.3", + "karma-sinon-chai": "^0.3.0", "karma-sourcemap-loader": "^0.3.4", "karma-webpack": "^1.5.0", "less": "^2.4.0", @@ -79,6 +80,7 @@ "rimraf": "^2.3.2", "semver": "^4.3.4", "sinon": "^1.10.3", + "sinon-chai": "^2.7.0", "style-loader": "^0.12.0", "transform-loader": "^0.2.1", "webpack": "^1.7.2", @@ -88,4 +90,4 @@ "dependencies": { "classnames": "^2.0.0" } -} \ No newline at end of file +} diff --git a/src/utils/CustomPropTypes.js b/src/utils/CustomPropTypes.js index 5a6ae4e0fc..9f930db324 100644 --- a/src/utils/CustomPropTypes.js +++ b/src/utils/CustomPropTypes.js @@ -1,6 +1,6 @@ const ANONYMOUS = '<>'; -let CustomPropTypes = { +const CustomPropTypes = { /** * Checks whether a prop provides a DOM element * @@ -14,6 +14,7 @@ let CustomPropTypes = { * @returns {Error|undefined} */ mountable: createMountableChecker(), + /** * Checks whether a prop matches a key of an associated object * @@ -32,7 +33,9 @@ let CustomPropTypes = { * @param componentName * @returns {Error|undefined} */ - singlePropFrom: createSinglePropFromChecker + singlePropFrom: createSinglePropFromChecker, + + all }; /** @@ -108,4 +111,28 @@ function createSinglePropFromChecker(arrOfProps) { 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; + } + } + }; +} + export default CustomPropTypes; diff --git a/test/.eslintrc b/test/.eslintrc index 87f84c88e2..bb09bc4415 100644 --- a/test/.eslintrc +++ b/test/.eslintrc @@ -4,6 +4,7 @@ }, "globals": { "assert": true, + "expect": true, "sinon": true }, "plugins": [ diff --git a/test/CustomPropTypesSpec.js b/test/utils/CustomPropTypesSpec.js similarity index 55% rename from test/CustomPropTypesSpec.js rename to test/utils/CustomPropTypesSpec.js index d28137f585..9e4e78de07 100644 --- a/test/CustomPropTypesSpec.js +++ b/test/utils/CustomPropTypesSpec.js @@ -1,8 +1,8 @@ import React from 'react'; import ReactTestUtils from 'react/lib/ReactTestUtils'; -import CustomPropTypes from '../src/utils/CustomPropTypes'; +import CustomPropTypes from '../../src/utils/CustomPropTypes'; -describe('CustomPropTypes', function () { +describe('CustomPropTypes', function() { describe('mountable', function () { function validate(prop) { @@ -67,4 +67,68 @@ describe('CustomPropTypes', function () { validate(testProps).should.be.instanceOf(Error); }); }); + + 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; + }); + }); });