@samitouri / QOS-React-2 / commits / 8855d6153e

Revamp and fix PropTypes

This uses the return value (an Error or nothing) to indicate whether a prop passes validation or not. It used to be done through calling `console.warn` as a side-effect, except this didn't work well with and `oneOfType`, which calls each validator. The solution was to insert a `.weak` prop to each validator to suppress the warning message. This is overkill and also doesn't work well in because it increases the potential API surface. Plus, letting the validators warn it can't be easily used for logging, especially for custom validators. So `.weak` is no longer needed: fixes https://github.com/facebook/react/issues/863. Backward compatibility for custom validators: since they didn't return anything and directly call `console.warn` or something, this doesn't break them. Improvements: - `arrayOf`, `oneOfType` and `oneOf` got better message now, since they can get hold of their validator's message and output that instead of a generic `warning: bla`. - More complete tests, including testing custom types and `isRequired` on everything. Bug fixes: - oneOfType(...).isRequired didn't work. The workaround was to use `oneOfType(a.isRequired, b.isRequired, ...)`. This means `oneOfType(a.isRequired, b)` doesn't make sense. The new version simply makes `oneOfType(...).isRequired` possible. - `oneOf([true])` worked for 'true' boolean because it converted everything to string before comparing. It no longer does. - `oneOf([true]).isRequired` didn't work. (see #1294)

Cheng Lou committed Apr 9, 2014 at 17:50 UTC 8855d6153e252c735de0e6cc373787d22c1a467b
3 files changed +655 -738
src/core/ReactCompositeComponent.js
+5 -1
@@ -927,7 +927,11 @@ var ReactCompositeComponentMixin = {
927 var componentName = this.constructor.displayName;
928 for (var propName in propTypes) {
929 if (propTypes.hasOwnProperty(propName)) {
930 - propTypes[propName](props, propName, componentName, location);
930 + var error =
931 + propTypes[propName](props, propName, componentName, location);
932 + if (error instanceof Error) {
933 + warning(false, error.message);
934 + }
935 }
936 }
937 },
src/core/ReactPropTypes.js
+173 -239
@@ -21,8 +21,7 @@
21 var ReactDescriptor = require('ReactDescriptor');
22 var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
23
24 -var warning = require('warning');
25 -var createObjectFrom = require('createObjectFrom');
24 +var emptyFunction = require('emptyFunction');
25
26 /**
27 * Collection of methods that allow declaration and validation of props that are
@@ -51,29 +50,28 @@ var createObjectFrom = require('createObjectFrom');
50 * Each and every declaration produces a function with the same signature. This
51 * allows the creation of custom validation functions. For example:
52 *
54 - * var Props = require('ReactPropTypes');
55 - * var MyLink = React.createClass({
56 - * propTypes: {
57 - * // An optional string or URI prop named "href".
58 - * href: function(props, propName, componentName) {
59 - * var propValue = props[propName];
60 - * warning(
61 - * propValue == null ||
62 - * typeof propValue === 'string' ||
63 - * propValue instanceof URI,
64 - * 'Invalid `%s` supplied to `%s`, expected string or URI.',
65 - * propName,
66 - * componentName
67 - * );
68 - * }
69 - * },
70 - * render: function() { ... }
71 - * });
53 + * var MyLink = React.createClass({
54 + * propTypes: {
55 + * // An optional string or URI prop named "href".
56 + * href: function(props, propName, componentName) {
57 + * var propValue = props[propName];
58 + * if (typeof propValue === 'string' || propValue instanceof URI) {
59 + * return new Error(
60 + * 'Expected a string or an URI for ' + propName + ' in ' +
61 + * componentName
62 + * );
63 + * }
64 + * }
65 + * },
66 + * render: function() {...}
67 + * });
68 *
69 * @internal
70 */
75 -var Props = {
71
72 +var ANONYMOUS = '<<anonymous>>';
73 +
74 +var ReactPropTypes = {
75 array: createPrimitiveTypeChecker('array'),
76 bool: createPrimitiveTypeChecker('boolean'),
77 func: createPrimitiveTypeChecker('function'),
@@ -81,279 +79,215 @@ var Props = {
79 object: createPrimitiveTypeChecker('object'),
80 string: createPrimitiveTypeChecker('string'),
81
84 - shape: createShapeTypeChecker,
85 - oneOf: createEnumTypeChecker,
86 - oneOfType: createUnionTypeChecker,
82 + any: createAnyTypeChecker(),
83 arrayOf: createArrayOfTypeChecker,
88 -
84 + component: createComponentTypeChecker(),
85 instanceOf: createInstanceTypeChecker,
90 -
86 + oneOf: createEnumTypeChecker,
87 + oneOfType: createUnionTypeChecker,
88 renderable: createRenderableTypeChecker(),
92 -
93 - component: createComponentTypeChecker(),
94 -
95 - any: createAnyTypeChecker()
89 + shape: createShapeTypeChecker
90 };
91
98 -var ANONYMOUS = '<<anonymous>>';
99 -
100 -function isRenderable(propValue) {
101 - switch(typeof propValue) {
102 - case 'number':
103 - case 'string':
104 - return true;
105 - case 'object':
106 - if (Array.isArray(propValue)) {
107 - return propValue.every(isRenderable);
108 - }
109 - if (ReactDescriptor.isValidDescriptor(propValue)) {
110 - return true;
111 - }
112 - for (var k in propValue) {
113 - if (!isRenderable(propValue[k])) {
114 - return false;
115 - }
92 +function createChainableTypeChecker(validate) {
93 + function checkType(isRequired, props, propName, componentName, location) {
94 + componentName = componentName || ANONYMOUS;
95 + if (props[propName] == null) {
96 + var locationName = ReactPropTypeLocationNames[location];
97 + if (isRequired) {
98 + return new Error(
99 + `Required ${locationName} \`${propName}\` was not specified in `+
100 + `\`${componentName}\`.`
101 + );
102 }
117 - return true;
118 - default:
119 - return false;
103 + } else {
104 + return validate(props, propName, componentName, location);
105 + }
106 }
121 -}
107
123 -// Equivalent of typeof but with special handling for arrays
124 -function getPropType(propValue) {
125 - var propType = typeof propValue;
126 - if (propType === 'object' && Array.isArray(propValue)) {
127 - return 'array';
128 - }
129 - return propType;
130 -}
108 + var chainedCheckType = checkType.bind(null, false);
109 + chainedCheckType.isRequired = checkType.bind(null, true);
110
132 -function createAnyTypeChecker() {
133 - function validateAnyType(
134 - shouldWarn, propValue, propName, componentName, location
135 - ) {
136 - return true; // is always valid
137 - }
138 - return createChainableTypeChecker(validateAnyType);
111 + return chainedCheckType;
112 }
113
114 function createPrimitiveTypeChecker(expectedType) {
142 - function validatePrimitiveType(
143 - shouldWarn, propValue, propName, componentName, location
144 - ) {
145 - var propType = getPropType(propValue);
146 - var isValid = propType === expectedType;
147 - if (shouldWarn) {
148 - warning(
149 - isValid,
150 - 'Invalid %s `%s` of type `%s` supplied to `%s`, expected `%s`.',
151 - ReactPropTypeLocationNames[location],
152 - propName,
153 - propType,
154 - componentName,
155 - expectedType
115 + function validate(props, propName, componentName, location) {
116 + var propType = getPropType(props[propName]);
117 + if (propType !== expectedType) {
118 + var locationName = ReactPropTypeLocationNames[location];
119 + return new Error(
120 + `Invalid ${locationName} \`${propName}\` of type \`${propType}\` ` +
121 + `supplied to \`${componentName}\`, expected \`${expectedType}\`.`
122 );
123 }
158 - return isValid;
124 }
160 - return createChainableTypeChecker(validatePrimitiveType);
125 + return createChainableTypeChecker(validate);
126 }
127
163 -function createEnumTypeChecker(expectedValues) {
164 - var expectedEnum = createObjectFrom(expectedValues);
165 - function validateEnumType(
166 - shouldWarn, propValue, propName, componentName, location
167 - ) {
168 - var isValid = expectedEnum[propValue];
169 - if (shouldWarn) {
170 - warning(
171 - isValid,
172 - 'Invalid %s `%s` supplied to `%s`, expected one of %s.',
173 - ReactPropTypeLocationNames[location],
174 - propName,
175 - componentName,
176 - JSON.stringify(Object.keys(expectedEnum))
128 +function createAnyTypeChecker() {
129 + return createChainableTypeChecker(emptyFunction.thatReturns());
130 +}
131 +
132 +function createArrayOfTypeChecker(typeChecker) {
133 + function validate(props, propName, componentName, location) {
134 + var propValue = props[propName];
135 + if (!Array.isArray(propValue)) {
136 + var locationName = ReactPropTypeLocationNames[location];
137 + var propType = getPropType(propValue);
138 + return new Error(
139 + `Invalid ${locationName} \`${propName}\` of type ` +
140 + `\`${propType}\` supplied to \`${componentName}\`, expected an array.`
141 );
142 }
179 - return isValid;
143 + for (var i = 0; i < propValue.length; i++) {
144 + var error = typeChecker(propValue, i, componentName, location);
145 + if (error instanceof Error) {
146 + return error;
147 + }
148 + }
149 }
181 - return createChainableTypeChecker(validateEnumType);
150 + return createChainableTypeChecker(validate);
151 }
152
184 -function createShapeTypeChecker(shapeTypes) {
185 - function validateShapeType(
186 - shouldWarn, propValue, propName, componentName, location
187 - ) {
188 - var propType = getPropType(propValue);
189 - var isValid = propType === 'object';
190 - if (isValid) {
191 - for (var key in shapeTypes) {
192 - var checker = shapeTypes[key];
193 - if (checker && !checker(propValue, key, componentName, location)) {
194 - return false;
195 - }
196 - }
197 - }
198 - if (shouldWarn) {
199 - warning(
200 - isValid,
201 - 'Invalid %s `%s` of type `%s` supplied to `%s`, expected `object`.',
202 - ReactPropTypeLocationNames[location],
203 - propName,
204 - propType,
205 - componentName
153 +function createComponentTypeChecker() {
154 + function validate(props, propName, componentName, location) {
155 + if (!ReactDescriptor.isValidDescriptor(props[propName])) {
156 + var locationName = ReactPropTypeLocationNames[location];
157 + return new Error(
158 + `Invalid ${locationName} \`${propName}\` supplied to ` +
159 + `\`${componentName}\`, expected a React component.`
160 );
161 }
208 - return isValid;
162 }
210 - return createChainableTypeChecker(validateShapeType);
163 + return createChainableTypeChecker(validate);
164 }
165
166 function createInstanceTypeChecker(expectedClass) {
214 - function validateInstanceType(
215 - shouldWarn, propValue, propName, componentName, location
216 - ) {
217 - var isValid = propValue instanceof expectedClass;
218 - if (shouldWarn) {
219 - warning(
220 - isValid,
221 - 'Invalid %s `%s` supplied to `%s`, expected instance of `%s`.',
222 - ReactPropTypeLocationNames[location],
223 - propName,
224 - componentName,
225 - expectedClass.name || ANONYMOUS
167 + function validate(props, propName, componentName, location) {
168 + if (!(props[propName] instanceof expectedClass)) {
169 + var locationName = ReactPropTypeLocationNames[location];
170 + var expectedClassName = expectedClass.name || ANONYMOUS;
171 + return new Error(
172 + `Invalid ${locationName} \`${propName}\` supplied to ` +
173 + `\`${componentName}\`, expected instance of \`${expectedClassName}\`.`
174 );
175 }
228 - return isValid;
176 }
230 - return createChainableTypeChecker(validateInstanceType);
177 + return createChainableTypeChecker(validate);
178 }
179
233 -function createArrayOfTypeChecker(propTypeChecker) {
234 - function validateArrayType(
235 - shouldWarn, propValue, propName, componentName, location
236 - ) {
237 - var isValid = Array.isArray(propValue);
238 - if (isValid) {
239 - for (var i = 0; i < propValue.length; i++) {
240 - if (!propTypeChecker(propValue, i, componentName, location)) {
241 - return false;
242 - }
180 +function createEnumTypeChecker(expectedValues) {
181 + function validate(props, propName, componentName, location) {
182 + var propValue = props[propName];
183 + for (var i = 0; i < expectedValues.length; i++) {
184 + if (propValue === expectedValues[i]) {
185 + return;
186 }
187 }
245 - if (shouldWarn) {
246 - warning(
247 - isValid,
248 - 'Invalid %s `%s` supplied to `%s`, expected an array.',
249 - ReactPropTypeLocationNames[location],
250 - propName,
251 - componentName
252 - );
188 +
189 + var locationName = ReactPropTypeLocationNames[location];
190 + var valuesString = JSON.stringify(expectedValues);
191 + return new Error(
192 + `Invalid ${locationName} \`${propName}\` of value \`${propValue}\` ` +
193 + `supplied to \`${componentName}\`, expected one of ${valuesString}.`
194 + );
195 + }
196 + return createChainableTypeChecker(validate);
197 +}
198 +
199 +function createUnionTypeChecker(arrayOfTypeCheckers) {
200 + function validate(props, propName, componentName, location) {
201 + for (var i = 0; i < arrayOfTypeCheckers.length; i++) {
202 + var checker = arrayOfTypeCheckers[i];
203 + if (checker(props, propName, componentName, location) == null) {
204 + return;
205 + }
206 }
254 - return isValid;
207 +
208 + var locationName = ReactPropTypeLocationNames[location];
209 + return new Error(
210 + `Invalid ${locationName} \`${propName}\` supplied to ` +
211 + `\`${componentName}\`.`
212 + );
213 }
256 - return createChainableTypeChecker(validateArrayType);
214 + return createChainableTypeChecker(validate);
215 }
216
217 function createRenderableTypeChecker() {
260 - function validateRenderableType(
261 - shouldWarn, propValue, propName, componentName, location
262 - ) {
263 - var isValid = isRenderable(propValue);
264 - if (shouldWarn) {
265 - warning(
266 - isValid,
267 - 'Invalid %s `%s` supplied to `%s`, expected a renderable prop.',
268 - ReactPropTypeLocationNames[location],
269 - propName,
270 - componentName
218 + function validate(props, propName, componentName, location) {
219 + if (!isRenderable(props[propName])) {
220 + var locationName = ReactPropTypeLocationNames[location];
221 + return new Error(
222 + `Invalid ${locationName} \`${propName}\` supplied to ` +
223 + `\`${componentName}\`, expected a renderable prop.`
224 );
225 }
273 - return isValid;
226 }
275 - return createChainableTypeChecker(validateRenderableType);
227 + return createChainableTypeChecker(validate);
228 }
229
278 -function createComponentTypeChecker() {
279 - function validateComponentType(
280 - shouldWarn, propValue, propName, componentName, location
281 - ) {
282 - var isValid = ReactDescriptor.isValidDescriptor(propValue);
283 - if (shouldWarn) {
284 - warning(
285 - isValid,
286 - 'Invalid %s `%s` supplied to `%s`, expected a React component.',
287 - ReactPropTypeLocationNames[location],
288 - propName,
289 - componentName
230 +function createShapeTypeChecker(shapeTypes) {
231 + function validate(props, propName, componentName, location) {
232 + var propValue = props[propName];
233 + var propType = getPropType(propValue);
234 + if (propType !== 'object') {
235 + var locationName = ReactPropTypeLocationNames[location];
236 + return new Error(
237 + `Invalid ${locationName} \`${propName}\` of type \`${propType}\` ` +
238 + `supplied to \`${componentName}\`, expected \`object\`.`
239 );
240 }
292 - return isValid;
241 + for (var key in shapeTypes) {
242 + var checker = shapeTypes[key];
243 + if (!checker) {
244 + continue;
245 + }
246 + var error = checker(propValue, key, componentName, location);
247 + if (error) {
248 + return error;
249 + }
250 + }
251 }
294 - return createChainableTypeChecker(validateComponentType);
252 + return createChainableTypeChecker(validate, 'expected `object`');
253 }
254
297 -function createUnionTypeChecker(arrayOfValidators) {
298 - return function(props, propName, componentName, location) {
299 - var isValid = false;
300 - for (var ii = 0; ii < arrayOfValidators.length; ii++) {
301 - var validate = arrayOfValidators[ii];
302 - if (typeof validate.weak === 'function') {
303 - validate = validate.weak;
255 +function isRenderable(propValue) {
256 + switch(typeof propValue) {
257 + case 'number':
258 + case 'string':
259 + return true;
260 + case 'object':
261 + if (Array.isArray(propValue)) {
262 + return propValue.every(isRenderable);
263 }
305 - if (validate(props, propName, componentName, location)) {
306 - isValid = true;
307 - break;
264 + if (ReactDescriptor.isValidDescriptor(propValue)) {
265 + return true;
266 }
309 - }
310 - warning(
311 - isValid,
312 - 'Invalid %s `%s` supplied to `%s`.',
313 - ReactPropTypeLocationNames[location],
314 - propName,
315 - componentName || ANONYMOUS
316 - );
317 - return isValid;
318 - };
267 + for (var k in propValue) {
268 + if (!isRenderable(propValue[k])) {
269 + return false;
270 + }
271 + }
272 + return true;
273 + default:
274 + return false;
275 + }
276 }
277
321 -function createChainableTypeChecker(validate) {
322 - function checkType(
323 - isRequired, shouldWarn, props, propName, componentName, location
324 - ) {
325 - var propValue = props[propName];
326 - if (propValue != null) {
327 - // Only validate if there is a value to check.
328 - return validate(
329 - shouldWarn,
330 - propValue,
331 - propName,
332 - componentName || ANONYMOUS,
333 - location
334 - );
335 - } else {
336 - var isValid = !isRequired;
337 - if (shouldWarn) {
338 - warning(
339 - isValid,
340 - 'Required %s `%s` was not specified in `%s`.',
341 - ReactPropTypeLocationNames[location],
342 - propName,
343 - componentName || ANONYMOUS
344 - );
345 - }
346 - return isValid;
278 +// Equivalent of typeof but with special handling for arrays
279 +function getPropType(propValue) {
280 + var propType = typeof propValue;
281 + if (propType === 'object') {
282 + if (Array.isArray(propValue)) {
283 + return 'array';
284 + } else if (propValue instanceof Date) {
285 + return 'date';
286 + } else if(propValue instanceof RegExp) {
287 + return 'regexp';
288 }
289 }
349 -
350 - var checker = checkType.bind(null, false, true);
351 - checker.weak = checkType.bind(null, false, false);
352 - checker.isRequired = checkType.bind(null, true, true);
353 - checker.weak.isRequired = checkType.bind(null, true, false);
354 - checker.isRequired.weak = checker.weak.isRequired;
355 -
356 - return checker;
290 + return propType;
291 }
292
359 -module.exports = Props;
293 +module.exports = ReactPropTypes;
src/core/__tests__/ReactPropTypes-test.js
+477 -498
@@ -21,654 +21,633 @@
21
22 var ReactTestUtils;
23
24 -var Props = require('ReactPropTypes');
24 +var PropTypes = require('ReactPropTypes');
25 var React = require('React');
26 var ReactPropTypeLocations = require('ReactPropTypeLocations');
27
28 -var warn;
29 -var mocks;
30 -
31 -function typeCheck(declaration, value) {
32 - var props = {};
33 - if (arguments.length > 1) {
34 - props.testProp = value;
35 - }
36 - return declaration(
37 - props, 'testProp', 'testComponent', ReactPropTypeLocations.prop
28 +var Component;
29 +var MyComponent;
30 +var requiredMessage =
31 + 'Required prop `testProp` was not specified in `testComponent`.';
32 +
33 +function typeCheckFail(declaration, value, message) {
34 + var props = {testProp: value};
35 + var error = declaration(
36 + props,
37 + 'testProp',
38 + 'testComponent',
39 + ReactPropTypeLocations.prop
40 );
41 + expect(error instanceof Error).toBe(true);
42 + expect(error.message).toBe(message);
43 }
44
41 -var MyComponent = React.createClass({
42 - render: function() {
43 - return <div />;
44 - }
45 -});
45 +function typeCheckPass(declaration, value) {
46 + var props = {testProp: value};
47 + var error = declaration(
48 + props,
49 + 'testProp',
50 + 'testComponent',
51 + ReactPropTypeLocations.prop
52 + );
53 + expect(error).toBe(undefined);
54 +}
55
56 describe('Primitive Types', function() {
48 - beforeEach(function() {
49 - require('mock-modules').dumpCache();
50 -
51 - mocks = require('mocks');
52 - ReactTestUtils = require('ReactTestUtils');
53 -
54 - warn = console.warn;
55 - console.warn = mocks.getMockFunction();
56 - });
57 -
58 - afterEach(function() {
59 - console.warn = warn;
60 - });
61 -
57 it("should warn for invalid strings", function() {
63 - typeCheck(Props.string, []);
64 - typeCheck(Props.string, false);
65 - typeCheck(Props.string, 1);
66 - typeCheck(Props.string, {});
67 -
68 - expect(console.warn.mock.calls.length).toBe(4);
69 -
70 - expect(console.warn.mock.calls[0][0]).toBe(
71 - 'Warning: Invalid prop `testProp` of type `array` ' +
72 - 'supplied to `testComponent`, expected `string`.'
58 + typeCheckFail(
59 + PropTypes.string,
60 + [],
61 + 'Invalid prop `testProp` of type `array` supplied to `testComponent`, ' +
62 + 'expected `string`.'
63 );
74 - expect(console.warn.mock.calls[1][0]).toBe(
75 - 'Warning: Invalid prop `testProp` of type `boolean` ' +
76 - 'supplied to `testComponent`, expected `string`.'
64 + typeCheckFail(
65 + PropTypes.string,
66 + false,
67 + 'Invalid prop `testProp` of type `boolean` supplied to ' +
68 + '`testComponent`, expected `string`.'
69 );
78 - expect(console.warn.mock.calls[2][0]).toBe(
79 - 'Warning: Invalid prop `testProp` of type `number` ' +
80 - 'supplied to `testComponent`, expected `string`.'
70 + typeCheckFail(
71 + PropTypes.string,
72 + 0,
73 + 'Invalid prop `testProp` of type `number` supplied to ' +
74 + '`testComponent`, expected `string`.'
75 );
82 - expect(console.warn.mock.calls[3][0]).toBe(
83 - 'Warning: Invalid prop `testProp` of type `object` ' +
84 - 'supplied to `testComponent`, expected `string`.'
76 + typeCheckFail(
77 + PropTypes.string,
78 + {},
79 + 'Invalid prop `testProp` of type `object` supplied to ' +
80 + '`testComponent`, expected `string`.'
81 );
82 });
83
88 - it("should not warn for valid values", function() {
89 - typeCheck(Props.array, []);
90 - typeCheck(Props.bool, false);
91 - typeCheck(Props.func, function() {});
92 - typeCheck(Props.number, 0);
93 - typeCheck(Props.object, {});
94 - typeCheck(Props.string, '');
84 + it('should check for corner case JS types', function() {
85 + typeCheckFail(
86 + PropTypes.string,
87 + new Date(),
88 + 'Invalid prop `testProp` of type `date` supplied to ' +
89 + '`testComponent`, expected `string`.'
90 + );
91 + typeCheckFail(
92 + PropTypes.string,
93 + /please/,
94 + 'Invalid prop `testProp` of type `regexp` supplied to ' +
95 + '`testComponent`, expected `string`.'
96 + );
97 + });
98
96 - // No warnings should have been logged.
97 - expect(console.warn.mock.calls.length).toBe(0);
99 + it("should not warn for valid values", function() {
100 + typeCheckPass(PropTypes.array, []);
101 + typeCheckPass(PropTypes.bool, false);
102 + typeCheckPass(PropTypes.func, function() {});
103 + typeCheckPass(PropTypes.number, 0);
104 + typeCheckPass(PropTypes.object, {});
105 + typeCheckPass(PropTypes.string, '');
106 });
107
108 it("should be implicitly optional and not warn without values", function() {
101 - typeCheck(Props.string, null);
102 - typeCheck(Props.string, undefined);
103 -
104 - // No warnings should have been logged.
105 - expect(console.warn.mock.calls.length).toBe(0);
109 + typeCheckPass(PropTypes.string, null);
110 + typeCheckPass(PropTypes.string, undefined);
111 });
112
113 it("should warn for missing required values", function() {
109 - typeCheck(Props.string.isRequired, null);
110 - typeCheck(Props.string.isRequired, undefined);
114 + typeCheckFail(PropTypes.string.isRequired, null, requiredMessage);
115 + typeCheckFail(PropTypes.string.isRequired, undefined, requiredMessage);
116 + });
117 +});
118
112 - expect(console.warn.mock.calls.length).toBe(2);
119 +describe('Any type', function() {
120 + it('should should accept any value', function() {
121 + typeCheckPass(PropTypes.any, 0);
122 + typeCheckPass(PropTypes.any, 'str');
123 + typeCheckPass(PropTypes.any, []);
124 + });
125
114 - expect(console.warn.mock.calls[0][0]).toBe(
115 - 'Warning: Required prop `testProp` was not specified in ' +
116 - '`testComponent`.'
117 - );
118 - expect(console.warn.mock.calls[1][0]).toBe(
119 - 'Warning: Required prop `testProp` was not specified in ' +
120 - '`testComponent`.'
121 - );
126 + it("should be implicitly optional and not warn without values", function() {
127 + typeCheckPass(PropTypes.any, null);
128 + typeCheckPass(PropTypes.any, undefined);
129 });
130
124 - it("should have a weak version that returns true/false", function() {
125 - expect(typeCheck(Props.string.weak, null)).toEqual(true);
126 - expect(typeCheck(Props.string.weak.isRequired, null)).toEqual(false);
127 - expect(typeCheck(Props.string.isRequired.weak, null)).toEqual(false);
131 + it("should warn for missing required values", function() {
132 + typeCheckFail(PropTypes.any.isRequired, null, requiredMessage);
133 + typeCheckFail(PropTypes.any.isRequired, undefined, requiredMessage);
134 });
135 });
136
131 -describe('Enum Types', function() {
132 - beforeEach(function() {
133 - require('mock-modules').dumpCache();
134 - mocks = require('mocks');
135 - warn = console.warn;
136 - console.warn = mocks.getMockFunction();
137 +describe('ArrayOf Type', function() {
138 + it('should support the arrayOf propTypes', function() {
139 + typeCheckPass(PropTypes.arrayOf(PropTypes.number), [1, 2, 3]);
140 + typeCheckPass(PropTypes.arrayOf(PropTypes.string), ['a', 'b', 'c']);
141 + typeCheckPass(PropTypes.arrayOf(PropTypes.oneOf(['a', 'b'])), ['a', 'b']);
142 });
143
139 - afterEach(function() {
140 - console.warn = warn;
144 + it('should support arrayOf with complex types', function() {
145 + typeCheckPass(
146 + PropTypes.arrayOf(PropTypes.shape({a: PropTypes.number.isRequired})),
147 + [{a: 1}, {a: 2}]
148 + );
149 +
150 + function Thing() {}
151 + typeCheckPass(
152 + PropTypes.arrayOf(PropTypes.instanceOf(Thing)),
153 + [new Thing(), new Thing()]
154 + );
155 });
156
143 - it("should warn for invalid strings", function() {
144 - typeCheck(Props.oneOf(['red', 'blue']), true);
145 - typeCheck(Props.oneOf(['red', 'blue']), []);
146 - typeCheck(Props.oneOf(['red', 'blue']), '');
157 + it('should warn with invalid items in the array', function() {
158 + typeCheckFail(
159 + PropTypes.arrayOf(PropTypes.number),
160 + [1, 2, 'b'],
161 + 'Invalid prop `2` of type `string` supplied to `testComponent`, ' +
162 + 'expected `number`.'
163 + );
164 + });
165
148 - expect(console.warn.mock.calls.length).toBe(3);
166 + it('should warn with invalid complex types', function() {
167 + function Thing() {}
168 + var name = Thing.name || '<<anonymous>>';
169
150 - expect(console.warn.mock.calls[0][0]).toBe(
151 - 'Warning: Invalid prop `testProp` supplied to ' +
152 - '`testComponent`, expected one of ["blue","red"].'
170 + typeCheckFail(
171 + PropTypes.arrayOf(PropTypes.instanceOf(Thing)),
172 + [new Thing(), 'xyz'],
173 + 'Invalid prop `1` supplied to `testComponent`, expected instance of `' +
174 + name + '`.'
175 );
176 + });
177
155 - expect(console.warn.mock.calls[1][0]).toBe(
156 - 'Warning: Invalid prop `testProp` supplied to ' +
157 - '`testComponent`, expected one of ["blue","red"].'
178 + it('should warn when passed something other than an array', function() {
179 + typeCheckFail(
180 + PropTypes.arrayOf(PropTypes.number),
181 + {'0': 'maybe-array', length: 1},
182 + 'Invalid prop `testProp` of type `object` supplied to `testComponent`, ' +
183 + 'expected an array.'
184 );
159 -
160 - expect(console.warn.mock.calls[2][0]).toBe(
161 - 'Warning: Invalid prop `testProp` supplied to ' +
162 - '`testComponent`, expected one of ["blue","red"].'
185 + typeCheckFail(
186 + PropTypes.arrayOf(PropTypes.number),
187 + 123,
188 + 'Invalid prop `testProp` of type `number` supplied to `testComponent`, ' +
189 + 'expected an array.'
190 + );
191 + typeCheckFail(
192 + PropTypes.arrayOf(PropTypes.number),
193 + 'string',
194 + 'Invalid prop `testProp` of type `string` supplied to `testComponent`, ' +
195 + 'expected an array.'
196 );
197 });
198
166 - it("should not warn for valid values", function() {
167 - typeCheck(Props.oneOf(['red', 'blue']), 'red');
168 - typeCheck(Props.oneOf(['red', 'blue']), 'blue');
169 -
170 - // No warnings should have been logged.
171 - expect(console.warn.mock.calls.length).toBe(0);
199 + it('should not warn when passing an empty array', function() {
200 + typeCheckPass(PropTypes.arrayOf(PropTypes.number), []);
201 });
202
203 it("should be implicitly optional and not warn without values", function() {
175 - typeCheck(Props.oneOf(['red', 'blue']), null);
176 - typeCheck(Props.oneOf(['red', 'blue']), undefined);
177 -
178 - // No warnings should have been logged.
179 - expect(console.warn.mock.calls.length).toBe(0);
204 + typeCheckPass(PropTypes.arrayOf(PropTypes.number), null);
205 + typeCheckPass(PropTypes.arrayOf(PropTypes.number), undefined);
206 });
207
182 - it("should have a weak version that returns true/false", function() {
183 - var checker = Props.oneOf(['red', 'blue']);
184 - expect(typeCheck(checker.weak, null)).toEqual(true);
185 - expect(typeCheck(checker.weak.isRequired, null)).toEqual(false);
186 - expect(typeCheck(checker.isRequired.weak, null)).toEqual(false);
208 + it("should warn for missing required values", function() {
209 + typeCheckFail(
210 + PropTypes.arrayOf(PropTypes.number).isRequired, null, requiredMessage
211 + );
212 + typeCheckFail(
213 + PropTypes.arrayOf(PropTypes.number).isRequired, undefined, requiredMessage
214 + );
215 });
216 });
217
190 -describe('Shape Types', function() {
218 +describe('Component Type', function() {
219 beforeEach(function() {
220 require('mock-modules').dumpCache();
193 - mocks = require('mocks');
194 - warn = console.warn;
195 - console.warn = mocks.getMockFunction();
221 + ReactTestUtils = require('ReactTestUtils');
222 + Component = React.createClass({
223 + propTypes: {
224 + label: PropTypes.component.isRequired
225 + },
226 +
227 + render: function() {
228 + return <div>{this.props.label}</div>;
229 + }
230 + });
231 + spyOn(console, 'warn');
232 });
233
198 - afterEach(function() {
199 - console.warn = warn;
234 + it('should support components', () => {
235 + typeCheckPass(PropTypes.component, <div />);
236 });
237
202 - it("should warn for non objects", function() {
203 - typeCheck(Props.shape({}), 'some string');
204 - typeCheck(Props.shape({}), ['array']);
238 + it('should not support multiple components or scalar values', () => {
239 + var message = 'Invalid prop `testProp` supplied to `testComponent`, ' +
240 + 'expected a React component.';
241 + typeCheckFail(PropTypes.component, [<div />, <div />], message);
242 + typeCheckFail(PropTypes.component, 123, message);
243 + typeCheckFail(PropTypes.component, 'foo', message);
244 + typeCheckFail(PropTypes.component, false, message);
245 + });
246
206 - expect(console.warn.mock.calls.length).toBe(2);
247 + it('should be able to define a single child as label', () => {
248 + var instance = <Component label={<div />} />;
249 + instance = ReactTestUtils.renderIntoDocument(instance);
250
208 - expect(console.warn.mock.calls[0][0]).toBe(
209 - 'Warning: Invalid prop `testProp` of type `string` ' +
210 - 'supplied to `testComponent`, expected `object`.'
211 - );
212 - expect(console.warn.mock.calls[1][0]).toBe(
213 - 'Warning: Invalid prop `testProp` of type `array` ' +
214 - 'supplied to `testComponent`, expected `object`.'
215 - );
251 + expect(console.warn.argsForCall.length).toBe(0);
252 });
253
218 - it("should not warn for empty values", function() {
219 - typeCheck(Props.shape({}), undefined);
220 - typeCheck(Props.shape({}), null);
221 - typeCheck(Props.shape({}), {});
254 + it('should warn when passing no label and isRequired is set', () => {
255 + var instance = <Component />;
256 + instance = ReactTestUtils.renderIntoDocument(instance);
257
223 - // No warnings should have been logged.
224 - expect(console.warn.mock.calls.length).toBe(0);
258 + expect(console.warn.argsForCall.length).toBe(1);
259 });
260
227 - it("should warn for empty required value", function() {
228 - typeCheck(Props.shape({}).isRequired, undefined);
229 - typeCheck(Props.shape({}).isRequired, null);
261 + it("should be implicitly optional and not warn without values", function() {
262 + typeCheckPass(PropTypes.component, null);
263 + typeCheckPass(PropTypes.component, undefined);
264 + });
265
231 - expect(console.warn.mock.calls.length).toBe(2);
266 + it("should warn for missing required values", function() {
267 + typeCheckFail(PropTypes.component.isRequired, null, requiredMessage);
268 + typeCheckFail(PropTypes.component.isRequired, undefined, requiredMessage);
269 + });
270 +});
271
233 - expect(console.warn.mock.calls[0][0]).toBe(
234 - 'Warning: Required prop `testProp` was not specified in ' +
235 - '`testComponent`.'
272 +describe('Instance Types', function() {
273 + it("should warn for invalid instances", function() {
274 + function Person() {}
275 + var name = Person.name || '<<anonymous>>';
276 + typeCheckFail(
277 + PropTypes.instanceOf(Person),
278 + false,
279 + 'Invalid prop `testProp` supplied to `testComponent`, expected ' +
280 + 'instance of `' + name + '`.'
281 );
237 - expect(console.warn.mock.calls[1][0]).toBe(
238 - 'Warning: Required prop `testProp` was not specified in ' +
239 - '`testComponent`.'
282 + typeCheckFail(
283 + PropTypes.instanceOf(Person),
284 + {},
285 + 'Invalid prop `testProp` supplied to `testComponent`, expected ' +
286 + 'instance of `' + name + '`.'
287 + );
288 + typeCheckFail(
289 + PropTypes.instanceOf(Person),
290 + '',
291 + 'Invalid prop `testProp` supplied to `testComponent`, expected ' +
292 + 'instance of `' + name + '`.'
293 );
241 -
242 - // Should not warn
243 - typeCheck(Props.shape({}).isRequired, {});
244 - expect(console.warn.mock.calls.length).toBe(2);
294 });
295
247 - it("should not warn for non specified types", function() {
248 - typeCheck(Props.shape({}), {key: 1});
296 + it("should not warn for valid values", function() {
297 + function Person() {}
298 + function Engineer() {}
299 + Engineer.prototype = new Person();
300
250 - // No warnings should have been logged.
251 - expect(console.warn.mock.calls.length).toBe(0);
301 + typeCheckPass(PropTypes.instanceOf(Person), new Person());
302 + typeCheckPass(PropTypes.instanceOf(Person), new Engineer());
303 });
304
254 - it("should not warn for valid types", function() {
255 - typeCheck(Props.shape({
256 - key: Props.number
257 - }), {key: 1});
258 -
259 - // No warnings should have been logged.
260 - expect(console.warn.mock.calls.length).toBe(0);
305 + it("should be implicitly optional and not warn without values", function() {
306 + typeCheckPass(PropTypes.instanceOf(String), null);
307 + typeCheckPass(PropTypes.instanceOf(String), undefined);
308 });
309
263 - it("should warn for required valid types", function() {
264 - typeCheck(Props.shape({
265 - key: Props.number.isRequired
266 - }), {});
267 -
268 - expect(console.warn.mock.calls.length).toBe(1);
269 -
270 - expect(console.warn.mock.calls[0][0]).toBe(
271 - 'Warning: Required prop `key` was not specified in ' +
272 - '`testComponent`.'
310 + it("should warn for missing required values", function() {
311 + typeCheckFail(
312 + PropTypes.instanceOf(String).isRequired, null, requiredMessage
313 );
274 - });
275 -
276 - it("should warn for invalid key types", function() {
277 - typeCheck(Props.shape({
278 - key: Props.number
279 - }), {key: 'abc'});
280 -
281 - expect(console.warn.mock.calls.length).toBe(1);
282 -
283 - expect(console.warn.mock.calls[0][0]).toBe(
284 - 'Warning: Invalid prop `key` of type `string` supplied to ' +
285 - '`testComponent`, expected `number`.'
314 + typeCheckFail(
315 + PropTypes.instanceOf(String).isRequired, undefined, requiredMessage
316 );
317 });
318 });
319
290 -describe('ArrayOf Type', function() {
320 +describe('React Component Types', function() {
321 beforeEach(function() {
292 - require('mock-modules').dumpCache();
293 - mocks = require('mocks');
294 - warn = console.warn;
295 - console.warn = mocks.getMockFunction();
322 + MyComponent = React.createClass({
323 + render: function() {
324 + return <div />;
325 + }
326 + });
327 });
328
298 - afterEach(function() {
299 - console.warn = warn;
329 + it('should warn for invalid values', function() {
330 + typeCheckFail(
331 + PropTypes.renderable,
332 + false,
333 + 'Invalid prop `testProp` supplied to `testComponent`, expected a ' +
334 + 'renderable prop.'
335 + );
336 + typeCheckFail(
337 + PropTypes.renderable,
338 + function() {},
339 + 'Invalid prop `testProp` supplied to `testComponent`, expected a ' +
340 + 'renderable prop.'
341 + );
342 + typeCheckFail(
343 + PropTypes.renderable,
344 + {key: function() {}},
345 + 'Invalid prop `testProp` supplied to `testComponent`, expected a ' +
346 + 'renderable prop.'
347 + );
348 });
349
302 - it('should support the arrayOf propType', function() {
303 - typeCheck(Props.arrayOf(Props.number), [1, 2, 3]);
304 - typeCheck(Props.arrayOf(Props.string), ['a', 'b', 'c']);
305 - typeCheck(Props.arrayOf(Props.oneOf(['a', 'b'])), ['a', 'b']);
350 + it('should not warn for valid values', function() {
351 + // DOM component
352 + typeCheckPass(PropTypes.renderable, <div />);
353 + // Custom component
354 + typeCheckPass(PropTypes.renderable, <MyComponent />);
355 + // String
356 + typeCheckPass(PropTypes.renderable, 'Some string');
357 + // Empty array
358 + typeCheckPass(PropTypes.renderable, []);
359 + // Empty object
360 + typeCheckPass(PropTypes.renderable, {});
361 + // Array of renderable things
362 + typeCheckPass(PropTypes.renderable, [
363 + 123,
364 + 'Some string',
365 + <div />,
366 + ['Another string', [456], <span />, <MyComponent />],
367 + <MyComponent />
368 + ]);
369
307 - // No warnings should have been logged.
308 - expect(console.warn.mock.calls.length).toBe(0);
370 + // Object of rendereable things
371 + typeCheckPass(PropTypes.renderable, {
372 + k0: 123,
373 + k1: 'Some string',
374 + k2: <div />,
375 + k3: {
376 + k30: <MyComponent />,
377 + k31: {k310: <a />},
378 + k32: 'Another string'
379 + }
380 + });
381 });
382
311 - it('should support arrayOf with complex types', function() {
312 - typeCheck(Props.arrayOf(Props.shape({
313 - a: Props.number.isRequired
314 - })), [{a: 1}, {a: 2}]);
315 -
316 - function Thing() {}
317 - typeCheck(
318 - Props.arrayOf(Props.instanceOf(Thing)),
319 - [new Thing(), new Thing()]
320 - );
321 -
322 - // No warnings should have been logged.
323 - expect(console.warn.mock.calls.length).toBe(0);
383 + it('should not warn for null/undefined if not required', function() {
384 + typeCheckPass(PropTypes.renderable, null);
385 + typeCheckPass(PropTypes.renderable, undefined);
386 });
387
326 - it('should warn with invalid items in the array', function() {
327 - typeCheck(Props.arrayOf(Props.number), [1, 2, 'b']);
328 -
329 - expect(console.warn.mock.calls.length).toBe(1);
330 -
331 - expect(console.warn.mock.calls[0][0]).toBe(
332 - 'Warning: Invalid prop `2` of type `string` supplied to ' +
333 - '`testComponent`, expected `number`.'
388 + it('should warn for missing required values', function() {
389 + typeCheckFail(
390 + PropTypes.renderable.isRequired,
391 + null,
392 + 'Required prop `testProp` was not specified in `testComponent`.'
393 + );
394 + typeCheckFail(
395 + PropTypes.renderable.isRequired,
396 + undefined,
397 + 'Required prop `testProp` was not specified in `testComponent`.'
398 );
399 });
400
337 - it('should warn with invalid complex types', function() {
338 - function Thing() {}
339 - var name = Thing.name || '<<anonymous>>';
401 + it('should accept empty array & object for required props', function() {
402 + typeCheckPass(PropTypes.renderable.isRequired, []);
403 + typeCheckPass(PropTypes.renderable.isRequired, {});
404 + });
405 +});
406
341 - typeCheck(
342 - Props.arrayOf(Props.instanceOf(Thing)),
343 - [new Thing(), 'xyz']
407 +describe('OneOf Types', function() {
408 + it("should warn for invalid strings", function() {
409 + typeCheckFail(
410 + PropTypes.oneOf(['red', 'blue']),
411 + true,
412 + 'Invalid prop `testProp` of value `true` supplied to `testComponent`, ' +
413 + 'expected one of ["red","blue"].'
414 );
345 -
346 - expect(console.warn.mock.calls.length).toBe(1);
347 -
348 - expect(console.warn.mock.calls[0][0]).toBe(
349 - 'Warning: Invalid prop `1` supplied to ' +
350 - '`testComponent`, expected instance of `' + name + '`.'
415 + typeCheckFail(
416 + PropTypes.oneOf(['red', 'blue']),
417 + [],
418 + 'Invalid prop `testProp` of value `` supplied to `testComponent`, ' +
419 + 'expected one of ["red","blue"].'
420 + );
421 + typeCheckFail(
422 + PropTypes.oneOf(['red', 'blue']),
423 + '',
424 + 'Invalid prop `testProp` of value `` supplied to `testComponent`, ' +
425 + 'expected one of ["red","blue"].'
426 + );
427 + typeCheckFail(
428 + PropTypes.oneOf([0, 'false']),
429 + false,
430 + 'Invalid prop `testProp` of value `false` supplied to `testComponent`, ' +
431 + 'expected one of [0,"false"].'
432 );
433 });
434
354 - it('should warn when passed something other than an array', function() {
355 - typeCheck(Props.arrayOf(Props.number), {'0': 'maybe-array', length: 1});
356 - typeCheck(Props.arrayOf(Props.number), 123);
357 - typeCheck(Props.arrayOf(Props.number), 'string');
358 -
359 - expect(console.warn.mock.calls.length).toBe(3);
360 -
361 - var message = 'Warning: Invalid prop `testProp` supplied to ' +
362 - '`testComponent`, expected an array.';
435 + it("should not warn for valid values", function() {
436 + typeCheckPass(PropTypes.oneOf(['red', 'blue']), 'red');
437 + typeCheckPass(PropTypes.oneOf(['red', 'blue']), 'blue');
438 + });
439
364 - expect(console.warn.mock.calls[0][0]).toBe(message);
365 - expect(console.warn.mock.calls[1][0]).toBe(message);
366 - expect(console.warn.mock.calls[2][0]).toBe(message);
440 + it("should be implicitly optional and not warn without values", function() {
441 + typeCheckPass(PropTypes.oneOf(['red', 'blue']), null);
442 + typeCheckPass(PropTypes.oneOf(['red', 'blue']), undefined);
443 });
444
445 + it("should warn for missing required values", function() {
446 + typeCheckFail(
447 + PropTypes.oneOf(['red', 'blue']).isRequired, null, requiredMessage
448 + );
449 + typeCheckFail(
450 + PropTypes.oneOf(['red', 'blue']).isRequired, undefined, requiredMessage
451 + );
452 + });
453 });
454
371 -describe('Instance Types', function() {
372 - beforeEach(function() {
373 - require('mock-modules').dumpCache();
374 - mocks = require('mocks');
375 - warn = console.warn;
376 - console.warn = mocks.getMockFunction();
455 +describe('Union Types', function() {
456 + it('should warn if none of the types are valid', function() {
457 + typeCheckFail(
458 + PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
459 + [],
460 + 'Invalid prop `testProp` supplied to `testComponent`.'
461 + );
462 });
463
379 - afterEach(function() {
380 - console.warn = warn;
464 + it('should not warn if one of the types are valid', function() {
465 + var checker = PropTypes.oneOfType([
466 + PropTypes.string,
467 + PropTypes.number
468 + ]);
469 + typeCheckPass(checker, null);
470 + typeCheckPass(checker, 'foo');
471 + typeCheckPass(checker, 123);
472 });
473
383 - it("should warn for invalid instances", function() {
384 - function Person() {}
385 - var name = Person.name || '<<anonymous>>';
386 - typeCheck(Props.instanceOf(Person), false);
387 - typeCheck(Props.instanceOf(Person), {});
388 - typeCheck(Props.instanceOf(Person), '');
389 -
390 - expect(console.warn.mock.calls.length).toBe(3);
391 -
392 - expect(console.warn.mock.calls[0][0]).toBe(
393 - 'Warning: Invalid prop `testProp` supplied to ' +
394 - '`testComponent`, expected instance of `' + name + '`.'
474 + it("should be implicitly optional and not warn without values", function() {
475 + typeCheckPass(
476 + PropTypes.oneOfType([PropTypes.string, PropTypes.number]), null
477 );
396 -
397 - expect(console.warn.mock.calls[1][0]).toBe(
398 - 'Warning: Invalid prop `testProp` supplied to ' +
399 - '`testComponent`, expected instance of `' + name + '`.'
400 - );
401 -
402 - expect(console.warn.mock.calls[2][0]).toBe(
403 - 'Warning: Invalid prop `testProp` supplied to ' +
404 - '`testComponent`, expected instance of `' + name + '`.'
478 + typeCheckPass(
479 + PropTypes.oneOfType([PropTypes.string, PropTypes.number]), undefined
480 );
481 });
482
408 - it("should not warn for valid values", function() {
409 - function Person() {}
410 - function Engineer() {}
411 - Engineer.prototype = new Person();
412 -
413 - typeCheck(Props.instanceOf(Person), new Person());
414 - typeCheck(Props.instanceOf(Person), new Engineer());
415 -
416 - // No warnings should have been logged.
417 - expect(console.warn.mock.calls.length).toBe(0);
483 + it("should warn for missing required values", function() {
484 + typeCheckFail(
485 + PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
486 + null,
487 + requiredMessage
488 + );
489 + typeCheckFail(
490 + PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
491 + undefined,
492 + requiredMessage
493 + );
494 });
495 });
496
421 -describe('Component Type', function() {
422 - beforeEach(function() {
423 - require('mock-modules').dumpCache();
424 - mocks = require('mocks');
425 - warn = console.warn;
426 - console.warn = mocks.getMockFunction();
497 +describe('Shape Types', function() {
498 + it("should warn for non objects", function() {
499 + typeCheckFail(
500 + PropTypes.shape({}),
501 + 'some string',
502 + 'Invalid prop `testProp` of type `string` supplied to `testComponent`, ' +
503 + 'expected `object`.'
504 + );
505 + typeCheckFail(
506 + PropTypes.shape({}),
507 + ['array'],
508 + 'Invalid prop `testProp` of type `array` supplied to `testComponent`, ' +
509 + 'expected `object`.'
510 + );
511 });
512
429 - afterEach(function() {
430 - console.warn = warn;
513 + it("should not warn for empty values", function() {
514 + typeCheckPass(PropTypes.shape({}), undefined);
515 + typeCheckPass(PropTypes.shape({}), null);
516 + typeCheckPass(PropTypes.shape({}), {});
517 });
518
433 - it('should support components', () => {
434 - typeCheck(Props.component, <div />);
435 -
436 - // No warnings should have been logged.
437 - expect(console.warn.mock.calls.length).toBe(0);
519 + it("should not warn for an empty object", function() {
520 + typeCheckPass(PropTypes.shape({}).isRequired, {});
521 });
522
440 - it('should not support multiple components or scalar values', () => {
441 - var list = [[<div />, <div />], 123, 'foo', false];
442 - list.forEach((value) => typeCheck(Props.component, value));
443 - expect(console.warn.mock.calls.length).toBe(list.length);
523 + it("should not warn for non specified types", function() {
524 + typeCheckPass(PropTypes.shape({}), {key: 1});
525 });
526
446 - var Component = React.createClass({
447 - propTypes: {
448 - label: Props.component.isRequired
449 - },
450 -
451 - render: function() {
452 - return <div>{this.props.label}</div>;
453 - }
527 + it("should not warn for valid types", function() {
528 + typeCheckPass(PropTypes.shape({key: PropTypes.number}), {key: 1});
529 });
530
456 - it('should be able to define a single child as label', () => {
457 - var instance = <Component label={<div />} />;
458 - instance = ReactTestUtils.renderIntoDocument(instance);
531 + it("should warn for required valid types", function() {
532 + typeCheckFail(
533 + PropTypes.shape({key: PropTypes.number.isRequired}),
534 + {},
535 + 'Required prop `key` was not specified in `testComponent`.'
536 + );
537 + });
538
460 - // No warnings should have been logged.
461 - expect(console.warn.mock.calls.length).toBe(0);
539 + it("should warn for invalid key types", function() {
540 + typeCheckFail(PropTypes.shape({key: PropTypes.number}),
541 + {key: 'abc'},
542 + 'Invalid prop `key` of type `string` supplied to `testComponent`, ' +
543 + 'expected `number`.'
544 + );
545 });
546
464 - it('should warn when passing no label and isRequired is set', () => {
465 - var instance = <Component />;
466 - instance = ReactTestUtils.renderIntoDocument(instance);
547 + it("should be implicitly optional and not warn without values", function() {
548 + typeCheckPass(
549 + PropTypes.shape(PropTypes.shape({key: PropTypes.number})), null
550 + );
551 + typeCheckPass(
552 + PropTypes.shape(PropTypes.shape({key: PropTypes.number})), undefined
553 + );
554 + });
555
468 - expect(console.warn.mock.calls.length).toBe(1);
556 + it("should warn for missing required values", function() {
557 + typeCheckFail(
558 + PropTypes.shape({key: PropTypes.number}).isRequired,
559 + null,
560 + requiredMessage
561 + );
562 + typeCheckFail(
563 + PropTypes.shape({key: PropTypes.number}).isRequired,
564 + undefined,
565 + requiredMessage
566 + );
567 });
568 });
569
472 -describe('Union Types', function() {
570 +describe('Custom validator', function() {
571 beforeEach(function() {
572 require('mock-modules').dumpCache();
475 - mocks = require('mocks');
476 - warn = console.warn;
477 - console.warn = mocks.getMockFunction();
478 - });
479 -
480 - afterEach(function() {
481 - console.warn = warn;
573 + ReactTestUtils = require('ReactTestUtils');
574 + spyOn(console, 'warn');
575 });
576
484 - it('should warn if none of the types are valid', function() {
485 - var checker = Props.oneOfType([
486 - Props.string,
487 - Props.number
488 - ]);
489 - typeCheck(checker, []);
490 - checker = Props.oneOfType([
491 - Props.string.isRequired,
492 - Props.number.isRequired
493 - ]);
494 - typeCheck(checker, null);
577 + it('should have been called with the right params', function() {
578 + var spy = jasmine.createSpy();
579 + var Component = React.createClass({
580 + propTypes: {num: spy},
581
496 - expect(console.warn.mock.calls.length).toBe(2);
582 + render: function() {
583 + return <div />;
584 + }
585 + });
586
498 - expect(console.warn.mock.calls[0][0]).toBe(
499 - 'Warning: Invalid prop `testProp` ' +
500 - 'supplied to `testComponent`.'
501 - );
587 + var instance = <Component num={5} />;
588 + instance = ReactTestUtils.renderIntoDocument(instance);
589
503 - expect(console.warn.mock.calls[1][0]).toBe(
504 - 'Warning: Invalid prop `testProp` ' +
505 - 'supplied to `testComponent`.'
506 - );
590 + expect(spy.argsForCall.length).toBe(1);
591 + expect(spy.argsForCall[0][1]).toBe('num');
592 + expect(spy.argsForCall[0][2]).toBe('Component');
593 });
594
509 - it('should not warn if one of the types are valid', function() {
510 - var checker = Props.oneOfType([
511 - Props.string,
512 - Props.number
513 - ]);
514 - typeCheck(checker, null);
515 - expect(console.warn.mock.calls.length).toBe(0);
516 - typeCheck(checker, 'foo');
517 - expect(console.warn.mock.calls.length).toBe(0);
518 - typeCheck(checker, 123);
519 - expect(console.warn.mock.calls.length).toBe(0);
520 -
521 - checker = Props.oneOfType([
522 - Props.string,
523 - Props.number.isRequired
524 - ]);
525 - typeCheck(checker, null);
526 - expect(console.warn.mock.calls.length).toBe(0);
527 - typeCheck(checker, 'foo');
528 - expect(console.warn.mock.calls.length).toBe(0);
529 - typeCheck(checker, 123);
530 - expect(console.warn.mock.calls.length).toBe(0);
531 - });
595 + it('should have been called even if the prop is not present', function() {
596 + var spy = jasmine.createSpy();
597 + var Component = React.createClass({
598 + propTypes: {num: spy},
599
533 - describe('React Component Types', function() {
534 - beforeEach(function() {
535 - require('mock-modules').dumpCache();
600 + render: function() {
601 + return <div />;
602 + }
603 });
604
538 - var myFunc = function() {};
539 -
540 - it('should warn for invalid values', function() {
541 - typeCheck(Props.renderable, false);
542 - typeCheck(Props.renderable, myFunc);
543 - typeCheck(Props.renderable, {key: myFunc});
544 -
545 - expect(console.warn.mock.calls.length).toBe(3);
546 -
547 - expect(console.warn.mock.calls[0][0]).toBe(
548 - 'Warning: Invalid prop `testProp` supplied to ' +
549 - '`testComponent`, expected a renderable prop.'
550 - );
551 -
552 - expect(console.warn.mock.calls[1][0]).toBe(
553 - 'Warning: Invalid prop `testProp` supplied to ' +
554 - '`testComponent`, expected a renderable prop.'
555 - );
605 + var instance = <Component bla={5} />;
606 + instance = ReactTestUtils.renderIntoDocument(instance);
607
557 - expect(console.warn.mock.calls[2][0]).toBe(
558 - 'Warning: Invalid prop `testProp` supplied to ' +
559 - '`testComponent`, expected a renderable prop.'
560 - );
561 - });
608 + expect(spy.argsForCall.length).toBe(1);
609 + });
610
563 - it('should not warn for valid values', function() {
564 - // DOM component
565 - typeCheck(Props.renderable, <div />);
566 - expect(console.warn.mock.calls.length).toBe(0);
567 - // Custom component
568 - typeCheck(Props.renderable, <MyComponent />);
569 - expect(console.warn.mock.calls.length).toBe(0);
570 - // String
571 - typeCheck(Props.renderable, 'Some string');
572 - expect(console.warn.mock.calls.length).toBe(0);
573 - // Empty array
574 - typeCheck(Props.renderable, []);
575 - expect(console.warn.mock.calls.length).toBe(0);
576 - // Empty object
577 - typeCheck(Props.renderable, {});
578 - expect(console.warn.mock.calls.length).toBe(0);
579 - // Array of renderable things
580 -
581 - typeCheck(Props.renderable, [
582 - 123,
583 - 'Some string',
584 - <div />,
585 - ['Another string', [456], <span />, <MyComponent />],
586 - <MyComponent />
587 - ]);
588 - expect(console.warn.mock.calls.length).toBe(0);
589 -
590 - // Object of rendereable things
591 - typeCheck(Props.renderable, {
592 - k0: 123,
593 - k1: 'Some string',
594 - k2: <div />,
595 - k3: {
596 - k30: <MyComponent />,
597 - k31: {k310: <a />},
598 - k32: 'Another string'
611 + it('should have received the validator\'s return value', function() {
612 + var spy = jasmine.createSpy().andCallFake(
613 + function(props, propName, componentName) {
614 + if (props[propName] !== 5) {
615 + return new Error('num must be 5!');
616 }
600 - });
601 - expect(console.warn.mock.calls.length).toBe(0);
602 - });
603 -
604 - it('should not warn for null/undefined if not required', function() {
605 - typeCheck(Props.renderable, null);
606 - typeCheck(Props.renderable, undefined);
607 -
608 - // No warnings should have been logged.
609 - expect(console.warn.mock.calls.length).toBe(0);
610 - });
611 -
612 - it('should warn for missing required values', function() {
613 - typeCheck(Props.renderable.isRequired, null);
614 - typeCheck(Props.renderable.isRequired, undefined);
615 -
616 - expect(console.warn.mock.calls.length).toBe(2);
617 + }
618 + );
619 + var Component = React.createClass({
620 + propTypes: {num: spy},
621
618 - expect(console.warn.mock.calls[0][0]).toBe(
619 - 'Warning: Required prop `testProp` was not specified in ' +
620 - '`testComponent`.'
621 - );
622 - expect(console.warn.mock.calls[1][0]).toBe(
623 - 'Warning: Required prop `testProp` was not specified in ' +
624 - '`testComponent`.'
625 - );
622 + render: function() {
623 + return <div />;
624 + }
625 });
626
628 - it('should accept empty array & object for required props', function() {
629 - typeCheck(Props.renderable.isRequired, []);
630 - typeCheck(Props.renderable.isRequired, {});
631 -
632 - // No warnings should have been logged.
633 - expect(console.warn.mock.calls.length).toBe(0);
634 - });
627 + var instance = <Component num={6} />;
628 + instance = ReactTestUtils.renderIntoDocument(instance);
629 + expect(console.warn.argsForCall.length).toBe(1);
630 + expect(console.warn.argsForCall[0][0]).toBe('Warning: num must be 5!');
631 });
632
637 - describe('Any type', function() {
638 - it('should should accept any value', function() {
639 - typeCheck(Props.any, 1);
640 - expect(console.warn.mock.calls.length).toBe(0);
641 - typeCheck(Props.any, 'str');
642 - expect(console.warn.mock.calls.length).toBe(0);
643 - typeCheck(Props.any.isRequired, 1);
644 - expect(console.warn.mock.calls.length).toBe(0);
645 - typeCheck(Props.any.isRequired, 'str');
646 - expect(console.warn.mock.calls.length).toBe(0);
647 -
648 - typeCheck(Props.any, null);
649 - expect(console.warn.mock.calls.length).toBe(0);
650 - typeCheck(Props.any, undefined);
651 - expect(console.warn.mock.calls.length).toBe(0);
652 -
653 - typeCheck(Props.any.isRequired, null);
654 - typeCheck(Props.any.isRequired, undefined);
655 -
656 - expect(console.warn.mock.calls.length).toBe(2);
657 - expect(console.warn.mock.calls[0][0]).toBe(
658 - 'Warning: Required prop `testProp` was not specified in ' +
659 - '`testComponent`.'
633 + it('should not warn if the validator returned anything else than an error',
634 + function() {
635 + var spy = jasmine.createSpy().andCallFake(
636 + function(props, propName, componentName) {
637 + return 'This message will never reach anyone';
638 + }
639 );
640 + var Component = React.createClass({
641 + propTypes: {num: spy},
642
662 - expect(console.warn.mock.calls[1][0]).toBe(
663 - 'Warning: Required prop `testProp` was not specified in ' +
664 - '`testComponent`.'
665 - );
666 - });
643 + render: function() {
644 + return <div />;
645 + }
646 + });
647
668 - it('should have a weak version that returns true/false', function() {
669 - expect(typeCheck(Props.any.weak, null)).toEqual(true);
670 - expect(typeCheck(Props.any.weak.isRequired, null)).toEqual(false);
671 - expect(typeCheck(Props.any.isRequired.weak, null)).toEqual(false);
672 - });
673 - });
648 + var instance = <Component num={5} />;
649 + instance = ReactTestUtils.renderIntoDocument(instance);
650 + expect(console.warn.argsForCall.length).toBe(0);
651 + }
652 + );
653 });