├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── karma.conf.js ├── package.json ├── src ├── PropTypes.js ├── __tests__ │ └── PropTypes-test.js ├── index.js ├── validate.js ├── validateWithErrors.js └── warning.js └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | lib/* 2 | *.log 3 | node_modules 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src/* 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | For React software 4 | 5 | Copyright (c) 2013-2015, Facebook, Inc. 6 | All rights reserved. 7 | 8 | Redistribution and use in source and binary forms, with or without modification, 9 | are permitted provided that the following conditions are met: 10 | 11 | * Redistributions of source code must retain the above copyright notice, this 12 | list of conditions and the following disclaimer. 13 | 14 | * Redistributions in binary form must reproduce the above copyright notice, 15 | this list of conditions and the following disclaimer in the documentation 16 | and/or other materials provided with the distribution. 17 | 18 | * Neither the name Facebook nor the names of its contributors may be used to 19 | endorse or promote products derived from this software without specific 20 | prior written permission. 21 | 22 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 23 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 24 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 25 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 26 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 27 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 29 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 31 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Install 2 | 3 | ``` 4 | npm install --save prop-types 5 | ``` 6 | 7 | ### Notice 8 | 9 | The source code presented here is representative of `prop-types@0.2.0`. 10 | 11 | The npm releases have since been turned over to the team responsible for React. 12 | 13 | The latest releases of `prop-types` on npm are representative of the source code in the React codebase. 14 | 15 | The source code for `prop-types@15.5.0` lives [here](https://github.com/facebook/react/tree/v15.5.0/addons/prop-types) and any further releases of `prop-types` will be based on the source living in the React codebase. 16 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | config.set({ 3 | basePath: '', 4 | 5 | frameworks: ['jasmine'], 6 | 7 | files: ['src/__tests__/*-test.js'], 8 | 9 | preprocessors: { 10 | 'src/__tests__/*-test.js': ['webpack'], 11 | }, 12 | 13 | reporters: ['progress'], 14 | 15 | port: 9876, 16 | 17 | colors: true, 18 | 19 | logLevel: config.LOG_INFO, 20 | 21 | autoWatch: true, 22 | 23 | browsers: ['PhantomJS'], 24 | 25 | singleRun: true, 26 | 27 | webpackMiddleware: { 28 | noInfo: true 29 | }, 30 | 31 | webpack: { 32 | module: { 33 | loaders: [ 34 | { test: /\.js/, loader: 'babel-loader' } 35 | ] 36 | } 37 | }, 38 | 39 | webpackPort: 1234 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prop-types", 3 | "version": "0.2.0", 4 | "description": "PropType validation extracted from React", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "test": "karma start karma.conf.js", 8 | "prepublish": "webpack" 9 | }, 10 | "author": "Aaron Ackerman ", 11 | "license": "BSD", 12 | "devDependencies": { 13 | "babel-core": "^4.0.1", 14 | "babel-loader": "^4.0.0", 15 | "eslint": "^0.14.1", 16 | "jasmine-core": "^2.1.3", 17 | "karma": "^0.13.22", 18 | "karma-chrome-launcher": "^0.1.7", 19 | "karma-cli": "0.0.4", 20 | "karma-jasmine": "^0.3.3", 21 | "karma-phantomjs-launcher": "^1.0.0", 22 | "karma-webpack": "^1.3.1", 23 | "phantomjs-prebuilt": "^2.1.7", 24 | "webpack": "^1.4.15", 25 | "webpack-dev-server": "^1.7.0" 26 | }, 27 | "directories": { 28 | "test": "test" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "https://github.com/aackerman/PropTypes.git" 33 | }, 34 | "bugs": { 35 | "url": "https://github.com/aackerman/PropTypes/issues" 36 | }, 37 | "homepage": "https://github.com/aackerman/PropTypes", 38 | "dependencies": { 39 | "invariant": "^2.2.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/PropTypes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | function nullFunction() { 12 | return null; 13 | } 14 | 15 | var ANONYMOUS = '<>'; 16 | 17 | // Equivalent of `typeof` but with special handling for array and regexp. 18 | function getPropType(propValue) { 19 | var propType = typeof propValue; 20 | if (Array.isArray(propValue)) { 21 | return 'array'; 22 | } 23 | if (propValue instanceof RegExp) { 24 | // Old webkits (at least until Android 4.0) return 'function' rather than 25 | // 'object' for typeof a RegExp. We'll normalize this here so that /bla/ 26 | // passes PropTypes.object. 27 | return 'object'; 28 | } 29 | return propType; 30 | } 31 | 32 | function createChainableTypeChecker(validate) { 33 | function checkType(isRequired, props, propName, descriptiveName, location) { 34 | descriptiveName = descriptiveName || ANONYMOUS; 35 | if (props[propName] == null) { 36 | var locationName = location; 37 | if (isRequired) { 38 | return new Error( 39 | `Required ${locationName} \`${propName}\` was not specified in ` + 40 | `\`${descriptiveName}\`.` 41 | ); 42 | } 43 | return null; 44 | } else { 45 | return validate(props, propName, descriptiveName, location); 46 | } 47 | } 48 | 49 | var chainedCheckType = checkType.bind(null, false); 50 | chainedCheckType.isRequired = checkType.bind(null, true); 51 | 52 | return chainedCheckType; 53 | } 54 | 55 | function createPrimitiveTypeChecker(expectedType) { 56 | function validate(props, propName, descriptiveName, location) { 57 | var propValue = props[propName]; 58 | var propType = getPropType(propValue); 59 | if (propType !== expectedType) { 60 | var locationName = location; 61 | // `propValue` being instance of, say, date/regexp, pass the 'object' 62 | // check, but we can offer a more precise error message here rather than 63 | // 'of type `object`'. 64 | var preciseType = getPreciseType(propValue); 65 | 66 | return new Error( 67 | `Invalid ${locationName} \`${propName}\` of type \`${preciseType}\` ` + 68 | `supplied to \`${descriptiveName}\`, expected \`${expectedType}\`.` 69 | ); 70 | } 71 | return null; 72 | } 73 | return createChainableTypeChecker(validate); 74 | } 75 | 76 | function createAnyTypeChecker() { 77 | return createChainableTypeChecker(nullFunction); 78 | } 79 | 80 | function createArrayOfTypeChecker(typeChecker) { 81 | function validate(props, propName, descriptiveName, location) { 82 | var propValue = props[propName]; 83 | if (!Array.isArray(propValue)) { 84 | var locationName = location; 85 | var propType = getPropType(propValue); 86 | return new Error( 87 | `Invalid ${locationName} \`${propName}\` of type ` + 88 | `\`${propType}\` supplied to \`${descriptiveName}\`, expected an array.` 89 | ); 90 | } 91 | for (var i = 0; i < propValue.length; i++) { 92 | var error = typeChecker(propValue, i, descriptiveName, location); 93 | if (error instanceof Error) { 94 | return error; 95 | } 96 | } 97 | return null; 98 | } 99 | return createChainableTypeChecker(validate); 100 | } 101 | 102 | function createInstanceTypeChecker(expectedClass) { 103 | function validate(props, propName, descriptiveName, location) { 104 | if (!(props[propName] instanceof expectedClass)) { 105 | var locationName = location; 106 | var expectedClassName = expectedClass.name || ANONYMOUS; 107 | return new Error( 108 | `Invalid ${locationName} \`${propName}\` supplied to ` + 109 | `\`${descriptiveName}\`, expected instance of \`${expectedClassName}\`.` 110 | ); 111 | } 112 | return null; 113 | } 114 | return createChainableTypeChecker(validate); 115 | } 116 | 117 | function createEnumTypeChecker(expectedValues) { 118 | function validate(props, propName, descriptiveName, location) { 119 | var propValue = props[propName]; 120 | for (var i = 0; i < expectedValues.length; i++) { 121 | if (propValue === expectedValues[i]) { 122 | return null; 123 | } 124 | } 125 | 126 | var locationName = location; 127 | var valuesString = JSON.stringify(expectedValues); 128 | return new Error( 129 | `Invalid ${locationName} \`${propName}\` of value \`${propValue}\` ` + 130 | `supplied to \`${descriptiveName}\`, expected one of ${valuesString}.` 131 | ); 132 | } 133 | return createChainableTypeChecker(validate); 134 | } 135 | 136 | function createObjectOfTypeChecker(typeChecker) { 137 | function validate(props, propName, descriptiveName, location) { 138 | var propValue = props[propName]; 139 | var propType = getPropType(propValue); 140 | if (propType !== 'object') { 141 | var locationName = location; 142 | return new Error( 143 | `Invalid ${locationName} \`${propName}\` of type ` + 144 | `\`${propType}\` supplied to \`${descriptiveName}\`, expected an object.` 145 | ); 146 | } 147 | for (var key in propValue) { 148 | if (propValue.hasOwnProperty(key)) { 149 | var error = typeChecker(propValue, key, descriptiveName, location); 150 | if (error instanceof Error) { 151 | return error; 152 | } 153 | } 154 | } 155 | return null; 156 | } 157 | return createChainableTypeChecker(validate); 158 | } 159 | 160 | function createUnionTypeChecker(arrayOfTypeCheckers) { 161 | function validate(props, propName, descriptiveName, location) { 162 | for (var i = 0; i < arrayOfTypeCheckers.length; i++) { 163 | var checker = arrayOfTypeCheckers[i]; 164 | if (checker(props, propName, descriptiveName, location) == null) { 165 | return null; 166 | } 167 | } 168 | 169 | var locationName = location; 170 | return new Error( 171 | `Invalid ${locationName} \`${propName}\` supplied to ` + 172 | `\`${descriptiveName}\`.` 173 | ); 174 | } 175 | return createChainableTypeChecker(validate); 176 | } 177 | 178 | function createShapeTypeChecker(shapeTypes) { 179 | function validate(props, propName, descriptiveName, location) { 180 | var propValue = props[propName]; 181 | var propType = getPropType(propValue); 182 | if (propType !== 'object') { 183 | var locationName = location; 184 | return new Error( 185 | `Invalid ${locationName} \`${propName}\` of type \`${propType}\` ` + 186 | `supplied to \`${descriptiveName}\`, expected \`object\`.` 187 | ); 188 | } 189 | for (var key in shapeTypes) { 190 | var checker = shapeTypes[key]; 191 | if (!checker) { 192 | continue; 193 | } 194 | var error = checker(propValue, key, descriptiveName, location); 195 | if (error) { 196 | return error; 197 | } 198 | } 199 | return null; 200 | } 201 | return createChainableTypeChecker(validate); 202 | } 203 | 204 | // This handles more types than `getPropType`. Only used for error messages. 205 | // See `createPrimitiveTypeChecker`. 206 | function getPreciseType(propValue) { 207 | var propType = getPropType(propValue); 208 | if (propType === 'object') { 209 | if (propValue instanceof Date) { 210 | return 'date'; 211 | } else if (propValue instanceof RegExp) { 212 | return 'regexp'; 213 | } 214 | } 215 | return propType; 216 | } 217 | 218 | export default { 219 | array: createPrimitiveTypeChecker('array'), 220 | bool: createPrimitiveTypeChecker('boolean'), 221 | func: createPrimitiveTypeChecker('function'), 222 | number: createPrimitiveTypeChecker('number'), 223 | object: createPrimitiveTypeChecker('object'), 224 | string: createPrimitiveTypeChecker('string'), 225 | 226 | any: createAnyTypeChecker(), 227 | arrayOf: createArrayOfTypeChecker, 228 | instanceOf: createInstanceTypeChecker, 229 | objectOf: createObjectOfTypeChecker, 230 | oneOf: createEnumTypeChecker, 231 | oneOfType: createUnionTypeChecker, 232 | shape: createShapeTypeChecker 233 | }; 234 | -------------------------------------------------------------------------------- /src/__tests__/PropTypes-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | 'use strict'; 12 | 13 | var PropTypes; 14 | 15 | var requiredMessage = 16 | 'Required prop `testProp` was not specified in `testClass`.'; 17 | 18 | function typeCheckFail(declaration, value, message) { 19 | var props = {testProp: value}; 20 | var error = declaration( 21 | props, 22 | 'testProp', 23 | 'testClass', 24 | 'prop' 25 | ); 26 | expect(error instanceof Error).toBe(true); 27 | expect(error.message).toBe(message); 28 | } 29 | 30 | function typeCheckPass(declaration, value) { 31 | var props = {testProp: value}; 32 | var error = declaration( 33 | props, 34 | 'testProp', 35 | 'testClass', 36 | 'prop' 37 | ); 38 | expect(error).toBe(null); 39 | } 40 | 41 | describe('PropTypes', function() { 42 | beforeEach(function() { 43 | PropTypes = require('../index'); 44 | }); 45 | 46 | describe('Primitive Types', function() { 47 | it("should warn for invalid strings", function() { 48 | typeCheckFail( 49 | PropTypes.string, 50 | [], 51 | 'Invalid prop `testProp` of type `array` supplied to ' + 52 | '`testClass`, expected `string`.' 53 | ); 54 | typeCheckFail( 55 | PropTypes.string, 56 | false, 57 | 'Invalid prop `testProp` of type `boolean` supplied to ' + 58 | '`testClass`, expected `string`.' 59 | ); 60 | typeCheckFail( 61 | PropTypes.string, 62 | 0, 63 | 'Invalid prop `testProp` of type `number` supplied to ' + 64 | '`testClass`, expected `string`.' 65 | ); 66 | typeCheckFail( 67 | PropTypes.string, 68 | {}, 69 | 'Invalid prop `testProp` of type `object` supplied to ' + 70 | '`testClass`, expected `string`.' 71 | ); 72 | }); 73 | 74 | it('should fail date and regexp correctly', function() { 75 | typeCheckFail( 76 | PropTypes.string, 77 | new Date(), 78 | 'Invalid prop `testProp` of type `date` supplied to ' + 79 | '`testClass`, expected `string`.' 80 | ); 81 | typeCheckFail( 82 | PropTypes.string, 83 | /please/, 84 | 'Invalid prop `testProp` of type `regexp` supplied to ' + 85 | '`testClass`, expected `string`.' 86 | ); 87 | }); 88 | 89 | it("should not warn for valid values", function() { 90 | typeCheckPass(PropTypes.array, []); 91 | typeCheckPass(PropTypes.bool, false); 92 | typeCheckPass(PropTypes.func, function() {}); 93 | typeCheckPass(PropTypes.number, 0); 94 | typeCheckPass(PropTypes.string, ''); 95 | typeCheckPass(PropTypes.object, {}); 96 | typeCheckPass(PropTypes.object, new Date()); 97 | typeCheckPass(PropTypes.object, /please/); 98 | }); 99 | 100 | it("should be implicitly optional and not warn without values", function() { 101 | typeCheckPass(PropTypes.string, null); 102 | typeCheckPass(PropTypes.string, undefined); 103 | }); 104 | 105 | it("should warn for missing required values", function() { 106 | typeCheckFail(PropTypes.string.isRequired, null, requiredMessage); 107 | typeCheckFail(PropTypes.string.isRequired, undefined, requiredMessage); 108 | }); 109 | }); 110 | 111 | describe('Any type', function() { 112 | it('should should accept any value', function() { 113 | typeCheckPass(PropTypes.any, 0); 114 | typeCheckPass(PropTypes.any, 'str'); 115 | typeCheckPass(PropTypes.any, []); 116 | }); 117 | 118 | it("should be implicitly optional and not warn without values", function() { 119 | typeCheckPass(PropTypes.any, null); 120 | typeCheckPass(PropTypes.any, undefined); 121 | }); 122 | 123 | it("should warn for missing required values", function() { 124 | typeCheckFail(PropTypes.any.isRequired, null, requiredMessage); 125 | typeCheckFail(PropTypes.any.isRequired, undefined, requiredMessage); 126 | }); 127 | }); 128 | 129 | describe('ArrayOf Type', function() { 130 | it('should support the arrayOf propTypes', function() { 131 | typeCheckPass(PropTypes.arrayOf(PropTypes.number), [1, 2, 3]); 132 | typeCheckPass(PropTypes.arrayOf(PropTypes.string), ['a', 'b', 'c']); 133 | typeCheckPass(PropTypes.arrayOf(PropTypes.oneOf(['a', 'b'])), ['a', 'b']); 134 | }); 135 | 136 | it('should support arrayOf with complex types', function() { 137 | typeCheckPass( 138 | PropTypes.arrayOf(PropTypes.shape({a: PropTypes.number.isRequired})), 139 | [{a: 1}, {a: 2}] 140 | ); 141 | 142 | function Thing() {} 143 | typeCheckPass( 144 | PropTypes.arrayOf(PropTypes.instanceOf(Thing)), 145 | [new Thing(), new Thing()] 146 | ); 147 | }); 148 | 149 | it('should warn with invalid items in the array', function() { 150 | typeCheckFail( 151 | PropTypes.arrayOf(PropTypes.number), 152 | [1, 2, 'b'], 153 | 'Invalid prop `2` of type `string` supplied to `testClass`, ' + 154 | 'expected `number`.' 155 | ); 156 | }); 157 | 158 | it('should warn with invalid complex types', function() { 159 | function Thing() {} 160 | var name = Thing.name || '<>'; 161 | 162 | typeCheckFail( 163 | PropTypes.arrayOf(PropTypes.instanceOf(Thing)), 164 | [new Thing(), 'xyz'], 165 | 'Invalid prop `1` supplied to `testClass`, expected instance of `' + 166 | name + '`.' 167 | ); 168 | }); 169 | 170 | it('should warn when passed something other than an array', function() { 171 | typeCheckFail( 172 | PropTypes.arrayOf(PropTypes.number), 173 | {'0': 'maybe-array', length: 1}, 174 | 'Invalid prop `testProp` of type `object` supplied to ' + 175 | '`testClass`, expected an array.' 176 | ); 177 | typeCheckFail( 178 | PropTypes.arrayOf(PropTypes.number), 179 | 123, 180 | 'Invalid prop `testProp` of type `number` supplied to ' + 181 | '`testClass`, expected an array.' 182 | ); 183 | typeCheckFail( 184 | PropTypes.arrayOf(PropTypes.number), 185 | 'string', 186 | 'Invalid prop `testProp` of type `string` supplied to ' + 187 | '`testClass`, expected an array.' 188 | ); 189 | }); 190 | 191 | it('should not warn when passing an empty array', function() { 192 | typeCheckPass(PropTypes.arrayOf(PropTypes.number), []); 193 | }); 194 | 195 | it("should be implicitly optional and not warn without values", function() { 196 | typeCheckPass(PropTypes.arrayOf(PropTypes.number), null); 197 | typeCheckPass(PropTypes.arrayOf(PropTypes.number), undefined); 198 | }); 199 | 200 | it("should warn for missing required values", function() { 201 | typeCheckFail( 202 | PropTypes.arrayOf(PropTypes.number).isRequired, 203 | null, 204 | requiredMessage 205 | ); 206 | typeCheckFail( 207 | PropTypes.arrayOf(PropTypes.number).isRequired, 208 | undefined, 209 | requiredMessage 210 | ); 211 | }); 212 | }); 213 | 214 | describe('Instance Types', function() { 215 | it("should warn for invalid instances", function() { 216 | function Person() {} 217 | var personName = Person.name || '<>'; 218 | var dateName = Date.name || '<>'; 219 | var regExpName = RegExp.name || '<>'; 220 | 221 | typeCheckFail( 222 | PropTypes.instanceOf(Person), 223 | false, 224 | 'Invalid prop `testProp` supplied to `testClass`, expected ' + 225 | 'instance of `' + personName + '`.' 226 | ); 227 | typeCheckFail( 228 | PropTypes.instanceOf(Person), 229 | {}, 230 | 'Invalid prop `testProp` supplied to `testClass`, expected ' + 231 | 'instance of `' + personName + '`.' 232 | ); 233 | typeCheckFail( 234 | PropTypes.instanceOf(Person), 235 | '', 236 | 'Invalid prop `testProp` supplied to `testClass`, expected ' + 237 | 'instance of `' + personName + '`.' 238 | ); 239 | typeCheckFail( 240 | PropTypes.instanceOf(Date), 241 | {}, 242 | 'Invalid prop `testProp` supplied to `testClass`, expected ' + 243 | 'instance of `' + dateName + '`.' 244 | ); 245 | typeCheckFail( 246 | PropTypes.instanceOf(RegExp), 247 | {}, 248 | 'Invalid prop `testProp` supplied to `testClass`, expected ' + 249 | 'instance of `' + regExpName + '`.' 250 | ); 251 | }); 252 | 253 | it("should not warn for valid values", function() { 254 | function Person() {} 255 | function Engineer() {} 256 | Engineer.prototype = new Person(); 257 | 258 | typeCheckPass(PropTypes.instanceOf(Person), new Person()); 259 | typeCheckPass(PropTypes.instanceOf(Person), new Engineer()); 260 | 261 | typeCheckPass(PropTypes.instanceOf(Date), new Date()); 262 | typeCheckPass(PropTypes.instanceOf(RegExp), /please/); 263 | }); 264 | 265 | it("should be implicitly optional and not warn without values", function() { 266 | typeCheckPass(PropTypes.instanceOf(String), null); 267 | typeCheckPass(PropTypes.instanceOf(String), undefined); 268 | }); 269 | 270 | it("should warn for missing required values", function() { 271 | typeCheckFail( 272 | PropTypes.instanceOf(String).isRequired, null, requiredMessage 273 | ); 274 | typeCheckFail( 275 | PropTypes.instanceOf(String).isRequired, undefined, requiredMessage 276 | ); 277 | }); 278 | }); 279 | 280 | describe('ObjectOf Type', function() { 281 | it('should support the objectOf propTypes', function() { 282 | typeCheckPass(PropTypes.objectOf(PropTypes.number), {a: 1, b: 2, c: 3}); 283 | typeCheckPass( 284 | PropTypes.objectOf(PropTypes.string), 285 | {a: 'a', b: 'b', c: 'c'} 286 | ); 287 | typeCheckPass( 288 | PropTypes.objectOf(PropTypes.oneOf(['a', 'b'])), 289 | {a: 'a', b: 'b'} 290 | ); 291 | }); 292 | 293 | it('should support objectOf with complex types', function() { 294 | typeCheckPass( 295 | PropTypes.objectOf(PropTypes.shape({a: PropTypes.number.isRequired})), 296 | {a: {a: 1}, b: {a: 2}} 297 | ); 298 | 299 | function Thing() {} 300 | typeCheckPass( 301 | PropTypes.objectOf(PropTypes.instanceOf(Thing)), 302 | {a: new Thing(), b: new Thing()} 303 | ); 304 | }); 305 | 306 | it('should warn with invalid items in the object', function() { 307 | typeCheckFail( 308 | PropTypes.objectOf(PropTypes.number), 309 | {a: 1, b: 2, c: 'b'}, 310 | 'Invalid prop `c` of type `string` supplied to `testClass`, ' + 311 | 'expected `number`.' 312 | ); 313 | }); 314 | 315 | it('should warn with invalid complex types', function() { 316 | function Thing() {} 317 | var name = Thing.name || '<>'; 318 | 319 | typeCheckFail( 320 | PropTypes.objectOf(PropTypes.instanceOf(Thing)), 321 | {a: new Thing(), b: 'xyz'}, 322 | 'Invalid prop `b` supplied to `testClass`, expected instance of `' + 323 | name + '`.' 324 | ); 325 | }); 326 | 327 | it('should warn when passed something other than an object', function() { 328 | typeCheckFail( 329 | PropTypes.objectOf(PropTypes.number), 330 | [1, 2], 331 | 'Invalid prop `testProp` of type `array` supplied to ' + 332 | '`testClass`, expected an object.' 333 | ); 334 | typeCheckFail( 335 | PropTypes.objectOf(PropTypes.number), 336 | 123, 337 | 'Invalid prop `testProp` of type `number` supplied to ' + 338 | '`testClass`, expected an object.' 339 | ); 340 | typeCheckFail( 341 | PropTypes.objectOf(PropTypes.number), 342 | 'string', 343 | 'Invalid prop `testProp` of type `string` supplied to ' + 344 | '`testClass`, expected an object.' 345 | ); 346 | }); 347 | 348 | it('should not warn when passing an empty object', function() { 349 | typeCheckPass(PropTypes.objectOf(PropTypes.number), {}); 350 | }); 351 | 352 | it("should be implicitly optional and not warn without values", function() { 353 | typeCheckPass(PropTypes.objectOf(PropTypes.number), null); 354 | typeCheckPass(PropTypes.objectOf(PropTypes.number), undefined); 355 | }); 356 | 357 | it("should warn for missing required values", function() { 358 | typeCheckFail( 359 | PropTypes.objectOf(PropTypes.number).isRequired, 360 | null, 361 | requiredMessage 362 | ); 363 | typeCheckFail( 364 | PropTypes.objectOf(PropTypes.number).isRequired, 365 | undefined, 366 | requiredMessage 367 | ); 368 | }); 369 | }); 370 | 371 | describe('OneOf Types', function() { 372 | it("should warn for invalid strings", function() { 373 | typeCheckFail( 374 | PropTypes.oneOf(['red', 'blue']), 375 | true, 376 | 'Invalid prop `testProp` of value `true` supplied to ' + 377 | '`testClass`, expected one of ["red","blue"].' 378 | ); 379 | typeCheckFail( 380 | PropTypes.oneOf(['red', 'blue']), 381 | [], 382 | 'Invalid prop `testProp` of value `` supplied to `testClass`, ' + 383 | 'expected one of ["red","blue"].' 384 | ); 385 | typeCheckFail( 386 | PropTypes.oneOf(['red', 'blue']), 387 | '', 388 | 'Invalid prop `testProp` of value `` supplied to `testClass`, ' + 389 | 'expected one of ["red","blue"].' 390 | ); 391 | typeCheckFail( 392 | PropTypes.oneOf([0, 'false']), 393 | false, 394 | 'Invalid prop `testProp` of value `false` supplied to ' + 395 | '`testClass`, expected one of [0,"false"].' 396 | ); 397 | }); 398 | 399 | it("should not warn for valid values", function() { 400 | typeCheckPass(PropTypes.oneOf(['red', 'blue']), 'red'); 401 | typeCheckPass(PropTypes.oneOf(['red', 'blue']), 'blue'); 402 | }); 403 | 404 | it("should be implicitly optional and not warn without values", function() { 405 | typeCheckPass(PropTypes.oneOf(['red', 'blue']), null); 406 | typeCheckPass(PropTypes.oneOf(['red', 'blue']), undefined); 407 | }); 408 | 409 | it("should warn for missing required values", function() { 410 | typeCheckFail( 411 | PropTypes.oneOf(['red', 'blue']).isRequired, 412 | null, 413 | requiredMessage 414 | ); 415 | typeCheckFail( 416 | PropTypes.oneOf(['red', 'blue']).isRequired, 417 | undefined, 418 | requiredMessage 419 | ); 420 | }); 421 | }); 422 | 423 | describe('Union Types', function() { 424 | it('should warn if none of the types are valid', function() { 425 | typeCheckFail( 426 | PropTypes.oneOfType([PropTypes.string, PropTypes.number]), 427 | [], 428 | 'Invalid prop `testProp` supplied to `testClass`.' 429 | ); 430 | 431 | var checker = PropTypes.oneOfType([ 432 | PropTypes.shape({a: PropTypes.number.isRequired}), 433 | PropTypes.shape({b: PropTypes.number.isRequired}) 434 | ]); 435 | typeCheckFail( 436 | checker, 437 | {c: 1}, 438 | 'Invalid prop `testProp` supplied to `testClass`.' 439 | ); 440 | }); 441 | 442 | it('should not warn if one of the types are valid', function() { 443 | var checker = PropTypes.oneOfType([ 444 | PropTypes.string, 445 | PropTypes.number 446 | ]); 447 | typeCheckPass(checker, null); 448 | typeCheckPass(checker, 'foo'); 449 | typeCheckPass(checker, 123); 450 | 451 | checker = PropTypes.oneOfType([ 452 | PropTypes.shape({a: PropTypes.number.isRequired}), 453 | PropTypes.shape({b: PropTypes.number.isRequired}) 454 | ]); 455 | typeCheckPass(checker, {a: 1}); 456 | typeCheckPass(checker, {b: 1}); 457 | }); 458 | 459 | it("should be implicitly optional and not warn without values", function() { 460 | typeCheckPass( 461 | PropTypes.oneOfType([PropTypes.string, PropTypes.number]), null 462 | ); 463 | typeCheckPass( 464 | PropTypes.oneOfType([PropTypes.string, PropTypes.number]), undefined 465 | ); 466 | }); 467 | 468 | it("should warn for missing required values", function() { 469 | typeCheckFail( 470 | PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, 471 | null, 472 | requiredMessage 473 | ); 474 | typeCheckFail( 475 | PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, 476 | undefined, 477 | requiredMessage 478 | ); 479 | }); 480 | }); 481 | 482 | describe('Shape Types', function() { 483 | it("should warn for non objects", function() { 484 | typeCheckFail( 485 | PropTypes.shape({}), 486 | 'some string', 487 | 'Invalid prop `testProp` of type `string` supplied to ' + 488 | '`testClass`, expected `object`.' 489 | ); 490 | typeCheckFail( 491 | PropTypes.shape({}), 492 | ['array'], 493 | 'Invalid prop `testProp` of type `array` supplied to ' + 494 | '`testClass`, expected `object`.' 495 | ); 496 | }); 497 | 498 | it("should not warn for empty values", function() { 499 | typeCheckPass(PropTypes.shape({}), undefined); 500 | typeCheckPass(PropTypes.shape({}), null); 501 | typeCheckPass(PropTypes.shape({}), {}); 502 | }); 503 | 504 | it("should not warn for an empty object", function() { 505 | typeCheckPass(PropTypes.shape({}).isRequired, {}); 506 | }); 507 | 508 | it("should not warn for non specified types", function() { 509 | typeCheckPass(PropTypes.shape({}), {key: 1}); 510 | }); 511 | 512 | it("should not warn for valid types", function() { 513 | typeCheckPass(PropTypes.shape({key: PropTypes.number}), {key: 1}); 514 | }); 515 | 516 | it("should warn for required valid types", function() { 517 | typeCheckFail( 518 | PropTypes.shape({key: PropTypes.number.isRequired}), 519 | {}, 520 | 'Required prop `key` was not specified in `testClass`.' 521 | ); 522 | }); 523 | 524 | it("should warn for the first required type", function() { 525 | typeCheckFail( 526 | PropTypes.shape({ 527 | key: PropTypes.number.isRequired, 528 | secondKey: PropTypes.number.isRequired 529 | }), 530 | {}, 531 | 'Required prop `key` was not specified in `testClass`.' 532 | ); 533 | }); 534 | 535 | it("should warn for invalid key types", function() { 536 | typeCheckFail(PropTypes.shape({key: PropTypes.number}), 537 | {key: 'abc'}, 538 | 'Invalid prop `key` of type `string` supplied to `testClass`, ' + 539 | 'expected `number`.' 540 | ); 541 | }); 542 | 543 | it("should be implicitly optional and not warn without values", function() { 544 | typeCheckPass( 545 | PropTypes.shape(PropTypes.shape({key: PropTypes.number})), null 546 | ); 547 | typeCheckPass( 548 | PropTypes.shape(PropTypes.shape({key: PropTypes.number})), undefined 549 | ); 550 | }); 551 | 552 | it("should warn for missing required values", function() { 553 | typeCheckFail( 554 | PropTypes.shape({key: PropTypes.number}).isRequired, 555 | null, 556 | requiredMessage 557 | ); 558 | typeCheckFail( 559 | PropTypes.shape({key: PropTypes.number}).isRequired, 560 | undefined, 561 | requiredMessage 562 | ); 563 | }); 564 | }); 565 | 566 | describe('Custom validator', function() { 567 | beforeEach(function() { 568 | spyOn(console, 'warn'); 569 | }); 570 | 571 | it('should have been called with the right params', function() { 572 | var spy = jasmine.createSpy(); 573 | 574 | var schema = {num: spy}; 575 | PropTypes.validate(schema, {num: 5}, 'Component'); 576 | 577 | expect(spy.calls.argsFor(0).length).toBe(4); // temp double validation 578 | expect(spy.calls.argsFor(0)[1]).toBe('num'); 579 | expect(spy.calls.argsFor(0)[2]).toBe('Component'); 580 | }); 581 | 582 | it('should have been called even if the prop is not present', function() { 583 | var spy = jasmine.createSpy(); 584 | var schema = {num: spy}; 585 | PropTypes.validate(schema, {bla: 5}, 'Component'); 586 | expect(spy.calls.argsFor(0).length).toBe(4); // temp double validation 587 | }); 588 | 589 | it('should have received the validator\'s return value', function() { 590 | var spy = jasmine.createSpy().and.callFake( 591 | function(props, propName, descriptiveName) { 592 | if (props[propName] !== 5) { 593 | return new Error('num must be 5!'); 594 | } 595 | } 596 | ); 597 | 598 | var schema = {num: spy}; 599 | PropTypes.validate(schema, {num: 6}, 'Component'); 600 | 601 | expect(console.warn.calls.argsFor(0).length).toBe(1); 602 | expect(console.warn.calls.argsFor(0)[0]).toBe( 603 | 'Warning: Failed propType: num must be 5!'); 604 | }); 605 | 606 | it('should not warn if the validator returned anything else than an error', 607 | function() { 608 | var spy = jasmine.createSpy().and.callFake( 609 | function(props, propName, descriptiveName) { 610 | return 'This message will never reach anyone'; 611 | } 612 | ); 613 | 614 | var schema = {num: spy}; 615 | PropTypes.validate(schema, {num: 5}, 'Component'); 616 | 617 | expect(console.warn.calls.argsFor(0).length).toBe(0); 618 | } 619 | ); 620 | 621 | it('should throw when using validateWithErrors', function(){ 622 | var spy = jasmine.createSpy().and.callFake( 623 | function(props, propName, descriptiveName) { 624 | if (props[propName] !== 5) { 625 | return new Error('num must be 5!'); 626 | } 627 | } 628 | ); 629 | var schema = {num: spy}; 630 | expect(() => { 631 | PropTypes.validateWithErrors(schema, {num: 6}, 'Component'); 632 | }).toThrow(); 633 | }); 634 | }); 635 | }); 636 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from './PropTypes'; 2 | import validate from './validate'; 3 | import validateWithErrors from './validateWithErrors'; 4 | 5 | var assign = Object.assign || function (target) { 6 | for (var i = 1; i < arguments.length; i++) { 7 | var source = arguments[i]; 8 | for (var key in source) { 9 | if (Object.prototype.hasOwnProperty.call(source, key)) { 10 | target[key] = source[key]; 11 | } 12 | } 13 | } 14 | return target; 15 | }; 16 | 17 | export default assign({}, PropTypes, { validate, validateWithErrors }); 18 | -------------------------------------------------------------------------------- /src/validate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | import invariant from 'invariant'; 12 | import warning from './warning'; 13 | 14 | var loggedTypeFailures = {}; 15 | 16 | var validate = (propTypes, props, className) => { 17 | for (var propName in propTypes) { 18 | if (propTypes.hasOwnProperty(propName)) { 19 | var error; 20 | // Prop type validation may throw. In case they do, we don't want to 21 | // fail the render phase where it didn't fail before. So we log it. 22 | // After these have been cleaned up, we'll let them throw. 23 | try { 24 | // This is intentionally an invariant that gets caught. It's the same 25 | // behavior as without this statement except with a better message. 26 | invariant( 27 | typeof propTypes[propName] === 'function', 28 | '%s: %s type `%s` is invalid; it must be a function, usually from ' + 29 | 'PropTypes.', 30 | className, 31 | 'attributes', 32 | propName 33 | ); 34 | 35 | error = propTypes[propName](props, propName, className, 'prop'); 36 | } catch (ex) { 37 | error = ex; 38 | } 39 | if (error instanceof Error && !(error.message in loggedTypeFailures)) { 40 | // Only monitor this failure once because there tends to be a lot of the 41 | // same error. 42 | loggedTypeFailures[error.message] = true; 43 | warning(false, 'Failed propType: ' + error.message); 44 | } 45 | } 46 | } 47 | }; 48 | 49 | export default validate; 50 | -------------------------------------------------------------------------------- /src/validateWithErrors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2013-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | import invariant from 'invariant'; 12 | 13 | var validateWithErrors = (propTypes, props, className) => { 14 | for (var propName in propTypes) { 15 | if (propTypes.hasOwnProperty(propName)) { 16 | var error; 17 | // Prop type validation may throw. In case they do, we don't want to 18 | // fail the render phase where it didn't fail before. So we log it. 19 | // After these have been cleaned up, we'll let them throw. 20 | try { 21 | // This is intentionally an invariant that gets caught. It's the same 22 | // behavior as without this statement except with a better message. 23 | invariant( 24 | typeof propTypes[propName] === 'function', 25 | '%s: %s type `%s` is invalid; it must be a function, usually from ' + 26 | 'PropTypes.', 27 | className, 28 | 'attributes', 29 | propName 30 | ); 31 | 32 | error = propTypes[propName](props, propName, className, 'prop'); 33 | } catch (ex) { 34 | error = ex; 35 | } 36 | // rethrow the error 37 | if (error instanceof Error) { throw error; } 38 | } 39 | } 40 | }; 41 | 42 | export default validateWithErrors; 43 | -------------------------------------------------------------------------------- /src/warning.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2014-2015, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | * 9 | */ 10 | 11 | var warning = (condition, format, ...args) => { 12 | if (format === undefined) { 13 | throw new Error( 14 | '`warning(condition, format, ...args)` requires a warning ' + 15 | 'message argument' 16 | ); 17 | } 18 | 19 | if (format.length < 10 || /^[s\W]*$/.test(format)) { 20 | throw new Error( 21 | 'The warning format should be able to uniquely identify this ' + 22 | 'warning. Please, use a more descriptive format than: ' + format 23 | ); 24 | } 25 | 26 | if (!condition) { 27 | var argIndex = 0; 28 | var message = 'Warning: ' + format.replace(/%s/g, () => args[argIndex++]); 29 | console.warn(message); 30 | try { 31 | // This error was thrown as a convenience so that you can use this stack 32 | // to find the callsite that caused this warning to fire. 33 | throw new Error(message); 34 | } catch(x) {} 35 | } 36 | }; 37 | 38 | export default warning; 39 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | 3 | module.exports = { 4 | entry: "./src/index", 5 | output: { 6 | path: __dirname + '/lib', 7 | filename: 'index.js', 8 | libraryTarget: 'commonjs2' 9 | }, 10 | module: { 11 | loaders: [ 12 | { test: /\.js/, loader: 'babel-loader' } 13 | ] 14 | } 15 | } 16 | --------------------------------------------------------------------------------