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..31e2a261949ca --- /dev/null +++ b/packages/react-dom/test-utils.js @@ -0,0 +1,7 @@ +'use strict'; + +if (process.env.NODE_ENV === 'production') { + throw Error('test-utils is not available in production mode.'); +} else { + module.exports = require('./cjs/react-dom-test-utils.development'); +} 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'); +} 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..b75c643cf0eca --- /dev/null +++ b/packages/react-test-renderer/shallow.js @@ -0,0 +1,7 @@ +'use strict'; + +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'); +} diff --git a/scripts/fiber/tests-passing.txt b/scripts/fiber/tests-passing.txt index 0cf8d024093b5..ab1c2f7577866 100644 --- a/scripts/fiber/tests-passing.txt +++ b/scripts/fiber/tests-passing.txt @@ -1560,6 +1560,41 @@ 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 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 +* 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 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 +* should warn about propTypes (but only once) +* 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 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 * should skip fields that are equal @@ -1868,32 +1903,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 cc33df8d123e8..7d90027c59339 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -89,9 +89,7 @@ const bundles = [ 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', ], @@ -118,9 +116,41 @@ const bundles = [ 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', + ], + }, + { + babelOpts: babelOptsReact, + bundleTypes: [FB_DEV, NODE_DEV], + config: { + destDir: 'build/', + globals: { + react: 'React', + }, + moduleName: 'ReactTestUtils', + sourceMap: false, + }, + entry: 'src/renderers/dom/test/ReactTestUtils', + externals: [ + 'prop-types', + 'prop-types/checkPropTypes', + 'react', + 'react-dom', + 'react-test-renderer', // TODO (bvaughn) Remove this dependency before 16.0.0 + ], + 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/ReactVersion.js', 'src/shared/**/*.js', ], @@ -151,7 +181,6 @@ const bundles = [ 'src/renderers/dom/**/*.js', 'src/renderers/shared/**/*.js', - 'src/isomorphic/classic/types/checkPropTypes.js', 'src/ReactVersion.js', 'src/shared/**/*.js', ], @@ -190,7 +219,6 @@ const bundles = [ 'src/renderers/art/**/*.js', 'src/renderers/shared/**/*.js', - 'src/isomorphic/classic/types/checkPropTypes.js', 'src/ReactVersion.js', 'src/shared/**/*.js', ], @@ -226,7 +254,6 @@ const bundles = [ 'src/renderers/art/**/*.js', 'src/renderers/shared/**/*.js', - 'src/isomorphic/classic/types/checkPropTypes.js', 'src/ReactVersion.js', 'src/shared/**/*.js', ], @@ -328,12 +355,10 @@ const bundles = [ 'src/renderers/shared/**/*.js', 'src/renderers/testing/**/*.js', - 'src/isomorphic/classic/types/checkPropTypes.js', 'src/ReactVersion.js', 'src/shared/**/*.js', ], }, - { babelOpts: babelOptsReact, bundleTypes: [FB_DEV], @@ -355,11 +380,32 @@ const bundles = [ 'src/renderers/shared/**/*.js', 'src/renderers/testing/**/*.js', - 'src/isomorphic/classic/types/checkPropTypes.js', 'src/ReactVersion.js', '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', + 'prop-types/checkPropTypes', + 'react-test-renderer', + ], + fbEntry: 'src/renderers/testing/ReactShallowRenderer', + hasteName: 'ReactShallowRenderer', + isRenderer: true, + label: 'shallow-renderer', + manglePropertiesOnProd: false, + name: 'react-test-renderer/shallow', + paths: ['src/renderers/shared/**/*.js', 'src/renderers/testing/**/*.js'], + }, /******* React Noop Renderer (used only for fixtures/fiber-debugger) *******/ { @@ -383,7 +429,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/shims/facebook-www/ReactTestUtils.js b/scripts/rollup/shims/facebook-www/ReactTestUtils.js deleted file mode 100644 index dafb421d1d187..0000000000000 --- a/scripts/rollup/shims/facebook-www/ReactTestUtils.js +++ /dev/null @@ -1,18 +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 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; 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/src/isomorphic/React.js b/src/isomorphic/React.js index 807a1c369f4f5..eebe7b9b556a9 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; @@ -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() { 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-test-renderer/index.js similarity index 66% rename from src/node_modules/react/lib/checkPropTypes.js rename to src/node_modules/react-test-renderer/index.js index 083e4df83ad8a..a11b1836c7b0d 100644 --- a/src/node_modules/react/lib/checkPropTypes.js +++ b/src/node_modules/react-test-renderer/index.js @@ -6,4 +6,4 @@ 'use strict'; -module.exports = require('checkPropTypes'); +module.exports = require('ReactTestRenderer'); diff --git a/src/renderers/dom/ReactDOM.js b/src/renderers/dom/ReactDOM.js index f5f976ea7d4aa..601ce0d924b33 100644 --- a/src/renderers/dom/ReactDOM.js +++ b/src/renderers/dom/ReactDOM.js @@ -42,6 +42,12 @@ 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 + 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 bb327bb36b441..d3ed8381ca2d4 100644 --- a/src/renderers/dom/fiber/ReactDOMFiber.js +++ b/src/renderers/dom/fiber/ReactDOMFiber.js @@ -542,6 +542,12 @@ 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 + EventPluginRegistry: require('EventPluginRegistry'), + EventPropagators: require('EventPropagators'), + ReactControlledComponent, + ReactDOMComponentTree, + ReactBrowserEventEmitter, }, }; diff --git a/src/test/ReactTestUtils.js b/src/renderers/dom/test/ReactTestUtils.js similarity index 93% rename from src/test/ReactTestUtils.js rename to src/renderers/dom/test/ReactTestUtils.js index f320be4bd12b5..23ac6fd774f9b 100644 --- a/src/test/ReactTestUtils.js +++ b/src/renderers/dom/test/ReactTestUtils.js @@ -12,23 +12,26 @@ '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('ReactDOM'); -var ReactDOMComponentTree = require('ReactDOMComponentTree'); -var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter'); +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 ReactShallowRenderer = require('ReactShallowRenderer'); -var findDOMNode = require('findDOMNode'); var invariant = require('fbjs/lib/invariant'); +var warning = require('fbjs/lib/warning'); + +var {findDOMNode} = ReactDOM; +var { + EventPluginHub, + EventPluginRegistry, + EventPropagators, + ReactBrowserEventEmitter, + ReactControlledComponent, + ReactDOMComponentTree, +} = ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; var topLevelTypes = EventConstants.topLevelTypes; var { @@ -38,6 +41,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) {} /** @@ -403,9 +420,9 @@ var ReactTestUtils = { }; }, - createRenderer: function() { - return new ReactShallowRenderer(); - }, + // TODO (bvaughn) Remove this warning accessor before 16.0.0. + // It's only being added for temporary deprecation notice in RN. + createRenderer: createRendererWithWarning, Simulate: null, SimulateNative: {}, @@ -462,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/test/__tests__/ReactTestUtils-test.js b/src/renderers/dom/test/__tests__/ReactTestUtils-test.js similarity index 55% rename from src/test/__tests__/ReactTestUtils-test.js rename to src/renderers/dom/test/__tests__/ReactTestUtils-test.js index cd1b2a5dc8a5c..2623e0fa89393 100644 --- a/src/test/__tests__/ReactTestUtils-test.js +++ b/src/renderers/dom/test/__tests__/ReactTestUtils-test.js @@ -11,14 +11,16 @@ 'use strict'; -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(() => { + createRenderer = require('ReactShallowRenderer').createRenderer; PropTypes = require('prop-types'); React = require('react'); ReactDOM = require('react-dom'); @@ -26,6 +28,66 @@ 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
; + } + } + + const shallowRenderer = createRenderer(); + shallowRenderer.render(); + + // 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); + + const instance = shallowRenderer.getMountedInstance(); + instance.setState({}); + + // The previous shallow renderer triggered cDU for setState() calls. + expect(logs).toEqual([ + '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', () => { + function Parent() { + return
; + } + function Child() { + throw Error('This component should not render'); + } + + const shallowRenderer = createRenderer(); + shallowRenderer.render(React.createElement(Parent)); + }); + it('should have shallow rendering', () => { class SomeComponent extends React.Component { render() { @@ -38,8 +100,8 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = ReactTestUtils.createRenderer(); - var result = shallowRenderer.render(); + const shallowRenderer = createRenderer(); + const result = shallowRenderer.render(); expect(result.type).toBe('div'); expect(result.props.children).toEqual([ @@ -48,21 +110,52 @@ 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 this.state.update !== nextState.update; + } + render() { + renderCounter++; + return
{`${renderCounter}`}
; + } + } + + const shallowRenderer = createRenderer(); + shallowRenderer.render(); + expect(shallowRenderer.getRenderOutput()).toEqual(
1
); + + const 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() { + function SomeComponent(props, context) { return (
+
{props.foo}
+
{context.bar}
); } - var shallowRenderer = ReactTestUtils.createRenderer(); - var result = shallowRenderer.render(); + const shallowRenderer = createRenderer(); + const result = shallowRenderer.render(, { + bar: 'BAR', + }); expect(result.type).toBe('div'); expect(result.props.children).toEqual([ +
FOO
, +
BAR
, , , ]); @@ -75,7 +168,7 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = ReactTestUtils.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 ' + @@ -90,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; @@ -99,7 +192,7 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = ReactTestUtils.createRenderer(); + const shallowRenderer = createRenderer(); shallowRenderer.render(); shallowRenderer.unmount(); @@ -113,8 +206,8 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = ReactTestUtils.createRenderer(); - var result = shallowRenderer.render(); + const shallowRenderer = createRenderer(); + const result = shallowRenderer.render(); expect(result).toBe(null); }); @@ -126,7 +219,7 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = ReactTestUtils.createRenderer(); + const shallowRenderer = createRenderer(); // Shouldn't crash. shallowRenderer.render(); }); @@ -140,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 ( @@ -159,21 +252,21 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = ReactTestUtils.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'); }); @@ -189,7 +282,7 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = ReactTestUtils.createRenderer(); + const shallowRenderer = createRenderer(); shallowRenderer.render(); expect(shallowRenderer.getMountedInstance().someMethod()).toEqual(5); }); @@ -205,8 +298,8 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = ReactTestUtils.createRenderer(); - var result = shallowRenderer.render(); + const shallowRenderer = createRenderer(); + const result = shallowRenderer.render(); expect(result).toEqual(
); }); @@ -229,9 +322,9 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = ReactTestUtils.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(); @@ -252,11 +345,66 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = ReactTestUtils.createRenderer(); - var result = shallowRenderer.render(); + const shallowRenderer = createRenderer(); + const result = shallowRenderer.render(); 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; + + 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 = { @@ -268,13 +416,41 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = ReactTestUtils.createRenderer(); - var result = shallowRenderer.render(, { + const shallowRenderer = createRenderer(); + const result = shallowRenderer.render(, { name: 'foo', }); 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}`}
; + } + } + + const shallowRenderer = createRenderer(); + let result = shallowRenderer.render(, { + foo: 'foo', + }); + expect(result).toEqual(
foo:bar
); + + const 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'); @@ -288,7 +464,7 @@ describe('ReactTestUtils', () => { } } - var shallowRenderer = ReactTestUtils.createRenderer(); + const shallowRenderer = createRenderer(); shallowRenderer.render(); expectDev(console.error.calls.count()).toBe(1); expect( @@ -300,6 +476,32 @@ describe('ReactTestUtils', () => { ); }); + 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, + }; + + const 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 { render() { @@ -307,8 +509,8 @@ describe('ReactTestUtils', () => { } } - var renderedComponent = ReactTestUtils.renderIntoDocument(); - var scryResults = ReactTestUtils.scryRenderedDOMComponentsWithClass( + const renderedComponent = ReactTestUtils.renderIntoDocument(); + const scryResults = ReactTestUtils.scryRenderedDOMComponentsWithClass( renderedComponent, 'NonExistentClass', ); @@ -322,8 +524,8 @@ describe('ReactTestUtils', () => { } } - var renderedComponent = ReactTestUtils.renderIntoDocument(); - var scryResults = ReactTestUtils.scryRenderedDOMComponentsWithClass( + const renderedComponent = ReactTestUtils.renderIntoDocument(); + const scryResults = ReactTestUtils.scryRenderedDOMComponentsWithClass( renderedComponent, 'x', ); @@ -337,20 +539,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'], ); @@ -359,13 +561,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'], ); @@ -379,7 +581,7 @@ describe('ReactTestUtils', () => { } } - var container = document.createElement('div'); + const container = document.createElement('div'); ReactDOM.render( {null} @@ -387,7 +589,7 @@ describe('ReactTestUtils', () => { , container, ); - var tree = ReactDOM.render( + const tree = ReactDOM.render(
orange
purple
@@ -395,7 +597,7 @@ describe('ReactTestUtils', () => { container, ); - var log = []; + const log = []; ReactTestUtils.findAllInRenderedTree(tree, function(child) { if (ReactTestUtils.isDOMComponent(child)) { log.push(ReactDOM.findDOMNode(child).textContent); @@ -407,9 +609,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', @@ -421,7 +623,7 @@ describe('ReactTestUtils', () => { ]; injectedDOMComponents.forEach(function(type) { - var testComponent = ReactTestUtils.renderIntoDocument( + const testComponent = ReactTestUtils.renderIntoDocument( React.createElement(type), ); expect(testComponent.tagName).toBe(type.toUpperCase()); @@ -445,9 +647,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'); @@ -458,19 +660,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); @@ -490,19 +692,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); @@ -522,9 +724,9 @@ describe('ReactTestUtils', () => { } } - var handler = jasmine.createSpy('spy'); - var shallowRenderer = ReactTestUtils.createRenderer(); - var result = shallowRenderer.render( + const handler = jasmine.createSpy('spy'); + const shallowRenderer = createRenderer(); + const result = shallowRenderer.render( , ); @@ -538,7 +740,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 => { @@ -550,8 +752,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, }); @@ -559,7 +761,7 @@ describe('ReactTestUtils', () => { }); it('can scry with stateless components involved', () => { - var Stateless = () =>

; + const Stateless = () =>

; class SomeComponent extends React.Component { render() { @@ -572,11 +774,36 @@ 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); }); + 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; @@ -594,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/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'); diff --git a/src/renderers/testing/ReactShallowRenderer.js b/src/renderers/testing/ReactShallowRenderer.js new file mode 100644 index 0000000000000..ee114dfde3639 --- /dev/null +++ b/src/renderers/testing/ReactShallowRenderer.js @@ -0,0 +1,238 @@ +/** + * 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'; + +const checkPropTypes = require('prop-types/checkPropTypes'); +const React = require('react'); + +const emptyObject = require('fbjs/lib/emptyObject'); +const invariant = require('fbjs/lib/invariant'); + +const {ReactDebugCurrentFrame} = require('ReactGlobalSharedState'); + +class ReactShallowRenderer { + static createRenderer = function() { + return new ReactShallowRenderer(); + }; + + constructor() { + this._context = null; + this._element = null; + this._instance = null; + this._newState = null; + this._rendered = null; + this._rendering = false; + this._updater = new Updater(this); + } + + getMountedInstance() { + return this._instance; + } + + getRenderOutput() { + return this._rendered; + } + + render(element, context = emptyObject) { + 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 (this._rendering) { + return; + } + + this._rendering = true; + this._element = element; + this._context = context; + + if (this._instance) { + this._updateClassComponent(element.props, context); + } else { + if (shouldConstruct(element.type)) { + this._instance = new element.type( + element.props, + context, + this._updater, + ); + + 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._mountClassComponent(element.props, context); + } else { + this._rendered = element.type(element.props, context); + } + } + + this._rendering = false; + + return this.getRenderOutput(); + } + + unmount() { + if (this._instance) { + if (typeof this._instance.componentWillUnmount === 'function') { + this._instance.componentWillUnmount(); + } + } + + this._context = null; + this._element = null; + this._newState = null; + 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 + // 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 ( + 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 { + constructor(renderer) { + this._renderer = renderer; + } + + isMounted(publicInstance) { + return !!this._renderer._element; + } + + enqueueForceUpdate(publicInstance, callback, callerName) { + this._renderer.render(this._renderer._element, this._renderer._context); + } + + enqueueReplaceState(publicInstance, completeState, callback, callerName) { + this._renderer._newState = completeState; + this._renderer.render(this._renderer._element, this._renderer._context); + } + + enqueueSetState(publicInstance, partialState, callback, callerName) { + if (typeof partialState === 'function') { + partialState = partialState(publicInstance.state, publicInstance.props); + } + + this._renderer._newState = { + ...publicInstance.state, + ...partialState, + }; + + this._renderer.render(this._renderer._element, this._renderer._context); + } +} + +function getName(type, instance) { + var constructor = instance && instance.constructor; + return type.displayName || + (constructor && constructor.displayName) || + type.name || + (constructor && constructor.name) || + null; +} + +function shouldConstruct(Component) { + return !!(Component.prototype && Component.prototype.isReactComponent); +} + +module.exports = ReactShallowRenderer; diff --git a/src/renderers/testing/stack/ReactTestRendererStack.js b/src/renderers/testing/stack/ReactTestRendererStack.js index f87b0383bd2a8..bad8b3589d827 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 = { @@ -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. @@ -54,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; @@ -82,7 +107,7 @@ class ReactTestComponent { } receiveComponent( - nextElement: ReactElement, + nextElement: ReactElement, transaction: ReactTestReconcileTransaction, context: Object, ) { @@ -113,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, }; @@ -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: (element: ReactElement, options?: TestRendererOptions) => { + inject(); + + return ReactTestMount.render(element, options); + }, /* eslint-disable camelcase */ unstable_batchedUpdates: ReactUpdates.batchedUpdates, /* eslint-enable camelcase */ 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;