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/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/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/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..bd06887f803 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": 56492, + "gzip": 14646 }, "react.production.min.js (UMD_PROD)": { "size": 6359, "gzip": 2597 }, "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": 621329, + "gzip": 143350 }, "react-dom.production.min.js (UMD_PROD)": { - "size": 108806, - "gzip": 34152 + "size": 101669, + "gzip": 31792 }, "react-dom.development.js (NODE_DEV)": { - "size": 594767, - "gzip": 135242 + "size": 581701, + "gzip": 133925 }, "react-dom.production.min.js (NODE_PROD)": { - "size": 112503, - "gzip": 35538 + "size": 105591, + "gzip": 33210 }, "ReactDOMFiber-dev.js (FB_DEV)": { - "size": 591587, - "gzip": 134688 + "size": 578611, + "gzip": 133416 }, "ReactDOMFiber-prod.js (FB_PROD)": { - "size": 425507, - "gzip": 95372 + "size": 414873, + "gzip": 92899 }, "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": 87141, + "gzip": 22039 }, "react-dom-unstable-native-dependencies.production.min.js (UMD_PROD)": { - "size": 15149, - "gzip": 4910 + "size": 15271, + "gzip": 4963 }, "react-dom-unstable-native-dependencies.development.js (NODE_DEV)": { - "size": 81355, - "gzip": 20240 + "size": 80589, + "gzip": 19940 }, "react-dom-unstable-native-dependencies.production.min.js (NODE_PROD)": { - "size": 13926, - "gzip": 4403 + "size": 13728, + "gzip": 4363 }, "ReactDOMUnstableNativeDependencies-dev.js (FB_DEV)": { - "size": 81054, - "gzip": 20200 + "size": 80308, + "gzip": 19906 }, "ReactDOMUnstableNativeDependencies-prod.js (FB_PROD)": { - "size": 66066, - "gzip": 15736 + "size": 65111, + "gzip": 15516 }, "react-dom-server.browser.development.js (UMD_DEV)": { - "size": 123203, - "gzip": 31145 + "size": 131118, + "gzip": 34007 }, "react-dom-server.browser.production.min.js (UMD_PROD)": { - "size": 19874, - "gzip": 7592 + "size": 14805, + "gzip": 5834 }, "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": 275556, + "gzip": 48152 }, "ReactNativeFiber-prod.js (RN_PROD)": { - "size": 218403, - "gzip": 37966 + "size": 215855, + "gzip": 37513 }, "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/isomorphic/ReactEntry.js b/src/isomorphic/ReactEntry.js index e6ecd54048e..6838d3df2b8 100644 --- a/src/isomorphic/ReactEntry.js +++ b/src/isomorphic/ReactEntry.js @@ -58,7 +58,6 @@ var React = { 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'), }); } 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/classic/element/ReactCurrentOwner.js b/src/isomorphic/classic/element/ReactCurrentOwner.js index 3a12b523bd6..056a9173003 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; 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/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__/EventPluginHub-test.js b/src/renderers/__tests__/EventPluginHub-test.js index 4ad5bd3db2e..ee157bdb9fb 100644 --- a/src/renderers/__tests__/EventPluginHub-test.js +++ b/src/renderers/__tests__/EventPluginHub-test.js @@ -16,13 +16,11 @@ jest.mock('isEventSupported'); describe('EventPluginHub', () => { var React; var ReactTestUtils; - var ReactDOMFeatureFlags; beforeEach(() => { jest.resetModules(); React = require('react'); ReactTestUtils = require('react-dom/test-utils'); - ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); }); it('should prevent non-function listeners, at dispatch', () => { @@ -35,14 +33,10 @@ describe('EventPluginHub', () => { }).toThrowError( 'Expected `onClick` listener to be a function, instead got a value of `string` type.', ); - if (ReactDOMFeatureFlags.useFiber) { - expectDev(console.error.calls.count()).toBe(1); - expectDev(console.error.calls.argsFor(0)[0]).toContain( - 'Expected `onClick` listener to be a function, instead got a value of `string` type.', - ); - } else { - expectDev(console.error.calls.count()).toBe(0); - } + expectDev(console.error.calls.count()).toBe(1); + expectDev(console.error.calls.argsFor(0)[0]).toContain( + 'Expected `onClick` listener to be a function, instead got a value of `string` type.', + ); }); it('should not prevent null listeners, at dispatch', () => { 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/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/__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/ReactDOMFiberEntry.js b/src/renderers/dom/fiber/ReactDOMFiberEntry.js index e7a25947121..48e96fd0c47 100644 --- a/src/renderers/dom/fiber/ReactDOMFiberEntry.js +++ b/src/renderers/dom/fiber/ReactDOMFiberEntry.js @@ -12,7 +12,6 @@ 'use strict'; -import type {Fiber} from 'ReactFiber'; import type {ReactNodeList} from 'ReactTypes'; require('checkReact'); @@ -31,6 +30,7 @@ var ReactInputSelection = require('ReactInputSelection'); var ReactInstanceMap = require('ReactInstanceMap'); var ReactPortal = require('ReactPortal'); var ReactVersion = require('ReactVersion'); +var {ReactCurrentOwner} = require('ReactGlobalSharedState'); var {isValidElement} = require('react'); var {injectInternals} = require('ReactFiberDevToolsHook'); var { @@ -42,7 +42,7 @@ var { } = require('HTMLNodeType'); var {ROOT_ATTRIBUTE_NAME} = require('DOMProperty'); -var findDOMNode = require('findDOMNode'); +var getComponentName = require('getComponentName'); var invariant = require('fbjs/lib/invariant'); var {getChildNamespace} = DOMNamespaces; @@ -88,9 +88,6 @@ require('ReactDOMInjection'); ReactControlledComponent.injection.injectFiberControlledHostComponent( ReactDOMFiberComponent, ); -findDOMNode._injectFiber(function(fiber: Fiber) { - return DOMRenderer.findHostInstance(fiber); -}); type DOMContainer = | (Element & { @@ -235,7 +232,7 @@ var DOMRenderer = ReactFiberReconciler({ if (__DEV__) { // TODO: take namespace into account when validating. const hostContextDev = ((hostContext: any): HostContextDev); - validateDOMNesting(type, null, null, hostContextDev.ancestorInfo); + validateDOMNesting(type, null, hostContextDev.ancestorInfo); if ( typeof props.children === 'string' || typeof props.children === 'number' @@ -246,7 +243,7 @@ var DOMRenderer = ReactFiberReconciler({ type, null, ); - validateDOMNesting(null, string, null, ownAncestorInfo); + validateDOMNesting(null, string, ownAncestorInfo); } parentNamespace = hostContextDev.namespace; } else { @@ -301,7 +298,7 @@ var DOMRenderer = ReactFiberReconciler({ type, null, ); - validateDOMNesting(null, string, null, ownAncestorInfo); + validateDOMNesting(null, string, ownAncestorInfo); } } return diffProperties( @@ -368,7 +365,7 @@ var DOMRenderer = ReactFiberReconciler({ ): TextInstance { if (__DEV__) { const hostContextDev = ((hostContext: any): HostContextDev); - validateDOMNesting(null, text, null, hostContextDev.ancestorInfo); + validateDOMNesting(null, text, hostContextDev.ancestorInfo); } var textNode: TextInstance = document.createTextNode(text); precacheFiberNode(internalInstanceHandle, textNode); @@ -649,6 +646,48 @@ function renderSubtreeIntoContainer( } var ReactDOMFiber = { + findDOMNode( + componentOrElement: Element | ?ReactComponent, + ): null | Element | Text { + if (__DEV__) { + var owner = (ReactCurrentOwner.current: any); + if (owner !== null) { + var warnedAboutRefsInRender = owner.stateNode._warnedAboutRefsInRender; + warning( + warnedAboutRefsInRender, + '%s is accessing findDOMNode inside its render(). ' + + 'render() should be a pure function of props and state. It should ' + + 'never access something that requires stale data from the previous ' + + 'render, such as refs. Move this logic to componentDidMount and ' + + 'componentDidUpdate instead.', + getComponentName(owner) || 'A component', + ); + owner.stateNode._warnedAboutRefsInRender = true; + } + } + if (componentOrElement == null) { + return null; + } + if ((componentOrElement: any).nodeType === ELEMENT_NODE) { + return (componentOrElement: any); + } + + var inst = ReactInstanceMap.get(componentOrElement); + if (inst) { + return DOMRenderer.findHostInstance(inst); + } + + if (typeof componentOrElement.render === 'function') { + invariant(false, 'Unable to find node on an unmounted component.'); + } else { + invariant( + false, + 'Element appears to be neither ReactComponent nor DOMNode. Keys: %s', + Object.keys(componentOrElement), + ); + } + }, + hydrate( element: ReactElement, container: DOMContainer, @@ -778,8 +817,6 @@ var ReactDOMFiber = { } }, - findDOMNode: findDOMNode, - unstable_createPortal( children: ReactNodeList, container: DOMContainer, diff --git a/src/renderers/dom/fiber/__tests__/ReactDOMFiber-test.js b/src/renderers/dom/fiber/__tests__/ReactDOMFiber-test.js index 2910041d9bb..30ee7088ec5 100644 --- a/src/renderers/dom/fiber/__tests__/ReactDOMFiber-test.js +++ b/src/renderers/dom/fiber/__tests__/ReactDOMFiber-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'); var PropTypes = require('prop-types'); @@ -84,1024 +83,1017 @@ describe('ReactDOMFiber', () => { expect(called).toEqual(true); }); - if (ReactDOMFeatureFlags.useFiber) { - it('should render a component returning strings directly from render', () => { - const Text = ({value}) => value; + it('should render a component returning strings directly from render', () => { + const Text = ({value}) => value; - ReactDOM.render(, container); - expect(container.textContent).toEqual('foo'); - }); + ReactDOM.render(, container); + expect(container.textContent).toEqual('foo'); + }); - it('should render a component returning numbers directly from render', () => { - const Text = ({value}) => value; + it('should render a component returning numbers directly from render', () => { + const Text = ({value}) => value; - ReactDOM.render(, container); + ReactDOM.render(, container); - expect(container.textContent).toEqual('10'); - }); + expect(container.textContent).toEqual('10'); + }); - it('finds the DOM Text node of a string child', () => { - class Text extends React.Component { - render() { - return this.props.value; - } + it('finds the DOM Text node of a string child', () => { + class Text extends React.Component { + render() { + return this.props.value; } + } - let instance = null; - ReactDOM.render( - (instance = ref)} />, - container, - ); + let instance = null; + ReactDOM.render( + (instance = ref)} />, + container, + ); - const textNode = ReactDOM.findDOMNode(instance); - expect(textNode).toBe(container.firstChild); - expect(textNode.nodeType).toBe(3); - expect(textNode.nodeValue).toBe('foo'); - }); + const textNode = ReactDOM.findDOMNode(instance); + expect(textNode).toBe(container.firstChild); + expect(textNode.nodeType).toBe(3); + expect(textNode.nodeValue).toBe('foo'); + }); - it('finds the first child when a component returns a fragment', () => { - class Fragment extends React.Component { - render() { - return [

, ]; - } + 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( -