diff --git a/package.json b/package.json index 90511293b6a..15e51ecb428 100644 --- a/package.json +++ b/package.json @@ -108,9 +108,40 @@ "version-check": "node ./scripts/tasks/version-check.js" }, "jest": { - "projects": [ - "/scripts/jest/stack.config.json", - "/scripts/jest/fiber.config.json" - ] + "modulePathIgnorePatterns": [ + "/.module-cache/", + "/build/", + "/scripts/rollup/shims/", + "/scripts/bench/" + ], + "transform": { + ".*": "./scripts/jest/preprocessor.js" + }, + "setupFiles": [ + "./scripts/jest/setup.js", + "./scripts/jest/environment.js" + ], + "setupTestFrameworkScriptFile": "./scripts/jest/test-framework-setup.js", + "testRegex": "/__tests__/.*(\\.js|coffee|ts)$", + "moduleFileExtensions": [ + "js", + "json", + "node", + "coffee", + "ts" + ], + "roots": [ + "/eslint-rules", + "/mocks", + "/scripts", + "/src", + "node_modules/fbjs" + ], + "collectCoverageFrom": [ + "src/**/*.js", + "!src/__mocks__/vendor/third_party/*.js", + "!src/test/*.js" + ], + "timers": "fake" } } diff --git a/scripts/babel/transform-object-assign-require.js b/scripts/babel/transform-object-assign-require.js index d11fba24663..3f3430d3e47 100644 --- a/scripts/babel/transform-object-assign-require.js +++ b/scripts/babel/transform-object-assign-require.js @@ -13,16 +13,12 @@ module.exports = function autoImporter(babel) { const t = babel.types; function getAssignIdent(path, file, state) { - if (!state.id) { - state.id = path.scope.generateUidIdentifier('assign'); - path.scope.getProgramParent().push({ - id: state.id, - init: t.callExpression(t.identifier('require'), [ - t.stringLiteral('object-assign'), - ]), - }); + if (state.id) { + return state.id; } - return state.id; + const id = file.addImport('object-assign', 'default', 'assign'); + state.id = id; + return id; } return { diff --git a/scripts/error-codes/replace-invariant-error-codes.js b/scripts/error-codes/replace-invariant-error-codes.js index 820953377b3..d252a03f1cc 100644 --- a/scripts/error-codes/replace-invariant-error-codes.js +++ b/scripts/error-codes/replace-invariant-error-codes.js @@ -20,19 +20,17 @@ module.exports = function(babel) { var SEEN_SYMBOL = Symbol('replace-invariant-error-codes.seen'); // Generate a hygienic identifier - function getProdInvariantIdentifier(path, localState) { - if (!localState.prodInvariantIdentifier) { - localState.prodInvariantIdentifier = path.scope.generateUidIdentifier( - 'prodInvariant' - ); - path.scope.getProgramParent().push({ - id: localState.prodInvariantIdentifier, - init: t.callExpression(t.identifier('require'), [ - t.stringLiteral('reactProdInvariant'), - ]), - }); + function getProdInvariantIdentifier(path, file, localState) { + if (localState.prodInvariantIdentifier) { + return localState.prodInvariantIdentifier; } - return localState.prodInvariantIdentifier; + const id = file.addImport( + 'reactProdInvariant', + 'default', + 'reactProdInvariant' + ); + localState.prodInvariantIdentifier = id; + return id; } var DEV_EXPRESSION = t.identifier('__DEV__'); @@ -44,7 +42,7 @@ module.exports = function(babel) { visitor: { CallExpression: { - exit: function(path) { + exit: function(path, file) { var node = path.node; // Ignore if it's already been processed if (node[SEEN_SYMBOL]) { @@ -59,7 +57,7 @@ module.exports = function(babel) { path.get('arguments')[0].isStringLiteral({value: 'invariant'}) ) { node[SEEN_SYMBOL] = true; - getProdInvariantIdentifier(path, this); + getProdInvariantIdentifier(path, file, this); } else if (path.get('callee').isIdentifier({name: 'invariant'})) { // Turns this code: // @@ -110,7 +108,7 @@ module.exports = function(babel) { devInvariant[SEEN_SYMBOL] = true; - var localInvariantId = getProdInvariantIdentifier(path, this); + var localInvariantId = getProdInvariantIdentifier(path, file, this); var prodInvariant = t.callExpression( localInvariantId, [t.stringLiteral(prodErrorId)].concat(node.arguments.slice(2)) diff --git a/scripts/jest/fiber.config.json b/scripts/jest/fiber.config.json deleted file mode 100644 index c6f47bf86cd..00000000000 --- a/scripts/jest/fiber.config.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "modulePathIgnorePatterns": [ - "/.module-cache/", - "/build/", - "/scripts/rollup/shims/", - "/scripts/bench/" - ], - "rootDir": "../../", - "transform": { - ".*": "./scripts/jest/preprocessor.js" - }, - "setupFiles": [ - "./scripts/jest/fiber.setup.js", - "./scripts/jest/environment.js" - ], - "setupTestFrameworkScriptFile": "./scripts/jest/test-framework-setup.js", - "testRegex": "/__tests__/.*(\\.js|coffee|ts)$", - "moduleFileExtensions": [ - "js", - "json", - "node", - "coffee", - "ts" - ], - "roots": [ - "/eslint-rules", - "/mocks", - "/scripts", - "/src", - "node_modules/fbjs" - ], - "collectCoverageFrom": [ - "src/**/*.js", - "!src/__mocks__/vendor/third_party/*.js", - "!src/test/*.js" - ], - "timers": "fake" -} diff --git a/scripts/jest/preprocessor.js b/scripts/jest/preprocessor.js index 7727b1b2d8b..24b563d652b 100644 --- a/scripts/jest/preprocessor.js +++ b/scripts/jest/preprocessor.js @@ -48,6 +48,8 @@ var babelOptions = { }), }, ], + // TODO: is this enough? + require.resolve('babel-plugin-transform-es2015-modules-commonjs'), // Keep stacks detailed in tests. // Don't put this in .babelrc so that we don't embed filenames // into ReactART builds that include JSX. diff --git a/scripts/jest/fiber.setup.js b/scripts/jest/setup.js similarity index 74% rename from scripts/jest/fiber.setup.js rename to scripts/jest/setup.js index 9972f16fff8..a0fbd78530a 100644 --- a/scripts/jest/fiber.setup.js +++ b/scripts/jest/setup.js @@ -2,12 +2,6 @@ // We want to globally mock this but jest doesn't let us do that by default // for a file that already exists. So we have to explicitly mock it. -jest.mock('ReactDOMFeatureFlags', () => { - const flags = require.requireActual('ReactDOMFeatureFlags'); - return Object.assign({}, flags, { - useFiber: true, - }); -}); jest.mock('ReactFeatureFlags', () => { const flags = require.requireActual('ReactFeatureFlags'); return Object.assign({}, flags, { diff --git a/scripts/jest/stack.config.json b/scripts/jest/stack.config.json deleted file mode 100644 index 77972ca00cb..00000000000 --- a/scripts/jest/stack.config.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "modulePathIgnorePatterns": [ - "/.module-cache/", - "/build/", - "/scripts/rollup/shims/", - "/scripts/bench/" - ], - "rootDir": "../../", - "transform": { - ".*": "./scripts/jest/preprocessor.js" - }, - "setupFiles": [ - "./scripts/jest/stack.setup.js", - "./scripts/jest/environment.js" - ], - "setupTestFrameworkScriptFile": "./scripts/jest/test-framework-setup.js", - "testRegex": "/__tests__/.*(\\.js|coffee|ts)$", - "moduleFileExtensions": [ - "js", - "json", - "node", - "coffee", - "ts" - ], - "roots": [ - "/eslint-rules", - "/mocks", - "/scripts", - "/src", - "node_modules/fbjs" - ], - "collectCoverageFrom": [ - "src/**/*.js", - "!src/__mocks__/vendor/third_party/*.js", - "!src/test/*.js" - ], - "timers": "fake" -} diff --git a/scripts/jest/stack.setup.js b/scripts/jest/stack.setup.js deleted file mode 100644 index 820ad8c5c0e..00000000000 --- a/scripts/jest/stack.setup.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -// We want to globally mock this but jest doesn't let us do that by default -// for a file that already exists. So we have to explicitly mock it. -jest.mock('ReactDOMFeatureFlags', () => { - const flags = require.requireActual('ReactDOMFeatureFlags'); - return Object.assign({}, flags, { - useFiber: false, - }); -}); - -// Error logging varies between Fiber and Stack; -// Rather than fork dozens of tests, mock the error-logging file by default. -jest.mock('ReactFiberErrorLogger'); diff --git a/scripts/rollup/modules.js b/scripts/rollup/modules.js index 63636d3ae28..3c86653ae40 100644 --- a/scripts/rollup/modules.js +++ b/scripts/rollup/modules.js @@ -46,10 +46,7 @@ const fbjsModules = [ ]; const devOnlyFilesToStubOut = [ - "'ReactDebugCurrentFrame'", - "'ReactComponentTreeHook'", - "'ReactPerf'", - "'ReactTestUtils'", + // TODO: figure out the ESM strategy ]; const legacyModules = [ diff --git a/scripts/rollup/packaging.js b/scripts/rollup/packaging.js index e5cf6f7eae6..caa09834c50 100644 --- a/scripts/rollup/packaging.js +++ b/scripts/rollup/packaging.js @@ -25,7 +25,7 @@ const facebookWWWSrcDependencies = [ // these files need to be copied to the react-native build const reactNativeSrcDependencies = [ // TODO: copy this to RN repository and delete from React - 'src/renderers/shared/stack/PooledClass.js', + 'src/renderers/native/PooledClass.js', 'src/renderers/shared/fiber/isomorphic/ReactTypes.js', 'src/renderers/native/ReactNativeTypes.js', ]; diff --git a/scripts/rollup/results.json b/scripts/rollup/results.json index 18926d3dda1..12269dfbdeb 100644 --- a/scripts/rollup/results.json +++ b/scripts/rollup/results.json @@ -1,168 +1,168 @@ { "bundleSizes": { "react.development.js (UMD_DEV)": { - "size": 65990, - "gzip": 16792 + "size": 56348, + "gzip": 14331 }, "react.production.min.js (UMD_PROD)": { - "size": 6359, - "gzip": 2597 + "size": 6010, + "gzip": 2510 }, "react.development.js (NODE_DEV)": { - "size": 56312, - "gzip": 14447 + "size": 46827, + "gzip": 12290 }, "react.production.min.js (NODE_PROD)": { "size": 5382, "gzip": 2230 }, "React-dev.js (FB_DEV)": { - "size": 53513, - "gzip": 13663 + "size": 44081, + "gzip": 11508 }, "React-prod.js (FB_PROD)": { "size": 25014, "gzip": 6703 }, "react-dom.development.js (UMD_DEV)": { - "size": 635358, - "gzip": 144868 + "size": 607001, + "gzip": 136492 }, "react-dom.production.min.js (UMD_PROD)": { - "size": 108806, - "gzip": 34152 + "size": 93693, + "gzip": 30033 }, "react-dom.development.js (NODE_DEV)": { - "size": 594767, - "gzip": 135242 + "size": 580949, + "gzip": 133808 }, "react-dom.production.min.js (NODE_PROD)": { - "size": 112503, - "gzip": 35538 + "size": 105524, + "gzip": 33161 }, "ReactDOMFiber-dev.js (FB_DEV)": { - "size": 591587, - "gzip": 134688 + "size": 577839, + "gzip": 133294 }, "ReactDOMFiber-prod.js (FB_PROD)": { - "size": 425507, - "gzip": 95372 + "size": 414529, + "gzip": 92865 }, "react-dom-test-utils.development.js (NODE_DEV)": { - "size": 53332, - "gzip": 13396 + "size": 50829, + "gzip": 12937 }, "ReactTestUtils-dev.js (FB_DEV)": { - "size": 53124, - "gzip": 13357 + "size": 50634, + "gzip": 12899 }, "react-dom-unstable-native-dependencies.development.js (UMD_DEV)": { - "size": 87907, - "gzip": 22195 + "size": 77861, + "gzip": 19204 }, "react-dom-unstable-native-dependencies.production.min.js (UMD_PROD)": { - "size": 15149, - "gzip": 4910 + "size": 11820, + "gzip": 4061 }, "react-dom-unstable-native-dependencies.development.js (NODE_DEV)": { - "size": 81355, - "gzip": 20240 + "size": 80593, + "gzip": 19937 }, "react-dom-unstable-native-dependencies.production.min.js (NODE_PROD)": { - "size": 13926, - "gzip": 4403 + "size": 13658, + "gzip": 4315 }, "ReactDOMUnstableNativeDependencies-dev.js (FB_DEV)": { - "size": 81054, - "gzip": 20200 + "size": 80292, + "gzip": 19898 }, "ReactDOMUnstableNativeDependencies-prod.js (FB_PROD)": { - "size": 66066, - "gzip": 15736 + "size": 65095, + "gzip": 15511 }, "react-dom-server.browser.development.js (UMD_DEV)": { - "size": 123203, - "gzip": 31145 + "size": 123208, + "gzip": 30978 }, "react-dom-server.browser.production.min.js (UMD_PROD)": { - "size": 19874, - "gzip": 7592 + "size": 14621, + "gzip": 5872 }, "react-dom-server.browser.development.js (NODE_DEV)": { - "size": 92379, - "gzip": 23736 + "size": 100294, + "gzip": 26540 }, "react-dom-server.browser.production.min.js (NODE_PROD)": { - "size": 18930, - "gzip": 7264 + "size": 14095, + "gzip": 5614 }, "ReactDOMServer-dev.js (FB_DEV)": { - "size": 91572, - "gzip": 23660 + "size": 99487, + "gzip": 26440 }, "ReactDOMServer-prod.js (FB_PROD)": { - "size": 49652, - "gzip": 13866 + "size": 42941, + "gzip": 12198 }, "react-dom-server.node.development.js (NODE_DEV)": { - "size": 95156, - "gzip": 24301 + "size": 103071, + "gzip": 27107 }, "react-dom-server.node.production.min.js (NODE_PROD)": { - "size": 19986, - "gzip": 7613 + "size": 15152, + "gzip": 5954 }, "react-art.development.js (UMD_DEV)": { - "size": 373742, - "gzip": 82778 + "size": 373348, + "gzip": 82768 }, "react-art.production.min.js (UMD_PROD)": { - "size": 83498, - "gzip": 25838 + "size": 83304, + "gzip": 25830 }, "react-art.development.js (NODE_DEV)": { - "size": 295131, - "gzip": 62776 + "size": 294743, + "gzip": 62622 }, "react-art.production.min.js (NODE_PROD)": { - "size": 52520, - "gzip": 16419 + "size": 52331, + "gzip": 16319 }, "ReactARTFiber-dev.js (FB_DEV)": { - "size": 294072, - "gzip": 62835 + "size": 293684, + "gzip": 62680 }, "ReactARTFiber-prod.js (FB_PROD)": { - "size": 220269, - "gzip": 45845 + "size": 219775, + "gzip": 45708 }, "ReactNativeFiber-dev.js (RN_DEV)": { - "size": 303055, - "gzip": 52683 + "size": 275492, + "gzip": 48139 }, "ReactNativeFiber-prod.js (RN_PROD)": { - "size": 218403, - "gzip": 37966 + "size": 215839, + "gzip": 37506 }, "react-test-renderer.development.js (NODE_DEV)": { - "size": 299567, - "gzip": 63217 + "size": 298589, + "gzip": 62995 }, "ReactTestRendererFiber-dev.js (FB_DEV)": { - "size": 298493, - "gzip": 63294 + "size": 297530, + "gzip": 63062 }, "react-test-renderer-shallow.development.js (NODE_DEV)": { - "size": 9556, - "gzip": 2379 + "size": 9272, + "gzip": 2316 }, "ReactShallowRenderer-dev.js (FB_DEV)": { - "size": 9464, - "gzip": 2338 + "size": 9180, + "gzip": 2271 }, "react-noop-renderer.development.js (NODE_DEV)": { - "size": 286842, - "gzip": 60085 + "size": 286448, + "gzip": 59927 }, "react-dom-server.development.js (UMD_DEV)": { "size": 120897, diff --git a/src/ReactVersion.js b/src/ReactVersion.js index d06ad351a50..926067151fe 100644 --- a/src/ReactVersion.js +++ b/src/ReactVersion.js @@ -11,4 +11,5 @@ 'use strict'; +// TODO: convert this file to ESM (this currently breaks Node script). module.exports = '16.0.0-beta.5'; diff --git a/src/__mocks__/UIManager.js b/src/__mocks__/UIManager.js index 28aa5304a6d..3391ea3f5d7 100644 --- a/src/__mocks__/UIManager.js +++ b/src/__mocks__/UIManager.js @@ -11,8 +11,8 @@ // Mock of the Native Hooks -const ReactNativeTagHandles = require('ReactNativeTagHandles'); -const invariant = require('fbjs/lib/invariant'); +import {reactTagIsNativeTopRootID} from 'ReactNativeTagHandles'; +import invariant from 'fbjs/lib/invariant'; // Map of viewTag -> {children: [childTag], parent: ?parentTag} const roots = []; @@ -20,7 +20,7 @@ let views = new Map(); function autoCreateRoot(tag) { // Seriously, this is how we distinguish roots in RN. - if (!views.has(tag) && ReactNativeTagHandles.reactTagIsNativeTopRootID(tag)) { + if (!views.has(tag) && reactTagIsNativeTopRootID(tag)) { roots.push(tag); views.set(tag, { children: [], @@ -203,4 +203,4 @@ var RCTUIManager = { }, }; -module.exports = RCTUIManager; +export default RCTUIManager; diff --git a/src/isomorphic/ReactEntry.js b/src/isomorphic/ReactEntry.js index e6ecd54048e..865522f4594 100644 --- a/src/isomorphic/ReactEntry.js +++ b/src/isomorphic/ReactEntry.js @@ -11,56 +11,57 @@ 'use strict'; -var ReactBaseClasses = require('ReactBaseClasses'); -var ReactChildren = require('ReactChildren'); -var ReactElement = require('ReactElement'); -var ReactVersion = require('ReactVersion'); - -var onlyChild = require('onlyChild'); - -var createElement = ReactElement.createElement; -var createFactory = ReactElement.createFactory; -var cloneElement = ReactElement.cloneElement; - -if (__DEV__) { - var ReactElementValidator = require('ReactElementValidator'); - createElement = ReactElementValidator.createElement; - createFactory = ReactElementValidator.createFactory; - cloneElement = ReactElementValidator.cloneElement; -} +import { + Component, + PureComponent, + AsyncComponent as unstable_AsyncComponent, +} from 'ReactBaseClasses'; +import {forEach, map, count, toArray, only} from 'ReactChildren'; +import { + createElement, + createFactory, + cloneElement, + isValidElement, +} from 'ReactElement'; +import { + createElementWithValidation, + createFactoryWithValidation, + cloneElementWithValidation, +} from 'ReactElementValidator'; +import ReactCurrentOwner from 'ReactCurrentOwner'; +import ReactVersion from 'ReactVersion'; +import * as ReactDebugCurrentFrame from 'ReactDebugCurrentFrame'; var React = { Children: { - map: ReactChildren.map, - forEach: ReactChildren.forEach, - count: ReactChildren.count, - toArray: ReactChildren.toArray, - only: onlyChild, + map, + forEach, + count, + toArray, + only, }, - Component: ReactBaseClasses.Component, - PureComponent: ReactBaseClasses.PureComponent, - unstable_AsyncComponent: ReactBaseClasses.AsyncComponent, - - createElement: createElement, - cloneElement: cloneElement, - isValidElement: ReactElement.isValidElement, + Component, + PureComponent, + unstable_AsyncComponent, - createFactory: createFactory, + createElement: __DEV__ ? createElementWithValidation : createElement, + cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement, + createFactory: __DEV__ ? createFactoryWithValidation : createFactory, + isValidElement: isValidElement, version: ReactVersion, __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: { - ReactCurrentOwner: require('ReactCurrentOwner'), + ReactCurrentOwner, }, }; if (__DEV__) { Object.assign(React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, { // These should not be included in production. - ReactComponentTreeHook: require('ReactComponentTreeHook'), - ReactDebugCurrentFrame: require('ReactDebugCurrentFrame'), + ReactDebugCurrentFrame, }); } -module.exports = React; +export default React; diff --git a/src/isomorphic/children/ReactChildren.js b/src/isomorphic/children/ReactChildren.js index dd70a5d9684..6f99d80e960 100644 --- a/src/isomorphic/children/ReactChildren.js +++ b/src/isomorphic/children/ReactChildren.js @@ -11,15 +11,11 @@ 'use strict'; -var ReactElement = require('ReactElement'); - -var emptyFunction = require('fbjs/lib/emptyFunction'); -var invariant = require('fbjs/lib/invariant'); - -if (__DEV__) { - var warning = require('fbjs/lib/warning'); - var {getStackAddendum} = require('ReactDebugCurrentFrame'); -} +import {isValidElement, cloneAndReplaceKey} from 'ReactElement'; +import emptyFunction from 'fbjs/lib/emptyFunction'; +import invariant from 'fbjs/lib/invariant'; +import warning from 'fbjs/lib/warning'; +import {getStackAddendum} from 'ReactDebugCurrentFrame'; var ITERATOR_SYMBOL = typeof Symbol === 'function' && Symbol.iterator; var FAUX_ITERATOR_SYMBOL = '@@iterator'; // Before Symbol spec. @@ -274,7 +270,7 @@ function forEachSingleChild(bookKeeping, child, name) { * @param {function(*, int)} forEachFunc * @param {*} forEachContext Context for forEachContext. */ -function forEachChildren(children, forEachFunc, forEachContext) { +export function forEach(children, forEachFunc, forEachContext) { if (children == null) { return children; } @@ -300,8 +296,8 @@ function mapSingleChildIntoContext(bookKeeping, child, childKey) { emptyFunction.thatReturnsArgument, ); } else if (mappedChild != null) { - if (ReactElement.isValidElement(mappedChild)) { - mappedChild = ReactElement.cloneAndReplaceKey( + if (isValidElement(mappedChild)) { + mappedChild = cloneAndReplaceKey( mappedChild, // Keep both the (mapped) and old keys if they differ, just as // traverseAllChildren used to do for objects as children @@ -344,7 +340,7 @@ function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) { * @param {*} context Context for mapFunction. * @return {object} Object containing the ordered map of results. */ -function mapChildren(children, func, context) { +export function map(children, func, context) { if (children == null) { return children; } @@ -362,7 +358,7 @@ function mapChildren(children, func, context) { * @param {?*} children Children tree container. * @return {number} The number of children. */ -function countChildren(children, context) { +export function count(children, context) { return traverseAllChildren(children, emptyFunction.thatReturnsNull, null); } @@ -372,7 +368,7 @@ function countChildren(children, context) { * * See https://facebook.github.io/react/docs/react-api.html#react.children.toarray */ -function toArray(children) { +export function toArray(children) { var result = []; mapIntoWithKeyPrefixInternal( children, @@ -383,11 +379,24 @@ function toArray(children) { return result; } -var ReactChildren = { - forEach: forEachChildren, - map: mapChildren, - count: countChildren, - toArray: toArray, -}; - -module.exports = ReactChildren; +/** + * Returns the first child in a collection of children and verifies that there + * is only one child in the collection. + * + * See https://facebook.github.io/react/docs/react-api.html#react.children.only + * + * The current implementation of this function assumes that a single child gets + * passed without a wrapper, but the purpose of this helper function is to + * abstract away the particular structure of children. + * + * @param {?object} children Child collection structure. + * @return {ReactElement} The first and only `ReactElement` contained in the + * structure. + */ +export function only(children) { + invariant( + isValidElement(children), + 'React.Children.only expected to receive a single React element child.', + ); + return children; +} diff --git a/src/isomorphic/children/__tests__/ReactChildren-test.js b/src/isomorphic/children/__tests__/ReactChildren-test.js index 9d97007cd17..d92a3b38473 100644 --- a/src/isomorphic/children/__tests__/ReactChildren-test.js +++ b/src/isomorphic/children/__tests__/ReactChildren-test.js @@ -11,8 +11,6 @@ 'use strict'; -const ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); - describe('ReactChildren', () => { var React; var ReactTestUtils; @@ -881,57 +879,55 @@ describe('ReactChildren', () => { ); }); - if (ReactDOMFeatureFlags.useFiber) { - describe('with fragments enabled', () => { - beforeEach(() => { - ReactFeatureFlags = require('ReactFeatureFlags'); - ReactFeatureFlags.disableNewFiberFeatures = false; - }); + describe('with fragments enabled', () => { + beforeEach(() => { + ReactFeatureFlags = require('ReactFeatureFlags'); + ReactFeatureFlags.disableNewFiberFeatures = false; + }); - it('warns for keys for arrays of elements in a fragment', () => { - spyOn(console, 'error'); - class ComponentReturningArray extends React.Component { - render() { - return [
,
]; - } + it('warns for keys for arrays of elements in a fragment', () => { + spyOn(console, 'error'); + class ComponentReturningArray extends React.Component { + render() { + return [
,
]; } + } - ReactTestUtils.renderIntoDocument(); + ReactTestUtils.renderIntoDocument(); - expectDev(console.error.calls.count()).toBe(1); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: ' + - 'Each child in an array or iterator should have a unique "key" prop.' + - ' See https://fb.me/react-warning-keys for more information.' + - '\n in ComponentReturningArray (at **)', - ); - }); + expectDev(console.error.calls.count()).toBe(1); + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: ' + + 'Each child in an array or iterator should have a unique "key" prop.' + + ' See https://fb.me/react-warning-keys for more information.' + + '\n in ComponentReturningArray (at **)', + ); + }); - it('does not warn when there are keys on elements in a fragment', () => { - spyOn(console, 'error'); - class ComponentReturningArray extends React.Component { - render() { - return [
,
]; - } + it('does not warn when there are keys on elements in a fragment', () => { + spyOn(console, 'error'); + class ComponentReturningArray extends React.Component { + render() { + return [
,
]; } + } - ReactTestUtils.renderIntoDocument(); + ReactTestUtils.renderIntoDocument(); - expectDev(console.error.calls.count()).toBe(0); - }); + expectDev(console.error.calls.count()).toBe(0); + }); - it('warns for keys for arrays at the top level', () => { - spyOn(console, 'error'); + it('warns for keys for arrays at the top level', () => { + spyOn(console, 'error'); - ReactTestUtils.renderIntoDocument([
,
]); + ReactTestUtils.renderIntoDocument([
,
]); - expectDev(console.error.calls.count()).toBe(1); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: ' + - 'Each child in an array or iterator should have a unique "key" prop.' + - ' See https://fb.me/react-warning-keys for more information.', - ); - }); + expectDev(console.error.calls.count()).toBe(1); + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: ' + + 'Each child in an array or iterator should have a unique "key" prop.' + + ' See https://fb.me/react-warning-keys for more information.', + ); }); - } + }); }); diff --git a/src/isomorphic/children/onlyChild.js b/src/isomorphic/children/onlyChild.js deleted file mode 100644 index efe815594c8..00000000000 --- a/src/isomorphic/children/onlyChild.js +++ /dev/null @@ -1,39 +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 onlyChild - */ -'use strict'; - -var ReactElement = require('ReactElement'); - -var invariant = require('fbjs/lib/invariant'); - -/** - * Returns the first child in a collection of children and verifies that there - * is only one child in the collection. - * - * See https://facebook.github.io/react/docs/react-api.html#react.children.only - * - * The current implementation of this function assumes that a single child gets - * passed without a wrapper, but the purpose of this helper function is to - * abstract away the particular structure of children. - * - * @param {?object} children Child collection structure. - * @return {ReactElement} The first and only `ReactElement` contained in the - * structure. - */ -function onlyChild(children) { - invariant( - ReactElement.isValidElement(children), - 'React.Children.only expected to receive a single React element child.', - ); - return children; -} - -module.exports = onlyChild; diff --git a/src/isomorphic/classic/element/ReactCurrentOwner.js b/src/isomorphic/classic/element/ReactCurrentOwner.js index 3a12b523bd6..f42a1e4b8c7 100644 --- a/src/isomorphic/classic/element/ReactCurrentOwner.js +++ b/src/isomorphic/classic/element/ReactCurrentOwner.js @@ -12,7 +12,6 @@ 'use strict'; -import type {ReactInstance} from 'ReactInstanceType'; import type {Fiber} from 'ReactFiber'; /** @@ -26,7 +25,7 @@ var ReactCurrentOwner = { * @internal * @type {ReactComponent} */ - current: (null: null | ReactInstance | Fiber), + current: (null: null | Fiber), }; -module.exports = ReactCurrentOwner; +export default ReactCurrentOwner; diff --git a/src/isomorphic/classic/element/ReactDebugCurrentFrame.js b/src/isomorphic/classic/element/ReactDebugCurrentFrame.js index fad26ea1556..cb053bc3645 100644 --- a/src/isomorphic/classic/element/ReactDebugCurrentFrame.js +++ b/src/isomorphic/classic/element/ReactDebugCurrentFrame.js @@ -12,19 +12,21 @@ 'use strict'; -const ReactDebugCurrentFrame = {}; +let getCurrentStack: null | (() => string | null) = null; -if (__DEV__) { - // Component that is being worked on - ReactDebugCurrentFrame.getCurrentStack = (null: null | (() => string | null)); +export function setCurrentStackImplementation( + impl: null | (() => string | null), +) { + if (__DEV__) { + getCurrentStack = impl; + } +} - ReactDebugCurrentFrame.getStackAddendum = function(): string | null { - const impl = ReactDebugCurrentFrame.getCurrentStack; - if (impl) { - return impl(); +export function getStackAddendum(): string | null { + if (__DEV__) { + if (getCurrentStack) { + return getCurrentStack(); } - return null; - }; + } + return null; } - -module.exports = ReactDebugCurrentFrame; diff --git a/src/isomorphic/classic/element/ReactElement.js b/src/isomorphic/classic/element/ReactElement.js index 38fe85378e6..d7cf2bb5c81 100644 --- a/src/isomorphic/classic/element/ReactElement.js +++ b/src/isomorphic/classic/element/ReactElement.js @@ -11,12 +11,10 @@ 'use strict'; -var ReactCurrentOwner = require('ReactCurrentOwner'); -var hasOwnProperty = Object.prototype.hasOwnProperty; +import ReactCurrentOwner from 'ReactCurrentOwner'; +import warning from 'fbjs/lib/warning'; -if (__DEV__) { - var warning = require('fbjs/lib/warning'); -} +var hasOwnProperty = Object.prototype.hasOwnProperty; // The Symbol used to tag the ReactElement type. If there is no native Symbol // nor polyfill, then a plain number is used for performance. @@ -179,7 +177,7 @@ var ReactElement = function(type, key, ref, self, source, owner, props) { * Create and return a new ReactElement of the given type. * See https://facebook.github.io/react/docs/react-api.html#createelement */ -ReactElement.createElement = function(type, config, children) { +export function createElement(type, config, children) { var propName; // Reserved names are extracted @@ -265,14 +263,14 @@ ReactElement.createElement = function(type, config, children) { ReactCurrentOwner.current, props, ); -}; +} /** * Return a function that produces ReactElements of a given type. * See https://facebook.github.io/react/docs/react-api.html#createfactory */ -ReactElement.createFactory = function(type) { - var factory = ReactElement.createElement.bind(null, type); +export function createFactory(type) { + var factory = createElement.bind(null, type); // Expose the type on the factory and the prototype so that it can be // easily accessed on elements. E.g. `.type === Foo`. // This should not be named `constructor` since this may not be the function @@ -280,9 +278,9 @@ ReactElement.createFactory = function(type) { // Legacy hook TODO: Warn if this is accessed factory.type = type; return factory; -}; +} -ReactElement.cloneAndReplaceKey = function(oldElement, newKey) { +export function cloneAndReplaceKey(oldElement, newKey) { var newElement = ReactElement( oldElement.type, newKey, @@ -294,13 +292,13 @@ ReactElement.cloneAndReplaceKey = function(oldElement, newKey) { ); return newElement; -}; +} /** * Clone and return a new ReactElement using element as the starting point. * See https://facebook.github.io/react/docs/react-api.html#cloneelement */ -ReactElement.cloneElement = function(element, config, children) { +export function cloneElement(element, config, children) { var propName; // Original props are copied @@ -363,7 +361,7 @@ ReactElement.cloneElement = function(element, config, children) { } return ReactElement(element.type, key, ref, self, source, owner, props); -}; +} /** * Verifies the object is a ReactElement. @@ -372,12 +370,10 @@ ReactElement.cloneElement = function(element, config, children) { * @return {boolean} True if `object` is a valid component. * @final */ -ReactElement.isValidElement = function(object) { +export function isValidElement(object) { return ( typeof object === 'object' && object !== null && object.$$typeof === REACT_ELEMENT_TYPE ); -}; - -module.exports = ReactElement; +} diff --git a/src/isomorphic/classic/element/ReactElementValidator.js b/src/isomorphic/classic/element/ReactElementValidator.js index 8d2f6e55395..e18f675b59f 100644 --- a/src/isomorphic/classic/element/ReactElementValidator.js +++ b/src/isomorphic/classic/element/ReactElementValidator.js @@ -18,17 +18,16 @@ 'use strict'; -var ReactCurrentOwner = require('ReactCurrentOwner'); -var ReactElement = require('ReactElement'); +import ReactCurrentOwner from 'ReactCurrentOwner'; +import {isValidElement, createElement, cloneElement} from 'ReactElement'; +import checkPropTypes from 'prop-types/checkPropTypes'; +import lowPriorityWarning from 'lowPriorityWarning'; +import {getStackAddendum} from 'ReactDebugCurrentFrame'; +import warning from 'fbjs/lib/warning'; +import describeComponentFrame from 'describeComponentFrame'; +import getComponentName from 'getComponentName'; if (__DEV__) { - var checkPropTypes = require('prop-types/checkPropTypes'); - var lowPriorityWarning = require('lowPriorityWarning'); - var ReactDebugCurrentFrame = require('ReactDebugCurrentFrame'); - var warning = require('fbjs/lib/warning'); - var describeComponentFrame = require('describeComponentFrame'); - var getComponentName = require('getComponentName'); - var currentlyValidatingElement = null; var getDisplayName = function(element: ?ReactElement): string { @@ -43,7 +42,7 @@ if (__DEV__) { } }; - var getStackAddendum = function(): string { + var getElementStackAddendum = function(): string { var stack = ''; if (currentlyValidatingElement) { var name = getDisplayName(currentlyValidatingElement); @@ -54,7 +53,7 @@ if (__DEV__) { owner && getComponentName(owner), ); } - stack += ReactDebugCurrentFrame.getStackAddendum() || ''; + stack += getStackAddendum() || ''; return stack; }; } @@ -151,7 +150,7 @@ function validateExplicitKey(element, parentType) { '%s%s See https://fb.me/react-warning-keys for more information.%s', currentComponentErrorInfo, childOwner, - getStackAddendum(), + getElementStackAddendum(), ); } currentlyValidatingElement = null; @@ -173,11 +172,11 @@ function validateChildKeys(node, parentType) { if (Array.isArray(node)) { for (var i = 0; i < node.length; i++) { var child = node[i]; - if (ReactElement.isValidElement(child)) { + if (isValidElement(child)) { validateExplicitKey(child, parentType); } } - } else if (ReactElement.isValidElement(node)) { + } else if (isValidElement(node)) { // This element was passed in a valid location. if (node._store) { node._store.validated = true; @@ -192,7 +191,7 @@ function validateChildKeys(node, parentType) { var iterator = iteratorFn.call(node); var step; while (!(step = iterator.next()).done) { - if (ReactElement.isValidElement(step.value)) { + if (isValidElement(step.value)) { validateExplicitKey(step.value, parentType); } } @@ -226,7 +225,13 @@ function validatePropTypes(element) { if (propTypes) { currentlyValidatingElement = element; - checkPropTypes(propTypes, element.props, 'prop', name, getStackAddendum); + checkPropTypes( + propTypes, + element.props, + 'prop', + name, + getElementStackAddendum, + ); currentlyValidatingElement = null; } if (typeof componentClass.getDefaultProps === 'function') { @@ -238,100 +243,96 @@ function validatePropTypes(element) { } } -var ReactElementValidator = { - createElement: function(type, props, children) { - var validType = typeof type === 'string' || typeof type === 'function'; - // We warn in this case but don't throw. We expect the element creation to - // succeed and there will likely be errors in render. - if (!validType) { - var info = ''; - if ( - type === undefined || - (typeof type === 'object' && - type !== null && - Object.keys(type).length === 0) - ) { - info += - ' You likely forgot to export your component from the file ' + - "it's defined in."; - } +export function createElementWithValidation(type, props, children) { + var validType = typeof type === 'string' || typeof type === 'function'; + // We warn in this case but don't throw. We expect the element creation to + // succeed and there will likely be errors in render. + if (!validType) { + var info = ''; + if ( + type === undefined || + (typeof type === 'object' && + type !== null && + Object.keys(type).length === 0) + ) { + info += + ' You likely forgot to export your component from the file ' + + "it's defined in."; + } - var sourceInfo = getSourceInfoErrorAddendum(props); - if (sourceInfo) { - info += sourceInfo; - } else { - info += getDeclarationErrorAddendum(); - } + var sourceInfo = getSourceInfoErrorAddendum(props); + if (sourceInfo) { + info += sourceInfo; + } else { + info += getDeclarationErrorAddendum(); + } - info += ReactDebugCurrentFrame.getStackAddendum() || ''; + info += getStackAddendum() || ''; - warning( - false, - 'React.createElement: type is invalid -- expected a string (for ' + - 'built-in components) or a class/function (for composite ' + - 'components) but got: %s.%s', - type == null ? type : typeof type, - info, - ); - } + warning( + false, + 'React.createElement: type is invalid -- expected a string (for ' + + 'built-in components) or a class/function (for composite ' + + 'components) but got: %s.%s', + type == null ? type : typeof type, + info, + ); + } - var element = ReactElement.createElement.apply(this, arguments); + var element = createElement.apply(this, arguments); - // The result can be nullish if a mock or a custom function is used. - // TODO: Drop this when these are no longer allowed as the type argument. - if (element == null) { - return element; - } + // The result can be nullish if a mock or a custom function is used. + // TODO: Drop this when these are no longer allowed as the type argument. + if (element == null) { + return element; + } - // Skip key warning if the type isn't valid since our key validation logic - // doesn't expect a non-string/function type and can throw confusing errors. - // We don't want exception behavior to differ between dev and prod. - // (Rendering will throw with a helpful message and as soon as the type is - // fixed, the key warnings will appear.) - if (validType) { - for (var i = 2; i < arguments.length; i++) { - validateChildKeys(arguments[i], type); - } + // Skip key warning if the type isn't valid since our key validation logic + // doesn't expect a non-string/function type and can throw confusing errors. + // We don't want exception behavior to differ between dev and prod. + // (Rendering will throw with a helpful message and as soon as the type is + // fixed, the key warnings will appear.) + if (validType) { + for (var i = 2; i < arguments.length; i++) { + validateChildKeys(arguments[i], type); } + } - validatePropTypes(element); + validatePropTypes(element); - return element; - }, - - createFactory: function(type) { - var validatedFactory = ReactElementValidator.createElement.bind(null, type); - // Legacy hook TODO: Warn if this is accessed - validatedFactory.type = type; - - if (__DEV__) { - Object.defineProperty(validatedFactory, 'type', { - enumerable: false, - get: function() { - lowPriorityWarning( - false, - 'Factory.type is deprecated. Access the class directly ' + - 'before passing it to createFactory.', - ); - Object.defineProperty(this, 'type', { - value: type, - }); - return type; - }, - }); - } + return element; +} - return validatedFactory; - }, +export function createFactoryWithValidation(type) { + var validatedFactory = createElementWithValidation.bind(null, type); + // Legacy hook TODO: Warn if this is accessed + validatedFactory.type = type; - cloneElement: function(element, props, children) { - var newElement = ReactElement.cloneElement.apply(this, arguments); - for (var i = 2; i < arguments.length; i++) { - validateChildKeys(arguments[i], newElement.type); - } - validatePropTypes(newElement); - return newElement; - }, -}; + if (__DEV__) { + Object.defineProperty(validatedFactory, 'type', { + enumerable: false, + get: function() { + lowPriorityWarning( + false, + 'Factory.type is deprecated. Access the class directly ' + + 'before passing it to createFactory.', + ); + Object.defineProperty(this, 'type', { + value: type, + }); + return type; + }, + }); + } -module.exports = ReactElementValidator; + return validatedFactory; +} + +export function cloneElementWithValidation(element, props, children) { + var newElement = cloneElement.apply(this, arguments); + for (var i = 2; i < arguments.length; i++) { + validateChildKeys(arguments[i], newElement.type); + } + validatePropTypes(newElement); + return newElement; +} diff --git a/src/isomorphic/classic/element/__tests__/ReactElement-test.js b/src/isomorphic/classic/element/__tests__/ReactElement-test.js index e0f0047c47e..af21beb8c62 100644 --- a/src/isomorphic/classic/element/__tests__/ReactElement-test.js +++ b/src/isomorphic/classic/element/__tests__/ReactElement-test.js @@ -14,7 +14,6 @@ var React; var ReactDOM; var ReactTestUtils; -var ReactDOMFeatureFlags; describe('ReactElement', () => { var ComponentClass; @@ -31,7 +30,6 @@ describe('ReactElement', () => { React = require('react'); ReactDOM = require('react-dom'); ReactTestUtils = require('react-dom/test-utils'); - ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); // NOTE: We're explicitly not using JSX here. This is intended to test // classic JS without JSX. ComponentClass = class extends React.Component { @@ -237,12 +235,7 @@ describe('ReactElement', () => { var instance = ReactTestUtils.renderIntoDocument( React.createElement(Wrapper), ); - - if (ReactDOMFeatureFlags.useFiber) { - expect(element._owner.stateNode).toBe(instance); - } else { - expect(element._owner.getPublicInstance()).toBe(instance); - } + expect(element._owner.stateNode).toBe(instance); }); it('merges an additional argument onto the children prop', () => { diff --git a/src/isomorphic/hooks/ReactComponentTreeHook.js b/src/isomorphic/hooks/ReactComponentTreeHook.js deleted file mode 100644 index 08c107e5791..00000000000 --- a/src/isomorphic/hooks/ReactComponentTreeHook.js +++ /dev/null @@ -1,398 +0,0 @@ -/** - * Copyright 2016-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. - * - * @flow - * @providesModule ReactComponentTreeHook - */ - -'use strict'; - -var ReactCurrentOwner = require('ReactCurrentOwner'); -var invariant = require('fbjs/lib/invariant'); -var describeComponentFrame = require('describeComponentFrame'); - -if (__DEV__) { - var warning = require('fbjs/lib/warning'); -} - -import type {ReactElement, Source} from 'ReactElementType'; -import type {DebugID} from 'ReactInstanceType'; - -function isNative(fn) { - // Based on isNative() from Lodash - var funcToString = Function.prototype.toString; - var reIsNative = RegExp( - '^' + - funcToString - // Take an example native function source for comparison - .call(Object.prototype.hasOwnProperty) - // Strip regex characters so we can use it for regex - .replace(/[\\^$.*+?()[\]{}|]/g, '\\$&') - // Remove hasOwnProperty from the template to make it generic - .replace( - /hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, - '$1.*?', - ) + - '$', - ); - try { - var source = funcToString.call(fn); - return reIsNative.test(source); - } catch (err) { - return false; - } -} - -var canUseCollections = - // Array.from - typeof Array.from === 'function' && - // Map - typeof Map === 'function' && - isNative(Map) && - // Map.prototype.keys - Map.prototype != null && - typeof Map.prototype.keys === 'function' && - isNative(Map.prototype.keys) && - // Set - typeof Set === 'function' && - isNative(Set) && - // Set.prototype.keys - Set.prototype != null && - typeof Set.prototype.keys === 'function' && - isNative(Set.prototype.keys); - -var setItem; -var getItem; -var removeItem; -var getItemIDs; -var addRoot; -var removeRoot; -var getRootIDs; - -if (canUseCollections) { - var itemMap = new Map(); - var rootIDSet = new Set(); - - setItem = function(id, item) { - itemMap.set(id, item); - }; - getItem = function(id) { - return itemMap.get(id); - }; - removeItem = function(id) { - itemMap.delete(id); - }; - getItemIDs = function() { - return Array.from(itemMap.keys()); - }; - - addRoot = function(id) { - rootIDSet.add(id); - }; - removeRoot = function(id) { - rootIDSet.delete(id); - }; - getRootIDs = function() { - return Array.from(rootIDSet.keys()); - }; -} else { - var itemByKey = {}; - var rootByKey = {}; - - // Use non-numeric keys to prevent V8 performance issues: - // https://github.com/facebook/react/pull/7232 - var getKeyFromID = function(id: DebugID): string { - return '.' + id; - }; - var getIDFromKey = function(key: string): DebugID { - return parseInt(key.substr(1), 10); - }; - - setItem = function(id, item) { - var key = getKeyFromID(id); - itemByKey[key] = item; - }; - getItem = function(id) { - var key = getKeyFromID(id); - return itemByKey[key]; - }; - removeItem = function(id) { - var key = getKeyFromID(id); - delete itemByKey[key]; - }; - getItemIDs = function() { - return Object.keys(itemByKey).map(getIDFromKey); - }; - - addRoot = function(id) { - var key = getKeyFromID(id); - rootByKey[key] = true; - }; - removeRoot = function(id) { - var key = getKeyFromID(id); - delete rootByKey[key]; - }; - getRootIDs = function() { - return Object.keys(rootByKey).map(getIDFromKey); - }; -} - -var unmountedIDs: Array = []; - -function purgeDeep(id) { - var item = getItem(id); - if (item) { - var {childIDs} = item; - removeItem(id); - childIDs.forEach(purgeDeep); - } -} - -function getDisplayName(element: ?ReactElement): string { - if (element == null) { - return '#empty'; - } else if (typeof element === 'string' || typeof element === 'number') { - return '#text'; - } else if (typeof element.type === 'string') { - return element.type; - } else { - return element.type.displayName || element.type.name || 'Unknown'; - } -} - -function describeID(id: DebugID): string { - const name = ReactComponentTreeHook.getDisplayName(id); - const element = ReactComponentTreeHook.getElement(id); - const ownerID = ReactComponentTreeHook.getOwnerID(id); - let ownerName; - - if (ownerID) { - ownerName = ReactComponentTreeHook.getDisplayName(ownerID); - } - warning( - element, - 'ReactComponentTreeHook: Missing React element for debugID %s when ' + - 'building stack', - id, - ); - return describeComponentFrame( - name || '', - element && element._source, - ownerName || '', - ); -} - -var ReactComponentTreeHook = { - onSetChildren(id: DebugID, nextChildIDs: Array): void { - var item = getItem(id); - invariant(item, 'Item must have been set'); - item.childIDs = nextChildIDs; - - for (var i = 0; i < nextChildIDs.length; i++) { - var nextChildID = nextChildIDs[i]; - var nextChild = getItem(nextChildID); - invariant( - nextChild, - 'Expected hook events to fire for the child ' + - 'before its parent includes it in onSetChildren().', - ); - invariant( - nextChild.childIDs != null || - typeof nextChild.element !== 'object' || - nextChild.element == null, - 'Expected onSetChildren() to fire for a container child ' + - 'before its parent includes it in onSetChildren().', - ); - invariant( - nextChild.isMounted, - 'Expected onMountComponent() to fire for the child ' + - 'before its parent includes it in onSetChildren().', - ); - if (nextChild.parentID == null) { - nextChild.parentID = id; - // TODO: This shouldn't be necessary but mounting a new root during in - // componentWillMount currently causes not-yet-mounted components to - // be purged from our tree data so their parent id is missing. - } - invariant( - nextChild.parentID === id, - 'Expected onBeforeMountComponent() parent and onSetChildren() to ' + - 'be consistent (%s has parents %s and %s).', - nextChildID, - nextChild.parentID, - id, - ); - } - }, - - onBeforeMountComponent( - id: DebugID, - element: ReactElement, - parentID: DebugID, - ): void { - var item = { - element, - parentID, - text: null, - childIDs: [], - isMounted: false, - updateCount: 0, - }; - setItem(id, item); - }, - - onBeforeUpdateComponent(id: DebugID, element: ReactElement): void { - var item = getItem(id); - if (!item || !item.isMounted) { - // We may end up here as a result of setState() in componentWillUnmount(). - // In this case, ignore the element. - return; - } - item.element = element; - }, - - onMountComponent(id: DebugID): void { - var item = getItem(id); - invariant(item, 'Item must have been set'); - item.isMounted = true; - var isRoot = item.parentID === 0; - if (isRoot) { - addRoot(id); - } - }, - - onUpdateComponent(id: DebugID): void { - var item = getItem(id); - if (!item || !item.isMounted) { - // We may end up here as a result of setState() in componentWillUnmount(). - // In this case, ignore the element. - return; - } - item.updateCount++; - }, - - onUnmountComponent(id: DebugID): void { - var item = getItem(id); - if (item) { - // We need to check if it exists. - // `item` might not exist if it is inside an error boundary, and a sibling - // error boundary child threw while mounting. Then this instance never - // got a chance to mount, but it still gets an unmounting event during - // the error boundary cleanup. - item.isMounted = false; - var isRoot = item.parentID === 0; - if (isRoot) { - removeRoot(id); - } - } - unmountedIDs.push(id); - }, - - purgeUnmountedComponents(): void { - if (ReactComponentTreeHook._preventPurging) { - // Should only be used for testing. - return; - } - - for (var i = 0; i < unmountedIDs.length; i++) { - var id = unmountedIDs[i]; - purgeDeep(id); - } - unmountedIDs.length = 0; - }, - - isMounted(id: DebugID): boolean { - var item = getItem(id); - return item ? item.isMounted : false; - }, - - getCurrentStackAddendum(): string { - var info = ''; - var currentOwner = ReactCurrentOwner.current; - if (currentOwner) { - invariant( - typeof currentOwner.tag !== 'number', - 'Fiber owners should not show up in Stack stack traces.', - ); - if (typeof currentOwner._debugID === 'number') { - info += ReactComponentTreeHook.getStackAddendumByID( - currentOwner._debugID, - ); - } - } - return info; - }, - - getStackAddendumByID(id: ?DebugID): string { - var info = ''; - while (id) { - info += describeID(id); - id = ReactComponentTreeHook.getParentID(id); - } - return info; - }, - - getChildIDs(id: DebugID): Array { - var item = getItem(id); - return item ? item.childIDs : []; - }, - - getDisplayName(id: DebugID): ?string { - var element = ReactComponentTreeHook.getElement(id); - if (!element) { - return null; - } - return getDisplayName(element); - }, - - getElement(id: DebugID): ?ReactElement { - var item = getItem(id); - return item ? item.element : null; - }, - - getOwnerID(id: DebugID): ?DebugID { - var element = ReactComponentTreeHook.getElement(id); - if (!element || !element._owner) { - return null; - } - return element._owner._debugID; - }, - - getParentID(id: DebugID): ?DebugID { - var item = getItem(id); - return item ? item.parentID : null; - }, - - getSource(id: DebugID): ?Source { - var item = getItem(id); - var element = item ? item.element : null; - var source = element != null ? element._source : null; - return source; - }, - - getText(id: DebugID): ?string { - var element = ReactComponentTreeHook.getElement(id); - if (typeof element === 'string') { - return element; - } else if (typeof element === 'number') { - return '' + element; - } else { - return null; - } - }, - - getUpdateCount(id: DebugID): number { - var item = getItem(id); - return item ? item.updateCount : 0; - }, - - getRootIDs, - getRegisteredIDs: getItemIDs, -}; - -module.exports = ReactComponentTreeHook; diff --git a/src/isomorphic/modern/class/ReactBaseClasses.js b/src/isomorphic/modern/class/ReactBaseClasses.js index f2b3a6c6d6a..e4a88d42868 100644 --- a/src/isomorphic/modern/class/ReactBaseClasses.js +++ b/src/isomorphic/modern/class/ReactBaseClasses.js @@ -11,16 +11,15 @@ 'use strict'; -var ReactNoopUpdateQueue = require('ReactNoopUpdateQueue'); - -var emptyObject = require('fbjs/lib/emptyObject'); -var invariant = require('fbjs/lib/invariant'); -var lowPriorityWarning = require('lowPriorityWarning'); +import ReactNoopUpdateQueue from 'ReactNoopUpdateQueue'; +import emptyObject from 'fbjs/lib/emptyObject'; +import invariant from 'fbjs/lib/invariant'; +import lowPriorityWarning from 'lowPriorityWarning'; /** * Base class helpers for the updating state of a component. */ -function ReactComponent(props, context, updater) { +export function Component(props, context, updater) { this.props = props; this.context = context; this.refs = emptyObject; @@ -29,7 +28,7 @@ function ReactComponent(props, context, updater) { this.updater = updater || ReactNoopUpdateQueue; } -ReactComponent.prototype.isReactComponent = {}; +Component.prototype.isReactComponent = {}; /** * Sets a subset of the state. Always use this to mutate @@ -56,7 +55,7 @@ ReactComponent.prototype.isReactComponent = {}; * @final * @protected */ -ReactComponent.prototype.setState = function(partialState, callback) { +Component.prototype.setState = function(partialState, callback) { invariant( typeof partialState === 'object' || typeof partialState === 'function' || @@ -81,7 +80,7 @@ ReactComponent.prototype.setState = function(partialState, callback) { * @final * @protected */ -ReactComponent.prototype.forceUpdate = function(callback) { +Component.prototype.forceUpdate = function(callback) { this.updater.enqueueForceUpdate(this, callback, 'forceUpdate'); }; @@ -104,7 +103,7 @@ if (__DEV__) { ], }; var defineDeprecationWarning = function(methodName, info) { - Object.defineProperty(ReactComponent.prototype, methodName, { + Object.defineProperty(Component.prototype, methodName, { get: function() { lowPriorityWarning( false, @@ -126,8 +125,8 @@ if (__DEV__) { /** * Base class helpers for the updating state of a component. */ -function ReactPureComponent(props, context, updater) { - // Duplicated from ReactComponent. +export function PureComponent(props, context, updater) { + // Duplicated from Component. this.props = props; this.context = context; this.refs = emptyObject; @@ -137,15 +136,15 @@ function ReactPureComponent(props, context, updater) { } function ComponentDummy() {} -ComponentDummy.prototype = ReactComponent.prototype; -var pureComponentPrototype = (ReactPureComponent.prototype = new ComponentDummy()); -pureComponentPrototype.constructor = ReactPureComponent; +ComponentDummy.prototype = Component.prototype; +var pureComponentPrototype = (PureComponent.prototype = new ComponentDummy()); +pureComponentPrototype.constructor = PureComponent; // Avoid an extra prototype jump for these methods. -Object.assign(pureComponentPrototype, ReactComponent.prototype); +Object.assign(pureComponentPrototype, Component.prototype); pureComponentPrototype.isPureReactComponent = true; -function ReactAsyncComponent(props, context, updater) { - // Duplicated from ReactComponent. +export function AsyncComponent(props, context, updater) { + // Duplicated from Component. this.props = props; this.context = context; this.refs = emptyObject; @@ -154,17 +153,11 @@ function ReactAsyncComponent(props, context, updater) { this.updater = updater || ReactNoopUpdateQueue; } -var asyncComponentPrototype = (ReactAsyncComponent.prototype = new ComponentDummy()); -asyncComponentPrototype.constructor = ReactAsyncComponent; +var asyncComponentPrototype = (AsyncComponent.prototype = new ComponentDummy()); +asyncComponentPrototype.constructor = AsyncComponent; // Avoid an extra prototype jump for these methods. -Object.assign(asyncComponentPrototype, ReactComponent.prototype); +Object.assign(asyncComponentPrototype, Component.prototype); asyncComponentPrototype.unstable_isAsyncReactComponent = true; asyncComponentPrototype.render = function() { return this.props.children; }; - -module.exports = { - Component: ReactComponent, - PureComponent: ReactPureComponent, - AsyncComponent: ReactAsyncComponent, -}; diff --git a/src/isomorphic/modern/class/ReactNoopUpdateQueue.js b/src/isomorphic/modern/class/ReactNoopUpdateQueue.js index e3822644506..f0aa03f0430 100644 --- a/src/isomorphic/modern/class/ReactNoopUpdateQueue.js +++ b/src/isomorphic/modern/class/ReactNoopUpdateQueue.js @@ -11,9 +11,7 @@ 'use strict'; -if (__DEV__) { - var warning = require('fbjs/lib/warning'); -} +import warning from 'fbjs/lib/warning'; function warnNoop(publicInstance, callerName) { if (__DEV__) { @@ -109,4 +107,4 @@ var ReactNoopUpdateQueue = { }, }; -module.exports = ReactNoopUpdateQueue; +export default ReactNoopUpdateQueue; diff --git a/src/node_modules/react-dom/index.js b/src/node_modules/react-dom/index.js index 9e3e32c735b..509750de3b7 100644 --- a/src/node_modules/react-dom/index.js +++ b/src/node_modules/react-dom/index.js @@ -6,9 +6,4 @@ 'use strict'; -var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); -var useFiber = ReactDOMFeatureFlags.useFiber; - -module.exports = useFiber - ? require('ReactDOMFiberEntry') - : require('ReactDOMStackEntry'); +module.exports = require('ReactDOMFiberEntry'); diff --git a/src/node_modules/react-dom/server.browser.js b/src/node_modules/react-dom/server.browser.js index 8c08b1d6768..bc357430978 100644 --- a/src/node_modules/react-dom/server.browser.js +++ b/src/node_modules/react-dom/server.browser.js @@ -6,9 +6,4 @@ 'use strict'; -var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); -var useFiber = ReactDOMFeatureFlags.useFiber; - -module.exports = useFiber - ? require('ReactDOMServerBrowserEntry') - : require('ReactDOMServerStackEntry'); +module.exports = require('ReactDOMServerBrowserEntry'); diff --git a/src/node_modules/react-dom/server.js b/src/node_modules/react-dom/server.js index fa6285bfefc..f19a4b36a91 100644 --- a/src/node_modules/react-dom/server.js +++ b/src/node_modules/react-dom/server.js @@ -6,9 +6,4 @@ 'use strict'; -var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); -var useFiber = ReactDOMFeatureFlags.useFiber; - -module.exports = useFiber - ? require('ReactDOMServerNodeEntry') - : require('ReactDOMServerStackEntry'); +module.exports = require('ReactDOMServerNodeEntry'); diff --git a/src/renderers/__tests__/ReactComponent-test.js b/src/renderers/__tests__/ReactComponent-test.js index a5f5ce287f6..b1007b688c1 100644 --- a/src/renderers/__tests__/ReactComponent-test.js +++ b/src/renderers/__tests__/ReactComponent-test.js @@ -16,8 +16,6 @@ var ReactDOM; var ReactDOMServer; var ReactTestUtils; -var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); - describe('ReactComponent', () => { function normalizeCodeLocInfo(str) { return str && str.replace(/\(at .+?:\d+\)/g, '(at **)'); @@ -299,21 +297,11 @@ describe('ReactComponent', () => { 'outer componentDidMount', 'start update', // Previous (equivalent) refs get cleared - ...(ReactDOMFeatureFlags.useFiber - ? [ - // Fiber renders first, resets refs later - 'inner 1 render', - 'inner 2 render', - 'ref 1 got null', - 'ref 2 got null', - ] - : [ - // Stack resets refs before rendering - 'ref 1 got null', - 'inner 1 render', - 'ref 2 got null', - 'inner 2 render', - ]), + // Fiber renders first, resets refs later + 'inner 1 render', + 'inner 2 render', + 'ref 1 got null', + 'ref 2 got null', 'inner 1 componentDidUpdate', 'ref 1 got instance 1', 'inner 2 componentDidUpdate', @@ -408,8 +396,7 @@ describe('ReactComponent', () => { 'Objects are not valid as a React child (found: object with keys ' + '{x, y, z}). If you meant to render a collection of children, use ' + 'an array instead.' + - // Fiber gives a slightly better stack with the nearest host components - (ReactDOMFeatureFlags.useFiber ? '\n in div (at **)' : ''), + '\n in div (at **)', ); }); @@ -436,8 +423,7 @@ describe('ReactComponent', () => { 'Objects are not valid as a React child (found: object with keys ' + '{a, b, c}). If you meant to render a collection of children, use ' + 'an array instead.\n' + - // Fiber gives a slightly better stack with the nearest host components - (ReactDOMFeatureFlags.useFiber ? ' in div (at **)\n' : '') + + ' in div (at **)\n' + ' in Foo (at **)', ); }); @@ -460,8 +446,7 @@ describe('ReactComponent', () => { 'Objects are not valid as a React child (found: object with keys ' + '{x, y, z}). If you meant to render a collection of children, use ' + 'an array instead.' + - // Fiber gives a slightly better stack with the nearest host components - (ReactDOMFeatureFlags.useFiber ? '\n in div (at **)' : ''), + '\n in div (at **)', ); }); @@ -488,83 +473,80 @@ describe('ReactComponent', () => { 'Objects are not valid as a React child (found: object with keys ' + '{a, b, c}). If you meant to render a collection of children, use ' + 'an array instead.\n' + - // Fiber gives a slightly better stack with the nearest host components - (ReactDOMFeatureFlags.useFiber ? ' in div (at **)\n' : '') + + ' in div (at **)\n' + ' in Foo (at **)', ); }); - if (ReactDOMFeatureFlags.useFiber) { - describe('with new features', () => { - beforeEach(() => { - require('ReactFeatureFlags').disableNewFiberFeatures = false; - }); + describe('with new features', () => { + beforeEach(() => { + require('ReactFeatureFlags').disableNewFiberFeatures = false; + }); - it('warns on function as a return value from a function', () => { - function Foo() { - return Foo; - } - spyOn(console, 'error'); - var container = document.createElement('div'); - ReactDOM.render(, container); - expectDev(console.error.calls.count()).toBe(1); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: Functions are not valid as a React child. This may happen if ' + - 'you return a Component instead of from render. ' + - 'Or maybe you meant to call this function rather than return it.\n' + - ' in Foo (at **)', - ); - }); + it('warns on function as a return value from a function', () => { + function Foo() { + return Foo; + } + spyOn(console, 'error'); + var container = document.createElement('div'); + ReactDOM.render(, container); + expectDev(console.error.calls.count()).toBe(1); + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: Functions are not valid as a React child. This may happen if ' + + 'you return a Component instead of from render. ' + + 'Or maybe you meant to call this function rather than return it.\n' + + ' in Foo (at **)', + ); + }); - it('warns on function as a return value from a class', () => { - class Foo extends React.Component { - render() { - return Foo; - } + it('warns on function as a return value from a class', () => { + class Foo extends React.Component { + render() { + return Foo; } - spyOn(console, 'error'); - var container = document.createElement('div'); - ReactDOM.render(, container); - expectDev(console.error.calls.count()).toBe(1); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: Functions are not valid as a React child. This may happen if ' + - 'you return a Component instead of from render. ' + - 'Or maybe you meant to call this function rather than return it.\n' + - ' in Foo (at **)', - ); - }); + } + spyOn(console, 'error'); + var container = document.createElement('div'); + ReactDOM.render(, container); + expectDev(console.error.calls.count()).toBe(1); + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: Functions are not valid as a React child. This may happen if ' + + 'you return a Component instead of from render. ' + + 'Or maybe you meant to call this function rather than return it.\n' + + ' in Foo (at **)', + ); + }); - it('warns on function as a child to host component', () => { - function Foo() { - return
{Foo}
; - } - spyOn(console, 'error'); - var container = document.createElement('div'); - ReactDOM.render(, container); - expectDev(console.error.calls.count()).toBe(1); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( - 'Warning: Functions are not valid as a React child. This may happen if ' + - 'you return a Component instead of from render. ' + - 'Or maybe you meant to call this function rather than return it.\n' + - ' in span (at **)\n' + - ' in div (at **)\n' + - ' in Foo (at **)', - ); - }); + it('warns on function as a child to host component', () => { + function Foo() { + return
{Foo}
; + } + spyOn(console, 'error'); + var container = document.createElement('div'); + ReactDOM.render(, container); + expectDev(console.error.calls.count()).toBe(1); + expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(0)[0])).toBe( + 'Warning: Functions are not valid as a React child. This may happen if ' + + 'you return a Component instead of from render. ' + + 'Or maybe you meant to call this function rather than return it.\n' + + ' in span (at **)\n' + + ' in div (at **)\n' + + ' in Foo (at **)', + ); + }); - it('does not warn for function-as-a-child that gets resolved', () => { - function Bar(props) { - return props.children(); - } - function Foo() { - return {() => 'Hello'}; - } - spyOn(console, 'error'); - var container = document.createElement('div'); - ReactDOM.render(, container); - expect(container.innerHTML).toBe('Hello'); - expectDev(console.error.calls.count()).toBe(0); - }); + it('does not warn for function-as-a-child that gets resolved', () => { + function Bar(props) { + return props.children(); + } + function Foo() { + return {() => 'Hello'}; + } + spyOn(console, 'error'); + var container = document.createElement('div'); + ReactDOM.render(, container); + expect(container.innerHTML).toBe('Hello'); + expectDev(console.error.calls.count()).toBe(0); }); - } + }); }); diff --git a/src/renderers/__tests__/ReactComponentTreeHook-test.js b/src/renderers/__tests__/ReactComponentTreeHook-test.js deleted file mode 100644 index c7ca3644ecd..00000000000 --- a/src/renderers/__tests__/ReactComponentTreeHook-test.js +++ /dev/null @@ -1,2217 +0,0 @@ -/** - * Copyright 2016-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @emails react-core - */ - -'use strict'; - -var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); -var describeStack = ReactDOMFeatureFlags.useFiber ? describe.skip : describe; - -describe('ReactComponentTreeHook', () => { - var React; - var ReactDOM; - var ReactDOMServer; - var ReactInstanceMap; - var ReactComponentTreeHook; - var ReactDebugCurrentFiber; - var ReactComponentTreeTestUtils; - - beforeEach(() => { - jest.resetModules(); - - React = require('react'); - ReactDOM = require('react-dom'); - ReactDOMServer = require('react-dom/server'); - ReactInstanceMap = require('ReactInstanceMap'); - ReactDebugCurrentFiber = require('ReactDebugCurrentFiber'); - ReactComponentTreeHook = require('ReactComponentTreeHook'); - ReactComponentTreeTestUtils = require('ReactComponentTreeTestUtils'); - }); - - // This is the only part used both by Stack and Fiber. - describe('stack addenda', () => { - it('gets created', () => { - function getAddendum(element) { - var addendum = ReactDOMFeatureFlags.useFiber - ? ReactDebugCurrentFiber.getCurrentFiberStackAddendum() || '' - : ReactComponentTreeHook.getCurrentStackAddendum(); - return addendum.replace(/\(at .+?:\d+\)/g, '(at **)'); - } - - function Anon() { - return null; - } - Object.defineProperty(Anon, 'name', { - value: null, - }); - // function Orange() { - // return null; - // } - - expectDev(getAddendum()).toBe(''); - // expectDev(getAddendum(
)).toBe('\n in div (at **)'); - // expectDev(getAddendum()).toBe('\n in Unknown (at **)'); - // expectDev(getAddendum()).toBe('\n in Orange (at **)'); - // expectDev(getAddendum(React.createElement(Orange))).toBe( - // '\n in Orange', - // ); - - var renders = 0; - //var rOwnedByQ; - - function Q() { - return /*rOwnedByQ =*/ React.createElement(R); - } - function R() { - return
; - } - class S extends React.Component { - componentDidMount() { - // Check that the parent path is still fetched when only S itself is on - // the stack. - this.forceUpdate(); - } - render() { - expectDev(getAddendum()).toBe( - '\n in S (at **)' + - '\n in div (at **)' + - '\n in R (created by Q)' + - '\n in Q (at **)', - ); - expectDev(getAddendum()).toBe( - // '\n in span (at **)' + - '\n in S (at **)' + - '\n in div (at **)' + - '\n in R (created by Q)' + - '\n in Q (at **)', - ); - expectDev(getAddendum(React.createElement('span'))).toBe( - // '\n in span (created by S)' + - '\n in S (at **)' + - '\n in div (at **)' + - '\n in R (created by Q)' + - '\n in Q (at **)', - ); - renders++; - return null; - } - } - ReactDOM.render(, document.createElement('div')); - expectDev(renders).toBe(2); - - // Make sure owner is fetched for the top element too. - // expectDev(getAddendum(rOwnedByQ)).toBe('\n in R (created by Q)'); - }); - - // These are features and regression tests that only affect - // the Stack implementation of the stack addendum. - if (!ReactDOMFeatureFlags.useFiber) { - it('can be retrieved by ID', () => { - function getAddendum(id) { - var addendum = ReactComponentTreeHook.getStackAddendumByID(id); - return addendum.replace(/\(at .+?:\d+\)/g, '(at **)'); - } - - class Q extends React.Component { - render() { - return null; - } - } - - var q = ReactDOM.render(, document.createElement('div')); - expectDev(getAddendum(ReactInstanceMap.get(q)._debugID)).toBe( - '\n in Q (at **)', - ); - - spyOn(console, 'error'); - getAddendum(-17); - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toBe( - 'Warning: ReactComponentTreeHook: Missing React element for ' + - 'debugID -17 when building stack', - ); - }); - - it('is created during mounting', () => { - // https://github.com/facebook/react/issues/7187 - var el = document.createElement('div'); - var portalEl = document.createElement('div'); - class Foo extends React.Component { - componentWillMount() { - ReactDOM.render(
, portalEl); - } - render() { - return
; - } - } - ReactDOM.render(, el); - }); - - it('is created when calling renderToString during render', () => { - // https://github.com/facebook/react/issues/7190 - var el = document.createElement('div'); - class Foo extends React.Component { - render() { - return ( -
-
- {ReactDOMServer.renderToString(
)} -
-
- ); - } - } - ReactDOM.render(, el); - }); - } - }); - - // The rest of this file is not relevant for Fiber. - // TODO: remove tests below when we delete Stack. - - function assertTreeMatches(pairs) { - if (!Array.isArray(pairs[0])) { - pairs = [pairs]; - } - - var node = document.createElement('div'); - var currentElement; - var rootInstance; - - class Wrapper extends React.Component { - render() { - rootInstance = ReactInstanceMap.get(this); - return currentElement; - } - } - - function expectWrapperTreeToEqual(expectedTree, andStayMounted) { - ReactComponentTreeTestUtils.expectTree(rootInstance._debugID, { - displayName: 'Wrapper', - children: expectedTree ? [expectedTree] : [], - }); - var rootDisplayNames = ReactComponentTreeTestUtils.getRootDisplayNames(); - var registeredDisplayNames = ReactComponentTreeTestUtils.getRegisteredDisplayNames(); - if (!expectedTree) { - expectDev(rootDisplayNames).toEqual([]); - expectDev(registeredDisplayNames).toEqual([]); - } else if (andStayMounted) { - expectDev(rootDisplayNames).toContain('Wrapper'); - expectDev(registeredDisplayNames).toContain('Wrapper'); - } - } - - // Mount once, render updates, then unmount. - // Ensure the tree is correct on every step. - pairs.forEach(([element, expectedTree]) => { - currentElement = element; - - // Mount a new tree or update the existing tree. - ReactDOM.render(, node); - expectWrapperTreeToEqual(expectedTree, true); - - // Purging should have no effect - // on the tree we expect to see. - ReactComponentTreeHook.purgeUnmountedComponents(); - expectWrapperTreeToEqual(expectedTree, true); - }); - - // Unmounting the root node should purge - // the whole subtree automatically. - ReactDOM.unmountComponentAtNode(node); - expectWrapperTreeToEqual(null); - } - - describeStack('mount', () => { - it('uses displayName or Unknown for classic components', () => { - class Foo extends React.Component { - render() { - return null; - } - } - - Foo.displayName = 'Bar'; - - class Baz extends React.Component { - render() { - return null; - } - } - - class Qux extends React.Component { - render() { - return null; - } - } - - delete Qux.displayName; - - var element =
; - var tree = { - displayName: 'div', - children: [ - { - displayName: 'Bar', - children: [], - }, - { - displayName: 'Baz', - children: [], - }, - { - displayName: 'Unknown', - children: [], - }, - ], - }; - assertTreeMatches([element, tree]); - }); - - it('uses displayName, name, or ReactComponent for modern components', () => { - class Foo extends React.Component { - render() { - return null; - } - } - Foo.displayName = 'Bar'; - class Baz extends React.Component { - render() { - return null; - } - } - class Qux extends React.Component { - render() { - return null; - } - } - delete Qux.name; - - var element =
; - var tree = { - displayName: 'div', - element, - children: [ - { - displayName: 'Bar', - children: [], - }, - { - displayName: 'Baz', - children: [], - }, - { - // Note: Ideally fallback name should be consistent (e.g. "Unknown") - displayName: 'ReactComponent', - children: [], - }, - ], - }; - assertTreeMatches([element, tree]); - }); - - it('uses displayName, name, or Object for factory components', () => { - function Foo() { - return { - render() { - return null; - }, - }; - } - Foo.displayName = 'Bar'; - function Baz() { - return { - render() { - return null; - }, - }; - } - function Qux() { - return { - render() { - return null; - }, - }; - } - delete Qux.name; - - var element =
; - var tree = { - displayName: 'div', - element, - children: [ - { - displayName: 'Bar', - children: [], - }, - { - displayName: 'Baz', - children: [], - }, - { - displayName: 'Unknown', - children: [], - }, - ], - }; - assertTreeMatches([element, tree]); - }); - - it('uses displayName, name, or StatelessComponent for functional components', () => { - function Foo() { - return null; - } - Foo.displayName = 'Bar'; - function Baz() { - return null; - } - function Qux() { - return null; - } - delete Qux.name; - - var element =
; - var tree = { - displayName: 'div', - element, - children: [ - { - displayName: 'Bar', - children: [], - }, - { - displayName: 'Baz', - children: [], - }, - { - displayName: 'Unknown', - children: [], - }, - ], - }; - assertTreeMatches([element, tree]); - }); - - it('reports a host tree correctly', () => { - var element = ( -
-

- - Hi! - - Wow. -

-
-
- ); - var tree = { - displayName: 'div', - children: [ - { - displayName: 'p', - children: [ - { - displayName: 'span', - children: [ - { - displayName: '#text', - text: 'Hi!', - }, - ], - }, - { - displayName: '#text', - text: 'Wow.', - }, - ], - }, - { - displayName: 'hr', - element:
, - children: [], - }, - ], - }; - assertTreeMatches([element, tree]); - }); - - it('reports a simple tree with composites correctly', () => { - class Foo extends React.Component { - render() { - return
; - } - } - - var element = ; - var tree = { - displayName: 'Foo', - element, - children: [ - { - displayName: 'div', - element:
, - children: [], - }, - ], - }; - assertTreeMatches([element, tree]); - }); - - it('reports a tree with composites correctly', () => { - class Qux extends React.Component { - render() { - return null; - } - } - - function Foo() { - return { - render() { - return ; - }, - }; - } - function Bar({children}) { - return

{children}

; - } - class Baz extends React.Component { - render() { - return ( -
- - - Hi, - Mom - - Click me. -
- ); - } - } - - var element = ; - var tree = { - displayName: 'Baz', - element, - children: [ - { - displayName: 'div', - children: [ - { - displayName: 'Foo', - element: , - children: [ - { - displayName: 'Qux', - element: , - children: [], - }, - ], - }, - { - displayName: 'Bar', - children: [ - { - displayName: 'h1', - children: [ - { - displayName: 'span', - children: [ - { - displayName: '#text', - element: 'Hi,', - text: 'Hi,', - }, - ], - }, - { - displayName: '#text', - text: 'Mom', - element: 'Mom', - }, - ], - }, - ], - }, - { - displayName: 'a', - children: [ - { - displayName: '#text', - text: 'Click me.', - element: 'Click me.', - }, - ], - }, - ], - }, - ], - }; - assertTreeMatches([element, tree]); - }); - - it('ignores null children', () => { - class Foo extends React.Component { - render() { - return null; - } - } - var element = ; - var tree = { - displayName: 'Foo', - children: [], - }; - assertTreeMatches([element, tree]); - }); - - it('ignores false children', () => { - class Foo extends React.Component { - render() { - return false; - } - } - var element = ; - var tree = { - displayName: 'Foo', - children: [], - }; - assertTreeMatches([element, tree]); - }); - - it('reports text nodes as children', () => { - var element =
{'1'}{2}
; - var tree = { - displayName: 'div', - element, - children: [ - { - displayName: '#text', - text: '1', - }, - { - displayName: '#text', - text: '2', - }, - ], - }; - assertTreeMatches([element, tree]); - }); - - it('reports a single text node as a child', () => { - var element =
{'1'}
; - var tree = { - displayName: 'div', - element, - children: [ - { - displayName: '#text', - text: '1', - }, - ], - }; - assertTreeMatches([element, tree]); - }); - - it('reports a single number node as a child', () => { - var element =
{42}
; - var tree = { - displayName: 'div', - element, - children: [ - { - displayName: '#text', - text: '42', - }, - ], - }; - assertTreeMatches([element, tree]); - }); - - it('reports a zero as a child', () => { - var element =
{0}
; - var tree = { - displayName: 'div', - element, - children: [ - { - displayName: '#text', - text: '0', - }, - ], - }; - assertTreeMatches([element, tree]); - }); - - it('skips empty nodes for multiple children', () => { - function Foo() { - return
; - } - var element = ( -
- {'hi'} - {false} - {42} - {null} - -
- ); - var tree = { - displayName: 'div', - element, - children: [ - { - displayName: '#text', - text: 'hi', - element: 'hi', - }, - { - displayName: '#text', - text: '42', - element: 42, - }, - { - displayName: 'Foo', - element: , - children: [ - { - displayName: 'div', - element:
, - children: [], - }, - ], - }, - ], - }; - assertTreeMatches([element, tree]); - }); - - it('reports html content as no children', () => { - var element =
; - var tree = { - displayName: 'div', - children: [], - }; - assertTreeMatches([element, tree]); - }); - }); - - describeStack('update', () => { - describe('host component', () => { - it('updates text of a single text child', () => { - var elementBefore =
Hi.
; - var treeBefore = { - displayName: 'div', - children: [ - { - displayName: '#text', - text: 'Hi.', - }, - ], - }; - - var elementAfter =
Bye.
; - var treeAfter = { - displayName: 'div', - children: [ - { - displayName: '#text', - text: 'Bye.', - }, - ], - }; - - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - - it('updates from no children to a single text child', () => { - var elementBefore =
; - var treeBefore = { - displayName: 'div', - children: [], - }; - - var elementAfter =
Hi.
; - var treeAfter = { - displayName: 'div', - children: [ - { - displayName: '#text', - text: 'Hi.', - }, - ], - }; - - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - - it('updates from a single text child to no children', () => { - var elementBefore =
Hi.
; - var treeBefore = { - displayName: 'div', - children: [ - { - displayName: '#text', - text: 'Hi.', - }, - ], - }; - - var elementAfter =
; - var treeAfter = { - displayName: 'div', - children: [], - }; - - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - - it('updates from html content to a single text child', () => { - var elementBefore =
; - var treeBefore = { - displayName: 'div', - children: [], - }; - - var elementAfter =
Hi.
; - var treeAfter = { - displayName: 'div', - children: [ - { - displayName: '#text', - text: 'Hi.', - }, - ], - }; - - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - - it('updates from a single text child to html content', () => { - var elementBefore =
Hi.
; - var treeBefore = { - displayName: 'div', - children: [ - { - displayName: '#text', - text: 'Hi.', - }, - ], - }; - - var elementAfter =
; - var treeAfter = { - displayName: 'div', - children: [], - }; - - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - - it('updates from no children to multiple text children', () => { - var elementBefore =
; - var treeBefore = { - displayName: 'div', - children: [], - }; - - var elementAfter =
{'Hi.'}{'Bye.'}
; - var treeAfter = { - displayName: 'div', - children: [ - { - displayName: '#text', - text: 'Hi.', - }, - { - displayName: '#text', - text: 'Bye.', - }, - ], - }; - - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - - it('updates from multiple text children to no children', () => { - var elementBefore =
{'Hi.'}{'Bye.'}
; - var treeBefore = { - displayName: 'div', - children: [ - { - displayName: '#text', - text: 'Hi.', - }, - { - displayName: '#text', - text: 'Bye.', - }, - ], - }; - - var elementAfter =
; - var treeAfter = { - displayName: 'div', - children: [], - }; - - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - - it('updates from html content to multiple text children', () => { - var elementBefore =
; - var treeBefore = { - displayName: 'div', - children: [], - }; - - var elementAfter =
{'Hi.'}{'Bye.'}
; - var treeAfter = { - displayName: 'div', - children: [ - { - displayName: '#text', - text: 'Hi.', - }, - { - displayName: '#text', - text: 'Bye.', - }, - ], - }; - - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - - it('updates from multiple text children to html content', () => { - var elementBefore =
{'Hi.'}{'Bye.'}
; - var treeBefore = { - displayName: 'div', - children: [ - { - displayName: '#text', - text: 'Hi.', - }, - { - displayName: '#text', - text: 'Bye.', - }, - ], - }; - - var elementAfter =
; - var treeAfter = { - displayName: 'div', - children: [], - }; - - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - - it('updates from html content to no children', () => { - var elementBefore =
; - var treeBefore = { - displayName: 'div', - children: [], - }; - - var elementAfter =
; - var treeAfter = { - displayName: 'div', - children: [], - }; - - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - - it('updates from no children to html content', () => { - var elementBefore =
; - var treeBefore = { - displayName: 'div', - children: [], - }; - - var elementAfter =
; - var treeAfter = { - displayName: 'div', - children: [], - }; - - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - - it('updates from one text child to multiple text children', () => { - var elementBefore =
Hi.
; - var treeBefore = { - displayName: 'div', - children: [ - { - displayName: '#text', - text: 'Hi.', - }, - ], - }; - - var elementAfter =
{'Hi.'}{'Bye.'}
; - var treeAfter = { - displayName: 'div', - children: [ - { - displayName: '#text', - text: 'Hi.', - }, - { - displayName: '#text', - text: 'Bye.', - }, - ], - }; - - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - - it('updates from multiple text children to one text child', () => { - var elementBefore =
{'Hi.'}{'Bye.'}
; - var treeBefore = { - displayName: 'div', - children: [ - { - displayName: '#text', - text: 'Hi.', - }, - { - displayName: '#text', - text: 'Bye.', - }, - ], - }; - - var elementAfter =
Hi.
; - var treeAfter = { - displayName: 'div', - children: [ - { - displayName: '#text', - text: 'Hi.', - }, - ], - }; - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - - it('updates text nodes when reordering', () => { - var elementBefore =
{'Hi.'}{'Bye.'}
; - var treeBefore = { - displayName: 'div', - children: [ - { - displayName: '#text', - text: 'Hi.', - }, - { - displayName: '#text', - text: 'Bye.', - }, - ], - }; - - var elementAfter =
{'Bye.'}{'Hi.'}
; - var treeAfter = { - displayName: 'div', - children: [ - { - displayName: '#text', - text: 'Bye.', - }, - { - displayName: '#text', - text: 'Hi.', - }, - ], - }; - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - - it('updates host nodes when reordering with keys', () => { - var elementBefore = ( -
-
Hi.
-
Bye.
-
- ); - var treeBefore = { - displayName: 'div', - children: [ - { - displayName: 'div', - children: [ - { - displayName: '#text', - text: 'Hi.', - }, - ], - }, - { - displayName: 'div', - children: [ - { - displayName: '#text', - text: 'Bye.', - }, - ], - }, - ], - }; - - var elementAfter = ( -
-
Bye.
-
Hi.
-
- ); - var treeAfter = { - displayName: 'div', - children: [ - { - displayName: 'div', - children: [ - { - displayName: '#text', - text: 'Bye.', - }, - ], - }, - { - displayName: 'div', - children: [ - { - displayName: '#text', - text: 'Hi.', - }, - ], - }, - ], - }; - - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - - it('updates host nodes when reordering without keys', () => { - var elementBefore = ( -
-
Hi.
-
Bye.
-
- ); - var treeBefore = { - displayName: 'div', - children: [ - { - displayName: 'div', - children: [ - { - displayName: '#text', - text: 'Hi.', - }, - ], - }, - { - displayName: 'div', - children: [ - { - displayName: '#text', - text: 'Bye.', - }, - ], - }, - ], - }; - - var elementAfter = ( -
-
Bye.
-
Hi.
-
- ); - var treeAfter = { - displayName: 'div', - children: [ - { - displayName: 'div', - children: [ - { - displayName: '#text', - text: 'Bye.', - }, - ], - }, - { - displayName: 'div', - children: [ - { - displayName: '#text', - text: 'Hi.', - }, - ], - }, - ], - }; - - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - - it('updates a single composite child of a different type', () => { - function Foo() { - return null; - } - - function Bar() { - return null; - } - - var elementBefore =
; - var treeBefore = { - displayName: 'div', - children: [ - { - displayName: 'Foo', - children: [], - }, - ], - }; - - var elementAfter =
; - var treeAfter = { - displayName: 'div', - children: [ - { - displayName: 'Bar', - children: [], - }, - ], - }; - - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - - it('updates a single composite child of the same type', () => { - function Foo({children}) { - return children; - } - - var elementBefore =
; - var treeBefore = { - displayName: 'div', - children: [ - { - displayName: 'Foo', - children: [ - { - displayName: 'div', - children: [], - }, - ], - }, - ], - }; - - var elementAfter =
; - var treeAfter = { - displayName: 'div', - children: [ - { - displayName: 'Foo', - children: [ - { - displayName: 'span', - children: [], - }, - ], - }, - ], - }; - - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - - it('updates from no children to a single composite child', () => { - function Foo() { - return null; - } - - var elementBefore =
; - var treeBefore = { - displayName: 'div', - children: [], - }; - - var elementAfter =
; - var treeAfter = { - displayName: 'div', - children: [ - { - displayName: 'Foo', - children: [], - }, - ], - }; - - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - - it('updates from a single composite child to no children', () => { - function Foo() { - return null; - } - - var elementBefore =
; - var treeBefore = { - displayName: 'div', - children: [ - { - displayName: 'Foo', - children: [], - }, - ], - }; - - var elementAfter =
; - var treeAfter = { - displayName: 'div', - children: [], - }; - - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - - it('updates mixed children', () => { - function Foo() { - return
; - } - var element1 = ( -
- {'hi'} - {false} - {42} - {null} - -
- ); - var tree1 = { - displayName: 'div', - children: [ - { - displayName: '#text', - text: 'hi', - }, - { - displayName: '#text', - text: '42', - }, - { - displayName: 'Foo', - children: [ - { - displayName: 'div', - children: [], - }, - ], - }, - ], - }; - - var element2 = ( -
- - {false} - {'hi'} - {null} -
- ); - var tree2 = { - displayName: 'div', - children: [ - { - displayName: 'Foo', - children: [ - { - displayName: 'div', - children: [], - }, - ], - }, - { - displayName: '#text', - text: 'hi', - }, - ], - }; - - var element3 = ( -
- -
- ); - var tree3 = { - displayName: 'div', - children: [ - { - displayName: 'Foo', - children: [ - { - displayName: 'div', - children: [], - }, - ], - }, - ], - }; - - assertTreeMatches([ - [element1, tree1], - [element2, tree2], - [element3, tree3], - ]); - }); - }); - - describe('functional component', () => { - it('updates with a host child', () => { - function Foo({children}) { - return children; - } - - var elementBefore =
; - var treeBefore = { - displayName: 'Foo', - children: [ - { - displayName: 'div', - children: [], - }, - ], - }; - - var elementAfter = ; - var treeAfter = { - displayName: 'Foo', - children: [ - { - displayName: 'span', - children: [], - }, - ], - }; - - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - - it('updates from null to a host child', () => { - function Foo({children}) { - return children; - } - - var elementBefore = {null}; - var treeBefore = { - displayName: 'Foo', - children: [], - }; - - var elementAfter =
; - var treeAfter = { - displayName: 'Foo', - children: [ - { - displayName: 'div', - children: [], - }, - ], - }; - - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - - it('updates from a host child to null', () => { - function Foo({children}) { - return children; - } - - var elementBefore =
; - var treeBefore = { - displayName: 'Foo', - children: [ - { - displayName: 'div', - children: [], - }, - ], - }; - - var elementAfter = {null}; - var treeAfter = { - displayName: 'Foo', - children: [], - }; - - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - - it('updates from a host child to a composite child', () => { - function Bar() { - return null; - } - - function Foo({children}) { - return children; - } - - var elementBefore =
; - var treeBefore = { - displayName: 'Foo', - children: [ - { - displayName: 'div', - children: [], - }, - ], - }; - - var elementAfter = ; - var treeAfter = { - displayName: 'Foo', - children: [ - { - displayName: 'Bar', - children: [], - }, - ], - }; - - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - - it('updates from a composite child to a host child', () => { - function Bar() { - return null; - } - - function Foo({children}) { - return children; - } - - var elementBefore = ; - var treeBefore = { - displayName: 'Foo', - children: [ - { - displayName: 'Bar', - children: [], - }, - ], - }; - - var elementAfter =
; - var treeAfter = { - displayName: 'Foo', - children: [ - { - displayName: 'div', - children: [], - }, - ], - }; - - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - - it('updates from null to a composite child', () => { - function Bar() { - return null; - } - - function Foo({children}) { - return children; - } - - var elementBefore = {null}; - var treeBefore = { - displayName: 'Foo', - children: [], - }; - - var elementAfter = ; - var treeAfter = { - displayName: 'Foo', - children: [ - { - displayName: 'Bar', - children: [], - }, - ], - }; - - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - - it('updates from a composite child to null', () => { - function Bar() { - return null; - } - - function Foo({children}) { - return children; - } - - var elementBefore = ; - var treeBefore = { - displayName: 'Foo', - children: [ - { - displayName: 'Bar', - children: [], - }, - ], - }; - - var elementAfter = {null}; - var treeAfter = { - displayName: 'Foo', - children: [], - }; - - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - }); - - describe('class component', () => { - it('updates with a host child', () => { - class Foo extends React.Component { - render() { - return this.props.children; - } - } - - var elementBefore =
; - var treeBefore = { - displayName: 'Foo', - children: [ - { - displayName: 'div', - children: [], - }, - ], - }; - - var elementAfter = ; - var treeAfter = { - displayName: 'Foo', - children: [ - { - displayName: 'span', - children: [], - }, - ], - }; - - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - - it('updates from null to a host child', () => { - class Foo extends React.Component { - render() { - return this.props.children; - } - } - - var elementBefore = {null}; - var treeBefore = { - displayName: 'Foo', - children: [], - }; - - var elementAfter =
; - var treeAfter = { - displayName: 'Foo', - children: [ - { - displayName: 'div', - children: [], - }, - ], - }; - - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - - it('updates from a host child to null', () => { - class Foo extends React.Component { - render() { - return this.props.children; - } - } - - var elementBefore =
; - var treeBefore = { - displayName: 'Foo', - children: [ - { - displayName: 'div', - children: [], - }, - ], - }; - - var elementAfter = {null}; - var treeAfter = { - displayName: 'Foo', - children: [], - }; - - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - - it('updates from a host child to a composite child', () => { - class Bar extends React.Component { - render() { - return null; - } - } - - class Foo extends React.Component { - render() { - return this.props.children; - } - } - - var elementBefore =
; - var treeBefore = { - displayName: 'Foo', - children: [ - { - displayName: 'div', - children: [], - }, - ], - }; - - var elementAfter = ; - var treeAfter = { - displayName: 'Foo', - children: [ - { - displayName: 'Bar', - children: [], - }, - ], - }; - - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - - it('updates from a composite child to a host child', () => { - class Bar extends React.Component { - render() { - return null; - } - } - - class Foo extends React.Component { - render() { - return this.props.children; - } - } - - var elementBefore = ; - var treeBefore = { - displayName: 'Foo', - children: [ - { - displayName: 'Bar', - children: [], - }, - ], - }; - - var elementAfter =
; - var treeAfter = { - displayName: 'Foo', - children: [ - { - displayName: 'div', - children: [], - }, - ], - }; - - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - - it('updates from null to a composite child', () => { - class Bar extends React.Component { - render() { - return null; - } - } - - class Foo extends React.Component { - render() { - return this.props.children; - } - } - - var elementBefore = {null}; - var treeBefore = { - displayName: 'Foo', - children: [], - }; - - var elementAfter = ; - var treeAfter = { - displayName: 'Foo', - children: [ - { - displayName: 'Bar', - children: [], - }, - ], - }; - - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - - it('updates from a composite child to null', () => { - class Bar extends React.Component { - render() { - return null; - } - } - - class Foo extends React.Component { - render() { - return this.props.children; - } - } - - var elementBefore = ; - var treeBefore = { - displayName: 'Foo', - children: [ - { - displayName: 'Bar', - children: [], - }, - ], - }; - - var elementAfter = {null}; - var treeAfter = { - displayName: 'Foo', - children: [], - }; - - assertTreeMatches([ - [elementBefore, treeBefore], - [elementAfter, treeAfter], - ]); - }); - }); - }); - - describeStack('misc', () => { - it('tracks owner correctly', () => { - class Foo extends React.Component { - render() { - return

Hi.

; - } - } - function Bar({children}) { - return
{children} Mom
; - } - - // Note that owner is not calculated for text nodes - // because they are not created from real elements. - var element =
; - var tree = { - displayName: 'article', - children: [ - { - displayName: 'Foo', - children: [ - { - displayName: 'Bar', - ownerDisplayName: 'Foo', - children: [ - { - displayName: 'div', - ownerDisplayName: 'Bar', - children: [ - { - displayName: 'h1', - ownerDisplayName: 'Foo', - children: [ - { - displayName: '#text', - text: 'Hi.', - }, - ], - }, - { - displayName: '#text', - text: ' Mom', - }, - ], - }, - ], - }, - ], - }, - ], - }; - assertTreeMatches([element, tree]); - }); - - it('purges unmounted components automatically', () => { - var node = document.createElement('div'); - var renderBar = true; - var fooInstance; - var barInstance; - - class Foo extends React.Component { - render() { - fooInstance = ReactInstanceMap.get(this); - return renderBar ? : null; - } - } - - class Bar extends React.Component { - render() { - barInstance = ReactInstanceMap.get(this); - return null; - } - } - - ReactDOM.render(, node); - ReactComponentTreeTestUtils.expectTree( - barInstance._debugID, - { - displayName: 'Bar', - parentDisplayName: 'Foo', - parentID: fooInstance._debugID, - children: [], - }, - 'Foo', - ); - - renderBar = false; - ReactDOM.render(, node); - ReactDOM.render(, node); - ReactComponentTreeTestUtils.expectTree( - barInstance._debugID, - { - displayName: 'Unknown', - children: [], - parentID: null, - }, - 'Foo', - ); - - ReactDOM.unmountComponentAtNode(node); - ReactComponentTreeTestUtils.expectTree( - barInstance._debugID, - { - displayName: 'Unknown', - children: [], - parentID: null, - }, - 'Foo', - ); - }); - - it('reports update counts', () => { - var node = document.createElement('div'); - - ReactDOM.render(
, node); - var divID = ReactComponentTreeHook.getRootIDs()[0]; - expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0); - - ReactDOM.render(, node); - var spanID = ReactComponentTreeHook.getRootIDs()[0]; - expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0); - expectDev(ReactComponentTreeHook.getUpdateCount(spanID)).toEqual(0); - - ReactDOM.render(, node); - expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0); - expectDev(ReactComponentTreeHook.getUpdateCount(spanID)).toEqual(1); - - ReactDOM.render(, node); - expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0); - expectDev(ReactComponentTreeHook.getUpdateCount(spanID)).toEqual(2); - - ReactDOM.unmountComponentAtNode(node); - expectDev(ReactComponentTreeHook.getUpdateCount(divID)).toEqual(0); - expectDev(ReactComponentTreeHook.getUpdateCount(spanID)).toEqual(0); - }); - - it('does not report top-level wrapper as a root', () => { - var node = document.createElement('div'); - - ReactDOM.render(
, node); - expectDev(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual([ - 'div', - ]); - - ReactDOM.render(
, node); - expectDev(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual([ - 'div', - ]); - - ReactDOM.unmountComponentAtNode(node); - expectDev(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual([]); - expectDev( - ReactComponentTreeTestUtils.getRegisteredDisplayNames(), - ).toEqual([]); - }); - - it('registers inlined text nodes', () => { - var node = document.createElement('div'); - - ReactDOM.render(
hi
, node); - expectDev( - ReactComponentTreeTestUtils.getRegisteredDisplayNames(), - ).toEqual(['div', '#text']); - - ReactDOM.unmountComponentAtNode(node); - expectDev( - ReactComponentTreeTestUtils.getRegisteredDisplayNames(), - ).toEqual([]); - }); - }); - - describeStack('in environment without Map, Set and Array.from', () => { - var realMap; - var realSet; - var realArrayFrom; - - beforeEach(() => { - realMap = global.Map; - realSet = global.Set; - realArrayFrom = Array.from; - - global.Map = undefined; - global.Set = undefined; - Array.from = undefined; - - jest.resetModules(); - - React = require('react'); - ReactDOM = require('react-dom'); - ReactDOMServer = require('react-dom/server'); - ReactInstanceMap = require('ReactInstanceMap'); - ReactComponentTreeHook = require('ReactComponentTreeHook'); - ReactComponentTreeTestUtils = require('ReactComponentTreeTestUtils'); - }); - - afterEach(() => { - global.Map = realMap; - global.Set = realSet; - Array.from = realArrayFrom; - }); - - it('works', () => { - class Qux extends React.Component { - render() { - return null; - } - } - - function Foo() { - return { - render() { - return ; - }, - }; - } - function Bar({children}) { - return

{children}

; - } - class Baz extends React.Component { - render() { - return ( -
- - - Hi, - Mom - - Click me. -
- ); - } - } - - var element = ; - var tree = { - displayName: 'Baz', - element, - children: [ - { - displayName: 'div', - children: [ - { - displayName: 'Foo', - element: , - children: [ - { - displayName: 'Qux', - element: , - children: [], - }, - ], - }, - { - displayName: 'Bar', - children: [ - { - displayName: 'h1', - children: [ - { - displayName: 'span', - children: [ - { - displayName: '#text', - element: 'Hi,', - text: 'Hi,', - }, - ], - }, - { - displayName: '#text', - text: 'Mom', - element: 'Mom', - }, - ], - }, - ], - }, - { - displayName: 'a', - children: [ - { - displayName: '#text', - text: 'Click me.', - element: 'Click me.', - }, - ], - }, - ], - }, - ], - }; - assertTreeMatches([element, tree]); - }); - }); -}); diff --git a/src/renderers/__tests__/ReactCompositeComponent-test.js b/src/renderers/__tests__/ReactCompositeComponent-test.js index 650157f967f..89b44563ee2 100644 --- a/src/renderers/__tests__/ReactCompositeComponent-test.js +++ b/src/renderers/__tests__/ReactCompositeComponent-test.js @@ -15,7 +15,6 @@ var ChildUpdates; var MorphingComponent; var React; var ReactDOM; -var ReactDOMFeatureFlags; var ReactDOMServer; var ReactCurrentOwner; var ReactTestUtils; @@ -28,7 +27,6 @@ describe('ReactCompositeComponent', () => { jest.resetModules(); React = require('react'); ReactDOM = require('react-dom'); - ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); ReactDOMServer = require('react-dom/server'); ReactCurrentOwner = require('react') .__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner; @@ -125,25 +123,19 @@ describe('ReactCompositeComponent', () => { var container = document.createElement('div'); container.innerHTML = markup; ReactDOM.render(, container); - if (ReactDOMFeatureFlags.useFiber) { - expectDev(console.warn.calls.count()).toBe(1); - expectDev(console.warn.calls.argsFor(0)[0]).toContain( - 'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' + - 'will stop working in React v17. Replace the ReactDOM.render() call ' + - 'with ReactDOM.hydrate() if you want React to attach to the server HTML.', - ); - } else { - expectDev(console.warn.calls.count()).toBe(0); - } + expectDev(console.warn.calls.count()).toBe(1); + expectDev(console.warn.calls.argsFor(0)[0]).toContain( + 'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' + + 'will stop working in React v17. Replace the ReactDOM.render() call ' + + 'with ReactDOM.hydrate() if you want React to attach to the server HTML.', + ); // New explicit API console.warn.calls.reset(); - if (ReactDOMFeatureFlags.useFiber) { - container = document.createElement('div'); - container.innerHTML = markup; - ReactDOM.hydrate(, container); - expectDev(console.warn.calls.count()).toBe(0); - } + container = document.createElement('div'); + container.innerHTML = markup; + ReactDOM.hydrate(, container); + expectDev(console.warn.calls.count()).toBe(0); }); it('should react to state changes from callbacks', () => { diff --git a/src/renderers/__tests__/ReactCompositeComponentState-test.js b/src/renderers/__tests__/ReactCompositeComponentState-test.js index ddfdabe106b..12d86b35fef 100644 --- a/src/renderers/__tests__/ReactCompositeComponentState-test.js +++ b/src/renderers/__tests__/ReactCompositeComponentState-test.js @@ -13,7 +13,6 @@ var React; var ReactDOM; -var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); var TestComponent; @@ -181,17 +180,9 @@ describe('ReactCompositeComponent-state', () => { // setState({color:'green'}) only enqueues a pending state. ['componentWillReceiveProps-end', 'yellow'], // pending state queue is processed - ]; - - if (ReactDOMFeatureFlags.useFiber) { - // In Stack, this is never called because replaceState drops all updates - // from the queue. In Fiber, we keep updates in the queue to support + // We keep updates in the queue to support // replaceState(prevState => newState). - // TODO: Fix Stack to match Fiber. - expected.push(['before-setState-receiveProps', 'yellow']); - } - - expected.push( + ['before-setState-receiveProps', 'yellow'], ['before-setState-again-receiveProps', undefined], ['after-setState-receiveProps', 'green'], ['shouldComponentUpdate-currentState', 'yellow'], @@ -222,7 +213,7 @@ describe('ReactCompositeComponent-state', () => { // unmountComponent() // state is available within `componentWillUnmount()` ['componentWillUnmount', 'blue'], - ); + ]; expect(stateListener.mock.calls.join('\n')).toEqual(expected.join('\n')); }); @@ -291,20 +282,7 @@ describe('ReactCompositeComponent-state', () => { child.setState({bar: false}); }); // We expect the same thing to happen if we bail out in the middle. - expect(ops).toEqual( - ReactDOMFeatureFlags.useFiber - ? [ - // Fiber works as expected - 'child did update', - 'parent did update', - ] - : [ - // Stack treats these as two separate updates and therefore the order - // is inverse. - 'parent did update', - 'child did update', - ], - ); + expect(ops).toEqual(['child did update', 'parent did update']); }); it('should batch unmounts', () => { @@ -447,45 +425,43 @@ describe('ReactCompositeComponent-state', () => { ); }); - if (ReactDOMFeatureFlags.useFiber) { - it('should treat assigning to this.state inside cWM as a replaceState, with a warning', () => { - spyOn(console, 'error'); - - let ops = []; - class Test extends React.Component { - state = {step: 1, extra: true}; - componentWillMount() { - this.setState({step: 2}, () => { - // Tests that earlier setState callbacks are not dropped - ops.push( - `callback -- step: ${this.state.step}, extra: ${!!this.state.extra}`, - ); - }); - // Treat like replaceState - this.state = {step: 3}; - } - render() { + it('should treat assigning to this.state inside cWM as a replaceState, with a warning', () => { + spyOn(console, 'error'); + + let ops = []; + class Test extends React.Component { + state = {step: 1, extra: true}; + componentWillMount() { + this.setState({step: 2}, () => { + // Tests that earlier setState callbacks are not dropped ops.push( - `render -- step: ${this.state.step}, extra: ${!!this.state.extra}`, + `callback -- step: ${this.state.step}, extra: ${!!this.state.extra}`, ); - return null; - } + }); + // Treat like replaceState + this.state = {step: 3}; } + render() { + ops.push( + `render -- step: ${this.state.step}, extra: ${!!this.state.extra}`, + ); + return null; + } + } - // Mount - const container = document.createElement('div'); - ReactDOM.render(, container); - - expect(ops).toEqual([ - 'render -- step: 3, extra: false', - 'callback -- step: 3, extra: false', - ]); - expect(console.error.calls.count()).toEqual(1); - expect(console.error.calls.argsFor(0)[0]).toEqual( - 'Warning: Test.componentWillMount(): Assigning directly to ' + - "this.state is deprecated (except inside a component's constructor). " + - 'Use setState instead.', - ); - }); - } + // Mount + const container = document.createElement('div'); + ReactDOM.render(, container); + + expect(ops).toEqual([ + 'render -- step: 3, extra: false', + 'callback -- step: 3, extra: false', + ]); + expect(console.error.calls.count()).toEqual(1); + expect(console.error.calls.argsFor(0)[0]).toEqual( + 'Warning: Test.componentWillMount(): Assigning directly to ' + + "this.state is deprecated (except inside a component's constructor). " + + 'Use setState instead.', + ); + }); }); diff --git a/src/renderers/__tests__/ReactEmptyComponent-test.js b/src/renderers/__tests__/ReactEmptyComponent-test.js index 5368ae06a90..f3b08b22c07 100644 --- a/src/renderers/__tests__/ReactEmptyComponent-test.js +++ b/src/renderers/__tests__/ReactEmptyComponent-test.js @@ -13,7 +13,6 @@ var React; var ReactDOM; -var ReactDOMFeatureFlags; var ReactTestUtils; var TogglingComponent; @@ -25,7 +24,6 @@ describe('ReactEmptyComponent', () => { React = require('react'); ReactDOM = require('react-dom'); - ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); ReactTestUtils = require('react-dom/test-utils'); log = jasmine.createSpy(); @@ -237,15 +235,8 @@ describe('ReactEmptyComponent', () => { var div = document.createElement('div'); try { - if (ReactDOMFeatureFlags.useFiber) { - ReactDOM.render(null, div); - expect(div.innerHTML).toBe(''); - } else { - // Stack does not implement this. - expect(function() { - ReactDOM.render(null, div); - }).toThrowError('ReactDOM.render(): Invalid component element.'); - } + ReactDOM.render(null, div); + expect(div.innerHTML).toBe(''); } finally { ReactFeatureFlags.disableNewFiberFeatures = true; } @@ -300,21 +291,11 @@ describe('ReactEmptyComponent', () => { ReactDOM.render(, container); var noscript1 = container.firstChild; - if (ReactDOMFeatureFlags.useFiber) { - expect(noscript1).toBe(null); - } else { - expect(noscript1.nodeName).toBe('#comment'); - } + expect(noscript1).toBe(null); // This update shouldn't create a DOM node ReactDOM.render(, container); var noscript2 = container.firstChild; - if (ReactDOMFeatureFlags.useFiber) { - expect(noscript1).toBe(null); - } else { - expect(noscript2.nodeName).toBe('#comment'); - } - - expect(noscript1).toBe(noscript2); + expect(noscript2).toBe(null); }); }); diff --git a/src/renderers/__tests__/ReactErrorBoundaries-test.js b/src/renderers/__tests__/ReactErrorBoundaries-test.js index ea994b41922..7be72fb2818 100644 --- a/src/renderers/__tests__/ReactErrorBoundaries-test.js +++ b/src/renderers/__tests__/ReactErrorBoundaries-test.js @@ -11,8 +11,6 @@ 'use strict'; -var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); - var PropTypes; var React; var ReactDOM; @@ -401,12 +399,7 @@ describe('ReactErrorBoundaries', () => { log.push('NoopErrorBoundary componentWillUnmount'); } componentDidCatch() { - if (ReactDOMFeatureFlags.useFiber) { - log.push('NoopErrorBoundary componentDidCatch'); - } else { - // In Stack, not calling setState() is treated as a rethrow. - log.push('NoopErrorBoundary componentDidCatch [*]'); - } + log.push('NoopErrorBoundary componentDidCatch'); } }; @@ -509,14 +502,9 @@ describe('ReactErrorBoundaries', () => { log.push('RetryErrorBoundary componentWillUnmount'); } componentDidCatch(error) { - if (ReactDOMFeatureFlags.useFiber) { - log.push('RetryErrorBoundary componentDidCatch [!]'); - // In Fiber, calling setState() (and failing) is treated as a rethrow. - this.setState({}); - } else { - log.push('RetryErrorBoundary componentDidCatch [*]'); - // In Stack, not calling setState() is treated as a rethrow. - } + log.push('RetryErrorBoundary componentDidCatch [!]'); + // In Fiber, calling setState() (and failing) is treated as a rethrow. + this.setState({}); } }; @@ -641,22 +629,13 @@ describe('ReactErrorBoundaries', () => { 'BrokenRender constructor', 'BrokenRender componentWillMount', 'BrokenRender render [!]', - ...(ReactDOMFeatureFlags.useFiber - ? [ - // Fiber mounts with null children before capturing error - 'ErrorBoundary componentDidMount', - // Catch and render an error message - 'ErrorBoundary componentDidCatch', - 'ErrorBoundary componentWillUpdate', - 'ErrorBoundary render error', - 'ErrorBoundary componentDidUpdate', - ] - : [ - // Catch and render an error message - 'ErrorBoundary componentDidCatch', - 'ErrorBoundary render error', - 'ErrorBoundary componentDidMount', - ]), + // Fiber mounts with null children before capturing error + 'ErrorBoundary componentDidMount', + // Catch and render an error message + 'ErrorBoundary componentDidCatch', + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render error', + 'ErrorBoundary componentDidUpdate', ]); log.length = 0; @@ -678,22 +657,13 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary componentWillMount', 'ErrorBoundary render success', 'BrokenConstructor constructor [!]', - ...(ReactDOMFeatureFlags.useFiber - ? [ - // Fiber mounts with null children before capturing error - 'ErrorBoundary componentDidMount', - // Catch and render an error message - 'ErrorBoundary componentDidCatch', - 'ErrorBoundary componentWillUpdate', - 'ErrorBoundary render error', - 'ErrorBoundary componentDidUpdate', - ] - : [ - // Catch and render an error message - 'ErrorBoundary componentDidCatch', - 'ErrorBoundary render error', - 'ErrorBoundary componentDidMount', - ]), + // Fiber mounts with null children before capturing error + 'ErrorBoundary componentDidMount', + // Catch and render an error message + 'ErrorBoundary componentDidCatch', + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render error', + 'ErrorBoundary componentDidUpdate', ]); log.length = 0; @@ -716,20 +686,11 @@ describe('ReactErrorBoundaries', () => { 'ErrorBoundary render success', 'BrokenComponentWillMount constructor', 'BrokenComponentWillMount componentWillMount [!]', - ...(ReactDOMFeatureFlags.useFiber - ? [ - 'ErrorBoundary componentDidMount', - 'ErrorBoundary componentDidCatch', - 'ErrorBoundary componentWillUpdate', - 'ErrorBoundary render error', - 'ErrorBoundary componentDidUpdate', - ] - : [ - // Catch and render an error message - 'ErrorBoundary componentDidCatch', - 'ErrorBoundary render error', - 'ErrorBoundary componentDidMount', - ]), + 'ErrorBoundary componentDidMount', + 'ErrorBoundary componentDidCatch', + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render error', + 'ErrorBoundary componentDidUpdate', ]); log.length = 0; @@ -808,29 +769,15 @@ describe('ReactErrorBoundaries', () => { 'BrokenRender constructor', 'BrokenRender componentWillMount', 'BrokenRender render [!]', - ...(ReactDOMFeatureFlags.useFiber - ? [ - 'ErrorBoundary componentDidMount', - 'ErrorBoundary componentDidCatch', - 'ErrorBoundary componentWillUpdate', - 'ErrorBoundary render error', - 'ErrorMessage constructor', - 'ErrorMessage componentWillMount', - 'ErrorMessage render', - 'ErrorMessage componentDidMount', - 'ErrorBoundary componentDidUpdate', - ] - : [ - // Handle the error: - 'ErrorBoundary componentDidCatch', - 'ErrorBoundary render error', - // Mount the error message: - 'ErrorMessage constructor', - 'ErrorMessage componentWillMount', - 'ErrorMessage render', - 'ErrorMessage componentDidMount', - 'ErrorBoundary componentDidMount', - ]), + 'ErrorBoundary componentDidMount', + 'ErrorBoundary componentDidCatch', + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render error', + 'ErrorMessage constructor', + 'ErrorMessage componentWillMount', + 'ErrorMessage render', + 'ErrorMessage componentDidMount', + 'ErrorBoundary componentDidUpdate', ]); log.length = 0; @@ -862,39 +809,22 @@ describe('ReactErrorBoundaries', () => { 'BrokenRender constructor', 'BrokenRender componentWillMount', 'BrokenRender render [!]', - ...(ReactDOMFeatureFlags.useFiber - ? [ - // In Fiber, failed error boundaries render null before attempting to recover - 'RetryErrorBoundary componentDidMount', - 'RetryErrorBoundary componentDidCatch [!]', - 'ErrorBoundary componentDidMount', - // Retry - 'RetryErrorBoundary render', - 'BrokenRender constructor', - 'BrokenRender componentWillMount', - 'BrokenRender render [!]', - // This time, the error propagates to the higher boundary - 'RetryErrorBoundary componentWillUnmount', - 'ErrorBoundary componentDidCatch', - // Render the error - 'ErrorBoundary componentWillUpdate', - 'ErrorBoundary render error', - 'ErrorBoundary componentDidUpdate', - ] - : [ - // The first error boundary catches the error. - // However, it doesn't adjust its state so next render will also fail. - 'RetryErrorBoundary componentDidCatch [*]', - 'RetryErrorBoundary render', - 'BrokenRender constructor', - 'BrokenRender componentWillMount', - 'BrokenRender render [!]', - // This time, the error propagates to the higher boundary - 'ErrorBoundary componentDidCatch', - // Render the error - 'ErrorBoundary render error', - 'ErrorBoundary componentDidMount', - ]), + // In Fiber, failed error boundaries render null before attempting to recover + 'RetryErrorBoundary componentDidMount', + 'RetryErrorBoundary componentDidCatch [!]', + 'ErrorBoundary componentDidMount', + // Retry + 'RetryErrorBoundary render', + 'BrokenRender constructor', + 'BrokenRender componentWillMount', + 'BrokenRender render [!]', + // This time, the error propagates to the higher boundary + 'RetryErrorBoundary componentWillUnmount', + 'ErrorBoundary componentDidCatch', + // Render the error + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render error', + 'ErrorBoundary componentDidUpdate', ]); log.length = 0; @@ -918,20 +848,11 @@ describe('ReactErrorBoundaries', () => { 'BrokenComponentWillMountErrorBoundary constructor', 'BrokenComponentWillMountErrorBoundary componentWillMount [!]', // The error propagates to the higher boundary - ...(ReactDOMFeatureFlags.useFiber - ? [ - 'ErrorBoundary componentDidMount', - 'ErrorBoundary componentDidCatch', - 'ErrorBoundary componentWillUpdate', - 'ErrorBoundary render error', - 'ErrorBoundary componentDidUpdate', - ] - : [ - 'ErrorBoundary componentDidCatch', - // Render the error - 'ErrorBoundary render error', - 'ErrorBoundary componentDidMount', - ]), + 'ErrorBoundary componentDidMount', + 'ErrorBoundary componentDidCatch', + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render error', + 'ErrorBoundary componentDidUpdate', ]); log.length = 0; @@ -962,31 +883,19 @@ describe('ReactErrorBoundaries', () => { 'BrokenRender render [!]', // The first error boundary catches the error // It adjusts state but throws displaying the message - ...(ReactDOMFeatureFlags.useFiber - ? [ - // Finish mounting with null children - 'BrokenRenderErrorBoundary componentDidMount', - // Attempt to handle the error - 'BrokenRenderErrorBoundary componentDidCatch', - 'ErrorBoundary componentDidMount', - 'BrokenRenderErrorBoundary render error [!]', - // Boundary fails with new error, propagate to next boundary - 'BrokenRenderErrorBoundary componentWillUnmount', - // Attempt to handle the error again - 'ErrorBoundary componentDidCatch', - 'ErrorBoundary componentWillUpdate', - 'ErrorBoundary render error', - 'ErrorBoundary componentDidUpdate', - ] - : [ - 'BrokenRenderErrorBoundary componentDidCatch', - 'BrokenRenderErrorBoundary render error [!]', - // The error propagates to the higher boundary - 'ErrorBoundary componentDidCatch', - // Render the error - 'ErrorBoundary render error', - 'ErrorBoundary componentDidMount', - ]), + // Finish mounting with null children + 'BrokenRenderErrorBoundary componentDidMount', + // Attempt to handle the error + 'BrokenRenderErrorBoundary componentDidCatch', + 'ErrorBoundary componentDidMount', + 'BrokenRenderErrorBoundary render error [!]', + // Boundary fails with new error, propagate to next boundary + 'BrokenRenderErrorBoundary componentWillUnmount', + // Attempt to handle the error again + 'ErrorBoundary componentDidCatch', + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render error', + 'ErrorBoundary componentDidUpdate', ]); log.length = 0; @@ -1017,23 +926,14 @@ describe('ReactErrorBoundaries', () => { 'BrokenRender constructor', 'BrokenRender componentWillMount', 'BrokenRender render [!]', - ...(ReactDOMFeatureFlags.useFiber - ? [ - // Finish mounting with null children - 'ErrorBoundary componentDidMount', - // Handle the error - 'ErrorBoundary componentDidCatch', - // Render the error message - 'ErrorBoundary componentWillUpdate', - 'ErrorBoundary render error', - 'ErrorBoundary componentDidUpdate', - ] - : [ - 'ErrorBoundary componentDidCatch', - // Render the error message - 'ErrorBoundary render error', - 'ErrorBoundary componentDidMount', - ]), + // Finish mounting with null children + 'ErrorBoundary componentDidMount', + // Handle the error + 'ErrorBoundary componentDidCatch', + // Render the error message + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render error', + 'ErrorBoundary componentDidUpdate', ]); log.length = 0; @@ -1066,28 +966,15 @@ describe('ReactErrorBoundaries', () => { 'BrokenRender componentWillMount', 'BrokenRender render [!]', // Handle error: - ...(ReactDOMFeatureFlags.useFiber - ? [ - // Finish mounting with null children - 'ErrorBoundary componentDidMount', - // Handle the error - 'ErrorBoundary componentDidCatch', - // Render the error message - 'ErrorBoundary componentWillUpdate', - 'ErrorBoundary render error', - 'Error message ref is set to [object HTMLDivElement]', - 'ErrorBoundary componentDidUpdate', - ] - : [ - 'ErrorBoundary componentDidCatch', - // Stack reconciler resets ref on update, as it doesn't know ref was never set. - // This is unnecessary, and Fiber doesn't do it: - 'Child ref is set to null', - 'ErrorBoundary render error', - // Ref to error message should get set: - 'Error message ref is set to [object HTMLDivElement]', - 'ErrorBoundary componentDidMount', - ]), + // Finish mounting with null children + 'ErrorBoundary componentDidMount', + // Handle the error + 'ErrorBoundary componentDidCatch', + // Render the error message + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render error', + 'Error message ref is set to [object HTMLDivElement]', + 'ErrorBoundary componentDidUpdate', ]); log.length = 0; @@ -1151,26 +1038,15 @@ describe('ReactErrorBoundaries', () => { 'Normal2 render', // BrokenConstructor will abort rendering: 'BrokenConstructor constructor [!]', - ...(ReactDOMFeatureFlags.useFiber - ? [ - // Finish updating with null children - 'Normal componentWillUnmount', - 'ErrorBoundary componentDidUpdate', - // Handle the error - 'ErrorBoundary componentDidCatch', - // Render the error message - 'ErrorBoundary componentWillUpdate', - 'ErrorBoundary render error', - 'ErrorBoundary componentDidUpdate', - ] - : [ - 'ErrorBoundary componentDidCatch', - // Stack unmounts first, then renders: - 'Normal componentWillUnmount', - 'ErrorBoundary render error', - // Normal2 does not get lifefycle because it was never mounted - 'ErrorBoundary componentDidUpdate', - ]), + // Finish updating with null children + 'Normal componentWillUnmount', + 'ErrorBoundary componentDidUpdate', + // Handle the error + 'ErrorBoundary componentDidCatch', + // Render the error message + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render error', + 'ErrorBoundary componentDidUpdate', ]); log.length = 0; @@ -1211,26 +1087,15 @@ describe('ReactErrorBoundaries', () => { // BrokenComponentWillMount will abort rendering: 'BrokenComponentWillMount constructor', 'BrokenComponentWillMount componentWillMount [!]', - ...(ReactDOMFeatureFlags.useFiber - ? [ - // Finish updating with null children - 'Normal componentWillUnmount', - 'ErrorBoundary componentDidUpdate', - // Handle the error - 'ErrorBoundary componentDidCatch', - // Render the error message - 'ErrorBoundary componentWillUpdate', - 'ErrorBoundary render error', - 'ErrorBoundary componentDidUpdate', - ] - : [ - 'ErrorBoundary componentDidCatch', - // Stack unmounts first, then renders: - 'Normal componentWillUnmount', - 'ErrorBoundary render error', - // Normal2 does not get lifefycle because it was never mounted - 'ErrorBoundary componentDidUpdate', - ]), + // Finish updating with null children + 'Normal componentWillUnmount', + 'ErrorBoundary componentDidUpdate', + // Handle the error + 'ErrorBoundary componentDidCatch', + // Render the error message + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render error', + 'ErrorBoundary componentDidUpdate', ]); log.length = 0; @@ -1266,26 +1131,15 @@ describe('ReactErrorBoundaries', () => { 'Normal render', // BrokenComponentWillReceiveProps will abort rendering: 'BrokenComponentWillReceiveProps componentWillReceiveProps [!]', - ...(ReactDOMFeatureFlags.useFiber - ? [ - // Finish updating with null children - 'Normal componentWillUnmount', - 'BrokenComponentWillReceiveProps componentWillUnmount', - 'ErrorBoundary componentDidUpdate', - // Handle the error - 'ErrorBoundary componentDidCatch', - 'ErrorBoundary componentWillUpdate', - 'ErrorBoundary render error', - 'ErrorBoundary componentDidUpdate', - ] - : [ - 'ErrorBoundary componentDidCatch', - // Stack unmounts first, then renders: - 'Normal componentWillUnmount', - 'BrokenComponentWillReceiveProps componentWillUnmount', - 'ErrorBoundary render error', - 'ErrorBoundary componentDidUpdate', - ]), + // Finish updating with null children + 'Normal componentWillUnmount', + 'BrokenComponentWillReceiveProps componentWillUnmount', + 'ErrorBoundary componentDidUpdate', + // Handle the error + 'ErrorBoundary componentDidCatch', + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render error', + 'ErrorBoundary componentDidUpdate', ]); log.length = 0; @@ -1322,26 +1176,15 @@ describe('ReactErrorBoundaries', () => { // BrokenComponentWillUpdate will abort rendering: 'BrokenComponentWillUpdate componentWillReceiveProps', 'BrokenComponentWillUpdate componentWillUpdate [!]', - ...(ReactDOMFeatureFlags.useFiber - ? [ - // Finish updating with null children - 'Normal componentWillUnmount', - 'BrokenComponentWillUpdate componentWillUnmount', - 'ErrorBoundary componentDidUpdate', - // Handle the error - 'ErrorBoundary componentDidCatch', - 'ErrorBoundary componentWillUpdate', - 'ErrorBoundary render error', - 'ErrorBoundary componentDidUpdate', - ] - : [ - 'ErrorBoundary componentDidCatch', - // Stack unmounts first, then renders: - 'Normal componentWillUnmount', - 'BrokenComponentWillUpdate componentWillUnmount', - 'ErrorBoundary render error', - 'ErrorBoundary componentDidUpdate', - ]), + // Finish updating with null children + 'Normal componentWillUnmount', + 'BrokenComponentWillUpdate componentWillUnmount', + 'ErrorBoundary componentDidUpdate', + // Handle the error + 'ErrorBoundary componentDidCatch', + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render error', + 'ErrorBoundary componentDidUpdate', ]); log.length = 0; @@ -1383,25 +1226,14 @@ describe('ReactErrorBoundaries', () => { 'BrokenRender constructor', 'BrokenRender componentWillMount', 'BrokenRender render [!]', - ...(ReactDOMFeatureFlags.useFiber - ? [ - // Finish updating with null children - 'Normal componentWillUnmount', - 'ErrorBoundary componentDidUpdate', - // Handle the error - 'ErrorBoundary componentDidCatch', - 'ErrorBoundary componentWillUpdate', - 'ErrorBoundary render error', - 'ErrorBoundary componentDidUpdate', - ] - : [ - 'ErrorBoundary componentDidCatch', - // Stack unmounts first, then renders: - 'Normal componentWillUnmount', - 'ErrorBoundary render error', - // Normal2 does not get lifefycle because it was never mounted - 'ErrorBoundary componentDidUpdate', - ]), + // Finish updating with null children + 'Normal componentWillUnmount', + 'ErrorBoundary componentDidUpdate', + // Handle the error + 'ErrorBoundary componentDidCatch', + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render error', + 'ErrorBoundary componentDidUpdate', ]); log.length = 0; @@ -1453,22 +1285,13 @@ describe('ReactErrorBoundaries', () => { 'BrokenRender constructor', 'BrokenRender componentWillMount', 'BrokenRender render [!]', - ...(ReactDOMFeatureFlags.useFiber - ? [ - // Finish updating with null children - 'Child1 ref is set to null', - 'ErrorBoundary componentDidUpdate', - // Handle the error - 'ErrorBoundary componentDidCatch', - 'ErrorBoundary componentWillUpdate', - 'ErrorBoundary render error', - ] - : [ - 'ErrorBoundary componentDidCatch', - // Stack resets ref first, renders later - 'Child1 ref is set to null', - 'ErrorBoundary render error', - ]), + // Finish updating with null children + 'Child1 ref is set to null', + 'ErrorBoundary componentDidUpdate', + // Handle the error + 'ErrorBoundary componentDidCatch', + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render error', 'Error message ref is set to [object HTMLDivElement]', // Child2 ref is never set because its mounting aborted 'ErrorBoundary componentDidUpdate', @@ -1511,35 +1334,22 @@ describe('ReactErrorBoundaries', () => { 'BrokenComponentWillUnmount render', // Unmounting throws: 'BrokenComponentWillUnmount componentWillUnmount [!]', - ...(ReactDOMFeatureFlags.useFiber - ? [ - // Fiber proceeds with lifecycles despite errors - 'Normal componentWillUnmount', - // The components have updated in this phase - 'BrokenComponentWillUnmount componentDidUpdate', - 'ErrorBoundary componentDidUpdate', - // Now that commit phase is done, Fiber unmounts the boundary's children - 'BrokenComponentWillUnmount componentWillUnmount [!]', - 'ErrorBoundary componentDidCatch', - // The initial render was aborted, so - // Fiber retries from the root. - 'ErrorBoundary componentWillUpdate', - // Render an error now (stack will do it later) - 'ErrorBoundary render error', - // Attempt to unmount previous child: - // Done - 'ErrorBoundary componentDidUpdate', - ] - : [ - // Stack will handle error immediately - 'ErrorBoundary componentDidCatch', - // Attempt to unmount previous children: - 'BrokenComponentWillUnmount componentWillUnmount [!]', - 'Normal componentWillUnmount', - // Render an error now (Fiber will do it earlier) - 'ErrorBoundary render error', - 'ErrorBoundary componentDidUpdate', - ]), + // Fiber proceeds with lifecycles despite errors + 'Normal componentWillUnmount', + // The components have updated in this phase + 'BrokenComponentWillUnmount componentDidUpdate', + 'ErrorBoundary componentDidUpdate', + // Now that commit phase is done, Fiber unmounts the boundary's children + 'BrokenComponentWillUnmount componentWillUnmount [!]', + 'ErrorBoundary componentDidCatch', + // The initial render was aborted, so + // Fiber retries from the root. + 'ErrorBoundary componentWillUpdate', + // Render an error now (stack will do it later) + 'ErrorBoundary render error', + // Attempt to unmount previous child: + // Done + 'ErrorBoundary componentDidUpdate', ]); log.length = 0; @@ -1582,33 +1392,21 @@ describe('ReactErrorBoundaries', () => { 'BrokenComponentWillUnmount render', // Unmounting throws: 'BrokenComponentWillUnmount componentWillUnmount [!]', - ...(ReactDOMFeatureFlags.useFiber - ? [ - // Fiber proceeds with lifecycles despite errors - 'BrokenComponentWillUnmount componentDidUpdate', - 'Normal componentDidUpdate', - 'ErrorBoundary componentDidUpdate', - 'Normal componentWillUnmount', - 'BrokenComponentWillUnmount componentWillUnmount [!]', - // Now that commit phase is done, Fiber handles errors - 'ErrorBoundary componentDidCatch', - // The initial render was aborted, so - // Fiber retries from the root. - 'ErrorBoundary componentWillUpdate', - // Render an error now (stack will do it later) - 'ErrorBoundary render error', - // Done - 'ErrorBoundary componentDidUpdate', - ] - : [ - 'ErrorBoundary componentDidCatch', - // Attempt to unmount previous children: - 'Normal componentWillUnmount', - 'BrokenComponentWillUnmount componentWillUnmount [!]', - // Stack calls lifecycles first, then renders. - 'ErrorBoundary render error', - 'ErrorBoundary componentDidUpdate', - ]), + // Fiber proceeds with lifecycles despite errors + 'BrokenComponentWillUnmount componentDidUpdate', + 'Normal componentDidUpdate', + 'ErrorBoundary componentDidUpdate', + 'Normal componentWillUnmount', + 'BrokenComponentWillUnmount componentWillUnmount [!]', + // Now that commit phase is done, Fiber handles errors + 'ErrorBoundary componentDidCatch', + // The initial render was aborted, so + // Fiber retries from the root. + 'ErrorBoundary componentWillUpdate', + // Render an error now (stack will do it later) + 'ErrorBoundary render error', + // Done + 'ErrorBoundary componentDidUpdate', ]); log.length = 0; @@ -1662,31 +1460,19 @@ describe('ReactErrorBoundaries', () => { 'InnerErrorBoundary render success', // Try unmounting child 'BrokenComponentWillUnmount componentWillUnmount [!]', - ...(ReactDOMFeatureFlags.useFiber - ? [ - // Fiber proceeds with lifecycles despite errors - // Inner and outer boundaries have updated in this phase - 'InnerErrorBoundary componentDidUpdate', - 'OuterErrorBoundary componentDidUpdate', - // Now that commit phase is done, Fiber handles errors - // Only inner boundary receives the error: - 'InnerErrorBoundary componentDidCatch', - 'InnerErrorBoundary componentWillUpdate', - // Render an error now - 'InnerErrorBoundary render error', - // In Fiber, this was a local update to the - // inner boundary so only its hook fires - 'InnerErrorBoundary componentDidUpdate', - ] - : [ - // Stack will handle error immediately - 'InnerErrorBoundary componentDidCatch', - 'InnerErrorBoundary render error', - // In stack, this was a part of the update to the - // outer boundary so both lifecycles fire - 'InnerErrorBoundary componentDidUpdate', - 'OuterErrorBoundary componentDidUpdate', - ]), + // Fiber proceeds with lifecycles despite errors + // Inner and outer boundaries have updated in this phase + 'InnerErrorBoundary componentDidUpdate', + 'OuterErrorBoundary componentDidUpdate', + // Now that commit phase is done, Fiber handles errors + // Only inner boundary receives the error: + 'InnerErrorBoundary componentDidCatch', + 'InnerErrorBoundary componentWillUpdate', + // Render an error now + 'InnerErrorBoundary render error', + // In Fiber, this was a local update to the + // inner boundary so only its hook fires + 'InnerErrorBoundary componentDidUpdate', ]); log.length = 0; @@ -1858,374 +1644,371 @@ describe('ReactErrorBoundaries', () => { expect(log).toEqual(['ErrorBoundary componentWillUnmount']); }); - // The tests below implement new features in Fiber. - if (ReactDOMFeatureFlags.useFiber) { - it('catches errors originating downstream', () => { - var fail = false; - class Stateful extends React.Component { - state = {shouldThrow: false}; + it('catches errors originating downstream', () => { + var fail = false; + class Stateful extends React.Component { + state = {shouldThrow: false}; - render() { - if (fail) { - log.push('Stateful render [!]'); - throw new Error('Hello'); - } - return
{this.props.children}
; + render() { + if (fail) { + log.push('Stateful render [!]'); + throw new Error('Hello'); } + return
{this.props.children}
; } + } - var statefulInst; - var container = document.createElement('div'); - ReactDOM.render( - - (statefulInst = inst)} /> - , - container, - ); - - log.length = 0; - expect(() => { - fail = true; - statefulInst.forceUpdate(); - }).not.toThrow(); - - expect(log).toEqual([ - 'Stateful render [!]', - 'ErrorBoundary componentDidCatch', - 'ErrorBoundary componentWillUpdate', - 'ErrorBoundary render error', - 'ErrorBoundary componentDidUpdate', - ]); - - log.length = 0; - ReactDOM.unmountComponentAtNode(container); - expect(log).toEqual(['ErrorBoundary componentWillUnmount']); - }); - - it('catches errors in componentDidMount', () => { - var container = document.createElement('div'); - ReactDOM.render( - - - - - - - , - container, - ); - expect(log).toEqual([ - 'ErrorBoundary constructor', - 'ErrorBoundary componentWillMount', - 'ErrorBoundary render success', - 'BrokenComponentWillUnmount constructor', - 'BrokenComponentWillUnmount componentWillMount', - 'BrokenComponentWillUnmount render', - 'Normal constructor', - 'Normal componentWillMount', - 'Normal render', - 'BrokenComponentDidMount constructor', - 'BrokenComponentDidMount componentWillMount', - 'BrokenComponentDidMount render', - 'LastChild constructor', - 'LastChild componentWillMount', - 'LastChild render', - // Start flushing didMount queue - 'Normal componentDidMount', - 'BrokenComponentWillUnmount componentDidMount', - 'BrokenComponentDidMount componentDidMount [!]', - // Continue despite the error - 'LastChild componentDidMount', - 'ErrorBoundary componentDidMount', - // Now we are ready to handle the error - // Safely unmount every child - 'BrokenComponentWillUnmount componentWillUnmount [!]', - // Continue unmounting safely despite any errors - 'Normal componentWillUnmount', - 'BrokenComponentDidMount componentWillUnmount', - 'LastChild componentWillUnmount', - // Handle the error - 'ErrorBoundary componentDidCatch', - 'ErrorBoundary componentWillUpdate', - 'ErrorBoundary render error', - // The update has finished - 'ErrorBoundary componentDidUpdate', - ]); - - log.length = 0; - ReactDOM.unmountComponentAtNode(container); - expect(log).toEqual(['ErrorBoundary componentWillUnmount']); - }); - - it('catches errors in componentDidUpdate', () => { - var container = document.createElement('div'); - ReactDOM.render( - - - , - container, - ); - - log.length = 0; - ReactDOM.render( - - - , - container, - ); - expect(log).toEqual([ - 'ErrorBoundary componentWillReceiveProps', - 'ErrorBoundary componentWillUpdate', - 'ErrorBoundary render success', - 'BrokenComponentDidUpdate componentWillReceiveProps', - 'BrokenComponentDidUpdate componentWillUpdate', - 'BrokenComponentDidUpdate render', - // All lifecycles run - 'BrokenComponentDidUpdate componentDidUpdate [!]', - 'ErrorBoundary componentDidUpdate', - 'BrokenComponentDidUpdate componentWillUnmount', - // Then, error is handled - 'ErrorBoundary componentDidCatch', - 'ErrorBoundary componentWillUpdate', - 'ErrorBoundary render error', - 'ErrorBoundary componentDidUpdate', - ]); - - log.length = 0; - ReactDOM.unmountComponentAtNode(container); - expect(log).toEqual(['ErrorBoundary componentWillUnmount']); - }); - - it('propagates errors inside boundary during componentDidMount', () => { - var container = document.createElement('div'); - ReactDOM.render( - - ( -
- We should never catch our own error: {error.message}. -
- )} - /> -
, - container, - ); - expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); - expect(log).toEqual([ - 'ErrorBoundary constructor', - 'ErrorBoundary componentWillMount', - 'ErrorBoundary render success', - 'BrokenComponentDidMountErrorBoundary constructor', - 'BrokenComponentDidMountErrorBoundary componentWillMount', - 'BrokenComponentDidMountErrorBoundary render success', - 'BrokenComponentDidMountErrorBoundary componentDidMount [!]', - // Fiber proceeds with the hooks - 'ErrorBoundary componentDidMount', - 'BrokenComponentDidMountErrorBoundary componentWillUnmount', - // The error propagates to the higher boundary - 'ErrorBoundary componentDidCatch', - // Fiber retries from the root - 'ErrorBoundary componentWillUpdate', - 'ErrorBoundary render error', - 'ErrorBoundary componentDidUpdate', - ]); - - log.length = 0; - ReactDOM.unmountComponentAtNode(container); - expect(log).toEqual(['ErrorBoundary componentWillUnmount']); - }); - - it('lets different boundaries catch their own first errors', () => { - function renderUnmountError(error) { - return
Caught an unmounting error: {error.message}.
; - } - function renderUpdateError(error) { - return
Caught an updating error: {error.message}.
; - } - - var container = document.createElement('div'); - ReactDOM.render( - - - - - - - - - - , - container, - ); - - log.length = 0; - ReactDOM.render( - - - - - - - , - container, - ); - - expect(container.firstChild.textContent).toBe( - 'Caught an unmounting error: E1.' + 'Caught an updating error: E3.', - ); - expect(log).toEqual([ - // Begin update phase - 'OuterErrorBoundary componentWillReceiveProps', - 'OuterErrorBoundary componentWillUpdate', - 'OuterErrorBoundary render success', - 'InnerUnmountBoundary componentWillReceiveProps', - 'InnerUnmountBoundary componentWillUpdate', - 'InnerUnmountBoundary render success', - 'InnerUpdateBoundary componentWillReceiveProps', - 'InnerUpdateBoundary componentWillUpdate', - 'InnerUpdateBoundary render success', - // First come the updates - 'BrokenComponentDidUpdate componentWillReceiveProps', - 'BrokenComponentDidUpdate componentWillUpdate', - 'BrokenComponentDidUpdate render', - 'BrokenComponentDidUpdate componentWillReceiveProps', - 'BrokenComponentDidUpdate componentWillUpdate', - 'BrokenComponentDidUpdate render', - // We're in commit phase now, deleting - 'BrokenComponentWillUnmount componentWillUnmount [!]', - 'BrokenComponentWillUnmount componentWillUnmount [!]', - // Continue despite errors, handle them after commit is done - 'InnerUnmountBoundary componentDidUpdate', - // We're still in commit phase, now calling update lifecycles - 'BrokenComponentDidUpdate componentDidUpdate [!]', - // Again, continue despite errors, we'll handle them later - 'BrokenComponentDidUpdate componentDidUpdate [!]', - 'InnerUpdateBoundary componentDidUpdate', - 'OuterErrorBoundary componentDidUpdate', - // After the commit phase, attempt to recover from any errors that - // were captured - 'BrokenComponentDidUpdate componentWillUnmount', - 'BrokenComponentDidUpdate componentWillUnmount', - 'InnerUnmountBoundary componentDidCatch', - 'InnerUpdateBoundary componentDidCatch', - 'InnerUnmountBoundary componentWillUpdate', - 'InnerUnmountBoundary render error', - 'InnerUpdateBoundary componentWillUpdate', - 'InnerUpdateBoundary render error', - 'InnerUnmountBoundary componentDidUpdate', - 'InnerUpdateBoundary componentDidUpdate', - ]); - - log.length = 0; - ReactDOM.unmountComponentAtNode(container); - expect(log).toEqual([ - 'OuterErrorBoundary componentWillUnmount', - 'InnerUnmountBoundary componentWillUnmount', - 'InnerUpdateBoundary componentWillUnmount', - ]); - }); - - it('discards a bad root if the root component fails', () => { - spyOn(console, 'error'); - - const X = null; - const Y = undefined; - let err1; - let err2; - - try { - let container = document.createElement('div'); - ReactDOM.render(, container); - } catch (err) { - err1 = err; - } - try { - let container = document.createElement('div'); - ReactDOM.render(, container); - } catch (err) { - err2 = err; - } - - expect(err1.message).toMatch(/got: null/); - expect(err2.message).toMatch(/got: undefined/); - }); - - it('renders empty output if error boundary does not handle the error', () => { - var container = document.createElement('div'); - ReactDOM.render( -
- Sibling - - - -
, - container, - ); - expect(container.firstChild.textContent).toBe('Sibling'); - expect(log).toEqual([ - 'NoopErrorBoundary constructor', - 'NoopErrorBoundary componentWillMount', - 'NoopErrorBoundary render', - 'BrokenRender constructor', - 'BrokenRender componentWillMount', - 'BrokenRender render [!]', - // In Fiber, noop error boundaries render null - 'NoopErrorBoundary componentDidMount', - 'NoopErrorBoundary componentDidCatch', - // Nothing happens. - ]); - - log.length = 0; - ReactDOM.unmountComponentAtNode(container); - expect(log).toEqual(['NoopErrorBoundary componentWillUnmount']); - }); + var statefulInst; + var container = document.createElement('div'); + ReactDOM.render( + + (statefulInst = inst)} /> + , + container, + ); - it('passes first error when two errors happen in commit', () => { - const errors = []; - let caughtError; - class Parent extends React.Component { - render() { - return ; - } - componentDidMount() { - errors.push('parent sad'); - throw new Error('parent sad'); - } + log.length = 0; + expect(() => { + fail = true; + statefulInst.forceUpdate(); + }).not.toThrow(); + + expect(log).toEqual([ + 'Stateful render [!]', + 'ErrorBoundary componentDidCatch', + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render error', + 'ErrorBoundary componentDidUpdate', + ]); + + log.length = 0; + ReactDOM.unmountComponentAtNode(container); + expect(log).toEqual(['ErrorBoundary componentWillUnmount']); + }); + + it('catches errors in componentDidMount', () => { + var container = document.createElement('div'); + ReactDOM.render( + + + + + + + , + container, + ); + expect(log).toEqual([ + 'ErrorBoundary constructor', + 'ErrorBoundary componentWillMount', + 'ErrorBoundary render success', + 'BrokenComponentWillUnmount constructor', + 'BrokenComponentWillUnmount componentWillMount', + 'BrokenComponentWillUnmount render', + 'Normal constructor', + 'Normal componentWillMount', + 'Normal render', + 'BrokenComponentDidMount constructor', + 'BrokenComponentDidMount componentWillMount', + 'BrokenComponentDidMount render', + 'LastChild constructor', + 'LastChild componentWillMount', + 'LastChild render', + // Start flushing didMount queue + 'Normal componentDidMount', + 'BrokenComponentWillUnmount componentDidMount', + 'BrokenComponentDidMount componentDidMount [!]', + // Continue despite the error + 'LastChild componentDidMount', + 'ErrorBoundary componentDidMount', + // Now we are ready to handle the error + // Safely unmount every child + 'BrokenComponentWillUnmount componentWillUnmount [!]', + // Continue unmounting safely despite any errors + 'Normal componentWillUnmount', + 'BrokenComponentDidMount componentWillUnmount', + 'LastChild componentWillUnmount', + // Handle the error + 'ErrorBoundary componentDidCatch', + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render error', + // The update has finished + 'ErrorBoundary componentDidUpdate', + ]); + + log.length = 0; + ReactDOM.unmountComponentAtNode(container); + expect(log).toEqual(['ErrorBoundary componentWillUnmount']); + }); + + it('catches errors in componentDidUpdate', () => { + var container = document.createElement('div'); + ReactDOM.render( + + + , + container, + ); + + log.length = 0; + ReactDOM.render( + + + , + container, + ); + expect(log).toEqual([ + 'ErrorBoundary componentWillReceiveProps', + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render success', + 'BrokenComponentDidUpdate componentWillReceiveProps', + 'BrokenComponentDidUpdate componentWillUpdate', + 'BrokenComponentDidUpdate render', + // All lifecycles run + 'BrokenComponentDidUpdate componentDidUpdate [!]', + 'ErrorBoundary componentDidUpdate', + 'BrokenComponentDidUpdate componentWillUnmount', + // Then, error is handled + 'ErrorBoundary componentDidCatch', + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render error', + 'ErrorBoundary componentDidUpdate', + ]); + + log.length = 0; + ReactDOM.unmountComponentAtNode(container); + expect(log).toEqual(['ErrorBoundary componentWillUnmount']); + }); + + it('propagates errors inside boundary during componentDidMount', () => { + var container = document.createElement('div'); + ReactDOM.render( + + ( +
+ We should never catch our own error: {error.message}. +
+ )} + /> +
, + container, + ); + expect(container.firstChild.textContent).toBe('Caught an error: Hello.'); + expect(log).toEqual([ + 'ErrorBoundary constructor', + 'ErrorBoundary componentWillMount', + 'ErrorBoundary render success', + 'BrokenComponentDidMountErrorBoundary constructor', + 'BrokenComponentDidMountErrorBoundary componentWillMount', + 'BrokenComponentDidMountErrorBoundary render success', + 'BrokenComponentDidMountErrorBoundary componentDidMount [!]', + // Fiber proceeds with the hooks + 'ErrorBoundary componentDidMount', + 'BrokenComponentDidMountErrorBoundary componentWillUnmount', + // The error propagates to the higher boundary + 'ErrorBoundary componentDidCatch', + // Fiber retries from the root + 'ErrorBoundary componentWillUpdate', + 'ErrorBoundary render error', + 'ErrorBoundary componentDidUpdate', + ]); + + log.length = 0; + ReactDOM.unmountComponentAtNode(container); + expect(log).toEqual(['ErrorBoundary componentWillUnmount']); + }); + + it('lets different boundaries catch their own first errors', () => { + function renderUnmountError(error) { + return
Caught an unmounting error: {error.message}.
; + } + function renderUpdateError(error) { + return
Caught an updating error: {error.message}.
; + } + + var container = document.createElement('div'); + ReactDOM.render( + + + + + + + + + + , + container, + ); + + log.length = 0; + ReactDOM.render( + + + + + + + , + container, + ); + + expect(container.firstChild.textContent).toBe( + 'Caught an unmounting error: E1.' + 'Caught an updating error: E3.', + ); + expect(log).toEqual([ + // Begin update phase + 'OuterErrorBoundary componentWillReceiveProps', + 'OuterErrorBoundary componentWillUpdate', + 'OuterErrorBoundary render success', + 'InnerUnmountBoundary componentWillReceiveProps', + 'InnerUnmountBoundary componentWillUpdate', + 'InnerUnmountBoundary render success', + 'InnerUpdateBoundary componentWillReceiveProps', + 'InnerUpdateBoundary componentWillUpdate', + 'InnerUpdateBoundary render success', + // First come the updates + 'BrokenComponentDidUpdate componentWillReceiveProps', + 'BrokenComponentDidUpdate componentWillUpdate', + 'BrokenComponentDidUpdate render', + 'BrokenComponentDidUpdate componentWillReceiveProps', + 'BrokenComponentDidUpdate componentWillUpdate', + 'BrokenComponentDidUpdate render', + // We're in commit phase now, deleting + 'BrokenComponentWillUnmount componentWillUnmount [!]', + 'BrokenComponentWillUnmount componentWillUnmount [!]', + // Continue despite errors, handle them after commit is done + 'InnerUnmountBoundary componentDidUpdate', + // We're still in commit phase, now calling update lifecycles + 'BrokenComponentDidUpdate componentDidUpdate [!]', + // Again, continue despite errors, we'll handle them later + 'BrokenComponentDidUpdate componentDidUpdate [!]', + 'InnerUpdateBoundary componentDidUpdate', + 'OuterErrorBoundary componentDidUpdate', + // After the commit phase, attempt to recover from any errors that + // were captured + 'BrokenComponentDidUpdate componentWillUnmount', + 'BrokenComponentDidUpdate componentWillUnmount', + 'InnerUnmountBoundary componentDidCatch', + 'InnerUpdateBoundary componentDidCatch', + 'InnerUnmountBoundary componentWillUpdate', + 'InnerUnmountBoundary render error', + 'InnerUpdateBoundary componentWillUpdate', + 'InnerUpdateBoundary render error', + 'InnerUnmountBoundary componentDidUpdate', + 'InnerUpdateBoundary componentDidUpdate', + ]); + + log.length = 0; + ReactDOM.unmountComponentAtNode(container); + expect(log).toEqual([ + 'OuterErrorBoundary componentWillUnmount', + 'InnerUnmountBoundary componentWillUnmount', + 'InnerUpdateBoundary componentWillUnmount', + ]); + }); + + it('discards a bad root if the root component fails', () => { + spyOn(console, 'error'); + + const X = null; + const Y = undefined; + let err1; + let err2; + + try { + let container = document.createElement('div'); + ReactDOM.render(, container); + } catch (err) { + err1 = err; + } + try { + let container = document.createElement('div'); + ReactDOM.render(, container); + } catch (err) { + err2 = err; + } + + expect(err1.message).toMatch(/got: null/); + expect(err2.message).toMatch(/got: undefined/); + }); + + it('renders empty output if error boundary does not handle the error', () => { + var container = document.createElement('div'); + ReactDOM.render( +
+ Sibling + + + +
, + container, + ); + expect(container.firstChild.textContent).toBe('Sibling'); + expect(log).toEqual([ + 'NoopErrorBoundary constructor', + 'NoopErrorBoundary componentWillMount', + 'NoopErrorBoundary render', + 'BrokenRender constructor', + 'BrokenRender componentWillMount', + 'BrokenRender render [!]', + // In Fiber, noop error boundaries render null + 'NoopErrorBoundary componentDidMount', + 'NoopErrorBoundary componentDidCatch', + // Nothing happens. + ]); + + log.length = 0; + ReactDOM.unmountComponentAtNode(container); + expect(log).toEqual(['NoopErrorBoundary componentWillUnmount']); + }); + + it('passes first error when two errors happen in commit', () => { + const errors = []; + let caughtError; + class Parent extends React.Component { + render() { + return ; } - class Child extends React.Component { - render() { - return
; - } - componentDidMount() { - errors.push('child sad'); - throw new Error('child sad'); - } + componentDidMount() { + errors.push('parent sad'); + throw new Error('parent sad'); } - - var container = document.createElement('div'); - try { - // Here, we test the behavior where there is no error boundary and we - // delegate to the host root. - ReactDOM.render(, container); - } catch (e) { - if (e.message !== 'parent sad' && e.message !== 'child sad') { - throw e; - } - caughtError = e; + } + class Child extends React.Component { + render() { + return
; + } + componentDidMount() { + errors.push('child sad'); + throw new Error('child sad'); } + } - expect(errors).toEqual(['child sad', 'parent sad']); - // Error should be the first thrown - expect(caughtError.message).toBe('child sad'); - }); - } + var container = document.createElement('div'); + try { + // Here, we test the behavior where there is no error boundary and we + // delegate to the host root. + ReactDOM.render(, container); + } catch (e) { + if (e.message !== 'parent sad' && e.message !== 'child sad') { + throw e; + } + caughtError = e; + } + + expect(errors).toEqual(['child sad', 'parent sad']); + // Error should be the first thrown + expect(caughtError.message).toBe('child sad'); + }); }); diff --git a/src/renderers/__tests__/ReactHostOperationHistoryHook-test.js b/src/renderers/__tests__/ReactHostOperationHistoryHook-test.js deleted file mode 100644 index fa43369f8df..00000000000 --- a/src/renderers/__tests__/ReactHostOperationHistoryHook-test.js +++ /dev/null @@ -1,781 +0,0 @@ -/** - * Copyright 2016-present, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - * - * @emails react-core - */ - -'use strict'; - -var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); -var describeStack = ReactDOMFeatureFlags.useFiber ? describe.skip : describe; - -// This is only used by ReactPerf which is currently not supported on Fiber. -// Use browser timeline integration instead. -describeStack('ReactHostOperationHistoryHook', () => { - var React; - var ReactPerf; - var ReactDOM; - var ReactDOMComponentTree; - var ReactHostOperationHistoryHook; - - beforeEach(() => { - jest.resetModules(); - - React = require('react'); - ReactPerf = require('ReactPerf'); - ReactDOM = require('react-dom'); - ReactDOMComponentTree = require('ReactDOMComponentTree'); - ReactHostOperationHistoryHook = require('ReactHostOperationHistoryHook'); - - ReactPerf.start(); - }); - - afterEach(() => { - ReactPerf.stop(); - }); - - function assertHistoryMatches(expectedHistory) { - var actualHistory = ReactHostOperationHistoryHook.getHistory(); - expectDev(actualHistory).toEqual(expectedHistory); - } - - describe('mount', () => { - it('gets recorded for host roots', () => { - var node = document.createElement('div'); - ReactHostOperationHistoryHook._preventClearing = true; - ReactDOM.render(

Hi.

, node); - - var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); - assertHistoryMatches([ - { - instanceID: inst._debugID, - type: 'mount', - payload: 'DIV', - }, - ]); - }); - - it('gets recorded for composite roots', () => { - function Foo() { - return

Hi.

; - } - var node = document.createElement('div'); - - ReactHostOperationHistoryHook._preventClearing = true; - ReactDOM.render(, node); - - var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); - assertHistoryMatches([ - { - instanceID: inst._debugID, - type: 'mount', - payload: 'DIV', - }, - ]); - }); - - it('gets ignored for composite roots that return null', () => { - function Foo() { - return null; - } - var node = document.createElement('div'); - - ReactHostOperationHistoryHook._preventClearing = true; - ReactDOM.render(, node); - - // Empty DOM components should be invisible to hooks. - assertHistoryMatches([]); - }); - - it('gets recorded when a native is mounted deeply instead of null', () => { - var element; - function Foo() { - return element; - } - - ReactHostOperationHistoryHook._preventClearing = true; - - var node = document.createElement('div'); - element = null; - ReactDOM.render(, node); - - element = ; - ReactDOM.render(, node); - var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); - - // Since empty components should be invisible to hooks, - // we record a "mount" event rather than a "replace with". - assertHistoryMatches([ - { - instanceID: inst._debugID, - type: 'mount', - payload: 'SPAN', - }, - ]); - }); - }); - - describe('update styles', () => { - it('gets recorded during mount', () => { - var node = document.createElement('div'); - - ReactHostOperationHistoryHook._preventClearing = true; - ReactDOM.render( -
, - node, - ); - - var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); - assertHistoryMatches([ - { - instanceID: inst._debugID, - type: 'update styles', - payload: { - color: 'red', - backgroundColor: 'yellow', - }, - }, - { - instanceID: inst._debugID, - type: 'mount', - payload: 'DIV', - }, - ]); - }); - - it('gets recorded during an update', () => { - var node = document.createElement('div'); - ReactDOM.render(
, node); - var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); - - ReactHostOperationHistoryHook._preventClearing = true; - ReactDOM.render(
, node); - ReactDOM.render( -
, - node, - ); - ReactDOM.render(
, node); - ReactDOM.render(
, node); - - assertHistoryMatches([ - { - instanceID: inst._debugID, - type: 'update styles', - payload: {color: 'red'}, - }, - { - instanceID: inst._debugID, - type: 'update styles', - payload: {color: 'blue', backgroundColor: 'yellow'}, - }, - { - instanceID: inst._debugID, - type: 'update styles', - payload: {color: '', backgroundColor: 'green'}, - }, - { - instanceID: inst._debugID, - type: 'update styles', - payload: {backgroundColor: ''}, - }, - ]); - }); - - it('gets ignored if the styles are shallowly equal', () => { - var node = document.createElement('div'); - ReactDOM.render(
, node); - var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); - - ReactHostOperationHistoryHook._preventClearing = true; - ReactDOM.render( -
, - node, - ); - ReactDOM.render( -
, - node, - ); - - assertHistoryMatches([ - { - instanceID: inst._debugID, - type: 'update styles', - payload: { - color: 'red', - backgroundColor: 'yellow', - }, - }, - ]); - }); - }); - - describe('update attribute', () => { - describe('simple attribute', () => { - it('gets recorded during mount', () => { - var node = document.createElement('div'); - - ReactHostOperationHistoryHook._preventClearing = true; - ReactDOM.render(
, node); - - var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); - assertHistoryMatches([ - { - instanceID: inst._debugID, - type: 'update attribute', - payload: {className: 'rad'}, - }, - { - instanceID: inst._debugID, - type: 'update attribute', - payload: {tabIndex: 42}, - }, - { - instanceID: inst._debugID, - type: 'mount', - payload: 'DIV', - }, - ]); - }); - - it('gets recorded during an update', () => { - var node = document.createElement('div'); - ReactDOM.render(
, node); - var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); - - ReactHostOperationHistoryHook._preventClearing = true; - ReactDOM.render(
, node); - ReactDOM.render(
, node); - ReactDOM.render(
, node); - - assertHistoryMatches([ - { - instanceID: inst._debugID, - type: 'update attribute', - payload: {className: 'rad'}, - }, - { - instanceID: inst._debugID, - type: 'update attribute', - payload: {className: 'mad'}, - }, - { - instanceID: inst._debugID, - type: 'update attribute', - payload: {tabIndex: 42}, - }, - { - instanceID: inst._debugID, - type: 'remove attribute', - payload: 'className', - }, - { - instanceID: inst._debugID, - type: 'update attribute', - payload: {tabIndex: 43}, - }, - ]); - }); - }); - - describe('attribute that gets removed with certain values', () => { - it('gets recorded as a removal during an update', () => { - var node = document.createElement('div'); - ReactDOM.render(
, node); - var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); - - ReactHostOperationHistoryHook._preventClearing = true; - ReactDOM.render(
, node); - ReactDOM.render(
, node); - - assertHistoryMatches([ - { - instanceID: inst._debugID, - type: 'update attribute', - payload: {disabled: true}, - }, - { - instanceID: inst._debugID, - type: 'remove attribute', - payload: 'disabled', - }, - ]); - }); - }); - - describe('custom attribute', () => { - it('gets recorded during mount', () => { - var node = document.createElement('div'); - - ReactHostOperationHistoryHook._preventClearing = true; - ReactDOM.render(
, node); - - var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); - assertHistoryMatches([ - { - instanceID: inst._debugID, - type: 'update attribute', - payload: {'data-x': 'rad'}, - }, - { - instanceID: inst._debugID, - type: 'update attribute', - payload: {'data-y': 42}, - }, - { - instanceID: inst._debugID, - type: 'mount', - payload: 'DIV', - }, - ]); - }); - - it('gets recorded during an update', () => { - var node = document.createElement('div'); - ReactDOM.render(
, node); - var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); - - ReactHostOperationHistoryHook._preventClearing = true; - ReactDOM.render(
, node); - ReactDOM.render(
, node); - ReactDOM.render(
, node); - - assertHistoryMatches([ - { - instanceID: inst._debugID, - type: 'update attribute', - payload: {'data-x': 'rad'}, - }, - { - instanceID: inst._debugID, - type: 'update attribute', - payload: {'data-x': 'mad'}, - }, - { - instanceID: inst._debugID, - type: 'update attribute', - payload: {'data-y': 42}, - }, - { - instanceID: inst._debugID, - type: 'remove attribute', - payload: 'data-x', - }, - { - instanceID: inst._debugID, - type: 'update attribute', - payload: {'data-y': 43}, - }, - ]); - }); - }); - - describe('attribute on a web component', () => { - it('gets recorded during mount', () => { - var node = document.createElement('div'); - - ReactHostOperationHistoryHook._preventClearing = true; - ReactDOM.render(, node); - - var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); - assertHistoryMatches([ - { - instanceID: inst._debugID, - type: 'update attribute', - payload: {className: 'rad'}, - }, - { - instanceID: inst._debugID, - type: 'update attribute', - payload: {tabIndex: 42}, - }, - { - instanceID: inst._debugID, - type: 'mount', - payload: 'MY-COMPONENT', - }, - ]); - }); - - it('gets recorded during an update', () => { - var node = document.createElement('div'); - ReactDOM.render(, node); - var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); - - ReactHostOperationHistoryHook._preventClearing = true; - ReactDOM.render(, node); - ReactDOM.render(, node); - ReactDOM.render(, node); - - assertHistoryMatches([ - { - instanceID: inst._debugID, - type: 'update attribute', - payload: {className: 'rad'}, - }, - { - instanceID: inst._debugID, - type: 'update attribute', - payload: {className: 'mad'}, - }, - { - instanceID: inst._debugID, - type: 'update attribute', - payload: {tabIndex: 42}, - }, - { - instanceID: inst._debugID, - type: 'remove attribute', - payload: 'className', - }, - { - instanceID: inst._debugID, - type: 'update attribute', - payload: {tabIndex: 43}, - }, - ]); - }); - }); - }); - - describe('replace text', () => { - describe('text content', () => { - it('gets recorded during an update from text content', () => { - var node = document.createElement('div'); - ReactDOM.render(
Hi.
, node); - var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); - - ReactHostOperationHistoryHook._preventClearing = true; - ReactDOM.render(
Bye.
, node); - - assertHistoryMatches([ - { - instanceID: inst._debugID, - type: 'replace text', - payload: 'Bye.', - }, - ]); - }); - - it('gets recorded during an update from html', () => { - var node = document.createElement('div'); - ReactDOM.render( -
, - node, - ); - var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); - - ReactHostOperationHistoryHook._preventClearing = true; - ReactDOM.render(
Bye.
, node); - - assertHistoryMatches([ - { - instanceID: inst._debugID, - type: 'replace text', - payload: 'Bye.', - }, - ]); - }); - - it('gets recorded during an update from children', () => { - var node = document.createElement('div'); - ReactDOM.render(

, node); - var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); - - ReactHostOperationHistoryHook._preventClearing = true; - ReactDOM.render(
Bye.
, node); - - assertHistoryMatches([ - { - instanceID: inst._debugID, - type: 'remove child', - payload: {fromIndex: 0}, - }, - { - instanceID: inst._debugID, - type: 'remove child', - payload: {fromIndex: 1}, - }, - { - instanceID: inst._debugID, - type: 'replace text', - payload: 'Bye.', - }, - ]); - }); - - it('gets ignored if new text is equal', () => { - var node = document.createElement('div'); - ReactDOM.render(
Hi.
, node); - - ReactHostOperationHistoryHook._preventClearing = true; - ReactDOM.render(
Hi.
, node); - - assertHistoryMatches([]); - }); - }); - - describe('text node', () => { - it('gets recorded during an update', () => { - var node = document.createElement('div'); - ReactDOM.render(
{'Hi.'}{42}
, node); - var inst1 = ReactDOMComponentTree.getInstanceFromNode( - node.firstChild.childNodes[0], - ); - var inst2 = ReactDOMComponentTree.getInstanceFromNode( - node.firstChild.childNodes[3], - ); - - ReactHostOperationHistoryHook._preventClearing = true; - ReactDOM.render(
{'Bye.'}{43}
, node); - - assertHistoryMatches([ - { - instanceID: inst1._debugID, - type: 'replace text', - payload: 'Bye.', - }, - { - instanceID: inst2._debugID, - type: 'replace text', - payload: '43', - }, - ]); - }); - - it('gets ignored if new text is equal', () => { - var node = document.createElement('div'); - ReactDOM.render(
{'Hi.'}{42}
, node); - - ReactHostOperationHistoryHook._preventClearing = true; - ReactDOM.render(
{'Hi.'}{42}
, node); - - assertHistoryMatches([]); - }); - }); - }); - - describe('replace with', () => { - it('gets recorded when composite renders to a different type', () => { - var element; - function Foo() { - return element; - } - - var node = document.createElement('div'); - element =
; - ReactDOM.render(, node); - var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); - - element = ; - - ReactHostOperationHistoryHook._preventClearing = true; - ReactDOM.render(, node); - - assertHistoryMatches([ - { - instanceID: inst._debugID, - type: 'replace with', - payload: 'SPAN', - }, - ]); - }); - - it('gets recorded when composite renders to null after a native', () => { - var element; - function Foo() { - return element; - } - - var node = document.createElement('div'); - element = ; - ReactDOM.render(, node); - - var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); - element = null; - - ReactHostOperationHistoryHook._preventClearing = true; - ReactDOM.render(, node); - - assertHistoryMatches([ - { - instanceID: inst._debugID, - type: 'replace with', - payload: '#comment', - }, - ]); - }); - - it('gets ignored if the type has not changed', () => { - var element; - function Foo() { - return element; - } - - var node = document.createElement('div'); - element =
; - ReactDOM.render(, node); - - element =
; - - ReactHostOperationHistoryHook._preventClearing = true; - ReactDOM.render(, node); - - assertHistoryMatches([]); - }); - }); - - describe('replace children', () => { - it('gets recorded during an update from text content', () => { - var node = document.createElement('div'); - ReactDOM.render(
Hi.
, node); - var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); - - ReactHostOperationHistoryHook._preventClearing = true; - ReactDOM.render(
, node); - - assertHistoryMatches([ - { - instanceID: inst._debugID, - type: 'replace children', - payload: 'Bye.', - }, - ]); - }); - - it('gets recorded during an update from html', () => { - var node = document.createElement('div'); - ReactDOM.render(
, node); - var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); - - ReactHostOperationHistoryHook._preventClearing = true; - ReactDOM.render(
, node); - - assertHistoryMatches([ - { - instanceID: inst._debugID, - type: 'replace children', - payload: 'Bye.', - }, - ]); - }); - - it('gets recorded during an update from children', () => { - var node = document.createElement('div'); - ReactDOM.render(

, node); - var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); - - ReactHostOperationHistoryHook._preventClearing = true; - ReactDOM.render(
, node); - - assertHistoryMatches([ - { - instanceID: inst._debugID, - type: 'remove child', - payload: {fromIndex: 0}, - }, - { - instanceID: inst._debugID, - type: 'remove child', - payload: {fromIndex: 1}, - }, - { - instanceID: inst._debugID, - type: 'replace children', - payload: 'Hi.', - }, - ]); - }); - - it('gets ignored if new html is equal', () => { - var node = document.createElement('div'); - ReactDOM.render(
, node); - - ReactHostOperationHistoryHook._preventClearing = true; - ReactDOM.render(
, node); - - assertHistoryMatches([]); - }); - }); - - describe('insert child', () => { - it('gets reported when a child is inserted', () => { - var node = document.createElement('div'); - ReactDOM.render(
, node); - var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); - - ReactHostOperationHistoryHook._preventClearing = true; - ReactDOM.render(

, node); - - assertHistoryMatches([ - { - instanceID: inst._debugID, - type: 'insert child', - payload: {toIndex: 1, content: 'P'}, - }, - ]); - }); - }); - - describe('move child', () => { - it('gets reported when a child is inserted', () => { - var node = document.createElement('div'); - ReactDOM.render(

, node); - var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); - - ReactHostOperationHistoryHook._preventClearing = true; - ReactDOM.render(

, node); - - assertHistoryMatches([ - { - instanceID: inst._debugID, - type: 'move child', - payload: {fromIndex: 0, toIndex: 1}, - }, - ]); - }); - }); - - describe('remove child', () => { - it('gets reported when a child is removed', () => { - var node = document.createElement('div'); - ReactDOM.render(

, node); - var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); - - ReactHostOperationHistoryHook._preventClearing = true; - ReactDOM.render(
, node); - - assertHistoryMatches([ - { - instanceID: inst._debugID, - type: 'remove child', - payload: {fromIndex: 1}, - }, - ]); - }); - }); -}); diff --git a/src/renderers/__tests__/ReactMultiChild-test.js b/src/renderers/__tests__/ReactMultiChild-test.js index cdf104ba3b3..c8cd520d63f 100644 --- a/src/renderers/__tests__/ReactMultiChild-test.js +++ b/src/renderers/__tests__/ReactMultiChild-test.js @@ -18,13 +18,11 @@ describe('ReactMultiChild', () => { var React; var ReactDOM; - var ReactDOMFeatureFlags; beforeEach(() => { jest.resetModules(); React = require('react'); ReactDOM = require('react-dom'); - ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); }); describe('reconciliation', () => { @@ -282,8 +280,7 @@ describe('ReactMultiChild', () => { 'Warning: Using Maps as children is unsupported and will likely yield ' + 'unexpected results. Convert it to a sequence/iterable of keyed ' + 'ReactElements instead.\n' + - // Fiber gives a slightly better stack with the nearest host components - (ReactDOMFeatureFlags.useFiber ? ' in div (at **)\n' : '') + + ' in div (at **)\n' + ' in Parent (at **)', ); }); @@ -370,23 +367,12 @@ describe('ReactMultiChild', () => { 'oneA componentDidMount', 'twoA componentDidMount', - ...(ReactDOMFeatureFlags.useFiber - ? [ - 'oneB componentWillMount', - 'oneB render', - 'twoB componentWillMount', - 'twoB render', - 'oneA componentWillUnmount', - 'twoA componentWillUnmount', - ] - : [ - 'oneB componentWillMount', - 'oneB render', - 'oneA componentWillUnmount', - 'twoB componentWillMount', - 'twoB render', - 'twoA componentWillUnmount', - ]), + 'oneB componentWillMount', + 'oneB render', + 'twoB componentWillMount', + 'twoB render', + 'oneA componentWillUnmount', + 'twoA componentWillUnmount', 'oneB componentDidMount', 'twoB componentDidMount', diff --git a/src/renderers/__tests__/ReactMultiChildText-test.js b/src/renderers/__tests__/ReactMultiChildText-test.js index 53daf581353..e4fcca19a74 100644 --- a/src/renderers/__tests__/ReactMultiChildText-test.js +++ b/src/renderers/__tests__/ReactMultiChildText-test.js @@ -13,7 +13,6 @@ var React = require('react'); var ReactDOM = require('react-dom'); -var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); var ReactTestUtils = require('react-dom/test-utils'); // Helpers @@ -50,43 +49,16 @@ var expectChildren = function(container, children) { expect(textNode.data).toBe('' + children); } } else { - var openingCommentNode; - var closingCommentNode; var mountIndex = 0; for (var i = 0; i < children.length; i++) { var child = children[i]; if (typeof child === 'string') { - if (ReactDOMFeatureFlags.useFiber) { - textNode = outerNode.childNodes[mountIndex]; - expect(textNode.nodeType).toBe(3); - expect(textNode.data).toBe('' + child); - mountIndex++; - } else { - openingCommentNode = outerNode.childNodes[mountIndex]; - - expect(openingCommentNode.nodeType).toBe(8); - expect(openingCommentNode.nodeValue).toMatch(/ react-text: [0-9]+ /); - - if (child === '') { - textNode = null; - closingCommentNode = openingCommentNode.nextSibling; - mountIndex += 2; - } else { - textNode = openingCommentNode.nextSibling; - closingCommentNode = textNode.nextSibling; - mountIndex += 3; - } - - if (textNode) { - expect(textNode.nodeType).toBe(3); - expect(textNode.data).toBe('' + child); - } - - expect(closingCommentNode.nodeType).toBe(8); - expect(closingCommentNode.nodeValue).toBe(' /react-text '); - } + textNode = outerNode.childNodes[mountIndex]; + expect(textNode.nodeType).toBe(3); + expect(textNode.data).toBe('' + child); + mountIndex++; } else { var elementDOMNode = outerNode.childNodes[mountIndex]; expect(elementDOMNode.tagName).toBe('DIV'); @@ -191,20 +163,14 @@ describe('ReactMultiChildText', () => { [true,
{1.2}{''}{
}{'foo'}
, true, 1.2], [
, '1.2'], ['', 'foo',
{true}{
}{1.2}{''}
, 'foo'], ['', 'foo',
, 'foo'], ]); - if (ReactDOMFeatureFlags.useFiber) { - expectDev(console.error.calls.count()).toBe(2); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'Warning: Each child in an array or iterator should have a unique "key" prop.', - ); - expectDev(console.error.calls.argsFor(1)[0]).toContain( - 'Warning: Each child in an array or iterator should have a unique "key" prop.', - ); - } else { - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'Warning: Each child in an array or iterator should have a unique "key" prop.', - ); - } + + expectDev(console.error.calls.count()).toBe(2); + expectDev(console.error.calls.argsFor(0)[0]).toContain( + 'Warning: Each child in an array or iterator should have a unique "key" prop.', + ); + expectDev(console.error.calls.argsFor(1)[0]).toContain( + 'Warning: Each child in an array or iterator should have a unique "key" prop.', + ); }); it('should throw if rendering both HTML and children', () => { diff --git a/src/renderers/__tests__/ReactPerf-test.js b/src/renderers/__tests__/ReactPerf-test.js deleted file mode 100644 index 40d76b81207..00000000000 --- a/src/renderers/__tests__/ReactPerf-test.js +++ /dev/null @@ -1,746 +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. - * - * @emails react-core - */ - -'use strict'; - -var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); -var describeStack = ReactDOMFeatureFlags.useFiber ? describe.skip : describe; - -// ReactPerf is currently not supported on Fiber. -// Use browser timeline integration instead. -describeStack('ReactPerf', () => { - var React; - var ReactDOM; - var ReactPerf; - var ReactTestUtils; - var emptyFunction; - - var App; - var Box; - var Div; - var LifeCycle; - - beforeEach(() => { - var now = 0; - jest.setMock('fbjs/lib/performanceNow', function() { - return now++; - }); - - if (typeof console.table !== 'function') { - console.table = () => {}; - console.table.isFake = true; - } - - React = require('react'); - ReactDOM = require('react-dom'); - ReactPerf = require('ReactPerf'); - ReactTestUtils = require('react-dom/test-utils'); - emptyFunction = require('fbjs/lib/emptyFunction'); - - App = class extends React.Component { - render() { - return
; - } - }; - - Box = class extends React.Component { - render() { - return
; - } - }; - - // ReactPerf only measures composites, so we put everything in one. - Div = class extends React.Component { - render() { - return
; - } - }; - - LifeCycle = class extends React.Component { - shouldComponentUpdate = emptyFunction.thatReturnsTrue; - componentWillMount = emptyFunction; - componentDidMount = emptyFunction; - componentWillReceiveProps = emptyFunction; - componentWillUpdate = emptyFunction; - componentDidUpdate = emptyFunction; - componentWillUnmount = emptyFunction; - render = emptyFunction.thatReturnsNull; - }; - }); - - afterEach(() => { - if (console.table.isFake) { - delete console.table; - } - }); - - function measure(fn) { - ReactPerf.start(); - fn(); - ReactPerf.stop(); - - // Make sure none of the methods crash. - ReactPerf.getWasted(); - ReactPerf.getInclusive(); - ReactPerf.getExclusive(); - ReactPerf.getOperations(); - - return ReactPerf.getLastMeasurements(); - } - - it('should count no-op update as waste', () => { - var container = document.createElement('div'); - ReactDOM.render(, container); - var measurements = measure(() => { - ReactDOM.render(, container); - }); - - var summary = ReactPerf.getWasted(measurements); - expect(summary).toEqual([ - { - key: 'App', - instanceCount: 1, - inclusiveRenderDuration: 3, - renderCount: 1, - }, - { - key: 'App > Box', - instanceCount: 2, - inclusiveRenderDuration: 2, - renderCount: 2, - }, - ]); - }); - - it('should count no-op update in child as waste', () => { - var container = document.createElement('div'); - ReactDOM.render(, container); - - // Here, we add a Box -- two of the updates are wasted time (but the - // addition of the third is not) - var measurements = measure(() => { - ReactDOM.render(, container); - }); - - var summary = ReactPerf.getWasted(measurements); - expect(summary).toEqual([ - { - key: 'App > Box', - instanceCount: 1, - inclusiveRenderDuration: 1, - renderCount: 1, - }, - ]); - }); - - function expectNoWaste(fn) { - var measurements = measure(fn); - var summary = ReactPerf.getWasted(measurements); - expect(summary).toEqual([]); - } - - it('should not count initial render as waste', () => { - expectNoWaste(() => { - ReactTestUtils.renderIntoDocument(); - }); - }); - - it('should not count unmount as waste', () => { - var container = document.createElement('div'); - ReactDOM.render(
hello
, container); - expectNoWaste(() => { - ReactDOM.unmountComponentAtNode(container); - }); - }); - - it('should not count content update as waste', () => { - var container = document.createElement('div'); - ReactDOM.render(
hello
, container); - expectNoWaste(() => { - ReactDOM.render(
hello world
, container); - }); - }); - - it('should not count child addition as waste', () => { - var container = document.createElement('div'); - ReactDOM.render(
, container); - expectNoWaste(() => { - ReactDOM.render(
, container); - }); - }); - - it('should not count child removal as waste', () => { - var container = document.createElement('div'); - ReactDOM.render(
, container); - expectNoWaste(() => { - ReactDOM.render(
, container); - }); - }); - - it('should not count property update as waste', () => { - var container = document.createElement('div'); - ReactDOM.render(
hey
, container); - expectNoWaste(() => { - ReactDOM.render(
hey
, container); - }); - }); - - it('should not count style update as waste', () => { - var container = document.createElement('div'); - ReactDOM.render(
hey
, container); - expectNoWaste(() => { - ReactDOM.render(
hey
, container); - }); - }); - - it('should not count property removal as waste', () => { - var container = document.createElement('div'); - ReactDOM.render(
hey
, container); - expectNoWaste(() => { - ReactDOM.render(
hey
, container); - }); - }); - - it('should not count raw HTML update as waste', () => { - var container = document.createElement('div'); - ReactDOM.render( -
, - container, - ); - expectNoWaste(() => { - ReactDOM.render( -
, - container, - ); - }); - }); - - it('should not count child reordering as waste', () => { - var container = document.createElement('div'); - ReactDOM.render(
, container); - expectNoWaste(() => { - ReactDOM.render(
, container); - }); - }); - - it('should not count text update as waste', () => { - var container = document.createElement('div'); - ReactDOM.render(
{'hello'}{'world'}
, container); - expectNoWaste(() => { - ReactDOM.render(
{'hello'}{'friend'}
, container); - }); - }); - - it('should not count replacing null with a host as waste', () => { - var element = null; - function Foo() { - return element; - } - var container = document.createElement('div'); - ReactDOM.render(, container); - expectNoWaste(() => { - element =
; - ReactDOM.render(, container); - }); - }); - - it('should not count replacing a host with null as waste', () => { - var element =
; - function Foo() { - return element; - } - var container = document.createElement('div'); - ReactDOM.render(, container); - expectNoWaste(() => { - element = null; - ReactDOM.render(, container); - }); - }); - - it('should include stats for components unmounted during measurement', () => { - var container = document.createElement('div'); - var measurements = measure(() => { - ReactDOM.render(
, container); - ReactDOM.render(
, container); - }); - expect(ReactPerf.getExclusive(measurements)).toEqual([ - { - key: 'Div', - instanceCount: 3, - counts: {ctor: 3, render: 4}, - durations: {ctor: 3, render: 4}, - totalDuration: 7, - }, - ]); - }); - - it('should include lifecycle methods in measurements', () => { - var container = document.createElement('div'); - var measurements = measure(() => { - var instance = ReactDOM.render(, container); - ReactDOM.render(, container); - instance.setState({}); - ReactDOM.unmountComponentAtNode(container); - }); - expect(ReactPerf.getExclusive(measurements)).toEqual([ - { - key: 'LifeCycle', - instanceCount: 1, - totalDuration: 14, - counts: { - ctor: 1, - shouldComponentUpdate: 2, - componentWillMount: 1, - componentDidMount: 1, - componentWillReceiveProps: 1, - componentWillUpdate: 2, - componentDidUpdate: 2, - componentWillUnmount: 1, - render: 3, - }, - durations: { - ctor: 1, - shouldComponentUpdate: 2, - componentWillMount: 1, - componentDidMount: 1, - componentWillReceiveProps: 1, - componentWillUpdate: 2, - componentDidUpdate: 2, - componentWillUnmount: 1, - render: 3, - }, - }, - ]); - }); - - it('should include render time of functional components', () => { - function Foo() { - return null; - } - - var container = document.createElement('div'); - var measurements = measure(() => { - ReactDOM.render(, container); - }); - expect(ReactPerf.getExclusive(measurements)).toEqual([ - { - key: 'Foo', - instanceCount: 1, - totalDuration: 1, - counts: { - render: 1, - }, - durations: { - render: 1, - }, - }, - ]); - }); - - it('should not count time in a portal towards lifecycle method', () => { - function Foo() { - return null; - } - - var portalContainer = document.createElement('div'); - class Portal extends React.Component { - componentDidMount() { - ReactDOM.render(, portalContainer); - } - render() { - return null; - } - } - - var container = document.createElement('div'); - var measurements = measure(() => { - ReactDOM.render(, container); - }); - - expect(ReactPerf.getExclusive(measurements)).toEqual([ - { - key: 'Portal', - instanceCount: 1, - totalDuration: 6, - counts: { - ctor: 1, - componentDidMount: 1, - render: 1, - }, - durations: { - ctor: 1, - // We want to exclude nested imperative ReactDOM.render() from lifecycle hook's own time. - // Otherwise it would artificially float to the top even though its exclusive time is small. - // This is how we get 4 as a number with the performanceNow() mock: - // - we capture the time we enter componentDidMount (n = 0) - // - we capture the time when we enter a nested flush (n = 1) - // - in the nested flush, we call it twice: before and after rendering. (n = 3) - // - we capture the time when we exit a nested flush (n = 4) - // - we capture the time we exit componentDidMount (n = 5) - // Time spent in componentDidMount = (5 - 0 - (4 - 3)) = 4. - componentDidMount: 4, - render: 1, - }, - }, - { - key: 'Foo', - instanceCount: 1, - totalDuration: 1, - counts: { - render: 1, - }, - durations: { - render: 1, - }, - }, - ]); - }); - - it('warns once when using getMeasurementsSummaryMap', () => { - var measurements = measure(() => {}); - spyOn(console, 'warn'); - ReactPerf.getMeasurementsSummaryMap(measurements); - expectDev(console.warn.calls.count()).toBe(1); - expectDev(console.warn.calls.argsFor(0)[0]).toContain( - '`ReactPerf.getMeasurementsSummaryMap(...)` is deprecated. Use ' + - '`ReactPerf.getWasted(...)` instead.', - ); - - ReactPerf.getMeasurementsSummaryMap(measurements); - expectDev(console.warn.calls.count()).toBe(1); - }); - - it('warns once when using printDOM', () => { - var measurements = measure(() => {}); - spyOn(console, 'warn'); - ReactPerf.printDOM(measurements); - expectDev(console.warn.calls.count()).toBe(1); - expectDev(console.warn.calls.argsFor(0)[0]).toContain( - '`ReactPerf.printDOM(...)` is deprecated. Use ' + - '`ReactPerf.printOperations(...)` instead.', - ); - - ReactPerf.printDOM(measurements); - expectDev(console.warn.calls.count()).toBe(1); - }); - - it('returns isRunning state', () => { - expect(ReactPerf.isRunning()).toBe(false); - - ReactPerf.start(); - expect(ReactPerf.isRunning()).toBe(true); - - ReactPerf.stop(); - expect(ReactPerf.isRunning()).toBe(false); - }); - - it('start has no effect when already running', () => { - expect(ReactPerf.isRunning()).toBe(false); - - ReactPerf.start(); - expect(ReactPerf.isRunning()).toBe(true); - - ReactPerf.start(); - expect(ReactPerf.isRunning()).toBe(true); - - ReactPerf.stop(); - expect(ReactPerf.isRunning()).toBe(false); - }); - - it('stop has no effect when already stopped', () => { - expect(ReactPerf.isRunning()).toBe(false); - - ReactPerf.stop(); - expect(ReactPerf.isRunning()).toBe(false); - - ReactPerf.stop(); - expect(ReactPerf.isRunning()).toBe(false); - }); - - it('should print console error only once', () => { - __DEV__ = false; - - spyOn(console, 'error'); - - expect(ReactPerf.getLastMeasurements()).toEqual([]); - expect(ReactPerf.getExclusive()).toEqual([]); - expect(ReactPerf.getInclusive()).toEqual([]); - expect(ReactPerf.getWasted()).toEqual([]); - expect(ReactPerf.getOperations()).toEqual([]); - expect(ReactPerf.printExclusive()).toEqual(undefined); - expect(ReactPerf.printInclusive()).toEqual(undefined); - expect(ReactPerf.printWasted()).toEqual(undefined); - expect(ReactPerf.printOperations()).toEqual(undefined); - expect(ReactPerf.start()).toBe(undefined); - expect(ReactPerf.stop()).toBe(undefined); - expect(ReactPerf.isRunning()).toBe(false); - - expectDev(console.error.calls.count()).toBe(1); - - __DEV__ = true; - }); - - it('should work when measurement starts during reconciliation', () => { - // https://github.com/facebook/react/issues/6949#issuecomment-230371009 - class Measurer extends React.Component { - componentWillMount() { - ReactPerf.start(); - } - - componentDidMount() { - ReactPerf.stop(); - } - - componentWillUpdate() { - ReactPerf.start(); - } - - componentDidUpdate() { - ReactPerf.stop(); - } - - render() { - // Force reconciliation despite constant element - return React.cloneElement(this.props.children); - } - } - - var container = document.createElement('div'); - ReactDOM.render(, container); - expect(ReactPerf.getWasted()).toEqual([]); - - ReactDOM.render(, container); - expect(ReactPerf.getWasted()).toEqual([ - { - key: 'Measurer', - instanceCount: 1, - inclusiveRenderDuration: 4, - renderCount: 1, - }, - { - key: 'App', - instanceCount: 1, - inclusiveRenderDuration: 3, - renderCount: 1, - }, - { - key: 'App > Box', - instanceCount: 2, - inclusiveRenderDuration: 2, - renderCount: 2, - }, - ]); - }); - - it('should not print errant warnings if render() throws', () => { - var container = document.createElement('div'); - var thrownErr = new Error('Muhaha!'); - - class Evil extends React.Component { - render() { - throw thrownErr; - } - } - - ReactPerf.start(); - try { - ReactDOM.render( -
- - -
, - container, - ); - } catch (err) { - if (err !== thrownErr) { - throw err; - } - } - ReactPerf.stop(); - }); - - it('should not print errant warnings if componentWillMount() throws', () => { - var container = document.createElement('div'); - var thrownErr = new Error('Muhaha!'); - - class Evil extends React.Component { - componentWillMount() { - throw thrownErr; - } - render() { - return
; - } - } - - ReactPerf.start(); - try { - ReactDOM.render( -
- - -
, - container, - ); - } catch (err) { - if (err !== thrownErr) { - throw err; - } - } - ReactPerf.stop(); - }); - - it('should not print errant warnings if componentDidMount() throws', () => { - var container = document.createElement('div'); - var thrownErr = new Error('Muhaha!'); - - class Evil extends React.Component { - componentDidMount() { - throw thrownErr; - } - render() { - return
; - } - } - - ReactPerf.start(); - try { - ReactDOM.render( -
- - -
, - container, - ); - } catch (err) { - if (err !== thrownErr) { - throw err; - } - } - ReactPerf.stop(); - }); - - it('should not print errant warnings if portal throws in render()', () => { - var container = document.createElement('div'); - var thrownErr = new Error('Muhaha!'); - - class Evil extends React.Component { - render() { - throw thrownErr; - } - } - class EvilPortal extends React.Component { - componentDidMount() { - var portalContainer = document.createElement('div'); - ReactDOM.render(, portalContainer); - } - render() { - return
; - } - } - - ReactPerf.start(); - try { - ReactDOM.render( -
- - -
, - container, - ); - } catch (err) { - if (err !== thrownErr) { - throw err; - } - } - ReactDOM.unmountComponentAtNode(container); - ReactPerf.stop(); - }); - - it('should not print errant warnings if portal throws in componentWillMount()', () => { - var container = document.createElement('div'); - var thrownErr = new Error('Muhaha!'); - - class Evil extends React.Component { - componentWillMount() { - throw thrownErr; - } - render() { - return
; - } - } - class EvilPortal extends React.Component { - componentWillMount() { - var portalContainer = document.createElement('div'); - ReactDOM.render(, portalContainer); - } - render() { - return
; - } - } - - ReactPerf.start(); - try { - ReactDOM.render( -
- - -
, - container, - ); - } catch (err) { - if (err !== thrownErr) { - throw err; - } - } - ReactDOM.unmountComponentAtNode(container); - ReactPerf.stop(); - }); - - it('should not print errant warnings if portal throws in componentDidMount()', () => { - var container = document.createElement('div'); - var thrownErr = new Error('Muhaha!'); - - class Evil extends React.Component { - componentDidMount() { - throw thrownErr; - } - render() { - return
; - } - } - class EvilPortal extends React.Component { - componentDidMount() { - var portalContainer = document.createElement('div'); - ReactDOM.render(, portalContainer); - } - render() { - return
; - } - } - - ReactPerf.start(); - try { - ReactDOM.render( -
- - -
, - container, - ); - } catch (err) { - if (err !== thrownErr) { - throw err; - } - } - ReactDOM.unmountComponentAtNode(container); - ReactPerf.stop(); - }); -}); diff --git a/src/renderers/__tests__/ReactStatelessComponent-test.js b/src/renderers/__tests__/ReactStatelessComponent-test.js index f3eafc118ee..7df6fccca75 100644 --- a/src/renderers/__tests__/ReactStatelessComponent-test.js +++ b/src/renderers/__tests__/ReactStatelessComponent-test.js @@ -16,8 +16,6 @@ var React; var ReactDOM; var ReactTestUtils; -var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); - function StatelessComponent(props) { return
{props.name}
; } @@ -120,43 +118,13 @@ describe('ReactStatelessComponent', () => { ReactDOM.render(, container); - // Stack and Fiber differ in terms of they show warnings - if (ReactDOMFeatureFlags.useFiber) { - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'StatelessComponentWithChildContext(...): childContextTypes cannot ' + - 'be defined on a functional component.', - ); - } else { - expectDev(console.error.calls.count()).toBe(2); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'StatelessComponentWithChildContext(...): childContextTypes cannot ' + - 'be defined on a functional component.', - ); - expectDev(normalizeCodeLocInfo(console.error.calls.argsFor(1)[0])).toBe( - 'Warning: StatelessComponentWithChildContext.childContextTypes is specified ' + - 'but there is no getChildContext() method on the instance. You can either ' + - 'define getChildContext() on StatelessComponentWithChildContext or remove ' + - 'childContextTypes from it.', - ); - } + expectDev(console.error.calls.count()).toBe(1); + expectDev(console.error.calls.argsFor(0)[0]).toContain( + 'StatelessComponentWithChildContext(...): childContextTypes cannot ' + + 'be defined on a functional component.', + ); }); - if (!ReactDOMFeatureFlags.useFiber) { - // Stack doesn't support fragments - it('should throw when stateless component returns array', () => { - function NotAComponent() { - return [
,
]; - } - expect(function() { - ReactTestUtils.renderIntoDocument(
); - }).toThrowError( - 'NotAComponent(...): A valid React element (or null) must be returned. ' + - 'You may have returned undefined, an array or some other invalid object.', - ); - }); - } - it('should throw when stateless component returns undefined', () => { function NotAComponent() {} expect(function() { diff --git a/src/renderers/__tests__/ReactUpdates-test.js b/src/renderers/__tests__/ReactUpdates-test.js index 14fe5109d59..14c4d149503 100644 --- a/src/renderers/__tests__/ReactUpdates-test.js +++ b/src/renderers/__tests__/ReactUpdates-test.js @@ -14,13 +14,11 @@ var React; var ReactDOM; var ReactTestUtils; -var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); describe('ReactUpdates', () => { beforeEach(() => { React = require('react'); ReactDOM = require('react-dom'); - ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); ReactTestUtils = require('react-dom/test-utils'); }); @@ -521,12 +519,10 @@ describe('ReactUpdates', () => { render() { var portal = null; // If we're using Fiber, we use Portals instead to achieve this. - if (ReactDOMFeatureFlags.useFiber) { - portal = ReactDOM.unstable_createPortal( - (b = n)} />, - bContainer, - ); - } + portal = ReactDOM.unstable_createPortal( + (b = n)} />, + bContainer, + ); return
A{this.state.x}{portal}
; } } @@ -540,10 +536,6 @@ describe('ReactUpdates', () => { } a = ReactTestUtils.renderIntoDocument(); - if (!ReactDOMFeatureFlags.useFiber) { - ReactDOM.render( (b = n)} />, bContainer); - } - ReactDOM.unstable_batchedUpdates(function() { a.setState({x: 1}); b.setState({x: 1}); @@ -1159,35 +1151,33 @@ describe('ReactUpdates', () => { }).toThrow('Maximum'); }); - if (ReactDOMFeatureFlags.useFiber) { - it('does not fall into an infinite error loop', () => { - function BadRender() { - throw new Error('error'); - } + it('does not fall into an infinite error loop', () => { + function BadRender() { + throw new Error('error'); + } - class ErrorBoundary extends React.Component { - componentDidCatch() { - this.props.parent.remount(); - } - render() { - return ; - } + class ErrorBoundary extends React.Component { + componentDidCatch() { + this.props.parent.remount(); + } + render() { + return ; } + } - class NonTerminating extends React.Component { - state = {step: 0}; - remount() { - this.setState(state => ({step: state.step + 1})); - } - render() { - return ; - } + class NonTerminating extends React.Component { + state = {step: 0}; + remount() { + this.setState(state => ({step: state.step + 1})); } + render() { + return ; + } + } - const container = document.createElement('div'); - expect(() => { - ReactDOM.render(, container); - }).toThrow('Maximum'); - }); - } + const container = document.createElement('div'); + expect(() => { + ReactDOM.render(, container); + }).toThrow('Maximum'); + }); }); diff --git a/src/renderers/__tests__/multiple-copies-of-react-test.js b/src/renderers/__tests__/multiple-copies-of-react-test.js index 5151a54dac2..9eb35db6aad 100644 --- a/src/renderers/__tests__/multiple-copies-of-react-test.js +++ b/src/renderers/__tests__/multiple-copies-of-react-test.js @@ -12,7 +12,6 @@ 'use strict'; let React = require('react'); -var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); var ReactTestUtils = require('react-dom/test-utils'); class TextWithStringRef extends React.Component { @@ -29,23 +28,12 @@ class TextWithStringRef extends React.Component { describe('when different React version is used with string ref', () => { it('throws the "Refs must have owner" warning', () => { - if (ReactDOMFeatureFlags.useFiber) { - expect(() => { - ReactTestUtils.renderIntoDocument(); - }).toThrow( - 'Element ref was specified as a string (foo) but no owner was set.' + - ' You may have multiple copies of React loaded. (details: ' + - 'https://fb.me/react-refs-must-have-owner).', - ); - } else { - expect(() => { - ReactTestUtils.renderIntoDocument(); - }).toThrow( - 'Only a ReactOwner can have refs. You might be adding a ref to a ' + - "component that was not created inside a component's `render` " + - 'method, or you have multiple copies of React loaded ' + - '(details: https://fb.me/react-refs-must-have-owner)', - ); - } + expect(() => { + ReactTestUtils.renderIntoDocument(); + }).toThrow( + 'Element ref was specified as a string (foo) but no owner was set.' + + ' You may have multiple copies of React loaded. (details: ' + + 'https://fb.me/react-refs-must-have-owner).', + ); }); }); diff --git a/src/renderers/__tests__/refs-test.js b/src/renderers/__tests__/refs-test.js index 6f16f2fd34e..cbed01652f8 100644 --- a/src/renderers/__tests__/refs-test.js +++ b/src/renderers/__tests__/refs-test.js @@ -12,7 +12,6 @@ 'use strict'; var React = require('react'); -var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); var ReactTestUtils = require('react-dom/test-utils'); /** @@ -308,91 +307,6 @@ describe('ref swapping', () => { }); }); -describe('string refs between fiber and stack', () => { - beforeEach(() => { - jest.resetModules(); - React = require('react'); - ReactTestUtils = require('react-dom/test-utils'); - }); - - it('attaches, detaches from fiber component with stack layer', () => { - const ReactCurrentOwner = require('ReactCurrentOwner'); - - const ReactDOMStack = require('ReactDOMStackEntry'); - const ReactDOMFiber = require('ReactDOMFiberEntry'); - const ReactInstanceMap = require('ReactInstanceMap'); - let layerMounted = false; - class A extends React.Component { - render() { - return
; - } - componentDidMount() { - // ReactLayeredComponentMixin sets ReactCurrentOwner manually - ReactCurrentOwner.current = ReactInstanceMap.get(this); - const span = ; - ReactCurrentOwner.current = null; - - ReactDOMStack.unstable_renderSubtreeIntoContainer( - this, - span, - (this._container = document.createElement('div')), - () => { - expect(this.refs.span.nodeName).toBe('SPAN'); - layerMounted = true; - }, - ); - } - componentWillUnmount() { - ReactDOMStack.unmountComponentAtNode(this._container); - } - } - const container = document.createElement('div'); - const a = ReactDOMFiber.render(, container); - expect(a.refs.span).toBeTruthy(); - ReactDOMFiber.unmountComponentAtNode(container); - expect(a.refs.span).toBe(undefined); - expect(layerMounted).toBe(true); - }); - - it('attaches, detaches from stack component with fiber layer', () => { - const ReactCurrentOwner = require('ReactCurrentOwner'); - const ReactDOM = require('ReactDOMStackEntry'); - const ReactDOMFiber = require('ReactDOMFiberEntry'); - const ReactInstanceMap = require('ReactInstanceMap'); - let layerMounted = false; - class A extends React.Component { - render() { - return
; - } - componentDidMount() { - // ReactLayeredComponentMixin sets ReactCurrentOwner manually - ReactCurrentOwner.current = ReactInstanceMap.get(this); - const span = ; - ReactCurrentOwner.current = null; - - ReactDOMFiber.unstable_renderSubtreeIntoContainer( - this, - span, - (this._container = document.createElement('div')), - () => { - expect(this.refs.span.nodeName).toBe('SPAN'); - layerMounted = true; - }, - ); - } - componentWillUnmount() { - ReactDOMFiber.unmountComponentAtNode(this._container); - } - } - const container = document.createElement('div'); - const a = ReactDOM.render(, container); - expect(a.refs.span).toBeTruthy(); - ReactDOM.unmountComponentAtNode(container); - expect(a.refs.span).toBe(undefined); - expect(layerMounted).toBe(true); - }); -}); - describe('root level refs', () => { beforeEach(() => { var ReactFeatureFlags = require('ReactFeatureFlags'); @@ -440,40 +354,38 @@ describe('root level refs', () => { expect(ref).toHaveBeenCalledTimes(2); expect(ref.mock.calls[1][0]).toBe(null); - if (ReactDOMFeatureFlags.useFiber) { - // fragment - inst = null; - ref = jest.fn(value => (inst = value)); - var divInst = null; - var ref2 = jest.fn(value => (divInst = value)); - result = ReactDOM.render( - [, 5,
Hello
], - container, - ); + // fragment + inst = null; + ref = jest.fn(value => (inst = value)); + var divInst = null; + var ref2 = jest.fn(value => (divInst = value)); + result = ReactDOM.render( + [, 5,
Hello
], + container, + ); - // first call should be `Comp` - expect(ref).toHaveBeenCalledTimes(1); - expect(ref.mock.calls[0][0]).toBeInstanceOf(Comp); - expect(result).toBe(ref.mock.calls[0][0]); + // first call should be `Comp` + expect(ref).toHaveBeenCalledTimes(1); + expect(ref.mock.calls[0][0]).toBeInstanceOf(Comp); + expect(result).toBe(ref.mock.calls[0][0]); - expect(ref2).toHaveBeenCalledTimes(1); - expect(divInst).toBeInstanceOf(HTMLDivElement); - expect(result).not.toBe(divInst); + expect(ref2).toHaveBeenCalledTimes(1); + expect(divInst).toBeInstanceOf(HTMLDivElement); + expect(result).not.toBe(divInst); - ReactDOM.unmountComponentAtNode(container); - expect(ref).toHaveBeenCalledTimes(2); - expect(ref.mock.calls[1][0]).toBe(null); - expect(ref2).toHaveBeenCalledTimes(2); - expect(ref2.mock.calls[1][0]).toBe(null); + ReactDOM.unmountComponentAtNode(container); + expect(ref).toHaveBeenCalledTimes(2); + expect(ref.mock.calls[1][0]).toBe(null); + expect(ref2).toHaveBeenCalledTimes(2); + expect(ref2.mock.calls[1][0]).toBe(null); - // null - result = ReactDOM.render(null, container); - expect(result).toBe(null); + // null + result = ReactDOM.render(null, container); + expect(result).toBe(null); - // primitives - result = ReactDOM.render(5, container); - expect(result).toBeInstanceOf(Text); - } + // primitives + result = ReactDOM.render(5, container); + expect(result).toBeInstanceOf(Text); }); }); @@ -489,28 +401,6 @@ describe('creating element with ref in constructor', () => { } } - var devErrorMessage = - 'addComponentAsRefTo(...): Only a ReactOwner can have refs. You might ' + - "be adding a ref to a component that was not created inside a component's " + - '`render` method, or you have multiple copies of React loaded ' + - '(details: https://fb.me/react-refs-must-have-owner).'; - - var prodErrorMessage = - 'Minified React error #119; visit ' + - 'http://facebook.github.io/react/docs/error-decoder.html?invariant=119 for the full message ' + - 'or use the non-minified dev environment for full errors and additional helpful warnings.'; - - var fiberDevErrorMessage = - 'Element ref was specified as a string (p) but no owner was ' + - 'set. You may have multiple copies of React loaded. ' + - '(details: https://fb.me/react-refs-must-have-owner).'; - - var fiberProdErrorMessage = - 'Minified React error #149; visit ' + - 'http://facebook.github.io/react/docs/error-decoder.html?invariant=149&args[]=p ' + - 'for the full message or use the non-minified dev environment for full errors and additional ' + - 'helpful warnings.'; - it('throws an error when __DEV__ = true', () => { ReactTestUtils = require('react-dom/test-utils'); @@ -521,7 +411,9 @@ describe('creating element with ref in constructor', () => { expect(function() { ReactTestUtils.renderIntoDocument(); }).toThrowError( - ReactDOMFeatureFlags.useFiber ? fiberDevErrorMessage : devErrorMessage, + 'Element ref was specified as a string (p) but no owner was ' + + 'set. You may have multiple copies of React loaded. ' + + '(details: https://fb.me/react-refs-must-have-owner).', ); } finally { __DEV__ = originalDev; @@ -538,9 +430,10 @@ describe('creating element with ref in constructor', () => { expect(function() { ReactTestUtils.renderIntoDocument(); }).toThrowError( - ReactDOMFeatureFlags.useFiber - ? fiberProdErrorMessage - : prodErrorMessage, + 'Minified React error #149; visit ' + + 'http://facebook.github.io/react/docs/error-decoder.html?invariant=149&args[]=p ' + + 'for the full message or use the non-minified dev environment for full errors and additional ' + + 'helpful warnings.', ); } finally { __DEV__ = originalDev; diff --git a/src/renderers/art/ReactARTFiberEntry.js b/src/renderers/art/ReactARTFiberEntry.js index 0f4086d6fab..db9d5d3d029 100644 --- a/src/renderers/art/ReactARTFiberEntry.js +++ b/src/renderers/art/ReactARTFiberEntry.js @@ -10,19 +10,20 @@ */ 'use strict'; -require('art/modes/current').setCurrent( +import Mode from 'art/modes/current'; +import fastNoSideEffectsMode from 'art/modes/fast-noSideEffects'; +import Transform from 'art/core/transform'; +import invariant from 'fbjs/lib/invariant'; +import emptyObject from 'fbjs/lib/emptyObject'; +import React from 'react'; +import ReactFiberReconciler from 'ReactFiberReconciler'; +import {rIC} from 'ReactDOMFrameScheduling'; + +Mode.setCurrent( // Change to 'art/modes/dom' for easier debugging via SVG - require('art/modes/fast-noSideEffects'), + fastNoSideEffectsMode, ); -const Mode = require('art/modes/current'); -const Transform = require('art/core/transform'); -const invariant = require('fbjs/lib/invariant'); -const emptyObject = require('fbjs/lib/emptyObject'); -const React = require('react'); -const ReactFiberReconciler = require('ReactFiberReconciler'); -const ReactDOMFrameScheduling = require('ReactDOMFrameScheduling'); - const {Component} = React; const pooledTransform = new Transform(); @@ -526,7 +527,7 @@ const ARTRenderer = ReactFiberReconciler({ return emptyObject; }, - scheduleDeferredCallback: ReactDOMFrameScheduling.rIC, + scheduleDeferredCallback: rIC, shouldSetTextContent(type, props) { return ( @@ -539,7 +540,9 @@ const ARTRenderer = ReactFiberReconciler({ /** API */ -module.exports = { +// TODO: this fixes Rollup build but probably breaks Jest. +// Need to figure something out. +export default { ClippingRectangle: TYPES.CLIPPING_RECTANGLE, Group: TYPES.GROUP, LinearGradient, diff --git a/src/renderers/dom/ReactDOMNodeStreamRenderer.js b/src/renderers/dom/ReactDOMNodeStreamRenderer.js index 7a54db65317..0e208b9b2c9 100644 --- a/src/renderers/dom/ReactDOMNodeStreamRenderer.js +++ b/src/renderers/dom/ReactDOMNodeStreamRenderer.js @@ -11,12 +11,12 @@ 'use strict'; -var invariant = require('fbjs/lib/invariant'); -var React = require('react'); -var ReactPartialRenderer = require('ReactPartialRenderer'); -var ReactFeatureFlags = require('ReactFeatureFlags'); +import invariant from 'fbjs/lib/invariant'; +import React from 'react'; +import ReactPartialRenderer from 'ReactPartialRenderer'; +import ReactFeatureFlags from 'ReactFeatureFlags'; -var Readable = require('stream').Readable; +import {Readable} from 'stream'; // This is a Readable Node.js stream which wraps the ReactDOMPartialRenderer. class ReactMarkupReadableStream extends Readable { @@ -40,7 +40,7 @@ class ReactMarkupReadableStream extends Readable { * server. * See https://facebook.github.io/react/docs/react-dom-stream.html#rendertonodestream */ -function renderToNodeStream(element) { +export function renderToNodeStream(element) { const disableNewFiberFeatures = ReactFeatureFlags.disableNewFiberFeatures; if (disableNewFiberFeatures) { invariant( @@ -56,7 +56,7 @@ function renderToNodeStream(element) { * such as data-react-id that React uses internally. * See https://facebook.github.io/react/docs/react-dom-stream.html#rendertostaticnodestream */ -function renderToStaticNodeStream(element) { +export function renderToStaticNodeStream(element) { const disableNewFiberFeatures = ReactFeatureFlags.disableNewFiberFeatures; if (disableNewFiberFeatures) { invariant( @@ -66,8 +66,3 @@ function renderToStaticNodeStream(element) { } return new ReactMarkupReadableStream(element, true); } - -module.exports = { - renderToNodeStream: renderToNodeStream, - renderToStaticNodeStream: renderToStaticNodeStream, -}; diff --git a/src/renderers/dom/ReactDOMServerBrowserEntry.js b/src/renderers/dom/ReactDOMServerBrowserEntry.js index 0b1ecfefa3b..6ffef37f415 100644 --- a/src/renderers/dom/ReactDOMServerBrowserEntry.js +++ b/src/renderers/dom/ReactDOMServerBrowserEntry.js @@ -11,15 +11,16 @@ 'use strict'; -var ReactDOMStringRenderer = require('ReactDOMStringRenderer'); -var ReactVersion = require('ReactVersion'); -var invariant = require('fbjs/lib/invariant'); +import 'ReactDOMInjection'; +import {renderToString, renderToStaticMarkup} from 'ReactDOMStringRenderer'; +import ReactVersion from 'ReactVersion'; +import invariant from 'fbjs/lib/invariant'; -require('ReactDOMInjection'); - -module.exports = { - renderToString: ReactDOMStringRenderer.renderToString, - renderToStaticMarkup: ReactDOMStringRenderer.renderToStaticMarkup, +// TODO: this fixes Rollup build but probably breaks Jest. +// Need to figure something out. +export default { + renderToString, + renderToStaticMarkup, renderToNodeStream() { invariant( false, diff --git a/src/renderers/dom/ReactDOMServerNodeEntry.js b/src/renderers/dom/ReactDOMServerNodeEntry.js index 1996b57ee54..6087b7d9f72 100644 --- a/src/renderers/dom/ReactDOMServerNodeEntry.js +++ b/src/renderers/dom/ReactDOMServerNodeEntry.js @@ -11,16 +11,20 @@ 'use strict'; -var ReactDOMStringRenderer = require('ReactDOMStringRenderer'); -var ReactDOMNodeStreamRenderer = require('ReactDOMNodeStreamRenderer'); -var ReactVersion = require('ReactVersion'); +import 'ReactDOMInjection'; +import {renderToString, renderToStaticMarkup} from 'ReactDOMStringRenderer'; +import { + renderToNodeStream, + renderToStaticNodeStream, +} from 'ReactDOMNodeStreamRenderer'; +import ReactVersion from 'ReactVersion'; -require('ReactDOMInjection'); - -module.exports = { - renderToString: ReactDOMStringRenderer.renderToString, - renderToStaticMarkup: ReactDOMStringRenderer.renderToStaticMarkup, - renderToNodeStream: ReactDOMNodeStreamRenderer.renderToNodeStream, - renderToStaticNodeStream: ReactDOMNodeStreamRenderer.renderToStaticNodeStream, +// TODO: this fixes Rollup build but probably breaks Jest. +// Need to figure something out. +export default { + renderToString, + renderToStaticMarkup, + renderToNodeStream, + renderToStaticNodeStream, version: ReactVersion, }; diff --git a/src/renderers/dom/ReactDOMServerStackEntry.js b/src/renderers/dom/ReactDOMServerStackEntry.js deleted file mode 100644 index 67ca724da8f..00000000000 --- a/src/renderers/dom/ReactDOMServerStackEntry.js +++ /dev/null @@ -1,37 +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 ReactDOMServerStackEntry - */ - -'use strict'; - -var ReactServerRendering = require('ReactServerRendering'); -var ReactVersion = require('ReactVersion'); - -require('ReactDOMInjection'); -require('ReactDOMStackInjection'); - -var ReactDOMServerStack = { - renderToString: ReactServerRendering.renderToString, - renderToStaticMarkup: ReactServerRendering.renderToStaticMarkup, - version: ReactVersion, -}; - -if (__DEV__) { - var ReactInstrumentation = require('ReactInstrumentation'); - var ReactDOMUnknownPropertyHook = require('ReactDOMUnknownPropertyHook'); - var ReactDOMNullInputValuePropHook = require('ReactDOMNullInputValuePropHook'); - var ReactDOMInvalidARIAHook = require('ReactDOMInvalidARIAHook'); - - ReactInstrumentation.debugTool.addHook(ReactDOMUnknownPropertyHook); - ReactInstrumentation.debugTool.addHook(ReactDOMNullInputValuePropHook); - ReactInstrumentation.debugTool.addHook(ReactDOMInvalidARIAHook); -} - -module.exports = ReactDOMServerStack; diff --git a/src/renderers/dom/ReactDOMStackEntry.js b/src/renderers/dom/ReactDOMStackEntry.js deleted file mode 100644 index fe12a60eb07..00000000000 --- a/src/renderers/dom/ReactDOMStackEntry.js +++ /dev/null @@ -1,170 +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 ReactDOMStackEntry - */ - -/* globals __REACT_DEVTOOLS_GLOBAL_HOOK__*/ - -'use strict'; - -require('checkReact'); -var ReactDOMComponentTree = require('ReactDOMComponentTree'); -var ReactGenericBatching = require('ReactGenericBatching'); -var ReactMount = require('ReactMount'); -var ReactUpdates = require('ReactUpdates'); -var ReactReconciler = require('ReactReconciler'); -var ReactVersion = require('ReactVersion'); - -var findDOMNode = require('findDOMNode'); -var getHostComponentFromComposite = require('getHostComponentFromComposite'); - -if (__DEV__) { - var warning = require('fbjs/lib/warning'); -} - -require('ReactDOMInjection'); -require('ReactDOMClientInjection'); -require('ReactDOMStackInjection'); - -var ReactDOMStack = { - findDOMNode: findDOMNode, - render: ReactMount.render, - unmountComponentAtNode: ReactMount.unmountComponentAtNode, - version: ReactVersion, - - /* eslint-disable camelcase */ - unstable_batchedUpdates: ReactGenericBatching.batchedUpdates, - unstable_renderSubtreeIntoContainer: ReactMount.renderSubtreeIntoContainer, - /* eslint-enable camelcase */ - - __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, - ReactDOMEventListener: require('ReactDOMEventListener'), - ReactUpdates: ReactUpdates, - }, -}; - -// Inject the runtime into a devtools global hook regardless of browser. -// Allows for debugging when the hook is injected on the page. -if ( - typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ !== 'undefined' && - typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.inject === 'function' -) { - __REACT_DEVTOOLS_GLOBAL_HOOK__.inject({ - ComponentTree: { - getClosestInstanceFromNode: ReactDOMComponentTree.getClosestInstanceFromNode, - getNodeFromInstance: function(inst) { - // inst is an internal instance (but could be a composite) - if (inst._renderedComponent) { - inst = getHostComponentFromComposite(inst); - } - if (inst) { - return ReactDOMComponentTree.getNodeFromInstance(inst); - } else { - return null; - } - }, - }, - Mount: ReactMount, - Reconciler: ReactReconciler, - rendererPackageName: 'react-dom', - }); -} - -if (__DEV__) { - var ExecutionEnvironment = require('fbjs/lib/ExecutionEnvironment'); - if (ExecutionEnvironment.canUseDOM && window.top === window.self) { - // First check if devtools is not installed - if (typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ === 'undefined') { - // If we're in Chrome or Firefox, provide a download link if not installed. - if ( - (navigator.userAgent.indexOf('Chrome') > -1 && - navigator.userAgent.indexOf('Edge') === -1) || - navigator.userAgent.indexOf('Firefox') > -1 - ) { - // Firefox does not have the issue with devtools loaded over file:// - var showFileUrlMessage = - window.location.protocol.indexOf('http') === -1 && - navigator.userAgent.indexOf('Firefox') === -1; - console.debug( - 'Download the React DevTools ' + - (showFileUrlMessage - ? 'and use an HTTP server (instead of a file: URL) ' - : '') + - 'for a better development experience: ' + - 'https://fb.me/react-devtools', - ); - } - } - - var testFunc = function testFn() {}; - warning( - (testFunc.name || testFunc.toString()).indexOf('testFn') !== -1, - "It looks like you're using a minified copy of the development build " + - 'of React. When deploying React apps to production, make sure to use ' + - 'the production build which skips development warnings and is faster. ' + - 'See https://fb.me/react-minification for more details.', - ); - - // If we're in IE8, check to see if we are in compatibility mode and provide - // information on preventing compatibility mode - var ieCompatibilityMode = - document.documentMode && document.documentMode < 8; - - warning( - !ieCompatibilityMode, - 'Internet Explorer is running in compatibility mode; please add the ' + - 'following tag to your HTML to prevent this from happening: ' + - '', - ); - - var expectedFeatures = [ - // shims - Array.isArray, - Array.prototype.every, - Array.prototype.forEach, - Array.prototype.indexOf, - Array.prototype.map, - Date.now, - Function.prototype.bind, - Object.keys, - String.prototype.trim, - ]; - - for (var i = 0; i < expectedFeatures.length; i++) { - if (!expectedFeatures[i]) { - warning( - false, - 'One or more ES5 shims expected by React are not available: ' + - 'https://fb.me/react-warning-polyfills', - ); - break; - } - } - } -} - -if (__DEV__) { - var ReactInstrumentation = require('ReactInstrumentation'); - var ReactDOMUnknownPropertyHook = require('ReactDOMUnknownPropertyHook'); - var ReactDOMNullInputValuePropHook = require('ReactDOMNullInputValuePropHook'); - var ReactDOMInvalidARIAHook = require('ReactDOMInvalidARIAHook'); - - ReactInstrumentation.debugTool.addHook(ReactDOMUnknownPropertyHook); - ReactInstrumentation.debugTool.addHook(ReactDOMNullInputValuePropHook); - ReactInstrumentation.debugTool.addHook(ReactDOMInvalidARIAHook); -} - -module.exports = ReactDOMStack; diff --git a/src/renderers/dom/ReactDOMStringRenderer.js b/src/renderers/dom/ReactDOMStringRenderer.js index 4374e29721d..e392ddc69eb 100644 --- a/src/renderers/dom/ReactDOMStringRenderer.js +++ b/src/renderers/dom/ReactDOMStringRenderer.js @@ -11,17 +11,17 @@ 'use strict'; -var invariant = require('fbjs/lib/invariant'); -var React = require('react'); -var ReactPartialRenderer = require('ReactPartialRenderer'); -var ReactFeatureFlags = require('ReactFeatureFlags'); +import invariant from 'fbjs/lib/invariant'; +import React from 'react'; +import ReactPartialRenderer from 'ReactPartialRenderer'; +import ReactFeatureFlags from 'ReactFeatureFlags'; /** * Render a ReactElement to its initial HTML. This should only be used on the * server. * See https://facebook.github.io/react/docs/react-dom-server.html#rendertostring */ -function renderToString(element) { +export function renderToString(element) { const disableNewFiberFeatures = ReactFeatureFlags.disableNewFiberFeatures; if (disableNewFiberFeatures) { invariant( @@ -39,7 +39,7 @@ function renderToString(element) { * such as data-react-id that React uses internally. * See https://facebook.github.io/react/docs/react-dom-server.html#rendertostaticmarkup */ -function renderToStaticMarkup(element) { +export function renderToStaticMarkup(element) { const disableNewFiberFeatures = ReactFeatureFlags.disableNewFiberFeatures; if (disableNewFiberFeatures) { invariant( @@ -51,8 +51,3 @@ function renderToStaticMarkup(element) { var markup = renderer.read(Infinity); return markup; } - -module.exports = { - renderToString: renderToString, - renderToStaticMarkup: renderToStaticMarkup, -}; diff --git a/src/renderers/dom/__tests__/ReactDOMProduction-test.js b/src/renderers/dom/__tests__/ReactDOMProduction-test.js index d4fab65a45d..1c33de7dca0 100644 --- a/src/renderers/dom/__tests__/ReactDOMProduction-test.js +++ b/src/renderers/dom/__tests__/ReactDOMProduction-test.js @@ -11,8 +11,6 @@ 'use strict'; describe('ReactDOMProduction', () => { - var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); - var React; var ReactDOM; var ReactDOMServer; @@ -236,60 +234,58 @@ describe('ReactDOMProduction', () => { } }); - if (ReactDOMFeatureFlags.useFiber) { - // This test is originally from ReactDOMFiber-test but we replicate it here - // to avoid production-only regressions because of host context differences - // in dev and prod. - it('should keep track of namespace across portals in production', () => { - var svgEls, htmlEls; - var expectSVG = {ref: el => svgEls.push(el)}; - var expectHTML = {ref: el => htmlEls.push(el)}; - var usePortal = function(tree) { - return ReactDOM.unstable_createPortal( - tree, - document.createElement('div'), - ); - }; - var assertNamespacesMatch = function(tree) { - var container = document.createElement('div'); - svgEls = []; - htmlEls = []; - ReactDOM.render(tree, container); - svgEls.forEach(el => { - expect(el.namespaceURI).toBe('http://www.w3.org/2000/svg'); - }); - htmlEls.forEach(el => { - expect(el.namespaceURI).toBe('http://www.w3.org/1999/xhtml'); - }); - ReactDOM.unmountComponentAtNode(container); - expect(container.innerHTML).toBe(''); - }; + // This test is originally from ReactDOMFiber-test but we replicate it here + // to avoid production-only regressions because of host context differences + // in dev and prod. + it('should keep track of namespace across portals in production', () => { + var svgEls, htmlEls; + var expectSVG = {ref: el => svgEls.push(el)}; + var expectHTML = {ref: el => htmlEls.push(el)}; + var usePortal = function(tree) { + return ReactDOM.unstable_createPortal( + tree, + document.createElement('div'), + ); + }; + var assertNamespacesMatch = function(tree) { + var container = document.createElement('div'); + svgEls = []; + htmlEls = []; + ReactDOM.render(tree, container); + svgEls.forEach(el => { + expect(el.namespaceURI).toBe('http://www.w3.org/2000/svg'); + }); + htmlEls.forEach(el => { + expect(el.namespaceURI).toBe('http://www.w3.org/1999/xhtml'); + }); + ReactDOM.unmountComponentAtNode(container); + expect(container.innerHTML).toBe(''); + }; - assertNamespacesMatch( -
- - -

- {usePortal( + assertNamespacesMatch( +

+ + +

+ {usePortal( + + - - - -

- - {usePortal(

)} - - - , - )} -

- - - -

- , - ); - }); - } + +

+ + {usePortal(

)} + + + , + )} +

+ + + +

+ , + ); + }); }); diff --git a/src/renderers/dom/fiber/ReactDOMFiberComponent.js b/src/renderers/dom/fiber/ReactDOMFiberComponent.js index 475960cdf7e..f9c685f860e 100644 --- a/src/renderers/dom/fiber/ReactDOMFiberComponent.js +++ b/src/renderers/dom/fiber/ReactDOMFiberComponent.js @@ -12,55 +12,82 @@ 'use strict'; -var CSSPropertyOperations = require('CSSPropertyOperations'); -var DOMNamespaces = require('DOMNamespaces'); -var DOMProperty = require('DOMProperty'); -var DOMPropertyOperations = require('DOMPropertyOperations'); -var EventPluginRegistry = require('EventPluginRegistry'); -var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter'); -var ReactDOMFiberInput = require('ReactDOMFiberInput'); -var ReactDOMFiberOption = require('ReactDOMFiberOption'); -var ReactDOMFiberSelect = require('ReactDOMFiberSelect'); -var ReactDOMFiberTextarea = require('ReactDOMFiberTextarea'); -var {getCurrentFiberOwnerName} = require('ReactDebugCurrentFiber'); -var {DOCUMENT_NODE, DOCUMENT_FRAGMENT_NODE} = require('HTMLNodeType'); +import { + setValueForStyles, + createDangerousStringForStyles, +} from 'CSSPropertyOperations'; +import {Namespaces, getIntrinsicNamespace} from 'DOMNamespaces'; +import {shouldSetAttribute, getPropertyInfo} from 'DOMProperty'; +import { + deleteValueForAttribute, + deleteValueForProperty, + getValueForAttribute, + getValueForProperty, + setValueForAttribute, + setValueForProperty, +} from 'DOMPropertyOperations'; +import {registrationNameModules} from 'EventPluginRegistry'; +import {listenTo, trapBubbledEvent} from 'ReactBrowserEventEmitter'; +import { + getHostProps as getHostPropsForInput, + initWrapperState as initWrapperStateForInput, + postMountWrapper as postMountWrapperForInput, + restoreControlledState as restoreControlledStateForInput, + updateWrapper as updateWrapperForInput, +} from 'ReactDOMFiberInput'; +import { + getHostProps as getHostPropsForOption, + postMountWrapper as postMountWrapperForOption, + validateProps as validatePropsForOption, +} from 'ReactDOMFiberOption'; +import { + getHostProps as getHostPropsForSelect, + initWrapperState as initWrapperStateForSelect, + postMountWrapper as postMountWrapperForSelect, + postUpdateWrapper as postUpdateWrapperForSelect, + restoreControlledState as restoreControlledStateForSelect, +} from 'ReactDOMFiberSelect'; +import { + initWrapperState as initWrapperStateForTextarea, + getHostProps as getHostPropsForTextarea, + postMountWrapper as postMountWrapperForTextarea, + restoreControlledState as restoreControlledStateForTextarea, + updateWrapper as updateWrapperForTextarea, +} from 'ReactDOMFiberTextarea'; +import {getCurrentFiberOwnerName} from 'ReactDebugCurrentFiber'; +import {DOCUMENT_NODE, DOCUMENT_FRAGMENT_NODE} from 'HTMLNodeType'; +import assertValidProps from 'assertValidProps'; +import emptyFunction from 'fbjs/lib/emptyFunction'; +import { + track as trackInputValue, + updateValueIfChanged as updateInputValueIfChanged, +} from 'inputValueTracking'; +import isCustomComponent from 'isCustomComponent'; +import setInnerHTML from 'setInnerHTML'; +import setTextContent from 'setTextContent'; +import warning from 'fbjs/lib/warning'; +import { + validateProperties as validateARIAProperties, +} from 'ReactDOMInvalidARIAHook'; +import { + validateProperties as validateInputPropertes, +} from 'ReactDOMNullInputValuePropHook'; +import { + validateProperties as validateUnknownPropertes, +} from 'ReactDOMUnknownPropertyHook'; +import {getCurrentFiberStackAddendum} from 'ReactDebugCurrentFiber'; -var assertValidProps = require('assertValidProps'); -var emptyFunction = require('fbjs/lib/emptyFunction'); -var inputValueTracking = require('inputValueTracking'); -var isCustomComponent = require('isCustomComponent'); -var setInnerHTML = require('setInnerHTML'); -var setTextContent = require('setTextContent'); - -if (__DEV__) { - var warning = require('fbjs/lib/warning'); - var {getCurrentFiberStackAddendum} = require('ReactDebugCurrentFiber'); - var ReactDOMInvalidARIAHook = require('ReactDOMInvalidARIAHook'); - var ReactDOMNullInputValuePropHook = require('ReactDOMNullInputValuePropHook'); - var ReactDOMUnknownPropertyHook = require('ReactDOMUnknownPropertyHook'); - var {validateProperties: validateARIAProperties} = ReactDOMInvalidARIAHook; - var { - validateProperties: validateInputPropertes, - } = ReactDOMNullInputValuePropHook; - var { - validateProperties: validateUnknownPropertes, - } = ReactDOMUnknownPropertyHook; -} +var {html: HTML_NAMESPACE} = Namespaces; var didWarnInvalidHydration = false; var didWarnShadyDOM = false; -var listenTo = ReactBrowserEventEmitter.listenTo; -var registrationNameModules = EventPluginRegistry.registrationNameModules; - var DANGEROUSLY_SET_INNER_HTML = 'dangerouslySetInnerHTML'; var SUPPRESS_CONTENT_EDITABLE_WARNING = 'suppressContentEditableWarning'; var CHILDREN = 'children'; var STYLE = 'style'; var HTML = '__html'; -var {Namespaces: {html: HTML_NAMESPACE}, getIntrinsicNamespace} = DOMNamespaces; - if (__DEV__) { var warnedUnknownTags = { // Chrome is the only major browser not shipping

, ]; - } + it('finds the first child when a component returns a fragment', () => { + class Fragment extends React.Component { + render() { + return [
, ]; } + } - let instance = null; - ReactDOM.render( (instance = ref)} />, container); + let instance = null; + ReactDOM.render( (instance = ref)} />, container); - expect(container.childNodes.length).toBe(2); + expect(container.childNodes.length).toBe(2); - const firstNode = ReactDOM.findDOMNode(instance); - expect(firstNode).toBe(container.firstChild); - expect(firstNode.tagName).toBe('DIV'); - }); + const firstNode = ReactDOM.findDOMNode(instance); + expect(firstNode).toBe(container.firstChild); + expect(firstNode.tagName).toBe('DIV'); + }); - it('finds the first child even when fragment is nested', () => { - class Wrapper extends React.Component { - render() { - return this.props.children; - } + it('finds the first child even when fragment is nested', () => { + class Wrapper extends React.Component { + render() { + return this.props.children; } + } - class Fragment extends React.Component { - render() { - return [
, ]; - } + class Fragment extends React.Component { + render() { + return [
, ]; } + } - let instance = null; - ReactDOM.render( (instance = ref)} />, container); + let instance = null; + ReactDOM.render( (instance = ref)} />, container); - expect(container.childNodes.length).toBe(2); + expect(container.childNodes.length).toBe(2); - const firstNode = ReactDOM.findDOMNode(instance); - expect(firstNode).toBe(container.firstChild); - expect(firstNode.tagName).toBe('DIV'); - }); + const firstNode = ReactDOM.findDOMNode(instance); + expect(firstNode).toBe(container.firstChild); + expect(firstNode.tagName).toBe('DIV'); + }); - it('finds the first child even when first child renders null', () => { - class NullComponent extends React.Component { - render() { - return null; - } + it('finds the first child even when first child renders null', () => { + class NullComponent extends React.Component { + render() { + return null; } + } - class Fragment extends React.Component { - render() { - return [,
, ]; - } + class Fragment extends React.Component { + render() { + return [,
, ]; } + } - let instance = null; - ReactDOM.render( (instance = ref)} />, container); + let instance = null; + ReactDOM.render( (instance = ref)} />, container); - expect(container.childNodes.length).toBe(2); + expect(container.childNodes.length).toBe(2); - const firstNode = ReactDOM.findDOMNode(instance); - expect(firstNode).toBe(container.firstChild); - expect(firstNode.tagName).toBe('DIV'); - }); - } + const firstNode = ReactDOM.findDOMNode(instance); + expect(firstNode).toBe(container.firstChild); + expect(firstNode.tagName).toBe('DIV'); + }); - if (ReactDOMFeatureFlags.useFiber) { - var svgEls, htmlEls, mathEls; - var expectSVG = {ref: el => svgEls.push(el)}; - var expectHTML = {ref: el => htmlEls.push(el)}; - var expectMath = {ref: el => mathEls.push(el)}; + var svgEls, htmlEls, mathEls; + var expectSVG = {ref: el => svgEls.push(el)}; + var expectHTML = {ref: el => htmlEls.push(el)}; + var expectMath = {ref: el => mathEls.push(el)}; - var usePortal = function(tree) { - return ReactDOM.unstable_createPortal( - tree, - document.createElement('div'), - ); - }; - - var assertNamespacesMatch = function(tree) { - container = document.createElement('div'); - svgEls = []; - htmlEls = []; - mathEls = []; - - ReactDOM.render(tree, container); - svgEls.forEach(el => { - expect(el.namespaceURI).toBe('http://www.w3.org/2000/svg'); - }); - htmlEls.forEach(el => { - expect(el.namespaceURI).toBe('http://www.w3.org/1999/xhtml'); - }); - mathEls.forEach(el => { - expect(el.namespaceURI).toBe('http://www.w3.org/1998/Math/MathML'); - }); - - ReactDOM.unmountComponentAtNode(container); - expect(container.innerHTML).toBe(''); - }; - - it('should render one portal', () => { - var portalContainer = document.createElement('div'); - - ReactDOM.render( -
- {ReactDOM.unstable_createPortal(
portal
, portalContainer)} -
, - container, - ); - expect(portalContainer.innerHTML).toBe('
portal
'); - expect(container.innerHTML).toBe('
'); + var usePortal = function(tree) { + return ReactDOM.unstable_createPortal(tree, document.createElement('div')); + }; - ReactDOM.unmountComponentAtNode(container); - expect(portalContainer.innerHTML).toBe(''); - expect(container.innerHTML).toBe(''); + var assertNamespacesMatch = function(tree) { + container = document.createElement('div'); + svgEls = []; + htmlEls = []; + mathEls = []; + + ReactDOM.render(tree, container); + svgEls.forEach(el => { + expect(el.namespaceURI).toBe('http://www.w3.org/2000/svg'); + }); + htmlEls.forEach(el => { + expect(el.namespaceURI).toBe('http://www.w3.org/1999/xhtml'); + }); + mathEls.forEach(el => { + expect(el.namespaceURI).toBe('http://www.w3.org/1998/Math/MathML'); }); - it('should render many portals', () => { - var portalContainer1 = document.createElement('div'); - var portalContainer2 = document.createElement('div'); + ReactDOM.unmountComponentAtNode(container); + expect(container.innerHTML).toBe(''); + }; - var ops = []; - class Child extends React.Component { - componentDidMount() { - ops.push(`${this.props.name} componentDidMount`); - } - componentDidUpdate() { - ops.push(`${this.props.name} componentDidUpdate`); - } - componentWillUnmount() { - ops.push(`${this.props.name} componentWillUnmount`); - } - render() { - return
{this.props.name}
; - } + it('should render one portal', () => { + var portalContainer = document.createElement('div'); + + ReactDOM.render( +
+ {ReactDOM.unstable_createPortal(
portal
, portalContainer)} +
, + container, + ); + expect(portalContainer.innerHTML).toBe('
portal
'); + expect(container.innerHTML).toBe('
'); + + ReactDOM.unmountComponentAtNode(container); + expect(portalContainer.innerHTML).toBe(''); + expect(container.innerHTML).toBe(''); + }); + + it('should render many portals', () => { + var portalContainer1 = document.createElement('div'); + var portalContainer2 = document.createElement('div'); + + var ops = []; + class Child extends React.Component { + componentDidMount() { + ops.push(`${this.props.name} componentDidMount`); } + componentDidUpdate() { + ops.push(`${this.props.name} componentDidUpdate`); + } + componentWillUnmount() { + ops.push(`${this.props.name} componentWillUnmount`); + } + render() { + return
{this.props.name}
; + } + } - class Parent extends React.Component { - componentDidMount() { - ops.push(`Parent:${this.props.step} componentDidMount`); - } - componentDidUpdate() { - ops.push(`Parent:${this.props.step} componentDidUpdate`); - } - componentWillUnmount() { - ops.push(`Parent:${this.props.step} componentWillUnmount`); - } - render() { - const {step} = this.props; - return [ - , + class Parent extends React.Component { + componentDidMount() { + ops.push(`Parent:${this.props.step} componentDidMount`); + } + componentDidUpdate() { + ops.push(`Parent:${this.props.step} componentDidUpdate`); + } + componentWillUnmount() { + ops.push(`Parent:${this.props.step} componentWillUnmount`); + } + render() { + const {step} = this.props; + return [ + , + ReactDOM.unstable_createPortal( + , + portalContainer1, + ), + , + ReactDOM.unstable_createPortal( + [ + , + , + ], + portalContainer2, + ), + ]; + } + } + + ReactDOM.render(, container); + expect(portalContainer1.innerHTML).toBe('
portal1[0]:a
'); + expect(portalContainer2.innerHTML).toBe( + '
portal2[0]:a
portal2[1]:a
', + ); + expect(container.innerHTML).toBe( + '
normal[0]:a
normal[1]:a
', + ); + expect(ops).toEqual([ + 'normal[0]:a componentDidMount', + 'portal1[0]:a componentDidMount', + 'normal[1]:a componentDidMount', + 'portal2[0]:a componentDidMount', + 'portal2[1]:a componentDidMount', + 'Parent:a componentDidMount', + ]); + + ops.length = 0; + ReactDOM.render(, container); + expect(portalContainer1.innerHTML).toBe('
portal1[0]:b
'); + expect(portalContainer2.innerHTML).toBe( + '
portal2[0]:b
portal2[1]:b
', + ); + expect(container.innerHTML).toBe( + '
normal[0]:b
normal[1]:b
', + ); + expect(ops).toEqual([ + 'normal[0]:b componentDidUpdate', + 'portal1[0]:b componentDidUpdate', + 'normal[1]:b componentDidUpdate', + 'portal2[0]:b componentDidUpdate', + 'portal2[1]:b componentDidUpdate', + 'Parent:b componentDidUpdate', + ]); + + ops.length = 0; + ReactDOM.unmountComponentAtNode(container); + expect(portalContainer1.innerHTML).toBe(''); + expect(portalContainer2.innerHTML).toBe(''); + expect(container.innerHTML).toBe(''); + expect(ops).toEqual([ + 'Parent:b componentWillUnmount', + 'normal[0]:b componentWillUnmount', + 'portal1[0]:b componentWillUnmount', + 'normal[1]:b componentWillUnmount', + 'portal2[0]:b componentWillUnmount', + 'portal2[1]:b componentWillUnmount', + ]); + }); + + it('should render nested portals', () => { + var portalContainer1 = document.createElement('div'); + var portalContainer2 = document.createElement('div'); + var portalContainer3 = document.createElement('div'); + + ReactDOM.render( + [ +
normal[0]
, + ReactDOM.unstable_createPortal( + [ +
portal1[0]
, ReactDOM.unstable_createPortal( - , - portalContainer1, +
portal2[0]
, + portalContainer2, ), - , ReactDOM.unstable_createPortal( - [ - , - , - ], - portalContainer2, +
portal3[0]
, + portalContainer3, ), - ]; - } - } +
portal1[1]
, + ], + portalContainer1, + ), +
normal[1]
, + ], + container, + ); + expect(portalContainer1.innerHTML).toBe( + '
portal1[0]
portal1[1]
', + ); + expect(portalContainer2.innerHTML).toBe('
portal2[0]
'); + expect(portalContainer3.innerHTML).toBe('
portal3[0]
'); + expect(container.innerHTML).toBe( + '
normal[0]
normal[1]
', + ); - ReactDOM.render(, container); - expect(portalContainer1.innerHTML).toBe('
portal1[0]:a
'); - expect(portalContainer2.innerHTML).toBe( - '
portal2[0]:a
portal2[1]:a
', - ); - expect(container.innerHTML).toBe( - '
normal[0]:a
normal[1]:a
', - ); - expect(ops).toEqual([ - 'normal[0]:a componentDidMount', - 'portal1[0]:a componentDidMount', - 'normal[1]:a componentDidMount', - 'portal2[0]:a componentDidMount', - 'portal2[1]:a componentDidMount', - 'Parent:a componentDidMount', - ]); - - ops.length = 0; - ReactDOM.render(, container); - expect(portalContainer1.innerHTML).toBe('
portal1[0]:b
'); - expect(portalContainer2.innerHTML).toBe( - '
portal2[0]:b
portal2[1]:b
', - ); - expect(container.innerHTML).toBe( - '
normal[0]:b
normal[1]:b
', - ); - expect(ops).toEqual([ - 'normal[0]:b componentDidUpdate', - 'portal1[0]:b componentDidUpdate', - 'normal[1]:b componentDidUpdate', - 'portal2[0]:b componentDidUpdate', - 'portal2[1]:b componentDidUpdate', - 'Parent:b componentDidUpdate', - ]); - - ops.length = 0; - ReactDOM.unmountComponentAtNode(container); - expect(portalContainer1.innerHTML).toBe(''); - expect(portalContainer2.innerHTML).toBe(''); - expect(container.innerHTML).toBe(''); - expect(ops).toEqual([ - 'Parent:b componentWillUnmount', - 'normal[0]:b componentWillUnmount', - 'portal1[0]:b componentWillUnmount', - 'normal[1]:b componentWillUnmount', - 'portal2[0]:b componentWillUnmount', - 'portal2[1]:b componentWillUnmount', - ]); - }); + ReactDOM.unmountComponentAtNode(container); + expect(portalContainer1.innerHTML).toBe(''); + expect(portalContainer2.innerHTML).toBe(''); + expect(portalContainer3.innerHTML).toBe(''); + expect(container.innerHTML).toBe(''); + }); - it('should render nested portals', () => { - var portalContainer1 = document.createElement('div'); - var portalContainer2 = document.createElement('div'); - var portalContainer3 = document.createElement('div'); + it('should reconcile portal children', () => { + var portalContainer = document.createElement('div'); - ReactDOM.render( - [ -
normal[0]
, - ReactDOM.unstable_createPortal( - [ -
portal1[0]
, - ReactDOM.unstable_createPortal( -
portal2[0]
, - portalContainer2, - ), - ReactDOM.unstable_createPortal( -
portal3[0]
, - portalContainer3, - ), -
portal1[1]
, - ], - portalContainer1, - ), -
normal[1]
, - ], - container, - ); - expect(portalContainer1.innerHTML).toBe( - '
portal1[0]
portal1[1]
', - ); - expect(portalContainer2.innerHTML).toBe('
portal2[0]
'); - expect(portalContainer3.innerHTML).toBe('
portal3[0]
'); - expect(container.innerHTML).toBe( - '
normal[0]
normal[1]
', - ); + ReactDOM.render( +
+ {ReactDOM.unstable_createPortal(
portal:1
, portalContainer)} +
, + container, + ); + expect(portalContainer.innerHTML).toBe('
portal:1
'); + expect(container.innerHTML).toBe('
'); - ReactDOM.unmountComponentAtNode(container); - expect(portalContainer1.innerHTML).toBe(''); - expect(portalContainer2.innerHTML).toBe(''); - expect(portalContainer3.innerHTML).toBe(''); - expect(container.innerHTML).toBe(''); - }); + ReactDOM.render( +
+ {ReactDOM.unstable_createPortal(
portal:2
, portalContainer)} +
, + container, + ); + expect(portalContainer.innerHTML).toBe('
portal:2
'); + expect(container.innerHTML).toBe('
'); - it('should reconcile portal children', () => { - var portalContainer = document.createElement('div'); + ReactDOM.render( +
+ {ReactDOM.unstable_createPortal(

portal:3

, portalContainer)} +
, + container, + ); + expect(portalContainer.innerHTML).toBe('

portal:3

'); + expect(container.innerHTML).toBe('
'); - ReactDOM.render( -
- {ReactDOM.unstable_createPortal(
portal:1
, portalContainer)} -
, - container, - ); - expect(portalContainer.innerHTML).toBe('
portal:1
'); - expect(container.innerHTML).toBe('
'); - - ReactDOM.render( -
- {ReactDOM.unstable_createPortal(
portal:2
, portalContainer)} -
, - container, - ); - expect(portalContainer.innerHTML).toBe('
portal:2
'); - expect(container.innerHTML).toBe('
'); - - ReactDOM.render( -
- {ReactDOM.unstable_createPortal(

portal:3

, portalContainer)} -
, - container, - ); - expect(portalContainer.innerHTML).toBe('

portal:3

'); - expect(container.innerHTML).toBe('
'); - - ReactDOM.render( -
- {ReactDOM.unstable_createPortal(['Hi', 'Bye'], portalContainer)} -
, - container, - ); - expect(portalContainer.innerHTML).toBe('HiBye'); - expect(container.innerHTML).toBe('
'); - - ReactDOM.render( -
- {ReactDOM.unstable_createPortal(['Bye', 'Hi'], portalContainer)} -
, - container, - ); - expect(portalContainer.innerHTML).toBe('ByeHi'); - expect(container.innerHTML).toBe('
'); - - ReactDOM.render( -
- {ReactDOM.unstable_createPortal(null, portalContainer)} -
, - container, - ); - expect(portalContainer.innerHTML).toBe(''); - expect(container.innerHTML).toBe('
'); - }); + ReactDOM.render( +
+ {ReactDOM.unstable_createPortal(['Hi', 'Bye'], portalContainer)} +
, + container, + ); + expect(portalContainer.innerHTML).toBe('HiBye'); + expect(container.innerHTML).toBe('
'); - it('should keep track of namespace across portals (simple)', () => { - assertNamespacesMatch( - - - {usePortal(
)} - - , - ); - assertNamespacesMatch( - - - {usePortal(
)} - - , - ); - assertNamespacesMatch( -
-

- {usePortal( - - - , - )} -

-

, - ); - }); + ReactDOM.render( +
+ {ReactDOM.unstable_createPortal(['Bye', 'Hi'], portalContainer)} +
, + container, + ); + expect(portalContainer.innerHTML).toBe('ByeHi'); + expect(container.innerHTML).toBe('
'); - it('should keep track of namespace across portals (medium)', () => { - assertNamespacesMatch( - - - {usePortal(
)} - - {usePortal(
)} - - , - ); - assertNamespacesMatch( -
- - - {usePortal( - - - , - )} - -

-

, - ); - assertNamespacesMatch( + ReactDOM.render( +
+ {ReactDOM.unstable_createPortal(null, portalContainer)} +
, + container, + ); + expect(portalContainer.innerHTML).toBe(''); + expect(container.innerHTML).toBe('
'); + }); + + it('should keep track of namespace across portals (simple)', () => { + assertNamespacesMatch( + + + {usePortal(
)} + + , + ); + assertNamespacesMatch( + + + {usePortal(
)} + + , + ); + assertNamespacesMatch( +
+

+ {usePortal( + + + , + )} +

+

, + ); + }); + + it('should keep track of namespace across portals (medium)', () => { + assertNamespacesMatch( + + + {usePortal(
)} + + {usePortal(
)} + + , + ); + assertNamespacesMatch( +
{usePortal( - -

- - - -

- - - , - )} - - , - ); - assertNamespacesMatch( -

- {usePortal( - - {usePortal(
)} - , )} -

-

, - ); - assertNamespacesMatch( - + +

+

, + ); + assertNamespacesMatch( + + + {usePortal( + + + +

+ + + +

+ + + , + )} + + , + ); + assertNamespacesMatch( +

+ {usePortal( {usePortal(
)} - + , + )} +

+

, + ); + assertNamespacesMatch( + + + {usePortal(
)} - , - ); - }); + + + , + ); + }); - it('should keep track of namespace across portals (complex)', () => { - assertNamespacesMatch( -
- {usePortal( - - - , - )} -

+ it('should keep track of namespace across portals (complex)', () => { + assertNamespacesMatch( +

+ {usePortal( - + , + )} +

+ + + + - - - -

-

, - ); - assertNamespacesMatch( -
+ + +

+

, + ); + assertNamespacesMatch( +
+ - - - {usePortal( + + {usePortal( + + - - - - - , - )} - - -

- {usePortal(

)} -

- - + + + , + )} - -

- , - ); - assertNamespacesMatch( -

-

- {usePortal( - - - - - -

- - {usePortal(

)} - - - , - )} + {usePortal(

)}

- -

- , - ); - }); - - it('should unwind namespaces on uncaught errors', () => { - function BrokenRender() { - throw new Error('Hello'); - } - - expect(() => { - assertNamespacesMatch( - - - , - ); - }).toThrow('Hello'); - assertNamespacesMatch(

); - }); - - it('should unwind namespaces on caught errors', () => { - function BrokenRender() { - throw new Error('Hello'); - } - - class ErrorBoundary extends React.Component { - state = {error: null}; - componentDidCatch(error) { - this.setState({error}); - } - render() { - if (this.state.error) { - return

; - } - return this.props.children; - } - } - - assertNamespacesMatch( + + +

+

, + ); + assertNamespacesMatch( +
- - - - - +

+ {usePortal( + + + + + +

+ + {usePortal(

)} + + + , + )} +

+ +

+ , + ); + }); + + it('should unwind namespaces on uncaught errors', () => { + function BrokenRender() { + throw new Error('Hello'); + } + + expect(() => { + assertNamespacesMatch( + + , ); - assertNamespacesMatch(

); - }); + }).toThrow('Hello'); + assertNamespacesMatch(
); + }); - it('should unwind namespaces on caught errors in a portal', () => { - function BrokenRender() { - throw new Error('Hello'); - } + it('should unwind namespaces on caught errors', () => { + function BrokenRender() { + throw new Error('Hello'); + } - class ErrorBoundary extends React.Component { - state = {error: null}; - componentDidCatch(error) { - this.setState({error}); - } - render() { - if (this.state.error) { - return ; - } - return this.props.children; + class ErrorBoundary extends React.Component { + state = {error: null}; + componentDidCatch(error) { + this.setState({error}); + } + render() { + if (this.state.error) { + return

; } + return this.props.children; } + } - assertNamespacesMatch( - + assertNamespacesMatch( + + - {usePortal( -
- - ) - -
, - )} + + +
- {usePortal(
)} - , - ); - }); - - it('should pass portal context when rendering subtree elsewhere', () => { - var portalContainer = document.createElement('div'); + + + , + ); + assertNamespacesMatch(
); + }); - class Component extends React.Component { - static contextTypes = { - foo: PropTypes.string.isRequired, - }; + it('should unwind namespaces on caught errors in a portal', () => { + function BrokenRender() { + throw new Error('Hello'); + } - render() { - return
{this.context.foo}
; + class ErrorBoundary extends React.Component { + state = {error: null}; + componentDidCatch(error) { + this.setState({error}); + } + render() { + if (this.state.error) { + return ; } + return this.props.children; } + } - class Parent extends React.Component { - static childContextTypes = { - foo: PropTypes.string.isRequired, - }; + assertNamespacesMatch( + + + {usePortal( +
+ + ) + +
, + )} + + {usePortal(
)} + , + ); + }); - getChildContext() { - return { - foo: 'bar', - }; - } + it('should pass portal context when rendering subtree elsewhere', () => { + var portalContainer = document.createElement('div'); - render() { - return ReactDOM.unstable_createPortal(, portalContainer); - } - } + class Component extends React.Component { + static contextTypes = { + foo: PropTypes.string.isRequired, + }; - ReactDOM.render(, container); - expect(container.innerHTML).toBe(''); - expect(portalContainer.innerHTML).toBe('
bar
'); - }); + render() { + return
{this.context.foo}
; + } + } - it('should update portal context if it changes due to setState', () => { - var portalContainer = document.createElement('div'); + class Parent extends React.Component { + static childContextTypes = { + foo: PropTypes.string.isRequired, + }; - class Component extends React.Component { - static contextTypes = { - foo: PropTypes.string.isRequired, - getFoo: PropTypes.func.isRequired, + getChildContext() { + return { + foo: 'bar', }; + } - render() { - return
{this.context.foo + '-' + this.context.getFoo()}
; - } + render() { + return ReactDOM.unstable_createPortal(, portalContainer); } + } - class Parent extends React.Component { - static childContextTypes = { - foo: PropTypes.string.isRequired, - getFoo: PropTypes.func.isRequired, - }; + ReactDOM.render(, container); + expect(container.innerHTML).toBe(''); + expect(portalContainer.innerHTML).toBe('
bar
'); + }); - state = { - bar: 'initial', - }; + it('should update portal context if it changes due to setState', () => { + var portalContainer = document.createElement('div'); - getChildContext() { - return { - foo: this.state.bar, - getFoo: () => this.state.bar, - }; - } + class Component extends React.Component { + static contextTypes = { + foo: PropTypes.string.isRequired, + getFoo: PropTypes.func.isRequired, + }; - render() { - return ReactDOM.unstable_createPortal(, portalContainer); - } + render() { + return
{this.context.foo + '-' + this.context.getFoo()}
; } + } - var instance = ReactDOM.render(, container); - expect(portalContainer.innerHTML).toBe('
initial-initial
'); - expect(container.innerHTML).toBe(''); - instance.setState({bar: 'changed'}); - expect(portalContainer.innerHTML).toBe('
changed-changed
'); - expect(container.innerHTML).toBe(''); - }); + class Parent extends React.Component { + static childContextTypes = { + foo: PropTypes.string.isRequired, + getFoo: PropTypes.func.isRequired, + }; - it('should update portal context if it changes due to re-render', () => { - var portalContainer = document.createElement('div'); + state = { + bar: 'initial', + }; - class Component extends React.Component { - static contextTypes = { - foo: PropTypes.string.isRequired, - getFoo: PropTypes.func.isRequired, + getChildContext() { + return { + foo: this.state.bar, + getFoo: () => this.state.bar, }; + } - render() { - return
{this.context.foo + '-' + this.context.getFoo()}
; - } + render() { + return ReactDOM.unstable_createPortal(, portalContainer); } + } - class Parent extends React.Component { - static childContextTypes = { - foo: PropTypes.string.isRequired, - getFoo: PropTypes.func.isRequired, - }; + var instance = ReactDOM.render(, container); + expect(portalContainer.innerHTML).toBe('
initial-initial
'); + expect(container.innerHTML).toBe(''); + instance.setState({bar: 'changed'}); + expect(portalContainer.innerHTML).toBe('
changed-changed
'); + expect(container.innerHTML).toBe(''); + }); - getChildContext() { - return { - foo: this.props.bar, - getFoo: () => this.props.bar, - }; - } + it('should update portal context if it changes due to re-render', () => { + var portalContainer = document.createElement('div'); - render() { - return ReactDOM.unstable_createPortal(, portalContainer); - } + class Component extends React.Component { + static contextTypes = { + foo: PropTypes.string.isRequired, + getFoo: PropTypes.func.isRequired, + }; + + render() { + return
{this.context.foo + '-' + this.context.getFoo()}
; } + } - ReactDOM.render(, container); - expect(portalContainer.innerHTML).toBe('
initial-initial
'); - expect(container.innerHTML).toBe(''); - ReactDOM.render(, container); - expect(portalContainer.innerHTML).toBe('
changed-changed
'); - expect(container.innerHTML).toBe(''); - }); + class Parent extends React.Component { + static childContextTypes = { + foo: PropTypes.string.isRequired, + getFoo: PropTypes.func.isRequired, + }; - it('findDOMNode should find dom element after expanding a fragment', () => { - class MyNode extends React.Component { - render() { - return !this.props.flag - ? [
] - : [,
]; - } + getChildContext() { + return { + foo: this.props.bar, + getFoo: () => this.props.bar, + }; } - var myNodeA = ReactDOM.render(, container); - var a = ReactDOM.findDOMNode(myNodeA); - expect(a.tagName).toBe('DIV'); + render() { + return ReactDOM.unstable_createPortal(, portalContainer); + } + } - var myNodeB = ReactDOM.render(, container); - expect(myNodeA === myNodeB).toBe(true); + ReactDOM.render(, container); + expect(portalContainer.innerHTML).toBe('
initial-initial
'); + expect(container.innerHTML).toBe(''); + ReactDOM.render(, container); + expect(portalContainer.innerHTML).toBe('
changed-changed
'); + expect(container.innerHTML).toBe(''); + }); - var b = ReactDOM.findDOMNode(myNodeB); - expect(b.tagName).toBe('SPAN'); - }); + it('findDOMNode should find dom element after expanding a fragment', () => { + class MyNode extends React.Component { + render() { + return !this.props.flag + ? [
] + : [,
]; + } + } - it('should bubble events from the portal to the parent', () => { - var portalContainer = document.createElement('div'); + var myNodeA = ReactDOM.render(, container); + var a = ReactDOM.findDOMNode(myNodeA); + expect(a.tagName).toBe('DIV'); - var ops = []; - var portal = null; + var myNodeB = ReactDOM.render(, container); + expect(myNodeA === myNodeB).toBe(true); - ReactDOM.render( -
ops.push('parent clicked')}> - {ReactDOM.unstable_createPortal( -
ops.push('portal clicked')} - ref={n => (portal = n)}> - portal -
, - portalContainer, - )} -
, - container, - ); + var b = ReactDOM.findDOMNode(myNodeB); + expect(b.tagName).toBe('SPAN'); + }); - expect(portal.tagName).toBe('DIV'); + it('should bubble events from the portal to the parent', () => { + var portalContainer = document.createElement('div'); - var fakeNativeEvent = {}; - ReactTestUtils.simulateNativeEventOnNode( - 'topClick', - portal, - fakeNativeEvent, - ); + var ops = []; + var portal = null; - expect(ops).toEqual(['portal clicked', 'parent clicked']); - }); + ReactDOM.render( +
ops.push('parent clicked')}> + {ReactDOM.unstable_createPortal( +
ops.push('portal clicked')} + ref={n => (portal = n)}> + portal +
, + portalContainer, + )} +
, + container, + ); - it('should not onMouseLeave when staying in the portal', () => { - var portalContainer = document.createElement('div'); + expect(portal.tagName).toBe('DIV'); - var ops = []; - var firstTarget = null; - var secondTarget = null; - var thirdTarget = null; + var fakeNativeEvent = {}; + ReactTestUtils.simulateNativeEventOnNode( + 'topClick', + portal, + fakeNativeEvent, + ); - function simulateMouseMove(from, to) { - if (from) { - ReactTestUtils.simulateNativeEventOnNode('topMouseOut', from, { - target: from, - relatedTarget: to, - }); - } - if (to) { - ReactTestUtils.simulateNativeEventOnNode('topMouseOver', to, { - target: to, - relatedTarget: from, - }); - } + expect(ops).toEqual(['portal clicked', 'parent clicked']); + }); + + it('should not onMouseLeave when staying in the portal', () => { + var portalContainer = document.createElement('div'); + + var ops = []; + var firstTarget = null; + var secondTarget = null; + var thirdTarget = null; + + function simulateMouseMove(from, to) { + if (from) { + ReactTestUtils.simulateNativeEventOnNode('topMouseOut', from, { + target: from, + relatedTarget: to, + }); + } + if (to) { + ReactTestUtils.simulateNativeEventOnNode('topMouseOver', to, { + target: to, + relatedTarget: from, + }); } + } - ReactDOM.render( -
-
ops.push('enter parent')} - onMouseLeave={() => ops.push('leave parent')}> -
(firstTarget = n)} /> - {ReactDOM.unstable_createPortal( -
ops.push('enter portal')} - onMouseLeave={() => ops.push('leave portal')} - ref={n => (secondTarget = n)}> - portal -
, - portalContainer, - )} -
-
(thirdTarget = n)} /> -
, - container, - ); + ReactDOM.render( +
+
ops.push('enter parent')} + onMouseLeave={() => ops.push('leave parent')}> +
(firstTarget = n)} /> + {ReactDOM.unstable_createPortal( +
ops.push('enter portal')} + onMouseLeave={() => ops.push('leave portal')} + ref={n => (secondTarget = n)}> + portal +
, + portalContainer, + )} +
+
(thirdTarget = n)} /> +
, + container, + ); - simulateMouseMove(null, firstTarget); - expect(ops).toEqual(['enter parent']); + simulateMouseMove(null, firstTarget); + expect(ops).toEqual(['enter parent']); - ops = []; + ops = []; - simulateMouseMove(firstTarget, secondTarget); - expect(ops).toEqual([ - // Parent did not invoke leave because we're still inside the portal. - 'enter portal', - ]); + simulateMouseMove(firstTarget, secondTarget); + expect(ops).toEqual([ + // Parent did not invoke leave because we're still inside the portal. + 'enter portal', + ]); - ops = []; + ops = []; - simulateMouseMove(secondTarget, thirdTarget); - expect(ops).toEqual([ - 'leave portal', - 'leave parent', // Only when we leave the portal does onMouseLeave fire. - ]); - }); + simulateMouseMove(secondTarget, thirdTarget); + expect(ops).toEqual([ + 'leave portal', + 'leave parent', // Only when we leave the portal does onMouseLeave fire. + ]); + }); - it('should warn for non-functional event listeners', () => { - spyOn(console, 'error'); - class Example extends React.Component { - render() { - return
; - } + it('should warn for non-functional event listeners', () => { + spyOn(console, 'error'); + class Example extends React.Component { + render() { + return
; } - ReactDOM.render(, container); - expectDev(console.error.calls.count()).toBe(1); - expectDev( - normalizeCodeLocInfo(console.error.calls.argsFor(0)[0]), - ).toContain( - 'Expected `onClick` listener to be a function, instead got a value of `string` type.\n' + - ' in div (at **)\n' + - ' in Example (at **)', - ); - }); + } + ReactDOM.render(, container); + expectDev(console.error.calls.count()).toBe(1); + expectDev( + normalizeCodeLocInfo(console.error.calls.argsFor(0)[0]), + ).toContain( + 'Expected `onClick` listener to be a function, instead got a value of `string` type.\n' + + ' in div (at **)\n' + + ' in Example (at **)', + ); + }); - it('should not update event handlers until commit', () => { - let ops = []; - const handlerA = () => ops.push('A'); - const handlerB = () => ops.push('B'); + it('should not update event handlers until commit', () => { + let ops = []; + const handlerA = () => ops.push('A'); + const handlerB = () => ops.push('B'); - class Example extends React.Component { - state = {flip: false, count: 0}; - flip() { - this.setState({flip: true, count: this.state.count + 1}); - } - tick() { - this.setState({count: this.state.count + 1}); - } - render() { - const useB = !this.props.forceA && this.state.flip; - return
; - } + class Example extends React.Component { + state = {flip: false, count: 0}; + flip() { + this.setState({flip: true, count: this.state.count + 1}); } - - class Click extends React.Component { - constructor() { - super(); - click(node); - } - render() { - return null; - } + tick() { + this.setState({count: this.state.count + 1}); + } + render() { + const useB = !this.props.forceA && this.state.flip; + return
; } + } - let inst; - ReactDOM.render([ (inst = n)} />], container); - const node = container.firstChild; - expect(node.tagName).toEqual('DIV'); - - function click(target) { - var fakeNativeEvent = {}; - ReactTestUtils.simulateNativeEventOnNode( - 'topClick', - target, - fakeNativeEvent, - ); + class Click extends React.Component { + constructor() { + super(); + click(node); + } + render() { + return null; } + } - click(node); + let inst; + ReactDOM.render([ (inst = n)} />], container); + const node = container.firstChild; + expect(node.tagName).toEqual('DIV'); - expect(ops).toEqual(['A']); - ops = []; + function click(target) { + var fakeNativeEvent = {}; + ReactTestUtils.simulateNativeEventOnNode( + 'topClick', + target, + fakeNativeEvent, + ); + } - // Render with the other event handler. - inst.flip(); + click(node); - click(node); + expect(ops).toEqual(['A']); + ops = []; - expect(ops).toEqual(['B']); - ops = []; + // Render with the other event handler. + inst.flip(); - // Rerender without changing any props. - inst.tick(); + click(node); - click(node); + expect(ops).toEqual(['B']); + ops = []; - expect(ops).toEqual(['B']); - ops = []; + // Rerender without changing any props. + inst.tick(); - // Render a flip back to the A handler. The second component invokes the - // click handler during render to simulate a click during an aborted - // render. I use this hack because at current time we don't have a way to - // test aborted ReactDOM renders. - ReactDOM.render( - [, ], - container, - ); + click(node); - // Because the new click handler has not yet committed, we should still - // invoke B. - expect(ops).toEqual(['B']); - ops = []; + expect(ops).toEqual(['B']); + ops = []; - // Any click that happens after commit, should invoke A. - click(node); - expect(ops).toEqual(['A']); - }); + // Render a flip back to the A handler. The second component invokes the + // click handler during render to simulate a click during an aborted + // render. I use this hack because at current time we don't have a way to + // test aborted ReactDOM renders. + ReactDOM.render( + [, ], + container, + ); - it('should not crash encountering low-priority tree', () => { - ReactDOM.render( -