From 02129cb83dabd0b1303354ddcca5b0baf3f969ef Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Wed, 12 Apr 2017 17:12:32 -0700 Subject: [PATCH 01/37] Added react-dom/test-utils target --- scripts/rollup/bundles.js | 30 ++++++++++++++++++++++++++++++ scripts/rollup/results.json | 6 +++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index cc33df8d123e8..569019a3c904a 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -125,6 +125,34 @@ const bundles = [ 'src/shared/**/*.js', ], }, + { + babelOpts: babelOptsReact, + bundleTypes: [NODE_DEV], + config: { + destDir: 'build/', + globals: { + react: 'React', + }, + moduleName: 'ReactTestUtils', + sourceMap: false, + }, + entry: 'src/test/ReactTestUtils.js', + externals: ['prop-types', 'prop-types/checkPropTypes', 'react'], + hasteName: 'ReactTestUtils', + isRenderer: false, + label: 'react-test-utils', + manglePropertiesOnProd: false, + name: 'react-test-utils', + paths: [ + 'src/renderers/dom/**/*.js', + 'src/renderers/shared/**/*.js', + 'src/test/**/*.js', // ReactTestUtils is currently very coupled to DOM. + + 'src/isomorphic/classic/types/checkPropTypes.js', + 'src/ReactVersion.js', + 'src/shared/**/*.js', + ], + }, /******* React DOM Server *******/ { @@ -361,6 +389,8 @@ const bundles = [ ], }, + // TODO (bvaughn) Add shallow renderer target + /******* React Noop Renderer (used only for fixtures/fiber-debugger) *******/ { babelOpts: babelOptsReact, diff --git a/scripts/rollup/results.json b/scripts/rollup/results.json index 01bee146eedc6..029533fc988ff 100644 --- a/scripts/rollup/results.json +++ b/scripts/rollup/results.json @@ -1,5 +1,5 @@ { - "branch": "master", + "branch": "react-dom-test-utils", "bundleSizes": { "react.development.js (UMD_DEV)": { "size": 121454, @@ -136,6 +136,10 @@ "react-test-renderer.development.js (NODE_DEV)": { "size": 262970, "gzip": 55891 + }, + "react-test-utils.development.js (NODE_DEV)": { + "size": 510282, + "gzip": 122096 } } } \ No newline at end of file From a3e4d6757289d64a027860e96d63ce95722941f7 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Wed, 12 Apr 2017 19:22:18 -0700 Subject: [PATCH 02/37] Removed level of indirection for checkPropTypes Files now import directly from prop-types/checkPropTypes --- scripts/rollup/bundles.js | 9 -- scripts/rollup/results.json | 88 +++++++++---------- src/isomorphic/React.js | 2 +- .../classic/element/ReactElementValidator.js | 2 +- .../types/__tests__/ReactPropTypes-test.js | 2 +- .../classic/types/checkPropTypes.js | 14 --- src/node_modules/react/lib/checkPropTypes.js | 9 -- .../shared/fiber/ReactFiberContext.js | 2 +- .../reconciler/ReactCompositeComponent.js | 2 +- 9 files changed, 49 insertions(+), 81 deletions(-) delete mode 100644 src/isomorphic/classic/types/checkPropTypes.js delete mode 100644 src/node_modules/react/lib/checkPropTypes.js diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 569019a3c904a..b08c10a2483af 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -91,7 +91,6 @@ const bundles = [ 'src/renderers/shared/**/*.js', 'src/test/**/*.js', // ReactTestUtils is currently very coupled to DOM. - 'src/isomorphic/classic/types/checkPropTypes.js', 'src/ReactVersion.js', 'src/shared/**/*.js', ], @@ -120,7 +119,6 @@ const bundles = [ 'src/renderers/shared/**/*.js', 'src/test/**/*.js', // ReactTestUtils is currently very coupled to DOM. - 'src/isomorphic/classic/types/checkPropTypes.js', 'src/ReactVersion.js', 'src/shared/**/*.js', ], @@ -148,7 +146,6 @@ const bundles = [ 'src/renderers/shared/**/*.js', 'src/test/**/*.js', // ReactTestUtils is currently very coupled to DOM. - 'src/isomorphic/classic/types/checkPropTypes.js', 'src/ReactVersion.js', 'src/shared/**/*.js', ], @@ -179,7 +176,6 @@ const bundles = [ 'src/renderers/dom/**/*.js', 'src/renderers/shared/**/*.js', - 'src/isomorphic/classic/types/checkPropTypes.js', 'src/ReactVersion.js', 'src/shared/**/*.js', ], @@ -218,7 +214,6 @@ const bundles = [ 'src/renderers/art/**/*.js', 'src/renderers/shared/**/*.js', - 'src/isomorphic/classic/types/checkPropTypes.js', 'src/ReactVersion.js', 'src/shared/**/*.js', ], @@ -254,7 +249,6 @@ const bundles = [ 'src/renderers/art/**/*.js', 'src/renderers/shared/**/*.js', - 'src/isomorphic/classic/types/checkPropTypes.js', 'src/ReactVersion.js', 'src/shared/**/*.js', ], @@ -356,7 +350,6 @@ const bundles = [ 'src/renderers/shared/**/*.js', 'src/renderers/testing/**/*.js', - 'src/isomorphic/classic/types/checkPropTypes.js', 'src/ReactVersion.js', 'src/shared/**/*.js', ], @@ -383,7 +376,6 @@ const bundles = [ 'src/renderers/shared/**/*.js', 'src/renderers/testing/**/*.js', - 'src/isomorphic/classic/types/checkPropTypes.js', 'src/ReactVersion.js', 'src/shared/**/*.js', ], @@ -413,7 +405,6 @@ const bundles = [ 'src/renderers/noop/**/*.js', 'src/renderers/shared/**/*.js', - 'src/isomorphic/classic/types/checkPropTypes.js', 'src/ReactVersion.js', 'src/shared/**/*.js', ], diff --git a/scripts/rollup/results.json b/scripts/rollup/results.json index 029533fc988ff..12d537433c001 100644 --- a/scripts/rollup/results.json +++ b/scripts/rollup/results.json @@ -2,112 +2,112 @@ "branch": "react-dom-test-utils", "bundleSizes": { "react.development.js (UMD_DEV)": { - "size": 121454, - "gzip": 30515 + "size": 121412, + "gzip": 30500 }, "react.production.min.js (UMD_PROD)": { - "size": 15685, - "gzip": 5765 + "size": 15679, + "gzip": 5761 }, "react-dom.development.js (UMD_DEV)": { - "size": 583190, - "gzip": 134534 + "size": 583148, + "gzip": 134526 }, "react-dom.production.min.js (UMD_PROD)": { "size": 120740, "gzip": 38094 }, "react-dom-server.development.js (UMD_DEV)": { - "size": 495558, - "gzip": 119685 + "size": 495516, + "gzip": 119682 }, "react-dom-server.production.min.js (UMD_PROD)": { "size": 107033, "gzip": 33273 }, "react-art.development.js (UMD_DEV)": { - "size": 342608, - "gzip": 76782 + "size": 342568, + "gzip": 76773 }, "react-art.production.min.js (UMD_PROD)": { "size": 95013, "gzip": 28991 }, "react.development.js (NODE_DEV)": { - "size": 70266, - "gzip": 17594 + "size": 70222, + "gzip": 17579 }, "react.production.min.js (NODE_PROD)": { - "size": 9226, - "gzip": 3628 + "size": 9220, + "gzip": 3621 }, "React-dev.js (FB_DEV)": { - "size": 72123, - "gzip": 18231 + "size": 72079, + "gzip": 18217 }, "React-prod.js (FB_PROD)": { - "size": 36643, - "gzip": 9256 + "size": 36606, + "gzip": 9248 }, "ReactDOMStack-dev.js (FB_DEV)": { - "size": 522763, - "gzip": 124727 + "size": 522721, + "gzip": 124723 }, "ReactDOMStack-prod.js (FB_PROD)": { "size": 352776, "gzip": 84675 }, "react-dom.development.js (NODE_DEV)": { - "size": 542188, - "gzip": 125158 + "size": 542144, + "gzip": 125150 }, "react-dom.production.min.js (NODE_PROD)": { "size": 116925, "gzip": 36732 }, "ReactDOMFiber-dev.js (FB_DEV)": { - "size": 797235, - "gzip": 184122 + "size": 797189, + "gzip": 184111 }, "ReactDOMFiber-prod.js (FB_PROD)": { "size": 407613, "gzip": 93586 }, "react-dom-server.development.js (NODE_DEV)": { - "size": 445589, - "gzip": 107597 + "size": 445547, + "gzip": 107594 }, "react-dom-server.production.min.js (NODE_PROD)": { "size": 101411, "gzip": 31292 }, "ReactDOMServerStack-dev.js (FB_DEV)": { - "size": 444281, - "gzip": 107443 + "size": 444239, + "gzip": 107440 }, "ReactDOMServerStack-prod.js (FB_PROD)": { "size": 334166, "gzip": 80444 }, "ReactARTStack-dev.js (FB_DEV)": { - "size": 142986, - "gzip": 32714 + "size": 142944, + "gzip": 32705 }, "ReactARTStack-prod.js (FB_PROD)": { "size": 101143, "gzip": 22993 }, "react-art.development.js (NODE_DEV)": { - "size": 265052, - "gzip": 56927 + "size": 265008, + "gzip": 56923 }, "react-art.production.min.js (NODE_PROD)": { "size": 56628, "gzip": 17152 }, "ReactARTFiber-dev.js (FB_DEV)": { - "size": 264230, - "gzip": 56736 + "size": 264186, + "gzip": 56732 }, "ReactARTFiber-prod.js (FB_PROD)": { "size": 205336, @@ -122,24 +122,24 @@ "gzip": 84001 }, "ReactTestRendererFiber-dev.js (FB_DEV)": { - "size": 262139, - "gzip": 55704 + "size": 262095, + "gzip": 55698 }, "ReactTestRendererStack-dev.js (FB_DEV)": { - "size": 151521, - "gzip": 34765 + "size": 151479, + "gzip": 34749 }, "react-noop-renderer.development.js (NODE_DEV)": { - "size": 254136, - "gzip": 53682 + "size": 254092, + "gzip": 53674 }, "react-test-renderer.development.js (NODE_DEV)": { - "size": 262970, - "gzip": 55891 + "size": 262926, + "gzip": 55887 }, "react-test-utils.development.js (NODE_DEV)": { - "size": 510282, - "gzip": 122096 + "size": 510240, + "gzip": 122093 } } } \ No newline at end of file diff --git a/src/isomorphic/React.js b/src/isomorphic/React.js index 807a1c369f4f5..2d6baadb5f3bf 100644 --- a/src/isomorphic/React.js +++ b/src/isomorphic/React.js @@ -19,7 +19,7 @@ var ReactPropTypes = require('ReactPropTypes'); var ReactVersion = require('ReactVersion'); var onlyChild = require('onlyChild'); -var checkPropTypes = require('checkPropTypes'); +var checkPropTypes = require('prop-types/checkPropTypes'); var createReactClass = require('createClass'); var createElement = ReactElement.createElement; diff --git a/src/isomorphic/classic/element/ReactElementValidator.js b/src/isomorphic/classic/element/ReactElementValidator.js index 9840ba3a59bb8..cf2682168b6e0 100644 --- a/src/isomorphic/classic/element/ReactElementValidator.js +++ b/src/isomorphic/classic/element/ReactElementValidator.js @@ -26,7 +26,7 @@ var getComponentName = require('getComponentName'); var getIteratorFn = require('getIteratorFn'); if (__DEV__) { - var checkPropTypes = require('checkPropTypes'); + var checkPropTypes = require('prop-types/checkPropTypes'); var warning = require('fbjs/lib/warning'); var ReactDebugCurrentFrame = require('ReactDebugCurrentFrame'); var { diff --git a/src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js b/src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js index edcaca7a6f9a0..ce4c2e183372e 100644 --- a/src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js +++ b/src/isomorphic/classic/types/__tests__/ReactPropTypes-test.js @@ -21,7 +21,7 @@ var MyComponent; function resetWarningCache() { jest.resetModules(); - checkPropTypes = require('checkPropTypes'); + checkPropTypes = require('prop-types/checkPropTypes'); } function getPropTypeWarningMessage(propTypes, object, componentName) { diff --git a/src/isomorphic/classic/types/checkPropTypes.js b/src/isomorphic/classic/types/checkPropTypes.js deleted file mode 100644 index 32d9ecc677497..0000000000000 --- a/src/isomorphic/classic/types/checkPropTypes.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * 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. - * - * @providesModule checkPropTypes - */ - -'use strict'; - -module.exports = require('prop-types/checkPropTypes'); diff --git a/src/node_modules/react/lib/checkPropTypes.js b/src/node_modules/react/lib/checkPropTypes.js deleted file mode 100644 index 083e4df83ad8a..0000000000000 --- a/src/node_modules/react/lib/checkPropTypes.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Copyright 2016-present Facebook. All Rights Reserved. - * - * @flow - */ - -'use strict'; - -module.exports = require('checkPropTypes'); diff --git a/src/renderers/shared/fiber/ReactFiberContext.js b/src/renderers/shared/fiber/ReactFiberContext.js index b2104d19e9039..4d669a8611b30 100644 --- a/src/renderers/shared/fiber/ReactFiberContext.js +++ b/src/renderers/shared/fiber/ReactFiberContext.js @@ -15,7 +15,7 @@ import type {Fiber} from 'ReactFiber'; import type {StackCursor} from 'ReactFiberStack'; -var checkPropTypes = require('checkPropTypes'); +var checkPropTypes = require('prop-types/checkPropTypes'); var emptyObject = require('fbjs/lib/emptyObject'); var getComponentName = require('getComponentName'); var invariant = require('fbjs/lib/invariant'); diff --git a/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js b/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js index 2ad0d4b40316b..ec117977dc02d 100644 --- a/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js +++ b/src/renderers/shared/stack/reconciler/ReactCompositeComponent.js @@ -27,7 +27,7 @@ if (__DEV__) { var warningAboutMissingGetChildContext = {}; } -var checkPropTypes = require('checkPropTypes'); +var checkPropTypes = require('prop-types/checkPropTypes'); var emptyObject = require('fbjs/lib/emptyObject'); var invariant = require('fbjs/lib/invariant'); var shallowEqual = require('fbjs/lib/shallowEqual'); From 85e338bc88a9b55118edaf29ecf52aacf72bfd85 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Thu, 13 Apr 2017 10:57:35 -0700 Subject: [PATCH 03/37] Updated Rollup bundle for test-utils * Moved test-utils into react-dom package * Added test-utils to package.json 'files' list * Added ReactTestUtilsFBEntry module (though it doesn't really do anything at the moment) * Replaced ReactDOM references with react-dom to avoid duplicating code --- packages/react-dom/package.json | 1 + packages/react-dom/test-utils.js | 5 +++ scripts/rollup/bundles.js | 16 +++++--- scripts/rollup/results.json | 40 +++++++++++-------- src/fb/ReactDOMFBEntry.js | 5 +-- src/fb/ReactDOMFiberFBEntry.js | 11 ----- .../fb/ReactTestUtilsFBEntry.js | 8 +--- src/renderers/dom/ReactDOM.js | 3 ++ src/renderers/dom/fiber/ReactDOMFiber.js | 3 ++ src/test/ReactTestUtils.js | 16 +++++--- 10 files changed, 60 insertions(+), 48 deletions(-) create mode 100644 packages/react-dom/test-utils.js rename scripts/rollup/shims/facebook-www/ReactTestUtils.js => src/fb/ReactTestUtilsFBEntry.js (60%) diff --git a/packages/react-dom/package.json b/packages/react-dom/package.json index 52070cd964734..d1863e6507af0 100644 --- a/packages/react-dom/package.json +++ b/packages/react-dom/package.json @@ -27,6 +27,7 @@ "README.md", "index.js", "server.js", + "test-utils.js", "cjs/", "umd/" ], diff --git a/packages/react-dom/test-utils.js b/packages/react-dom/test-utils.js new file mode 100644 index 0000000000000..b6bed7d549a17 --- /dev/null +++ b/packages/react-dom/test-utils.js @@ -0,0 +1,5 @@ +'use strict'; + +if (process.env.NODE_ENV !== 'production') { + module.exports = require('./cjs/test-utils.development.js'); +} diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index b08c10a2483af..6fb9e248b4827 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -125,7 +125,7 @@ const bundles = [ }, { babelOpts: babelOptsReact, - bundleTypes: [NODE_DEV], + bundleTypes: [FB_DEV, NODE_DEV], config: { destDir: 'build/', globals: { @@ -135,14 +135,20 @@ const bundles = [ sourceMap: false, }, entry: 'src/test/ReactTestUtils.js', - externals: ['prop-types', 'prop-types/checkPropTypes', 'react'], + externals: [ + 'prop-types', + 'prop-types/checkPropTypes', + 'react', + 'react-dom', + 'react-dom/test-utils', + ], + fbEntry: 'src/fb/ReactTestUtilsFBEntry.js', hasteName: 'ReactTestUtils', isRenderer: false, - label: 'react-test-utils', + label: 'test-utils', manglePropertiesOnProd: false, - name: 'react-test-utils', + name: 'react-dom/test-utils', paths: [ - 'src/renderers/dom/**/*.js', 'src/renderers/shared/**/*.js', 'src/test/**/*.js', // ReactTestUtils is currently very coupled to DOM. diff --git a/scripts/rollup/results.json b/scripts/rollup/results.json index 12d537433c001..3f10353846c3e 100644 --- a/scripts/rollup/results.json +++ b/scripts/rollup/results.json @@ -10,12 +10,12 @@ "gzip": 5761 }, "react-dom.development.js (UMD_DEV)": { - "size": 583148, - "gzip": 134526 + "size": 583284, + "gzip": 134555 }, "react-dom.production.min.js (UMD_PROD)": { - "size": 120740, - "gzip": 38094 + "size": 120793, + "gzip": 38119 }, "react-dom-server.development.js (UMD_DEV)": { "size": 495516, @@ -50,28 +50,28 @@ "gzip": 9248 }, "ReactDOMStack-dev.js (FB_DEV)": { - "size": 522721, - "gzip": 124723 + "size": 492312, + "gzip": 117399 }, "ReactDOMStack-prod.js (FB_PROD)": { - "size": 352776, - "gzip": 84675 + "size": 352924, + "gzip": 84697 }, "react-dom.development.js (NODE_DEV)": { - "size": 542144, - "gzip": 125150 + "size": 542280, + "gzip": 125179 }, "react-dom.production.min.js (NODE_PROD)": { - "size": 116925, - "gzip": 36732 + "size": 116978, + "gzip": 36758 }, "ReactDOMFiber-dev.js (FB_DEV)": { - "size": 797189, - "gzip": 184111 + "size": 543121, + "gzip": 125573 }, "ReactDOMFiber-prod.js (FB_PROD)": { - "size": 407613, - "gzip": 93586 + "size": 407761, + "gzip": 93608 }, "react-dom-server.development.js (NODE_DEV)": { "size": 445547, @@ -140,6 +140,14 @@ "react-test-utils.development.js (NODE_DEV)": { "size": 510240, "gzip": 122093 + }, + "react-dom-test-utils.development.js (NODE_DEV)": { + "size": 78532, + "gzip": 19766 + }, + "ReactTestUtils-dev.js (FB_DEV)": { + "size": 528, + "gzip": 328 } } } \ No newline at end of file diff --git a/src/fb/ReactDOMFBEntry.js b/src/fb/ReactDOMFBEntry.js index 754febaf3637a..c4e8c003fc8cf 100644 --- a/src/fb/ReactDOMFBEntry.js +++ b/src/fb/ReactDOMFBEntry.js @@ -43,10 +43,9 @@ Object.assign(ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, { if (__DEV__) { Object.assign(ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, { - // ReactPerf and ReactTestUtils currently only work with the DOM renderer - // so we expose them from here, but only in DEV mode. + // ReactPerf currently only works with the DOM renderer + // so we expose it here, but only in DEV mode. ReactPerf: require('ReactPerf'), - ReactTestUtils: require('ReactTestUtils'), }); } diff --git a/src/fb/ReactDOMFiberFBEntry.js b/src/fb/ReactDOMFiberFBEntry.js index 188b9c71248ca..9ecaa6fe07844 100644 --- a/src/fb/ReactDOMFiberFBEntry.js +++ b/src/fb/ReactDOMFiberFBEntry.js @@ -45,15 +45,4 @@ Object.assign( }, ); -if (__DEV__) { - Object.assign( - ReactDOMFiber.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, - { - // ReactPerf and ReactTestUtils currently only work with the DOM renderer - // so we expose them from here, but only in DEV mode. - ReactTestUtils: require('ReactTestUtils'), - }, - ); -} - module.exports = ReactDOMFiber; diff --git a/scripts/rollup/shims/facebook-www/ReactTestUtils.js b/src/fb/ReactTestUtilsFBEntry.js similarity index 60% rename from scripts/rollup/shims/facebook-www/ReactTestUtils.js rename to src/fb/ReactTestUtilsFBEntry.js index dafb421d1d187..11c3f04d942a8 100644 --- a/scripts/rollup/shims/facebook-www/ReactTestUtils.js +++ b/src/fb/ReactTestUtilsFBEntry.js @@ -5,14 +5,8 @@ * 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. - * - * @providesModule ReactTestUtils */ 'use strict'; -const { - __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, -} = require('ReactDOM-fb'); - -module.exports = __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactTestUtils; +module.exports = require('react-dom/test-utils'); diff --git a/src/renderers/dom/ReactDOM.js b/src/renderers/dom/ReactDOM.js index f5f976ea7d4aa..2ddf727c88f43 100644 --- a/src/renderers/dom/ReactDOM.js +++ b/src/renderers/dom/ReactDOM.js @@ -42,6 +42,9 @@ var ReactDOM = { __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: { // For TapEventPlugin which is popular in open source EventPluginHub: require('EventPluginHub'), + // Used by test-utils + ReactDOMComponentTree, + ReactBrowserEventEmitter: require('ReactBrowserEventEmitter'), }, }; diff --git a/src/renderers/dom/fiber/ReactDOMFiber.js b/src/renderers/dom/fiber/ReactDOMFiber.js index bb327bb36b441..778e0e733a61f 100644 --- a/src/renderers/dom/fiber/ReactDOMFiber.js +++ b/src/renderers/dom/fiber/ReactDOMFiber.js @@ -542,6 +542,9 @@ var ReactDOM = { __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: { // For TapEventPlugin which is popular in open source EventPluginHub: require('EventPluginHub'), + // Used by test-utils + ReactDOMComponentTree, + ReactBrowserEventEmitter, }, }; diff --git a/src/test/ReactTestUtils.js b/src/test/ReactTestUtils.js index f320be4bd12b5..f0179ab9a7a40 100644 --- a/src/test/ReactTestUtils.js +++ b/src/test/ReactTestUtils.js @@ -17,19 +17,21 @@ var EventPluginRegistry = require('EventPluginRegistry'); var EventPropagators = require('EventPropagators'); var React = require('react'); var ReactControlledComponent = require('ReactControlledComponent'); -var ReactDOM = require('ReactDOM'); -var ReactDOMComponentTree = require('ReactDOMComponentTree'); -var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter'); +var ReactDOM = require('react-dom'); var ReactFiberTreeReflection = require('ReactFiberTreeReflection'); var ReactInstanceMap = require('ReactInstanceMap'); var ReactTypeOfWork = require('ReactTypeOfWork'); var ReactGenericBatching = require('ReactGenericBatching'); var SyntheticEvent = require('SyntheticEvent'); -var ReactShallowRenderer = require('ReactShallowRenderer'); -var findDOMNode = require('findDOMNode'); var invariant = require('fbjs/lib/invariant'); +var {findDOMNode} = ReactDOM; +var { + ReactDOMComponentTree, + ReactBrowserEventEmitter, +} = ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; + var topLevelTypes = EventConstants.topLevelTypes; var { ClassComponent, @@ -403,9 +405,11 @@ var ReactTestUtils = { }; }, + /** + * TODO (bvaughn) Re-add with an export to react-test-renderer/shallow and a message createRenderer: function() { - return new ReactShallowRenderer(); }, + */ Simulate: null, SimulateNative: {}, From e9f542f03ed60257d6b434343ce9cef400558b07 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Thu, 13 Apr 2017 13:48:35 -0700 Subject: [PATCH 04/37] Cleaned up test-utils bundle slightly by removing unnecessary fb-specific entry point. Throw explicit error if imported in prod mode --- packages/react-dom/test-utils.js | 4 +++- scripts/rollup/bundles.js | 5 +++-- src/fb/ReactTestUtilsFBEntry.js | 12 ------------ src/isomorphic/React.js | 4 ++-- 4 files changed, 8 insertions(+), 17 deletions(-) delete mode 100644 src/fb/ReactTestUtilsFBEntry.js diff --git a/packages/react-dom/test-utils.js b/packages/react-dom/test-utils.js index b6bed7d549a17..db548be884aa3 100644 --- a/packages/react-dom/test-utils.js +++ b/packages/react-dom/test-utils.js @@ -1,5 +1,7 @@ 'use strict'; -if (process.env.NODE_ENV !== 'production') { +if (process.env.NODE_ENV === 'production') { + throw Error('test-utils is not available in production mode.'); +} else { module.exports = require('./cjs/test-utils.development.js'); } diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 6fb9e248b4827..0f8553f1d4a25 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -134,15 +134,16 @@ const bundles = [ moduleName: 'ReactTestUtils', sourceMap: false, }, - entry: 'src/test/ReactTestUtils.js', + entry: 'src/test/ReactTestUtils', externals: [ 'prop-types', 'prop-types/checkPropTypes', 'react', 'react-dom', 'react-dom/test-utils', + 'react-test-renderer/shallow', // TODO (bvaughn) Remove this dependency before 16.0.0 ], - fbEntry: 'src/fb/ReactTestUtilsFBEntry.js', + fbEntry: 'src/test/ReactTestUtils', hasteName: 'ReactTestUtils', isRenderer: false, label: 'test-utils', diff --git a/src/fb/ReactTestUtilsFBEntry.js b/src/fb/ReactTestUtilsFBEntry.js deleted file mode 100644 index 11c3f04d942a8..0000000000000 --- a/src/fb/ReactTestUtilsFBEntry.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * 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'; - -module.exports = require('react-dom/test-utils'); diff --git a/src/isomorphic/React.js b/src/isomorphic/React.js index 2d6baadb5f3bf..eebe7b9b556a9 100644 --- a/src/isomorphic/React.js +++ b/src/isomorphic/React.js @@ -57,7 +57,7 @@ var React = { cloneElement: cloneElement, isValidElement: ReactElement.isValidElement, - // TODO (bvaughn) Remove these getters in 16.0.0-alpha.10 + // TODO (bvaughn) Remove these getters before 16.0.0 PropTypes: ReactPropTypes, checkPropTypes: checkPropTypes, createClass: createReactClass, @@ -100,7 +100,7 @@ if (__DEV__) { return mixin; }; - // TODO (bvaughn) Remove both of these deprecation warnings in 16.0.0-alpha.10 + // TODO (bvaughn) Remove both of these deprecation warnings before 16.0.0 if (canDefineProperty) { Object.defineProperty(React, 'checkPropTypes', { get() { From b984bcf49c8e4bac247fc969019400d801abf57e Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Thu, 13 Apr 2017 18:39:46 -0700 Subject: [PATCH 05/37] Initial pass at building a shallow renderer on top of react-test-renderer > Doesn't handle context. Doesn't handle updates. Doesn't properly handle top-level createElement comparisons. --- scripts/rollup/bundles.js | 26 +++- src/fb/ReactShallowRendererFBEntry.js | 0 .../classic/element/ReactElement.js | 7 + src/renderers/testing/ReactShallowRenderer.js | 94 ++++++++++++ .../testing/ReactTestRendererFiber.js | 1 + src/test/ReactShallowRenderer.js | 145 ------------------ src/test/ReactTestUtils.js | 24 ++- src/test/__tests__/ReactTestUtils-test.js | 34 ++-- 8 files changed, 165 insertions(+), 166 deletions(-) create mode 100644 src/fb/ReactShallowRendererFBEntry.js create mode 100644 src/renderers/testing/ReactShallowRenderer.js delete mode 100644 src/test/ReactShallowRenderer.js diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 0f8553f1d4a25..deff49fc7c421 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -361,7 +361,6 @@ const bundles = [ 'src/shared/**/*.js', ], }, - { babelOpts: babelOptsReact, bundleTypes: [FB_DEV], @@ -387,8 +386,31 @@ const bundles = [ 'src/shared/**/*.js', ], }, + { + babelOpts: babelOptsReact, + bundleTypes: [FB_DEV, NODE_DEV], + config: { + destDir: 'build/', + moduleName: 'ReactShallowRenderer', + sourceMap: false, + }, + entry: 'src/renderers/testing/ReactShallowRenderer', + externals: ['react-dom'], + fbEntry: 'src/fb/ReactShallowRendererFBEntry', + hasteName: 'ReactShallowRenderer', + isRenderer: true, + label: 'shallow-renderer', + manglePropertiesOnProd: false, + name: 'react-test-renderer/shallow', + paths: [ + 'src/renderers/native/**/*.js', + 'src/renderers/shared/**/*.js', + 'src/renderers/testing/**/*.js', - // TODO (bvaughn) Add shallow renderer target + 'src/ReactVersion.js', + 'src/shared/**/*.js', + ], + }, /******* React Noop Renderer (used only for fixtures/fiber-debugger) *******/ { diff --git a/src/fb/ReactShallowRendererFBEntry.js b/src/fb/ReactShallowRendererFBEntry.js new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/src/isomorphic/classic/element/ReactElement.js b/src/isomorphic/classic/element/ReactElement.js index b47d5e247f545..6143d77c5868f 100644 --- a/src/isomorphic/classic/element/ReactElement.js +++ b/src/isomorphic/classic/element/ReactElement.js @@ -162,6 +162,13 @@ var ReactElement = function(type, key, ref, self, source, owner, props) { writable: false, value: source, }); + // Prevent the _owner attribute from breaking shallow renderer tests. + Object.defineProperty(element, '_owner', { + configurable: false, + enumerable: false, + writable: false, + value: owner, + }); } else { element._store.validated = false; element._self = self; diff --git a/src/renderers/testing/ReactShallowRenderer.js b/src/renderers/testing/ReactShallowRenderer.js new file mode 100644 index 0000000000000..99d2b902f4330 --- /dev/null +++ b/src/renderers/testing/ReactShallowRenderer.js @@ -0,0 +1,94 @@ +/** + * 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. + * + * @providesModule ReactShallowRenderer + * @preventMunge + */ + +'use strict'; + +var React = require('react'); +var ReactTestRenderer = require('ReactTestRenderer'); + +var emptyObject = require('fbjs/lib/emptyObject'); +var getNextDebugID = require('getNextDebugID'); +var invariant = require('fbjs/lib/invariant'); + +const ShallowNodeMockComponent = ({children}) => { + return children ? React.Children.toArray(children) : []; +}; + +function createShallowNodeMock() { + var isFirst = true; + return element => { + if (isFirst) { + isFirst = false; + return element.type; + } + return ShallowNodeMockComponent; + }; +} + +class ReactShallowRenderer { + getMountedInstance() { + return this._renderer ? this._renderer.getInstance() : null; // TODO (bvaughn) Is this the right instance? + } + + getRenderOutput() { + if (this._renderer) { + // TODO (bvaughn) This isn't the right type for the root. + // It's not a ReactElement. + // So it can't be compared with the output of React.createElement(). + const tree = this._renderer.toTree(); + return tree ? tree.rendered : null; + } else { + return null; + } + } + + render(element, context) { + invariant( + React.isValidElement(element), + 'ReactShallowRenderer render(): Invalid component element.%s', + typeof element === 'function' + ? ' Instead of passing a component class, make sure to instantiate ' + + 'it by passing it to React.createElement.' + : '', + ); + invariant( + typeof element.type !== 'string', + 'ReactShallowRenderer render(): Shallow rendering works only with custom ' + + 'components, not primitives (%s). Instead of calling `.render(el)` and ' + + 'inspecting the rendered output, look at `el.props` directly instead.', + element.type, + ); + + // TODO (bvaughn) This approach won't work with context + // Should we create a wrapper context-provider in this case? + // See ReactTestUtils-test 'can pass context when shallowly rendering' + + // TODO (bvaughn) How will updates (multiple render calls) work? + // See ReactTestUtils-test 'lets you update shallowly rendered components' + this._renderer = ReactTestRenderer.create(element, { + createNodeMock: createShallowNodeMock(), + }); + + return this.getRenderOutput(); + } + + unmount() { + this._renderer.unmount(); + } +} + +// Backwards compatible API +ReactShallowRenderer.createRenderer = function() { + return new ReactShallowRenderer(); +}; + +module.exports = ReactShallowRenderer; diff --git a/src/renderers/testing/ReactTestRendererFiber.js b/src/renderers/testing/ReactTestRendererFiber.js index 19c2fee0b2d2f..d240a38349a09 100644 --- a/src/renderers/testing/ReactTestRendererFiber.js +++ b/src/renderers/testing/ReactTestRendererFiber.js @@ -13,6 +13,7 @@ 'use strict'; +var React = require('react'); var ReactFiberReconciler = require('ReactFiberReconciler'); var ReactGenericBatching = require('ReactGenericBatching'); var emptyObject = require('fbjs/lib/emptyObject'); diff --git a/src/test/ReactShallowRenderer.js b/src/test/ReactShallowRenderer.js deleted file mode 100644 index 2606448439cfa..0000000000000 --- a/src/test/ReactShallowRenderer.js +++ /dev/null @@ -1,145 +0,0 @@ -/** - * 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. - * - * @providesModule ReactShallowRenderer - * @preventMunge - */ - -'use strict'; - -var React = require('react'); -var ReactDOMInjection = require('ReactDOMInjection'); -var ReactDOMStackInjection = require('ReactDOMStackInjection'); -var ReactCompositeComponent = require('ReactCompositeComponent'); -var ReactReconciler = require('ReactReconciler'); -var ReactUpdates = require('ReactUpdates'); - -var emptyObject = require('fbjs/lib/emptyObject'); -var getNextDebugID = require('getNextDebugID'); -var invariant = require('fbjs/lib/invariant'); - -class NoopInternalComponent { - constructor(element) { - this._renderedOutput = element; - this._currentElement = element; - - if (__DEV__) { - this._debugID = getNextDebugID(); - } - } - mountComponent() {} - receiveComponent(element) { - this._renderedOutput = element; - this._currentElement = element; - } - unmountComponent() {} - getHostNode() { - return undefined; - } - getPublicInstance() { - return null; - } -} - -var ShallowComponentWrapper = function(element) { - // TODO: Consolidate with instantiateReactComponent - if (__DEV__) { - this._debugID = getNextDebugID(); - } - - this.construct(element); -}; -Object.assign(ShallowComponentWrapper.prototype, ReactCompositeComponent, { - _constructComponent: ReactCompositeComponent._constructComponentWithoutOwner, - _instantiateReactComponent: function(element) { - return new NoopInternalComponent(element); - }, - _replaceNodeWithMarkup: function() {}, - _renderValidatedComponent: ReactCompositeComponent._renderValidatedComponentWithoutOwnerOrContext, -}); - -function _batchedRender(renderer, element, context) { - var transaction = ReactUpdates.ReactReconcileTransaction.getPooled(true); - renderer._render(element, transaction, context); - ReactUpdates.ReactReconcileTransaction.release(transaction); -} - -class ReactShallowRenderer { - _instance = null; - getMountedInstance() { - return this._instance ? this._instance._instance : null; - } - render(element, context) { - // Ensure we've done the default injections. This might not be true in the - // case of a simple test that only requires React and the TestUtils in - // conjunction with an inline-requires transform. - ReactDOMInjection.inject(); - ReactDOMStackInjection.inject(); - - invariant( - React.isValidElement(element), - 'ReactShallowRenderer render(): Invalid component element.%s', - typeof element === 'function' - ? ' Instead of passing a component class, make sure to instantiate ' + - 'it by passing it to React.createElement.' - : '', - ); - invariant( - typeof element.type !== 'string', - 'ReactShallowRenderer render(): Shallow rendering works only with custom ' + - 'components, not primitives (%s). Instead of calling `.render(el)` and ' + - 'inspecting the rendered output, look at `el.props` directly instead.', - element.type, - ); - - if (!context) { - context = emptyObject; - } - ReactUpdates.batchedUpdates(_batchedRender, this, element, context); - - return this.getRenderOutput(); - } - getRenderOutput() { - return (this._instance && - this._instance._renderedComponent && - this._instance._renderedComponent._renderedOutput) || - null; - } - unmount() { - if (this._instance) { - ReactReconciler.unmountComponent( - this._instance, - false /* safely */, - false /* skipLifecycle */, - ); - } - } - _render(element, transaction, context) { - if (this._instance) { - ReactReconciler.receiveComponent( - this._instance, - element, - transaction, - context, - ); - } else { - var instance = new ShallowComponentWrapper(element); - ReactReconciler.mountComponent( - instance, - transaction, - null, - null, - context, - 0, - ); - this._instance = instance; - } - } -} - -module.exports = ReactShallowRenderer; diff --git a/src/test/ReactTestUtils.js b/src/test/ReactTestUtils.js index f0179ab9a7a40..5250081dddc50 100644 --- a/src/test/ReactTestUtils.js +++ b/src/test/ReactTestUtils.js @@ -20,11 +20,13 @@ var ReactControlledComponent = require('ReactControlledComponent'); var ReactDOM = require('react-dom'); var ReactFiberTreeReflection = require('ReactFiberTreeReflection'); var ReactInstanceMap = require('ReactInstanceMap'); +var ReactShallowRenderer = require('ReactShallowRenderer'); // TODO (bvaughn) Remove this import before 16.0.0 var ReactTypeOfWork = require('ReactTypeOfWork'); var ReactGenericBatching = require('ReactGenericBatching'); var SyntheticEvent = require('SyntheticEvent'); var invariant = require('fbjs/lib/invariant'); +var warning = require('fbjs/lib/warning'); var {findDOMNode} = ReactDOM; var { @@ -40,6 +42,20 @@ var { HostText, } = ReactTypeOfWork; +// TODO (bvaughn) Remove this warning before 16.0.0 +// It's only being added for temporary deprecation notice in RN. +let warnedAboutShallowRenderer = false; +function createRendererWithWarning() { + warning( + warnedAboutShallowRenderer, + 'Shallow renderer has been moved to react-test-renderer/shallow. ' + + 'Update references to remove this warning. ' + + 'TestUtils.createRenderer will be removed completely in React 16.', + ); + warnedAboutShallowRenderer = true; + return new ReactShallowRenderer(); +} + function Event(suffix) {} /** @@ -405,11 +421,9 @@ var ReactTestUtils = { }; }, - /** - * TODO (bvaughn) Re-add with an export to react-test-renderer/shallow and a message - createRenderer: function() { - }, - */ + // TODO (bvaughn) Remove this warning accessor before the next alpha. + // It's only being added for temporary deprecation notice in RN. + createRenderer: createRendererWithWarning, Simulate: null, SimulateNative: {}, diff --git a/src/test/__tests__/ReactTestUtils-test.js b/src/test/__tests__/ReactTestUtils-test.js index cd1b2a5dc8a5c..fb630f22946e3 100644 --- a/src/test/__tests__/ReactTestUtils-test.js +++ b/src/test/__tests__/ReactTestUtils-test.js @@ -11,6 +11,7 @@ 'use strict'; +var createRenderer; var PropTypes; var React; var ReactDOM; @@ -19,6 +20,7 @@ var ReactTestUtils; describe('ReactTestUtils', () => { beforeEach(() => { + createRenderer = require('ReactShallowRenderer').createRenderer; PropTypes = require('prop-types'); React = require('react'); ReactDOM = require('react-dom'); @@ -38,7 +40,7 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = ReactTestUtils.createRenderer(); + var shallowRenderer = createRenderer(); var result = shallowRenderer.render(); expect(result.type).toBe('div'); @@ -58,7 +60,7 @@ describe('ReactTestUtils', () => { ); } - var shallowRenderer = ReactTestUtils.createRenderer(); + var shallowRenderer = createRenderer(); var result = shallowRenderer.render(); expect(result.type).toBe('div'); @@ -75,7 +77,7 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = ReactTestUtils.createRenderer(); + var shallowRenderer = createRenderer(); expect(() => shallowRenderer.render(SomeComponent)).toThrowError( 'ReactShallowRenderer render(): Invalid component element. Instead of ' + 'passing a component class, make sure to instantiate it by passing it ' + @@ -99,7 +101,7 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = ReactTestUtils.createRenderer(); + var shallowRenderer = createRenderer(); shallowRenderer.render(); shallowRenderer.unmount(); @@ -113,7 +115,7 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = ReactTestUtils.createRenderer(); + var shallowRenderer = createRenderer(); var result = shallowRenderer.render(); expect(result).toBe(null); @@ -126,7 +128,7 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = ReactTestUtils.createRenderer(); + var shallowRenderer = createRenderer(); // Shouldn't crash. shallowRenderer.render(); }); @@ -159,7 +161,7 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = ReactTestUtils.createRenderer(); + var shallowRenderer = createRenderer(); var result = shallowRenderer.render(); expect(result.type).toBe('div'); expect(result.props.children).toEqual([ @@ -189,11 +191,12 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = ReactTestUtils.createRenderer(); + var shallowRenderer = createRenderer(); shallowRenderer.render(); expect(shallowRenderer.getMountedInstance().someMethod()).toEqual(5); }); + // TODO (bvaughn) Fix this test it('can shallowly render components with contextTypes', () => { class SimpleComponent extends React.Component { static contextTypes = { @@ -205,7 +208,7 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = ReactTestUtils.createRenderer(); + var shallowRenderer = createRenderer(); var result = shallowRenderer.render(); expect(result).toEqual(
); }); @@ -229,7 +232,7 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = ReactTestUtils.createRenderer(); + var shallowRenderer = createRenderer(); shallowRenderer.render(); var result = shallowRenderer.getRenderOutput(); expect(result.type).toEqual('div'); @@ -241,6 +244,7 @@ describe('ReactTestUtils', () => { expect(result.props.className).toEqual('clicked'); }); + // TODO (bvaughn) Fix this test it('can setState in componentWillMount when shallow rendering', () => { class SimpleComponent extends React.Component { componentWillMount() { @@ -252,11 +256,12 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = ReactTestUtils.createRenderer(); + var shallowRenderer = createRenderer(); var result = shallowRenderer.render(); expect(result).toEqual(
doovy
); }); + // TODO (bvaughn) Fix this test it('can pass context when shallowly rendering', () => { class SimpleComponent extends React.Component { static contextTypes = { @@ -268,7 +273,7 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = ReactTestUtils.createRenderer(); + var shallowRenderer = createRenderer(); var result = shallowRenderer.render(, { name: 'foo', }); @@ -288,7 +293,7 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = ReactTestUtils.createRenderer(); + var shallowRenderer = createRenderer(); shallowRenderer.render(); expectDev(console.error.calls.count()).toBe(1); expect( @@ -511,6 +516,7 @@ describe('ReactTestUtils', () => { ); }); + // TODO (bvaughn) Fix this test it('should throw when attempting to use ReactTestUtils.Simulate with shallow rendering', () => { class SomeComponent extends React.Component { render() { @@ -523,7 +529,7 @@ describe('ReactTestUtils', () => { } var handler = jasmine.createSpy('spy'); - var shallowRenderer = ReactTestUtils.createRenderer(); + var shallowRenderer = createRenderer(); var result = shallowRenderer.render( , ); From 3dddc26140a5bc590aaeef27b9d8b8816cb51542 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Thu, 13 Apr 2017 18:59:52 -0700 Subject: [PATCH 06/37] Fixed top-level test comparison by wrapping output in ReactElement --- src/renderers/testing/ReactShallowRenderer.js | 21 ++++++++++--------- src/test/__tests__/ReactTestUtils-test.js | 3 --- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/renderers/testing/ReactShallowRenderer.js b/src/renderers/testing/ReactShallowRenderer.js index 99d2b902f4330..5c99965f40e57 100644 --- a/src/renderers/testing/ReactShallowRenderer.js +++ b/src/renderers/testing/ReactShallowRenderer.js @@ -36,19 +36,23 @@ function createShallowNodeMock() { class ReactShallowRenderer { getMountedInstance() { - return this._renderer ? this._renderer.getInstance() : null; // TODO (bvaughn) Is this the right instance? + return this._renderer ? this._renderer.getInstance() : null; } getRenderOutput() { if (this._renderer) { - // TODO (bvaughn) This isn't the right type for the root. - // It's not a ReactElement. - // So it can't be compared with the output of React.createElement(). const tree = this._renderer.toTree(); - return tree ? tree.rendered : null; - } else { - return null; + // Convert the rendered output to a ReactElement. + // This supports .toEqual() comparison for test elements. + if (tree && tree.rendered) { + return React.createElement( + tree.rendered.type, + tree.rendered.props, + tree.rendered.props.children, + ); + } } + return null; } render(element, context) { @@ -71,9 +75,6 @@ class ReactShallowRenderer { // TODO (bvaughn) This approach won't work with context // Should we create a wrapper context-provider in this case? // See ReactTestUtils-test 'can pass context when shallowly rendering' - - // TODO (bvaughn) How will updates (multiple render calls) work? - // See ReactTestUtils-test 'lets you update shallowly rendered components' this._renderer = ReactTestRenderer.create(element, { createNodeMock: createShallowNodeMock(), }); diff --git a/src/test/__tests__/ReactTestUtils-test.js b/src/test/__tests__/ReactTestUtils-test.js index fb630f22946e3..de18dfc7c410e 100644 --- a/src/test/__tests__/ReactTestUtils-test.js +++ b/src/test/__tests__/ReactTestUtils-test.js @@ -196,7 +196,6 @@ describe('ReactTestUtils', () => { expect(shallowRenderer.getMountedInstance().someMethod()).toEqual(5); }); - // TODO (bvaughn) Fix this test it('can shallowly render components with contextTypes', () => { class SimpleComponent extends React.Component { static contextTypes = { @@ -244,7 +243,6 @@ describe('ReactTestUtils', () => { expect(result.props.className).toEqual('clicked'); }); - // TODO (bvaughn) Fix this test it('can setState in componentWillMount when shallow rendering', () => { class SimpleComponent extends React.Component { componentWillMount() { @@ -516,7 +514,6 @@ describe('ReactTestUtils', () => { ); }); - // TODO (bvaughn) Fix this test it('should throw when attempting to use ReactTestUtils.Simulate with shallow rendering', () => { class SomeComponent extends React.Component { render() { From 713ed38110ec9811e97d2571911b88b907b500ce Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Thu, 13 Apr 2017 19:21:54 -0700 Subject: [PATCH 07/37] Added context rendering support to shallow renderer --- src/renderers/testing/ReactShallowRenderer.js | 48 +++++++++++++++---- src/test/__tests__/ReactTestUtils-test.js | 1 - 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/src/renderers/testing/ReactShallowRenderer.js b/src/renderers/testing/ReactShallowRenderer.js index 5c99965f40e57..04d31b8138276 100644 --- a/src/renderers/testing/ReactShallowRenderer.js +++ b/src/renderers/testing/ReactShallowRenderer.js @@ -34,6 +34,32 @@ function createShallowNodeMock() { }; } +function wrapElementWithContextProvider(element, context) { + function noop() {} + + const childContextTypes = Object.keys(context).reduce( + (context, key) => { + context[key] = noop; + return context; + }, + {}, + ); + + class ShallowRendererWrapper extends React.Component { + static childContextTypes = childContextTypes; + + getChildContext() { + return context; + } + + render() { + return this.props.children; + } + } + + return React.createElement(ShallowRendererWrapper, null, element); +} + class ReactShallowRenderer { getMountedInstance() { return this._renderer ? this._renderer.getInstance() : null; @@ -42,13 +68,18 @@ class ReactShallowRenderer { getRenderOutput() { if (this._renderer) { const tree = this._renderer.toTree(); - // Convert the rendered output to a ReactElement. - // This supports .toEqual() comparison for test elements. if (tree && tree.rendered) { + // If we created a context-wrapper then skip over it. + const element = tree.type.childContextTypes + ? tree.rendered.rendered + : tree.rendered; + + // Convert the rendered output to a ReactElement. + // This supports .toEqual() comparison for test elements. return React.createElement( - tree.rendered.type, - tree.rendered.props, - tree.rendered.props.children, + element.type, + element.props, + element.props.children, ); } } @@ -72,9 +103,10 @@ class ReactShallowRenderer { element.type, ); - // TODO (bvaughn) This approach won't work with context - // Should we create a wrapper context-provider in this case? - // See ReactTestUtils-test 'can pass context when shallowly rendering' + if (context && Object.keys(context).length) { + element = wrapElementWithContextProvider(element, context); + } + this._renderer = ReactTestRenderer.create(element, { createNodeMock: createShallowNodeMock(), }); diff --git a/src/test/__tests__/ReactTestUtils-test.js b/src/test/__tests__/ReactTestUtils-test.js index de18dfc7c410e..f476a849cba03 100644 --- a/src/test/__tests__/ReactTestUtils-test.js +++ b/src/test/__tests__/ReactTestUtils-test.js @@ -259,7 +259,6 @@ describe('ReactTestUtils', () => { expect(result).toEqual(
doovy
); }); - // TODO (bvaughn) Fix this test it('can pass context when shallowly rendering', () => { class SimpleComponent extends React.Component { static contextTypes = { From 579e7c942abc763c0563e0bcd52fb348809ea354 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Thu, 13 Apr 2017 19:41:47 -0700 Subject: [PATCH 08/37] Wordsmithing, minor cleanup --- .../classic/element/ReactElement.js | 2 +- src/renderers/testing/ReactShallowRenderer.js | 27 ++++++++----------- 2 files changed, 12 insertions(+), 17 deletions(-) diff --git a/src/isomorphic/classic/element/ReactElement.js b/src/isomorphic/classic/element/ReactElement.js index 6143d77c5868f..cecaf56b4a564 100644 --- a/src/isomorphic/classic/element/ReactElement.js +++ b/src/isomorphic/classic/element/ReactElement.js @@ -162,7 +162,7 @@ var ReactElement = function(type, key, ref, self, source, owner, props) { writable: false, value: source, }); - // Prevent the _owner attribute from breaking shallow renderer tests. + // _owner attribute would break shallow renderer equality checks. Object.defineProperty(element, '_owner', { configurable: false, enumerable: false, diff --git a/src/renderers/testing/ReactShallowRenderer.js b/src/renderers/testing/ReactShallowRenderer.js index 04d31b8138276..0826d7e532ae6 100644 --- a/src/renderers/testing/ReactShallowRenderer.js +++ b/src/renderers/testing/ReactShallowRenderer.js @@ -12,34 +12,31 @@ 'use strict'; -var React = require('react'); -var ReactTestRenderer = require('ReactTestRenderer'); +const React = require('react'); +const ReactTestRenderer = require('ReactTestRenderer'); -var emptyObject = require('fbjs/lib/emptyObject'); -var getNextDebugID = require('getNextDebugID'); -var invariant = require('fbjs/lib/invariant'); +const emptyFunction = require('fbjs/lib/emptyFunction'); +const emptyObject = require('fbjs/lib/emptyObject'); +const getNextDebugID = require('getNextDebugID'); +const invariant = require('fbjs/lib/invariant'); -const ShallowNodeMockComponent = ({children}) => { - return children ? React.Children.toArray(children) : []; -}; +const ShallowNodeMockComponent = ({children}) => React.Children.toArray(children); function createShallowNodeMock() { - var isFirst = true; - return element => { + let isFirst = true; + return function createNodeMock(element) { if (isFirst) { isFirst = false; return element.type; } return ShallowNodeMockComponent; - }; + } } function wrapElementWithContextProvider(element, context) { - function noop() {} - const childContextTypes = Object.keys(context).reduce( (context, key) => { - context[key] = noop; + context[key] = emptyFunction; return context; }, {}, @@ -47,11 +44,9 @@ function wrapElementWithContextProvider(element, context) { class ShallowRendererWrapper extends React.Component { static childContextTypes = childContextTypes; - getChildContext() { return context; } - render() { return this.props.children; } From b14307b1f35124cc982503a3a73e6acc59ea1526 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 14 Apr 2017 10:21:59 -0700 Subject: [PATCH 09/37] Don't run injections in stack ReactTestRenderer factory function --- .../testing/stack/ReactTestRendererStack.js | 47 ++++++++++++------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/src/renderers/testing/stack/ReactTestRendererStack.js b/src/renderers/testing/stack/ReactTestRendererStack.js index f87b0383bd2a8..c5f54b7120c1e 100644 --- a/src/renderers/testing/stack/ReactTestRendererStack.js +++ b/src/renderers/testing/stack/ReactTestRendererStack.js @@ -36,6 +36,31 @@ type ReactTestRendererJSON = { $$typeof?: any, }; +let injected = false; +function inject() { + if (injected) { + return; + } + + injected = true; + + ReactUpdates.injection.injectReconcileTransaction( + ReactTestReconcileTransaction, + ); + ReactUpdates.injection.injectBatchingStrategy(ReactDefaultBatchingStrategy); + + ReactHostComponent.injection.injectGenericComponentClass(ReactTestComponent); + ReactHostComponent.injection.injectTextComponentClass(ReactTestTextComponent); + ReactEmptyComponent.injection.injectEmptyComponentFactory(function() { + return new ReactTestEmptyComponent(); + }); + + ReactComponentEnvironment.injection.injectEnvironment({ + processChildrenUpdates: function() {}, + replaceNodeWithMarkup: function() {}, + }); +} + /** * Drill down (through composites and empty components) until we get a native or * native text component. @@ -134,24 +159,12 @@ Object.assign(ReactTestComponent.prototype, ReactMultiChild); // ============================================================================= -ReactUpdates.injection.injectReconcileTransaction( - ReactTestReconcileTransaction, -); -ReactUpdates.injection.injectBatchingStrategy(ReactDefaultBatchingStrategy); - -ReactHostComponent.injection.injectGenericComponentClass(ReactTestComponent); -ReactHostComponent.injection.injectTextComponentClass(ReactTestTextComponent); -ReactEmptyComponent.injection.injectEmptyComponentFactory(function() { - return new ReactTestEmptyComponent(); -}); - -ReactComponentEnvironment.injection.injectEnvironment({ - processChildrenUpdates: function() {}, - replaceNodeWithMarkup: function() {}, -}); - var ReactTestRenderer = { - create: ReactTestMount.render, + create: (...args) => { + inject(); + + return ReactTestMount.render(...args); + }, /* eslint-disable camelcase */ unstable_batchedUpdates: ReactUpdates.batchedUpdates, /* eslint-enable camelcase */ From 23a196d863f478a404683562312556c6ea50497b Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 14 Apr 2017 10:37:51 -0700 Subject: [PATCH 10/37] Disabled shallow rendering tests for stack since there is no stack-friendly shallow renderer --- src/test/__tests__/ReactTestUtils-test.js | 476 +++++++++++----------- 1 file changed, 242 insertions(+), 234 deletions(-) diff --git a/src/test/__tests__/ReactTestUtils-test.js b/src/test/__tests__/ReactTestUtils-test.js index f476a849cba03..117b0acb5aeab 100644 --- a/src/test/__tests__/ReactTestUtils-test.js +++ b/src/test/__tests__/ReactTestUtils-test.js @@ -11,6 +11,8 @@ 'use strict'; +var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); + var createRenderer; var PropTypes; var React; @@ -28,9 +30,32 @@ describe('ReactTestUtils', () => { ReactTestUtils = require('ReactTestUtils'); }); - it('should have shallow rendering', () => { - class SomeComponent extends React.Component { - render() { + // Shallow renderer only implemented for Fiber in 16+ + if (ReactDOMFeatureFlags.useFiber) { + it('should have shallow rendering', () => { + class SomeComponent extends React.Component { + render() { + return ( +
+ + +
+ ); + } + } + + var shallowRenderer = createRenderer(); + var result = shallowRenderer.render(); + + expect(result.type).toBe('div'); + expect(result.props.children).toEqual([ + , + , + ]); + }); + + it('should shallow render a functional component', () => { + function SomeComponent() { return (
@@ -38,269 +63,249 @@ describe('ReactTestUtils', () => {
); } - } - var shallowRenderer = createRenderer(); - var result = shallowRenderer.render(); + var shallowRenderer = createRenderer(); + var result = shallowRenderer.render(); - expect(result.type).toBe('div'); - expect(result.props.children).toEqual([ - , - , - ]); - }); - - it('should shallow render a functional component', () => { - function SomeComponent() { - return ( -
- - -
- ); - } - - var shallowRenderer = createRenderer(); - var result = shallowRenderer.render(); - - expect(result.type).toBe('div'); - expect(result.props.children).toEqual([ - , - , - ]); - }); + expect(result.type).toBe('div'); + expect(result.props.children).toEqual([ + , + , + ]); + }); - it('should throw for invalid elements', () => { - class SomeComponent extends React.Component { - render() { - return
; + it('should throw for invalid elements', () => { + class SomeComponent extends React.Component { + render() { + return
; + } } - } - var shallowRenderer = createRenderer(); - expect(() => shallowRenderer.render(SomeComponent)).toThrowError( - 'ReactShallowRenderer render(): Invalid component element. Instead of ' + - 'passing a component class, make sure to instantiate it by passing it ' + - 'to React.createElement.', - ); - expect(() => shallowRenderer.render(
)).toThrowError( - 'ReactShallowRenderer render(): Shallow rendering works only with ' + - 'custom components, not primitives (div). Instead of calling ' + - '`.render(el)` and inspecting the rendered output, look at `el.props` ' + - 'directly instead.', - ); - }); + var shallowRenderer = createRenderer(); + expect(() => shallowRenderer.render(SomeComponent)).toThrowError( + 'ReactShallowRenderer render(): Invalid component element. Instead of ' + + 'passing a component class, make sure to instantiate it by passing it ' + + 'to React.createElement.', + ); + expect(() => shallowRenderer.render(
)).toThrowError( + 'ReactShallowRenderer render(): Shallow rendering works only with ' + + 'custom components, not primitives (div). Instead of calling ' + + '`.render(el)` and inspecting the rendered output, look at `el.props` ' + + 'directly instead.', + ); + }); - it('should have shallow unmounting', () => { - var componentWillUnmount = jest.fn(); + it('should have shallow unmounting', () => { + var componentWillUnmount = jest.fn(); - class SomeComponent extends React.Component { - componentWillUnmount = componentWillUnmount; - render() { - return
; + class SomeComponent extends React.Component { + componentWillUnmount = componentWillUnmount; + render() { + return
; + } } - } - var shallowRenderer = createRenderer(); - shallowRenderer.render(); - shallowRenderer.unmount(); + var shallowRenderer = createRenderer(); + shallowRenderer.render(); + shallowRenderer.unmount(); - expect(componentWillUnmount).toBeCalled(); - }); + expect(componentWillUnmount).toBeCalled(); + }); - it('can shallow render to null', () => { - class SomeComponent extends React.Component { - render() { - return null; + it('can shallow render to null', () => { + class SomeComponent extends React.Component { + render() { + return null; + } } - } - var shallowRenderer = createRenderer(); - var result = shallowRenderer.render(); + var shallowRenderer = createRenderer(); + var result = shallowRenderer.render(); - expect(result).toBe(null); - }); + expect(result).toBe(null); + }); - it('can shallow render with a ref', () => { - class SomeComponent extends React.Component { - render() { - return
; + it('can shallow render with a ref', () => { + class SomeComponent extends React.Component { + render() { + return
; + } } - } - - var shallowRenderer = createRenderer(); - // Shouldn't crash. - shallowRenderer.render(); - }); - - it('lets you update shallowly rendered components', () => { - class SomeComponent extends React.Component { - state = {clicked: false}; - - onClick = () => { - this.setState({clicked: true}); - }; - render() { - var className = this.state.clicked ? 'was-clicked' : ''; + var shallowRenderer = createRenderer(); + // Shouldn't crash. + shallowRenderer.render(); + }); - if (this.props.aNew === 'prop') { - return ( - - Test link - - ); - } else { - return ( -
- - -
- ); + it('lets you update shallowly rendered components', () => { + class SomeComponent extends React.Component { + state = {clicked: false}; + + onClick = () => { + this.setState({clicked: true}); + }; + + render() { + var className = this.state.clicked ? 'was-clicked' : ''; + + if (this.props.aNew === 'prop') { + return ( + + Test link + + ); + } else { + return ( +
+ + +
+ ); + } } } - } - var shallowRenderer = createRenderer(); - var result = shallowRenderer.render(); - expect(result.type).toBe('div'); - expect(result.props.children).toEqual([ - , - , - ]); + var shallowRenderer = createRenderer(); + var result = shallowRenderer.render(); + expect(result.type).toBe('div'); + expect(result.props.children).toEqual([ + , + , + ]); - var updatedResult = shallowRenderer.render(); - expect(updatedResult.type).toBe('a'); + var updatedResult = shallowRenderer.render(); + expect(updatedResult.type).toBe('a'); - var mockEvent = {}; - updatedResult.props.onClick(mockEvent); + var mockEvent = {}; + updatedResult.props.onClick(mockEvent); - var updatedResultCausedByClick = shallowRenderer.getRenderOutput(); - expect(updatedResultCausedByClick.type).toBe('a'); - expect(updatedResultCausedByClick.props.className).toBe('was-clicked'); - }); + var updatedResultCausedByClick = shallowRenderer.getRenderOutput(); + expect(updatedResultCausedByClick.type).toBe('a'); + expect(updatedResultCausedByClick.props.className).toBe('was-clicked'); + }); - it('can access the mounted component instance', () => { - class SimpleComponent extends React.Component { - someMethod = () => { - return this.props.n; - }; + it('can access the mounted component instance', () => { + class SimpleComponent extends React.Component { + someMethod = () => { + return this.props.n; + }; - render() { - return
{this.props.n}
; + render() { + return
{this.props.n}
; + } } - } - var shallowRenderer = createRenderer(); - shallowRenderer.render(); - expect(shallowRenderer.getMountedInstance().someMethod()).toEqual(5); - }); + var shallowRenderer = createRenderer(); + shallowRenderer.render(); + expect(shallowRenderer.getMountedInstance().someMethod()).toEqual(5); + }); - it('can shallowly render components with contextTypes', () => { - class SimpleComponent extends React.Component { - static contextTypes = { - name: PropTypes.string, - }; + it('can shallowly render components with contextTypes', () => { + class SimpleComponent extends React.Component { + static contextTypes = { + name: PropTypes.string, + }; - render() { - return
; + render() { + return
; + } } - } - var shallowRenderer = createRenderer(); - var result = shallowRenderer.render(); - expect(result).toEqual(
); - }); + var shallowRenderer = createRenderer(); + var result = shallowRenderer.render(); + expect(result).toEqual(
); + }); - it('can shallowly render components with ref as function', () => { - class SimpleComponent extends React.Component { - state = {clicked: false}; + it('can shallowly render components with ref as function', () => { + class SimpleComponent extends React.Component { + state = {clicked: false}; - handleUserClick = () => { - this.setState({clicked: true}); - }; + handleUserClick = () => { + this.setState({clicked: true}); + }; - render() { - return ( -
{}} - onClick={this.handleUserClick} - className={this.state.clicked ? 'clicked' : ''} - /> - ); + render() { + return ( +
{}} + onClick={this.handleUserClick} + className={this.state.clicked ? 'clicked' : ''} + /> + ); + } } - } - var shallowRenderer = createRenderer(); - shallowRenderer.render(); - var result = shallowRenderer.getRenderOutput(); - expect(result.type).toEqual('div'); - expect(result.props.className).toEqual(''); - result.props.onClick(); + var shallowRenderer = createRenderer(); + shallowRenderer.render(); + var result = shallowRenderer.getRenderOutput(); + expect(result.type).toEqual('div'); + expect(result.props.className).toEqual(''); + result.props.onClick(); - result = shallowRenderer.getRenderOutput(); - expect(result.type).toEqual('div'); - expect(result.props.className).toEqual('clicked'); - }); + result = shallowRenderer.getRenderOutput(); + expect(result.type).toEqual('div'); + expect(result.props.className).toEqual('clicked'); + }); - it('can setState in componentWillMount when shallow rendering', () => { - class SimpleComponent extends React.Component { - componentWillMount() { - this.setState({groovy: 'doovy'}); - } + it('can setState in componentWillMount when shallow rendering', () => { + class SimpleComponent extends React.Component { + componentWillMount() { + this.setState({groovy: 'doovy'}); + } - render() { - return
{this.state.groovy}
; + render() { + return
{this.state.groovy}
; + } } - } - var shallowRenderer = createRenderer(); - var result = shallowRenderer.render(); - expect(result).toEqual(
doovy
); - }); + var shallowRenderer = createRenderer(); + var result = shallowRenderer.render(); + expect(result).toEqual(
doovy
); + }); - it('can pass context when shallowly rendering', () => { - class SimpleComponent extends React.Component { - static contextTypes = { - name: PropTypes.string, - }; + it('can pass context when shallowly rendering', () => { + class SimpleComponent extends React.Component { + static contextTypes = { + name: PropTypes.string, + }; - render() { - return
{this.context.name}
; + render() { + return
{this.context.name}
; + } } - } - var shallowRenderer = createRenderer(); - var result = shallowRenderer.render(, { - name: 'foo', + var shallowRenderer = createRenderer(); + var result = shallowRenderer.render(, { + name: 'foo', + }); + expect(result).toEqual(
foo
); }); - expect(result).toEqual(
foo
); - }); - it('can fail context when shallowly rendering', () => { - spyOn(console, 'error'); + it('can fail context when shallowly rendering', () => { + spyOn(console, 'error'); - class SimpleComponent extends React.Component { - static contextTypes = { - name: PropTypes.string.isRequired, - }; + class SimpleComponent extends React.Component { + static contextTypes = { + name: PropTypes.string.isRequired, + }; - render() { - return
{this.context.name}
; + render() { + return
{this.context.name}
; + } } - } - var shallowRenderer = createRenderer(); - shallowRenderer.render(); - expectDev(console.error.calls.count()).toBe(1); - expect( - console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)'), - ).toBe( - 'Warning: Failed context type: The context `name` is marked as ' + - 'required in `SimpleComponent`, but its value is `undefined`.\n' + - ' in SimpleComponent (at **)', - ); - }); + var shallowRenderer = createRenderer(); + shallowRenderer.render(); + expectDev(console.error.calls.count()).toBe(1); + expect( + console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)'), + ).toBe( + 'Warning: Failed context type: The context `name` is marked as ' + + 'required in `SimpleComponent`, but its value is `undefined`.\n' + + ' in SimpleComponent (at **)', + ); + }); + } // If fiber it('can scryRenderedDOMComponentsWithClass with TextComponent', () => { class Wrapper extends React.Component { @@ -513,29 +518,32 @@ describe('ReactTestUtils', () => { ); }); - it('should throw when attempting to use ReactTestUtils.Simulate with shallow rendering', () => { - class SomeComponent extends React.Component { - render() { - return ( -
- hello, world. -
- ); + // Shallow renderer only implemented for Fiber in 16+ + if (ReactDOMFeatureFlags.useFiber) { + it('should throw when attempting to use ReactTestUtils.Simulate with shallow rendering', () => { + class SomeComponent extends React.Component { + render() { + return ( +
+ hello, world. +
+ ); + } } - } - var handler = jasmine.createSpy('spy'); - var shallowRenderer = createRenderer(); - var result = shallowRenderer.render( - , - ); + var handler = jasmine.createSpy('spy'); + var shallowRenderer = createRenderer(); + var result = shallowRenderer.render( + , + ); - expect(() => ReactTestUtils.Simulate.click(result)).toThrowError( - 'TestUtils.Simulate expects a component instance and not a ReactElement.' + - 'TestUtils.Simulate will not work if you are using shallow rendering.', - ); - expect(handler).not.toHaveBeenCalled(); - }); + expect(() => ReactTestUtils.Simulate.click(result)).toThrowError( + 'TestUtils.Simulate expects a component instance and not a ReactElement.' + + 'TestUtils.Simulate will not work if you are using shallow rendering.', + ); + expect(handler).not.toHaveBeenCalled(); + }); + } it('should not warn when simulating events with extra properties', () => { spyOn(console, 'error'); From bd205a9b7880ac93943372988a88a8ea4e31441b Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 14 Apr 2017 11:01:05 -0700 Subject: [PATCH 11/37] Linting and prettier --- src/renderers/testing/ReactShallowRenderer.js | 13 ++++++------- src/renderers/testing/ReactTestRendererFiber.js | 1 - 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/renderers/testing/ReactShallowRenderer.js b/src/renderers/testing/ReactShallowRenderer.js index 0826d7e532ae6..8ba9aa162bfb3 100644 --- a/src/renderers/testing/ReactShallowRenderer.js +++ b/src/renderers/testing/ReactShallowRenderer.js @@ -16,11 +16,10 @@ const React = require('react'); const ReactTestRenderer = require('ReactTestRenderer'); const emptyFunction = require('fbjs/lib/emptyFunction'); -const emptyObject = require('fbjs/lib/emptyObject'); -const getNextDebugID = require('getNextDebugID'); const invariant = require('fbjs/lib/invariant'); -const ShallowNodeMockComponent = ({children}) => React.Children.toArray(children); +const ShallowNodeMockComponent = ({children}) => + React.Children.toArray(children); function createShallowNodeMock() { let isFirst = true; @@ -30,14 +29,14 @@ function createShallowNodeMock() { return element.type; } return ShallowNodeMockComponent; - } + }; } function wrapElementWithContextProvider(element, context) { const childContextTypes = Object.keys(context).reduce( - (context, key) => { - context[key] = emptyFunction; - return context; + (reduced, key) => { + reduced[key] = emptyFunction; + return reduced; }, {}, ); diff --git a/src/renderers/testing/ReactTestRendererFiber.js b/src/renderers/testing/ReactTestRendererFiber.js index d240a38349a09..19c2fee0b2d2f 100644 --- a/src/renderers/testing/ReactTestRendererFiber.js +++ b/src/renderers/testing/ReactTestRendererFiber.js @@ -13,7 +13,6 @@ 'use strict'; -var React = require('react'); var ReactFiberReconciler = require('ReactFiberReconciler'); var ReactGenericBatching = require('ReactGenericBatching'); var emptyObject = require('fbjs/lib/emptyObject'); From b091278ecb98269ab1c193f9936b0081c827c9f8 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 14 Apr 2017 12:54:55 -0700 Subject: [PATCH 12/37] Flow fixes --- .../testing/stack/ReactTestRendererStack.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/renderers/testing/stack/ReactTestRendererStack.js b/src/renderers/testing/stack/ReactTestRendererStack.js index c5f54b7120c1e..c2f59ba40fa40 100644 --- a/src/renderers/testing/stack/ReactTestRendererStack.js +++ b/src/renderers/testing/stack/ReactTestRendererStack.js @@ -25,8 +25,8 @@ var ReactTestTextComponent = require('ReactTestTextComponent'); var ReactTestEmptyComponent = require('ReactTestEmptyComponent'); var invariant = require('fbjs/lib/invariant'); -import type {ReactElement} from 'ReactElementType'; import type {ReactInstance} from 'ReactInstanceType'; +import type {TestRendererOptions} from 'ReactTestMount'; import type {ReactText} from 'ReactTypes'; type ReactTestRendererJSON = { @@ -79,13 +79,13 @@ function getRenderedHostOrTextFromComponent(component) { var UNSET = {}; class ReactTestComponent { - _currentElement: ReactElement; + _currentElement: ReactElement; _renderedChildren: null | Object; _topLevelWrapper: null | ReactInstance; _hostContainerInfo: null | Object; _nodeMock: Object; - constructor(element: ReactElement) { + constructor(element: ReactElement) { this._currentElement = element; this._renderedChildren = null; this._topLevelWrapper = null; @@ -107,7 +107,7 @@ class ReactTestComponent { } receiveComponent( - nextElement: ReactElement, + nextElement: ReactElement, transaction: ReactTestReconcileTransaction, context: Object, ) { @@ -138,7 +138,7 @@ class ReactTestComponent { } } var object: ReactTestRendererJSON = { - type: this._currentElement.type, + type: ((this._currentElement.type : any) : string), props: props, children: childrenJSON.length ? childrenJSON : null, }; @@ -160,10 +160,13 @@ Object.assign(ReactTestComponent.prototype, ReactMultiChild); // ============================================================================= var ReactTestRenderer = { - create: (...args) => { + create: ( + element: ReactElement, + options?: TestRendererOptions, + ) => { inject(); - return ReactTestMount.render(...args); + return ReactTestMount.render(element, options); }, /* eslint-disable camelcase */ unstable_batchedUpdates: ReactUpdates.batchedUpdates, From 631352deed29e8d435eb831bad9b50bad779cc57 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 14 Apr 2017 13:25:07 -0700 Subject: [PATCH 13/37] Tidying up the react-test-renderer/shallow bundle --- packages/react-test-renderer/package.json | 1 + packages/react-test-renderer/shallow.js | 3 +++ scripts/rollup/bundles.js | 4 ++-- scripts/rollup/results.json | 22 +++++++++++++++------- src/fb/ReactShallowRendererFBEntry.js | 0 src/test/ReactTestUtils.js | 2 +- 6 files changed, 22 insertions(+), 10 deletions(-) create mode 100644 packages/react-test-renderer/shallow.js delete mode 100644 src/fb/ReactShallowRendererFBEntry.js diff --git a/packages/react-test-renderer/package.json b/packages/react-test-renderer/package.json index a9cedf4ce70d9..86d25aff481d6 100644 --- a/packages/react-test-renderer/package.json +++ b/packages/react-test-renderer/package.json @@ -26,6 +26,7 @@ "PATENTS", "README.md", "index.js", + "shallow.js", "cjs/" ] } diff --git a/packages/react-test-renderer/shallow.js b/packages/react-test-renderer/shallow.js new file mode 100644 index 0000000000000..5f8f7e9e6f244 --- /dev/null +++ b/packages/react-test-renderer/shallow.js @@ -0,0 +1,3 @@ +'use strict'; + +module.exports = require('./cjs/react-test-renderer-shallow.development'); diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index deff49fc7c421..2c69446c2d157 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -395,8 +395,8 @@ const bundles = [ sourceMap: false, }, entry: 'src/renderers/testing/ReactShallowRenderer', - externals: ['react-dom'], - fbEntry: 'src/fb/ReactShallowRendererFBEntry', + externals: ['react-dom', 'prop-types/checkPropTypes'], + fbEntry: 'src/renderers/testing/ReactShallowRenderer', hasteName: 'ReactShallowRenderer', isRenderer: true, label: 'shallow-renderer', diff --git a/scripts/rollup/results.json b/scripts/rollup/results.json index 3f10353846c3e..8c976f2e93b02 100644 --- a/scripts/rollup/results.json +++ b/scripts/rollup/results.json @@ -1,5 +1,5 @@ { - "branch": "react-dom-test-utils", + "branch": "react-dom-test-utils-shallow-renderer", "bundleSizes": { "react.development.js (UMD_DEV)": { "size": 121412, @@ -126,8 +126,8 @@ "gzip": 55698 }, "ReactTestRendererStack-dev.js (FB_DEV)": { - "size": 151479, - "gzip": 34749 + "size": 151685, + "gzip": 34794 }, "react-noop-renderer.development.js (NODE_DEV)": { "size": 254092, @@ -142,12 +142,20 @@ "gzip": 122093 }, "react-dom-test-utils.development.js (NODE_DEV)": { - "size": 78532, - "gzip": 19766 + "size": 70094, + "gzip": 17717 }, "ReactTestUtils-dev.js (FB_DEV)": { - "size": 528, - "gzip": 328 + "size": 69835, + "gzip": 17705 + }, + "react-test-renderer-shallow.development.js (NODE_DEV)": { + "size": 411218, + "gzip": 88978 + }, + "ReactShallowRenderer-dev.js (FB_DEV)": { + "size": 405, + "gzip": 276 } } } \ No newline at end of file diff --git a/src/fb/ReactShallowRendererFBEntry.js b/src/fb/ReactShallowRendererFBEntry.js deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/src/test/ReactTestUtils.js b/src/test/ReactTestUtils.js index 5250081dddc50..24a8b888313c1 100644 --- a/src/test/ReactTestUtils.js +++ b/src/test/ReactTestUtils.js @@ -20,7 +20,7 @@ var ReactControlledComponent = require('ReactControlledComponent'); var ReactDOM = require('react-dom'); var ReactFiberTreeReflection = require('ReactFiberTreeReflection'); var ReactInstanceMap = require('ReactInstanceMap'); -var ReactShallowRenderer = require('ReactShallowRenderer'); // TODO (bvaughn) Remove this import before 16.0.0 +var ReactShallowRenderer = require('react-test-renderer/shallow'); // TODO (bvaughn) Remove this import before 16.0.0 var ReactTypeOfWork = require('ReactTypeOfWork'); var ReactGenericBatching = require('ReactGenericBatching'); var SyntheticEvent = require('SyntheticEvent'); From 39d3b1e439949372e68a26176ec8ea9819f8d538 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 14 Apr 2017 15:19:02 -0700 Subject: [PATCH 14/37] Fixed test-utils bundle not to include duplicate react-dom code --- packages/react-dom/test-utils.js | 2 +- scripts/rollup/bundles.js | 5 +- scripts/rollup/results.json | 60 +++++++++---------- src/renderers/dom/ReactDOM.js | 3 + src/renderers/dom/fiber/ReactDOMFiber.js | 3 + .../testing/stack/ReactTestRendererStack.js | 7 +-- src/test/ReactTestUtils.js | 10 ++-- 7 files changed, 46 insertions(+), 44 deletions(-) diff --git a/packages/react-dom/test-utils.js b/packages/react-dom/test-utils.js index db548be884aa3..31e2a261949ca 100644 --- a/packages/react-dom/test-utils.js +++ b/packages/react-dom/test-utils.js @@ -3,5 +3,5 @@ if (process.env.NODE_ENV === 'production') { throw Error('test-utils is not available in production mode.'); } else { - module.exports = require('./cjs/test-utils.development.js'); + module.exports = require('./cjs/react-dom-test-utils.development'); } diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 2c69446c2d157..8abd00da11f36 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -140,17 +140,16 @@ const bundles = [ 'prop-types/checkPropTypes', 'react', 'react-dom', - 'react-dom/test-utils', - 'react-test-renderer/shallow', // TODO (bvaughn) Remove this dependency before 16.0.0 ], fbEntry: 'src/test/ReactTestUtils', hasteName: 'ReactTestUtils', - isRenderer: false, + isRenderer: true, // TODO (bvaughn) This isn't a renderer but without this modules breaks us label: 'test-utils', manglePropertiesOnProd: false, name: 'react-dom/test-utils', paths: [ 'src/renderers/shared/**/*.js', + 'src/renderers/testing/**/*.js', // TODO (bvaughn) Remove this dependency before 16.0.0 'src/test/**/*.js', // ReactTestUtils is currently very coupled to DOM. 'src/ReactVersion.js', diff --git a/scripts/rollup/results.json b/scripts/rollup/results.json index 8c976f2e93b02..05ee21c37b97c 100644 --- a/scripts/rollup/results.json +++ b/scripts/rollup/results.json @@ -2,20 +2,20 @@ "branch": "react-dom-test-utils-shallow-renderer", "bundleSizes": { "react.development.js (UMD_DEV)": { - "size": 121412, - "gzip": 30500 + "size": 121635, + "gzip": 30538 }, "react.production.min.js (UMD_PROD)": { "size": 15679, "gzip": 5761 }, "react-dom.development.js (UMD_DEV)": { - "size": 583284, - "gzip": 134555 + "size": 583432, + "gzip": 134588 }, "react-dom.production.min.js (UMD_PROD)": { - "size": 120793, - "gzip": 38119 + "size": 120864, + "gzip": 38152 }, "react-dom-server.development.js (UMD_DEV)": { "size": 495516, @@ -34,44 +34,44 @@ "gzip": 28991 }, "react.development.js (NODE_DEV)": { - "size": 70222, - "gzip": 17579 + "size": 70445, + "gzip": 17615 }, "react.production.min.js (NODE_PROD)": { "size": 9220, "gzip": 3621 }, "React-dev.js (FB_DEV)": { - "size": 72079, - "gzip": 18217 + "size": 72302, + "gzip": 18255 }, "React-prod.js (FB_PROD)": { - "size": 36606, - "gzip": 9248 + "size": 36601, + "gzip": 9246 }, "ReactDOMStack-dev.js (FB_DEV)": { - "size": 492312, - "gzip": 117399 + "size": 492460, + "gzip": 117423 }, "ReactDOMStack-prod.js (FB_PROD)": { - "size": 352924, - "gzip": 84697 + "size": 353084, + "gzip": 84725 }, "react-dom.development.js (NODE_DEV)": { - "size": 542280, - "gzip": 125179 + "size": 542428, + "gzip": 125212 }, "react-dom.production.min.js (NODE_PROD)": { - "size": 116978, - "gzip": 36758 + "size": 117049, + "gzip": 36790 }, "ReactDOMFiber-dev.js (FB_DEV)": { - "size": 543121, - "gzip": 125573 + "size": 543269, + "gzip": 125597 }, "ReactDOMFiber-prod.js (FB_PROD)": { - "size": 407761, - "gzip": 93608 + "size": 407921, + "gzip": 93632 }, "react-dom-server.development.js (NODE_DEV)": { "size": 445547, @@ -142,20 +142,20 @@ "gzip": 122093 }, "react-dom-test-utils.development.js (NODE_DEV)": { - "size": 70094, - "gzip": 17717 + "size": 439031, + "gzip": 95802 }, "ReactTestUtils-dev.js (FB_DEV)": { - "size": 69835, - "gzip": 17705 + "size": 438009, + "gzip": 95669 }, "react-test-renderer-shallow.development.js (NODE_DEV)": { "size": 411218, "gzip": 88978 }, "ReactShallowRenderer-dev.js (FB_DEV)": { - "size": 405, - "gzip": 276 + "size": 410218, + "gzip": 88833 } } } \ No newline at end of file diff --git a/src/renderers/dom/ReactDOM.js b/src/renderers/dom/ReactDOM.js index 2ddf727c88f43..601ce0d924b33 100644 --- a/src/renderers/dom/ReactDOM.js +++ b/src/renderers/dom/ReactDOM.js @@ -43,6 +43,9 @@ var ReactDOM = { // For TapEventPlugin which is popular in open source EventPluginHub: require('EventPluginHub'), // Used by test-utils + EventPluginRegistry: require('EventPluginRegistry'), + EventPropagators: require('EventPropagators'), + ReactControlledComponent: require('ReactControlledComponent'), ReactDOMComponentTree, ReactBrowserEventEmitter: require('ReactBrowserEventEmitter'), }, diff --git a/src/renderers/dom/fiber/ReactDOMFiber.js b/src/renderers/dom/fiber/ReactDOMFiber.js index 778e0e733a61f..d3ed8381ca2d4 100644 --- a/src/renderers/dom/fiber/ReactDOMFiber.js +++ b/src/renderers/dom/fiber/ReactDOMFiber.js @@ -543,6 +543,9 @@ var ReactDOM = { // For TapEventPlugin which is popular in open source EventPluginHub: require('EventPluginHub'), // Used by test-utils + EventPluginRegistry: require('EventPluginRegistry'), + EventPropagators: require('EventPropagators'), + ReactControlledComponent, ReactDOMComponentTree, ReactBrowserEventEmitter, }, diff --git a/src/renderers/testing/stack/ReactTestRendererStack.js b/src/renderers/testing/stack/ReactTestRendererStack.js index c2f59ba40fa40..bad8b3589d827 100644 --- a/src/renderers/testing/stack/ReactTestRendererStack.js +++ b/src/renderers/testing/stack/ReactTestRendererStack.js @@ -138,7 +138,7 @@ class ReactTestComponent { } } var object: ReactTestRendererJSON = { - type: ((this._currentElement.type : any) : string), + type: ((this._currentElement.type: any): string), props: props, children: childrenJSON.length ? childrenJSON : null, }; @@ -160,10 +160,7 @@ Object.assign(ReactTestComponent.prototype, ReactMultiChild); // ============================================================================= var ReactTestRenderer = { - create: ( - element: ReactElement, - options?: TestRendererOptions, - ) => { + create: (element: ReactElement, options?: TestRendererOptions) => { inject(); return ReactTestMount.render(element, options); diff --git a/src/test/ReactTestUtils.js b/src/test/ReactTestUtils.js index 24a8b888313c1..8ca013fb96938 100644 --- a/src/test/ReactTestUtils.js +++ b/src/test/ReactTestUtils.js @@ -12,15 +12,11 @@ 'use strict'; var EventConstants = require('EventConstants'); -var EventPluginHub = require('EventPluginHub'); -var EventPluginRegistry = require('EventPluginRegistry'); -var EventPropagators = require('EventPropagators'); var React = require('react'); -var ReactControlledComponent = require('ReactControlledComponent'); var ReactDOM = require('react-dom'); var ReactFiberTreeReflection = require('ReactFiberTreeReflection'); var ReactInstanceMap = require('ReactInstanceMap'); -var ReactShallowRenderer = require('react-test-renderer/shallow'); // TODO (bvaughn) Remove this import before 16.0.0 +var ReactShallowRenderer = require('ReactShallowRenderer'); // TODO (bvaughn) Remove this import before 16.0.0 var ReactTypeOfWork = require('ReactTypeOfWork'); var ReactGenericBatching = require('ReactGenericBatching'); var SyntheticEvent = require('SyntheticEvent'); @@ -30,6 +26,10 @@ var warning = require('fbjs/lib/warning'); var {findDOMNode} = ReactDOM; var { + EventPluginHub, + EventPluginRegistry, + EventPropagators, + ReactControlledComponent, ReactDOMComponentTree, ReactBrowserEventEmitter, } = ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; From 1ad916614346e7547d04ba0486a22be57d03257b Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 14 Apr 2017 15:36:29 -0700 Subject: [PATCH 15/37] Shallow renderer no longer inlines ReactTestRenderer --- scripts/rollup/bundles.js | 5 +++-- scripts/rollup/results.json | 16 ++++++++-------- src/renderers/testing/ReactShallowRenderer.js | 2 +- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 8abd00da11f36..4add2a468f89c 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -140,10 +140,11 @@ const bundles = [ 'prop-types/checkPropTypes', 'react', 'react-dom', + 'react-test-renderer', // TODO (bvaughn) Remove this dependency before 16.0.0 ], fbEntry: 'src/test/ReactTestUtils', hasteName: 'ReactTestUtils', - isRenderer: true, // TODO (bvaughn) This isn't a renderer but without this modules breaks us + isRenderer: true, label: 'test-utils', manglePropertiesOnProd: false, name: 'react-dom/test-utils', @@ -394,7 +395,7 @@ const bundles = [ sourceMap: false, }, entry: 'src/renderers/testing/ReactShallowRenderer', - externals: ['react-dom', 'prop-types/checkPropTypes'], + externals: ['react-dom', 'prop-types/checkPropTypes', 'react-test-renderer'], fbEntry: 'src/renderers/testing/ReactShallowRenderer', hasteName: 'ReactShallowRenderer', isRenderer: true, diff --git a/scripts/rollup/results.json b/scripts/rollup/results.json index 05ee21c37b97c..67b52b24a0fc4 100644 --- a/scripts/rollup/results.json +++ b/scripts/rollup/results.json @@ -142,20 +142,20 @@ "gzip": 122093 }, "react-dom-test-utils.development.js (NODE_DEV)": { - "size": 439031, - "gzip": 95802 + "size": 61030, + "gzip": 15603 }, "ReactTestUtils-dev.js (FB_DEV)": { - "size": 438009, - "gzip": 95669 + "size": 60942, + "gzip": 15614 }, "react-test-renderer-shallow.development.js (NODE_DEV)": { - "size": 411218, - "gzip": 88978 + "size": 4896, + "gzip": 1682 }, "ReactShallowRenderer-dev.js (FB_DEV)": { - "size": 410218, - "gzip": 88833 + "size": 4902, + "gzip": 1684 } } } \ No newline at end of file diff --git a/src/renderers/testing/ReactShallowRenderer.js b/src/renderers/testing/ReactShallowRenderer.js index 8ba9aa162bfb3..6f607734ea998 100644 --- a/src/renderers/testing/ReactShallowRenderer.js +++ b/src/renderers/testing/ReactShallowRenderer.js @@ -13,7 +13,7 @@ 'use strict'; const React = require('react'); -const ReactTestRenderer = require('ReactTestRenderer'); +const ReactTestRenderer = require('react-test-renderer'); const emptyFunction = require('fbjs/lib/emptyFunction'); const invariant = require('fbjs/lib/invariant'); From 45fded4352ef58732add56e0d75e0bd5f4e85e2a Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 14 Apr 2017 15:39:52 -0700 Subject: [PATCH 16/37] Added production mode error for shallow renderer --- packages/react-test-renderer/shallow.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/react-test-renderer/shallow.js b/packages/react-test-renderer/shallow.js index 5f8f7e9e6f244..b75c643cf0eca 100644 --- a/packages/react-test-renderer/shallow.js +++ b/packages/react-test-renderer/shallow.js @@ -1,3 +1,7 @@ 'use strict'; -module.exports = require('./cjs/react-test-renderer-shallow.development'); +if (process.env.NODE_ENV === 'production') { + throw Error('shallow renderer is not available in production mode.'); +} else { + module.exports = require('./cjs/react-test-renderer-shallow.development'); +} From ec069f4e075e9641cd050d9c34aeb77e8bf525ee Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Fri, 14 Apr 2017 16:11:04 -0700 Subject: [PATCH 17/37] Added react-test-renderer stub for testing --- scripts/rollup/bundles.js | 6 +++++- src/node_modules/react-test-renderer/index.js | 9 +++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 src/node_modules/react-test-renderer/index.js diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 4add2a468f89c..4d19b330f4469 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -395,7 +395,11 @@ const bundles = [ sourceMap: false, }, entry: 'src/renderers/testing/ReactShallowRenderer', - externals: ['react-dom', 'prop-types/checkPropTypes', 'react-test-renderer'], + externals: [ + 'react-dom', + 'prop-types/checkPropTypes', + 'react-test-renderer', + ], fbEntry: 'src/renderers/testing/ReactShallowRenderer', hasteName: 'ReactShallowRenderer', isRenderer: true, diff --git a/src/node_modules/react-test-renderer/index.js b/src/node_modules/react-test-renderer/index.js new file mode 100644 index 0000000000000..a11b1836c7b0d --- /dev/null +++ b/src/node_modules/react-test-renderer/index.js @@ -0,0 +1,9 @@ +/** + * Copyright 2016-present Facebook. All Rights Reserved. + * + * @flow + */ + +'use strict'; + +module.exports = require('ReactTestRenderer'); From 037bda9994b43265daa667ed023e16e747fe42e0 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sat, 15 Apr 2017 08:45:14 -0700 Subject: [PATCH 18/37] Tightened up check for ShallowRendererWrapper type --- src/renderers/testing/ReactShallowRenderer.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/renderers/testing/ReactShallowRenderer.js b/src/renderers/testing/ReactShallowRenderer.js index 6f607734ea998..470b369a8e68e 100644 --- a/src/renderers/testing/ReactShallowRenderer.js +++ b/src/renderers/testing/ReactShallowRenderer.js @@ -32,6 +32,7 @@ function createShallowNodeMock() { }; } +// TODO Remove this wrapper if/when context parameter is removed function wrapElementWithContextProvider(element, context) { const childContextTypes = Object.keys(context).reduce( (reduced, key) => { @@ -42,6 +43,7 @@ function wrapElementWithContextProvider(element, context) { ); class ShallowRendererWrapper extends React.Component { + static __shallowRendererWrapperFlag = true; static childContextTypes = childContextTypes; getChildContext() { return context; @@ -64,7 +66,7 @@ class ReactShallowRenderer { const tree = this._renderer.toTree(); if (tree && tree.rendered) { // If we created a context-wrapper then skip over it. - const element = tree.type.childContextTypes + const element = tree.type.__shallowRendererWrapperFlag ? tree.rendered.rendered : tree.rendered; @@ -80,6 +82,7 @@ class ReactShallowRenderer { return null; } + // TODO We should probably remove support for the non-standard context parameter render(element, context) { invariant( React.isValidElement(element), @@ -97,6 +100,7 @@ class ReactShallowRenderer { element.type, ); + // TODO Remove this wrapper if/when context parameter is removed if (context && Object.keys(context).length) { element = wrapElementWithContextProvider(element, context); } From c65a6d09ac633ce79d5df5dfe23c46c21e45207d Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sat, 15 Apr 2017 08:48:26 -0700 Subject: [PATCH 19/37] Explicit error if test renderer is used in production mode --- packages/react-test-renderer/index.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/react-test-renderer/index.js b/packages/react-test-renderer/index.js index 3a0cbe1a0417c..48895ee7cc19c 100644 --- a/packages/react-test-renderer/index.js +++ b/packages/react-test-renderer/index.js @@ -1,3 +1,7 @@ 'use strict'; -module.exports = require('./cjs/react-test-renderer.development'); +if (process.env.NODE_ENV === 'production') { + throw Error('test renderer is not available in production mode.'); +} else { + module.exports = require('./cjs/react-test-renderer.development'); +} From a6c8ed8b46ea1d38bed9cd6f06164687ff01d3c9 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sat, 15 Apr 2017 08:54:30 -0700 Subject: [PATCH 20/37] Reverted modifications to results.json --- scripts/rollup/results.json | 122 +++++++++++++++--------------------- 1 file changed, 51 insertions(+), 71 deletions(-) diff --git a/scripts/rollup/results.json b/scripts/rollup/results.json index 67b52b24a0fc4..01bee146eedc6 100644 --- a/scripts/rollup/results.json +++ b/scripts/rollup/results.json @@ -1,113 +1,113 @@ { - "branch": "react-dom-test-utils-shallow-renderer", + "branch": "master", "bundleSizes": { "react.development.js (UMD_DEV)": { - "size": 121635, - "gzip": 30538 + "size": 121454, + "gzip": 30515 }, "react.production.min.js (UMD_PROD)": { - "size": 15679, - "gzip": 5761 + "size": 15685, + "gzip": 5765 }, "react-dom.development.js (UMD_DEV)": { - "size": 583432, - "gzip": 134588 + "size": 583190, + "gzip": 134534 }, "react-dom.production.min.js (UMD_PROD)": { - "size": 120864, - "gzip": 38152 + "size": 120740, + "gzip": 38094 }, "react-dom-server.development.js (UMD_DEV)": { - "size": 495516, - "gzip": 119682 + "size": 495558, + "gzip": 119685 }, "react-dom-server.production.min.js (UMD_PROD)": { "size": 107033, "gzip": 33273 }, "react-art.development.js (UMD_DEV)": { - "size": 342568, - "gzip": 76773 + "size": 342608, + "gzip": 76782 }, "react-art.production.min.js (UMD_PROD)": { "size": 95013, "gzip": 28991 }, "react.development.js (NODE_DEV)": { - "size": 70445, - "gzip": 17615 + "size": 70266, + "gzip": 17594 }, "react.production.min.js (NODE_PROD)": { - "size": 9220, - "gzip": 3621 + "size": 9226, + "gzip": 3628 }, "React-dev.js (FB_DEV)": { - "size": 72302, - "gzip": 18255 + "size": 72123, + "gzip": 18231 }, "React-prod.js (FB_PROD)": { - "size": 36601, - "gzip": 9246 + "size": 36643, + "gzip": 9256 }, "ReactDOMStack-dev.js (FB_DEV)": { - "size": 492460, - "gzip": 117423 + "size": 522763, + "gzip": 124727 }, "ReactDOMStack-prod.js (FB_PROD)": { - "size": 353084, - "gzip": 84725 + "size": 352776, + "gzip": 84675 }, "react-dom.development.js (NODE_DEV)": { - "size": 542428, - "gzip": 125212 + "size": 542188, + "gzip": 125158 }, "react-dom.production.min.js (NODE_PROD)": { - "size": 117049, - "gzip": 36790 + "size": 116925, + "gzip": 36732 }, "ReactDOMFiber-dev.js (FB_DEV)": { - "size": 543269, - "gzip": 125597 + "size": 797235, + "gzip": 184122 }, "ReactDOMFiber-prod.js (FB_PROD)": { - "size": 407921, - "gzip": 93632 + "size": 407613, + "gzip": 93586 }, "react-dom-server.development.js (NODE_DEV)": { - "size": 445547, - "gzip": 107594 + "size": 445589, + "gzip": 107597 }, "react-dom-server.production.min.js (NODE_PROD)": { "size": 101411, "gzip": 31292 }, "ReactDOMServerStack-dev.js (FB_DEV)": { - "size": 444239, - "gzip": 107440 + "size": 444281, + "gzip": 107443 }, "ReactDOMServerStack-prod.js (FB_PROD)": { "size": 334166, "gzip": 80444 }, "ReactARTStack-dev.js (FB_DEV)": { - "size": 142944, - "gzip": 32705 + "size": 142986, + "gzip": 32714 }, "ReactARTStack-prod.js (FB_PROD)": { "size": 101143, "gzip": 22993 }, "react-art.development.js (NODE_DEV)": { - "size": 265008, - "gzip": 56923 + "size": 265052, + "gzip": 56927 }, "react-art.production.min.js (NODE_PROD)": { "size": 56628, "gzip": 17152 }, "ReactARTFiber-dev.js (FB_DEV)": { - "size": 264186, - "gzip": 56732 + "size": 264230, + "gzip": 56736 }, "ReactARTFiber-prod.js (FB_PROD)": { "size": 205336, @@ -122,40 +122,20 @@ "gzip": 84001 }, "ReactTestRendererFiber-dev.js (FB_DEV)": { - "size": 262095, - "gzip": 55698 + "size": 262139, + "gzip": 55704 }, "ReactTestRendererStack-dev.js (FB_DEV)": { - "size": 151685, - "gzip": 34794 + "size": 151521, + "gzip": 34765 }, "react-noop-renderer.development.js (NODE_DEV)": { - "size": 254092, - "gzip": 53674 + "size": 254136, + "gzip": 53682 }, "react-test-renderer.development.js (NODE_DEV)": { - "size": 262926, - "gzip": 55887 - }, - "react-test-utils.development.js (NODE_DEV)": { - "size": 510240, - "gzip": 122093 - }, - "react-dom-test-utils.development.js (NODE_DEV)": { - "size": 61030, - "gzip": 15603 - }, - "ReactTestUtils-dev.js (FB_DEV)": { - "size": 60942, - "gzip": 15614 - }, - "react-test-renderer-shallow.development.js (NODE_DEV)": { - "size": 4896, - "gzip": 1682 - }, - "ReactShallowRenderer-dev.js (FB_DEV)": { - "size": 4902, - "gzip": 1684 + "size": 262970, + "gzip": 55891 } } } \ No newline at end of file From c76235b3ae6e70181e78124ad1040eb667cab5bb Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sat, 15 Apr 2017 09:23:12 -0700 Subject: [PATCH 21/37] Moved ReactTestUtils from src/test to src/renderers/dom/test --- scripts/fiber/tests-passing.txt | 52 +++++++++---------- scripts/rollup/bundles.js | 8 ++- .../dom}/test/ReactTestUtils.js | 0 .../test/__tests__/ReactTestUtils-test.js | 0 4 files changed, 29 insertions(+), 31 deletions(-) rename src/{ => renderers/dom}/test/ReactTestUtils.js (100%) rename src/{ => renderers/dom}/test/__tests__/ReactTestUtils-test.js (100%) diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt index 0cf8d024093b5..4a23c6a5379ae 100644 --- a/scripts/fiber/tests-passing.txt +++ b/scripts/fiber/tests-passing.txt @@ -1560,6 +1560,32 @@ src/renderers/dom/shared/wrappers/__tests__/ReactDOMTextarea-test.js * should warn if value is null * should warn if value and defaultValue are specified +src/renderers/dom/test/__tests__/ReactTestUtils-test.js +* should have shallow rendering +* should shallow render a functional component +* should throw for invalid elements +* should have shallow unmounting +* can shallow render to null +* can shallow render with a ref +* lets you update shallowly rendered components +* can access the mounted component instance +* can shallowly render components with contextTypes +* can shallowly render components with ref as function +* can setState in componentWillMount when shallow rendering +* can pass context when shallowly rendering +* can fail context when shallowly rendering +* can scryRenderedDOMComponentsWithClass with TextComponent +* can scryRenderedDOMComponentsWithClass with className contains \n +* can scryRenderedDOMComponentsWithClass with multiple classes +* traverses children in the correct order +* should support injected wrapper components as DOM components +* should change the value of an input field +* should change the value of an input field in a component +* should throw when attempting to use ReactTestUtils.Simulate with shallow rendering +* should not warn when simulating events with extra properties +* can scry with stateless components involved +* should set the type of the event + src/renderers/native/__tests__/ReactNativeAttributePayload-test.js * should work with simple example * should skip fields that are equal @@ -1868,32 +1894,6 @@ src/shared/utils/__tests__/PooledClass-test.js src/shared/utils/__tests__/reactProdInvariant-test.js * should throw with the correct number of `%s`s in the URL -src/test/__tests__/ReactTestUtils-test.js -* should have shallow rendering -* should shallow render a functional component -* should throw for invalid elements -* should have shallow unmounting -* can shallow render to null -* can shallow render with a ref -* lets you update shallowly rendered components -* can access the mounted component instance -* can shallowly render components with contextTypes -* can shallowly render components with ref as function -* can setState in componentWillMount when shallow rendering -* can pass context when shallowly rendering -* can fail context when shallowly rendering -* can scryRenderedDOMComponentsWithClass with TextComponent -* can scryRenderedDOMComponentsWithClass with className contains \n -* can scryRenderedDOMComponentsWithClass with multiple classes -* traverses children in the correct order -* should support injected wrapper components as DOM components -* should change the value of an input field -* should change the value of an input field in a component -* should throw when attempting to use ReactTestUtils.Simulate with shallow rendering -* should not warn when simulating events with extra properties -* can scry with stateless components involved -* should set the type of the event - src/test/__tests__/reactComponentExpect-test.js * should match composite components * should match empty DOM components diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 4d19b330f4469..65b9a48252f80 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -89,7 +89,6 @@ const bundles = [ paths: [ 'src/renderers/dom/**/*.js', 'src/renderers/shared/**/*.js', - 'src/test/**/*.js', // ReactTestUtils is currently very coupled to DOM. 'src/ReactVersion.js', 'src/shared/**/*.js', @@ -117,7 +116,6 @@ const bundles = [ paths: [ 'src/renderers/dom/**/*.js', 'src/renderers/shared/**/*.js', - 'src/test/**/*.js', // ReactTestUtils is currently very coupled to DOM. 'src/ReactVersion.js', 'src/shared/**/*.js', @@ -134,7 +132,7 @@ const bundles = [ moduleName: 'ReactTestUtils', sourceMap: false, }, - entry: 'src/test/ReactTestUtils', + entry: 'src/renderers/dom/test/ReactTestUtils', externals: [ 'prop-types', 'prop-types/checkPropTypes', @@ -142,16 +140,16 @@ const bundles = [ 'react-dom', 'react-test-renderer', // TODO (bvaughn) Remove this dependency before 16.0.0 ], - fbEntry: 'src/test/ReactTestUtils', + fbEntry: 'src/renderers/dom/test/ReactTestUtils', hasteName: 'ReactTestUtils', isRenderer: true, label: 'test-utils', manglePropertiesOnProd: false, name: 'react-dom/test-utils', paths: [ + 'src/renderers/dom/test/**/*.js', 'src/renderers/shared/**/*.js', 'src/renderers/testing/**/*.js', // TODO (bvaughn) Remove this dependency before 16.0.0 - 'src/test/**/*.js', // ReactTestUtils is currently very coupled to DOM. 'src/ReactVersion.js', 'src/shared/**/*.js', diff --git a/src/test/ReactTestUtils.js b/src/renderers/dom/test/ReactTestUtils.js similarity index 100% rename from src/test/ReactTestUtils.js rename to src/renderers/dom/test/ReactTestUtils.js diff --git a/src/test/__tests__/ReactTestUtils-test.js b/src/renderers/dom/test/__tests__/ReactTestUtils-test.js similarity index 100% rename from src/test/__tests__/ReactTestUtils-test.js rename to src/renderers/dom/test/__tests__/ReactTestUtils-test.js From f4b4c36e7f7abf679120d75fd1eaee5f6919f7d7 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Sun, 16 Apr 2017 09:18:57 -0700 Subject: [PATCH 22/37] Added test to verify shallow renderer is shallow to ReactTestUtils-test --- .../dom/test/__tests__/ReactTestUtils-test.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/renderers/dom/test/__tests__/ReactTestUtils-test.js b/src/renderers/dom/test/__tests__/ReactTestUtils-test.js index 117b0acb5aeab..b68200c55a13e 100644 --- a/src/renderers/dom/test/__tests__/ReactTestUtils-test.js +++ b/src/renderers/dom/test/__tests__/ReactTestUtils-test.js @@ -32,6 +32,18 @@ describe('ReactTestUtils', () => { // Shallow renderer only implemented for Fiber in 16+ if (ReactDOMFeatureFlags.useFiber) { + it('should only render 1 level deep', () => { + function Parent() { + return
+ } + function Child() { + throw Error('This component should not render') + } + + var shallowRenderer = createRenderer(); + shallowRenderer.render(React.createElement(Parent)); + }); + it('should have shallow rendering', () => { class SomeComponent extends React.Component { render() { From e71493d008e8fb42b72866094ffeba661d06985f Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 17 Apr 2017 11:37:19 -0700 Subject: [PATCH 23/37] New ReactShallowRenderer implementation Replaced shallow renderer based on test renderer with a custom, isolated implementation that's stack and fiber compatible --- scripts/fiber/tests-passing.txt | 2 + .../classic/element/ReactElement.js | 7 - .../dom/test/__tests__/ReactTestUtils-test.js | 522 +++++++++--------- src/renderers/testing/ReactShallowRenderer.js | 217 +++++--- 4 files changed, 420 insertions(+), 328 deletions(-) diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt index 4a23c6a5379ae..cdfb5dcc06a6b 100644 --- a/scripts/fiber/tests-passing.txt +++ b/scripts/fiber/tests-passing.txt @@ -1561,6 +1561,7 @@ src/renderers/dom/shared/wrappers/__tests__/ReactDOMTextarea-test.js * should warn if value and defaultValue are specified src/renderers/dom/test/__tests__/ReactTestUtils-test.js +* should only render 1 level deep * should have shallow rendering * should shallow render a functional component * should throw for invalid elements @@ -1574,6 +1575,7 @@ src/renderers/dom/test/__tests__/ReactTestUtils-test.js * can setState in componentWillMount when shallow rendering * can pass context when shallowly rendering * can fail context when shallowly rendering +* should warn about propTypes (but only once) * can scryRenderedDOMComponentsWithClass with TextComponent * can scryRenderedDOMComponentsWithClass with className contains \n * can scryRenderedDOMComponentsWithClass with multiple classes diff --git a/src/isomorphic/classic/element/ReactElement.js b/src/isomorphic/classic/element/ReactElement.js index cecaf56b4a564..b47d5e247f545 100644 --- a/src/isomorphic/classic/element/ReactElement.js +++ b/src/isomorphic/classic/element/ReactElement.js @@ -162,13 +162,6 @@ var ReactElement = function(type, key, ref, self, source, owner, props) { writable: false, value: source, }); - // _owner attribute would break shallow renderer equality checks. - Object.defineProperty(element, '_owner', { - configurable: false, - enumerable: false, - writable: false, - value: owner, - }); } else { element._store.validated = false; element._self = self; diff --git a/src/renderers/dom/test/__tests__/ReactTestUtils-test.js b/src/renderers/dom/test/__tests__/ReactTestUtils-test.js index b68200c55a13e..23c3f41970e9b 100644 --- a/src/renderers/dom/test/__tests__/ReactTestUtils-test.js +++ b/src/renderers/dom/test/__tests__/ReactTestUtils-test.js @@ -11,8 +11,6 @@ 'use strict'; -var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); - var createRenderer; var PropTypes; var React; @@ -30,44 +28,21 @@ describe('ReactTestUtils', () => { ReactTestUtils = require('ReactTestUtils'); }); - // Shallow renderer only implemented for Fiber in 16+ - if (ReactDOMFeatureFlags.useFiber) { - it('should only render 1 level deep', () => { - function Parent() { - return
- } - function Child() { - throw Error('This component should not render') - } - - var shallowRenderer = createRenderer(); - shallowRenderer.render(React.createElement(Parent)); - }); - - it('should have shallow rendering', () => { - class SomeComponent extends React.Component { - render() { - return ( -
- - -
- ); - } - } - - var shallowRenderer = createRenderer(); - var result = shallowRenderer.render(); + it('should only render 1 level deep', () => { + function Parent() { + return
; + } + function Child() { + throw Error('This component should not render'); + } - expect(result.type).toBe('div'); - expect(result.props.children).toEqual([ - , - , - ]); - }); + var shallowRenderer = createRenderer(); + shallowRenderer.render(React.createElement(Parent)); + }); - it('should shallow render a functional component', () => { - function SomeComponent() { + it('should have shallow rendering', () => { + class SomeComponent extends React.Component { + render() { return (
@@ -75,249 +50,295 @@ describe('ReactTestUtils', () => {
); } + } - var shallowRenderer = createRenderer(); - var result = shallowRenderer.render(); + var shallowRenderer = createRenderer(); + var result = shallowRenderer.render(); - expect(result.type).toBe('div'); - expect(result.props.children).toEqual([ - , - , - ]); - }); + expect(result.type).toBe('div'); + expect(result.props.children).toEqual([ + , + , + ]); + }); - it('should throw for invalid elements', () => { - class SomeComponent extends React.Component { - render() { - return
; - } + it('should shallow render a functional component', () => { + function SomeComponent() { + return ( +
+ + +
+ ); + } + + var shallowRenderer = createRenderer(); + var result = shallowRenderer.render(); + + expect(result.type).toBe('div'); + expect(result.props.children).toEqual([ + , + , + ]); + }); + + it('should throw for invalid elements', () => { + class SomeComponent extends React.Component { + render() { + return
; } + } - var shallowRenderer = createRenderer(); - expect(() => shallowRenderer.render(SomeComponent)).toThrowError( - 'ReactShallowRenderer render(): Invalid component element. Instead of ' + - 'passing a component class, make sure to instantiate it by passing it ' + - 'to React.createElement.', - ); - expect(() => shallowRenderer.render(
)).toThrowError( - 'ReactShallowRenderer render(): Shallow rendering works only with ' + - 'custom components, not primitives (div). Instead of calling ' + - '`.render(el)` and inspecting the rendered output, look at `el.props` ' + - 'directly instead.', - ); - }); + var shallowRenderer = createRenderer(); + expect(() => shallowRenderer.render(SomeComponent)).toThrowError( + 'ReactShallowRenderer render(): Invalid component element. Instead of ' + + 'passing a component class, make sure to instantiate it by passing it ' + + 'to React.createElement.', + ); + expect(() => shallowRenderer.render(
)).toThrowError( + 'ReactShallowRenderer render(): Shallow rendering works only with ' + + 'custom components, not primitives (div). Instead of calling ' + + '`.render(el)` and inspecting the rendered output, look at `el.props` ' + + 'directly instead.', + ); + }); - it('should have shallow unmounting', () => { - var componentWillUnmount = jest.fn(); + it('should have shallow unmounting', () => { + var componentWillUnmount = jest.fn(); - class SomeComponent extends React.Component { - componentWillUnmount = componentWillUnmount; - render() { - return
; - } + class SomeComponent extends React.Component { + componentWillUnmount = componentWillUnmount; + render() { + return
; } + } - var shallowRenderer = createRenderer(); - shallowRenderer.render(); - shallowRenderer.unmount(); + var shallowRenderer = createRenderer(); + shallowRenderer.render(); + shallowRenderer.unmount(); - expect(componentWillUnmount).toBeCalled(); - }); + expect(componentWillUnmount).toBeCalled(); + }); - it('can shallow render to null', () => { - class SomeComponent extends React.Component { - render() { - return null; - } + it('can shallow render to null', () => { + class SomeComponent extends React.Component { + render() { + return null; } + } - var shallowRenderer = createRenderer(); - var result = shallowRenderer.render(); + var shallowRenderer = createRenderer(); + var result = shallowRenderer.render(); - expect(result).toBe(null); - }); + expect(result).toBe(null); + }); - it('can shallow render with a ref', () => { - class SomeComponent extends React.Component { - render() { - return
; - } + it('can shallow render with a ref', () => { + class SomeComponent extends React.Component { + render() { + return
; } + } - var shallowRenderer = createRenderer(); - // Shouldn't crash. - shallowRenderer.render(); - }); + var shallowRenderer = createRenderer(); + // Shouldn't crash. + shallowRenderer.render(); + }); - it('lets you update shallowly rendered components', () => { - class SomeComponent extends React.Component { - state = {clicked: false}; - - onClick = () => { - this.setState({clicked: true}); - }; - - render() { - var className = this.state.clicked ? 'was-clicked' : ''; - - if (this.props.aNew === 'prop') { - return ( - - Test link - - ); - } else { - return ( -
- - -
- ); - } + it('lets you update shallowly rendered components', () => { + class SomeComponent extends React.Component { + state = {clicked: false}; + + onClick = () => { + this.setState({clicked: true}); + }; + + render() { + var className = this.state.clicked ? 'was-clicked' : ''; + + if (this.props.aNew === 'prop') { + return ( + + Test link + + ); + } else { + return ( +
+ + +
+ ); } } + } - var shallowRenderer = createRenderer(); - var result = shallowRenderer.render(); - expect(result.type).toBe('div'); - expect(result.props.children).toEqual([ - , - , - ]); + var shallowRenderer = createRenderer(); + var result = shallowRenderer.render(); + expect(result.type).toBe('div'); + expect(result.props.children).toEqual([ + , + , + ]); - var updatedResult = shallowRenderer.render(); - expect(updatedResult.type).toBe('a'); + var updatedResult = shallowRenderer.render(); + expect(updatedResult.type).toBe('a'); - var mockEvent = {}; - updatedResult.props.onClick(mockEvent); + var mockEvent = {}; + updatedResult.props.onClick(mockEvent); - var updatedResultCausedByClick = shallowRenderer.getRenderOutput(); - expect(updatedResultCausedByClick.type).toBe('a'); - expect(updatedResultCausedByClick.props.className).toBe('was-clicked'); - }); + var updatedResultCausedByClick = shallowRenderer.getRenderOutput(); + expect(updatedResultCausedByClick.type).toBe('a'); + expect(updatedResultCausedByClick.props.className).toBe('was-clicked'); + }); - it('can access the mounted component instance', () => { - class SimpleComponent extends React.Component { - someMethod = () => { - return this.props.n; - }; + it('can access the mounted component instance', () => { + class SimpleComponent extends React.Component { + someMethod = () => { + return this.props.n; + }; - render() { - return
{this.props.n}
; - } + render() { + return
{this.props.n}
; } + } - var shallowRenderer = createRenderer(); - shallowRenderer.render(); - expect(shallowRenderer.getMountedInstance().someMethod()).toEqual(5); - }); + var shallowRenderer = createRenderer(); + shallowRenderer.render(); + expect(shallowRenderer.getMountedInstance().someMethod()).toEqual(5); + }); - it('can shallowly render components with contextTypes', () => { - class SimpleComponent extends React.Component { - static contextTypes = { - name: PropTypes.string, - }; + it('can shallowly render components with contextTypes', () => { + class SimpleComponent extends React.Component { + static contextTypes = { + name: PropTypes.string, + }; - render() { - return
; - } + render() { + return
; } + } - var shallowRenderer = createRenderer(); - var result = shallowRenderer.render(); - expect(result).toEqual(
); - }); + var shallowRenderer = createRenderer(); + var result = shallowRenderer.render(); + expect(result).toEqual(
); + }); - it('can shallowly render components with ref as function', () => { - class SimpleComponent extends React.Component { - state = {clicked: false}; + it('can shallowly render components with ref as function', () => { + class SimpleComponent extends React.Component { + state = {clicked: false}; - handleUserClick = () => { - this.setState({clicked: true}); - }; + handleUserClick = () => { + this.setState({clicked: true}); + }; - render() { - return ( -
{}} - onClick={this.handleUserClick} - className={this.state.clicked ? 'clicked' : ''} - /> - ); - } + render() { + return ( +
{}} + onClick={this.handleUserClick} + className={this.state.clicked ? 'clicked' : ''} + /> + ); } + } - var shallowRenderer = createRenderer(); - shallowRenderer.render(); - var result = shallowRenderer.getRenderOutput(); - expect(result.type).toEqual('div'); - expect(result.props.className).toEqual(''); - result.props.onClick(); + var shallowRenderer = createRenderer(); + shallowRenderer.render(); + var result = shallowRenderer.getRenderOutput(); + expect(result.type).toEqual('div'); + expect(result.props.className).toEqual(''); + result.props.onClick(); - result = shallowRenderer.getRenderOutput(); - expect(result.type).toEqual('div'); - expect(result.props.className).toEqual('clicked'); - }); + result = shallowRenderer.getRenderOutput(); + expect(result.type).toEqual('div'); + expect(result.props.className).toEqual('clicked'); + }); - it('can setState in componentWillMount when shallow rendering', () => { - class SimpleComponent extends React.Component { - componentWillMount() { - this.setState({groovy: 'doovy'}); - } + it('can setState in componentWillMount when shallow rendering', () => { + class SimpleComponent extends React.Component { + componentWillMount() { + this.setState({groovy: 'doovy'}); + } - render() { - return
{this.state.groovy}
; - } + render() { + return
{this.state.groovy}
; } + } - var shallowRenderer = createRenderer(); - var result = shallowRenderer.render(); - expect(result).toEqual(
doovy
); - }); + var shallowRenderer = createRenderer(); + var result = shallowRenderer.render(); + expect(result).toEqual(
doovy
); + }); - it('can pass context when shallowly rendering', () => { - class SimpleComponent extends React.Component { - static contextTypes = { - name: PropTypes.string, - }; + it('can pass context when shallowly rendering', () => { + class SimpleComponent extends React.Component { + static contextTypes = { + name: PropTypes.string, + }; - render() { - return
{this.context.name}
; - } + render() { + return
{this.context.name}
; } + } - var shallowRenderer = createRenderer(); - var result = shallowRenderer.render(, { - name: 'foo', - }); - expect(result).toEqual(
foo
); + var shallowRenderer = createRenderer(); + var result = shallowRenderer.render(, { + name: 'foo', }); + expect(result).toEqual(
foo
); + }); - it('can fail context when shallowly rendering', () => { - spyOn(console, 'error'); + it('can fail context when shallowly rendering', () => { + spyOn(console, 'error'); - class SimpleComponent extends React.Component { - static contextTypes = { - name: PropTypes.string.isRequired, - }; + class SimpleComponent extends React.Component { + static contextTypes = { + name: PropTypes.string.isRequired, + }; - render() { - return
{this.context.name}
; - } + render() { + return
{this.context.name}
; } + } - var shallowRenderer = createRenderer(); - shallowRenderer.render(); - expectDev(console.error.calls.count()).toBe(1); - expect( - console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)'), - ).toBe( - 'Warning: Failed context type: The context `name` is marked as ' + - 'required in `SimpleComponent`, but its value is `undefined`.\n' + - ' in SimpleComponent (at **)', - ); - }); - } // If fiber + var shallowRenderer = createRenderer(); + shallowRenderer.render(); + expectDev(console.error.calls.count()).toBe(1); + expect( + console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)'), + ).toBe( + 'Warning: Failed context type: The context `name` is marked as ' + + 'required in `SimpleComponent`, but its value is `undefined`.\n' + + ' in SimpleComponent (at **)', + ); + }); + + it('should warn about propTypes (but only once)', () => { + spyOn(console, 'error'); + + class SimpleComponent extends React.Component { + render() { + return React.createElement('div', null, this.props.name); + } + } + + SimpleComponent.propTypes = { + name: PropTypes.string.isRequired, + }; + + var shallowRenderer = createRenderer(); + shallowRenderer.render(React.createElement(SimpleComponent, {name: 123})); + + expect(console.error.calls.count()).toBe(1); + expect( + console.error.calls.argsFor(0)[0].replace(/\(at .+?:\d+\)/g, '(at **)'), + ).toBe( + 'Warning: Failed prop type: Invalid prop `name` of type `number` ' + + 'supplied to `SimpleComponent`, expected `string`.\n' + + ' in SimpleComponent', + ); + }); it('can scryRenderedDOMComponentsWithClass with TextComponent', () => { class Wrapper extends React.Component { @@ -530,32 +551,29 @@ describe('ReactTestUtils', () => { ); }); - // Shallow renderer only implemented for Fiber in 16+ - if (ReactDOMFeatureFlags.useFiber) { - it('should throw when attempting to use ReactTestUtils.Simulate with shallow rendering', () => { - class SomeComponent extends React.Component { - render() { - return ( -
- hello, world. -
- ); - } + it('should throw when attempting to use ReactTestUtils.Simulate with shallow rendering', () => { + class SomeComponent extends React.Component { + render() { + return ( +
+ hello, world. +
+ ); } + } - var handler = jasmine.createSpy('spy'); - var shallowRenderer = createRenderer(); - var result = shallowRenderer.render( - , - ); + var handler = jasmine.createSpy('spy'); + var shallowRenderer = createRenderer(); + var result = shallowRenderer.render( + , + ); - expect(() => ReactTestUtils.Simulate.click(result)).toThrowError( - 'TestUtils.Simulate expects a component instance and not a ReactElement.' + - 'TestUtils.Simulate will not work if you are using shallow rendering.', - ); - expect(handler).not.toHaveBeenCalled(); - }); - } + expect(() => ReactTestUtils.Simulate.click(result)).toThrowError( + 'TestUtils.Simulate expects a component instance and not a ReactElement.' + + 'TestUtils.Simulate will not work if you are using shallow rendering.', + ); + expect(handler).not.toHaveBeenCalled(); + }); it('should not warn when simulating events with extra properties', () => { spyOn(console, 'error'); diff --git a/src/renderers/testing/ReactShallowRenderer.js b/src/renderers/testing/ReactShallowRenderer.js index 470b369a8e68e..4df2ec17a075b 100644 --- a/src/renderers/testing/ReactShallowRenderer.js +++ b/src/renderers/testing/ReactShallowRenderer.js @@ -12,78 +12,37 @@ 'use strict'; +const checkPropTypes = require('prop-types/checkPropTypes'); const React = require('react'); -const ReactTestRenderer = require('react-test-renderer'); -const emptyFunction = require('fbjs/lib/emptyFunction'); +const emptyObject = require('fbjs/lib/emptyObject'); const invariant = require('fbjs/lib/invariant'); -const ShallowNodeMockComponent = ({children}) => - React.Children.toArray(children); +const { + ReactDebugCurrentFrame, +} = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; -function createShallowNodeMock() { - let isFirst = true; - return function createNodeMock(element) { - if (isFirst) { - isFirst = false; - return element.type; - } - return ShallowNodeMockComponent; +class ReactShallowRenderer { + static createRenderer = function() { + return new ReactShallowRenderer(); }; -} -// TODO Remove this wrapper if/when context parameter is removed -function wrapElementWithContextProvider(element, context) { - const childContextTypes = Object.keys(context).reduce( - (reduced, key) => { - reduced[key] = emptyFunction; - return reduced; - }, - {}, - ); - - class ShallowRendererWrapper extends React.Component { - static __shallowRendererWrapperFlag = true; - static childContextTypes = childContextTypes; - getChildContext() { - return context; - } - render() { - return this.props.children; - } + constructor() { + this._element = null; + this._instance = null; + this._rendered = null; + this._updater = new Updater(this); } - return React.createElement(ShallowRendererWrapper, null, element); -} - -class ReactShallowRenderer { getMountedInstance() { - return this._renderer ? this._renderer.getInstance() : null; + return this._instance; } getRenderOutput() { - if (this._renderer) { - const tree = this._renderer.toTree(); - if (tree && tree.rendered) { - // If we created a context-wrapper then skip over it. - const element = tree.type.__shallowRendererWrapperFlag - ? tree.rendered.rendered - : tree.rendered; - - // Convert the rendered output to a ReactElement. - // This supports .toEqual() comparison for test elements. - return React.createElement( - element.type, - element.props, - element.props.children, - ); - } - } - return null; + return this._rendered; } - // TODO We should probably remove support for the non-standard context parameter - render(element, context) { + render(element, context = emptyObject) { invariant( React.isValidElement(element), 'ReactShallowRenderer render(): Invalid component element.%s', @@ -100,26 +59,146 @@ class ReactShallowRenderer { element.type, ); - // TODO Remove this wrapper if/when context parameter is removed - if (context && Object.keys(context).length) { - element = wrapElementWithContextProvider(element, context); - } + this._element = element; - this._renderer = ReactTestRenderer.create(element, { - createNodeMock: createShallowNodeMock(), - }); + if (this._instance) { + this._rendered = updateClassComponent( + this._instance, + element.props, + context, + ); + } else { + const prototype = element.type.prototype; + + if (typeof prototype.render === 'function') { + this._instance = new element.type( + element.props, + context, + this._updater, + ); + + // TODO context validation: ReactDebugCurrentFrame + if (element.type.hasOwnProperty('contextTypes')) { + ReactDebugCurrentFrame.element = element; + + checkPropTypes( + element.type.contextTypes, + context, + 'context', + getName(element.type, this._instance), + ReactDebugCurrentFrame.getStackAddendum, + ); + + ReactDebugCurrentFrame.element = null; + } + + this._rendered = mountClassComponent( + this._instance, + element.props, + context, + ); + } else { + this._rendered = element.type(); + } + } return this.getRenderOutput(); } unmount() { - this._renderer.unmount(); + if (this._instance) { + if (typeof this._instance.componentWillUnmount === 'function') { + this._instance.componentWillUnmount(); + } + } + + this._element = null; + this._rendered = null; + this._instance = null; } } -// Backwards compatible API -ReactShallowRenderer.createRenderer = function() { - return new ReactShallowRenderer(); -}; +class Updater { + constructor(renderer) { + this._renderer = renderer; + } + + isMounted(publicInstance) { + return !!this._renderer._element; + } + + enqueueForceUpdate(publicInstance, callback, callerName) { + this._renderer.render(this._renderer._element); // TODO + } + + enqueueReplaceState(publicInstance, completeState, callback, callerName) { + publicInstance.state = completeState; + this._renderer.render(this._renderer._element); + } + + enqueueSetState(publicInstance, partialState, callback, callerName) { + publicInstance.state = { + ...publicInstance.state, + ...partialState, + }; + this._renderer.render(this._renderer._element); + } +} + +function getName(type, instance) { + var constructor = instance && instance.constructor; + return type.displayName || + (constructor && constructor.displayName) || + type.name || + (constructor && constructor.name) || + null; +} + +function mountClassComponent( + instance, + props = emptyObject, + context = emptyObject, +) { + instance.context = context; + instance.props = props; + instance.state = instance.state || emptyObject; + + if (typeof instance.componentWillMount === 'function') { + instance.componentWillMount(); + } + + const rendered = instance.render(); + + if (typeof instance.componentDidMount === 'function') { + instance.componentDidMount(); + } + + return rendered; +} + +function updateClassComponent( + instance, + props = emptyObject, + context = emptyObject, +) { + const oldContext = instance.context; + const oldProps = instance.props; + const oldState = instance.state; + + if (typeof instance.componentWillUpdate === 'function') { + instance.componentWillUpdate(props, instance.state, context); + } + + instance.context = context; + instance.props = props; + + const rendered = instance.render(); + + if (typeof instance.componentDidUpdate === 'function') { + instance.componentDidUpdate(oldProps, oldState, oldContext); + } + + return rendered; +} module.exports = ReactShallowRenderer; From e1d3ded7c4c934183545b6725eab889a3c3b0bce Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 17 Apr 2017 14:52:13 -0700 Subject: [PATCH 24/37] Properly handle context when setState() is called Handling an edge case discovered while running the new shallow renderer against www. Also added a new test case for this --- scripts/fiber/tests-passing.txt | 1 + src/renderers/dom/test/ReactTestUtils.js | 2 +- .../dom/test/__tests__/ReactTestUtils-test.js | 28 +++++++++++++++++++ src/renderers/testing/ReactShallowRenderer.js | 9 +++--- 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt index cdfb5dcc06a6b..dc543710d2f63 100644 --- a/scripts/fiber/tests-passing.txt +++ b/scripts/fiber/tests-passing.txt @@ -1574,6 +1574,7 @@ src/renderers/dom/test/__tests__/ReactTestUtils-test.js * can shallowly render components with ref as function * can setState in componentWillMount when shallow rendering * can pass context when shallowly rendering +* should track context across updates * can fail context when shallowly rendering * should warn about propTypes (but only once) * can scryRenderedDOMComponentsWithClass with TextComponent diff --git a/src/renderers/dom/test/ReactTestUtils.js b/src/renderers/dom/test/ReactTestUtils.js index 8ca013fb96938..96ceb6ed0f745 100644 --- a/src/renderers/dom/test/ReactTestUtils.js +++ b/src/renderers/dom/test/ReactTestUtils.js @@ -421,7 +421,7 @@ var ReactTestUtils = { }; }, - // TODO (bvaughn) Remove this warning accessor before the next alpha. + // TODO (bvaughn) Remove this warning accessor before 16.0.0. // It's only being added for temporary deprecation notice in RN. createRenderer: createRendererWithWarning, diff --git a/src/renderers/dom/test/__tests__/ReactTestUtils-test.js b/src/renderers/dom/test/__tests__/ReactTestUtils-test.js index 23c3f41970e9b..e59879e66633a 100644 --- a/src/renderers/dom/test/__tests__/ReactTestUtils-test.js +++ b/src/renderers/dom/test/__tests__/ReactTestUtils-test.js @@ -289,6 +289,34 @@ describe('ReactTestUtils', () => { expect(result).toEqual(
foo
); }); + it('should track context across updates', () => { + class SimpleComponent extends React.Component { + static contextTypes = { + foo: PropTypes.string, + }; + + state = { + bar: 'bar', + }; + + render() { + return
{`${this.context.foo}:${this.state.bar}`}
; + } + } + + var shallowRenderer = createRenderer(); + var result = shallowRenderer.render(, { + foo: 'foo', + }); + expect(result).toEqual(
foo:bar
); + + var instance = shallowRenderer.getMountedInstance(); + instance.setState({bar: 'baz'}); + + result = shallowRenderer.getRenderOutput(); + expect(result).toEqual(
foo:baz
); + }); + it('can fail context when shallowly rendering', () => { spyOn(console, 'error'); diff --git a/src/renderers/testing/ReactShallowRenderer.js b/src/renderers/testing/ReactShallowRenderer.js index 4df2ec17a075b..ba255ad92d697 100644 --- a/src/renderers/testing/ReactShallowRenderer.js +++ b/src/renderers/testing/ReactShallowRenderer.js @@ -28,6 +28,7 @@ class ReactShallowRenderer { }; constructor() { + this._context = null; this._element = null; this._instance = null; this._rendered = null; @@ -60,6 +61,7 @@ class ReactShallowRenderer { ); this._element = element; + this._context = context; if (this._instance) { this._rendered = updateClassComponent( @@ -77,7 +79,6 @@ class ReactShallowRenderer { this._updater, ); - // TODO context validation: ReactDebugCurrentFrame if (element.type.hasOwnProperty('contextTypes')) { ReactDebugCurrentFrame.element = element; @@ -128,12 +129,12 @@ class Updater { } enqueueForceUpdate(publicInstance, callback, callerName) { - this._renderer.render(this._renderer._element); // TODO + this._renderer.render(this._renderer._element, this._renderer._context); } enqueueReplaceState(publicInstance, completeState, callback, callerName) { publicInstance.state = completeState; - this._renderer.render(this._renderer._element); + this._renderer.render(this._renderer._element, this._renderer._context); } enqueueSetState(publicInstance, partialState, callback, callerName) { @@ -141,7 +142,7 @@ class Updater { ...publicInstance.state, ...partialState, }; - this._renderer.render(this._renderer._element); + this._renderer.render(this._renderer._element, this._renderer._context); } } From 7db69a51eadc41500dcd7e84ad0c839133feeec6 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 17 Apr 2017 15:13:48 -0700 Subject: [PATCH 25/37] Support for shouldComponentUpdate; added tests for lifecycle hooks --- scripts/fiber/tests-passing.txt | 2 + .../dom/test/__tests__/ReactTestUtils-test.js | 58 +++++++++++++++++++ src/renderers/testing/ReactShallowRenderer.js | 16 ++++- 3 files changed, 75 insertions(+), 1 deletion(-) diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt index dc543710d2f63..3c73a93de6d9d 100644 --- a/scripts/fiber/tests-passing.txt +++ b/scripts/fiber/tests-passing.txt @@ -1561,8 +1561,10 @@ src/renderers/dom/shared/wrappers/__tests__/ReactDOMTextarea-test.js * should warn if value and defaultValue are specified src/renderers/dom/test/__tests__/ReactTestUtils-test.js +* should call all of the lifecycle hooks * should only render 1 level deep * should have shallow rendering +* should enable shouldComponentUpdate to prevent a re-render * should shallow render a functional component * should throw for invalid elements * should have shallow unmounting diff --git a/src/renderers/dom/test/__tests__/ReactTestUtils-test.js b/src/renderers/dom/test/__tests__/ReactTestUtils-test.js index e59879e66633a..16cfc064d3961 100644 --- a/src/renderers/dom/test/__tests__/ReactTestUtils-test.js +++ b/src/renderers/dom/test/__tests__/ReactTestUtils-test.js @@ -28,6 +28,40 @@ describe('ReactTestUtils', () => { ReactTestUtils = require('ReactTestUtils'); }); + it('should call all of the lifecycle hooks', () => { + const logs = []; + const logger = message => () => logs.push(message) || true; + + class SomeComponent extends React.Component { + componentWillMount = logger('componentWillMount'); + componentDidMount = logger('componentDidMount'); + componentWillReceiveProps = logger('componentWillReceiveProps'); + shouldComponentUpdate = logger('shouldComponentUpdate'); + componentWillUpdate = logger('componentWillUpdate'); + componentDidUpdate = logger('componentDidUpdate'); + componentWillUnmount = logger('componentWillUnmount'); + render() { + return
; + } + } + + var shallowRenderer = createRenderer(); + shallowRenderer.render(); + expect(logs).toEqual(['componentWillMount', 'componentDidMount']); + + logs.splice(0); + + var instance = shallowRenderer.getMountedInstance(); + instance.setState({}); + + expect(logs).toEqual([ + 'componentWillReceiveProps', + 'shouldComponentUpdate', + 'componentWillUpdate', + 'componentDidUpdate', + ]); + }); + it('should only render 1 level deep', () => { function Parent() { return
; @@ -62,6 +96,30 @@ describe('ReactTestUtils', () => { ]); }); + it('should enable shouldComponentUpdate to prevent a re-render', () => { + let renderCounter = 0; + class SimpleComponent extends React.Component { + shouldComponentUpdate(nextProps, nextState) { + return nextState.update; + } + render() { + renderCounter++; + return
{`${renderCounter}`}
; + } + } + + var shallowRenderer = createRenderer(); + shallowRenderer.render(); + expect(shallowRenderer.getRenderOutput()).toEqual(
1
); + + var instance = shallowRenderer.getMountedInstance(); + instance.setState({update: false}); + expect(shallowRenderer.getRenderOutput()).toEqual(
1
); + + instance.setState({update: true}); + expect(shallowRenderer.getRenderOutput()).toEqual(
2
); + }); + it('should shallow render a functional component', () => { function SomeComponent() { return ( diff --git a/src/renderers/testing/ReactShallowRenderer.js b/src/renderers/testing/ReactShallowRenderer.js index ba255ad92d697..e7ed41ead9632 100644 --- a/src/renderers/testing/ReactShallowRenderer.js +++ b/src/renderers/testing/ReactShallowRenderer.js @@ -66,6 +66,7 @@ class ReactShallowRenderer { if (this._instance) { this._rendered = updateClassComponent( this._instance, + this._rendered, element.props, context, ); @@ -179,6 +180,7 @@ function mountClassComponent( function updateClassComponent( instance, + rendered, props = emptyObject, context = emptyObject, ) { @@ -186,6 +188,18 @@ function updateClassComponent( const oldProps = instance.props; const oldState = instance.state; + if (typeof instance.componentWillReceiveProps === 'function') { + instance.componentWillReceiveProps(props); + } + + if (typeof instance.shouldComponentUpdate === 'function') { + if ( + instance.shouldComponentUpdate(props, instance.state, context) === false + ) { + return rendered; + } + } + if (typeof instance.componentWillUpdate === 'function') { instance.componentWillUpdate(props, instance.state, context); } @@ -193,7 +207,7 @@ function updateClassComponent( instance.context = context; instance.props = props; - const rendered = instance.render(); + rendered = instance.render(); if (typeof instance.componentDidUpdate === 'function') { instance.componentDidUpdate(oldProps, oldState, oldContext); From 9537e94488914b5517860841ce381a64612f364d Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 17 Apr 2017 16:26:02 -0700 Subject: [PATCH 26/37] Shallow renderer properly tracks new state for componentWill* and shouldComponent* hooks --- .../dom/test/__tests__/ReactTestUtils-test.js | 3 +- src/renderers/testing/ReactShallowRenderer.js | 31 +++++++++---------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/renderers/dom/test/__tests__/ReactTestUtils-test.js b/src/renderers/dom/test/__tests__/ReactTestUtils-test.js index 16cfc064d3961..4eeb245aa0fd1 100644 --- a/src/renderers/dom/test/__tests__/ReactTestUtils-test.js +++ b/src/renderers/dom/test/__tests__/ReactTestUtils-test.js @@ -99,8 +99,9 @@ describe('ReactTestUtils', () => { it('should enable shouldComponentUpdate to prevent a re-render', () => { let renderCounter = 0; class SimpleComponent extends React.Component { + state = {update: false}; shouldComponentUpdate(nextProps, nextState) { - return nextState.update; + return this.state.update !== nextState.update; } render() { renderCounter++; diff --git a/src/renderers/testing/ReactShallowRenderer.js b/src/renderers/testing/ReactShallowRenderer.js index e7ed41ead9632..90e07f593089b 100644 --- a/src/renderers/testing/ReactShallowRenderer.js +++ b/src/renderers/testing/ReactShallowRenderer.js @@ -31,6 +31,7 @@ class ReactShallowRenderer { this._context = null; this._element = null; this._instance = null; + this._newState = null; this._rendered = null; this._updater = new Updater(this); } @@ -68,6 +69,7 @@ class ReactShallowRenderer { this._instance, this._rendered, element.props, + this._newState, context, ); } else { @@ -114,7 +116,9 @@ class ReactShallowRenderer { } } + this._context = null; this._element = null; + this._newState = null; this._rendered = null; this._instance = null; } @@ -134,15 +138,16 @@ class Updater { } enqueueReplaceState(publicInstance, completeState, callback, callerName) { - publicInstance.state = completeState; + this._renderer._newState = completeState; this._renderer.render(this._renderer._element, this._renderer._context); } enqueueSetState(publicInstance, partialState, callback, callerName) { - publicInstance.state = { + this._renderer._newState = { ...publicInstance.state, ...partialState, }; + this._renderer.render(this._renderer._element, this._renderer._context); } } @@ -156,11 +161,7 @@ function getName(type, instance) { null; } -function mountClassComponent( - instance, - props = emptyObject, - context = emptyObject, -) { +function mountClassComponent(instance, props, context) { instance.context = context; instance.props = props; instance.state = instance.state || emptyObject; @@ -178,12 +179,9 @@ function mountClassComponent( return rendered; } -function updateClassComponent( - instance, - rendered, - props = emptyObject, - context = emptyObject, -) { +function updateClassComponent(instance, rendered, props, state, context) { + state = state || emptyObject; + const oldContext = instance.context; const oldProps = instance.props; const oldState = instance.state; @@ -193,19 +191,18 @@ function updateClassComponent( } if (typeof instance.shouldComponentUpdate === 'function') { - if ( - instance.shouldComponentUpdate(props, instance.state, context) === false - ) { + if (instance.shouldComponentUpdate(props, state, context) === false) { return rendered; } } if (typeof instance.componentWillUpdate === 'function') { - instance.componentWillUpdate(props, instance.state, context); + instance.componentWillUpdate(props, state, context); } instance.context = context; instance.props = props; + instance.state = state; rendered = instance.render(); From 4012acba72c15f1c5244559654f6509dd9488bae Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 17 Apr 2017 16:40:59 -0700 Subject: [PATCH 27/37] Shallow renderer passes props+context to functional component --- .../dom/test/__tests__/ReactTestUtils-test.js | 10 ++++++++-- src/renderers/testing/ReactShallowRenderer.js | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/renderers/dom/test/__tests__/ReactTestUtils-test.js b/src/renderers/dom/test/__tests__/ReactTestUtils-test.js index 4eeb245aa0fd1..957644100eae0 100644 --- a/src/renderers/dom/test/__tests__/ReactTestUtils-test.js +++ b/src/renderers/dom/test/__tests__/ReactTestUtils-test.js @@ -122,9 +122,11 @@ describe('ReactTestUtils', () => { }); it('should shallow render a functional component', () => { - function SomeComponent() { + function SomeComponent(props, context) { return (
+
{props.foo}
+
{context.bar}
@@ -132,10 +134,14 @@ describe('ReactTestUtils', () => { } var shallowRenderer = createRenderer(); - var result = shallowRenderer.render(); + var result = shallowRenderer.render(, { + bar: 'BAR', + }); expect(result.type).toBe('div'); expect(result.props.children).toEqual([ +
FOO
, +
BAR
, , , ]); diff --git a/src/renderers/testing/ReactShallowRenderer.js b/src/renderers/testing/ReactShallowRenderer.js index 90e07f593089b..cd238149daa0c 100644 --- a/src/renderers/testing/ReactShallowRenderer.js +++ b/src/renderers/testing/ReactShallowRenderer.js @@ -102,7 +102,7 @@ class ReactShallowRenderer { context, ); } else { - this._rendered = element.type(); + this._rendered = element.type(element.props, context); } } From fe1ca9f7c1ffa15fcbc7f7f14ab190a1f8fba3b3 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Mon, 17 Apr 2017 23:05:04 -0700 Subject: [PATCH 28/37] Trimmed some unnecessary paths from bundle --- scripts/rollup/bundles.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 65b9a48252f80..90e2969bf68a0 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -404,14 +404,7 @@ const bundles = [ label: 'shallow-renderer', manglePropertiesOnProd: false, name: 'react-test-renderer/shallow', - paths: [ - 'src/renderers/native/**/*.js', - 'src/renderers/shared/**/*.js', - 'src/renderers/testing/**/*.js', - - 'src/ReactVersion.js', - 'src/shared/**/*.js', - ], + paths: ['src/renderers/testing/**/*.js'], }, /******* React Noop Renderer (used only for fixtures/fiber-debugger) *******/ From 85213e93296013927a6e87de68b55b4c7c055cbf Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Tue, 18 Apr 2017 08:20:33 -0700 Subject: [PATCH 29/37] Udpate instance.props when sCU returns false --- src/renderers/testing/ReactShallowRenderer.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/renderers/testing/ReactShallowRenderer.js b/src/renderers/testing/ReactShallowRenderer.js index cd238149daa0c..bdee82bdaf0fa 100644 --- a/src/renderers/testing/ReactShallowRenderer.js +++ b/src/renderers/testing/ReactShallowRenderer.js @@ -192,6 +192,8 @@ function updateClassComponent(instance, rendered, props, state, context) { if (typeof instance.shouldComponentUpdate === 'function') { if (instance.shouldComponentUpdate(props, state, context) === false) { + instance.props = props; + return rendered; } } From ac0cc8e5f1f6933cc7167b33650d52751032bdc3 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Tue, 18 Apr 2017 11:06:52 -0700 Subject: [PATCH 30/37] Shallow renderer updates based on Dan's PR feedback: * No longer call the 'componentDidMount' lifecycle hook * Get 'ReactDebugCurrentFrame' from 'ReactGlobalSharedState' * Use more standard 'shouldConstruct' type check for class vs functional components * Don't pass 'prevContext' param to 'componentDidUpdate' Also update instance context and state if 'shouldComponentUpdate' returns false. --- scripts/rollup/bundles.js | 2 +- .../dom/test/__tests__/ReactTestUtils-test.js | 5 +++- src/renderers/testing/ReactShallowRenderer.js | 25 +++++++++++-------- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 90e2969bf68a0..7d90027c59339 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -404,7 +404,7 @@ const bundles = [ label: 'shallow-renderer', manglePropertiesOnProd: false, name: 'react-test-renderer/shallow', - paths: ['src/renderers/testing/**/*.js'], + paths: ['src/renderers/shared/**/*.js', 'src/renderers/testing/**/*.js'], }, /******* React Noop Renderer (used only for fixtures/fiber-debugger) *******/ diff --git a/src/renderers/dom/test/__tests__/ReactTestUtils-test.js b/src/renderers/dom/test/__tests__/ReactTestUtils-test.js index 957644100eae0..a3d7e4e0090b5 100644 --- a/src/renderers/dom/test/__tests__/ReactTestUtils-test.js +++ b/src/renderers/dom/test/__tests__/ReactTestUtils-test.js @@ -47,7 +47,10 @@ describe('ReactTestUtils', () => { var shallowRenderer = createRenderer(); shallowRenderer.render(); - expect(logs).toEqual(['componentWillMount', 'componentDidMount']); + + // Calling cDU might lead to problems with host component references. + // Since our components aren't really mounted, refs won't be available. + expect(logs).toEqual(['componentWillMount']); logs.splice(0); diff --git a/src/renderers/testing/ReactShallowRenderer.js b/src/renderers/testing/ReactShallowRenderer.js index bdee82bdaf0fa..bb9b3bbfc0ce2 100644 --- a/src/renderers/testing/ReactShallowRenderer.js +++ b/src/renderers/testing/ReactShallowRenderer.js @@ -18,9 +18,7 @@ const React = require('react'); const emptyObject = require('fbjs/lib/emptyObject'); const invariant = require('fbjs/lib/invariant'); -const { - ReactDebugCurrentFrame, -} = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; +const {ReactDebugCurrentFrame} = require('ReactGlobalSharedState'); class ReactShallowRenderer { static createRenderer = function() { @@ -73,9 +71,7 @@ class ReactShallowRenderer { context, ); } else { - const prototype = element.type.prototype; - - if (typeof prototype.render === 'function') { + if (shouldConstruct(element.type)) { this._instance = new element.type( element.props, context, @@ -172,17 +168,22 @@ function mountClassComponent(instance, props, context) { const rendered = instance.render(); - if (typeof instance.componentDidMount === 'function') { - instance.componentDidMount(); - } + // Calling cDU might lead to problems with host component references. + // Since our components aren't really mounted, refs won't be available. + // if (typeof instance.componentDidMount === 'function') { + // instance.componentDidMount(); + // } return rendered; } +function shouldConstruct(Component) { + return !!(Component.prototype && Component.prototype.isReactComponent); +} + function updateClassComponent(instance, rendered, props, state, context) { state = state || emptyObject; - const oldContext = instance.context; const oldProps = instance.props; const oldState = instance.state; @@ -192,7 +193,9 @@ function updateClassComponent(instance, rendered, props, state, context) { if (typeof instance.shouldComponentUpdate === 'function') { if (instance.shouldComponentUpdate(props, state, context) === false) { + instance.context = context; instance.props = props; + instance.state = state; return rendered; } @@ -209,7 +212,7 @@ function updateClassComponent(instance, rendered, props, state, context) { rendered = instance.render(); if (typeof instance.componentDidUpdate === 'function') { - instance.componentDidUpdate(oldProps, oldState, oldContext); + instance.componentDidUpdate(oldProps, oldState); } return rendered; From 35ae167736cc3c50dc42c14ab2347c189af45d79 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Tue, 18 Apr 2017 14:55:02 -0700 Subject: [PATCH 31/37] Explicitly set instance.updater after construction Don't require subclass constructors to pass this along to super --- src/renderers/testing/ReactShallowRenderer.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/renderers/testing/ReactShallowRenderer.js b/src/renderers/testing/ReactShallowRenderer.js index bb9b3bbfc0ce2..8a712d55d8407 100644 --- a/src/renderers/testing/ReactShallowRenderer.js +++ b/src/renderers/testing/ReactShallowRenderer.js @@ -72,11 +72,7 @@ class ReactShallowRenderer { ); } else { if (shouldConstruct(element.type)) { - this._instance = new element.type( - element.props, - context, - this._updater, - ); + this._instance = new element.type(element.props, context); if (element.type.hasOwnProperty('contextTypes')) { ReactDebugCurrentFrame.element = element; @@ -96,6 +92,7 @@ class ReactShallowRenderer { this._instance, element.props, context, + this._updater, ); } else { this._rendered = element.type(element.props, context); @@ -157,10 +154,11 @@ function getName(type, instance) { null; } -function mountClassComponent(instance, props, context) { +function mountClassComponent(instance, props, context, updater) { instance.context = context; instance.props = props; instance.state = instance.state || emptyObject; + instance.updater = updater; if (typeof instance.componentWillMount === 'function') { instance.componentWillMount(); From 4f15cbaa2c764b3b087890fe758e7e94e0451099 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Tue, 18 Apr 2017 20:00:34 -0700 Subject: [PATCH 32/37] Updated lifecycles tests and behavior to mirror 15.x shallow renderer --- .../dom/test/__tests__/ReactTestUtils-test.js | 15 +++++++++++++-- src/renderers/testing/ReactShallowRenderer.js | 8 ++++++-- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/renderers/dom/test/__tests__/ReactTestUtils-test.js b/src/renderers/dom/test/__tests__/ReactTestUtils-test.js index a3d7e4e0090b5..c1af9701cbb18 100644 --- a/src/renderers/dom/test/__tests__/ReactTestUtils-test.js +++ b/src/renderers/dom/test/__tests__/ReactTestUtils-test.js @@ -46,7 +46,7 @@ describe('ReactTestUtils', () => { } var shallowRenderer = createRenderer(); - shallowRenderer.render(); + shallowRenderer.render(); // Calling cDU might lead to problems with host component references. // Since our components aren't really mounted, refs won't be available. @@ -57,12 +57,23 @@ describe('ReactTestUtils', () => { var instance = shallowRenderer.getMountedInstance(); instance.setState({}); + // The previous shallow renderer triggered cDU for setState() calls. expect(logs).toEqual([ - 'componentWillReceiveProps', 'shouldComponentUpdate', 'componentWillUpdate', 'componentDidUpdate', ]); + + logs.splice(0); + + shallowRenderer.render(); + + // The previous shallow renderer did not trigger cDU for props changes. + expect(logs).toEqual([ + 'componentWillReceiveProps', + 'shouldComponentUpdate', + 'componentWillUpdate', + ]); }); it('should only render 1 level deep', () => { diff --git a/src/renderers/testing/ReactShallowRenderer.js b/src/renderers/testing/ReactShallowRenderer.js index 8a712d55d8407..da8c55d731033 100644 --- a/src/renderers/testing/ReactShallowRenderer.js +++ b/src/renderers/testing/ReactShallowRenderer.js @@ -185,7 +185,10 @@ function updateClassComponent(instance, rendered, props, state, context) { const oldProps = instance.props; const oldState = instance.state; - if (typeof instance.componentWillReceiveProps === 'function') { + if ( + oldProps !== props && + typeof instance.componentWillReceiveProps === 'function' + ) { instance.componentWillReceiveProps(props); } @@ -209,7 +212,8 @@ function updateClassComponent(instance, rendered, props, state, context) { rendered = instance.render(); - if (typeof instance.componentDidUpdate === 'function') { + // The 15.x shallow renderer triggered cDU for setState() calls only. + if (oldState !== state && typeof instance.componentDidUpdate === 'function') { instance.componentDidUpdate(oldProps, oldState); } From e8ef8fa7ad5138c53472148bce4bdfa361c100b1 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Tue, 18 Apr 2017 21:39:41 -0700 Subject: [PATCH 33/37] Shallow renderer setState() supports updater function. Also converted test vars to const/let --- .../dom/test/__tests__/ReactTestUtils-test.js | 184 ++++++++++-------- src/renderers/testing/ReactShallowRenderer.js | 4 + 2 files changed, 111 insertions(+), 77 deletions(-) diff --git a/src/renderers/dom/test/__tests__/ReactTestUtils-test.js b/src/renderers/dom/test/__tests__/ReactTestUtils-test.js index c1af9701cbb18..3de63689b989a 100644 --- a/src/renderers/dom/test/__tests__/ReactTestUtils-test.js +++ b/src/renderers/dom/test/__tests__/ReactTestUtils-test.js @@ -11,12 +11,12 @@ 'use strict'; -var createRenderer; -var PropTypes; -var React; -var ReactDOM; -var ReactDOMServer; -var ReactTestUtils; +let createRenderer; +let PropTypes; +let React; +let ReactDOM; +let ReactDOMServer; +let ReactTestUtils; describe('ReactTestUtils', () => { beforeEach(() => { @@ -45,7 +45,7 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = createRenderer(); + const shallowRenderer = createRenderer(); shallowRenderer.render(); // Calling cDU might lead to problems with host component references. @@ -54,7 +54,7 @@ describe('ReactTestUtils', () => { logs.splice(0); - var instance = shallowRenderer.getMountedInstance(); + const instance = shallowRenderer.getMountedInstance(); instance.setState({}); // The previous shallow renderer triggered cDU for setState() calls. @@ -84,7 +84,7 @@ describe('ReactTestUtils', () => { throw Error('This component should not render'); } - var shallowRenderer = createRenderer(); + const shallowRenderer = createRenderer(); shallowRenderer.render(React.createElement(Parent)); }); @@ -100,8 +100,8 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = createRenderer(); - var result = shallowRenderer.render(); + const shallowRenderer = createRenderer(); + const result = shallowRenderer.render(); expect(result.type).toBe('div'); expect(result.props.children).toEqual([ @@ -123,11 +123,11 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = createRenderer(); + const shallowRenderer = createRenderer(); shallowRenderer.render(); expect(shallowRenderer.getRenderOutput()).toEqual(
1
); - var instance = shallowRenderer.getMountedInstance(); + const instance = shallowRenderer.getMountedInstance(); instance.setState({update: false}); expect(shallowRenderer.getRenderOutput()).toEqual(
1
); @@ -147,8 +147,8 @@ describe('ReactTestUtils', () => { ); } - var shallowRenderer = createRenderer(); - var result = shallowRenderer.render(, { + const shallowRenderer = createRenderer(); + const result = shallowRenderer.render(, { bar: 'BAR', }); @@ -168,7 +168,7 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = createRenderer(); + const shallowRenderer = createRenderer(); expect(() => shallowRenderer.render(SomeComponent)).toThrowError( 'ReactShallowRenderer render(): Invalid component element. Instead of ' + 'passing a component class, make sure to instantiate it by passing it ' + @@ -183,7 +183,7 @@ describe('ReactTestUtils', () => { }); it('should have shallow unmounting', () => { - var componentWillUnmount = jest.fn(); + const componentWillUnmount = jest.fn(); class SomeComponent extends React.Component { componentWillUnmount = componentWillUnmount; @@ -192,7 +192,7 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = createRenderer(); + const shallowRenderer = createRenderer(); shallowRenderer.render(); shallowRenderer.unmount(); @@ -206,8 +206,8 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = createRenderer(); - var result = shallowRenderer.render(); + const shallowRenderer = createRenderer(); + const result = shallowRenderer.render(); expect(result).toBe(null); }); @@ -219,7 +219,7 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = createRenderer(); + const shallowRenderer = createRenderer(); // Shouldn't crash. shallowRenderer.render(); }); @@ -233,7 +233,7 @@ describe('ReactTestUtils', () => { }; render() { - var className = this.state.clicked ? 'was-clicked' : ''; + const className = this.state.clicked ? 'was-clicked' : ''; if (this.props.aNew === 'prop') { return ( @@ -252,21 +252,21 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = createRenderer(); - var result = shallowRenderer.render(); + const shallowRenderer = createRenderer(); + const result = shallowRenderer.render(); expect(result.type).toBe('div'); expect(result.props.children).toEqual([ , , ]); - var updatedResult = shallowRenderer.render(); + const updatedResult = shallowRenderer.render(); expect(updatedResult.type).toBe('a'); - var mockEvent = {}; + const mockEvent = {}; updatedResult.props.onClick(mockEvent); - var updatedResultCausedByClick = shallowRenderer.getRenderOutput(); + const updatedResultCausedByClick = shallowRenderer.getRenderOutput(); expect(updatedResultCausedByClick.type).toBe('a'); expect(updatedResultCausedByClick.props.className).toBe('was-clicked'); }); @@ -282,7 +282,7 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = createRenderer(); + const shallowRenderer = createRenderer(); shallowRenderer.render(); expect(shallowRenderer.getMountedInstance().someMethod()).toEqual(5); }); @@ -298,8 +298,8 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = createRenderer(); - var result = shallowRenderer.render(); + const shallowRenderer = createRenderer(); + const result = shallowRenderer.render(); expect(result).toEqual(
); }); @@ -322,9 +322,9 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = createRenderer(); + const shallowRenderer = createRenderer(); shallowRenderer.render(); - var result = shallowRenderer.getRenderOutput(); + let result = shallowRenderer.getRenderOutput(); expect(result.type).toEqual('div'); expect(result.props.className).toEqual(''); result.props.onClick(); @@ -345,11 +345,41 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = createRenderer(); - var result = shallowRenderer.render(); + const shallowRenderer = createRenderer(); + const result = shallowRenderer.render(); expect(result).toEqual(
doovy
); }); + it('can setState with an updater function', () => { + let instance; + + class SimpleComponent extends React.Component { + state = { + counter: 0, + }; + + render() { + instance = this; + return ( + + ); + } + } + + const shallowRenderer = createRenderer(); + let result = shallowRenderer.render(); + expect(result.props.children).toEqual(0); + + instance.setState((state, props) => { + return {counter: props.defaultCount + 1}; + }); + + result = shallowRenderer.getRenderOutput(); + expect(result.props.children).toEqual(2); + }); + it('can pass context when shallowly rendering', () => { class SimpleComponent extends React.Component { static contextTypes = { @@ -361,8 +391,8 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = createRenderer(); - var result = shallowRenderer.render(, { + const shallowRenderer = createRenderer(); + const result = shallowRenderer.render(, { name: 'foo', }); expect(result).toEqual(
foo
); @@ -383,13 +413,13 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = createRenderer(); - var result = shallowRenderer.render(, { + const shallowRenderer = createRenderer(); + let result = shallowRenderer.render(, { foo: 'foo', }); expect(result).toEqual(
foo:bar
); - var instance = shallowRenderer.getMountedInstance(); + const instance = shallowRenderer.getMountedInstance(); instance.setState({bar: 'baz'}); result = shallowRenderer.getRenderOutput(); @@ -409,7 +439,7 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = createRenderer(); + const shallowRenderer = createRenderer(); shallowRenderer.render(); expectDev(console.error.calls.count()).toBe(1); expect( @@ -434,7 +464,7 @@ describe('ReactTestUtils', () => { name: PropTypes.string.isRequired, }; - var shallowRenderer = createRenderer(); + const shallowRenderer = createRenderer(); shallowRenderer.render(React.createElement(SimpleComponent, {name: 123})); expect(console.error.calls.count()).toBe(1); @@ -454,8 +484,8 @@ describe('ReactTestUtils', () => { } } - var renderedComponent = ReactTestUtils.renderIntoDocument(); - var scryResults = ReactTestUtils.scryRenderedDOMComponentsWithClass( + const renderedComponent = ReactTestUtils.renderIntoDocument(); + const scryResults = ReactTestUtils.scryRenderedDOMComponentsWithClass( renderedComponent, 'NonExistentClass', ); @@ -469,8 +499,8 @@ describe('ReactTestUtils', () => { } } - var renderedComponent = ReactTestUtils.renderIntoDocument(); - var scryResults = ReactTestUtils.scryRenderedDOMComponentsWithClass( + const renderedComponent = ReactTestUtils.renderIntoDocument(); + const scryResults = ReactTestUtils.scryRenderedDOMComponentsWithClass( renderedComponent, 'x', ); @@ -484,20 +514,20 @@ describe('ReactTestUtils', () => { } } - var renderedComponent = ReactTestUtils.renderIntoDocument(); - var scryResults1 = ReactTestUtils.scryRenderedDOMComponentsWithClass( + const renderedComponent = ReactTestUtils.renderIntoDocument(); + const scryResults1 = ReactTestUtils.scryRenderedDOMComponentsWithClass( renderedComponent, 'x y', ); expect(scryResults1.length).toBe(1); - var scryResults2 = ReactTestUtils.scryRenderedDOMComponentsWithClass( + const scryResults2 = ReactTestUtils.scryRenderedDOMComponentsWithClass( renderedComponent, 'x z', ); expect(scryResults2.length).toBe(1); - var scryResults3 = ReactTestUtils.scryRenderedDOMComponentsWithClass( + const scryResults3 = ReactTestUtils.scryRenderedDOMComponentsWithClass( renderedComponent, ['x', 'y'], ); @@ -506,13 +536,13 @@ describe('ReactTestUtils', () => { expect(scryResults1[0]).toBe(scryResults2[0]); expect(scryResults1[0]).toBe(scryResults3[0]); - var scryResults4 = ReactTestUtils.scryRenderedDOMComponentsWithClass( + const scryResults4 = ReactTestUtils.scryRenderedDOMComponentsWithClass( renderedComponent, ['x', 'a'], ); expect(scryResults4.length).toBe(0); - var scryResults5 = ReactTestUtils.scryRenderedDOMComponentsWithClass( + const scryResults5 = ReactTestUtils.scryRenderedDOMComponentsWithClass( renderedComponent, ['x a'], ); @@ -526,7 +556,7 @@ describe('ReactTestUtils', () => { } } - var container = document.createElement('div'); + const container = document.createElement('div'); ReactDOM.render( {null} @@ -534,7 +564,7 @@ describe('ReactTestUtils', () => { , container, ); - var tree = ReactDOM.render( + const tree = ReactDOM.render(
orange
purple
@@ -542,7 +572,7 @@ describe('ReactTestUtils', () => { container, ); - var log = []; + const log = []; ReactTestUtils.findAllInRenderedTree(tree, function(child) { if (ReactTestUtils.isDOMComponent(child)) { log.push(ReactDOM.findDOMNode(child).textContent); @@ -554,9 +584,9 @@ describe('ReactTestUtils', () => { }); it('should support injected wrapper components as DOM components', () => { - var getTestDocument = require('getTestDocument'); + const getTestDocument = require('getTestDocument'); - var injectedDOMComponents = [ + const injectedDOMComponents = [ 'button', 'form', 'iframe', @@ -568,7 +598,7 @@ describe('ReactTestUtils', () => { ]; injectedDOMComponents.forEach(function(type) { - var testComponent = ReactTestUtils.renderIntoDocument( + const testComponent = ReactTestUtils.renderIntoDocument( React.createElement(type), ); expect(testComponent.tagName).toBe(type.toUpperCase()); @@ -592,9 +622,9 @@ describe('ReactTestUtils', () => { } } - var markup = ReactDOMServer.renderToString(); - var testDocument = getTestDocument(markup); - var component = ReactDOM.render(, testDocument); + const markup = ReactDOMServer.renderToString(); + const testDocument = getTestDocument(markup); + const component = ReactDOM.render(, testDocument); expect(component.refs.html.tagName).toBe('HTML'); expect(component.refs.head.tagName).toBe('HEAD'); @@ -605,19 +635,19 @@ describe('ReactTestUtils', () => { }); it('should change the value of an input field', () => { - var obj = { + const obj = { handler: function(e) { e.persist(); }, }; spyOn(obj, 'handler').and.callThrough(); - var container = document.createElement('div'); - var instance = ReactDOM.render( + const container = document.createElement('div'); + const instance = ReactDOM.render( , container, ); - var node = ReactDOM.findDOMNode(instance); + const node = ReactDOM.findDOMNode(instance); node.value = 'giraffe'; ReactTestUtils.Simulate.change(node); @@ -637,19 +667,19 @@ describe('ReactTestUtils', () => { } } - var obj = { + const obj = { handler: function(e) { e.persist(); }, }; spyOn(obj, 'handler').and.callThrough(); - var container = document.createElement('div'); - var instance = ReactDOM.render( + const container = document.createElement('div'); + const instance = ReactDOM.render( , container, ); - var node = ReactDOM.findDOMNode(instance.refs.input); + const node = ReactDOM.findDOMNode(instance.refs.input); node.value = 'zebra'; ReactTestUtils.Simulate.change(node); @@ -669,9 +699,9 @@ describe('ReactTestUtils', () => { } } - var handler = jasmine.createSpy('spy'); - var shallowRenderer = createRenderer(); - var result = shallowRenderer.render( + const handler = jasmine.createSpy('spy'); + const shallowRenderer = createRenderer(); + const result = shallowRenderer.render( , ); @@ -685,7 +715,7 @@ describe('ReactTestUtils', () => { it('should not warn when simulating events with extra properties', () => { spyOn(console, 'error'); - var CLIENT_X = 100; + const CLIENT_X = 100; class Component extends React.Component { handleClick = e => { @@ -697,8 +727,8 @@ describe('ReactTestUtils', () => { } } - var element = document.createElement('div'); - var instance = ReactDOM.render(, element); + const element = document.createElement('div'); + const instance = ReactDOM.render(, element); ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(instance), { clientX: CLIENT_X, }); @@ -706,7 +736,7 @@ describe('ReactTestUtils', () => { }); it('can scry with stateless components involved', () => { - var Stateless = () =>

; + const Stateless = () =>

; class SomeComponent extends React.Component { render() { @@ -719,8 +749,8 @@ describe('ReactTestUtils', () => { } } - var inst = ReactTestUtils.renderIntoDocument(); - var hrs = ReactTestUtils.scryRenderedDOMComponentsWithTag(inst, 'hr'); + const inst = ReactTestUtils.renderIntoDocument(); + const hrs = ReactTestUtils.scryRenderedDOMComponentsWithTag(inst, 'hr'); expect(hrs.length).toBe(2); }); diff --git a/src/renderers/testing/ReactShallowRenderer.js b/src/renderers/testing/ReactShallowRenderer.js index da8c55d731033..df9ef2484077f 100644 --- a/src/renderers/testing/ReactShallowRenderer.js +++ b/src/renderers/testing/ReactShallowRenderer.js @@ -136,6 +136,10 @@ class Updater { } enqueueSetState(publicInstance, partialState, callback, callerName) { + if (typeof partialState === 'function') { + partialState = partialState(publicInstance.state, publicInstance.props); + } + this._renderer._newState = { ...publicInstance.state, ...partialState, From bf0583b504011a0dba422edd93ff79d8f2fb2230 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Wed, 19 Apr 2017 09:47:03 -0700 Subject: [PATCH 34/37] Better handle setState() calls from cWM or cWRP --- .../dom/test/__tests__/ReactTestUtils-test.js | 25 +++ src/renderers/testing/ReactShallowRenderer.js | 156 +++++++++--------- 2 files changed, 106 insertions(+), 75 deletions(-) diff --git a/src/renderers/dom/test/__tests__/ReactTestUtils-test.js b/src/renderers/dom/test/__tests__/ReactTestUtils-test.js index 3de63689b989a..2a1004248ad6f 100644 --- a/src/renderers/dom/test/__tests__/ReactTestUtils-test.js +++ b/src/renderers/dom/test/__tests__/ReactTestUtils-test.js @@ -350,6 +350,31 @@ describe('ReactTestUtils', () => { expect(result).toEqual(
doovy
); }); + it('can setState in componentWillReceiveProps when shallow rendering', () => { + class SimpleComponent extends React.Component { + state = {count: 0}; + + componentWillReceiveProps(nextProps) { + if (nextProps.updateState) { + this.setState({count: 1}); + } + } + + render() { + return
{this.state.count}
; + } + } + + const shallowRenderer = createRenderer(); + let result = shallowRenderer.render( + , + ); + expect(result.props.children).toEqual(0); + + result = shallowRenderer.render(); + expect(result.props.children).toEqual(1); + }); + it('can setState with an updater function', () => { let instance; diff --git a/src/renderers/testing/ReactShallowRenderer.js b/src/renderers/testing/ReactShallowRenderer.js index df9ef2484077f..d9224920eb36d 100644 --- a/src/renderers/testing/ReactShallowRenderer.js +++ b/src/renderers/testing/ReactShallowRenderer.js @@ -31,6 +31,7 @@ class ReactShallowRenderer { this._instance = null; this._newState = null; this._rendered = null; + this._rendering = false; this._updater = new Updater(this); } @@ -59,17 +60,16 @@ class ReactShallowRenderer { element.type, ); + if (this._rendering) { + return; + } + + this._rendering = true; this._element = element; this._context = context; if (this._instance) { - this._rendered = updateClassComponent( - this._instance, - this._rendered, - element.props, - this._newState, - context, - ); + this._updateClassComponent(element.props, context); } else { if (shouldConstruct(element.type)) { this._instance = new element.type(element.props, context); @@ -88,17 +88,14 @@ class ReactShallowRenderer { ReactDebugCurrentFrame.element = null; } - this._rendered = mountClassComponent( - this._instance, - element.props, - context, - this._updater, - ); + this._mountClassComponent(element.props, context); } else { this._rendered = element.type(element.props, context); } } + this._rendering = false; + return this.getRenderOutput(); } @@ -115,6 +112,77 @@ class ReactShallowRenderer { this._rendered = null; this._instance = null; } + + _mountClassComponent(props, context) { + this._instance.context = context; + this._instance.props = props; + this._instance.state = this._instance.state || emptyObject; + this._instance.updater = this._updater; + + if (typeof this._instance.componentWillMount === 'function') { + const beforeState = this._newState; + + this._instance.componentWillMount(); + + // setState may have been called during cWM + if (beforeState !== this._newState) { + this._instance.state = this._newState || emptyObject; + } + } + + this._rendered = this._instance.render(); + + // Calling cDU might lead to problems with host component references. + // Since our components aren't really mounted, refs won't be available. + // if (typeof this._instance.componentDidMount === 'function') { + // this._instance.componentDidMount(); + // } + } + + _updateClassComponent(props, context) { + const oldProps = this._instance.props; + const oldState = this._instance.state; + + if ( + oldProps !== props && + typeof this._instance.componentWillReceiveProps === 'function' + ) { + this._instance.componentWillReceiveProps(props); + } + + // Read state after cWRP in case it calls setState + const state = this._newState || emptyObject; + + if (typeof this._instance.shouldComponentUpdate === 'function') { + if ( + this._instance.shouldComponentUpdate(props, state, context) === false + ) { + this._instance.context = context; + this._instance.props = props; + this._instance.state = state; + + return; + } + } + + if (typeof this._instance.componentWillUpdate === 'function') { + this._instance.componentWillUpdate(props, state, context); + } + + this._instance.context = context; + this._instance.props = props; + this._instance.state = state; + + this._rendered = this._instance.render(); + + // The 15.x shallow renderer triggered cDU for setState() calls only. + if ( + oldState !== state && + typeof this._instance.componentDidUpdate === 'function' + ) { + this._instance.componentDidUpdate(oldProps, oldState); + } + } } class Updater { @@ -158,70 +226,8 @@ function getName(type, instance) { null; } -function mountClassComponent(instance, props, context, updater) { - instance.context = context; - instance.props = props; - instance.state = instance.state || emptyObject; - instance.updater = updater; - - if (typeof instance.componentWillMount === 'function') { - instance.componentWillMount(); - } - - const rendered = instance.render(); - - // Calling cDU might lead to problems with host component references. - // Since our components aren't really mounted, refs won't be available. - // if (typeof instance.componentDidMount === 'function') { - // instance.componentDidMount(); - // } - - return rendered; -} - function shouldConstruct(Component) { return !!(Component.prototype && Component.prototype.isReactComponent); } -function updateClassComponent(instance, rendered, props, state, context) { - state = state || emptyObject; - - const oldProps = instance.props; - const oldState = instance.state; - - if ( - oldProps !== props && - typeof instance.componentWillReceiveProps === 'function' - ) { - instance.componentWillReceiveProps(props); - } - - if (typeof instance.shouldComponentUpdate === 'function') { - if (instance.shouldComponentUpdate(props, state, context) === false) { - instance.context = context; - instance.props = props; - instance.state = state; - - return rendered; - } - } - - if (typeof instance.componentWillUpdate === 'function') { - instance.componentWillUpdate(props, state, context); - } - - instance.context = context; - instance.props = props; - instance.state = state; - - rendered = instance.render(); - - // The 15.x shallow renderer triggered cDU for setState() calls only. - if (oldState !== state && typeof instance.componentDidUpdate === 'function') { - instance.componentDidUpdate(oldProps, oldState); - } - - return rendered; -} - module.exports = ReactShallowRenderer; From fc1c6f913cb74a2cb1ce1cfe6ba91b431786c4ac Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Wed, 19 Apr 2017 13:31:47 -0700 Subject: [PATCH 35/37] Shallow renderer preserves state for cloned element --- .../dom/test/__tests__/ReactTestUtils-test.js | 47 +++++++++++++++++++ src/renderers/testing/ReactShallowRenderer.js | 3 +- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/renderers/dom/test/__tests__/ReactTestUtils-test.js b/src/renderers/dom/test/__tests__/ReactTestUtils-test.js index 2a1004248ad6f..2623e0fa89393 100644 --- a/src/renderers/dom/test/__tests__/ReactTestUtils-test.js +++ b/src/renderers/dom/test/__tests__/ReactTestUtils-test.js @@ -779,6 +779,31 @@ describe('ReactTestUtils', () => { expect(hrs.length).toBe(2); }); + it('should enable rendering of cloned element', () => { + class SimpleComponent extends React.Component { + constructor(props) { + super(props); + + this.state = { + bar: 'bar', + }; + } + + render() { + return
{`${this.props.foo}:${this.state.bar}`}
; + } + } + + const shallowRenderer = createRenderer(); + let result = shallowRenderer.render(); + expect(result).toEqual(
foo:bar
); + + const instance = shallowRenderer.getMountedInstance(); + const cloned = React.cloneElement(instance, {foo: 'baz'}); + result = shallowRenderer.render(cloned); + expect(result).toEqual(
baz:bar
); + }); + describe('Simulate', () => { it('should set the type of the event', () => { let event; @@ -796,5 +821,27 @@ describe('ReactTestUtils', () => { expect(event.type).toBe('keydown'); expect(event.nativeEvent.type).toBe('keydown'); }); + + it('should work with renderIntoDocument', () => { + const onChange = jest.fn(); + + class MyComponent extends React.Component { + render() { + return
; + } + } + + const instance = ReactTestUtils.renderIntoDocument(); + const input = ReactTestUtils.findRenderedDOMComponentWithTag( + instance, + 'input', + ); + input.value = 'giraffe'; + ReactTestUtils.Simulate.change(input); + + expect(onChange).toHaveBeenCalledWith( + jasmine.objectContaining({target: input}), + ); + }); }); }); diff --git a/src/renderers/testing/ReactShallowRenderer.js b/src/renderers/testing/ReactShallowRenderer.js index d9224920eb36d..913e148724c42 100644 --- a/src/renderers/testing/ReactShallowRenderer.js +++ b/src/renderers/testing/ReactShallowRenderer.js @@ -151,7 +151,8 @@ class ReactShallowRenderer { } // Read state after cWRP in case it calls setState - const state = this._newState || emptyObject; + // Fallback to previous instance state to support rendering React.cloneElement() + const state = this._newState || this._instance.state || emptyObject; if (typeof this._instance.shouldComponentUpdate === 'function') { if ( From 50793d4f21a4b88a58125c507e280d277164d264 Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Wed, 19 Apr 2017 14:33:06 -0700 Subject: [PATCH 36/37] TestUtils use batchedUpdates from ReactDOM instead of loading separately --- src/renderers/dom/test/ReactTestUtils.js | 5 ++--- src/renderers/testing/ReactShallowRenderer.js | 6 +++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/renderers/dom/test/ReactTestUtils.js b/src/renderers/dom/test/ReactTestUtils.js index 96ceb6ed0f745..23ac6fd774f9b 100644 --- a/src/renderers/dom/test/ReactTestUtils.js +++ b/src/renderers/dom/test/ReactTestUtils.js @@ -18,7 +18,6 @@ var ReactFiberTreeReflection = require('ReactFiberTreeReflection'); var ReactInstanceMap = require('ReactInstanceMap'); var ReactShallowRenderer = require('ReactShallowRenderer'); // TODO (bvaughn) Remove this import before 16.0.0 var ReactTypeOfWork = require('ReactTypeOfWork'); -var ReactGenericBatching = require('ReactGenericBatching'); var SyntheticEvent = require('SyntheticEvent'); var invariant = require('fbjs/lib/invariant'); @@ -29,9 +28,9 @@ var { EventPluginHub, EventPluginRegistry, EventPropagators, + ReactBrowserEventEmitter, ReactControlledComponent, ReactDOMComponentTree, - ReactBrowserEventEmitter, } = ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; var topLevelTypes = EventConstants.topLevelTypes; @@ -480,7 +479,7 @@ function makeSimulator(eventType) { EventPropagators.accumulateDirectDispatches(event); } - ReactGenericBatching.batchedUpdates(function() { + ReactDOM.unstable_batchedUpdates(function() { // Normally extractEvent enqueues a state restore, but we'll just always // do that since we we're by-passing it here. ReactControlledComponent.enqueueStateRestore(node); diff --git a/src/renderers/testing/ReactShallowRenderer.js b/src/renderers/testing/ReactShallowRenderer.js index 913e148724c42..ee114dfde3639 100644 --- a/src/renderers/testing/ReactShallowRenderer.js +++ b/src/renderers/testing/ReactShallowRenderer.js @@ -72,7 +72,11 @@ class ReactShallowRenderer { this._updateClassComponent(element.props, context); } else { if (shouldConstruct(element.type)) { - this._instance = new element.type(element.props, context); + this._instance = new element.type( + element.props, + context, + this._updater, + ); if (element.type.hasOwnProperty('contextTypes')) { ReactDebugCurrentFrame.element = element; From 5710088a1a09c236d5898988dac00a631c6abf2c Mon Sep 17 00:00:00 2001 From: Brian Vaughn Date: Wed, 19 Apr 2017 15:38:51 -0700 Subject: [PATCH 37/37] Updated fiber passing tests --- scripts/fiber/tests-passing.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt index 3c73a93de6d9d..ab1c2f7577866 100644 --- a/scripts/fiber/tests-passing.txt +++ b/scripts/fiber/tests-passing.txt @@ -1575,6 +1575,8 @@ src/renderers/dom/test/__tests__/ReactTestUtils-test.js * can shallowly render components with contextTypes * can shallowly render components with ref as function * can setState in componentWillMount when shallow rendering +* can setState in componentWillReceiveProps when shallow rendering +* can setState with an updater function * can pass context when shallowly rendering * should track context across updates * can fail context when shallowly rendering @@ -1589,7 +1591,9 @@ src/renderers/dom/test/__tests__/ReactTestUtils-test.js * should throw when attempting to use ReactTestUtils.Simulate with shallow rendering * should not warn when simulating events with extra properties * can scry with stateless components involved +* should enable rendering of cloned element * should set the type of the event +* should work with renderIntoDocument src/renderers/native/__tests__/ReactNativeAttributePayload-test.js * should work with simple example