From bbb52418d9e0c1e004967f46532021a66d583e65 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Tue, 11 Apr 2017 16:41:39 +0100 Subject: [PATCH 01/12] Add a test for production bundle --- __tests__/PropTypesProduction-test.js | 239 ++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 __tests__/PropTypesProduction-test.js diff --git a/__tests__/PropTypesProduction-test.js b/__tests__/PropTypesProduction-test.js new file mode 100644 index 0000000..3d465c7 --- /dev/null +++ b/__tests__/PropTypesProduction-test.js @@ -0,0 +1,239 @@ +/** + * Copyright 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @emails react-core + */ + +'use strict'; + +describe('ReactPropTypesProduction', function() { + var React; + var PropTypes; + var checkPropTypes; + var oldProcess; + + beforeEach(function() { + process.env.NODE_ENV = 'production'; + React = require('react'); + PropTypes = require('../index'); + }); + + afterEach(function() { + delete process.env.NODE_ENV; + }); + + function resetWarningCache() { + jest.resetModules(); + checkPropTypes = require('../checkPropTypes'); + } + + function getPropTypeWarningMessage(propTypes, object, componentName) { + if (!console.error.calls) { + spyOn(console, 'error'); + } else { + console.error.calls.reset(); + } + resetWarningCache(); + checkPropTypes(propTypes, object, 'prop', 'testComponent'); + const callCount = console.error.calls.count(); + if (callCount > 1) { + throw new Error('Too many warnings.'); + } + const message = console.error.calls.argsFor(0)[0] || null; + console.error.calls.reset(); + + return message; + } + + function expectThrowsInProduction(declaration, value) { + resetWarningCache(); + var props = {testProp: value}; + expect(() => { + declaration(props, 'testProp', 'testComponent', 'prop'); + }).toThrowError('React.PropTypes type checking code is stripped in production.'); + } + + function typeCheckPass(declaration, value) { + const propTypes = { + testProp: declaration, + }; + const props = { + testProp: value, + }; + const message = getPropTypeWarningMessage(propTypes, props, 'testComponent'); + expect(message).toBe(null); + } + + describe('Primitive Types', function() { + it('should be a no-op', function() { + expectThrowsInProduction(PropTypes.array, /please/); + expectThrowsInProduction(PropTypes.array.isRequired, /please/); + expectThrowsInProduction(PropTypes.array.isRequired, null); + expectThrowsInProduction(PropTypes.array.isRequired, undefined); + expectThrowsInProduction(PropTypes.bool, []); + expectThrowsInProduction(PropTypes.bool.isRequired, []); + expectThrowsInProduction(PropTypes.bool.isRequired, null); + expectThrowsInProduction(PropTypes.bool.isRequired, undefined); + expectThrowsInProduction(PropTypes.func, false); + expectThrowsInProduction(PropTypes.func.isRequired, false); + expectThrowsInProduction(PropTypes.func.isRequired, null); + expectThrowsInProduction(PropTypes.func.isRequired, undefined); + expectThrowsInProduction(PropTypes.number, function() {}); + expectThrowsInProduction(PropTypes.number.isRequired, function() {}); + expectThrowsInProduction(PropTypes.number.isRequired, null); + expectThrowsInProduction(PropTypes.number.isRequired, undefined); + expectThrowsInProduction(PropTypes.string, 0); + expectThrowsInProduction(PropTypes.string.isRequired, 0); + expectThrowsInProduction(PropTypes.string.isRequired, null); + expectThrowsInProduction(PropTypes.string.isRequired, undefined); + expectThrowsInProduction(PropTypes.symbol, 0); + expectThrowsInProduction(PropTypes.symbol.isRequired, 0); + expectThrowsInProduction(PropTypes.symbol.isRequired, null); + expectThrowsInProduction(PropTypes.symbol.isRequired, undefined); + expectThrowsInProduction(PropTypes.object, ''); + expectThrowsInProduction(PropTypes.object.isRequired, ''); + expectThrowsInProduction(PropTypes.object.isRequired, null); + expectThrowsInProduction(PropTypes.object.isRequired, undefined); + }); + }); + + describe('Any Type', function() { + it('should be a no-op', function() { + expectThrowsInProduction(PropTypes.any, null); + expectThrowsInProduction(PropTypes.any.isRequired, null); + expectThrowsInProduction(PropTypes.any.isRequired, undefined); + }); + }); + + describe('ArrayOf Type', function() { + it('should be a no-op', function() { + expectThrowsInProduction(PropTypes.arrayOf({foo: PropTypes.string}), { + foo: 'bar', + }); + expectThrowsInProduction(PropTypes.arrayOf(PropTypes.number), [ + 1, + 2, + 'b', + ]); + expectThrowsInProduction(PropTypes.arrayOf(PropTypes.number), { + '0': 'maybe-array', + length: 1, + }); + expectThrowsInProduction( + PropTypes.arrayOf(PropTypes.number).isRequired, + null, + ); + expectThrowsInProduction( + PropTypes.arrayOf(PropTypes.number).isRequired, + undefined, + ); + }); + }); + + describe('Component Type', function() { + it('should be a no-op', function() { + expectThrowsInProduction(PropTypes.element, [
,
]); + expectThrowsInProduction(PropTypes.element, 123); + expectThrowsInProduction(PropTypes.element, 'foo'); + expectThrowsInProduction(PropTypes.element, false); + expectThrowsInProduction(PropTypes.element.isRequired, null); + expectThrowsInProduction(PropTypes.element.isRequired, undefined); + }); + }); + + describe('Instance Types', function() { + it('should be a no-op', function() { + expectThrowsInProduction(PropTypes.instanceOf(Date), {}); + expectThrowsInProduction(PropTypes.instanceOf(Date).isRequired, {}); + }); + }); + + describe('React Component Types', function() { + it('should be a no-op', function() { + expectThrowsInProduction(PropTypes.node, {}); + expectThrowsInProduction(PropTypes.node.isRequired, null); + expectThrowsInProduction(PropTypes.node.isRequired, undefined); + }); + }); + + describe('ObjectOf Type', function() { + it('should be a no-op', function() { + expectThrowsInProduction(PropTypes.objectOf({foo: PropTypes.string}), { + foo: 'bar', + }); + expectThrowsInProduction(PropTypes.objectOf(PropTypes.number), { + a: 1, + b: 2, + c: 'b', + }); + expectThrowsInProduction(PropTypes.objectOf(PropTypes.number), [1, 2]); + expectThrowsInProduction(PropTypes.objectOf(PropTypes.number), null); + expectThrowsInProduction(PropTypes.objectOf(PropTypes.number), undefined); + }); + }); + + describe('OneOf Types', function() { + it('should be a no-op', function() { + expectThrowsInProduction(PropTypes.oneOf('red', 'blue'), 'red'); + expectThrowsInProduction(PropTypes.oneOf(['red', 'blue']), true); + expectThrowsInProduction(PropTypes.oneOf(['red', 'blue']), null); + expectThrowsInProduction(PropTypes.oneOf(['red', 'blue']), undefined); + }); + }); + + describe('Union Types', function() { + it('should be a no-op', function() { + expectThrowsInProduction( + PropTypes.oneOfType(PropTypes.string, PropTypes.number), + 'red', + ); + expectThrowsInProduction( + PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + [], + ); + expectThrowsInProduction( + PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + null, + ); + expectThrowsInProduction( + PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + undefined, + ); + }); + }); + + describe('Shape Types', function() { + it('should be a no-op', function() { + expectThrowsInProduction(PropTypes.shape({}), 'some string'); + expectThrowsInProduction( + PropTypes.shape({key: PropTypes.number}).isRequired, + null, + ); + expectThrowsInProduction( + PropTypes.shape({key: PropTypes.number}).isRequired, + undefined, + ); + }); + }); + + describe('checkPropTypes', function() { + beforeEach(function() { + jest.resetModules(); + }); + + it('does not call validators', function() { + spyOn(console, 'error'); + + var spy = jest.fn(); + typeCheckPass(PropTypes.string, 42); + typeCheckPass(PropTypes.bool, 'whatever'); + typeCheckPass(spy, 'no way'); + expect(spy).not.toBeCalled(); + }); + }); +}); From 82ccca005a28ad5e8cd5da30e16087ee90269e83 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Tue, 11 Apr 2017 17:27:11 +0100 Subject: [PATCH 02/12] Add backwards compatibility fix for React 15.5 --- ...s-test.js => PropTypesDevelopment-test.js} | 23 +- __tests__/PropTypesProductionReact15-test.js | 952 ++++++++++++++++++ ... => PropTypesProductionStandalone-test.js} | 23 +- checkPropTypes.js | 16 +- factory.js | 478 +-------- factoryWithThrowingShims.js | 49 + factoryWithTypeCheckers.js | 453 +++++++++ index.js | 25 +- package.json | 2 + 9 files changed, 1504 insertions(+), 517 deletions(-) rename __tests__/{PropTypes-test.js => PropTypesDevelopment-test.js} (98%) create mode 100644 __tests__/PropTypesProductionReact15-test.js rename __tests__/{PropTypesProduction-test.js => PropTypesProductionStandalone-test.js} (96%) create mode 100644 factoryWithThrowingShims.js create mode 100644 factoryWithTypeCheckers.js diff --git a/__tests__/PropTypes-test.js b/__tests__/PropTypesDevelopment-test.js similarity index 98% rename from __tests__/PropTypes-test.js rename to __tests__/PropTypesDevelopment-test.js index 95431ac..b8e04b4 100644 --- a/__tests__/PropTypes-test.js +++ b/__tests__/PropTypesDevelopment-test.js @@ -13,11 +13,12 @@ var React; var PropTypes; -var checkPropTypes; function resetWarningCache() { jest.resetModules(); - checkPropTypes = require('../checkPropTypes'); + + React = require('react'); + PropTypes = require('../index'); } function getPropTypeWarningMessage(propTypes, object, componentName) { @@ -27,7 +28,8 @@ function getPropTypeWarningMessage(propTypes, object, componentName) { console.error.calls.reset(); } resetWarningCache(); - checkPropTypes(propTypes, object, 'prop', 'testComponent'); + + PropTypes.checkPropTypes(propTypes, object, 'prop', 'testComponent'); const callCount = console.error.calls.count(); if (callCount > 1) { throw new Error('Too many warnings.'); @@ -103,11 +105,8 @@ function expectWarningInDevelopment(declaration, value) { console.error.calls.reset(); } -describe('ReactPropTypes', () => { +describe('PropTypesDevelopment', () => { beforeEach(() => { - React = require('react'); - PropTypes = require('../index'); - checkPropTypes = PropTypes.checkPropTypes; resetWarningCache(); }); @@ -120,7 +119,7 @@ describe('ReactPropTypes', () => { }, }; const props = {foo: 'foo'}; - const returnValue = checkPropTypes( + const returnValue = PropTypes.checkPropTypes( propTypes, props, 'prop', @@ -139,7 +138,7 @@ describe('ReactPropTypes', () => { }, }; const props = {foo: 'foo'}; - const returnValue = checkPropTypes( + const returnValue = PropTypes.checkPropTypes( propTypes, props, 'prop', @@ -839,9 +838,9 @@ describe('ReactPropTypes', () => { it('should warn if called manually in development', () => { spyOn(console, 'error'); - expect(PropTypes.oneOf(['red', 'blue']), true); - expect(PropTypes.oneOf(['red', 'blue']), null); - expect(PropTypes.oneOf(['red', 'blue']), undefined); + expectWarningInDevelopment(PropTypes.oneOf(['red', 'blue']), true); + expectWarningInDevelopment(PropTypes.oneOf(['red', 'blue']), null); + expectWarningInDevelopment(PropTypes.oneOf(['red', 'blue']), undefined); }); }); diff --git a/__tests__/PropTypesProductionReact15-test.js b/__tests__/PropTypesProductionReact15-test.js new file mode 100644 index 0000000..1d16a31 --- /dev/null +++ b/__tests__/PropTypesProductionReact15-test.js @@ -0,0 +1,952 @@ +/** + * Copyright 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @emails react-core + */ + +'use strict'; + +var React; +var PropTypes; + +function resetWarningCache() { + jest.resetModules(); + + // Set production mode throughout this test. + process.env.NODE_ENV = 'production'; + React = require('react'); + // We are testing that when imported in the same way React 15 imports `prop-types`, + // it just suppresses warnings but doesn't actually throw when calling validators. + PropTypes = require('../factory')(React.isValidElement); +} + +function expectNoop(declaration, value) { + if (!console.error.calls) { + spyOn(console, 'error'); + } else { + console.error.calls.reset(); + } + + var props = {testProp: value}; + var propName = 'testProp' + Math.random().toString(); + var componentName = 'testComponent' + Math.random().toString(); + // Try calling it manually + for (var i = 0; i < 3; i++) { + declaration(props, propName, componentName, 'prop'); + } + // Try calling it via checkPropTypes + const propTypes = { + testProp: declaration, + }; + PropTypes.checkPropTypes(propTypes, props, 'prop', 'testComponent'); + + // They should all be no-ops + expect(console.error.calls.count()).toBe(0); + console.error.calls.reset(); +} + +describe('PropTypesProductionReact15', () => { + beforeEach(() => { + resetWarningCache(); + }); + + describe('Primitive Types', () => { + it('should warn for invalid strings', () => { + expectNoop( + PropTypes.string, + [], + 'Invalid prop `testProp` of type `array` supplied to ' + + '`testComponent`, expected `string`.', + ); + expectNoop( + PropTypes.string, + false, + 'Invalid prop `testProp` of type `boolean` supplied to ' + + '`testComponent`, expected `string`.', + ); + expectNoop( + PropTypes.string, + 0, + 'Invalid prop `testProp` of type `number` supplied to ' + + '`testComponent`, expected `string`.', + ); + expectNoop( + PropTypes.string, + {}, + 'Invalid prop `testProp` of type `object` supplied to ' + + '`testComponent`, expected `string`.', + ); + expectNoop( + PropTypes.string, + Symbol(), + 'Invalid prop `testProp` of type `symbol` supplied to ' + + '`testComponent`, expected `string`.', + ); + }); + + it('should fail date and regexp correctly', () => { + expectNoop( + PropTypes.string, + new Date(), + 'Invalid prop `testProp` of type `date` supplied to ' + + '`testComponent`, expected `string`.', + ); + expectNoop( + PropTypes.string, + /please/, + 'Invalid prop `testProp` of type `regexp` supplied to ' + + '`testComponent`, expected `string`.', + ); + }); + + it('should not warn for valid values', () => { + expectNoop(PropTypes.array, []); + expectNoop(PropTypes.bool, false); + expectNoop(PropTypes.func, function() {}); + expectNoop(PropTypes.number, 0); + expectNoop(PropTypes.string, ''); + expectNoop(PropTypes.object, {}); + expectNoop(PropTypes.object, new Date()); + expectNoop(PropTypes.object, /please/); + expectNoop(PropTypes.symbol, Symbol()); + }); + + it('should be implicitly optional and not warn without values', () => { + expectNoop(PropTypes.string, null); + expectNoop(PropTypes.string, undefined); + }); + + it('should warn for missing required values', () => { + expectNoop(PropTypes.string.isRequired); + }); + + it('should warn if called manually in development', () => { + spyOn(console, 'error'); + expectNoop(PropTypes.array, /please/); + expectNoop(PropTypes.array, []); + expectNoop(PropTypes.array.isRequired, /please/); + expectNoop(PropTypes.array.isRequired, []); + expectNoop(PropTypes.array.isRequired, null); + expectNoop(PropTypes.array.isRequired, undefined); + expectNoop(PropTypes.bool, []); + expectNoop(PropTypes.bool, true); + expectNoop(PropTypes.bool.isRequired, []); + expectNoop(PropTypes.bool.isRequired, true); + expectNoop(PropTypes.bool.isRequired, null); + expectNoop(PropTypes.bool.isRequired, undefined); + expectNoop(PropTypes.func, false); + expectNoop(PropTypes.func, function() {}); + expectNoop(PropTypes.func.isRequired, false); + expectNoop(PropTypes.func.isRequired, function() {}); + expectNoop(PropTypes.func.isRequired, null); + expectNoop(PropTypes.func.isRequired, undefined); + expectNoop(PropTypes.number, function() {}); + expectNoop(PropTypes.number, 42); + expectNoop(PropTypes.number.isRequired, function() {}); + expectNoop(PropTypes.number.isRequired, 42); + expectNoop(PropTypes.number.isRequired, null); + expectNoop(PropTypes.number.isRequired, undefined); + expectNoop(PropTypes.string, 0); + expectNoop(PropTypes.string, 'foo'); + expectNoop(PropTypes.string.isRequired, 0); + expectNoop(PropTypes.string.isRequired, 'foo'); + expectNoop(PropTypes.string.isRequired, null); + expectNoop(PropTypes.string.isRequired, undefined); + expectNoop(PropTypes.symbol, 0); + expectNoop(PropTypes.symbol, Symbol('Foo')); + expectNoop(PropTypes.symbol.isRequired, 0); + expectNoop(PropTypes.symbol.isRequired, Symbol('Foo')); + expectNoop(PropTypes.symbol.isRequired, null); + expectNoop(PropTypes.symbol.isRequired, undefined); + expectNoop(PropTypes.object, ''); + expectNoop(PropTypes.object, {foo: 'bar'}); + expectNoop(PropTypes.object.isRequired, ''); + expectNoop(PropTypes.object.isRequired, {foo: 'bar'}); + expectNoop(PropTypes.object.isRequired, null); + expectNoop(PropTypes.object.isRequired, undefined); + }); + }); + + describe('Any type', () => { + it('should should accept any value', () => { + expectNoop(PropTypes.any, 0); + expectNoop(PropTypes.any, 'str'); + expectNoop(PropTypes.any, []); + expectNoop(PropTypes.any, Symbol()); + }); + + it('should be implicitly optional and not warn without values', () => { + expectNoop(PropTypes.any, null); + expectNoop(PropTypes.any, undefined); + }); + + it('should warn for missing required values', () => { + expectNoop(PropTypes.any.isRequired); + }); + + it('should warn if called manually in development', () => { + spyOn(console, 'error'); + expectNoop(PropTypes.any, null); + expectNoop(PropTypes.any.isRequired, null); + expectNoop(PropTypes.any.isRequired, undefined); + }); + }); + + describe('ArrayOf Type', () => { + it('should fail for invalid argument', () => { + expectNoop( + PropTypes.arrayOf({foo: PropTypes.string}), + {foo: 'bar'}, + 'Property `testProp` of component `testComponent` has invalid PropType notation inside arrayOf.', + ); + }); + + it('should support the arrayOf propTypes', () => { + expectNoop(PropTypes.arrayOf(PropTypes.number), [1, 2, 3]); + expectNoop(PropTypes.arrayOf(PropTypes.string), ['a', 'b', 'c']); + expectNoop(PropTypes.arrayOf(PropTypes.oneOf(['a', 'b'])), ['a', 'b']); + expectNoop(PropTypes.arrayOf(PropTypes.symbol), [Symbol(), Symbol()]); + }); + + it('should support arrayOf with complex types', () => { + expectNoop( + PropTypes.arrayOf(PropTypes.shape({a: PropTypes.number.isRequired})), + [{a: 1}, {a: 2}], + ); + + function Thing() {} + expectNoop(PropTypes.arrayOf(PropTypes.instanceOf(Thing)), [ + new Thing(), + new Thing(), + ]); + }); + + it('should warn with invalid items in the array', () => { + expectNoop( + PropTypes.arrayOf(PropTypes.number), + [1, 2, 'b'], + 'Invalid prop `testProp[2]` of type `string` supplied to ' + + '`testComponent`, expected `number`.', + ); + }); + + it('should warn with invalid complex types', () => { + function Thing() {} + var name = Thing.name || '<>'; + + expectNoop( + PropTypes.arrayOf(PropTypes.instanceOf(Thing)), + [new Thing(), 'xyz'], + 'Invalid prop `testProp[1]` of type `String` supplied to ' + + '`testComponent`, expected instance of `' + + name + + '`.', + ); + }); + + it('should warn when passed something other than an array', () => { + expectNoop( + PropTypes.arrayOf(PropTypes.number), + {'0': 'maybe-array', length: 1}, + 'Invalid prop `testProp` of type `object` supplied to ' + + '`testComponent`, expected an array.', + ); + expectNoop( + PropTypes.arrayOf(PropTypes.number), + 123, + 'Invalid prop `testProp` of type `number` supplied to ' + + '`testComponent`, expected an array.', + ); + expectNoop( + PropTypes.arrayOf(PropTypes.number), + 'string', + 'Invalid prop `testProp` of type `string` supplied to ' + + '`testComponent`, expected an array.', + ); + }); + + it('should not warn when passing an empty array', () => { + expectNoop(PropTypes.arrayOf(PropTypes.number), []); + }); + + it('should be implicitly optional and not warn without values', () => { + expectNoop(PropTypes.arrayOf(PropTypes.number), null); + expectNoop(PropTypes.arrayOf(PropTypes.number), undefined); + }); + + it('should warn for missing required values', () => { + expectNoop( + PropTypes.arrayOf(PropTypes.number).isRequired, + ); + }); + + it('should warn if called manually in development', () => { + spyOn(console, 'error'); + expectNoop(PropTypes.arrayOf({foo: PropTypes.string}), { + foo: 'bar', + }); + expectNoop(PropTypes.arrayOf(PropTypes.number), [ + 1, + 2, + 'b', + ]); + expectNoop(PropTypes.arrayOf(PropTypes.number), { + '0': 'maybe-array', + length: 1, + }); + expectNoop( + PropTypes.arrayOf(PropTypes.number).isRequired, + null, + ); + expectNoop( + PropTypes.arrayOf(PropTypes.number).isRequired, + undefined, + ); + }); + }); + + describe('Component Type', () => { + + it('should support components', () => { + expectNoop(PropTypes.element,
); + }); + + it('should not support multiple components or scalar values', () => { + expectNoop( + PropTypes.element, + [
,
], + 'Invalid prop `testProp` of type `array` supplied to `testComponent`, ' + + 'expected a single ReactElement.', + ); + expectNoop( + PropTypes.element, + 123, + 'Invalid prop `testProp` of type `number` supplied to `testComponent`, ' + + 'expected a single ReactElement.', + ); + expectNoop( + PropTypes.element, + 'foo', + 'Invalid prop `testProp` of type `string` supplied to `testComponent`, ' + + 'expected a single ReactElement.', + ); + expectNoop( + PropTypes.element, + false, + 'Invalid prop `testProp` of type `boolean` supplied to `testComponent`, ' + + 'expected a single ReactElement.', + ); + }); + + it('should be implicitly optional and not warn without values', () => { + expectNoop(PropTypes.element, null); + expectNoop(PropTypes.element, undefined); + }); + + it('should warn for missing required values', () => { + expectNoop(PropTypes.element.isRequired); + }); + + it('should warn if called manually in development', () => { + spyOn(console, 'error'); + expectNoop(PropTypes.element, [
,
]); + expectNoop(PropTypes.element,
); + expectNoop(PropTypes.element, 123); + expectNoop(PropTypes.element, 'foo'); + expectNoop(PropTypes.element, false); + expectNoop(PropTypes.element.isRequired, null); + expectNoop(PropTypes.element.isRequired, undefined); + }); + }); + + describe('Instance Types', () => { + it('should warn for invalid instances', () => { + function Person() {} + function Cat() {} + var personName = Person.name || '<>'; + var dateName = Date.name || '<>'; + var regExpName = RegExp.name || '<>'; + + expectNoop( + PropTypes.instanceOf(Person), + false, + 'Invalid prop `testProp` of type `Boolean` supplied to ' + + '`testComponent`, expected instance of `' + + personName + + '`.', + ); + expectNoop( + PropTypes.instanceOf(Person), + {}, + 'Invalid prop `testProp` of type `Object` supplied to ' + + '`testComponent`, expected instance of `' + + personName + + '`.', + ); + expectNoop( + PropTypes.instanceOf(Person), + '', + 'Invalid prop `testProp` of type `String` supplied to ' + + '`testComponent`, expected instance of `' + + personName + + '`.', + ); + expectNoop( + PropTypes.instanceOf(Date), + {}, + 'Invalid prop `testProp` of type `Object` supplied to ' + + '`testComponent`, expected instance of `' + + dateName + + '`.', + ); + expectNoop( + PropTypes.instanceOf(RegExp), + {}, + 'Invalid prop `testProp` of type `Object` supplied to ' + + '`testComponent`, expected instance of `' + + regExpName + + '`.', + ); + expectNoop( + PropTypes.instanceOf(Person), + new Cat(), + 'Invalid prop `testProp` of type `Cat` supplied to ' + + '`testComponent`, expected instance of `' + + personName + + '`.', + ); + expectNoop( + PropTypes.instanceOf(Person), + Object.create(null), + 'Invalid prop `testProp` of type `<>` supplied to ' + + '`testComponent`, expected instance of `' + + personName + + '`.', + ); + }); + + it('should not warn for valid values', () => { + function Person() {} + function Engineer() {} + Engineer.prototype = new Person(); + + expectNoop(PropTypes.instanceOf(Person), new Person()); + expectNoop(PropTypes.instanceOf(Person), new Engineer()); + + expectNoop(PropTypes.instanceOf(Date), new Date()); + expectNoop(PropTypes.instanceOf(RegExp), /please/); + }); + + it('should be implicitly optional and not warn without values', () => { + expectNoop(PropTypes.instanceOf(String), null); + expectNoop(PropTypes.instanceOf(String), undefined); + }); + + it('should warn for missing required values', () => { + expectNoop(PropTypes.instanceOf(String).isRequired); + }); + + it('should warn if called manually in development', () => { + spyOn(console, 'error'); + expectNoop(PropTypes.instanceOf(Date), {}); + expectNoop(PropTypes.instanceOf(Date), new Date()); + expectNoop(PropTypes.instanceOf(Date).isRequired, {}); + expectNoop( + PropTypes.instanceOf(Date).isRequired, + new Date(), + ); + }); + }); + + describe('React Component Types', () => { + it('should warn for invalid values', () => { + var failMessage = 'Invalid prop `testProp` supplied to ' + + '`testComponent`, expected a ReactNode.'; + expectNoop(PropTypes.node, true, failMessage); + expectNoop(PropTypes.node, function() {}, failMessage); + expectNoop(PropTypes.node, {key: function() {}}, failMessage); + expectNoop(PropTypes.node, {key:
}, failMessage); + }); + + it('should not warn for valid values', () => { + function MyComponent() {} + MyComponent.prototype.render = function() { + return
; + }; + expectNoop(PropTypes.node,
); + expectNoop(PropTypes.node, false); + expectNoop(PropTypes.node, ); + expectNoop(PropTypes.node, 'Some string'); + expectNoop(PropTypes.node, []); + expectNoop(PropTypes.node, [ + 123, + 'Some string', +
, + ['Another string', [456], , ], + , + null, + undefined, + ]); + }); + + it('should not warn for iterables', () => { + function MyComponent() {} + MyComponent.prototype.render = function() { + return
; + }; + var iterable = { + '@@iterator': function() { + var i = 0; + return { + next: function() { + var done = ++i > 2; + return {value: done ? undefined : , done: done}; + }, + }; + }, + }; + + expectNoop(PropTypes.node, iterable); + }); + + it('should not warn for entry iterables', () => { + function MyComponent() {} + MyComponent.prototype.render = function() { + return
; + }; + var iterable = { + '@@iterator': function() { + var i = 0; + return { + next: function() { + var done = ++i > 2; + return { + value: done ? undefined : ['#' + i, ], + done: done, + }; + }, + }; + }, + }; + iterable.entries = iterable['@@iterator']; + + expectNoop(PropTypes.node, iterable); + }); + + it('should not warn for null/undefined if not required', () => { + expectNoop(PropTypes.node, null); + expectNoop(PropTypes.node, undefined); + }); + + it('should warn for missing required values', () => { + expectNoop(PropTypes.node.isRequired); + }); + + it('should accept empty array for required props', () => { + expectNoop(PropTypes.node.isRequired, []); + }); + + it('should warn if called manually in development', () => { + spyOn(console, 'error'); + expectNoop(PropTypes.node, 'node'); + expectNoop(PropTypes.node, {}); + expectNoop(PropTypes.node.isRequired, 'node'); + expectNoop(PropTypes.node.isRequired, undefined); + expectNoop(PropTypes.node.isRequired, undefined); + }); + }); + + describe('ObjectOf Type', () => { + it('should fail for invalid argument', () => { + expectNoop( + PropTypes.objectOf({foo: PropTypes.string}), + {foo: 'bar'}, + 'Property `testProp` of component `testComponent` has invalid PropType notation inside objectOf.', + ); + }); + + it('should support the objectOf propTypes', () => { + expectNoop(PropTypes.objectOf(PropTypes.number), {a: 1, b: 2, c: 3}); + expectNoop(PropTypes.objectOf(PropTypes.string), { + a: 'a', + b: 'b', + c: 'c', + }); + expectNoop(PropTypes.objectOf(PropTypes.oneOf(['a', 'b'])), { + a: 'a', + b: 'b', + }); + expectNoop(PropTypes.objectOf(PropTypes.symbol), { + a: Symbol(), + b: Symbol(), + c: Symbol(), + }); + }); + + it('should support objectOf with complex types', () => { + expectNoop( + PropTypes.objectOf(PropTypes.shape({a: PropTypes.number.isRequired})), + {a: {a: 1}, b: {a: 2}}, + ); + + function Thing() {} + expectNoop(PropTypes.objectOf(PropTypes.instanceOf(Thing)), { + a: new Thing(), + b: new Thing(), + }); + }); + + it('should warn with invalid items in the object', () => { + expectNoop( + PropTypes.objectOf(PropTypes.number), + {a: 1, b: 2, c: 'b'}, + 'Invalid prop `testProp.c` of type `string` supplied to `testComponent`, ' + + 'expected `number`.', + ); + }); + + it('should warn with invalid complex types', () => { + function Thing() {} + var name = Thing.name || '<>'; + + expectNoop( + PropTypes.objectOf(PropTypes.instanceOf(Thing)), + {a: new Thing(), b: 'xyz'}, + 'Invalid prop `testProp.b` of type `String` supplied to ' + + '`testComponent`, expected instance of `' + + name + + '`.', + ); + }); + + it('should warn when passed something other than an object', () => { + expectNoop( + PropTypes.objectOf(PropTypes.number), + [1, 2], + 'Invalid prop `testProp` of type `array` supplied to ' + + '`testComponent`, expected an object.', + ); + expectNoop( + PropTypes.objectOf(PropTypes.number), + 123, + 'Invalid prop `testProp` of type `number` supplied to ' + + '`testComponent`, expected an object.', + ); + expectNoop( + PropTypes.objectOf(PropTypes.number), + 'string', + 'Invalid prop `testProp` of type `string` supplied to ' + + '`testComponent`, expected an object.', + ); + expectNoop( + PropTypes.objectOf(PropTypes.symbol), + Symbol(), + 'Invalid prop `testProp` of type `symbol` supplied to ' + + '`testComponent`, expected an object.', + ); + }); + + it('should not warn when passing an empty object', () => { + expectNoop(PropTypes.objectOf(PropTypes.number), {}); + }); + + it('should be implicitly optional and not warn without values', () => { + expectNoop(PropTypes.objectOf(PropTypes.number), null); + expectNoop(PropTypes.objectOf(PropTypes.number), undefined); + }); + + it('should warn for missing required values', () => { + expectNoop( + PropTypes.objectOf(PropTypes.number).isRequired, + ); + }); + + it('should warn if called manually in development', () => { + spyOn(console, 'error'); + expectNoop(PropTypes.objectOf({foo: PropTypes.string}), { + foo: 'bar', + }); + expectNoop(PropTypes.objectOf(PropTypes.number), { + a: 1, + b: 2, + c: 'b', + }); + expectNoop(PropTypes.objectOf(PropTypes.number), [1, 2]); + expectNoop(PropTypes.objectOf(PropTypes.number), null); + expectNoop( + PropTypes.objectOf(PropTypes.number), + undefined, + ); + }); + }); + + describe('OneOf Types', () => { + it('should ignore invalid argument', () => { + spyOn(console, 'error'); + + PropTypes.oneOf('red', 'blue'); + + expect(console.error).not.toHaveBeenCalled(); + expectNoop(PropTypes.oneOf('red', 'blue'), 'red'); + }); + + it('should ignore invalid values', () => { + expectNoop( + PropTypes.oneOf(['red', 'blue']), + true, + 'Invalid prop `testProp` of value `true` supplied to ' + + '`testComponent`, expected one of ["red","blue"].', + ); + expectNoop( + PropTypes.oneOf(['red', 'blue']), + [], + 'Invalid prop `testProp` of value `` supplied to `testComponent`, ' + + 'expected one of ["red","blue"].', + ); + expectNoop( + PropTypes.oneOf(['red', 'blue']), + '', + 'Invalid prop `testProp` of value `` supplied to `testComponent`, ' + + 'expected one of ["red","blue"].', + ); + expectNoop( + PropTypes.oneOf([0, 'false']), + false, + 'Invalid prop `testProp` of value `false` supplied to ' + + '`testComponent`, expected one of [0,"false"].', + ); + }); + + it('should not warn for valid values', () => { + expectNoop(PropTypes.oneOf(['red', 'blue']), 'red'); + expectNoop(PropTypes.oneOf(['red', 'blue']), 'blue'); + expectNoop(PropTypes.oneOf(['red', 'blue', NaN]), NaN); + }); + + it('should be implicitly optional and not warn without values', () => { + expectNoop(PropTypes.oneOf(['red', 'blue']), null); + expectNoop(PropTypes.oneOf(['red', 'blue']), undefined); + }); + + it('should warn for missing required values', () => { + expectNoop(PropTypes.oneOf(['red', 'blue']).isRequired); + }); + + it('should warn if called manually in development', () => { + spyOn(console, 'error'); + expectNoop(PropTypes.oneOf(['red', 'blue']), true); + expectNoop(PropTypes.oneOf(['red', 'blue']), null); + expectNoop(PropTypes.oneOf(['red', 'blue']), undefined); + }); + }); + + describe('Union Types', () => { + it('should ignore invalid argument', () => { + spyOn(console, 'error'); + + PropTypes.oneOfType(PropTypes.string, PropTypes.number); + + expect(console.error).not.toHaveBeenCalled(); + expectNoop(PropTypes.oneOf(PropTypes.string, PropTypes.number), []); + }); + + it('should warn if none of the types are valid', () => { + expectNoop( + PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + [], + 'Invalid prop `testProp` supplied to `testComponent`.', + ); + + var checker = PropTypes.oneOfType([ + PropTypes.shape({a: PropTypes.number.isRequired}), + PropTypes.shape({b: PropTypes.number.isRequired}), + ]); + expectNoop( + checker, + {c: 1}, + 'Invalid prop `testProp` supplied to `testComponent`.', + ); + }); + + it('should not warn if one of the types are valid', () => { + var checker = PropTypes.oneOfType([PropTypes.string, PropTypes.number]); + expectNoop(checker, null); + expectNoop(checker, 'foo'); + expectNoop(checker, 123); + + checker = PropTypes.oneOfType([ + PropTypes.shape({a: PropTypes.number.isRequired}), + PropTypes.shape({b: PropTypes.number.isRequired}), + ]); + expectNoop(checker, {a: 1}); + expectNoop(checker, {b: 1}); + }); + + it('should be implicitly optional and not warn without values', () => { + expectNoop( + PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + null, + ); + expectNoop( + PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + undefined, + ); + }); + + it('should warn for missing required values', () => { + expectNoop( + PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + ); + }); + + it('should warn if called manually in development', () => { + spyOn(console, 'error'); + expectNoop( + PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + [], + ); + expectNoop( + PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + null, + ); + expectNoop( + PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + undefined, + ); + }); + }); + + describe('Shape Types', () => { + it('should warn for non objects', () => { + expectNoop( + PropTypes.shape({}), + 'some string', + 'Invalid prop `testProp` of type `string` supplied to ' + + '`testComponent`, expected `object`.', + ); + expectNoop( + PropTypes.shape({}), + ['array'], + 'Invalid prop `testProp` of type `array` supplied to ' + + '`testComponent`, expected `object`.', + ); + }); + + it('should not warn for empty values', () => { + expectNoop(PropTypes.shape({}), undefined); + expectNoop(PropTypes.shape({}), null); + expectNoop(PropTypes.shape({}), {}); + }); + + it('should not warn for an empty object', () => { + expectNoop(PropTypes.shape({}).isRequired, {}); + }); + + it('should not warn for non specified types', () => { + expectNoop(PropTypes.shape({}), {key: 1}); + }); + + it('should not warn for valid types', () => { + expectNoop(PropTypes.shape({key: PropTypes.number}), {key: 1}); + }); + + it('should warn for required valid types', () => { + expectNoop( + PropTypes.shape({key: PropTypes.number.isRequired}), + {}, + 'The prop `testProp.key` is marked as required in `testComponent`, ' + + 'but its value is `undefined`.', + ); + }); + + it('should warn for the first required type', () => { + expectNoop( + PropTypes.shape({ + key: PropTypes.number.isRequired, + secondKey: PropTypes.number.isRequired, + }), + {}, + 'The prop `testProp.key` is marked as required in `testComponent`, ' + + 'but its value is `undefined`.', + ); + }); + + it('should warn for invalid key types', () => { + expectNoop( + PropTypes.shape({key: PropTypes.number}), + {key: 'abc'}, + 'Invalid prop `testProp.key` of type `string` supplied to `testComponent`, ' + + 'expected `number`.', + ); + }); + + it('should be implicitly optional and not warn without values', () => { + expectNoop( + PropTypes.shape(PropTypes.shape({key: PropTypes.number})), + null, + ); + expectNoop( + PropTypes.shape(PropTypes.shape({key: PropTypes.number})), + undefined, + ); + }); + + it('should warn for missing required values', () => { + expectNoop( + PropTypes.shape({key: PropTypes.number}).isRequired, + ); + }); + + it('should warn if called manually in development', () => { + spyOn(console, 'error'); + expectNoop(PropTypes.shape({}), 'some string'); + expectNoop(PropTypes.shape({foo: PropTypes.number}), { + foo: 42, + }); + expectNoop( + PropTypes.shape({key: PropTypes.number}).isRequired, + null, + ); + expectNoop( + PropTypes.shape({key: PropTypes.number}).isRequired, + undefined, + ); + expectNoop(PropTypes.element,
); + }); + }); + + describe('Symbol Type', () => { + it('should warn for non-symbol', () => { + expectNoop( + PropTypes.symbol, + 'hello', + 'Invalid prop `testProp` of type `string` supplied to ' + + '`testComponent`, expected `symbol`.', + ); + expectNoop( + PropTypes.symbol, + function() {}, + 'Invalid prop `testProp` of type `function` supplied to ' + + '`testComponent`, expected `symbol`.', + ); + expectNoop( + PropTypes.symbol, + { + '@@toStringTag': 'Katana', + }, + 'Invalid prop `testProp` of type `object` supplied to ' + + '`testComponent`, expected `symbol`.', + ); + }); + + it('should not warn for a polyfilled Symbol', () => { + var CoreSymbol = require('core-js/library/es6/symbol'); + expectNoop(PropTypes.symbol, CoreSymbol('core-js')); + }); + }); +}); diff --git a/__tests__/PropTypesProduction-test.js b/__tests__/PropTypesProductionStandalone-test.js similarity index 96% rename from __tests__/PropTypesProduction-test.js rename to __tests__/PropTypesProductionStandalone-test.js index 3d465c7..006e09a 100644 --- a/__tests__/PropTypesProduction-test.js +++ b/__tests__/PropTypesProductionStandalone-test.js @@ -11,27 +11,26 @@ 'use strict'; -describe('ReactPropTypesProduction', function() { +describe('PropTypesProductionStandalone', function() { var React; var PropTypes; - var checkPropTypes; - var oldProcess; - beforeEach(function() { + function resetWarningCache() { + jest.resetModules(); process.env.NODE_ENV = 'production'; + React = require('react'); PropTypes = require('../index'); + } + + beforeEach(function() { + resetWarningCache(); }); afterEach(function() { delete process.env.NODE_ENV; }); - function resetWarningCache() { - jest.resetModules(); - checkPropTypes = require('../checkPropTypes'); - } - function getPropTypeWarningMessage(propTypes, object, componentName) { if (!console.error.calls) { spyOn(console, 'error'); @@ -39,7 +38,7 @@ describe('ReactPropTypesProduction', function() { console.error.calls.reset(); } resetWarningCache(); - checkPropTypes(propTypes, object, 'prop', 'testComponent'); + PropTypes.checkPropTypes(propTypes, object, 'prop', 'testComponent'); const callCount = console.error.calls.count(); if (callCount > 1) { throw new Error('Too many warnings.'); @@ -222,10 +221,6 @@ describe('ReactPropTypesProduction', function() { }); describe('checkPropTypes', function() { - beforeEach(function() { - jest.resetModules(); - }); - it('does not call validators', function() { spyOn(console, 'error'); diff --git a/checkPropTypes.js b/checkPropTypes.js index 0b68714..c2b536f 100644 --- a/checkPropTypes.js +++ b/checkPropTypes.js @@ -9,12 +9,12 @@ 'use strict'; -var invariant = require('fbjs/lib/invariant'); -var warning = require('fbjs/lib/warning'); - -var ReactPropTypesSecret = require('./lib/ReactPropTypesSecret'); - -var loggedTypeFailures = {}; +if (process.env.NODE_ENV !== 'production') { + var invariant = require('fbjs/lib/invariant'); + var warning = require('fbjs/lib/warning'); + var ReactPropTypesSecret = require('./lib/ReactPropTypesSecret'); + var loggedTypeFailures = {}; +} /** * Assert that the values match with the type specs. @@ -43,7 +43,7 @@ function checkPropTypes(typeSpecs, values, location, componentName, getStack) { } catch (ex) { error = ex; } - process.env.NODE_ENV !== 'production' ? warning(!error || error instanceof Error, '%s: type specification of %s `%s` is invalid; the type checker ' + 'function must return `null` or an `Error` but returned a %s. ' + 'You may have forgotten to pass an argument to the type checker ' + 'creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and ' + 'shape all require an argument).', componentName || 'React class', location, typeSpecName, typeof error) : void 0; + warning(!error || error instanceof Error, '%s: type specification of %s `%s` is invalid; the type checker ' + 'function must return `null` or an `Error` but returned a %s. ' + 'You may have forgotten to pass an argument to the type checker ' + 'creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and ' + 'shape all require an argument).', componentName || 'React class', location, typeSpecName, typeof error); if (error instanceof Error && !(error.message in loggedTypeFailures)) { // Only monitor this failure once because there tends to be a lot of the // same error. @@ -51,7 +51,7 @@ function checkPropTypes(typeSpecs, values, location, componentName, getStack) { var stack = getStack ? getStack() : ''; - process.env.NODE_ENV !== 'production' ? warning(false, 'Failed %s type: %s%s', location, error.message, stack != null ? stack : '') : void 0; + warning(false, 'Failed %s type: %s%s', location, error.message, stack != null ? stack : ''); } } } diff --git a/factory.js b/factory.js index 9cab0a4..21ff587 100644 --- a/factory.js +++ b/factory.js @@ -9,476 +9,8 @@ 'use strict'; -var emptyFunction = require('fbjs/lib/emptyFunction'); -var invariant = require('fbjs/lib/invariant'); -var warning = require('fbjs/lib/warning'); - -var ReactPropTypesSecret = require('./lib/ReactPropTypesSecret'); -var checkPropTypes = require('./checkPropTypes'); - -module.exports = function (isValidElement) { - /* global Symbol */ - var ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator; - var FAUX_ITERATOR_SYMBOL = '@@iterator'; // Before Symbol spec. - - /** - * Returns the iterator method function contained on the iterable object. - * - * Be sure to invoke the function with the iterable as context: - * - * var iteratorFn = getIteratorFn(myIterable); - * if (iteratorFn) { - * var iterator = iteratorFn.call(myIterable); - * ... - * } - * - * @param {?object} maybeIterable - * @return {?function} - */ - function getIteratorFn(maybeIterable) { - var iteratorFn = maybeIterable && (ITERATOR_SYMBOL && maybeIterable[ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL]); - if (typeof iteratorFn === 'function') { - return iteratorFn; - } - } - - /** - * Collection of methods that allow declaration and validation of props that are - * supplied to React components. Example usage: - * - * var Props = require('ReactPropTypes'); - * var MyArticle = React.createClass({ - * propTypes: { - * // An optional string prop named "description". - * description: Props.string, - * - * // A required enum prop named "category". - * category: Props.oneOf(['News','Photos']).isRequired, - * - * // A prop named "dialog" that requires an instance of Dialog. - * dialog: Props.instanceOf(Dialog).isRequired - * }, - * render: function() { ... } - * }); - * - * A more formal specification of how these methods are used: - * - * type := array|bool|func|object|number|string|oneOf([...])|instanceOf(...) - * decl := ReactPropTypes.{type}(.isRequired)? - * - * Each and every declaration produces a function with the same signature. This - * allows the creation of custom validation functions. For example: - * - * var MyLink = React.createClass({ - * propTypes: { - * // An optional string or URI prop named "href". - * href: function(props, propName, componentName) { - * var propValue = props[propName]; - * if (propValue != null && typeof propValue !== 'string' && - * !(propValue instanceof URI)) { - * return new Error( - * 'Expected a string or an URI for ' + propName + ' in ' + - * componentName - * ); - * } - * } - * }, - * render: function() {...} - * }); - * - * @internal - */ - - var ANONYMOUS = '<>'; - - var ReactPropTypes; - - if (process.env.NODE_ENV !== 'production') { - // Keep in sync with production version below - ReactPropTypes = { - array: createPrimitiveTypeChecker('array'), - bool: createPrimitiveTypeChecker('boolean'), - func: createPrimitiveTypeChecker('function'), - number: createPrimitiveTypeChecker('number'), - object: createPrimitiveTypeChecker('object'), - string: createPrimitiveTypeChecker('string'), - symbol: createPrimitiveTypeChecker('symbol'), - - any: createAnyTypeChecker(), - arrayOf: createArrayOfTypeChecker, - element: createElementTypeChecker(), - instanceOf: createInstanceTypeChecker, - node: createNodeChecker(), - objectOf: createObjectOfTypeChecker, - oneOf: createEnumTypeChecker, - oneOfType: createUnionTypeChecker, - shape: createShapeTypeChecker - }; - } else { - var productionTypeChecker = function () { - invariant(false, 'React.PropTypes type checking code is stripped in production.'); - }; - productionTypeChecker.isRequired = productionTypeChecker; - var getProductionTypeChecker = function () { - return productionTypeChecker; - }; - // Keep in sync with development version above - ReactPropTypes = { - array: productionTypeChecker, - bool: productionTypeChecker, - func: productionTypeChecker, - number: productionTypeChecker, - object: productionTypeChecker, - string: productionTypeChecker, - symbol: productionTypeChecker, - - any: productionTypeChecker, - arrayOf: getProductionTypeChecker, - element: productionTypeChecker, - instanceOf: getProductionTypeChecker, - node: productionTypeChecker, - objectOf: getProductionTypeChecker, - oneOf: getProductionTypeChecker, - oneOfType: getProductionTypeChecker, - shape: getProductionTypeChecker - }; - } - - /** - * inlined Object.is polyfill to avoid requiring consumers ship their own - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is - */ - /*eslint-disable no-self-compare*/ - function is(x, y) { - // SameValue algorithm - if (x === y) { - // Steps 1-5, 7-10 - // Steps 6.b-6.e: +0 != -0 - return x !== 0 || 1 / x === 1 / y; - } else { - // Step 6.a: NaN == NaN - return x !== x && y !== y; - } - } - /*eslint-enable no-self-compare*/ - - /** - * We use an Error-like object for backward compatibility as people may call - * PropTypes directly and inspect their output. However, we don't use real - * Errors anymore. We don't inspect their stack anyway, and creating them - * is prohibitively expensive if they are created too often, such as what - * happens in oneOfType() for any type before the one that matched. - */ - function PropTypeError(message) { - this.message = message; - this.stack = ''; - } - // Make `instanceof Error` still work for returned errors. - PropTypeError.prototype = Error.prototype; - - function createChainableTypeChecker(validate) { - if (process.env.NODE_ENV !== 'production') { - var manualPropTypeCallCache = {}; - } - function checkType(isRequired, props, propName, componentName, location, propFullName, secret) { - componentName = componentName || ANONYMOUS; - propFullName = propFullName || propName; - if (process.env.NODE_ENV !== 'production') { - if (secret !== ReactPropTypesSecret && typeof console !== 'undefined') { - var cacheKey = componentName + ':' + propName; - if (!manualPropTypeCallCache[cacheKey]) { - process.env.NODE_ENV !== 'production' ? warning(false, 'You are manually calling a React.PropTypes validation ' + 'function for the `%s` prop on `%s`. This is deprecated ' + 'and will not work in production with the next major version. ' + 'You may be seeing this warning due to a third-party PropTypes ' + 'library. See https://fb.me/react-warning-dont-call-proptypes ' + 'for details.', propFullName, componentName) : void 0; - manualPropTypeCallCache[cacheKey] = true; - } - } - } - if (props[propName] == null) { - if (isRequired) { - if (props[propName] === null) { - return new PropTypeError('The ' + location + ' `' + propFullName + '` is marked as required ' + ('in `' + componentName + '`, but its value is `null`.')); - } - return new PropTypeError('The ' + location + ' `' + propFullName + '` is marked as required in ' + ('`' + componentName + '`, but its value is `undefined`.')); - } - return null; - } else { - return validate(props, propName, componentName, location, propFullName); - } - } - - var chainedCheckType = checkType.bind(null, false); - chainedCheckType.isRequired = checkType.bind(null, true); - - return chainedCheckType; - } - - function createPrimitiveTypeChecker(expectedType) { - function validate(props, propName, componentName, location, propFullName, secret) { - var propValue = props[propName]; - var propType = getPropType(propValue); - if (propType !== expectedType) { - // `propValue` being instance of, say, date/regexp, pass the 'object' - // check, but we can offer a more precise error message here rather than - // 'of type `object`'. - var preciseType = getPreciseType(propValue); - - return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + preciseType + '` supplied to `' + componentName + '`, expected ') + ('`' + expectedType + '`.')); - } - return null; - } - return createChainableTypeChecker(validate); - } - - function createAnyTypeChecker() { - return createChainableTypeChecker(emptyFunction.thatReturnsNull); - } - - function createArrayOfTypeChecker(typeChecker) { - function validate(props, propName, componentName, location, propFullName) { - if (typeof typeChecker !== 'function') { - return new PropTypeError('Property `' + propFullName + '` of component `' + componentName + '` has invalid PropType notation inside arrayOf.'); - } - var propValue = props[propName]; - if (!Array.isArray(propValue)) { - var propType = getPropType(propValue); - return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected an array.')); - } - for (var i = 0; i < propValue.length; i++) { - var error = typeChecker(propValue, i, componentName, location, propFullName + '[' + i + ']', ReactPropTypesSecret); - if (error instanceof Error) { - return error; - } - } - return null; - } - return createChainableTypeChecker(validate); - } - - function createElementTypeChecker() { - function validate(props, propName, componentName, location, propFullName) { - var propValue = props[propName]; - if (!isValidElement(propValue)) { - var propType = getPropType(propValue); - return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected a single ReactElement.')); - } - return null; - } - return createChainableTypeChecker(validate); - } - - function createInstanceTypeChecker(expectedClass) { - function validate(props, propName, componentName, location, propFullName) { - if (!(props[propName] instanceof expectedClass)) { - var expectedClassName = expectedClass.name || ANONYMOUS; - var actualClassName = getClassName(props[propName]); - return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + actualClassName + '` supplied to `' + componentName + '`, expected ') + ('instance of `' + expectedClassName + '`.')); - } - return null; - } - return createChainableTypeChecker(validate); - } - - function createEnumTypeChecker(expectedValues) { - if (!Array.isArray(expectedValues)) { - process.env.NODE_ENV !== 'production' ? warning(false, 'Invalid argument supplied to oneOf, expected an instance of array.') : void 0; - return emptyFunction.thatReturnsNull; - } - - function validate(props, propName, componentName, location, propFullName) { - var propValue = props[propName]; - for (var i = 0; i < expectedValues.length; i++) { - if (is(propValue, expectedValues[i])) { - return null; - } - } - - var valuesString = JSON.stringify(expectedValues); - return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of value `' + propValue + '` ' + ('supplied to `' + componentName + '`, expected one of ' + valuesString + '.')); - } - return createChainableTypeChecker(validate); - } - - function createObjectOfTypeChecker(typeChecker) { - function validate(props, propName, componentName, location, propFullName) { - if (typeof typeChecker !== 'function') { - return new PropTypeError('Property `' + propFullName + '` of component `' + componentName + '` has invalid PropType notation inside objectOf.'); - } - var propValue = props[propName]; - var propType = getPropType(propValue); - if (propType !== 'object') { - return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected an object.')); - } - for (var key in propValue) { - if (propValue.hasOwnProperty(key)) { - var error = typeChecker(propValue, key, componentName, location, propFullName + '.' + key, ReactPropTypesSecret); - if (error instanceof Error) { - return error; - } - } - } - return null; - } - return createChainableTypeChecker(validate); - } - - function createUnionTypeChecker(arrayOfTypeCheckers) { - if (!Array.isArray(arrayOfTypeCheckers)) { - process.env.NODE_ENV !== 'production' ? warning(false, 'Invalid argument supplied to oneOfType, expected an instance of array.') : void 0; - return emptyFunction.thatReturnsNull; - } - - function validate(props, propName, componentName, location, propFullName) { - for (var i = 0; i < arrayOfTypeCheckers.length; i++) { - var checker = arrayOfTypeCheckers[i]; - if (checker(props, propName, componentName, location, propFullName, ReactPropTypesSecret) == null) { - return null; - } - } - - return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` supplied to ' + ('`' + componentName + '`.')); - } - return createChainableTypeChecker(validate); - } - - function createNodeChecker() { - function validate(props, propName, componentName, location, propFullName) { - if (!isNode(props[propName])) { - return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` supplied to ' + ('`' + componentName + '`, expected a ReactNode.')); - } - return null; - } - return createChainableTypeChecker(validate); - } - - function createShapeTypeChecker(shapeTypes) { - function validate(props, propName, componentName, location, propFullName) { - var propValue = props[propName]; - var propType = getPropType(propValue); - if (propType !== 'object') { - return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type `' + propType + '` ' + ('supplied to `' + componentName + '`, expected `object`.')); - } - for (var key in shapeTypes) { - var checker = shapeTypes[key]; - if (!checker) { - continue; - } - var error = checker(propValue, key, componentName, location, propFullName + '.' + key, ReactPropTypesSecret); - if (error) { - return error; - } - } - return null; - } - return createChainableTypeChecker(validate); - } - - function isNode(propValue) { - switch (typeof propValue) { - case 'number': - case 'string': - case 'undefined': - return true; - case 'boolean': - return !propValue; - case 'object': - if (Array.isArray(propValue)) { - return propValue.every(isNode); - } - if (propValue === null || isValidElement(propValue)) { - return true; - } - - var iteratorFn = getIteratorFn(propValue); - if (iteratorFn) { - var iterator = iteratorFn.call(propValue); - var step; - if (iteratorFn !== propValue.entries) { - while (!(step = iterator.next()).done) { - if (!isNode(step.value)) { - return false; - } - } - } else { - // Iterator will provide entry [k,v] tuples rather than values. - while (!(step = iterator.next()).done) { - var entry = step.value; - if (entry) { - if (!isNode(entry[1])) { - return false; - } - } - } - } - } else { - return false; - } - - return true; - default: - return false; - } - } - - function isSymbol(propType, propValue) { - // Native Symbol. - if (propType === 'symbol') { - return true; - } - - // 19.4.3.5 Symbol.prototype[@@toStringTag] === 'Symbol' - if (propValue['@@toStringTag'] === 'Symbol') { - return true; - } - - // Fallback for non-spec compliant Symbols which are polyfilled. - if (typeof Symbol === 'function' && propValue instanceof Symbol) { - return true; - } - - return false; - } - - // Equivalent of `typeof` but with special handling for array and regexp. - function getPropType(propValue) { - var propType = typeof propValue; - if (Array.isArray(propValue)) { - return 'array'; - } - if (propValue instanceof RegExp) { - // Old webkits (at least until Android 4.0) return 'function' rather than - // 'object' for typeof a RegExp. We'll normalize this here so that /bla/ - // passes PropTypes.object. - return 'object'; - } - if (isSymbol(propType, propValue)) { - return 'symbol'; - } - return propType; - } - - // This handles more types than `getPropType`. Only used for error messages. - // See `createPrimitiveTypeChecker`. - function getPreciseType(propValue) { - var propType = getPropType(propValue); - if (propType === 'object') { - if (propValue instanceof Date) { - return 'date'; - } else if (propValue instanceof RegExp) { - return 'regexp'; - } - } - return propType; - } - - // Returns class name of the object, if any. - function getClassName(propValue) { - if (!propValue.constructor || !propValue.constructor.name) { - return ANONYMOUS; - } - return propValue.constructor.name; - } - - ReactPropTypes.checkPropTypes = checkPropTypes; - ReactPropTypes.PropTypes = ReactPropTypes; - - return ReactPropTypes; -}; +// React 15.5 references this module, and assumes PropTypes are still callable in production. +// Therefore we re-export development-only version with all the PropTypes checks here. +// However if one is migrating to the `prop-types` npm library, they will go through the +// `index.js` entry point, and it will branch depending on the environment. +module.exports = require('./factoryWithTypeCheckers'); diff --git a/factoryWithThrowingShims.js b/factoryWithThrowingShims.js new file mode 100644 index 0000000..e8c4fa1 --- /dev/null +++ b/factoryWithThrowingShims.js @@ -0,0 +1,49 @@ +/** + * Copyright 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +'use strict'; + +var emptyFunction = require('fbjs/lib/emptyFunction'); +var invariant = require('fbjs/lib/invariant'); + +module.exports = function() { + // Important! + // Keep this list in sync with production version in `./factoryWithTypeCheckers.js`. + function shim() { + invariant(false, 'React.PropTypes type checking code is stripped in production.'); + }; + shim.isRequired = shim; + function getShim() { + return shim; + }; + var ReactPropTypes = { + array: shim, + bool: shim, + func: shim, + number: shim, + object: shim, + string: shim, + symbol: shim, + + any: shim, + arrayOf: getShim, + element: shim, + instanceOf: getShim, + node: shim, + objectOf: getShim, + oneOf: getShim, + oneOfType: getShim, + shape: getShim + }; + + ReactPropTypes.checkPropTypes = emptyFunction; + ReactPropTypes.PropTypes = ReactPropTypes; + + return ReactPropTypes; +}; diff --git a/factoryWithTypeCheckers.js b/factoryWithTypeCheckers.js new file mode 100644 index 0000000..7ad84cf --- /dev/null +++ b/factoryWithTypeCheckers.js @@ -0,0 +1,453 @@ +/** + * Copyright 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +'use strict'; + +var emptyFunction = require('fbjs/lib/emptyFunction'); +var invariant = require('fbjs/lib/invariant'); +var warning = require('fbjs/lib/warning'); + +var ReactPropTypesSecret = require('./lib/ReactPropTypesSecret'); +var checkPropTypes = require('./checkPropTypes'); + +module.exports = function(isValidElement) { + /* global Symbol */ + var ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator; + var FAUX_ITERATOR_SYMBOL = '@@iterator'; // Before Symbol spec. + + /** + * Returns the iterator method function contained on the iterable object. + * + * Be sure to invoke the function with the iterable as context: + * + * var iteratorFn = getIteratorFn(myIterable); + * if (iteratorFn) { + * var iterator = iteratorFn.call(myIterable); + * ... + * } + * + * @param {?object} maybeIterable + * @return {?function} + */ + function getIteratorFn(maybeIterable) { + var iteratorFn = maybeIterable && (ITERATOR_SYMBOL && maybeIterable[ITERATOR_SYMBOL] || maybeIterable[FAUX_ITERATOR_SYMBOL]); + if (typeof iteratorFn === 'function') { + return iteratorFn; + } + } + + /** + * Collection of methods that allow declaration and validation of props that are + * supplied to React components. Example usage: + * + * var Props = require('ReactPropTypes'); + * var MyArticle = React.createClass({ + * propTypes: { + * // An optional string prop named "description". + * description: Props.string, + * + * // A required enum prop named "category". + * category: Props.oneOf(['News','Photos']).isRequired, + * + * // A prop named "dialog" that requires an instance of Dialog. + * dialog: Props.instanceOf(Dialog).isRequired + * }, + * render: function() { ... } + * }); + * + * A more formal specification of how these methods are used: + * + * type := array|bool|func|object|number|string|oneOf([...])|instanceOf(...) + * decl := ReactPropTypes.{type}(.isRequired)? + * + * Each and every declaration produces a function with the same signature. This + * allows the creation of custom validation functions. For example: + * + * var MyLink = React.createClass({ + * propTypes: { + * // An optional string or URI prop named "href". + * href: function(props, propName, componentName) { + * var propValue = props[propName]; + * if (propValue != null && typeof propValue !== 'string' && + * !(propValue instanceof URI)) { + * return new Error( + * 'Expected a string or an URI for ' + propName + ' in ' + + * componentName + * ); + * } + * } + * }, + * render: function() {...} + * }); + * + * @internal + */ + + var ANONYMOUS = '<>'; + + // Important! + // Keep this list in sync with production version in `./factoryWithThrowingShims.js`. + var ReactPropTypes = { + array: createPrimitiveTypeChecker('array'), + bool: createPrimitiveTypeChecker('boolean'), + func: createPrimitiveTypeChecker('function'), + number: createPrimitiveTypeChecker('number'), + object: createPrimitiveTypeChecker('object'), + string: createPrimitiveTypeChecker('string'), + symbol: createPrimitiveTypeChecker('symbol'), + + any: createAnyTypeChecker(), + arrayOf: createArrayOfTypeChecker, + element: createElementTypeChecker(), + instanceOf: createInstanceTypeChecker, + node: createNodeChecker(), + objectOf: createObjectOfTypeChecker, + oneOf: createEnumTypeChecker, + oneOfType: createUnionTypeChecker, + shape: createShapeTypeChecker + }; + + /** + * inlined Object.is polyfill to avoid requiring consumers ship their own + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is + */ + /*eslint-disable no-self-compare*/ + function is(x, y) { + // SameValue algorithm + if (x === y) { + // Steps 1-5, 7-10 + // Steps 6.b-6.e: +0 != -0 + return x !== 0 || 1 / x === 1 / y; + } else { + // Step 6.a: NaN == NaN + return x !== x && y !== y; + } + } + /*eslint-enable no-self-compare*/ + + /** + * We use an Error-like object for backward compatibility as people may call + * PropTypes directly and inspect their output. However, we don't use real + * Errors anymore. We don't inspect their stack anyway, and creating them + * is prohibitively expensive if they are created too often, such as what + * happens in oneOfType() for any type before the one that matched. + */ + function PropTypeError(message) { + this.message = message; + this.stack = ''; + } + // Make `instanceof Error` still work for returned errors. + PropTypeError.prototype = Error.prototype; + + function createChainableTypeChecker(validate) { + if (process.env.NODE_ENV !== 'production') { + var manualPropTypeCallCache = {}; + } + function checkType(isRequired, props, propName, componentName, location, propFullName, secret) { + componentName = componentName || ANONYMOUS; + propFullName = propFullName || propName; + if (process.env.NODE_ENV !== 'production') { + if (secret !== ReactPropTypesSecret && typeof console !== 'undefined') { + var cacheKey = componentName + ':' + propName; + if (!manualPropTypeCallCache[cacheKey]) { + process.env.NODE_ENV !== 'production' ? warning(false, 'You are manually calling a React.PropTypes validation ' + 'function for the `%s` prop on `%s`. This is deprecated ' + 'and will not work in production with the next major version. ' + 'You may be seeing this warning due to a third-party PropTypes ' + 'library. See https://fb.me/react-warning-dont-call-proptypes ' + 'for details.', propFullName, componentName) : void 0; + manualPropTypeCallCache[cacheKey] = true; + } + } + } + if (props[propName] == null) { + if (isRequired) { + if (props[propName] === null) { + return new PropTypeError('The ' + location + ' `' + propFullName + '` is marked as required ' + ('in `' + componentName + '`, but its value is `null`.')); + } + return new PropTypeError('The ' + location + ' `' + propFullName + '` is marked as required in ' + ('`' + componentName + '`, but its value is `undefined`.')); + } + return null; + } else { + return validate(props, propName, componentName, location, propFullName); + } + } + + var chainedCheckType = checkType.bind(null, false); + chainedCheckType.isRequired = checkType.bind(null, true); + + return chainedCheckType; + } + + function createPrimitiveTypeChecker(expectedType) { + function validate(props, propName, componentName, location, propFullName, secret) { + var propValue = props[propName]; + var propType = getPropType(propValue); + if (propType !== expectedType) { + // `propValue` being instance of, say, date/regexp, pass the 'object' + // check, but we can offer a more precise error message here rather than + // 'of type `object`'. + var preciseType = getPreciseType(propValue); + + return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + preciseType + '` supplied to `' + componentName + '`, expected ') + ('`' + expectedType + '`.')); + } + return null; + } + return createChainableTypeChecker(validate); + } + + function createAnyTypeChecker() { + return createChainableTypeChecker(emptyFunction.thatReturnsNull); + } + + function createArrayOfTypeChecker(typeChecker) { + function validate(props, propName, componentName, location, propFullName) { + if (typeof typeChecker !== 'function') { + return new PropTypeError('Property `' + propFullName + '` of component `' + componentName + '` has invalid PropType notation inside arrayOf.'); + } + var propValue = props[propName]; + if (!Array.isArray(propValue)) { + var propType = getPropType(propValue); + return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected an array.')); + } + for (var i = 0; i < propValue.length; i++) { + var error = typeChecker(propValue, i, componentName, location, propFullName + '[' + i + ']', ReactPropTypesSecret); + if (error instanceof Error) { + return error; + } + } + return null; + } + return createChainableTypeChecker(validate); + } + + function createElementTypeChecker() { + function validate(props, propName, componentName, location, propFullName) { + var propValue = props[propName]; + if (!isValidElement(propValue)) { + var propType = getPropType(propValue); + return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected a single ReactElement.')); + } + return null; + } + return createChainableTypeChecker(validate); + } + + function createInstanceTypeChecker(expectedClass) { + function validate(props, propName, componentName, location, propFullName) { + if (!(props[propName] instanceof expectedClass)) { + var expectedClassName = expectedClass.name || ANONYMOUS; + var actualClassName = getClassName(props[propName]); + return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + actualClassName + '` supplied to `' + componentName + '`, expected ') + ('instance of `' + expectedClassName + '`.')); + } + return null; + } + return createChainableTypeChecker(validate); + } + + function createEnumTypeChecker(expectedValues) { + if (!Array.isArray(expectedValues)) { + process.env.NODE_ENV !== 'production' ? warning(false, 'Invalid argument supplied to oneOf, expected an instance of array.') : void 0; + return emptyFunction.thatReturnsNull; + } + + function validate(props, propName, componentName, location, propFullName) { + var propValue = props[propName]; + for (var i = 0; i < expectedValues.length; i++) { + if (is(propValue, expectedValues[i])) { + return null; + } + } + + var valuesString = JSON.stringify(expectedValues); + return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of value `' + propValue + '` ' + ('supplied to `' + componentName + '`, expected one of ' + valuesString + '.')); + } + return createChainableTypeChecker(validate); + } + + function createObjectOfTypeChecker(typeChecker) { + function validate(props, propName, componentName, location, propFullName) { + if (typeof typeChecker !== 'function') { + return new PropTypeError('Property `' + propFullName + '` of component `' + componentName + '` has invalid PropType notation inside objectOf.'); + } + var propValue = props[propName]; + var propType = getPropType(propValue); + if (propType !== 'object') { + return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type ' + ('`' + propType + '` supplied to `' + componentName + '`, expected an object.')); + } + for (var key in propValue) { + if (propValue.hasOwnProperty(key)) { + var error = typeChecker(propValue, key, componentName, location, propFullName + '.' + key, ReactPropTypesSecret); + if (error instanceof Error) { + return error; + } + } + } + return null; + } + return createChainableTypeChecker(validate); + } + + function createUnionTypeChecker(arrayOfTypeCheckers) { + if (!Array.isArray(arrayOfTypeCheckers)) { + process.env.NODE_ENV !== 'production' ? warning(false, 'Invalid argument supplied to oneOfType, expected an instance of array.') : void 0; + return emptyFunction.thatReturnsNull; + } + + function validate(props, propName, componentName, location, propFullName) { + for (var i = 0; i < arrayOfTypeCheckers.length; i++) { + var checker = arrayOfTypeCheckers[i]; + if (checker(props, propName, componentName, location, propFullName, ReactPropTypesSecret) == null) { + return null; + } + } + + return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` supplied to ' + ('`' + componentName + '`.')); + } + return createChainableTypeChecker(validate); + } + + function createNodeChecker() { + function validate(props, propName, componentName, location, propFullName) { + if (!isNode(props[propName])) { + return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` supplied to ' + ('`' + componentName + '`, expected a ReactNode.')); + } + return null; + } + return createChainableTypeChecker(validate); + } + + function createShapeTypeChecker(shapeTypes) { + function validate(props, propName, componentName, location, propFullName) { + var propValue = props[propName]; + var propType = getPropType(propValue); + if (propType !== 'object') { + return new PropTypeError('Invalid ' + location + ' `' + propFullName + '` of type `' + propType + '` ' + ('supplied to `' + componentName + '`, expected `object`.')); + } + for (var key in shapeTypes) { + var checker = shapeTypes[key]; + if (!checker) { + continue; + } + var error = checker(propValue, key, componentName, location, propFullName + '.' + key, ReactPropTypesSecret); + if (error) { + return error; + } + } + return null; + } + return createChainableTypeChecker(validate); + } + + function isNode(propValue) { + switch (typeof propValue) { + case 'number': + case 'string': + case 'undefined': + return true; + case 'boolean': + return !propValue; + case 'object': + if (Array.isArray(propValue)) { + return propValue.every(isNode); + } + if (propValue === null || isValidElement(propValue)) { + return true; + } + + var iteratorFn = getIteratorFn(propValue); + if (iteratorFn) { + var iterator = iteratorFn.call(propValue); + var step; + if (iteratorFn !== propValue.entries) { + while (!(step = iterator.next()).done) { + if (!isNode(step.value)) { + return false; + } + } + } else { + // Iterator will provide entry [k,v] tuples rather than values. + while (!(step = iterator.next()).done) { + var entry = step.value; + if (entry) { + if (!isNode(entry[1])) { + return false; + } + } + } + } + } else { + return false; + } + + return true; + default: + return false; + } + } + + function isSymbol(propType, propValue) { + // Native Symbol. + if (propType === 'symbol') { + return true; + } + + // 19.4.3.5 Symbol.prototype[@@toStringTag] === 'Symbol' + if (propValue['@@toStringTag'] === 'Symbol') { + return true; + } + + // Fallback for non-spec compliant Symbols which are polyfilled. + if (typeof Symbol === 'function' && propValue instanceof Symbol) { + return true; + } + + return false; + } + + // Equivalent of `typeof` but with special handling for array and regexp. + function getPropType(propValue) { + var propType = typeof propValue; + if (Array.isArray(propValue)) { + return 'array'; + } + if (propValue instanceof RegExp) { + // Old webkits (at least until Android 4.0) return 'function' rather than + // 'object' for typeof a RegExp. We'll normalize this here so that /bla/ + // passes PropTypes.object. + return 'object'; + } + if (isSymbol(propType, propValue)) { + return 'symbol'; + } + return propType; + } + + // This handles more types than `getPropType`. Only used for error messages. + // See `createPrimitiveTypeChecker`. + function getPreciseType(propValue) { + var propType = getPropType(propValue); + if (propType === 'object') { + if (propValue instanceof Date) { + return 'date'; + } else if (propValue instanceof RegExp) { + return 'regexp'; + } + } + return propType; + } + + // Returns class name of the object, if any. + function getClassName(propValue) { + if (!propValue.constructor || !propValue.constructor.name) { + return ANONYMOUS; + } + return propValue.constructor.name; + } + + ReactPropTypes.checkPropTypes = checkPropTypes; + ReactPropTypes.PropTypes = ReactPropTypes; + + return ReactPropTypes; +}; diff --git a/index.js b/index.js index e4b9ab3..ce98276 100644 --- a/index.js +++ b/index.js @@ -7,17 +7,22 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -var factory = require('./factory'); +if (process.env.NODE_ENV !== 'production') { + var REACT_ELEMENT_TYPE = (typeof Symbol === 'function' && + Symbol.for && + Symbol.for('react.element')) || + 0xeac7; -var REACT_ELEMENT_TYPE = (typeof Symbol === 'function' && - Symbol.for && - Symbol.for('react.element')) || - 0xeac7; + var isValidElement = function(object) { + return typeof object === 'object' && + object !== null && + object.$$typeof === REACT_ELEMENT_TYPE; + }; -function isValidElement(object) { - return typeof object === 'object' && - object !== null && - object.$$typeof === REACT_ELEMENT_TYPE; + module.exports = require('./factoryWithTypeCheckers')(isValidElement); +} else { + // By explicitly using `prop-types` you are opting into new production behavior. + // https://github.com/reactjs/prop-types#development-and-production-versions + module.exports = require('./factoryWithThrowingShims')(); } -module.exports = factory(isValidElement); diff --git a/package.json b/package.json index 8a4904d..c7da1d4 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,8 @@ "README.md", "checkPropTypes.js", "factory.js", + "factoryWithThrowingShims.js", + "factoryWithTypeCheckers.js", "index.js", "prop-types.js", "prop-types.min.js", From 38ba18a4a8f705f4b2b33c88204573ddd604f2d6 Mon Sep 17 00:00:00 2001 From: Brandon Dail Date: Mon, 10 Apr 2017 19:24:45 -0500 Subject: [PATCH 03/12] Use bundle-collapser/plugin for production build --- package.json | 3 +- yarn.lock | 94 ++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 90 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index c7da1d4..c4e097a 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "scripts": { "test": "jest", "umd": "NODE_ENV=development browserify index.js -t envify --standalone PropTypes -o prop-types.js", - "umd-min": "NODE_ENV=production browserify index.js -t envify --standalone PropTypes -o | uglifyjs --compress unused,dead_code -o prop-types.min.js", + "umd-min": "NODE_ENV=production browserify index.js -t envify --standalone PropTypes -p bundle-collapser/plugin -o | uglifyjs --compress unused,dead_code -o prop-types.min.js", "build": "yarn umd && yarn umd-min", "prepublish": "yarn build" }, @@ -39,6 +39,7 @@ "babel-jest": "^19.0.0", "babel-preset-react": "^6.24.1", "browserify": "^14.3.0", + "bundle-collapser": "^1.2.1", "envify": "^4.0.0", "jest": "^19.0.2", "react": "^15.5.1", diff --git a/yarn.lock b/yarn.lock index 20b2408..9368646 100644 --- a/yarn.lock +++ b/yarn.lock @@ -19,6 +19,10 @@ acorn-globals@^3.1.0: dependencies: acorn "^4.0.4" +acorn@^1.0.3: + version "1.2.2" + resolved "http://npme.walmart.com/acorn/-/acorn-1.2.2.tgz#c8ce27de0acc76d896d2b1fad3df588d9e82f014" + acorn@^4.0.3, acorn@^4.0.4: version "4.0.11" resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.11.tgz#edcda3bd937e7556410d42ed5860f67399c794c0" @@ -426,6 +430,16 @@ brorand@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" +browser-pack@^5.0.1: + version "5.0.1" + resolved "http://npme.walmart.com/browser-pack/-/browser-pack-5.0.1.tgz#4197719b20c6e0aaa09451c5111e53efb6fbc18d" + dependencies: + JSONStream "^1.0.3" + combine-source-map "~0.6.1" + defined "^1.0.0" + through2 "^1.0.0" + umd "^3.0.0" + browser-pack@^6.0.1: version "6.0.2" resolved "https://registry.yarnpkg.com/browser-pack/-/browser-pack-6.0.2.tgz#f86cd6cef4f5300c8e63e07a4d512f65fbff4531" @@ -442,6 +456,15 @@ browser-resolve@^1.11.0, browser-resolve@^1.11.2, browser-resolve@^1.7.0: dependencies: resolve "1.1.7" +browser-unpack@^1.1.0: + version "1.2.0" + resolved "http://npme.walmart.com/browser-unpack/-/browser-unpack-1.2.0.tgz#357aee31fc467831684d063e4355e070a782970d" + dependencies: + acorn "^4.0.3" + browser-pack "^5.0.1" + concat-stream "^1.5.0" + minimist "^1.1.1" + browserify-aes@^1.0.0, browserify-aes@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.0.6.tgz#5e7725dbdef1fd5930d4ebab48567ce451c48a0a" @@ -580,6 +603,17 @@ builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" +bundle-collapser@^1.2.1: + version "1.2.1" + resolved "http://npme.walmart.com/bundle-collapser/-/bundle-collapser-1.2.1.tgz#e119afc92638e440b9085f47ae081fa756cba33d" + dependencies: + browser-pack "^5.0.1" + browser-unpack "^1.1.0" + concat-stream "^1.5.0" + falafel "^1.2.0" + minimist "^1.1.1" + through2 "^2.0.0" + cached-path-relative@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/cached-path-relative/-/cached-path-relative-1.0.1.tgz#d09c4b52800aa4c078e2dd81a869aac90d2e54e7" @@ -661,6 +695,15 @@ color-name@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.2.tgz#5c8ab72b64bd2215d617ae9559ebb148475cf98d" +combine-source-map@~0.6.1: + version "0.6.1" + resolved "http://npme.walmart.com/combine-source-map/-/combine-source-map-0.6.1.tgz#9b4a09c316033d768e0f11e029fa2730e079ad96" + dependencies: + convert-source-map "~1.1.0" + inline-source-map "~0.5.0" + lodash.memoize "~3.0.3" + source-map "~0.4.2" + combine-source-map@~0.7.1: version "0.7.2" resolved "https://registry.yarnpkg.com/combine-source-map/-/combine-source-map-0.7.2.tgz#0870312856b307a87cc4ac486f3a9a62aeccc09e" @@ -680,7 +723,7 @@ concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" -concat-stream@~1.5.0, concat-stream@~1.5.1: +concat-stream@^1.5.0, concat-stream@~1.5.0, concat-stream@~1.5.1: version "1.5.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.5.2.tgz#708978624d856af41a5a741defdd261da752c266" dependencies: @@ -981,6 +1024,15 @@ extsprintf@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550" +falafel@^1.2.0: + version "1.2.0" + resolved "http://npme.walmart.com/falafel/-/falafel-1.2.0.tgz#c18d24ef5091174a497f318cd24b026a25cddab4" + dependencies: + acorn "^1.0.3" + foreach "^2.0.5" + isarray "0.0.1" + object-keys "^1.0.6" + fast-levenshtein@~2.0.4: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" @@ -1053,6 +1105,10 @@ for-own@^0.1.4: dependencies: for-in "^1.0.1" +foreach@^2.0.5: + version "2.0.5" + resolved "http://npme.walmart.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -1247,6 +1303,12 @@ inherits@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" +inline-source-map@~0.5.0: + version "0.5.0" + resolved "http://npme.walmart.com/inline-source-map/-/inline-source-map-0.5.0.tgz#4a4c5dd8e4fb5e9b3cda60c822dfadcaee66e0af" + dependencies: + source-map "~0.4.0" + inline-source-map@~0.6.0: version "0.6.2" resolved "https://registry.yarnpkg.com/inline-source-map/-/inline-source-map-0.6.2.tgz#f9393471c18a79d1724f863fa38b586370ade2a5" @@ -1358,14 +1420,14 @@ is-utf8@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" +isarray@0.0.1, isarray@~0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + isarray@1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" -isarray@~0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" @@ -1969,6 +2031,10 @@ object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" +object-keys@^1.0.6: + version "1.0.11" + resolved "http://npme.walmart.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" + object.omit@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" @@ -2238,6 +2304,15 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" +"readable-stream@>=1.1.13-1 <1.2.0-0": + version "1.1.14" + resolved "http://npme.walmart.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "0.0.1" + string_decoder "~0.10.x" + readable-stream@^2.0.2, readable-stream@^2.1.5, readable-stream@^2.2.6: version "2.2.8" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.8.tgz#ad28b686f3554c73d39bc32347fa058356624705" @@ -2431,7 +2506,7 @@ source-map@0.1.34: dependencies: amdefine ">=0.0.4" -source-map@^0.4.4: +source-map@^0.4.4, source-map@~0.4.0, source-map@~0.4.2: version "0.4.4" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" dependencies: @@ -2595,6 +2670,13 @@ throat@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/throat/-/throat-3.0.0.tgz#e7c64c867cbb3845f10877642f7b60055b8ec0d6" +through2@^1.0.0: + version "1.1.1" + resolved "http://npme.walmart.com/through2/-/through2-1.1.1.tgz#0847cbc4449f3405574dbdccd9bb841b83ac3545" + dependencies: + readable-stream ">=1.1.13-1 <1.2.0-0" + xtend ">=4.0.0 <4.1.0-0" + through2@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" From 7882a7285293db5f284bcf559b869fd2cd4c44d4 Mon Sep 17 00:00:00 2001 From: Brandon Dail Date: Mon, 10 Apr 2017 19:43:52 -0500 Subject: [PATCH 04/12] Use uglifyify to remove conditional invariant require --- package.json | 3 ++- yarn.lock | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index c4e097a..10388a9 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "scripts": { "test": "jest", "umd": "NODE_ENV=development browserify index.js -t envify --standalone PropTypes -o prop-types.js", - "umd-min": "NODE_ENV=production browserify index.js -t envify --standalone PropTypes -p bundle-collapser/plugin -o | uglifyjs --compress unused,dead_code -o prop-types.min.js", + "umd-min": "NODE_ENV=production browserify index.js -t envify -t uglifyify --standalone PropTypes -p bundle-collapser/plugin -o | uglifyjs --compress unused,dead_code -o prop-types.min.js", "build": "yarn umd && yarn umd-min", "prepublish": "yarn build" }, @@ -43,6 +43,7 @@ "envify": "^4.0.0", "jest": "^19.0.2", "react": "^15.5.1", + "uglifyify": "^3.0.4", "uglifyjs": "^2.4.10" } } diff --git a/yarn.lock b/yarn.lock index 9368646..a403d35 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1010,6 +1010,10 @@ expand-range@^1.8.1: dependencies: fill-range "^2.1.0" +extend@^1.2.1: + version "1.3.0" + resolved "http://npme.walmart.com/extend/-/extend-1.3.0.tgz#d1516fb0ff5624d2ebf9123ea1dac5a1994004f8" + extend@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.0.tgz#5a474353b9f3353ddd8176dfd37b91c83a46f1d4" @@ -2748,7 +2752,7 @@ ua-parser-js@^0.7.9: version "0.7.12" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.12.tgz#04c81a99bdd5dc52263ea29d24c6bf8d4818a4bb" -uglify-js@^2.6: +uglify-js@2.x.x, uglify-js@^2.6: version "2.8.21" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.21.tgz#1733f669ae6f82fc90c7b25ec0f5c783ee375314" dependencies: @@ -2761,6 +2765,16 @@ uglify-to-browserify@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" +uglifyify@^3.0.4: + version "3.0.4" + resolved "http://npme.walmart.com/uglifyify/-/uglifyify-3.0.4.tgz#487e080a5a7798880e68e90def9b06681fb13bd2" + dependencies: + convert-source-map "~1.1.0" + extend "^1.2.1" + minimatch "^3.0.2" + through "~2.3.4" + uglify-js "2.x.x" + uglifyjs@^2.4.10: version "2.4.10" resolved "https://registry.yarnpkg.com/uglifyjs/-/uglifyjs-2.4.10.tgz#632927319fa6a3da3fc91f9773ac27bfe6c3ee92" From 52323c67f669b0b85e05e79486c1ec732595b64f Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Tue, 11 Apr 2017 17:29:12 +0100 Subject: [PATCH 05/12] Update lockfile --- yarn.lock | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/yarn.lock b/yarn.lock index a403d35..0f11ded 100644 --- a/yarn.lock +++ b/yarn.lock @@ -745,11 +745,7 @@ content-type-parser@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/content-type-parser/-/content-type-parser-1.0.1.tgz#c3e56988c53c65127fb46d4032a3a900246fdc94" -convert-source-map@^1.1.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5" - -convert-source-map@~1.1.0: +convert-source-map@^1.1.0, convert-source-map@~1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.1.3.tgz#4829c877e9fe49b3161f3bf3673888e204699860" @@ -2877,14 +2873,10 @@ window-size@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" -wordwrap@0.0.2: +wordwrap@0.0.2, wordwrap@~0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" -wordwrap@~0.0.2: - version "0.0.3" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" - wordwrap@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" From f0baf01e71626ed0f027b43e1e114eec30af20be Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Tue, 11 Apr 2017 17:30:20 +0100 Subject: [PATCH 06/12] Remove extra whitespace --- index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/index.js b/index.js index ce98276..b7f1c6d 100644 --- a/index.js +++ b/index.js @@ -25,4 +25,3 @@ if (process.env.NODE_ENV !== 'production') { // https://github.com/reactjs/prop-types#development-and-production-versions module.exports = require('./factoryWithThrowingShims')(); } - From 86b763fd2066f83196df4b948d84617ff3ed2830 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Tue, 11 Apr 2017 17:36:26 +0100 Subject: [PATCH 07/12] Add separate tests for Standalone and React15 Development env --- __tests__/PropTypesDevelopmentReact15.js | 1056 +++++++++++++++++ ...=> PropTypesDevelopmentStandalone-test.js} | 2 +- 2 files changed, 1057 insertions(+), 1 deletion(-) create mode 100644 __tests__/PropTypesDevelopmentReact15.js rename __tests__/{PropTypesDevelopment-test.js => PropTypesDevelopmentStandalone-test.js} (99%) diff --git a/__tests__/PropTypesDevelopmentReact15.js b/__tests__/PropTypesDevelopmentReact15.js new file mode 100644 index 0000000..de801c1 --- /dev/null +++ b/__tests__/PropTypesDevelopmentReact15.js @@ -0,0 +1,1056 @@ +/** + * Copyright 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @emails react-core + */ + +'use strict'; + +var React; +var PropTypes; + +function resetWarningCache() { + jest.resetModules(); + + React = require('react'); + // This is how React 15 imports `prop-types`. + PropTypes = require('../factory')(React.isValidElement); +} + +function getPropTypeWarningMessage(propTypes, object, componentName) { + if (!console.error.calls) { + spyOn(console, 'error'); + } else { + console.error.calls.reset(); + } + resetWarningCache(); + + PropTypes.checkPropTypes(propTypes, object, 'prop', 'testComponent'); + const callCount = console.error.calls.count(); + if (callCount > 1) { + throw new Error('Too many warnings.'); + } + const message = console.error.calls.argsFor(0)[0] || null; + console.error.calls.reset(); + + return message; +} + +function typeCheckFail(declaration, value, expectedMessage) { + const propTypes = { + testProp: declaration, + }; + const props = { + testProp: value, + }; + const message = getPropTypeWarningMessage(propTypes, props, 'testComponent'); + expect(message).toContain(expectedMessage); +} + +function typeCheckFailRequiredValues(declaration) { + var specifiedButIsNullMsg = 'The prop `testProp` is marked as required in ' + + '`testComponent`, but its value is `null`.'; + var unspecifiedMsg = 'The prop `testProp` is marked as required in ' + + '`testComponent`, but its value is \`undefined\`.'; + + var propTypes = {testProp: declaration}; + + // Required prop is null + var message1 = getPropTypeWarningMessage( + propTypes, + {testProp: null}, + 'testComponent', + ); + expect(message1).toContain(specifiedButIsNullMsg); + + // Required prop is undefined + var message2 = getPropTypeWarningMessage( + propTypes, + {testProp: undefined}, + 'testComponent', + ); + expect(message2).toContain(unspecifiedMsg); + + // Required prop is not a member of props object + var message3 = getPropTypeWarningMessage(propTypes, {}, 'testComponent'); + expect(message3).toContain(unspecifiedMsg); +} + +function typeCheckPass(declaration, value) { + const propTypes = { + testProp: declaration, + }; + const props = { + testProp: value, + }; + const message = getPropTypeWarningMessage(propTypes, props, 'testComponent'); + expect(message).toBe(null); +} + +function expectWarningInDevelopment(declaration, value) { + var props = {testProp: value}; + var propName = 'testProp' + Math.random().toString(); + var componentName = 'testComponent' + Math.random().toString(); + for (var i = 0; i < 3; i++) { + declaration(props, propName, componentName, 'prop'); + } + expect(console.error.calls.count()).toBe(1); + expect(console.error.calls.argsFor(0)[0]).toContain( + 'You are manually calling a React.PropTypes validation ', + ); + console.error.calls.reset(); +} + +describe('PropTypesDevelopmentReact15', () => { + beforeEach(() => { + resetWarningCache(); + }); + + describe('checkPropTypes', () => { + it('does not return a value from a validator', () => { + spyOn(console, 'error'); + const propTypes = { + foo(props, propName, componentName) { + return new Error('some error'); + }, + }; + const props = {foo: 'foo'}; + const returnValue = PropTypes.checkPropTypes( + propTypes, + props, + 'prop', + 'testComponent', + null, + ); + expect(console.error.calls.argsFor(0)[0]).toContain('some error'); + expect(returnValue).toBe(undefined); + }); + + it('does not throw if validator throws', () => { + spyOn(console, 'error'); + const propTypes = { + foo(props, propName, componentName) { + throw new Error('some error'); + }, + }; + const props = {foo: 'foo'}; + const returnValue = PropTypes.checkPropTypes( + propTypes, + props, + 'prop', + 'testComponent', + null, + ); + expect(console.error.calls.argsFor(0)[0]).toContain('some error'); + expect(returnValue).toBe(undefined); + }); + }); + + describe('Primitive Types', () => { + it('should warn for invalid strings', () => { + typeCheckFail( + PropTypes.string, + [], + 'Invalid prop `testProp` of type `array` supplied to ' + + '`testComponent`, expected `string`.', + ); + typeCheckFail( + PropTypes.string, + false, + 'Invalid prop `testProp` of type `boolean` supplied to ' + + '`testComponent`, expected `string`.', + ); + typeCheckFail( + PropTypes.string, + 0, + 'Invalid prop `testProp` of type `number` supplied to ' + + '`testComponent`, expected `string`.', + ); + typeCheckFail( + PropTypes.string, + {}, + 'Invalid prop `testProp` of type `object` supplied to ' + + '`testComponent`, expected `string`.', + ); + typeCheckFail( + PropTypes.string, + Symbol(), + 'Invalid prop `testProp` of type `symbol` supplied to ' + + '`testComponent`, expected `string`.', + ); + }); + + it('should fail date and regexp correctly', () => { + typeCheckFail( + PropTypes.string, + new Date(), + 'Invalid prop `testProp` of type `date` supplied to ' + + '`testComponent`, expected `string`.', + ); + typeCheckFail( + PropTypes.string, + /please/, + 'Invalid prop `testProp` of type `regexp` supplied to ' + + '`testComponent`, expected `string`.', + ); + }); + + it('should not warn for valid values', () => { + typeCheckPass(PropTypes.array, []); + typeCheckPass(PropTypes.bool, false); + typeCheckPass(PropTypes.func, function() {}); + typeCheckPass(PropTypes.number, 0); + typeCheckPass(PropTypes.string, ''); + typeCheckPass(PropTypes.object, {}); + typeCheckPass(PropTypes.object, new Date()); + typeCheckPass(PropTypes.object, /please/); + typeCheckPass(PropTypes.symbol, Symbol()); + }); + + it('should be implicitly optional and not warn without values', () => { + typeCheckPass(PropTypes.string, null); + typeCheckPass(PropTypes.string, undefined); + }); + + it('should warn for missing required values', () => { + typeCheckFailRequiredValues(PropTypes.string.isRequired); + }); + + it('should warn if called manually in development', () => { + spyOn(console, 'error'); + expectWarningInDevelopment(PropTypes.array, /please/); + expectWarningInDevelopment(PropTypes.array, []); + expectWarningInDevelopment(PropTypes.array.isRequired, /please/); + expectWarningInDevelopment(PropTypes.array.isRequired, []); + expectWarningInDevelopment(PropTypes.array.isRequired, null); + expectWarningInDevelopment(PropTypes.array.isRequired, undefined); + expectWarningInDevelopment(PropTypes.bool, []); + expectWarningInDevelopment(PropTypes.bool, true); + expectWarningInDevelopment(PropTypes.bool.isRequired, []); + expectWarningInDevelopment(PropTypes.bool.isRequired, true); + expectWarningInDevelopment(PropTypes.bool.isRequired, null); + expectWarningInDevelopment(PropTypes.bool.isRequired, undefined); + expectWarningInDevelopment(PropTypes.func, false); + expectWarningInDevelopment(PropTypes.func, function() {}); + expectWarningInDevelopment(PropTypes.func.isRequired, false); + expectWarningInDevelopment(PropTypes.func.isRequired, function() {}); + expectWarningInDevelopment(PropTypes.func.isRequired, null); + expectWarningInDevelopment(PropTypes.func.isRequired, undefined); + expectWarningInDevelopment(PropTypes.number, function() {}); + expectWarningInDevelopment(PropTypes.number, 42); + expectWarningInDevelopment(PropTypes.number.isRequired, function() {}); + expectWarningInDevelopment(PropTypes.number.isRequired, 42); + expectWarningInDevelopment(PropTypes.number.isRequired, null); + expectWarningInDevelopment(PropTypes.number.isRequired, undefined); + expectWarningInDevelopment(PropTypes.string, 0); + expectWarningInDevelopment(PropTypes.string, 'foo'); + expectWarningInDevelopment(PropTypes.string.isRequired, 0); + expectWarningInDevelopment(PropTypes.string.isRequired, 'foo'); + expectWarningInDevelopment(PropTypes.string.isRequired, null); + expectWarningInDevelopment(PropTypes.string.isRequired, undefined); + expectWarningInDevelopment(PropTypes.symbol, 0); + expectWarningInDevelopment(PropTypes.symbol, Symbol('Foo')); + expectWarningInDevelopment(PropTypes.symbol.isRequired, 0); + expectWarningInDevelopment(PropTypes.symbol.isRequired, Symbol('Foo')); + expectWarningInDevelopment(PropTypes.symbol.isRequired, null); + expectWarningInDevelopment(PropTypes.symbol.isRequired, undefined); + expectWarningInDevelopment(PropTypes.object, ''); + expectWarningInDevelopment(PropTypes.object, {foo: 'bar'}); + expectWarningInDevelopment(PropTypes.object.isRequired, ''); + expectWarningInDevelopment(PropTypes.object.isRequired, {foo: 'bar'}); + expectWarningInDevelopment(PropTypes.object.isRequired, null); + expectWarningInDevelopment(PropTypes.object.isRequired, undefined); + }); + }); + + describe('Any type', () => { + it('should should accept any value', () => { + typeCheckPass(PropTypes.any, 0); + typeCheckPass(PropTypes.any, 'str'); + typeCheckPass(PropTypes.any, []); + typeCheckPass(PropTypes.any, Symbol()); + }); + + it('should be implicitly optional and not warn without values', () => { + typeCheckPass(PropTypes.any, null); + typeCheckPass(PropTypes.any, undefined); + }); + + it('should warn for missing required values', () => { + typeCheckFailRequiredValues(PropTypes.any.isRequired); + }); + + it('should warn if called manually in development', () => { + spyOn(console, 'error'); + expectWarningInDevelopment(PropTypes.any, null); + expectWarningInDevelopment(PropTypes.any.isRequired, null); + expectWarningInDevelopment(PropTypes.any.isRequired, undefined); + }); + }); + + describe('ArrayOf Type', () => { + it('should fail for invalid argument', () => { + typeCheckFail( + PropTypes.arrayOf({foo: PropTypes.string}), + {foo: 'bar'}, + 'Property `testProp` of component `testComponent` has invalid PropType notation inside arrayOf.', + ); + }); + + it('should support the arrayOf propTypes', () => { + typeCheckPass(PropTypes.arrayOf(PropTypes.number), [1, 2, 3]); + typeCheckPass(PropTypes.arrayOf(PropTypes.string), ['a', 'b', 'c']); + typeCheckPass(PropTypes.arrayOf(PropTypes.oneOf(['a', 'b'])), ['a', 'b']); + typeCheckPass(PropTypes.arrayOf(PropTypes.symbol), [Symbol(), Symbol()]); + }); + + it('should support arrayOf with complex types', () => { + typeCheckPass( + PropTypes.arrayOf(PropTypes.shape({a: PropTypes.number.isRequired})), + [{a: 1}, {a: 2}], + ); + + function Thing() {} + typeCheckPass(PropTypes.arrayOf(PropTypes.instanceOf(Thing)), [ + new Thing(), + new Thing(), + ]); + }); + + it('should warn with invalid items in the array', () => { + typeCheckFail( + PropTypes.arrayOf(PropTypes.number), + [1, 2, 'b'], + 'Invalid prop `testProp[2]` of type `string` supplied to ' + + '`testComponent`, expected `number`.', + ); + }); + + it('should warn with invalid complex types', () => { + function Thing() {} + var name = Thing.name || '<>'; + + typeCheckFail( + PropTypes.arrayOf(PropTypes.instanceOf(Thing)), + [new Thing(), 'xyz'], + 'Invalid prop `testProp[1]` of type `String` supplied to ' + + '`testComponent`, expected instance of `' + + name + + '`.', + ); + }); + + it('should warn when passed something other than an array', () => { + typeCheckFail( + PropTypes.arrayOf(PropTypes.number), + {'0': 'maybe-array', length: 1}, + 'Invalid prop `testProp` of type `object` supplied to ' + + '`testComponent`, expected an array.', + ); + typeCheckFail( + PropTypes.arrayOf(PropTypes.number), + 123, + 'Invalid prop `testProp` of type `number` supplied to ' + + '`testComponent`, expected an array.', + ); + typeCheckFail( + PropTypes.arrayOf(PropTypes.number), + 'string', + 'Invalid prop `testProp` of type `string` supplied to ' + + '`testComponent`, expected an array.', + ); + }); + + it('should not warn when passing an empty array', () => { + typeCheckPass(PropTypes.arrayOf(PropTypes.number), []); + }); + + it('should be implicitly optional and not warn without values', () => { + typeCheckPass(PropTypes.arrayOf(PropTypes.number), null); + typeCheckPass(PropTypes.arrayOf(PropTypes.number), undefined); + }); + + it('should warn for missing required values', () => { + typeCheckFailRequiredValues( + PropTypes.arrayOf(PropTypes.number).isRequired, + ); + }); + + it('should warn if called manually in development', () => { + spyOn(console, 'error'); + expectWarningInDevelopment(PropTypes.arrayOf({foo: PropTypes.string}), { + foo: 'bar', + }); + expectWarningInDevelopment(PropTypes.arrayOf(PropTypes.number), [ + 1, + 2, + 'b', + ]); + expectWarningInDevelopment(PropTypes.arrayOf(PropTypes.number), { + '0': 'maybe-array', + length: 1, + }); + expectWarningInDevelopment( + PropTypes.arrayOf(PropTypes.number).isRequired, + null, + ); + expectWarningInDevelopment( + PropTypes.arrayOf(PropTypes.number).isRequired, + undefined, + ); + }); + }); + + describe('Component Type', () => { + + it('should support components', () => { + typeCheckPass(PropTypes.element,
); + }); + + it('should not support multiple components or scalar values', () => { + typeCheckFail( + PropTypes.element, + [
,
], + 'Invalid prop `testProp` of type `array` supplied to `testComponent`, ' + + 'expected a single ReactElement.', + ); + typeCheckFail( + PropTypes.element, + 123, + 'Invalid prop `testProp` of type `number` supplied to `testComponent`, ' + + 'expected a single ReactElement.', + ); + typeCheckFail( + PropTypes.element, + 'foo', + 'Invalid prop `testProp` of type `string` supplied to `testComponent`, ' + + 'expected a single ReactElement.', + ); + typeCheckFail( + PropTypes.element, + false, + 'Invalid prop `testProp` of type `boolean` supplied to `testComponent`, ' + + 'expected a single ReactElement.', + ); + }); + + it('should be implicitly optional and not warn without values', () => { + typeCheckPass(PropTypes.element, null); + typeCheckPass(PropTypes.element, undefined); + }); + + it('should warn for missing required values', () => { + typeCheckFailRequiredValues(PropTypes.element.isRequired); + }); + + it('should warn if called manually in development', () => { + spyOn(console, 'error'); + expectWarningInDevelopment(PropTypes.element, [
,
]); + expectWarningInDevelopment(PropTypes.element,
); + expectWarningInDevelopment(PropTypes.element, 123); + expectWarningInDevelopment(PropTypes.element, 'foo'); + expectWarningInDevelopment(PropTypes.element, false); + expectWarningInDevelopment(PropTypes.element.isRequired, null); + expectWarningInDevelopment(PropTypes.element.isRequired, undefined); + }); + }); + + describe('Instance Types', () => { + it('should warn for invalid instances', () => { + function Person() {} + function Cat() {} + var personName = Person.name || '<>'; + var dateName = Date.name || '<>'; + var regExpName = RegExp.name || '<>'; + + typeCheckFail( + PropTypes.instanceOf(Person), + false, + 'Invalid prop `testProp` of type `Boolean` supplied to ' + + '`testComponent`, expected instance of `' + + personName + + '`.', + ); + typeCheckFail( + PropTypes.instanceOf(Person), + {}, + 'Invalid prop `testProp` of type `Object` supplied to ' + + '`testComponent`, expected instance of `' + + personName + + '`.', + ); + typeCheckFail( + PropTypes.instanceOf(Person), + '', + 'Invalid prop `testProp` of type `String` supplied to ' + + '`testComponent`, expected instance of `' + + personName + + '`.', + ); + typeCheckFail( + PropTypes.instanceOf(Date), + {}, + 'Invalid prop `testProp` of type `Object` supplied to ' + + '`testComponent`, expected instance of `' + + dateName + + '`.', + ); + typeCheckFail( + PropTypes.instanceOf(RegExp), + {}, + 'Invalid prop `testProp` of type `Object` supplied to ' + + '`testComponent`, expected instance of `' + + regExpName + + '`.', + ); + typeCheckFail( + PropTypes.instanceOf(Person), + new Cat(), + 'Invalid prop `testProp` of type `Cat` supplied to ' + + '`testComponent`, expected instance of `' + + personName + + '`.', + ); + typeCheckFail( + PropTypes.instanceOf(Person), + Object.create(null), + 'Invalid prop `testProp` of type `<>` supplied to ' + + '`testComponent`, expected instance of `' + + personName + + '`.', + ); + }); + + it('should not warn for valid values', () => { + function Person() {} + function Engineer() {} + Engineer.prototype = new Person(); + + typeCheckPass(PropTypes.instanceOf(Person), new Person()); + typeCheckPass(PropTypes.instanceOf(Person), new Engineer()); + + typeCheckPass(PropTypes.instanceOf(Date), new Date()); + typeCheckPass(PropTypes.instanceOf(RegExp), /please/); + }); + + it('should be implicitly optional and not warn without values', () => { + typeCheckPass(PropTypes.instanceOf(String), null); + typeCheckPass(PropTypes.instanceOf(String), undefined); + }); + + it('should warn for missing required values', () => { + typeCheckFailRequiredValues(PropTypes.instanceOf(String).isRequired); + }); + + it('should warn if called manually in development', () => { + spyOn(console, 'error'); + expectWarningInDevelopment(PropTypes.instanceOf(Date), {}); + expectWarningInDevelopment(PropTypes.instanceOf(Date), new Date()); + expectWarningInDevelopment(PropTypes.instanceOf(Date).isRequired, {}); + expectWarningInDevelopment( + PropTypes.instanceOf(Date).isRequired, + new Date(), + ); + }); + }); + + describe('React Component Types', () => { + it('should warn for invalid values', () => { + var failMessage = 'Invalid prop `testProp` supplied to ' + + '`testComponent`, expected a ReactNode.'; + typeCheckFail(PropTypes.node, true, failMessage); + typeCheckFail(PropTypes.node, function() {}, failMessage); + typeCheckFail(PropTypes.node, {key: function() {}}, failMessage); + typeCheckFail(PropTypes.node, {key:
}, failMessage); + }); + + it('should not warn for valid values', () => { + function MyComponent() {} + MyComponent.prototype.render = function() { + return
; + }; + typeCheckPass(PropTypes.node,
); + typeCheckPass(PropTypes.node, false); + typeCheckPass(PropTypes.node, ); + typeCheckPass(PropTypes.node, 'Some string'); + typeCheckPass(PropTypes.node, []); + typeCheckPass(PropTypes.node, [ + 123, + 'Some string', +
, + ['Another string', [456], , ], + , + null, + undefined, + ]); + }); + + it('should not warn for iterables', () => { + function MyComponent() {} + MyComponent.prototype.render = function() { + return
; + }; + var iterable = { + '@@iterator': function() { + var i = 0; + return { + next: function() { + var done = ++i > 2; + return {value: done ? undefined : , done: done}; + }, + }; + }, + }; + + typeCheckPass(PropTypes.node, iterable); + }); + + it('should not warn for entry iterables', () => { + function MyComponent() {} + MyComponent.prototype.render = function() { + return
; + }; + var iterable = { + '@@iterator': function() { + var i = 0; + return { + next: function() { + var done = ++i > 2; + return { + value: done ? undefined : ['#' + i, ], + done: done, + }; + }, + }; + }, + }; + iterable.entries = iterable['@@iterator']; + + typeCheckPass(PropTypes.node, iterable); + }); + + it('should not warn for null/undefined if not required', () => { + typeCheckPass(PropTypes.node, null); + typeCheckPass(PropTypes.node, undefined); + }); + + it('should warn for missing required values', () => { + typeCheckFailRequiredValues(PropTypes.node.isRequired); + }); + + it('should accept empty array for required props', () => { + typeCheckPass(PropTypes.node.isRequired, []); + }); + + it('should warn if called manually in development', () => { + spyOn(console, 'error'); + expectWarningInDevelopment(PropTypes.node, 'node'); + expectWarningInDevelopment(PropTypes.node, {}); + expectWarningInDevelopment(PropTypes.node.isRequired, 'node'); + expectWarningInDevelopment(PropTypes.node.isRequired, undefined); + expectWarningInDevelopment(PropTypes.node.isRequired, undefined); + }); + }); + + describe('ObjectOf Type', () => { + it('should fail for invalid argument', () => { + typeCheckFail( + PropTypes.objectOf({foo: PropTypes.string}), + {foo: 'bar'}, + 'Property `testProp` of component `testComponent` has invalid PropType notation inside objectOf.', + ); + }); + + it('should support the objectOf propTypes', () => { + typeCheckPass(PropTypes.objectOf(PropTypes.number), {a: 1, b: 2, c: 3}); + typeCheckPass(PropTypes.objectOf(PropTypes.string), { + a: 'a', + b: 'b', + c: 'c', + }); + typeCheckPass(PropTypes.objectOf(PropTypes.oneOf(['a', 'b'])), { + a: 'a', + b: 'b', + }); + typeCheckPass(PropTypes.objectOf(PropTypes.symbol), { + a: Symbol(), + b: Symbol(), + c: Symbol(), + }); + }); + + it('should support objectOf with complex types', () => { + typeCheckPass( + PropTypes.objectOf(PropTypes.shape({a: PropTypes.number.isRequired})), + {a: {a: 1}, b: {a: 2}}, + ); + + function Thing() {} + typeCheckPass(PropTypes.objectOf(PropTypes.instanceOf(Thing)), { + a: new Thing(), + b: new Thing(), + }); + }); + + it('should warn with invalid items in the object', () => { + typeCheckFail( + PropTypes.objectOf(PropTypes.number), + {a: 1, b: 2, c: 'b'}, + 'Invalid prop `testProp.c` of type `string` supplied to `testComponent`, ' + + 'expected `number`.', + ); + }); + + it('should warn with invalid complex types', () => { + function Thing() {} + var name = Thing.name || '<>'; + + typeCheckFail( + PropTypes.objectOf(PropTypes.instanceOf(Thing)), + {a: new Thing(), b: 'xyz'}, + 'Invalid prop `testProp.b` of type `String` supplied to ' + + '`testComponent`, expected instance of `' + + name + + '`.', + ); + }); + + it('should warn when passed something other than an object', () => { + typeCheckFail( + PropTypes.objectOf(PropTypes.number), + [1, 2], + 'Invalid prop `testProp` of type `array` supplied to ' + + '`testComponent`, expected an object.', + ); + typeCheckFail( + PropTypes.objectOf(PropTypes.number), + 123, + 'Invalid prop `testProp` of type `number` supplied to ' + + '`testComponent`, expected an object.', + ); + typeCheckFail( + PropTypes.objectOf(PropTypes.number), + 'string', + 'Invalid prop `testProp` of type `string` supplied to ' + + '`testComponent`, expected an object.', + ); + typeCheckFail( + PropTypes.objectOf(PropTypes.symbol), + Symbol(), + 'Invalid prop `testProp` of type `symbol` supplied to ' + + '`testComponent`, expected an object.', + ); + }); + + it('should not warn when passing an empty object', () => { + typeCheckPass(PropTypes.objectOf(PropTypes.number), {}); + }); + + it('should be implicitly optional and not warn without values', () => { + typeCheckPass(PropTypes.objectOf(PropTypes.number), null); + typeCheckPass(PropTypes.objectOf(PropTypes.number), undefined); + }); + + it('should warn for missing required values', () => { + typeCheckFailRequiredValues( + PropTypes.objectOf(PropTypes.number).isRequired, + ); + }); + + it('should warn if called manually in development', () => { + spyOn(console, 'error'); + expectWarningInDevelopment(PropTypes.objectOf({foo: PropTypes.string}), { + foo: 'bar', + }); + expectWarningInDevelopment(PropTypes.objectOf(PropTypes.number), { + a: 1, + b: 2, + c: 'b', + }); + expectWarningInDevelopment(PropTypes.objectOf(PropTypes.number), [1, 2]); + expectWarningInDevelopment(PropTypes.objectOf(PropTypes.number), null); + expectWarningInDevelopment( + PropTypes.objectOf(PropTypes.number), + undefined, + ); + }); + }); + + describe('OneOf Types', () => { + it('should warn but not error for invalid argument', () => { + spyOn(console, 'error'); + + PropTypes.oneOf('red', 'blue'); + + expect(console.error).toHaveBeenCalled(); + expect(console.error.calls.argsFor(0)[0]).toContain( + 'Invalid argument supplied to oneOf, expected an instance of array.', + ); + + typeCheckPass(PropTypes.oneOf('red', 'blue'), 'red'); + }); + + it('should warn for invalid values', () => { + typeCheckFail( + PropTypes.oneOf(['red', 'blue']), + true, + 'Invalid prop `testProp` of value `true` supplied to ' + + '`testComponent`, expected one of ["red","blue"].', + ); + typeCheckFail( + PropTypes.oneOf(['red', 'blue']), + [], + 'Invalid prop `testProp` of value `` supplied to `testComponent`, ' + + 'expected one of ["red","blue"].', + ); + typeCheckFail( + PropTypes.oneOf(['red', 'blue']), + '', + 'Invalid prop `testProp` of value `` supplied to `testComponent`, ' + + 'expected one of ["red","blue"].', + ); + typeCheckFail( + PropTypes.oneOf([0, 'false']), + false, + 'Invalid prop `testProp` of value `false` supplied to ' + + '`testComponent`, expected one of [0,"false"].', + ); + }); + + it('should not warn for valid values', () => { + typeCheckPass(PropTypes.oneOf(['red', 'blue']), 'red'); + typeCheckPass(PropTypes.oneOf(['red', 'blue']), 'blue'); + typeCheckPass(PropTypes.oneOf(['red', 'blue', NaN]), NaN); + }); + + it('should be implicitly optional and not warn without values', () => { + typeCheckPass(PropTypes.oneOf(['red', 'blue']), null); + typeCheckPass(PropTypes.oneOf(['red', 'blue']), undefined); + }); + + it('should warn for missing required values', () => { + typeCheckFailRequiredValues(PropTypes.oneOf(['red', 'blue']).isRequired); + }); + + it('should warn if called manually in development', () => { + spyOn(console, 'error'); + expectWarningInDevelopment(PropTypes.oneOf(['red', 'blue']), true); + expectWarningInDevelopment(PropTypes.oneOf(['red', 'blue']), null); + expectWarningInDevelopment(PropTypes.oneOf(['red', 'blue']), undefined); + }); + }); + + describe('Union Types', () => { + it('should warn but not error for invalid argument', () => { + spyOn(console, 'error'); + + PropTypes.oneOfType(PropTypes.string, PropTypes.number); + + expect(console.error).toHaveBeenCalled(); + expect(console.error.calls.argsFor(0)[0]).toContain( + 'Invalid argument supplied to oneOfType, expected an instance of array.', + ); + + typeCheckPass(PropTypes.oneOf(PropTypes.string, PropTypes.number), []); + }); + + it('should warn if none of the types are valid', () => { + typeCheckFail( + PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + [], + 'Invalid prop `testProp` supplied to `testComponent`.', + ); + + var checker = PropTypes.oneOfType([ + PropTypes.shape({a: PropTypes.number.isRequired}), + PropTypes.shape({b: PropTypes.number.isRequired}), + ]); + typeCheckFail( + checker, + {c: 1}, + 'Invalid prop `testProp` supplied to `testComponent`.', + ); + }); + + it('should not warn if one of the types are valid', () => { + var checker = PropTypes.oneOfType([PropTypes.string, PropTypes.number]); + typeCheckPass(checker, null); + typeCheckPass(checker, 'foo'); + typeCheckPass(checker, 123); + + checker = PropTypes.oneOfType([ + PropTypes.shape({a: PropTypes.number.isRequired}), + PropTypes.shape({b: PropTypes.number.isRequired}), + ]); + typeCheckPass(checker, {a: 1}); + typeCheckPass(checker, {b: 1}); + }); + + it('should be implicitly optional and not warn without values', () => { + typeCheckPass( + PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + null, + ); + typeCheckPass( + PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + undefined, + ); + }); + + it('should warn for missing required values', () => { + typeCheckFailRequiredValues( + PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, + ); + }); + + it('should warn if called manually in development', () => { + spyOn(console, 'error'); + expectWarningInDevelopment( + PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + [], + ); + expectWarningInDevelopment( + PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + null, + ); + expectWarningInDevelopment( + PropTypes.oneOfType([PropTypes.string, PropTypes.number]), + undefined, + ); + }); + }); + + describe('Shape Types', () => { + it('should warn for non objects', () => { + typeCheckFail( + PropTypes.shape({}), + 'some string', + 'Invalid prop `testProp` of type `string` supplied to ' + + '`testComponent`, expected `object`.', + ); + typeCheckFail( + PropTypes.shape({}), + ['array'], + 'Invalid prop `testProp` of type `array` supplied to ' + + '`testComponent`, expected `object`.', + ); + }); + + it('should not warn for empty values', () => { + typeCheckPass(PropTypes.shape({}), undefined); + typeCheckPass(PropTypes.shape({}), null); + typeCheckPass(PropTypes.shape({}), {}); + }); + + it('should not warn for an empty object', () => { + typeCheckPass(PropTypes.shape({}).isRequired, {}); + }); + + it('should not warn for non specified types', () => { + typeCheckPass(PropTypes.shape({}), {key: 1}); + }); + + it('should not warn for valid types', () => { + typeCheckPass(PropTypes.shape({key: PropTypes.number}), {key: 1}); + }); + + it('should warn for required valid types', () => { + typeCheckFail( + PropTypes.shape({key: PropTypes.number.isRequired}), + {}, + 'The prop `testProp.key` is marked as required in `testComponent`, ' + + 'but its value is `undefined`.', + ); + }); + + it('should warn for the first required type', () => { + typeCheckFail( + PropTypes.shape({ + key: PropTypes.number.isRequired, + secondKey: PropTypes.number.isRequired, + }), + {}, + 'The prop `testProp.key` is marked as required in `testComponent`, ' + + 'but its value is `undefined`.', + ); + }); + + it('should warn for invalid key types', () => { + typeCheckFail( + PropTypes.shape({key: PropTypes.number}), + {key: 'abc'}, + 'Invalid prop `testProp.key` of type `string` supplied to `testComponent`, ' + + 'expected `number`.', + ); + }); + + it('should be implicitly optional and not warn without values', () => { + typeCheckPass( + PropTypes.shape(PropTypes.shape({key: PropTypes.number})), + null, + ); + typeCheckPass( + PropTypes.shape(PropTypes.shape({key: PropTypes.number})), + undefined, + ); + }); + + it('should warn for missing required values', () => { + typeCheckFailRequiredValues( + PropTypes.shape({key: PropTypes.number}).isRequired, + ); + }); + + it('should warn if called manually in development', () => { + spyOn(console, 'error'); + expectWarningInDevelopment(PropTypes.shape({}), 'some string'); + expectWarningInDevelopment(PropTypes.shape({foo: PropTypes.number}), { + foo: 42, + }); + expectWarningInDevelopment( + PropTypes.shape({key: PropTypes.number}).isRequired, + null, + ); + expectWarningInDevelopment( + PropTypes.shape({key: PropTypes.number}).isRequired, + undefined, + ); + expectWarningInDevelopment(PropTypes.element,
); + }); + }); + + describe('Symbol Type', () => { + it('should warn for non-symbol', () => { + typeCheckFail( + PropTypes.symbol, + 'hello', + 'Invalid prop `testProp` of type `string` supplied to ' + + '`testComponent`, expected `symbol`.', + ); + typeCheckFail( + PropTypes.symbol, + function() {}, + 'Invalid prop `testProp` of type `function` supplied to ' + + '`testComponent`, expected `symbol`.', + ); + typeCheckFail( + PropTypes.symbol, + { + '@@toStringTag': 'Katana', + }, + 'Invalid prop `testProp` of type `object` supplied to ' + + '`testComponent`, expected `symbol`.', + ); + }); + + it('should not warn for a polyfilled Symbol', () => { + var CoreSymbol = require('core-js/library/es6/symbol'); + typeCheckPass(PropTypes.symbol, CoreSymbol('core-js')); + }); + }); +}); diff --git a/__tests__/PropTypesDevelopment-test.js b/__tests__/PropTypesDevelopmentStandalone-test.js similarity index 99% rename from __tests__/PropTypesDevelopment-test.js rename to __tests__/PropTypesDevelopmentStandalone-test.js index b8e04b4..a14eb5d 100644 --- a/__tests__/PropTypesDevelopment-test.js +++ b/__tests__/PropTypesDevelopmentStandalone-test.js @@ -105,7 +105,7 @@ function expectWarningInDevelopment(declaration, value) { console.error.calls.reset(); } -describe('PropTypesDevelopment', () => { +describe('PropTypesDevelopmentStandalone', () => { beforeEach(() => { resetWarningCache(); }); From a6f021fa61503761ba70da8e9c9de283dcd33928 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Tue, 11 Apr 2017 19:23:57 +0100 Subject: [PATCH 08/12] Change intended behavior in README --- README.md | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4dcd892..f5f841f 100644 --- a/README.md +++ b/README.md @@ -25,11 +25,42 @@ If you prefer a ` ``` -## Development and Production Versions +## Difference from `React.PropTypes`: Don’t Call Validator Functions -In production, all validator functions are replaced with empty functions that throw an error. This is done to optimize the bundle size. +When you migrate components to use the standalone `prop-types`, **all validator functions will start throwing an error if you call them directly**. This makes sure that nobody relies on them in production code, and it is safe to strip their implementations to optimize the bundle size. -Don’t call the validator functions manually in your code. React automatically calls `PropTypes` validators declared on your components in development version, and it won’t call them in production. +Code like this is still fine: + +```js +MyComponent.propTypes = { + myProp: PropTypes.bool +}; +``` + +However, code like this will not work with the `prop-types` package: + +```js +// Will not work with `prop-types` package! +var errorOrNull = PropTypes.bool(42, 'myProp', 'MyComponent', 'prop'); +``` + +It will throw an error: + +``` +Calling PropTypes validators directly is not supported by the `prop-types` package. +Use PropTypes.checkPropTypes() to call them. +``` + +This is new behavior, and you will only encounter it when you migrate from `React.PropTypes` to the `prop-types` package. For the vast majority of components, this doesn’t matter, and if you didn’t see [this warning](https://facebook.github.io/react/warnings/dont-call-proptypes.html) in your components, your code is safe to migrate. This is not a breaking change in React because you are only opting into this change for a component by explicitly changing your imports to use `prop-types`. If you temporarily need the old behavior, you can keep using `React.PropTypes` until React 16. + +**If you absolutely need to trigger the validation manually**, call `PropTypes.checkPropTypes()`. Unlike the validators themselves, this function is safe to call in production, as it will be replaced by an empty function: + +```js +// Works with standalone PropTypes +PropTypes.checkPropTypes(MyComponent.propTypes, props, 'prop', 'MyComponent'); +``` + +**You might also see this error** if you’re calling a `PropTypes` validator from your own custom `PropTypes` validator. In this case, the fix is to make sure that you are passing *all* of the arguments to the inner function. There is a more in-depth explanation of how to fix it [on this page](https://facebook.github.io/react/warnings/dont-call-proptypes.html#fixing-the-false-positive-in-third-party-proptypes). Alternatively, you can temporarily keep using `React.PropTypes` until React 16, as it would still only warn in this case. If you use a bundler like Browserify or Webpack, don’t forget to [follow these instructions](https://facebook.github.io/react/docs/installation.html#development-and-production-versions) to correctly bundle your application in development or production mode. Otherwise you’ll ship unnecessary code to your users. @@ -39,4 +70,4 @@ Refer to the [React documentation](https://facebook.github.io/react/docs/typeche ## Migrating from React.PropTypes -Check out [Migrating from React.PropTypes](https://facebook.github.io/react/blog/2017/04/07/react-v15.5.0.html#migrating-from-react.proptypes) for details on how to migrate to `prop-types` from `React.PropTypes` +Check out [Migrating from React.PropTypes](https://facebook.github.io/react/blog/2017/04/07/react-v15.5.0.html#migrating-from-react.proptypes) for details on how to migrate to `prop-types` from `React.PropTypes`. From 23256243aabdf11c8b9a9d3ef3bef6739d5ead3d Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Tue, 11 Apr 2017 17:58:43 +0100 Subject: [PATCH 09/12] 15.5.7-alpha --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 10388a9..6f6776f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prop-types", - "version": "15.5.6", + "version": "15.5.7-alpha", "description": "Runtime type checking for React props and similar objects.", "main": "index.js", "license": "BSD-3-Clause", From 36d7a3331b6b312b7a4ec28085f772603395b6fc Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Tue, 11 Apr 2017 19:35:11 +0100 Subject: [PATCH 10/12] Make it throw in development too when you use prop-types package --- .../PropTypesDevelopmentStandalone-test.js | 182 +++++++++--------- .../PropTypesProductionStandalone-test.js | 6 +- factory.js | 7 +- factoryWithThrowingShims.js | 7 +- factoryWithTypeCheckers.js | 27 ++- index.js | 7 +- 6 files changed, 135 insertions(+), 101 deletions(-) diff --git a/__tests__/PropTypesDevelopmentStandalone-test.js b/__tests__/PropTypesDevelopmentStandalone-test.js index a14eb5d..c5ee71d 100644 --- a/__tests__/PropTypesDevelopmentStandalone-test.js +++ b/__tests__/PropTypesDevelopmentStandalone-test.js @@ -91,18 +91,16 @@ function typeCheckPass(declaration, value) { expect(message).toBe(null); } -function expectWarningInDevelopment(declaration, value) { +function expectThrowsInDevelopment(declaration, value) { + resetWarningCache(); var props = {testProp: value}; - var propName = 'testProp' + Math.random().toString(); - var componentName = 'testComponent' + Math.random().toString(); - for (var i = 0; i < 3; i++) { - declaration(props, propName, componentName, 'prop'); - } - expect(console.error.calls.count()).toBe(1); - expect(console.error.calls.argsFor(0)[0]).toContain( - 'You are manually calling a React.PropTypes validation ', + expect(() => { + declaration(props, 'testProp', 'testComponent', 'prop'); + }).toThrowError( + 'Calling PropTypes validators directly is not supported by the `prop-types` package. ' + + 'Use PropTypes.checkPropTypes() to call them. ' + + 'Read more at http://fb.me/prop-types-in-prod' ); - console.error.calls.reset(); } describe('PropTypesDevelopmentStandalone', () => { @@ -222,48 +220,48 @@ describe('PropTypesDevelopmentStandalone', () => { it('should warn if called manually in development', () => { spyOn(console, 'error'); - expectWarningInDevelopment(PropTypes.array, /please/); - expectWarningInDevelopment(PropTypes.array, []); - expectWarningInDevelopment(PropTypes.array.isRequired, /please/); - expectWarningInDevelopment(PropTypes.array.isRequired, []); - expectWarningInDevelopment(PropTypes.array.isRequired, null); - expectWarningInDevelopment(PropTypes.array.isRequired, undefined); - expectWarningInDevelopment(PropTypes.bool, []); - expectWarningInDevelopment(PropTypes.bool, true); - expectWarningInDevelopment(PropTypes.bool.isRequired, []); - expectWarningInDevelopment(PropTypes.bool.isRequired, true); - expectWarningInDevelopment(PropTypes.bool.isRequired, null); - expectWarningInDevelopment(PropTypes.bool.isRequired, undefined); - expectWarningInDevelopment(PropTypes.func, false); - expectWarningInDevelopment(PropTypes.func, function() {}); - expectWarningInDevelopment(PropTypes.func.isRequired, false); - expectWarningInDevelopment(PropTypes.func.isRequired, function() {}); - expectWarningInDevelopment(PropTypes.func.isRequired, null); - expectWarningInDevelopment(PropTypes.func.isRequired, undefined); - expectWarningInDevelopment(PropTypes.number, function() {}); - expectWarningInDevelopment(PropTypes.number, 42); - expectWarningInDevelopment(PropTypes.number.isRequired, function() {}); - expectWarningInDevelopment(PropTypes.number.isRequired, 42); - expectWarningInDevelopment(PropTypes.number.isRequired, null); - expectWarningInDevelopment(PropTypes.number.isRequired, undefined); - expectWarningInDevelopment(PropTypes.string, 0); - expectWarningInDevelopment(PropTypes.string, 'foo'); - expectWarningInDevelopment(PropTypes.string.isRequired, 0); - expectWarningInDevelopment(PropTypes.string.isRequired, 'foo'); - expectWarningInDevelopment(PropTypes.string.isRequired, null); - expectWarningInDevelopment(PropTypes.string.isRequired, undefined); - expectWarningInDevelopment(PropTypes.symbol, 0); - expectWarningInDevelopment(PropTypes.symbol, Symbol('Foo')); - expectWarningInDevelopment(PropTypes.symbol.isRequired, 0); - expectWarningInDevelopment(PropTypes.symbol.isRequired, Symbol('Foo')); - expectWarningInDevelopment(PropTypes.symbol.isRequired, null); - expectWarningInDevelopment(PropTypes.symbol.isRequired, undefined); - expectWarningInDevelopment(PropTypes.object, ''); - expectWarningInDevelopment(PropTypes.object, {foo: 'bar'}); - expectWarningInDevelopment(PropTypes.object.isRequired, ''); - expectWarningInDevelopment(PropTypes.object.isRequired, {foo: 'bar'}); - expectWarningInDevelopment(PropTypes.object.isRequired, null); - expectWarningInDevelopment(PropTypes.object.isRequired, undefined); + expectThrowsInDevelopment(PropTypes.array, /please/); + expectThrowsInDevelopment(PropTypes.array, []); + expectThrowsInDevelopment(PropTypes.array.isRequired, /please/); + expectThrowsInDevelopment(PropTypes.array.isRequired, []); + expectThrowsInDevelopment(PropTypes.array.isRequired, null); + expectThrowsInDevelopment(PropTypes.array.isRequired, undefined); + expectThrowsInDevelopment(PropTypes.bool, []); + expectThrowsInDevelopment(PropTypes.bool, true); + expectThrowsInDevelopment(PropTypes.bool.isRequired, []); + expectThrowsInDevelopment(PropTypes.bool.isRequired, true); + expectThrowsInDevelopment(PropTypes.bool.isRequired, null); + expectThrowsInDevelopment(PropTypes.bool.isRequired, undefined); + expectThrowsInDevelopment(PropTypes.func, false); + expectThrowsInDevelopment(PropTypes.func, function() {}); + expectThrowsInDevelopment(PropTypes.func.isRequired, false); + expectThrowsInDevelopment(PropTypes.func.isRequired, function() {}); + expectThrowsInDevelopment(PropTypes.func.isRequired, null); + expectThrowsInDevelopment(PropTypes.func.isRequired, undefined); + expectThrowsInDevelopment(PropTypes.number, function() {}); + expectThrowsInDevelopment(PropTypes.number, 42); + expectThrowsInDevelopment(PropTypes.number.isRequired, function() {}); + expectThrowsInDevelopment(PropTypes.number.isRequired, 42); + expectThrowsInDevelopment(PropTypes.number.isRequired, null); + expectThrowsInDevelopment(PropTypes.number.isRequired, undefined); + expectThrowsInDevelopment(PropTypes.string, 0); + expectThrowsInDevelopment(PropTypes.string, 'foo'); + expectThrowsInDevelopment(PropTypes.string.isRequired, 0); + expectThrowsInDevelopment(PropTypes.string.isRequired, 'foo'); + expectThrowsInDevelopment(PropTypes.string.isRequired, null); + expectThrowsInDevelopment(PropTypes.string.isRequired, undefined); + expectThrowsInDevelopment(PropTypes.symbol, 0); + expectThrowsInDevelopment(PropTypes.symbol, Symbol('Foo')); + expectThrowsInDevelopment(PropTypes.symbol.isRequired, 0); + expectThrowsInDevelopment(PropTypes.symbol.isRequired, Symbol('Foo')); + expectThrowsInDevelopment(PropTypes.symbol.isRequired, null); + expectThrowsInDevelopment(PropTypes.symbol.isRequired, undefined); + expectThrowsInDevelopment(PropTypes.object, ''); + expectThrowsInDevelopment(PropTypes.object, {foo: 'bar'}); + expectThrowsInDevelopment(PropTypes.object.isRequired, ''); + expectThrowsInDevelopment(PropTypes.object.isRequired, {foo: 'bar'}); + expectThrowsInDevelopment(PropTypes.object.isRequired, null); + expectThrowsInDevelopment(PropTypes.object.isRequired, undefined); }); }); @@ -286,9 +284,9 @@ describe('PropTypesDevelopmentStandalone', () => { it('should warn if called manually in development', () => { spyOn(console, 'error'); - expectWarningInDevelopment(PropTypes.any, null); - expectWarningInDevelopment(PropTypes.any.isRequired, null); - expectWarningInDevelopment(PropTypes.any.isRequired, undefined); + expectThrowsInDevelopment(PropTypes.any, null); + expectThrowsInDevelopment(PropTypes.any.isRequired, null); + expectThrowsInDevelopment(PropTypes.any.isRequired, undefined); }); }); @@ -382,23 +380,23 @@ describe('PropTypesDevelopmentStandalone', () => { it('should warn if called manually in development', () => { spyOn(console, 'error'); - expectWarningInDevelopment(PropTypes.arrayOf({foo: PropTypes.string}), { + expectThrowsInDevelopment(PropTypes.arrayOf({foo: PropTypes.string}), { foo: 'bar', }); - expectWarningInDevelopment(PropTypes.arrayOf(PropTypes.number), [ + expectThrowsInDevelopment(PropTypes.arrayOf(PropTypes.number), [ 1, 2, 'b', ]); - expectWarningInDevelopment(PropTypes.arrayOf(PropTypes.number), { + expectThrowsInDevelopment(PropTypes.arrayOf(PropTypes.number), { '0': 'maybe-array', length: 1, }); - expectWarningInDevelopment( + expectThrowsInDevelopment( PropTypes.arrayOf(PropTypes.number).isRequired, null, ); - expectWarningInDevelopment( + expectThrowsInDevelopment( PropTypes.arrayOf(PropTypes.number).isRequired, undefined, ); @@ -449,13 +447,13 @@ describe('PropTypesDevelopmentStandalone', () => { it('should warn if called manually in development', () => { spyOn(console, 'error'); - expectWarningInDevelopment(PropTypes.element, [
,
]); - expectWarningInDevelopment(PropTypes.element,
); - expectWarningInDevelopment(PropTypes.element, 123); - expectWarningInDevelopment(PropTypes.element, 'foo'); - expectWarningInDevelopment(PropTypes.element, false); - expectWarningInDevelopment(PropTypes.element.isRequired, null); - expectWarningInDevelopment(PropTypes.element.isRequired, undefined); + expectThrowsInDevelopment(PropTypes.element, [
,
]); + expectThrowsInDevelopment(PropTypes.element,
); + expectThrowsInDevelopment(PropTypes.element, 123); + expectThrowsInDevelopment(PropTypes.element, 'foo'); + expectThrowsInDevelopment(PropTypes.element, false); + expectThrowsInDevelopment(PropTypes.element.isRequired, null); + expectThrowsInDevelopment(PropTypes.element.isRequired, undefined); }); }); @@ -548,10 +546,10 @@ describe('PropTypesDevelopmentStandalone', () => { it('should warn if called manually in development', () => { spyOn(console, 'error'); - expectWarningInDevelopment(PropTypes.instanceOf(Date), {}); - expectWarningInDevelopment(PropTypes.instanceOf(Date), new Date()); - expectWarningInDevelopment(PropTypes.instanceOf(Date).isRequired, {}); - expectWarningInDevelopment( + expectThrowsInDevelopment(PropTypes.instanceOf(Date), {}); + expectThrowsInDevelopment(PropTypes.instanceOf(Date), new Date()); + expectThrowsInDevelopment(PropTypes.instanceOf(Date).isRequired, {}); + expectThrowsInDevelopment( PropTypes.instanceOf(Date).isRequired, new Date(), ); @@ -648,11 +646,11 @@ describe('PropTypesDevelopmentStandalone', () => { it('should warn if called manually in development', () => { spyOn(console, 'error'); - expectWarningInDevelopment(PropTypes.node, 'node'); - expectWarningInDevelopment(PropTypes.node, {}); - expectWarningInDevelopment(PropTypes.node.isRequired, 'node'); - expectWarningInDevelopment(PropTypes.node.isRequired, undefined); - expectWarningInDevelopment(PropTypes.node.isRequired, undefined); + expectThrowsInDevelopment(PropTypes.node, 'node'); + expectThrowsInDevelopment(PropTypes.node, {}); + expectThrowsInDevelopment(PropTypes.node.isRequired, 'node'); + expectThrowsInDevelopment(PropTypes.node.isRequired, undefined); + expectThrowsInDevelopment(PropTypes.node.isRequired, undefined); }); }); @@ -763,17 +761,17 @@ describe('PropTypesDevelopmentStandalone', () => { it('should warn if called manually in development', () => { spyOn(console, 'error'); - expectWarningInDevelopment(PropTypes.objectOf({foo: PropTypes.string}), { + expectThrowsInDevelopment(PropTypes.objectOf({foo: PropTypes.string}), { foo: 'bar', }); - expectWarningInDevelopment(PropTypes.objectOf(PropTypes.number), { + expectThrowsInDevelopment(PropTypes.objectOf(PropTypes.number), { a: 1, b: 2, c: 'b', }); - expectWarningInDevelopment(PropTypes.objectOf(PropTypes.number), [1, 2]); - expectWarningInDevelopment(PropTypes.objectOf(PropTypes.number), null); - expectWarningInDevelopment( + expectThrowsInDevelopment(PropTypes.objectOf(PropTypes.number), [1, 2]); + expectThrowsInDevelopment(PropTypes.objectOf(PropTypes.number), null); + expectThrowsInDevelopment( PropTypes.objectOf(PropTypes.number), undefined, ); @@ -838,9 +836,9 @@ describe('PropTypesDevelopmentStandalone', () => { it('should warn if called manually in development', () => { spyOn(console, 'error'); - expectWarningInDevelopment(PropTypes.oneOf(['red', 'blue']), true); - expectWarningInDevelopment(PropTypes.oneOf(['red', 'blue']), null); - expectWarningInDevelopment(PropTypes.oneOf(['red', 'blue']), undefined); + expectThrowsInDevelopment(PropTypes.oneOf(['red', 'blue']), true); + expectThrowsInDevelopment(PropTypes.oneOf(['red', 'blue']), null); + expectThrowsInDevelopment(PropTypes.oneOf(['red', 'blue']), undefined); }); }); @@ -909,15 +907,15 @@ describe('PropTypesDevelopmentStandalone', () => { it('should warn if called manually in development', () => { spyOn(console, 'error'); - expectWarningInDevelopment( + expectThrowsInDevelopment( PropTypes.oneOfType([PropTypes.string, PropTypes.number]), [], ); - expectWarningInDevelopment( + expectThrowsInDevelopment( PropTypes.oneOfType([PropTypes.string, PropTypes.number]), null, ); - expectWarningInDevelopment( + expectThrowsInDevelopment( PropTypes.oneOfType([PropTypes.string, PropTypes.number]), undefined, ); @@ -1007,19 +1005,19 @@ describe('PropTypesDevelopmentStandalone', () => { it('should warn if called manually in development', () => { spyOn(console, 'error'); - expectWarningInDevelopment(PropTypes.shape({}), 'some string'); - expectWarningInDevelopment(PropTypes.shape({foo: PropTypes.number}), { + expectThrowsInDevelopment(PropTypes.shape({}), 'some string'); + expectThrowsInDevelopment(PropTypes.shape({foo: PropTypes.number}), { foo: 42, }); - expectWarningInDevelopment( + expectThrowsInDevelopment( PropTypes.shape({key: PropTypes.number}).isRequired, null, ); - expectWarningInDevelopment( + expectThrowsInDevelopment( PropTypes.shape({key: PropTypes.number}).isRequired, undefined, ); - expectWarningInDevelopment(PropTypes.element,
); + expectThrowsInDevelopment(PropTypes.element,
); }); }); diff --git a/__tests__/PropTypesProductionStandalone-test.js b/__tests__/PropTypesProductionStandalone-test.js index 006e09a..ab9e41c 100644 --- a/__tests__/PropTypesProductionStandalone-test.js +++ b/__tests__/PropTypesProductionStandalone-test.js @@ -54,7 +54,11 @@ describe('PropTypesProductionStandalone', function() { var props = {testProp: value}; expect(() => { declaration(props, 'testProp', 'testComponent', 'prop'); - }).toThrowError('React.PropTypes type checking code is stripped in production.'); + }).toThrowError( + 'Calling PropTypes validators directly is not supported by the `prop-types` package. ' + + 'Use PropTypes.checkPropTypes() to call them. ' + + 'Read more at http://fb.me/prop-types-in-prod' + ); } function typeCheckPass(declaration, value) { diff --git a/factory.js b/factory.js index 21ff587..7758ec1 100644 --- a/factory.js +++ b/factory.js @@ -13,4 +13,9 @@ // Therefore we re-export development-only version with all the PropTypes checks here. // However if one is migrating to the `prop-types` npm library, they will go through the // `index.js` entry point, and it will branch depending on the environment. -module.exports = require('./factoryWithTypeCheckers'); +var factory = require('./factoryWithTypeCheckers'); +module.exports = function(isValidElement) { + // It is still allowed in 15.5. + var throwOnDirectAccess = false; + return factory(isValidElement, throwOnDirectAccess); +}; diff --git a/factoryWithThrowingShims.js b/factoryWithThrowingShims.js index e8c4fa1..46baf3c 100644 --- a/factoryWithThrowingShims.js +++ b/factoryWithThrowingShims.js @@ -16,7 +16,12 @@ module.exports = function() { // Important! // Keep this list in sync with production version in `./factoryWithTypeCheckers.js`. function shim() { - invariant(false, 'React.PropTypes type checking code is stripped in production.'); + invariant( + false, + 'Calling PropTypes validators directly is not supported by the `prop-types` package. ' + + 'Use PropTypes.checkPropTypes() to call them. ' + + 'Read more at http://fb.me/prop-types-in-prod' + ); }; shim.isRequired = shim; function getShim() { diff --git a/factoryWithTypeCheckers.js b/factoryWithTypeCheckers.js index 7ad84cf..3a604ee 100644 --- a/factoryWithTypeCheckers.js +++ b/factoryWithTypeCheckers.js @@ -16,7 +16,7 @@ var warning = require('fbjs/lib/warning'); var ReactPropTypesSecret = require('./lib/ReactPropTypesSecret'); var checkPropTypes = require('./checkPropTypes'); -module.exports = function(isValidElement) { +module.exports = function(isValidElement, throwOnDirectAccess) { /* global Symbol */ var ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator; var FAUX_ITERATOR_SYMBOL = '@@iterator'; // Before Symbol spec. @@ -152,11 +152,30 @@ module.exports = function(isValidElement) { function checkType(isRequired, props, propName, componentName, location, propFullName, secret) { componentName = componentName || ANONYMOUS; propFullName = propFullName || propName; - if (process.env.NODE_ENV !== 'production') { - if (secret !== ReactPropTypesSecret && typeof console !== 'undefined') { + + if (secret !== ReactPropTypesSecret) { + if (throwOnDirectAccess) { + // New behavior only for users of `prop-types` package + invariant( + false, + 'Calling PropTypes validators directly is not supported by the `prop-types` package. ' + + 'Use PropTypes.checkPropTypes() to call them. ' + + 'Read more at http://fb.me/prop-types-in-prod' + ); + } else if (process.env.NODE_ENV !== 'production' && typeof console !== 'undefined') { + // Old behavior for people using React.PropTypes var cacheKey = componentName + ':' + propName; if (!manualPropTypeCallCache[cacheKey]) { - process.env.NODE_ENV !== 'production' ? warning(false, 'You are manually calling a React.PropTypes validation ' + 'function for the `%s` prop on `%s`. This is deprecated ' + 'and will not work in production with the next major version. ' + 'You may be seeing this warning due to a third-party PropTypes ' + 'library. See https://fb.me/react-warning-dont-call-proptypes ' + 'for details.', propFullName, componentName) : void 0; + warning( + false, + 'You are manually calling a React.PropTypes validation ' + + 'function for the `%s` prop on `%s`. This is deprecated ' + + 'and will throw in the standalone `prop-types` package. ' + + 'You may be seeing this warning due to a third-party PropTypes ' + + 'library. See https://fb.me/react-warning-dont-call-proptypes ' + 'for details.', + propFullName, + componentName + ); manualPropTypeCallCache[cacheKey] = true; } } diff --git a/index.js b/index.js index b7f1c6d..8f94a31 100644 --- a/index.js +++ b/index.js @@ -19,9 +19,12 @@ if (process.env.NODE_ENV !== 'production') { object.$$typeof === REACT_ELEMENT_TYPE; }; - module.exports = require('./factoryWithTypeCheckers')(isValidElement); + // By explicitly using `prop-types` you are opting into new development behavior. + // http://fb.me/prop-types-in-prod + var throwOnDirectAccess = true; + module.exports = require('./factoryWithTypeCheckers')(isValidElement, throwOnDirectAccess); } else { // By explicitly using `prop-types` you are opting into new production behavior. - // https://github.com/reactjs/prop-types#development-and-production-versions + // http://fb.me/prop-types-in-prod module.exports = require('./factoryWithThrowingShims')(); } From d03d32a93249c09a1cd69f7ea7ab4f0199ff9268 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Tue, 11 Apr 2017 19:35:56 +0100 Subject: [PATCH 11/12] 15.5.7-alpha.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6f6776f..45f1983 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prop-types", - "version": "15.5.7-alpha", + "version": "15.5.7-alpha.1", "description": "Runtime type checking for React props and similar objects.", "main": "index.js", "license": "BSD-3-Clause", From f59b9dbbf5e2b6efed53ed25d227c76e37929142 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Tue, 11 Apr 2017 19:49:12 +0100 Subject: [PATCH 12/12] Fix the link --- __tests__/PropTypesDevelopmentStandalone-test.js | 4 ++-- __tests__/PropTypesProductionStandalone-test.js | 2 +- factoryWithThrowingShims.js | 2 +- factoryWithTypeCheckers.js | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/__tests__/PropTypesDevelopmentStandalone-test.js b/__tests__/PropTypesDevelopmentStandalone-test.js index c5ee71d..98c8162 100644 --- a/__tests__/PropTypesDevelopmentStandalone-test.js +++ b/__tests__/PropTypesDevelopmentStandalone-test.js @@ -98,8 +98,8 @@ function expectThrowsInDevelopment(declaration, value) { declaration(props, 'testProp', 'testComponent', 'prop'); }).toThrowError( 'Calling PropTypes validators directly is not supported by the `prop-types` package. ' + - 'Use PropTypes.checkPropTypes() to call them. ' + - 'Read more at http://fb.me/prop-types-in-prod' + 'Use `PropTypes.checkPropTypes()` to call them. ' + + 'Read more at http://fb.me/use-check-prop-types' ); } diff --git a/__tests__/PropTypesProductionStandalone-test.js b/__tests__/PropTypesProductionStandalone-test.js index ab9e41c..b60af87 100644 --- a/__tests__/PropTypesProductionStandalone-test.js +++ b/__tests__/PropTypesProductionStandalone-test.js @@ -57,7 +57,7 @@ describe('PropTypesProductionStandalone', function() { }).toThrowError( 'Calling PropTypes validators directly is not supported by the `prop-types` package. ' + 'Use PropTypes.checkPropTypes() to call them. ' + - 'Read more at http://fb.me/prop-types-in-prod' + 'Read more at http://fb.me/use-check-prop-types' ); } diff --git a/factoryWithThrowingShims.js b/factoryWithThrowingShims.js index 46baf3c..257f710 100644 --- a/factoryWithThrowingShims.js +++ b/factoryWithThrowingShims.js @@ -20,7 +20,7 @@ module.exports = function() { false, 'Calling PropTypes validators directly is not supported by the `prop-types` package. ' + 'Use PropTypes.checkPropTypes() to call them. ' + - 'Read more at http://fb.me/prop-types-in-prod' + 'Read more at http://fb.me/use-check-prop-types' ); }; shim.isRequired = shim; diff --git a/factoryWithTypeCheckers.js b/factoryWithTypeCheckers.js index 3a604ee..4bef015 100644 --- a/factoryWithTypeCheckers.js +++ b/factoryWithTypeCheckers.js @@ -159,8 +159,8 @@ module.exports = function(isValidElement, throwOnDirectAccess) { invariant( false, 'Calling PropTypes validators directly is not supported by the `prop-types` package. ' + - 'Use PropTypes.checkPropTypes() to call them. ' + - 'Read more at http://fb.me/prop-types-in-prod' + 'Use `PropTypes.checkPropTypes()` to call them. ' + + 'Read more at http://fb.me/use-check-prop-types' ); } else if (process.env.NODE_ENV !== 'production' && typeof console !== 'undefined') { // Old behavior for people using React.PropTypes