From 600685bdefbb965129a73536a65b86cbaa73de48 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Tue, 14 Feb 2017 16:52:39 -0800 Subject: [PATCH] Add PropTypes.checkPropTypes API Allows users to check arbitrary objects against React prop types without relying on the implementation details of the prop validators themselves. An example of why this is important: some prop validators may throw, while others return an error object. Calling a prop validator manually requires handling both cases. `checkPropTypes` does this for you. --- .../classic/types/ReactPropTypes.js | 7 ++ .../types/__tests__/ReactPropTypes-test.js | 105 ++++++++---------- src/shared/types/checkReactTypeSpec.js | 4 +- 3 files changed, 58 insertions(+), 58 deletions(-) diff --git a/src/isomorphic/classic/types/ReactPropTypes.js b/src/isomorphic/classic/types/ReactPropTypes.js index 936de6dc75..ebbab9805b 100644 --- a/src/isomorphic/classic/types/ReactPropTypes.js +++ b/src/isomorphic/classic/types/ReactPropTypes.js @@ -13,6 +13,7 @@ var ReactElement = require('ReactElement'); var ReactPropTypesSecret = require('ReactPropTypesSecret'); +var checkReactTypeSpec = require('checkReactTypeSpec'); var emptyFunction = require('emptyFunction'); var getIteratorFn = require('getIteratorFn'); @@ -122,6 +123,12 @@ if (__DEV__) { }; } +function checkPropTypes(propTypes, object, location, componentName, warnOnRepeat) { + checkReactTypeSpec(propTypes, object, location, componentName, null, null, warnOnRepeat); +} +ReactPropTypes.checkPropTypes = checkPropTypes; + + /** * 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 diff --git a/src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js b/src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js index d482d3866d..824dffd807 100644 --- a/src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js +++ b/src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js @@ -15,23 +15,36 @@ var PropTypes; var React; var ReactFragment; var ReactTestUtils; -var ReactPropTypesSecret; var Component; var MyComponent; -function typeCheckFail(declaration, value, message) { - var props = {testProp: value}; - var error = declaration( - props, - 'testProp', - 'testComponent', - 'prop', - null, - ReactPropTypesSecret - ); - expect(error instanceof Error).toBe(true); - expect(error.message).toBe(message); +function getPropTypeWarningMessage(propTypes, object, componentName) { + if (!console.error.calls) { + spyOn(console, 'error'); + } else { + console.error.calls.reset(); + } + PropTypes.checkPropTypes(propTypes, object, 'prop', 'testComponent', true); + 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) { @@ -39,52 +52,31 @@ function typeCheckFailRequiredValues(declaration) { '`testComponent`, but its value is `null`.'; var unspecifiedMsg = 'The prop `testProp` is marked as required in ' + '`testComponent`, but its value is \`undefined\`.'; - var props1 = {testProp: null}; - var error1 = declaration( - props1, - 'testProp', - 'testComponent', - 'prop', - null, - ReactPropTypesSecret - ); - expect(error1 instanceof Error).toBe(true); - expect(error1.message).toBe(specifiedButIsNullMsg); - var props2 = {testProp: undefined}; - var error2 = declaration( - props2, - 'testProp', - 'testComponent', - 'prop', - null, - ReactPropTypesSecret - ); - expect(error2 instanceof Error).toBe(true); - expect(error2.message).toBe(unspecifiedMsg); - var props3 = {}; - var error3 = declaration( - props3, - 'testProp', - 'testComponent', - 'prop', - null, - ReactPropTypesSecret - ); - expect(error3 instanceof Error).toBe(true); - expect(error3.message).toBe(unspecifiedMsg); + + 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) { - var props = {testProp: value}; - var error = declaration( - props, - 'testProp', - 'testComponent', - 'prop', - null, - ReactPropTypesSecret - ); - expect(error).toBe(null); + const propTypes = { + testProp: declaration, + }; + const props = { + testProp: value, + }; + const message = getPropTypeWarningMessage(propTypes, props, 'testComponent'); + expect(message).toBe(null); } function expectWarningInDevelopment(declaration, value) { @@ -112,7 +104,6 @@ describe('ReactPropTypes', () => { React = require('React'); ReactFragment = require('ReactFragment'); ReactTestUtils = require('ReactTestUtils'); - ReactPropTypesSecret = require('ReactPropTypesSecret'); }); describe('Primitive Types', () => { diff --git a/src/shared/types/checkReactTypeSpec.js b/src/shared/types/checkReactTypeSpec.js index acb5032a66..e31114276c 100644 --- a/src/shared/types/checkReactTypeSpec.js +++ b/src/shared/types/checkReactTypeSpec.js @@ -54,6 +54,8 @@ function checkReactTypeSpec( // It is only safe to pass fiber if it is the work-in-progress version, and // only during reconciliation (begin and complete phase). workInProgressOrDebugID, + // Default behavior is to suppress repeated warnings, but can be overridden + warnOnRepeat ) { for (var typeSpecName in typeSpecs) { if (typeSpecs.hasOwnProperty(typeSpecName)) { @@ -88,7 +90,7 @@ function checkReactTypeSpec( typeSpecName, typeof error ); - if (error instanceof Error && !(error.message in loggedTypeFailures)) { + if (error instanceof Error && (warnOnRepeat || !(error.message in loggedTypeFailures))) { // Only monitor this failure once because there tends to be a lot of the // same error. loggedTypeFailures[error.message] = true;