From 0008beb1fb0c197ac117ddee32a5dd0e363b062a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Fri, 29 Apr 2016 15:57:12 -0700 Subject: [PATCH 01/32] Get rid of transformMatrix/decomposeMatrix special case (#6660) This is no longer needed on the native side. This is also the last use of the Platform flag. React Core is now platform agnostic with regard to React Native. So I'll remove the mocks and dependency. (cherry picked from commit 0e4b04663422d217f5a796936435603037b08bb5) --- gulpfile.js | 3 -- .../ReactNativeAttributePayload.js | 43 +++---------------- .../__mocks__/InteractionManager.js | 16 ------- .../native/ReactNative/__mocks__/Platform.js | 16 ------- 4 files changed, 7 insertions(+), 71 deletions(-) delete mode 100644 src/renderers/native/ReactNative/__mocks__/InteractionManager.js delete mode 100644 src/renderers/native/ReactNative/__mocks__/Platform.js diff --git a/gulpfile.js b/gulpfile.js index 2c2a7c9823..a51a094367 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -39,10 +39,7 @@ var whiteListNames = [ 'deepFreezeAndThrowOnMutationInDev', 'flattenStyle', 'InitializeJavaScriptAppEngine', - 'InteractionManager', 'JSTimersExecution', - 'merge', - 'Platform', 'RCTEventEmitter', 'RCTLog', 'TextInputState', diff --git a/src/renderers/native/ReactNative/ReactNativeAttributePayload.js b/src/renderers/native/ReactNative/ReactNativeAttributePayload.js index ca1cd57030..a2976c6025 100644 --- a/src/renderers/native/ReactNative/ReactNativeAttributePayload.js +++ b/src/renderers/native/ReactNative/ReactNativeAttributePayload.js @@ -11,7 +11,6 @@ */ 'use strict'; -var Platform = require('Platform'); var ReactNativePropRegistry = require('ReactNativePropRegistry'); var deepDiffer = require('deepDiffer'); @@ -47,21 +46,6 @@ type NestedNode = Array | Object | number; var removedKeys = null; var removedKeyCount = 0; -function translateKey(propKey: string) : string { - if (propKey === 'transform') { - // We currently special case the key for `transform`. iOS uses the - // transformMatrix name and Android uses the decomposedMatrix name. - // TODO: We could unify these names and just use the name `transform` - // all the time. Just need to update the native side. - if (Platform.OS === 'android') { - return 'decomposedMatrix'; - } else { - return 'transformMatrix'; - } - } - return propKey; -} - function defaultDiffer(prevProp: mixed, nextProp: mixed) : boolean { if (typeof nextProp !== 'object' || nextProp === null) { // Scalars have already been checked for equality @@ -326,7 +310,6 @@ function diffProperties( var attributeConfig : ?(CustomAttributeConfiguration | AttributeConfiguration); var nextProp; var prevProp; - var altKey; for (var propKey in nextProps) { attributeConfig = validAttributes[propKey]; @@ -334,12 +317,6 @@ function diffProperties( continue; // not a valid native prop } - altKey = translateKey(propKey); - if (!validAttributes[altKey]) { - // If there is no config for the alternative, bail out. Helps ART. - altKey = propKey; - } - prevProp = prevProps[propKey]; nextProp = nextProps[propKey]; @@ -367,7 +344,7 @@ function diffProperties( removedKeys[propKey] = false; } - if (updatePayload && updatePayload[altKey] !== undefined) { + if (updatePayload && updatePayload[propKey] !== undefined) { // Something else already triggered an update to this key because another // value diffed. Since we're now later in the nested arrays our value is // more important so we need to calculate it and override the existing @@ -376,14 +353,14 @@ function diffProperties( // Pattern match on: attributeConfig if (typeof attributeConfig !== 'object') { // case: !Object is the default case - updatePayload[altKey] = nextProp; + updatePayload[propKey] = nextProp; } else if (typeof attributeConfig.diff === 'function' || typeof attributeConfig.process === 'function') { // case: CustomAttributeConfiguration var nextValue = typeof attributeConfig.process === 'function' ? attributeConfig.process(nextProp) : nextProp; - updatePayload[altKey] = nextValue; + updatePayload[propKey] = nextValue; } continue; } @@ -397,7 +374,7 @@ function diffProperties( // case: !Object is the default case if (defaultDiffer(prevProp, nextProp)) { // a normal leaf has changed - (updatePayload || (updatePayload = {}))[altKey] = nextProp; + (updatePayload || (updatePayload = {}))[propKey] = nextProp; } } else if (typeof attributeConfig.diff === 'function' || typeof attributeConfig.process === 'function') { @@ -411,7 +388,7 @@ function diffProperties( nextValue = typeof attributeConfig.process === 'function' ? attributeConfig.process(nextProp) : nextProp; - (updatePayload || (updatePayload = {}))[altKey] = nextValue; + (updatePayload || (updatePayload = {}))[propKey] = nextValue; } } else { // default: fallthrough case when nested properties are defined @@ -446,13 +423,7 @@ function diffProperties( continue; // not a valid native prop } - altKey = translateKey(propKey); - if (!attributeConfig[altKey]) { - // If there is no config for the alternative, bail out. Helps ART. - altKey = propKey; - } - - if (updatePayload && updatePayload[altKey] !== undefined) { + if (updatePayload && updatePayload[propKey] !== undefined) { // This was already updated to a diff result earlier. continue; } @@ -468,7 +439,7 @@ function diffProperties( // case: CustomAttributeConfiguration | !Object // Flag the leaf property for removal by sending a sentinel. - (updatePayload || (updatePayload = {}))[altKey] = null; + (updatePayload || (updatePayload = {}))[propKey] = null; if (!removedKeys) { removedKeys = {}; } diff --git a/src/renderers/native/ReactNative/__mocks__/InteractionManager.js b/src/renderers/native/ReactNative/__mocks__/InteractionManager.js deleted file mode 100644 index de72e164a6..0000000000 --- a/src/renderers/native/ReactNative/__mocks__/InteractionManager.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright 2013-2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -'use strict'; - -// TODO: Figure out a way to drop this dependency - -var InteractionManager = {}; - -module.exports = InteractionManager; diff --git a/src/renderers/native/ReactNative/__mocks__/Platform.js b/src/renderers/native/ReactNative/__mocks__/Platform.js deleted file mode 100644 index fc04c24549..0000000000 --- a/src/renderers/native/ReactNative/__mocks__/Platform.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * Copyright 2013-2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -'use strict'; - -// Mock of the Native Hooks - -var Platform = {}; - -module.exports = Platform; From 64acaca8eee36859d095fcffaaa6625901f2ee02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Wed, 27 Apr 2016 19:54:40 -0700 Subject: [PATCH 02/32] Use spread instead of deprecated merge utility (#6634) (cherry picked from commit 72ba5971ae76b8889ceed84c70aa107fc7cfce3b) --- .../ReactIOS/IOSNativeBridgeEventPlugin.js | 3 +-- .../ReactNative/ReactNativeEventEmitter.js | 7 ++++--- .../native/ReactNative/__mocks__/merge.js | 18 ------------------ 3 files changed, 5 insertions(+), 23 deletions(-) delete mode 100644 src/renderers/native/ReactNative/__mocks__/merge.js diff --git a/src/renderers/native/ReactIOS/IOSNativeBridgeEventPlugin.js b/src/renderers/native/ReactIOS/IOSNativeBridgeEventPlugin.js index 564e10f28c..7cc595d5d9 100644 --- a/src/renderers/native/ReactIOS/IOSNativeBridgeEventPlugin.js +++ b/src/renderers/native/ReactIOS/IOSNativeBridgeEventPlugin.js @@ -15,7 +15,6 @@ var EventPropagators = require('EventPropagators'); var SyntheticEvent = require('SyntheticEvent'); var UIManager = require('UIManager'); -var merge = require('merge'); var warning = require('warning'); var customBubblingEventTypes = UIManager.customBubblingEventTypes; @@ -38,7 +37,7 @@ for (var directTypeName in customDirectEventTypes) { var IOSNativeBridgeEventPlugin = { - eventTypes: merge(customBubblingEventTypes, customDirectEventTypes), + eventTypes: { ...customBubblingEventTypes, ...customDirectEventTypes }, /** * @see {EventPluginHub.extractEvents} diff --git a/src/renderers/native/ReactNative/ReactNativeEventEmitter.js b/src/renderers/native/ReactNative/ReactNativeEventEmitter.js index bf7179c2ae..0018b63e55 100644 --- a/src/renderers/native/ReactNative/ReactNativeEventEmitter.js +++ b/src/renderers/native/ReactNative/ReactNativeEventEmitter.js @@ -19,7 +19,6 @@ var ReactNativeTagHandles = require('ReactNativeTagHandles'); var ReactUpdates = require('ReactUpdates'); var EventConstants = require('EventConstants'); -var merge = require('merge'); var warning = require('warning'); var topLevelTypes = EventConstants.topLevelTypes; @@ -91,7 +90,9 @@ var removeTouchesAtIndices = function( * * @internal */ -var ReactNativeEventEmitter = merge(ReactEventEmitterMixin, { +var ReactNativeEventEmitter = { + + ...ReactEventEmitterMixin, registrationNames: EventPluginRegistry.registrationNameModules, @@ -218,6 +219,6 @@ var ReactNativeEventEmitter = merge(ReactEventEmitterMixin, { ); } }, -}); +}; module.exports = ReactNativeEventEmitter; diff --git a/src/renderers/native/ReactNative/__mocks__/merge.js b/src/renderers/native/ReactNative/__mocks__/merge.js deleted file mode 100644 index 0c2dc8bf5d..0000000000 --- a/src/renderers/native/ReactNative/__mocks__/merge.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright 2013-2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -'use strict'; - -// TODO: Replace all callers with spread - -var merge = function(a, b) { - return {...a, ...b}; -}; - -module.exports = merge; From 772b5f9a5a949f725153a5c5d95a00ef2791ead4 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Tue, 3 May 2016 14:05:33 +0100 Subject: [PATCH 03/32] Move ReactIOS components to native subfolder (#6643) * Move ReactIOS components to ReactNative * Drop ReactNative subfolder (cherry picked from commit 222f5087fe1267921aaa5e2f600ea94455ee6d2a) --- .../native/{ReactIOS => }/NativeMethodsMixin.js | 0 .../native/{ReactNative => }/ReactNative.js | 0 .../ReactNativeAttributePayload.js | 0 .../{ReactNative => }/ReactNativeBaseComponent.js | 0 ...ntPlugin.js => ReactNativeBridgeEventPlugin.js} | 6 +++--- .../ReactNativeComponentEnvironment.js | 0 .../{ReactNative => }/ReactNativeComponentTree.js | 0 .../{ReactNative => }/ReactNativeContainerInfo.js | 0 .../ReactNativeDOMIDOperations.js | 0 .../ReactNativeDefaultInjection.js | 14 +++++++------- .../{ReactNative => }/ReactNativeEventEmitter.js | 0 ...uginOrder.js => ReactNativeEventPluginOrder.js} | 8 ++++---- .../ReactNativeGlobalResponderHandler.js | 0 .../native/{ReactNative => }/ReactNativeMount.js | 0 .../{ReactNative => }/ReactNativePropRegistry.js | 0 .../ReactNativeReconcileTransaction.js | 0 .../{ReactNative => }/ReactNativeTagHandles.js | 0 .../{ReactNative => }/ReactNativeTextComponent.js | 0 .../{ReactNative => }/ReactNativeTreeTraversal.js | 0 .../__mocks__/InitializeJavaScriptAppEngine.js | 0 .../__mocks__/JSTimersExecution.js | 0 .../{ReactNative => }/__mocks__/RCTEventEmitter.js | 0 .../native/{ReactNative => }/__mocks__/RCTLog.js | 0 .../{ReactNative => }/__mocks__/TextInputState.js | 0 .../{ReactNative => }/__mocks__/UIManager.js | 0 .../{ReactNative => }/__mocks__/deepDiffer.js | 0 .../__mocks__/deepFreezeAndThrowOnMutationInDev.js | 0 .../{ReactNative => }/__mocks__/flattenStyle.js | 0 .../__tests__/ReactNativeAttributePayload-test.js | 0 .../__tests__/ReactNativeMount-test.js | 0 .../createReactNativeComponentClass.js | 0 .../native/{ReactNative => }/findNodeHandle.js | 0 32 files changed, 14 insertions(+), 14 deletions(-) rename src/renderers/native/{ReactIOS => }/NativeMethodsMixin.js (100%) rename src/renderers/native/{ReactNative => }/ReactNative.js (100%) rename src/renderers/native/{ReactNative => }/ReactNativeAttributePayload.js (100%) rename src/renderers/native/{ReactNative => }/ReactNativeBaseComponent.js (100%) rename src/renderers/native/{ReactIOS/IOSNativeBridgeEventPlugin.js => ReactNativeBridgeEventPlugin.js} (93%) rename src/renderers/native/{ReactNative => }/ReactNativeComponentEnvironment.js (100%) rename src/renderers/native/{ReactNative => }/ReactNativeComponentTree.js (100%) rename src/renderers/native/{ReactNative => }/ReactNativeContainerInfo.js (100%) rename src/renderers/native/{ReactNative => }/ReactNativeDOMIDOperations.js (100%) rename src/renderers/native/{ReactNative => }/ReactNativeDefaultInjection.js (92%) rename src/renderers/native/{ReactNative => }/ReactNativeEventEmitter.js (100%) rename src/renderers/native/{ReactIOS/IOSDefaultEventPluginOrder.js => ReactNativeEventPluginOrder.js} (69%) rename src/renderers/native/{ReactNative => }/ReactNativeGlobalResponderHandler.js (100%) rename src/renderers/native/{ReactNative => }/ReactNativeMount.js (100%) rename src/renderers/native/{ReactNative => }/ReactNativePropRegistry.js (100%) rename src/renderers/native/{ReactNative => }/ReactNativeReconcileTransaction.js (100%) rename src/renderers/native/{ReactNative => }/ReactNativeTagHandles.js (100%) rename src/renderers/native/{ReactNative => }/ReactNativeTextComponent.js (100%) rename src/renderers/native/{ReactNative => }/ReactNativeTreeTraversal.js (100%) rename src/renderers/native/{ReactNative => }/__mocks__/InitializeJavaScriptAppEngine.js (100%) rename src/renderers/native/{ReactNative => }/__mocks__/JSTimersExecution.js (100%) rename src/renderers/native/{ReactNative => }/__mocks__/RCTEventEmitter.js (100%) rename src/renderers/native/{ReactNative => }/__mocks__/RCTLog.js (100%) rename src/renderers/native/{ReactNative => }/__mocks__/TextInputState.js (100%) rename src/renderers/native/{ReactNative => }/__mocks__/UIManager.js (100%) rename src/renderers/native/{ReactNative => }/__mocks__/deepDiffer.js (100%) rename src/renderers/native/{ReactNative => }/__mocks__/deepFreezeAndThrowOnMutationInDev.js (100%) rename src/renderers/native/{ReactNative => }/__mocks__/flattenStyle.js (100%) rename src/renderers/native/{ReactNative => }/__tests__/ReactNativeAttributePayload-test.js (100%) rename src/renderers/native/{ReactNative => }/__tests__/ReactNativeMount-test.js (100%) rename src/renderers/native/{ReactNative => }/createReactNativeComponentClass.js (100%) rename src/renderers/native/{ReactNative => }/findNodeHandle.js (100%) diff --git a/src/renderers/native/ReactIOS/NativeMethodsMixin.js b/src/renderers/native/NativeMethodsMixin.js similarity index 100% rename from src/renderers/native/ReactIOS/NativeMethodsMixin.js rename to src/renderers/native/NativeMethodsMixin.js diff --git a/src/renderers/native/ReactNative/ReactNative.js b/src/renderers/native/ReactNative.js similarity index 100% rename from src/renderers/native/ReactNative/ReactNative.js rename to src/renderers/native/ReactNative.js diff --git a/src/renderers/native/ReactNative/ReactNativeAttributePayload.js b/src/renderers/native/ReactNativeAttributePayload.js similarity index 100% rename from src/renderers/native/ReactNative/ReactNativeAttributePayload.js rename to src/renderers/native/ReactNativeAttributePayload.js diff --git a/src/renderers/native/ReactNative/ReactNativeBaseComponent.js b/src/renderers/native/ReactNativeBaseComponent.js similarity index 100% rename from src/renderers/native/ReactNative/ReactNativeBaseComponent.js rename to src/renderers/native/ReactNativeBaseComponent.js diff --git a/src/renderers/native/ReactIOS/IOSNativeBridgeEventPlugin.js b/src/renderers/native/ReactNativeBridgeEventPlugin.js similarity index 93% rename from src/renderers/native/ReactIOS/IOSNativeBridgeEventPlugin.js rename to src/renderers/native/ReactNativeBridgeEventPlugin.js index 7cc595d5d9..97c6496c6a 100644 --- a/src/renderers/native/ReactIOS/IOSNativeBridgeEventPlugin.js +++ b/src/renderers/native/ReactNativeBridgeEventPlugin.js @@ -6,7 +6,7 @@ * 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 IOSNativeBridgeEventPlugin + * @providesModule ReactNativeBridgeEventPlugin * @flow */ 'use strict'; @@ -35,7 +35,7 @@ for (var directTypeName in customDirectEventTypes) { allTypesByEventName[directTypeName] = customDirectEventTypes[directTypeName]; } -var IOSNativeBridgeEventPlugin = { +var ReactNativeBridgeEventPlugin = { eventTypes: { ...customBubblingEventTypes, ...customDirectEventTypes }, @@ -67,4 +67,4 @@ var IOSNativeBridgeEventPlugin = { }, }; -module.exports = IOSNativeBridgeEventPlugin; +module.exports = ReactNativeBridgeEventPlugin; diff --git a/src/renderers/native/ReactNative/ReactNativeComponentEnvironment.js b/src/renderers/native/ReactNativeComponentEnvironment.js similarity index 100% rename from src/renderers/native/ReactNative/ReactNativeComponentEnvironment.js rename to src/renderers/native/ReactNativeComponentEnvironment.js diff --git a/src/renderers/native/ReactNative/ReactNativeComponentTree.js b/src/renderers/native/ReactNativeComponentTree.js similarity index 100% rename from src/renderers/native/ReactNative/ReactNativeComponentTree.js rename to src/renderers/native/ReactNativeComponentTree.js diff --git a/src/renderers/native/ReactNative/ReactNativeContainerInfo.js b/src/renderers/native/ReactNativeContainerInfo.js similarity index 100% rename from src/renderers/native/ReactNative/ReactNativeContainerInfo.js rename to src/renderers/native/ReactNativeContainerInfo.js diff --git a/src/renderers/native/ReactNative/ReactNativeDOMIDOperations.js b/src/renderers/native/ReactNativeDOMIDOperations.js similarity index 100% rename from src/renderers/native/ReactNative/ReactNativeDOMIDOperations.js rename to src/renderers/native/ReactNativeDOMIDOperations.js diff --git a/src/renderers/native/ReactNative/ReactNativeDefaultInjection.js b/src/renderers/native/ReactNativeDefaultInjection.js similarity index 92% rename from src/renderers/native/ReactNative/ReactNativeDefaultInjection.js rename to src/renderers/native/ReactNativeDefaultInjection.js index c0c5fd2073..21fc07ba88 100644 --- a/src/renderers/native/ReactNative/ReactNativeDefaultInjection.js +++ b/src/renderers/native/ReactNativeDefaultInjection.js @@ -20,18 +20,18 @@ require('InitializeJavaScriptAppEngine'); var EventPluginHub = require('EventPluginHub'); var EventPluginUtils = require('EventPluginUtils'); -var IOSDefaultEventPluginOrder = require('IOSDefaultEventPluginOrder'); -var IOSNativeBridgeEventPlugin = require('IOSNativeBridgeEventPlugin'); -var ReactElement = require('ReactElement'); var ReactComponentEnvironment = require('ReactComponentEnvironment'); var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy'); +var ReactElement = require('ReactElement'); var ReactEmptyComponent = require('ReactEmptyComponent'); +var ReactNativeBridgeEventPlugin = require('ReactNativeBridgeEventPlugin'); +var ReactNativeComponent = require('ReactNativeComponent'); var ReactNativeComponentEnvironment = require('ReactNativeComponentEnvironment'); +var ReactNativeComponentTree = require('ReactNativeComponentTree'); +var ReactNativeEventPluginOrder = require('ReactNativeEventPluginOrder'); var ReactNativeGlobalResponderHandler = require('ReactNativeGlobalResponderHandler'); var ReactNativeTextComponent = require('ReactNativeTextComponent'); var ReactNativeTreeTraversal = require('ReactNativeTreeTraversal'); -var ReactNativeComponent = require('ReactNativeComponent'); -var ReactNativeComponentTree = require('ReactNativeComponentTree'); var ReactSimpleEmptyComponent = require('ReactSimpleEmptyComponent'); var ReactUpdates = require('ReactUpdates'); var ResponderEventPlugin = require('ResponderEventPlugin'); @@ -47,7 +47,7 @@ function inject() { /** * Inject module for resolving DOM hierarchy and plugin ordering. */ - EventPluginHub.injection.injectEventPluginOrder(IOSDefaultEventPluginOrder); + EventPluginHub.injection.injectEventPluginOrder(ReactNativeEventPluginOrder); EventPluginUtils.injection.injectComponentTree(ReactNativeComponentTree); EventPluginUtils.injection.injectTreeTraversal(ReactNativeTreeTraversal); @@ -61,7 +61,7 @@ function inject() { */ EventPluginHub.injection.injectEventPluginsByName({ 'ResponderEventPlugin': ResponderEventPlugin, - 'IOSNativeBridgeEventPlugin': IOSNativeBridgeEventPlugin, + 'ReactNativeBridgeEventPlugin': ReactNativeBridgeEventPlugin, }); ReactUpdates.injection.injectReconcileTransaction( diff --git a/src/renderers/native/ReactNative/ReactNativeEventEmitter.js b/src/renderers/native/ReactNativeEventEmitter.js similarity index 100% rename from src/renderers/native/ReactNative/ReactNativeEventEmitter.js rename to src/renderers/native/ReactNativeEventEmitter.js diff --git a/src/renderers/native/ReactIOS/IOSDefaultEventPluginOrder.js b/src/renderers/native/ReactNativeEventPluginOrder.js similarity index 69% rename from src/renderers/native/ReactIOS/IOSDefaultEventPluginOrder.js rename to src/renderers/native/ReactNativeEventPluginOrder.js index eae211c40f..b1b5f66d11 100644 --- a/src/renderers/native/ReactIOS/IOSDefaultEventPluginOrder.js +++ b/src/renderers/native/ReactNativeEventPluginOrder.js @@ -6,14 +6,14 @@ * 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 IOSDefaultEventPluginOrder + * @providesModule ReactNativeEventPluginOrder * @flow */ 'use strict'; -var IOSDefaultEventPluginOrder = [ +var ReactNativeEventPluginOrder = [ 'ResponderEventPlugin', - 'IOSNativeBridgeEventPlugin', + 'ReactNativeBridgeEventPlugin', ]; -module.exports = IOSDefaultEventPluginOrder; +module.exports = ReactNativeEventPluginOrder; diff --git a/src/renderers/native/ReactNative/ReactNativeGlobalResponderHandler.js b/src/renderers/native/ReactNativeGlobalResponderHandler.js similarity index 100% rename from src/renderers/native/ReactNative/ReactNativeGlobalResponderHandler.js rename to src/renderers/native/ReactNativeGlobalResponderHandler.js diff --git a/src/renderers/native/ReactNative/ReactNativeMount.js b/src/renderers/native/ReactNativeMount.js similarity index 100% rename from src/renderers/native/ReactNative/ReactNativeMount.js rename to src/renderers/native/ReactNativeMount.js diff --git a/src/renderers/native/ReactNative/ReactNativePropRegistry.js b/src/renderers/native/ReactNativePropRegistry.js similarity index 100% rename from src/renderers/native/ReactNative/ReactNativePropRegistry.js rename to src/renderers/native/ReactNativePropRegistry.js diff --git a/src/renderers/native/ReactNative/ReactNativeReconcileTransaction.js b/src/renderers/native/ReactNativeReconcileTransaction.js similarity index 100% rename from src/renderers/native/ReactNative/ReactNativeReconcileTransaction.js rename to src/renderers/native/ReactNativeReconcileTransaction.js diff --git a/src/renderers/native/ReactNative/ReactNativeTagHandles.js b/src/renderers/native/ReactNativeTagHandles.js similarity index 100% rename from src/renderers/native/ReactNative/ReactNativeTagHandles.js rename to src/renderers/native/ReactNativeTagHandles.js diff --git a/src/renderers/native/ReactNative/ReactNativeTextComponent.js b/src/renderers/native/ReactNativeTextComponent.js similarity index 100% rename from src/renderers/native/ReactNative/ReactNativeTextComponent.js rename to src/renderers/native/ReactNativeTextComponent.js diff --git a/src/renderers/native/ReactNative/ReactNativeTreeTraversal.js b/src/renderers/native/ReactNativeTreeTraversal.js similarity index 100% rename from src/renderers/native/ReactNative/ReactNativeTreeTraversal.js rename to src/renderers/native/ReactNativeTreeTraversal.js diff --git a/src/renderers/native/ReactNative/__mocks__/InitializeJavaScriptAppEngine.js b/src/renderers/native/__mocks__/InitializeJavaScriptAppEngine.js similarity index 100% rename from src/renderers/native/ReactNative/__mocks__/InitializeJavaScriptAppEngine.js rename to src/renderers/native/__mocks__/InitializeJavaScriptAppEngine.js diff --git a/src/renderers/native/ReactNative/__mocks__/JSTimersExecution.js b/src/renderers/native/__mocks__/JSTimersExecution.js similarity index 100% rename from src/renderers/native/ReactNative/__mocks__/JSTimersExecution.js rename to src/renderers/native/__mocks__/JSTimersExecution.js diff --git a/src/renderers/native/ReactNative/__mocks__/RCTEventEmitter.js b/src/renderers/native/__mocks__/RCTEventEmitter.js similarity index 100% rename from src/renderers/native/ReactNative/__mocks__/RCTEventEmitter.js rename to src/renderers/native/__mocks__/RCTEventEmitter.js diff --git a/src/renderers/native/ReactNative/__mocks__/RCTLog.js b/src/renderers/native/__mocks__/RCTLog.js similarity index 100% rename from src/renderers/native/ReactNative/__mocks__/RCTLog.js rename to src/renderers/native/__mocks__/RCTLog.js diff --git a/src/renderers/native/ReactNative/__mocks__/TextInputState.js b/src/renderers/native/__mocks__/TextInputState.js similarity index 100% rename from src/renderers/native/ReactNative/__mocks__/TextInputState.js rename to src/renderers/native/__mocks__/TextInputState.js diff --git a/src/renderers/native/ReactNative/__mocks__/UIManager.js b/src/renderers/native/__mocks__/UIManager.js similarity index 100% rename from src/renderers/native/ReactNative/__mocks__/UIManager.js rename to src/renderers/native/__mocks__/UIManager.js diff --git a/src/renderers/native/ReactNative/__mocks__/deepDiffer.js b/src/renderers/native/__mocks__/deepDiffer.js similarity index 100% rename from src/renderers/native/ReactNative/__mocks__/deepDiffer.js rename to src/renderers/native/__mocks__/deepDiffer.js diff --git a/src/renderers/native/ReactNative/__mocks__/deepFreezeAndThrowOnMutationInDev.js b/src/renderers/native/__mocks__/deepFreezeAndThrowOnMutationInDev.js similarity index 100% rename from src/renderers/native/ReactNative/__mocks__/deepFreezeAndThrowOnMutationInDev.js rename to src/renderers/native/__mocks__/deepFreezeAndThrowOnMutationInDev.js diff --git a/src/renderers/native/ReactNative/__mocks__/flattenStyle.js b/src/renderers/native/__mocks__/flattenStyle.js similarity index 100% rename from src/renderers/native/ReactNative/__mocks__/flattenStyle.js rename to src/renderers/native/__mocks__/flattenStyle.js diff --git a/src/renderers/native/ReactNative/__tests__/ReactNativeAttributePayload-test.js b/src/renderers/native/__tests__/ReactNativeAttributePayload-test.js similarity index 100% rename from src/renderers/native/ReactNative/__tests__/ReactNativeAttributePayload-test.js rename to src/renderers/native/__tests__/ReactNativeAttributePayload-test.js diff --git a/src/renderers/native/ReactNative/__tests__/ReactNativeMount-test.js b/src/renderers/native/__tests__/ReactNativeMount-test.js similarity index 100% rename from src/renderers/native/ReactNative/__tests__/ReactNativeMount-test.js rename to src/renderers/native/__tests__/ReactNativeMount-test.js diff --git a/src/renderers/native/ReactNative/createReactNativeComponentClass.js b/src/renderers/native/createReactNativeComponentClass.js similarity index 100% rename from src/renderers/native/ReactNative/createReactNativeComponentClass.js rename to src/renderers/native/createReactNativeComponentClass.js diff --git a/src/renderers/native/ReactNative/findNodeHandle.js b/src/renderers/native/findNodeHandle.js similarity index 100% rename from src/renderers/native/ReactNative/findNodeHandle.js rename to src/renderers/native/findNodeHandle.js From 69bb9e3c8cde351360f400d23fbc3232e7e2bec4 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Tue, 3 May 2016 16:46:51 +0100 Subject: [PATCH 04/32] Remove some mocks that are already packaged by InitializeJavaScriptAppEngine (#6642) (cherry picked from commit 760b1ef4c3b4706584f973bbcab273d86fa0a799) --- gulpfile.js | 2 -- .../native/ReactNativeDefaultInjection.js | 13 ++++++++----- src/renderers/native/ReactNativeEventEmitter.js | 2 +- .../__mocks__/InitializeJavaScriptAppEngine.js | 2 +- .../native/__mocks__/JSTimersExecution.js | 14 -------------- src/renderers/native/__mocks__/RCTEventEmitter.js | 6 ++++-- src/renderers/native/__mocks__/RCTLog.js | 14 -------------- 7 files changed, 14 insertions(+), 39 deletions(-) delete mode 100644 src/renderers/native/__mocks__/JSTimersExecution.js delete mode 100644 src/renderers/native/__mocks__/RCTLog.js diff --git a/gulpfile.js b/gulpfile.js index a51a094367..c3db76382c 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -39,9 +39,7 @@ var whiteListNames = [ 'deepFreezeAndThrowOnMutationInDev', 'flattenStyle', 'InitializeJavaScriptAppEngine', - 'JSTimersExecution', 'RCTEventEmitter', - 'RCTLog', 'TextInputState', 'UIManager', 'View', diff --git a/src/renderers/native/ReactNativeDefaultInjection.js b/src/renderers/native/ReactNativeDefaultInjection.js index 21fc07ba88..be8a5625ea 100644 --- a/src/renderers/native/ReactNativeDefaultInjection.js +++ b/src/renderers/native/ReactNativeDefaultInjection.js @@ -15,11 +15,13 @@ * Make sure essential globals are available and are patched correctly. Please don't remove this * line. Bundles created by react-packager `require` it before executing any application code. This * ensures it exists in the dependency graph and can be `require`d. + * TODO: require this in packager, not in React #10932517 */ require('InitializeJavaScriptAppEngine'); var EventPluginHub = require('EventPluginHub'); var EventPluginUtils = require('EventPluginUtils'); +var RCTEventEmitter = require('RCTEventEmitter'); var ReactComponentEnvironment = require('ReactComponentEnvironment'); var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy'); var ReactElement = require('ReactElement'); @@ -28,6 +30,7 @@ var ReactNativeBridgeEventPlugin = require('ReactNativeBridgeEventPlugin'); var ReactNativeComponent = require('ReactNativeComponent'); var ReactNativeComponentEnvironment = require('ReactNativeComponentEnvironment'); var ReactNativeComponentTree = require('ReactNativeComponentTree'); +var ReactNativeEventEmitter = require('ReactNativeEventEmitter'); var ReactNativeEventPluginOrder = require('ReactNativeEventPluginOrder'); var ReactNativeGlobalResponderHandler = require('ReactNativeGlobalResponderHandler'); var ReactNativeTextComponent = require('ReactNativeTextComponent'); @@ -38,12 +41,12 @@ var ResponderEventPlugin = require('ResponderEventPlugin'); var invariant = require('invariant'); -// Just to ensure this gets packaged, since its only caller is from Native. -require('RCTEventEmitter'); -require('RCTLog'); -require('JSTimersExecution'); - function inject() { + /** + * Register the event emitter with the native bridge + */ + RCTEventEmitter.register(ReactNativeEventEmitter); + /** * Inject module for resolving DOM hierarchy and plugin ordering. */ diff --git a/src/renderers/native/ReactNativeEventEmitter.js b/src/renderers/native/ReactNativeEventEmitter.js index 0018b63e55..699b092697 100644 --- a/src/renderers/native/ReactNativeEventEmitter.js +++ b/src/renderers/native/ReactNativeEventEmitter.js @@ -11,13 +11,13 @@ */ 'use strict'; +var EventConstants = require('EventConstants'); var EventPluginHub = require('EventPluginHub'); var EventPluginRegistry = require('EventPluginRegistry'); var ReactEventEmitterMixin = require('ReactEventEmitterMixin'); var ReactNativeComponentTree = require('ReactNativeComponentTree'); var ReactNativeTagHandles = require('ReactNativeTagHandles'); var ReactUpdates = require('ReactUpdates'); -var EventConstants = require('EventConstants'); var warning = require('warning'); diff --git a/src/renderers/native/__mocks__/InitializeJavaScriptAppEngine.js b/src/renderers/native/__mocks__/InitializeJavaScriptAppEngine.js index 3f4b4fdd0f..bc540c10d2 100644 --- a/src/renderers/native/__mocks__/InitializeJavaScriptAppEngine.js +++ b/src/renderers/native/__mocks__/InitializeJavaScriptAppEngine.js @@ -11,4 +11,4 @@ // Noop -// TODO: Move all initialization callers back into react-native +// TODO #10932517: Move all initialization callers back into react-native diff --git a/src/renderers/native/__mocks__/JSTimersExecution.js b/src/renderers/native/__mocks__/JSTimersExecution.js deleted file mode 100644 index 3f4b4fdd0f..0000000000 --- a/src/renderers/native/__mocks__/JSTimersExecution.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright 2013-2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -'use strict'; - -// Noop - -// TODO: Move all initialization callers back into react-native diff --git a/src/renderers/native/__mocks__/RCTEventEmitter.js b/src/renderers/native/__mocks__/RCTEventEmitter.js index 3f4b4fdd0f..d6e66ad500 100644 --- a/src/renderers/native/__mocks__/RCTEventEmitter.js +++ b/src/renderers/native/__mocks__/RCTEventEmitter.js @@ -9,6 +9,8 @@ 'use strict'; -// Noop +var RCTEventEmitter = { + register: jest.fn(), +}; -// TODO: Move all initialization callers back into react-native +module.exports = RCTEventEmitter; diff --git a/src/renderers/native/__mocks__/RCTLog.js b/src/renderers/native/__mocks__/RCTLog.js deleted file mode 100644 index 3f4b4fdd0f..0000000000 --- a/src/renderers/native/__mocks__/RCTLog.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright 2013-2015, Facebook, Inc. - * All rights reserved. - * - * This source code is licensed under the BSD-style license found in the - * LICENSE file in the root directory of this source tree. An additional grant - * of patent rights can be found in the PATENTS file in the same directory. - */ - -'use strict'; - -// Noop - -// TODO: Move all initialization callers back into react-native From 1b22f12acd8947f4e5cf2ce629cd4f909a03e97a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20O=E2=80=99Shannessy?= Date: Mon, 2 May 2016 13:16:25 -0700 Subject: [PATCH 05/32] Ensure we're using latest object-assign (#6681) This picks up the change to feature test against order bugs. (cherry picked from commit 468901c336c8562d3fdb8f4c985b579761d48778) --- package.json | 2 +- packages/react/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 38b3480878..5f0015ea77 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "gzip-js": "~0.3.2", "jest-cli": "^0.9.0", "loose-envify": "^1.1.0", - "object-assign": "^4.0.1", + "object-assign": "^4.1.0", "platform": "^1.1.0", "run-sequence": "^1.1.4", "through2": "^2.0.0", diff --git a/packages/react/package.json b/packages/react/package.json index 4a3ef56b14..693c5d66fa 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -25,7 +25,7 @@ "dependencies": { "fbjs": "^0.8.0", "loose-envify": "^1.1.0", - "object-assign": "^4.0.1" + "object-assign": "^4.1.0" }, "browserify": { "transform": [ From d737dc6dd267f30d82daf0b1f4b37319f693f046 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20O=E2=80=99Shannessy?= Date: Tue, 3 May 2016 12:21:33 -0700 Subject: [PATCH 06/32] 15.0.3-alpha.1 --- package.json | 2 +- packages/react-addons/package.json | 4 ++-- packages/react-dom/package.json | 4 ++-- packages/react-native-renderer/package.json | 4 ++-- packages/react/package.json | 2 +- src/ReactVersion.js | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 5f0015ea77..0961fbfa84 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "react-build", "private": true, - "version": "15.0.2", + "version": "15.0.3-alpha.1", "devDependencies": { "async": "^1.5.0", "babel-cli": "^6.6.5", diff --git a/packages/react-addons/package.json b/packages/react-addons/package.json index b3234fe909..4039200cb8 100644 --- a/packages/react-addons/package.json +++ b/packages/react-addons/package.json @@ -1,6 +1,6 @@ { "name": "react-addons-template", - "version": "15.0.2", + "version": "15.0.3-alpha.1", "main": "index.js", "repository": "facebook/react", "keywords": [ @@ -10,6 +10,6 @@ "license": "BSD-3-Clause", "dependencies": {}, "peerDependencies": { - "react": "^15.0.2" + "react": "^15.0.3-alpha.1" } } diff --git a/packages/react-dom/package.json b/packages/react-dom/package.json index dca80f0d0a..77f50174df 100644 --- a/packages/react-dom/package.json +++ b/packages/react-dom/package.json @@ -1,6 +1,6 @@ { "name": "react-dom", - "version": "15.0.2", + "version": "15.0.3-alpha.1", "description": "React package for working with the DOM.", "main": "index.js", "repository": "facebook/react", @@ -14,6 +14,6 @@ "homepage": "https://facebook.github.io/react/", "dependencies": {}, "peerDependencies": { - "react": "^15.0.2" + "react": "^15.0.3-alpha.1" } } diff --git a/packages/react-native-renderer/package.json b/packages/react-native-renderer/package.json index 89adaee983..703602be46 100644 --- a/packages/react-native-renderer/package.json +++ b/packages/react-native-renderer/package.json @@ -1,6 +1,6 @@ { "name": "react-native-renderer", - "version": "15.0.2", + "version": "15.0.3-alpha.1", "description": "React package for use inside react-native.", "main": "index.js", "repository": "facebook/react", @@ -14,6 +14,6 @@ }, "homepage": "https://facebook.github.io/react-native/", "dependencies": { - "react": "^15.0.2" + "react": "^15.0.3-alpha.1" } } diff --git a/packages/react/package.json b/packages/react/package.json index 693c5d66fa..beb473fe5b 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,7 +1,7 @@ { "name": "react", "description": "React is a JavaScript library for building user interfaces.", - "version": "15.0.2", + "version": "15.0.3-alpha.1", "keywords": [ "react" ], diff --git a/src/ReactVersion.js b/src/ReactVersion.js index 5735f2b6c6..e03beff969 100644 --- a/src/ReactVersion.js +++ b/src/ReactVersion.js @@ -11,4 +11,4 @@ 'use strict'; -module.exports = '15.0.2'; +module.exports = '15.0.3-alpha.1'; From 2f435912d8d77f83077508fc6a7d9ca107dacec1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20O=E2=80=99Shannessy?= Date: Tue, 3 May 2016 12:38:01 -0700 Subject: [PATCH 07/32] Shrinkwrap to account for updated object-assign --- npm-shrinkwrap.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 3fc4f414a5..6a912fb228 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,6 +1,6 @@ { "name": "react-build", - "version": "15.0.1", + "version": "15.0.3-alpha.1", "dependencies": { "async": { "version": "1.5.2", @@ -14353,9 +14353,9 @@ } }, "object-assign": { - "version": "4.0.1", - "from": "object-assign@>=4.0.1 <5.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.0.1.tgz" + "version": "4.1.0", + "from": "object-assign@>=4.1.0 <5.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz" }, "platform": { "version": "1.3.1", From b0deadc05db767b1229daf29160ff7f73d1c73f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Sch=C3=A4r?= Date: Sat, 30 Apr 2016 00:05:33 +0200 Subject: [PATCH 08/32] Allow custom elements extending native ones (#6570) ...by passing the `is` attribute as the second param to `createElement`. See http://webcomponents.org/polyfills/custom-elements/ (cherry picked from commit 3d31361cfbe2ceb74a3f365515cd4a3298b021f9) --- src/renderers/dom/shared/ReactDOMComponent.js | 2 +- .../dom/shared/__tests__/ReactDOMComponent-test.js | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/renderers/dom/shared/ReactDOMComponent.js b/src/renderers/dom/shared/ReactDOMComponent.js index 445c9d8170..c7c8a0c747 100644 --- a/src/renderers/dom/shared/ReactDOMComponent.js +++ b/src/renderers/dom/shared/ReactDOMComponent.js @@ -569,7 +569,7 @@ ReactDOMComponent.Mixin = { div.innerHTML = `<${type}>`; el = div.removeChild(div.firstChild); } else { - el = ownerDocument.createElement(this._currentElement.type); + el = ownerDocument.createElement(this._currentElement.type, props.is || null); } } else { el = ownerDocument.createElementNS( diff --git a/src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js b/src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js index d009ed80d0..e7d6765e6b 100644 --- a/src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js +++ b/src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js @@ -15,12 +15,14 @@ describe('ReactDOMComponent', function() { var React; var ReactDOM; + var ReactDOMFeatureFlags; var ReactDOMServer; beforeEach(function() { jest.resetModuleRegistry(); React = require('React'); ReactDOM = require('ReactDOM'); + ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); ReactDOMServer = require('ReactDOMServer'); }); @@ -886,6 +888,17 @@ describe('ReactDOMComponent', function() { 'or use `props.dangerouslySetInnerHTML`. Check the render method of X.' ); }); + + it('should support custom elements which extend native elements', function() { + if (ReactDOMFeatureFlags.useCreateElement) { + var container = document.createElement('div'); + spyOn(document, 'createElement').andCallThrough(); + ReactDOM.render(
, container); + expect(document.createElement).toHaveBeenCalledWith('div', 'custom-div'); + } else { + expect(ReactDOMServer.renderToString(
)).toContain('is="custom-div"'); + } + }); }); describe('updateComponent', function() { From ff3cec5bebe97d68c8a77d9a159338970a0e3564 Mon Sep 17 00:00:00 2001 From: Brandon Dail Date: Mon, 2 May 2016 16:26:35 -0500 Subject: [PATCH 09/32] Return early from enqueuePutListener for SSR (#6678) (cherry picked from commit eb116482a34a65c52f7de92493985852371da727) --- src/renderers/dom/shared/ReactDOMComponent.js | 8 ++++---- .../dom/shared/__tests__/ReactDOMComponent-test.js | 6 ++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/renderers/dom/shared/ReactDOMComponent.js b/src/renderers/dom/shared/ReactDOMComponent.js index c7c8a0c747..2d3907b448 100644 --- a/src/renderers/dom/shared/ReactDOMComponent.js +++ b/src/renderers/dom/shared/ReactDOMComponent.js @@ -34,6 +34,7 @@ var ReactDOMSelect = require('ReactDOMSelect'); var ReactDOMTextarea = require('ReactDOMTextarea'); var ReactMultiChild = require('ReactMultiChild'); var ReactPerf = require('ReactPerf'); +var ReactServerRenderingTransaction = require('ReactServerRenderingTransaction'); var escapeTextContentForBrowser = require('escapeTextContentForBrowser'); var invariant = require('invariant'); @@ -207,6 +208,9 @@ function assertValidProps(component, props) { } function enqueuePutListener(inst, registrationName, listener, transaction) { + if (transaction instanceof ReactServerRenderingTransaction) { + return; + } if (__DEV__) { // IE8 has no API for event capturing and the `onScroll` event doesn't // bubble. @@ -218,10 +222,6 @@ function enqueuePutListener(inst, registrationName, listener, transaction) { var containerInfo = inst._nativeContainerInfo; var isDocumentFragment = containerInfo._node && containerInfo._node.nodeType === DOC_FRAGMENT_TYPE; var doc = isDocumentFragment ? containerInfo._node : containerInfo._ownerDocument; - if (!doc) { - // Server rendering. - return; - } listenTo(registrationName, doc); transaction.getReactMountReady().enqueue(putListener, { inst: inst, diff --git a/src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js b/src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js index e7d6765e6b..ea1826624b 100644 --- a/src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js +++ b/src/renderers/dom/shared/__tests__/ReactDOMComponent-test.js @@ -1063,6 +1063,12 @@ describe('ReactDOMComponent', function() { 'Warning: This browser doesn\'t support the `onScroll` event' ); }); + + it('should not warn when server-side rendering `onScroll`', function() { + spyOn(console, 'error'); + ReactDOMServer.renderToString(
{}}/>); + expect(console.error).not.toHaveBeenCalled(); + }); }); describe('tag sanitization', function() { From d1f519e0cd54cdd93ddae9ed41a60dfc58d1c759 Mon Sep 17 00:00:00 2001 From: Jake Boone Date: Mon, 2 May 2016 15:23:22 -0700 Subject: [PATCH 10/32] Grammar correction in ReactDOMInput.js warning (#6657) Changed "a uncontrolled input" to "an uncontrolled input". (cherry picked from commit 393a1798fadb42852b2c7ae6babdc596e423c917) --- src/renderers/dom/client/wrappers/ReactDOMInput.js | 2 +- .../dom/client/wrappers/__tests__/ReactDOMInput-test.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/renderers/dom/client/wrappers/ReactDOMInput.js b/src/renderers/dom/client/wrappers/ReactDOMInput.js index c1ad1a6330..859177f354 100644 --- a/src/renderers/dom/client/wrappers/ReactDOMInput.js +++ b/src/renderers/dom/client/wrappers/ReactDOMInput.js @@ -169,7 +169,7 @@ var ReactDOMInput = { ) { warning( false, - '%s is changing a uncontrolled input of type %s to be controlled. ' + + '%s is changing an uncontrolled input of type %s to be controlled. ' + 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' + 'Decide between using a controlled or uncontrolled input ' + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components', diff --git a/src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js b/src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js index 7a1f57dd0d..664f67e89e 100644 --- a/src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js +++ b/src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js @@ -488,7 +488,7 @@ describe('ReactDOMInput', function() { ReactDOM.render(, container); expect(console.error.argsForCall.length).toBe(1); expect(console.error.argsForCall[0][0]).toContain( - 'A component is changing a uncontrolled input of type text to be controlled. ' + + 'A component is changing an uncontrolled input of type text to be controlled. ' + 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' + 'Decide between using a controlled or uncontrolled input ' + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components' @@ -530,7 +530,7 @@ describe('ReactDOMInput', function() { ReactDOM.render(, container); expect(console.error.argsForCall.length).toBe(1); expect(console.error.argsForCall[0][0]).toContain( - 'A component is changing a uncontrolled input of type checkbox to be controlled. ' + + 'A component is changing an uncontrolled input of type checkbox to be controlled. ' + 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' + 'Decide between using a controlled or uncontrolled input ' + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components' @@ -572,7 +572,7 @@ describe('ReactDOMInput', function() { ReactDOM.render(, container); expect(console.error.argsForCall.length).toBe(1); expect(console.error.argsForCall[0][0]).toContain( - 'A component is changing a uncontrolled input of type radio to be controlled. ' + + 'A component is changing an uncontrolled input of type radio to be controlled. ' + 'Input elements should not switch from uncontrolled to controlled (or vice versa). ' + 'Decide between using a controlled or uncontrolled input ' + 'element for the lifetime of the component. More info: https://fb.me/react-controlled-components' From c3d99b5292b02836df5fa9d28a551181ead2683f Mon Sep 17 00:00:00 2001 From: Tanase Hagi Date: Tue, 3 May 2016 06:50:25 +0300 Subject: [PATCH 11/32] Update examples/basic-commonjs/package.json (#6685) (cherry picked from commit 83521bddb00988531c852122f89417d40e13caa9) --- examples/basic-commonjs/package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/basic-commonjs/package.json b/examples/basic-commonjs/package.json index 89490abdf9..e42eb80de4 100644 --- a/examples/basic-commonjs/package.json +++ b/examples/basic-commonjs/package.json @@ -6,11 +6,11 @@ "dependencies": { "babel-preset-es2015": "^6.6.0", "babel-preset-react": "^6.5.0", - "babelify": "^7.2.0", - "browserify": "^11.0.1", - "react": "15.0.1", - "react-dom": "15.0.1", - "watchify": "^3.4.0" + "babelify": "^7.3.0", + "browserify": "^13.0.0", + "react": "^15.0.2", + "react-dom": "^15.0.2", + "watchify": "^3.7.0" }, "scripts": { "build": "browserify ./index.js -t babelify -o bundle.js", From 743e4c6231c178f03c129cc1ca7030f7c8eaa136 Mon Sep 17 00:00:00 2001 From: Andreas Svensson Date: Fri, 6 May 2016 00:12:11 +0200 Subject: [PATCH 12/32] DOMLazyTree, populate before insertion into DOM (#6691) (cherry picked from commit 2af4765a2af0d4de81d2f23f702d9d39d8917a0b) --- src/renderers/dom/client/utils/DOMLazyTree.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/renderers/dom/client/utils/DOMLazyTree.js b/src/renderers/dom/client/utils/DOMLazyTree.js index 249eb475c5..8212d8751e 100644 --- a/src/renderers/dom/client/utils/DOMLazyTree.js +++ b/src/renderers/dom/client/utils/DOMLazyTree.js @@ -11,9 +11,14 @@ 'use strict'; +var DOMNamespaces = require('DOMNamespaces'); + var createMicrosoftUnsafeLocalFunction = require('createMicrosoftUnsafeLocalFunction'); var setTextContent = require('setTextContent'); +var ELEMENT_NODE_TYPE = 1; +var DOCUMENT_FRAGMENT_NODE_TYPE = 11; + /** * In IE (8-11) and Edge, appending nodes with no children is dramatically * faster than appending a full subtree, so we essentially queue up the @@ -56,8 +61,15 @@ var insertTreeBefore = createMicrosoftUnsafeLocalFunction( // DocumentFragments aren't actually part of the DOM after insertion so // appending children won't update the DOM. We need to ensure the fragment // is properly populated first, breaking out of our lazy approach for just - // this level. - if (tree.node.nodeType === 11) { + // this level. Also, some plugins (like Flash Player) will read + // nodes immediately upon insertion into the DOM, so + // must also be populated prior to insertion into the DOM. + if (tree.node.nodeType === DOCUMENT_FRAGMENT_NODE_TYPE + || + tree.node.nodeType === ELEMENT_NODE_TYPE && + tree.node.nodeName.toLowerCase() === 'object' && + (tree.node.namespaceURI == null || + tree.node.namespaceURI === DOMNamespaces.html)) { insertTreeChildren(tree); parentNode.insertBefore(tree.node, referenceNode); } else { From 0244879c8fceb714039a9e95e53d9ad6ad5889a8 Mon Sep 17 00:00:00 2001 From: Jim Date: Fri, 6 May 2016 16:54:06 -0700 Subject: [PATCH 13/32] Basic SSR support for error boundaries (#6694) (cherry picked from commit 96cb8c5fc4ca20ff7393b5e7fd0271bd7c18cb7d) --- .../__tests__/ReactErrorBoundaries-test.js | 37 +++++++++++++++++++ .../server/ReactServerRenderingTransaction.js | 6 +++ 2 files changed, 43 insertions(+) diff --git a/src/core/__tests__/ReactErrorBoundaries-test.js b/src/core/__tests__/ReactErrorBoundaries-test.js index 0ba37323d5..6272c89c58 100644 --- a/src/core/__tests__/ReactErrorBoundaries-test.js +++ b/src/core/__tests__/ReactErrorBoundaries-test.js @@ -13,11 +13,13 @@ var React; var ReactDOM; +var ReactDOMServer; describe('ReactErrorBoundaries', function() { beforeEach(function() { ReactDOM = require('ReactDOM'); + ReactDOMServer = require('ReactDOMServer'); React = require('React'); }); @@ -55,6 +57,41 @@ describe('ReactErrorBoundaries', function() { expect(EventPluginHub.putListener).not.toBeCalled(); }); + it('renders an error state (ssr)', function() { + class Angry extends React.Component { + render() { + throw new Error('Please, do not render me.'); + } + } + + class Boundary extends React.Component { + constructor(props) { + super(props); + this.state = {error: false}; + } + render() { + if (!this.state.error) { + return (
); + } else { + return (
Happy Birthday!
); + } + } + onClick() { + /* do nothing */ + } + unstable_handleError() { + this.setState({error: true}); + } + } + + var EventPluginHub = require('EventPluginHub'); + var container = document.createElement('div'); + EventPluginHub.putListener = jest.fn(); + container.innerHTML = ReactDOMServer.renderToString(); + expect(container.firstChild.innerHTML).toBe('Happy Birthday!'); + expect(EventPluginHub.putListener).not.toBeCalled(); + }); + it('will catch exceptions in componentWillUnmount', function() { class ErrorBoundary extends React.Component { constructor() { diff --git a/src/renderers/dom/server/ReactServerRenderingTransaction.js b/src/renderers/dom/server/ReactServerRenderingTransaction.js index 176a550c59..e7909eeeed 100644 --- a/src/renderers/dom/server/ReactServerRenderingTransaction.js +++ b/src/renderers/dom/server/ReactServerRenderingTransaction.js @@ -60,6 +60,12 @@ var Mixin = { */ destructor: function() { }, + + checkpoint: function() { + }, + + rollback: function() { + }, }; From 4f0163fd38954b83c6c2abf04436a45f78d96d0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20O=E2=80=99Shannessy?= Date: Tue, 3 May 2016 13:58:06 -0700 Subject: [PATCH 14/32] Ensure babelrc gets added to zipfile too (#6688) (cherry picked from commit 48f4684b5ff09fd76c223c598aa7bde4ac2ee848) --- grunt/config/compress.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/grunt/config/compress.js b/grunt/config/compress.js index f225443776..020fe3d425 100644 --- a/grunt/config/compress.js +++ b/grunt/config/compress.js @@ -10,7 +10,7 @@ module.exports = { archive: './build/react-' + version + '.zip', }, files: [ - {cwd: './build/starter', src: ['**'], dest: 'react-' + version + '/'}, + {cwd: './build/starter', src: ['**'], dot: true, dest: 'react-' + version + '/'}, ], }, }; From d8d6c7a07b6f0b5015286b62fdb0c5b59a243c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20O=E2=80=99Shannessy?= Date: Mon, 9 May 2016 21:32:13 -0700 Subject: [PATCH 15/32] 15.0.3-alpha.2 --- package.json | 2 +- packages/react-addons/package.json | 4 ++-- packages/react-dom/package.json | 4 ++-- packages/react-native-renderer/package.json | 4 ++-- packages/react/package.json | 2 +- src/ReactVersion.js | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 0961fbfa84..6616bd76be 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "react-build", "private": true, - "version": "15.0.3-alpha.1", + "version": "15.0.3-alpha.2", "devDependencies": { "async": "^1.5.0", "babel-cli": "^6.6.5", diff --git a/packages/react-addons/package.json b/packages/react-addons/package.json index 4039200cb8..119b550ff7 100644 --- a/packages/react-addons/package.json +++ b/packages/react-addons/package.json @@ -1,6 +1,6 @@ { "name": "react-addons-template", - "version": "15.0.3-alpha.1", + "version": "15.0.3-alpha.2", "main": "index.js", "repository": "facebook/react", "keywords": [ @@ -10,6 +10,6 @@ "license": "BSD-3-Clause", "dependencies": {}, "peerDependencies": { - "react": "^15.0.3-alpha.1" + "react": "^15.0.3-alpha.2" } } diff --git a/packages/react-dom/package.json b/packages/react-dom/package.json index 77f50174df..cf5a937542 100644 --- a/packages/react-dom/package.json +++ b/packages/react-dom/package.json @@ -1,6 +1,6 @@ { "name": "react-dom", - "version": "15.0.3-alpha.1", + "version": "15.0.3-alpha.2", "description": "React package for working with the DOM.", "main": "index.js", "repository": "facebook/react", @@ -14,6 +14,6 @@ "homepage": "https://facebook.github.io/react/", "dependencies": {}, "peerDependencies": { - "react": "^15.0.3-alpha.1" + "react": "^15.0.3-alpha.2" } } diff --git a/packages/react-native-renderer/package.json b/packages/react-native-renderer/package.json index 703602be46..c4b0cc8ec4 100644 --- a/packages/react-native-renderer/package.json +++ b/packages/react-native-renderer/package.json @@ -1,6 +1,6 @@ { "name": "react-native-renderer", - "version": "15.0.3-alpha.1", + "version": "15.0.3-alpha.2", "description": "React package for use inside react-native.", "main": "index.js", "repository": "facebook/react", @@ -14,6 +14,6 @@ }, "homepage": "https://facebook.github.io/react-native/", "dependencies": { - "react": "^15.0.3-alpha.1" + "react": "^15.0.3-alpha.2" } } diff --git a/packages/react/package.json b/packages/react/package.json index beb473fe5b..08b510996a 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -1,7 +1,7 @@ { "name": "react", "description": "React is a JavaScript library for building user interfaces.", - "version": "15.0.3-alpha.1", + "version": "15.0.3-alpha.2", "keywords": [ "react" ], diff --git a/src/ReactVersion.js b/src/ReactVersion.js index e03beff969..0dd608b17d 100644 --- a/src/ReactVersion.js +++ b/src/ReactVersion.js @@ -11,4 +11,4 @@ 'use strict'; -module.exports = '15.0.3-alpha.1'; +module.exports = '15.0.3-alpha.2'; From 42d27cd15239691cd6f5fa3d7209ed4228840699 Mon Sep 17 00:00:00 2001 From: Jim Date: Thu, 7 Apr 2016 14:09:18 -0700 Subject: [PATCH 16/32] Merge pull request #6134 from richardscarrott/master Warn if props obj passed into createElement / cloneElement inherits from anything other than Object (cherry picked from commit 7b47e3e537c91bc5e43de087d831e4abf017ad96) --- src/isomorphic/classic/element/ReactElement.js | 16 ++++++++++++++++ .../element/__tests__/ReactElement-test.js | 12 ++++++++++++ .../element/__tests__/ReactElementClone-test.js | 12 ++++++++++++ 3 files changed, 40 insertions(+) diff --git a/src/isomorphic/classic/element/ReactElement.js b/src/isomorphic/classic/element/ReactElement.js index ee46a5f392..307bb4d5c8 100644 --- a/src/isomorphic/classic/element/ReactElement.js +++ b/src/isomorphic/classic/element/ReactElement.js @@ -126,6 +126,13 @@ ReactElement.createElement = function(type, config, children) { if (config != null) { if (__DEV__) { + warning( + /* eslint-disable no-proto */ + config.__proto__ == null || config.__proto__ === Object.prototype, + /* eslint-enable no-proto */ + 'React.createElement(...): Expected props argument to be a plain object. ' + + 'Properties defined in its prototype chain will be ignored.' + ); ref = !config.hasOwnProperty('ref') || Object.getOwnPropertyDescriptor(config, 'ref').get ? null : config.ref; key = !config.hasOwnProperty('key') || @@ -268,6 +275,15 @@ ReactElement.cloneElement = function(element, config, children) { var owner = element._owner; if (config != null) { + if (__DEV__) { + warning( + /* eslint-disable no-proto */ + config.__proto__ == null || config.__proto__ === Object.prototype, + /* eslint-enable no-proto */ + 'React.cloneElement(...): Expected props argument to be a plain object. ' + + 'Properties defined in its prototype chain will be ignored.' + ); + } if (config.ref !== undefined) { // Silently steal the ref from the parent. ref = config.ref; diff --git a/src/isomorphic/classic/element/__tests__/ReactElement-test.js b/src/isomorphic/classic/element/__tests__/ReactElement-test.js index 402c9b95ed..8b3900db9f 100644 --- a/src/isomorphic/classic/element/__tests__/ReactElement-test.js +++ b/src/isomorphic/classic/element/__tests__/ReactElement-test.js @@ -138,6 +138,18 @@ describe('ReactElement', function() { expect(element.props.foo).toBe(1); }); + it('warns if the config object inherits from any type other than Object', function() { + spyOn(console, 'error'); + React.createElement('div', {foo: 1}); + expect(console.error).not.toHaveBeenCalled(); + React.createElement('div', Object.create({foo: 1})); + expect(console.error.argsForCall.length).toBe(1); + expect(console.error.argsForCall[0][0]).toContain( + 'React.createElement(...): Expected props argument to be a plain object. ' + + 'Properties defined in its prototype chain will be ignored.' + ); + }); + it('extracts key and ref from the config', function() { var element = React.createFactory(ComponentClass)({ key: '12', diff --git a/src/isomorphic/classic/element/__tests__/ReactElementClone-test.js b/src/isomorphic/classic/element/__tests__/ReactElementClone-test.js index f4c5b4b761..20b4089726 100644 --- a/src/isomorphic/classic/element/__tests__/ReactElementClone-test.js +++ b/src/isomorphic/classic/element/__tests__/ReactElementClone-test.js @@ -66,6 +66,18 @@ describe('ReactElementClone', function() { expect(ReactDOM.findDOMNode(component).childNodes[0].className).toBe('xyz'); }); + it('should warn if the config object inherits from any type other than Object', function() { + spyOn(console, 'error'); + React.cloneElement('div', {foo: 1}); + expect(console.error).not.toHaveBeenCalled(); + React.cloneElement('div', Object.create({foo: 1})); + expect(console.error.argsForCall.length).toBe(1); + expect(console.error.argsForCall[0][0]).toContain( + 'React.cloneElement(...): Expected props argument to be a plain object. ' + + 'Properties defined in its prototype chain will be ignored.' + ); + }); + it('should keep the original ref if it is not overridden', function() { var Grandparent = React.createClass({ render: function() { From 258e591e458f5443bf74caaa57562aa50e7ea48a Mon Sep 17 00:00:00 2001 From: Jim Date: Thu, 7 Apr 2016 14:08:03 -0700 Subject: [PATCH 17/32] Merge pull request #6341 from borisyankov/master Add more information to warning 'Input elements must be either controlled or uncontrolled' (cherry picked from commit 006058daa5e1413bc46bd52f87385a1297101b63) --- src/renderers/dom/client/wrappers/ReactDOMInput.js | 12 ++++++++++-- .../client/wrappers/__tests__/ReactDOMInput-test.js | 2 ++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/renderers/dom/client/wrappers/ReactDOMInput.js b/src/renderers/dom/client/wrappers/ReactDOMInput.js index 859177f354..d4ce97698e 100644 --- a/src/renderers/dom/client/wrappers/ReactDOMInput.js +++ b/src/renderers/dom/client/wrappers/ReactDOMInput.js @@ -92,6 +92,8 @@ var ReactDOMInput = { inst._currentElement._owner ); + var owner = inst._currentElement._owner; + if (props.valueLink !== undefined && !didWarnValueLink) { warning( false, @@ -113,11 +115,14 @@ var ReactDOMInput = { ) { warning( false, + '%s contains an input of type %s with both checked and defaultChecked props. ' + 'Input elements must be either controlled or uncontrolled ' + '(specify either the checked prop, or the defaultChecked prop, but not ' + 'both). Decide between using a controlled or uncontrolled input ' + 'element and remove one of these props. More info: ' + - 'https://fb.me/react-controlled-components' + 'https://fb.me/react-controlled-components', + owner && owner.getName() || 'A component', + props.type ); didWarnCheckedDefaultChecked = true; } @@ -128,11 +133,14 @@ var ReactDOMInput = { ) { warning( false, + '%s contains an input of type %s with both value and defaultValue props. ' + 'Input elements must be either controlled or uncontrolled ' + '(specify either the value prop, or the defaultValue prop, but not ' + 'both). Decide between using a controlled or uncontrolled input ' + 'element and remove one of these props. More info: ' + - 'https://fb.me/react-controlled-components' + 'https://fb.me/react-controlled-components', + owner && owner.getName() || 'A component', + props.type ); didWarnValueDefaultValue = true; } diff --git a/src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js b/src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js index 664f67e89e..c749675262 100644 --- a/src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js +++ b/src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js @@ -422,6 +422,7 @@ describe('ReactDOMInput', function() { ); expect(console.error.argsForCall[0][0]).toContain( + 'A component contains an input of type radio with both checked and defaultChecked props. ' + 'Input elements must be either controlled or uncontrolled ' + '(specify either the checked prop, or the defaultChecked prop, but not ' + 'both). Decide between using a controlled or uncontrolled input ' + @@ -440,6 +441,7 @@ describe('ReactDOMInput', function() { ); expect(console.error.argsForCall[0][0]).toContain( + 'A component contains an input of type text with both value and defaultValue props. ' + 'Input elements must be either controlled or uncontrolled ' + '(specify either the value prop, or the defaultValue prop, but not ' + 'both). Decide between using a controlled or uncontrolled input ' + From 1f1dba92a85c71c8a418d7220f26217e953ce369 Mon Sep 17 00:00:00 2001 From: Jim Date: Thu, 7 Apr 2016 14:11:12 -0700 Subject: [PATCH 18/32] Merge pull request #6364 from p-jackson/issue-5700 Don't wrap drag events in IE/Edge in dev builds (cherry picked from commit 2e8f28c29fd366d83cdbfc00e2b2dcb526e25202) --- src/shared/utils/ReactErrorUtils.js | 42 +++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/src/shared/utils/ReactErrorUtils.js b/src/shared/utils/ReactErrorUtils.js index cff46bc15e..2746220920 100644 --- a/src/shared/utils/ReactErrorUtils.js +++ b/src/shared/utils/ReactErrorUtils.js @@ -11,6 +11,8 @@ 'use strict'; +var SyntheticDragEvent = require('SyntheticDragEvent'); + var caughtError = null; /** @@ -65,6 +67,11 @@ if (__DEV__) { typeof document.createEvent === 'function') { var fakeNode = document.createElement('react'); ReactErrorUtils.invokeGuardedCallback = function(name, func, a, b) { + if (!canWrapEvent(a)) { + invokeGuardedCallback(name, func, a, b); + return; + } + var boundFunc = func.bind(null, a, b); var evtType = `react-${name}`; fakeNode.addEventListener(evtType, boundFunc, false); @@ -74,6 +81,41 @@ if (__DEV__) { fakeNode.removeEventListener(evtType, boundFunc, false); }; } + + var cacheCanWrapEvent = null; + + /** + * IE and Edge don't allow access to the DataTransfer.dropEffect property when + * it's wrapped in another event. This function detects whether we're in an + * environment that behaves this way. + * + * @param {*} ev Event that is being tested + */ + function canWrapEvent(ev) { + if (!(ev instanceof SyntheticDragEvent)) { + return true; + } else if (cacheCanWrapEvent !== null) { + return cacheCanWrapEvent; + } + + var canAccessDropEffect = false; + function handleWrappedEvent() { + try { + ev.dataTransfer.dropEffect; // eslint-disable-line no-unused-expressions + canAccessDropEffect = true; + } catch (e) {} + } + + var wrappedEventName = 'react-wrappeddragevent'; + var wrappedEvent = document.createEvent('Event'); + wrappedEvent.initEvent(wrappedEventName, false, false); + fakeNode.addEventListener(wrappedEventName, handleWrappedEvent, false); + fakeNode.dispatchEvent(wrappedEvent); + fakeNode.removeEventListener(wrappedEventName, handleWrappedEvent, false); + + cacheCanWrapEvent = canAccessDropEffect; + return canAccessDropEffect; + } } module.exports = ReactErrorUtils; From 583d7205c6a6af06fda82b8c88246854ed95e3bc Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Tue, 26 Apr 2016 01:12:23 +0100 Subject: [PATCH 19/32] Merge pull request #6549 from gaearon/instrumentation-new Provide info about component tree to devtools (cherry picked from commit 76a4c46dbaadfd4e717aec7a9e401251cb9b402c) --- src/isomorphic/ReactDebugInstanceMap.js | 124 -- src/isomorphic/ReactDebugTool.js | 28 +- .../__tests__/ReactDebugInstanceMap-test.js | 173 -- .../devtools/ReactComponentTreeDevtool.js | 148 ++ .../ReactComponentTreeDevtool-test.js | 1725 +++++++++++++++++ src/renderers/dom/client/ReactMount.js | 11 +- .../dom/server/ReactServerRendering.js | 10 +- src/renderers/dom/shared/ReactDOMComponent.js | 44 + .../dom/shared/ReactDOMTextComponent.js | 7 + .../reconciler/ReactCompositeComponent.js | 24 + .../shared/reconciler/ReactMultiChild.js | 22 + .../shared/reconciler/ReactReconciler.js | 16 +- .../reconciler/instantiateReactComponent.js | 33 +- 13 files changed, 2053 insertions(+), 312 deletions(-) delete mode 100644 src/isomorphic/ReactDebugInstanceMap.js delete mode 100644 src/isomorphic/__tests__/ReactDebugInstanceMap-test.js create mode 100644 src/isomorphic/devtools/ReactComponentTreeDevtool.js create mode 100644 src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js diff --git a/src/isomorphic/ReactDebugInstanceMap.js b/src/isomorphic/ReactDebugInstanceMap.js deleted file mode 100644 index 50dddf4ad0..0000000000 --- a/src/isomorphic/ReactDebugInstanceMap.js +++ /dev/null @@ -1,124 +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. - * - * @providesModule ReactDebugInstanceMap - */ - -'use strict'; - -var warning = require('warning'); - -function checkValidInstance(internalInstance) { - if (!internalInstance) { - warning( - false, - 'There is an internal error in the React developer tools integration. ' + - 'Instead of an internal instance, received %s. ' + - 'Please report this as a bug in React.', - internalInstance - ); - return false; - } - var isValid = typeof internalInstance.mountComponent === 'function'; - warning( - isValid, - 'There is an internal error in the React developer tools integration. ' + - 'Instead of an internal instance, received an object with the following ' + - 'keys: %s. Please report this as a bug in React.', - Object.keys(internalInstance).join(', ') - ); - return isValid; -} - -var idCounter = 1; -var instancesByIDs = {}; -var instancesToIDs; - -function getIDForInstance(internalInstance) { - if (!instancesToIDs) { - instancesToIDs = new WeakMap(); - } - if (instancesToIDs.has(internalInstance)) { - return instancesToIDs.get(internalInstance); - } else { - var instanceID = (idCounter++).toString(); - instancesToIDs.set(internalInstance, instanceID); - return instanceID; - } -} - -function getInstanceByID(instanceID) { - return instancesByIDs[instanceID] || null; -} - -function isRegisteredInstance(internalInstance) { - var instanceID = getIDForInstance(internalInstance); - if (instanceID) { - return instancesByIDs.hasOwnProperty(instanceID); - } else { - return false; - } -} - -function registerInstance(internalInstance) { - var instanceID = getIDForInstance(internalInstance); - if (instanceID) { - instancesByIDs[instanceID] = internalInstance; - } -} - -function unregisterInstance(internalInstance) { - var instanceID = getIDForInstance(internalInstance); - if (instanceID) { - delete instancesByIDs[instanceID]; - } -} - -var ReactDebugInstanceMap = { - getIDForInstance(internalInstance) { - if (!checkValidInstance(internalInstance)) { - return null; - } - return getIDForInstance(internalInstance); - }, - getInstanceByID(instanceID) { - return getInstanceByID(instanceID); - }, - isRegisteredInstance(internalInstance) { - if (!checkValidInstance(internalInstance)) { - return false; - } - return isRegisteredInstance(internalInstance); - }, - registerInstance(internalInstance) { - if (!checkValidInstance(internalInstance)) { - return; - } - warning( - !isRegisteredInstance(internalInstance), - 'There is an internal error in the React developer tools integration. ' + - 'A registered instance should not be registered again. ' + - 'Please report this as a bug in React.' - ); - registerInstance(internalInstance); - }, - unregisterInstance(internalInstance) { - if (!checkValidInstance(internalInstance)) { - return; - } - warning( - isRegisteredInstance(internalInstance), - 'There is an internal error in the React developer tools integration. ' + - 'An unregistered instance should not be unregistered again. ' + - 'Please report this as a bug in React.' - ); - unregisterInstance(internalInstance); - }, -}; - -module.exports = ReactDebugInstanceMap; diff --git a/src/isomorphic/ReactDebugTool.js b/src/isomorphic/ReactDebugTool.js index 4d2c2a3536..49c6cb5500 100644 --- a/src/isomorphic/ReactDebugTool.js +++ b/src/isomorphic/ReactDebugTool.js @@ -58,17 +58,29 @@ var ReactDebugTool = { onSetState() { emitEvent('onSetState'); }, - onMountRootComponent(internalInstance) { - emitEvent('onMountRootComponent', internalInstance); + onSetDisplayName(debugID, displayName) { + emitEvent('onSetDisplayName', debugID, displayName); }, - onMountComponent(internalInstance) { - emitEvent('onMountComponent', internalInstance); + onSetChildren(debugID, childDebugIDs) { + emitEvent('onSetChildren', debugID, childDebugIDs); }, - onUpdateComponent(internalInstance) { - emitEvent('onUpdateComponent', internalInstance); + onSetOwner(debugID, ownerDebugID) { + emitEvent('onSetOwner', debugID, ownerDebugID); }, - onUnmountComponent(internalInstance) { - emitEvent('onUnmountComponent', internalInstance); + onSetText(debugID, text) { + emitEvent('onSetText', debugID, text); + }, + onMountRootComponent(debugID) { + emitEvent('onMountRootComponent', debugID); + }, + onMountComponent(debugID) { + emitEvent('onMountComponent', debugID); + }, + onUpdateComponent(debugID) { + emitEvent('onUpdateComponent', debugID); + }, + onUnmountComponent(debugID) { + emitEvent('onUnmountComponent', debugID); }, }; diff --git a/src/isomorphic/__tests__/ReactDebugInstanceMap-test.js b/src/isomorphic/__tests__/ReactDebugInstanceMap-test.js deleted file mode 100644 index d9a063e2c6..0000000000 --- a/src/isomorphic/__tests__/ReactDebugInstanceMap-test.js +++ /dev/null @@ -1,173 +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'; - -describe('ReactDebugInstanceMap', function() { - var React; - var ReactDebugInstanceMap; - var ReactDOM; - - beforeEach(function() { - jest.resetModuleRegistry(); - React = require('React'); - ReactDebugInstanceMap = require('ReactDebugInstanceMap'); - ReactDOM = require('ReactDOM'); - }); - - function createStubInstance() { - return { mountComponent: () => {} }; - } - - it('should register and unregister instances', function() { - var inst1 = createStubInstance(); - var inst2 = createStubInstance(); - - expect(ReactDebugInstanceMap.isRegisteredInstance(inst1)).toBe(false); - expect(ReactDebugInstanceMap.isRegisteredInstance(inst2)).toBe(false); - - ReactDebugInstanceMap.registerInstance(inst1); - expect(ReactDebugInstanceMap.isRegisteredInstance(inst1)).toBe(true); - expect(ReactDebugInstanceMap.isRegisteredInstance(inst2)).toBe(false); - - ReactDebugInstanceMap.registerInstance(inst2); - expect(ReactDebugInstanceMap.isRegisteredInstance(inst1)).toBe(true); - expect(ReactDebugInstanceMap.isRegisteredInstance(inst2)).toBe(true); - - ReactDebugInstanceMap.unregisterInstance(inst2); - expect(ReactDebugInstanceMap.isRegisteredInstance(inst1)).toBe(true); - expect(ReactDebugInstanceMap.isRegisteredInstance(inst2)).toBe(false); - - ReactDebugInstanceMap.unregisterInstance(inst1); - expect(ReactDebugInstanceMap.isRegisteredInstance(inst1)).toBe(false); - expect(ReactDebugInstanceMap.isRegisteredInstance(inst2)).toBe(false); - }); - - it('should assign stable IDs', function() { - var inst1 = createStubInstance(); - var inst2 = createStubInstance(); - - var inst1ID = ReactDebugInstanceMap.getIDForInstance(inst1); - var inst2ID = ReactDebugInstanceMap.getIDForInstance(inst2); - expect(typeof inst1ID).toBe('string'); - expect(typeof inst2ID).toBe('string'); - expect(inst1ID).not.toBe(inst2ID); - - ReactDebugInstanceMap.registerInstance(inst1); - ReactDebugInstanceMap.registerInstance(inst2); - expect(ReactDebugInstanceMap.getIDForInstance(inst1)).toBe(inst1ID); - expect(ReactDebugInstanceMap.getIDForInstance(inst2)).toBe(inst2ID); - - ReactDebugInstanceMap.unregisterInstance(inst1); - ReactDebugInstanceMap.unregisterInstance(inst2); - expect(ReactDebugInstanceMap.getIDForInstance(inst1)).toBe(inst1ID); - expect(ReactDebugInstanceMap.getIDForInstance(inst2)).toBe(inst2ID); - }); - - it('should retrieve registered instance by its ID', function() { - var inst1 = createStubInstance(); - var inst2 = createStubInstance(); - - var inst1ID = ReactDebugInstanceMap.getIDForInstance(inst1); - var inst2ID = ReactDebugInstanceMap.getIDForInstance(inst2); - expect(ReactDebugInstanceMap.getInstanceByID(inst1ID)).toBe(null); - expect(ReactDebugInstanceMap.getInstanceByID(inst2ID)).toBe(null); - - ReactDebugInstanceMap.registerInstance(inst1); - ReactDebugInstanceMap.registerInstance(inst2); - expect(ReactDebugInstanceMap.getInstanceByID(inst1ID)).toBe(inst1); - expect(ReactDebugInstanceMap.getInstanceByID(inst2ID)).toBe(inst2); - - ReactDebugInstanceMap.unregisterInstance(inst1); - ReactDebugInstanceMap.unregisterInstance(inst2); - expect(ReactDebugInstanceMap.getInstanceByID(inst1ID)).toBe(null); - expect(ReactDebugInstanceMap.getInstanceByID(inst2ID)).toBe(null); - }); - - it('should warn when registering an instance twice', function() { - spyOn(console, 'error'); - - var inst = createStubInstance(); - ReactDebugInstanceMap.registerInstance(inst); - expect(console.error.argsForCall.length).toBe(0); - - ReactDebugInstanceMap.registerInstance(inst); - expect(console.error.argsForCall.length).toBe(1); - expect(console.error.argsForCall[0][0]).toContain( - 'There is an internal error in the React developer tools integration. ' + - 'A registered instance should not be registered again. ' + - 'Please report this as a bug in React.' - ); - - ReactDebugInstanceMap.unregisterInstance(inst); - ReactDebugInstanceMap.registerInstance(inst); - expect(console.error.argsForCall.length).toBe(1); - }); - - it('should warn when unregistering an instance twice', function() { - spyOn(console, 'error'); - var inst = createStubInstance(); - - ReactDebugInstanceMap.unregisterInstance(inst); - expect(console.error.argsForCall.length).toBe(1); - expect(console.error.argsForCall[0][0]).toContain( - 'There is an internal error in the React developer tools integration. ' + - 'An unregistered instance should not be unregistered again. ' + - 'Please report this as a bug in React.' - ); - - ReactDebugInstanceMap.registerInstance(inst); - ReactDebugInstanceMap.unregisterInstance(inst); - expect(console.error.argsForCall.length).toBe(1); - - ReactDebugInstanceMap.unregisterInstance(inst); - expect(console.error.argsForCall.length).toBe(2); - expect(console.error.argsForCall[1][0]).toContain( - 'There is an internal error in the React developer tools integration. ' + - 'An unregistered instance should not be unregistered again. ' + - 'Please report this as a bug in React.' - ); - }); - - it('should warn about anything than is not an internal instance', function() { - class Foo extends React.Component { - render() { - return
; - } - } - - spyOn(console, 'error'); - var warningCount = 0; - var div = document.createElement('div'); - var publicInst = ReactDOM.render(, div); - - [false, null, undefined, {}, div, publicInst].forEach(falsyValue => { - ReactDebugInstanceMap.registerInstance(falsyValue); - warningCount++; - expect(ReactDebugInstanceMap.getIDForInstance(falsyValue)).toBe(null); - warningCount++; - expect(ReactDebugInstanceMap.isRegisteredInstance(falsyValue)).toBe(false); - warningCount++; - ReactDebugInstanceMap.unregisterInstance(falsyValue); - warningCount++; - }); - - expect(console.error.argsForCall.length).toBe(warningCount); - for (var i = 0; i < warningCount.length; i++) { - // Ideally we could check for the more detailed error message here - // but it depends on the input type and is meant for internal bugs - // anyway so I don't think it's worth complicating the test with it. - expect(console.error.argsForCall[i][0]).toContain( - 'There is an internal error in the React developer tools integration.' - ); - } - }); -}); diff --git a/src/isomorphic/devtools/ReactComponentTreeDevtool.js b/src/isomorphic/devtools/ReactComponentTreeDevtool.js new file mode 100644 index 0000000000..bdba5b378f --- /dev/null +++ b/src/isomorphic/devtools/ReactComponentTreeDevtool.js @@ -0,0 +1,148 @@ +/** + * 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. + * + * @providesModule ReactComponentTreeDevtool + */ + +'use strict'; + +var invariant = require('invariant'); + +var tree = {}; +var rootIDs = []; + +function updateTree(id, update) { + if (!tree[id]) { + tree[id] = { + parentID: null, + ownerID: null, + text: null, + childIDs: [], + displayName: 'Unknown', + isMounted: false, + }; + } + update(tree[id]); +} + +function purgeDeep(id) { + var item = tree[id]; + if (item) { + var {childIDs} = item; + delete tree[id]; + childIDs.forEach(purgeDeep); + } +} + +var ReactComponentTreeDevtool = { + onSetDisplayName(id, displayName) { + updateTree(id, item => item.displayName = displayName); + }, + + onSetChildren(id, nextChildIDs) { + updateTree(id, item => { + var prevChildIDs = item.childIDs; + item.childIDs = nextChildIDs; + + nextChildIDs.forEach(nextChildID => { + var nextChild = tree[nextChildID]; + invariant( + nextChild, + 'Expected devtool events to fire for the child ' + + 'before its parent includes it in onSetChildren().' + ); + invariant( + nextChild.displayName != null, + 'Expected onSetDisplayName() to fire for the child ' + + 'before its parent includes it in onSetChildren().' + ); + invariant( + nextChild.childIDs != null || nextChild.text != null, + 'Expected onSetChildren() or onSetText() to fire for the 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 (prevChildIDs.indexOf(nextChildID) === -1) { + nextChild.parentID = id; + } + }); + }); + }, + + onSetOwner(id, ownerID) { + updateTree(id, item => item.ownerID = ownerID); + }, + + onSetText(id, text) { + updateTree(id, item => item.text = text); + }, + + onMountComponent(id) { + updateTree(id, item => item.isMounted = true); + }, + + onMountRootComponent(id) { + rootIDs.push(id); + }, + + onUnmountComponent(id) { + updateTree(id, item => item.isMounted = false); + rootIDs = rootIDs.filter(rootID => rootID !== id); + }, + + purgeUnmountedComponents() { + Object.keys(tree) + .filter(id => !tree[id].isMounted) + .forEach(purgeDeep); + }, + + isMounted(id) { + var item = tree[id]; + return item ? item.isMounted : false; + }, + + getChildIDs(id) { + var item = tree[id]; + return item ? item.childIDs : []; + }, + + getDisplayName(id) { + var item = tree[id]; + return item ? item.displayName : 'Unknown'; + }, + + getOwnerID(id) { + var item = tree[id]; + return item ? item.ownerID : null; + }, + + getParentID(id) { + var item = tree[id]; + return item ? item.parentID : null; + }, + + getText(id) { + var item = tree[id]; + return item ? item.text : null; + }, + + getRootIDs() { + return rootIDs; + }, + + getRegisteredIDs() { + return Object.keys(tree); + }, +}; + +module.exports = ReactComponentTreeDevtool; diff --git a/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js b/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js new file mode 100644 index 0000000000..48f3b9d3d9 --- /dev/null +++ b/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js @@ -0,0 +1,1725 @@ +/** + * 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'; + +describe('ReactComponentTreeDevtool', () => { + var React; + var ReactDebugTool; + var ReactDOM; + var ReactDOMServer; + var ReactInstanceMap; + var ReactComponentTreeDevtool; + + beforeEach(() => { + jest.resetModuleRegistry(); + + React = require('React'); + ReactDebugTool = require('ReactDebugTool'); + ReactDOM = require('ReactDOM'); + ReactDOMServer = require('ReactDOMServer'); + ReactInstanceMap = require('ReactInstanceMap'); + ReactComponentTreeDevtool = require('ReactComponentTreeDevtool'); + + ReactDebugTool.addDevtool(ReactComponentTreeDevtool); + }); + + afterEach(() => { + ReactDebugTool.removeDevtool(ReactComponentTreeDevtool); + }); + + function getRootDisplayNames() { + return ReactComponentTreeDevtool.getRootIDs() + .map(ReactComponentTreeDevtool.getDisplayName); + } + + function getRegisteredDisplayNames() { + return ReactComponentTreeDevtool.getRegisteredIDs() + .map(ReactComponentTreeDevtool.getDisplayName); + } + + function getTree(rootID, options = {}) { + var { + includeOwnerDisplayName = false, + includeParentDisplayName = false, + expectedParentID = null, + } = options; + + var result = { + displayName: ReactComponentTreeDevtool.getDisplayName(rootID), + }; + + var ownerID = ReactComponentTreeDevtool.getOwnerID(rootID); + var parentID = ReactComponentTreeDevtool.getParentID(rootID); + expect(parentID).toBe(expectedParentID); + + if (includeParentDisplayName && parentID) { + result.parentDisplayName = ReactComponentTreeDevtool.getDisplayName(parentID); + } + if (includeOwnerDisplayName && ownerID) { + result.ownerDisplayName = ReactComponentTreeDevtool.getDisplayName(ownerID); + } + + var childIDs = ReactComponentTreeDevtool.getChildIDs(rootID); + var text = ReactComponentTreeDevtool.getText(rootID); + if (text != null) { + result.text = text; + } else { + result.children = childIDs.map(childID => + getTree(childID, {...options, expectedParentID: rootID }) + ); + } + + return result; + } + + function assertTreeMatches(pairs, options) { + 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 getActualTree() { + return getTree(rootInstance._debugID, options).children[0]; + } + + pairs.forEach(([element, expectedTree]) => { + currentElement = element; + ReactDOM.render(, node); + expect(getActualTree()).toEqual(expectedTree); + ReactComponentTreeDevtool.purgeUnmountedComponents(); + expect(getActualTree()).toEqual(expectedTree); + }); + ReactDOM.unmountComponentAtNode(node); + + ReactComponentTreeDevtool.purgeUnmountedComponents(); + expect(getActualTree()).toBe(undefined); + expect(getRootDisplayNames()).toEqual([]); + expect(getRegisteredDisplayNames()).toEqual([]); + + pairs.forEach(([element, expectedTree]) => { + currentElement = element; + ReactDOMServer.renderToString(); + expect(getActualTree()).toEqual(expectedTree); + + ReactComponentTreeDevtool.purgeUnmountedComponents(); + expect(getActualTree()).toBe(undefined); + expect(getRootDisplayNames()).toEqual([]); + expect(getRegisteredDisplayNames()).toEqual([]); + }); + } + + describe('mount', () => { + it('uses displayName or Unknown for classic components', () => { + var Foo = React.createClass({ + render() { + return null; + }, + }); + Foo.displayName = 'Bar'; + var Baz = React.createClass({ + render() { + return null; + }, + }); + var Qux = React.createClass({ + 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', + 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', + 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', + children: [{ + displayName: 'Bar', + children: [], + }, { + displayName: 'Baz', + children: [], + }, { + displayName: 'Unknown', + children: [], + }], + }; + assertTreeMatches([element, tree]); + }); + + it('reports a native 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', + 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', + children: [{ + displayName: 'div', + children: [], + }], + }; + assertTreeMatches([element, tree]); + }); + + it('reports a tree with composites correctly', () => { + var Qux = React.createClass({ + 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', + children: [{ + displayName: 'div', + children: [{ + displayName: 'Foo', + children: [{ + displayName: 'Qux', + children: [], + }], + }, { + displayName: 'Bar', + children: [{ + displayName: 'h1', + children: [{ + displayName: 'span', + children: [{ + displayName: '#text', + text: 'Hi,', + }], + }, { + displayName: '#text', + text: 'Mom', + }], + }], + }, { + displayName: 'a', + children: [{ + displayName: '#text', + text: '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', + 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', + children: [{ + displayName: '#text', + text: '1', + }], + }; + assertTreeMatches([element, tree]); + }); + + it('reports a single number node as a child', () => { + var element =
{42}
; + var tree = { + displayName: 'div', + children: [{ + displayName: '#text', + text: '42', + }], + }; + assertTreeMatches([element, tree]); + }); + + it('reports a zero as a child', () => { + var element =
{0}
; + var tree = { + displayName: 'div', + 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', + children: [{ + displayName: '#text', + text: 'hi', + }, { + displayName: '#text', + text: '42', + }, { + displayName: 'Foo', + children: [{ + displayName: 'div', + children: [], + }], + }], + }; + assertTreeMatches([element, tree]); + }); + + it('reports html content as no children', () => { + var element =
; + var tree = { + displayName: 'div', + children: [], + }; + assertTreeMatches([element, tree]); + }); + }); + + describe('update', () => { + describe('native 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 native 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 native 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 native 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 native 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 native 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 native 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 native 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 native child', () => { + var Foo = React.createClass({ + 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 native child', () => { + var Foo = React.createClass({ + 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 native child to null', () => { + var Foo = React.createClass({ + 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 native child to a composite child', () => { + var Bar = React.createClass({ + render() { + return null; + }, + }); + + var Foo = React.createClass({ + 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 native child', () => { + var Bar = React.createClass({ + render() { + return null; + }, + }); + + var Foo = React.createClass({ + 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', () => { + var Bar = React.createClass({ + render() { + return null; + }, + }); + + var Foo = React.createClass({ + 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', () => { + var Bar = React.createClass({ + render() { + return null; + }, + }); + + var Foo = React.createClass({ + 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], + ]); + }); + }); + }); + + 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], {includeOwnerDisplayName: true}); + }); + + it('preserves unmounted components until purge', () => { + 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); + expect( + getTree(barInstance._debugID, { + includeParentDisplayName: true, + expectedParentID: fooInstance._debugID, + }) + ).toEqual({ + displayName: 'Bar', + parentDisplayName: 'Foo', + children: [], + }); + + renderBar = false; + ReactDOM.render(, node); + expect( + getTree(barInstance._debugID, { + includeParentDisplayName: true, + expectedParentID: fooInstance._debugID, + }) + ).toEqual({ + displayName: 'Bar', + parentDisplayName: 'Foo', + children: [], + }); + + ReactDOM.unmountComponentAtNode(node); + expect( + getTree(barInstance._debugID, { + includeParentDisplayName: true, + expectedParentID: fooInstance._debugID, + }) + ).toEqual({ + displayName: 'Bar', + parentDisplayName: 'Foo', + children: [], + }); + + ReactComponentTreeDevtool.purgeUnmountedComponents(); + expect( + getTree(barInstance._debugID, {includeParentDisplayName: true}) + ).toEqual({ + displayName: 'Unknown', + children: [], + }); + }); + + it('does not report top-level wrapper as a root', () => { + var node = document.createElement('div'); + + ReactDOM.render(
, node); + expect(getRootDisplayNames()).toEqual(['div']); + + ReactDOM.render(
, node); + expect(getRootDisplayNames()).toEqual(['div']); + + ReactDOM.unmountComponentAtNode(node); + expect(getRootDisplayNames()).toEqual([]); + + ReactComponentTreeDevtool.purgeUnmountedComponents(); + expect(getRootDisplayNames()).toEqual([]); + + // This currently contains TopLevelWrapper until purge + // so we only check it at the very end. + expect(getRegisteredDisplayNames()).toEqual([]); + }); +}); diff --git a/src/renderers/dom/client/ReactMount.js b/src/renderers/dom/client/ReactMount.js index 7f6f8cac6f..1984e146f3 100644 --- a/src/renderers/dom/client/ReactMount.js +++ b/src/renderers/dom/client/ReactMount.js @@ -333,6 +333,12 @@ var ReactMount = { ReactBrowserEventEmitter.ensureScrollValueMonitoring(); var componentInstance = instantiateReactComponent(nextElement); + if (__DEV__) { + // Mute future events from the top level wrapper. + // It is an implementation detail that devtools should not know about. + componentInstance._debugID = 0; + } + // The initial render is synchronous but any updates that happen during // rendering, in componentWillMount or componentDidMount, will be batched // according to the current batching strategy. @@ -349,7 +355,10 @@ var ReactMount = { instancesByReactRootID[wrapperID] = componentInstance; if (__DEV__) { - ReactInstrumentation.debugTool.onMountRootComponent(componentInstance); + // The instance here is TopLevelWrapper so we report mount for its child. + ReactInstrumentation.debugTool.onMountRootComponent( + componentInstance._renderedComponent._debugID + ); } return componentInstance; diff --git a/src/renderers/dom/server/ReactServerRendering.js b/src/renderers/dom/server/ReactServerRendering.js index 5a5d604b7d..2d30f1a18d 100644 --- a/src/renderers/dom/server/ReactServerRendering.js +++ b/src/renderers/dom/server/ReactServerRendering.js @@ -13,7 +13,9 @@ var ReactDOMContainerInfo = require('ReactDOMContainerInfo'); var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy'); var ReactElement = require('ReactElement'); +var ReactInstrumentation = require('ReactInstrumentation'); var ReactMarkupChecksum = require('ReactMarkupChecksum'); +var ReactReconciler = require('ReactReconciler'); var ReactServerBatchingStrategy = require('ReactServerBatchingStrategy'); var ReactServerRenderingTransaction = require('ReactServerRenderingTransaction'); @@ -36,12 +38,18 @@ function renderToStringImpl(element, makeStaticMarkup) { return transaction.perform(function() { var componentInstance = instantiateReactComponent(element); - var markup = componentInstance.mountComponent( + var markup = ReactReconciler.mountComponent( + componentInstance, transaction, null, ReactDOMContainerInfo(), emptyObject ); + if (__DEV__) { + ReactInstrumentation.debugTool.onUnmountComponent( + componentInstance._debugID + ); + } if (!makeStaticMarkup) { markup = ReactMarkupChecksum.addChecksumToMarkup(markup); } diff --git a/src/renderers/dom/shared/ReactDOMComponent.js b/src/renderers/dom/shared/ReactDOMComponent.js index 2d3907b448..f728339c29 100644 --- a/src/renderers/dom/shared/ReactDOMComponent.js +++ b/src/renderers/dom/shared/ReactDOMComponent.js @@ -32,10 +32,12 @@ var ReactDOMInput = require('ReactDOMInput'); var ReactDOMOption = require('ReactDOMOption'); var ReactDOMSelect = require('ReactDOMSelect'); var ReactDOMTextarea = require('ReactDOMTextarea'); +var ReactInstrumentation = require('ReactInstrumentation'); var ReactMultiChild = require('ReactMultiChild'); var ReactPerf = require('ReactPerf'); var ReactServerRenderingTransaction = require('ReactServerRenderingTransaction'); +var emptyFunction = require('emptyFunction'); var escapeTextContentForBrowser = require('escapeTextContentForBrowser'); var invariant = require('invariant'); var isEventSupported = require('isEventSupported'); @@ -244,6 +246,19 @@ function optionPostMount() { ReactDOMOption.postMountWrapper(inst); } +var setContentChildForInstrumentation = emptyFunction; +if (__DEV__) { + setContentChildForInstrumentation = function(contentToUse) { + var debugID = this._debugID; + var contentDebugID = debugID + '#text'; + this._contentDebugID = contentDebugID; + ReactInstrumentation.debugTool.onSetDisplayName(contentDebugID, '#text'); + ReactInstrumentation.debugTool.onSetText(contentDebugID, '' + contentToUse); + ReactInstrumentation.debugTool.onMountComponent(contentDebugID); + ReactInstrumentation.debugTool.onSetChildren(debugID, [contentDebugID]); + }; +} + // There are so many media events, it makes sense to just // maintain a list rather than create a `trapBubbledEvent` for each var mediaEvents = { @@ -447,6 +462,7 @@ function ReactDOMComponent(element) { this._flags = 0; if (__DEV__) { this._ancestorInfo = null; + this._contentDebugID = null; } } @@ -710,6 +726,9 @@ ReactDOMComponent.Mixin = { if (contentToUse != null) { // TODO: Validate that text is allowed as a child of this node ret = escapeTextContentForBrowser(contentToUse); + if (__DEV__) { + setContentChildForInstrumentation.call(this, contentToUse); + } } else if (childrenToUse != null) { var mountImages = this.mountChildren( childrenToUse, @@ -749,6 +768,9 @@ ReactDOMComponent.Mixin = { var childrenToUse = contentToUse != null ? null : props.children; if (contentToUse != null) { // TODO: Validate that text is allowed as a child of this node + if (__DEV__) { + setContentChildForInstrumentation.call(this, contentToUse); + } DOMLazyTree.queueText(lazyTree, contentToUse); } else if (childrenToUse != null) { var mountImages = this.mountChildren( @@ -996,17 +1018,34 @@ ReactDOMComponent.Mixin = { this.updateChildren(null, transaction, context); } else if (lastHasContentOrHtml && !nextHasContentOrHtml) { this.updateTextContent(''); + if (__DEV__) { + ReactInstrumentation.debugTool.onSetChildren(this._debugID, []); + } } if (nextContent != null) { if (lastContent !== nextContent) { this.updateTextContent('' + nextContent); + if (__DEV__) { + this._contentDebugID = this._debugID + '#text'; + setContentChildForInstrumentation.call(this, nextContent); + } } } else if (nextHtml != null) { if (lastHtml !== nextHtml) { this.updateMarkup('' + nextHtml); } + if (__DEV__) { + ReactInstrumentation.debugTool.onSetChildren(this._debugID, []); + } } else if (nextChildren != null) { + if (__DEV__) { + if (this._contentDebugID) { + ReactInstrumentation.debugTool.onUnmountComponent(this._contentDebugID); + this._contentDebugID = null; + } + } + this.updateChildren(nextChildren, transaction, context); } }, @@ -1064,6 +1103,11 @@ ReactDOMComponent.Mixin = { this._rootNodeID = null; this._domID = null; this._wrapperState = null; + + if (this._contentDebugID) { + ReactInstrumentation.debugTool.onUnmountComponent(this._contentDebugID); + this._contentDebugID = null; + } }, getPublicInstance: function() { diff --git a/src/renderers/dom/shared/ReactDOMTextComponent.js b/src/renderers/dom/shared/ReactDOMTextComponent.js index 9a69c7f977..5041706287 100644 --- a/src/renderers/dom/shared/ReactDOMTextComponent.js +++ b/src/renderers/dom/shared/ReactDOMTextComponent.js @@ -14,6 +14,7 @@ var DOMChildrenOperations = require('DOMChildrenOperations'); var DOMLazyTree = require('DOMLazyTree'); var ReactDOMComponentTree = require('ReactDOMComponentTree'); +var ReactInstrumentation = require('ReactInstrumentation'); var ReactPerf = require('ReactPerf'); var escapeTextContentForBrowser = require('escapeTextContentForBrowser'); @@ -67,6 +68,8 @@ Object.assign(ReactDOMTextComponent.prototype, { context ) { if (__DEV__) { + ReactInstrumentation.debugTool.onSetText(this._debugID, this._stringText); + var parentInfo; if (nativeParent != null) { parentInfo = nativeParent._ancestorInfo; @@ -140,6 +143,10 @@ Object.assign(ReactDOMTextComponent.prototype, { commentNodes[1], nextStringText ); + + if (__DEV__) { + ReactInstrumentation.debugTool.onSetText(this._debugID, nextStringText); + } } } }, diff --git a/src/renderers/shared/reconciler/ReactCompositeComponent.js b/src/renderers/shared/reconciler/ReactCompositeComponent.js index d6ea8b1233..0f8574545a 100644 --- a/src/renderers/shared/reconciler/ReactCompositeComponent.js +++ b/src/renderers/shared/reconciler/ReactCompositeComponent.js @@ -389,6 +389,17 @@ var ReactCompositeComponentMixin = { this._processChildContext(context) ); + if (__DEV__) { + if (this._debugID !== 0) { + ReactInstrumentation.debugTool.onSetChildren( + this._debugID, + this._renderedComponent._debugID !== 0 ? + [this._renderedComponent._debugID] : + [] + ); + } + } + return markup; }, @@ -857,6 +868,7 @@ var ReactCompositeComponentMixin = { this._renderedComponent = this._instantiateReactComponent( nextRenderedElement ); + var nextMarkup = ReactReconciler.mountComponent( this._renderedComponent, transaction, @@ -864,6 +876,18 @@ var ReactCompositeComponentMixin = { this._nativeContainerInfo, this._processChildContext(context) ); + + if (__DEV__) { + if (this._debugID !== 0) { + ReactInstrumentation.debugTool.onSetChildren( + this._debugID, + this._renderedComponent._debugID !== 0 ? + [this._renderedComponent._debugID] : + [] + ); + } + } + this._replaceNodeWithMarkup(oldNativeNode, nextMarkup); } }, diff --git a/src/renderers/shared/reconciler/ReactMultiChild.js b/src/renderers/shared/reconciler/ReactMultiChild.js index 66f3861321..474ddc63dc 100644 --- a/src/renderers/shared/reconciler/ReactMultiChild.js +++ b/src/renderers/shared/reconciler/ReactMultiChild.js @@ -12,12 +12,14 @@ 'use strict'; var ReactComponentEnvironment = require('ReactComponentEnvironment'); +var ReactInstrumentation = require('ReactInstrumentation'); var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes'); var ReactCurrentOwner = require('ReactCurrentOwner'); var ReactReconciler = require('ReactReconciler'); var ReactChildReconciler = require('ReactChildReconciler'); +var emptyFunction = require('emptyFunction'); var flattenChildren = require('flattenChildren'); var invariant = require('invariant'); @@ -137,6 +139,16 @@ function processQueue(inst, updateQueue) { ); } +var setChildrenForInstrumentation = emptyFunction; +if (__DEV__) { + setChildrenForInstrumentation = function(children) { + ReactInstrumentation.debugTool.onSetChildren( + this._debugID, + children ? Object.keys(children).map(key => children[key]._debugID) : [] + ); + }; +} + /** * ReactMultiChild are capable of reconciling multiple children. * @@ -214,6 +226,7 @@ var ReactMultiChild = { nestedChildren, transaction, context ); this._renderedChildren = children; + var mountImages = []; var index = 0; for (var name in children) { @@ -230,6 +243,11 @@ var ReactMultiChild = { mountImages.push(mountImage); } } + + if (__DEV__) { + setChildrenForInstrumentation.call(this, children); + } + return mountImages; }, @@ -357,6 +375,10 @@ var ReactMultiChild = { processQueue(this, updates); } this._renderedChildren = nextChildren; + + if (__DEV__) { + setChildrenForInstrumentation.call(this, nextChildren); + } }, /** diff --git a/src/renderers/shared/reconciler/ReactReconciler.js b/src/renderers/shared/reconciler/ReactReconciler.js index ae7806e4fc..d2a2dbcac8 100644 --- a/src/renderers/shared/reconciler/ReactReconciler.js +++ b/src/renderers/shared/reconciler/ReactReconciler.js @@ -53,7 +53,9 @@ var ReactReconciler = { transaction.getReactMountReady().enqueue(attachRefs, internalInstance); } if (__DEV__) { - ReactInstrumentation.debugTool.onMountComponent(internalInstance); + if (internalInstance._debugID !== 0) { + ReactInstrumentation.debugTool.onMountComponent(internalInstance._debugID); + } } return markup; }, @@ -76,7 +78,9 @@ var ReactReconciler = { ReactRef.detachRefs(internalInstance, internalInstance._currentElement); internalInstance.unmountComponent(safely); if (__DEV__) { - ReactInstrumentation.debugTool.onUnmountComponent(internalInstance); + if (internalInstance._debugID !== 0) { + ReactInstrumentation.debugTool.onUnmountComponent(internalInstance._debugID); + } } }, @@ -128,7 +132,9 @@ var ReactReconciler = { } if (__DEV__) { - ReactInstrumentation.debugTool.onUpdateComponent(internalInstance); + if (internalInstance._debugID !== 0) { + ReactInstrumentation.debugTool.onUpdateComponent(internalInstance._debugID); + } } }, @@ -145,7 +151,9 @@ var ReactReconciler = { ) { internalInstance.performUpdateIfNecessary(transaction); if (__DEV__) { - ReactInstrumentation.debugTool.onUpdateComponent(internalInstance); + if (internalInstance._debugID !== 0) { + ReactInstrumentation.debugTool.onUpdateComponent(internalInstance._debugID); + } } }, diff --git a/src/renderers/shared/reconciler/instantiateReactComponent.js b/src/renderers/shared/reconciler/instantiateReactComponent.js index 796e037b99..db22ea3b91 100644 --- a/src/renderers/shared/reconciler/instantiateReactComponent.js +++ b/src/renderers/shared/reconciler/instantiateReactComponent.js @@ -14,6 +14,7 @@ var ReactCompositeComponent = require('ReactCompositeComponent'); var ReactEmptyComponent = require('ReactEmptyComponent'); var ReactNativeComponent = require('ReactNativeComponent'); +var ReactInstrumentation = require('ReactInstrumentation'); var invariant = require('invariant'); var warning = require('warning'); @@ -40,6 +41,21 @@ function getDeclarationErrorAddendum(owner) { return ''; } +function getDisplayName(instance) { + var element = instance._currentElement; + 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 if (instance.getName) { + return instance.getName() || 'Unknown'; + } else { + return element.type.displayName || element.type.name || 'Unknown'; + } +} + /** * Check if the type reference is a known internal type. I.e. not a user * provided composite type. @@ -56,6 +72,8 @@ function isInternalComponentType(type) { ); } +var nextDebugID = 1; + /** * Given a ReactNode, create an instance that will actually be mounted. * @@ -66,7 +84,8 @@ function isInternalComponentType(type) { function instantiateReactComponent(node) { var instance; - if (node === null || node === false) { + var isEmpty = node === null || node === false; + if (isEmpty) { instance = ReactEmptyComponent.create(instantiateReactComponent); } else if (typeof node === 'object') { var element = node; @@ -121,6 +140,18 @@ function instantiateReactComponent(node) { instance._warnedAboutRefsInRender = false; } + if (__DEV__) { + var debugID = isEmpty ? 0 : nextDebugID++; + instance._debugID = debugID; + + var displayName = getDisplayName(instance); + ReactInstrumentation.debugTool.onSetDisplayName(debugID, displayName); + var owner = node && node._owner; + if (owner) { + ReactInstrumentation.debugTool.onSetOwner(debugID, owner._debugID); + } + } + // Internal instances should fully constructed at this point, so they should // not get any new fields added to them at this point. if (__DEV__) { From f50d542ff7b0d786178076fd8207f8ba8c920728 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Wed, 27 Apr 2016 19:03:55 +0100 Subject: [PATCH 20/32] Merge pull request #6612 from gaearon/instrumentation-new-operations Add ReactNativeOperationHistoryDevtool to track native operations (cherry picked from commit 3bdf09e86f84dbd822bb678ab2b4a78b567e4a63) --- src/isomorphic/ReactDebugTool.js | 3 + .../ReactNativeOperationHistoryDevtool.js | 34 + ...ReactNativeOperationHistoryDevtool-test.js | 654 ++++++++++++++++++ src/renderers/dom/client/ReactMount.js | 8 + .../__tests__/ReactDOMIDOperations-test.js | 7 +- .../dom/client/utils/DOMChildrenOperations.js | 64 +- src/renderers/dom/client/utils/DOMLazyTree.js | 5 + .../dom/shared/CSSPropertyOperations.js | 9 + .../dom/shared/DOMPropertyOperations.js | 40 +- .../__tests__/DOMPropertyOperations-test.js | 10 + .../reconciler/ReactCompositeComponent.js | 11 +- 11 files changed, 833 insertions(+), 12 deletions(-) create mode 100644 src/isomorphic/devtools/ReactNativeOperationHistoryDevtool.js create mode 100644 src/isomorphic/devtools/__tests__/ReactNativeOperationHistoryDevtool-test.js diff --git a/src/isomorphic/ReactDebugTool.js b/src/isomorphic/ReactDebugTool.js index 49c6cb5500..cd0a3a0c45 100644 --- a/src/isomorphic/ReactDebugTool.js +++ b/src/isomorphic/ReactDebugTool.js @@ -55,6 +55,9 @@ var ReactDebugTool = { onEndProcessingChildContext() { emitEvent('onEndProcessingChildContext'); }, + onNativeOperation(debugID, type, payload) { + emitEvent('onNativeOperation', debugID, type, payload); + }, onSetState() { emitEvent('onSetState'); }, diff --git a/src/isomorphic/devtools/ReactNativeOperationHistoryDevtool.js b/src/isomorphic/devtools/ReactNativeOperationHistoryDevtool.js new file mode 100644 index 0000000000..50478cc405 --- /dev/null +++ b/src/isomorphic/devtools/ReactNativeOperationHistoryDevtool.js @@ -0,0 +1,34 @@ +/** + * 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. + * + * @providesModule ReactNativeOperationHistoryDevtool + */ + +'use strict'; + +var history = []; + +var ReactNativeOperationHistoryDevtool = { + onNativeOperation(debugID, type, payload) { + history.push({ + instanceID: debugID, + type, + payload, + }); + }, + + clearHistory() { + history = []; + }, + + getHistory() { + return history; + }, +}; + +module.exports = ReactNativeOperationHistoryDevtool; diff --git a/src/isomorphic/devtools/__tests__/ReactNativeOperationHistoryDevtool-test.js b/src/isomorphic/devtools/__tests__/ReactNativeOperationHistoryDevtool-test.js new file mode 100644 index 0000000000..3b5924aae5 --- /dev/null +++ b/src/isomorphic/devtools/__tests__/ReactNativeOperationHistoryDevtool-test.js @@ -0,0 +1,654 @@ +/** + * 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'; + +describe('ReactNativeOperationHistoryDevtool', () => { + var React; + var ReactDebugTool; + var ReactDOM; + var ReactDOMComponentTree; + var ReactDOMFeatureFlags; + var ReactNativeOperationHistoryDevtool; + + beforeEach(() => { + jest.resetModuleRegistry(); + + React = require('React'); + ReactDebugTool = require('ReactDebugTool'); + ReactDOM = require('ReactDOM'); + ReactDOMComponentTree = require('ReactDOMComponentTree'); + ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); + ReactNativeOperationHistoryDevtool = require('ReactNativeOperationHistoryDevtool'); + + ReactDebugTool.addDevtool(ReactNativeOperationHistoryDevtool); + }); + + afterEach(() => { + ReactDebugTool.removeDevtool(ReactNativeOperationHistoryDevtool); + }); + + function assertHistoryMatches(expectedHistory) { + var actualHistory = ReactNativeOperationHistoryDevtool.getHistory(); + expect(actualHistory).toEqual(expectedHistory); + } + + describe('mount', () => { + it('gets recorded for native roots', () => { + var node = document.createElement('div'); + ReactDOM.render(

Hi.

, node); + var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); + + assertHistoryMatches([{ + instanceID: inst._debugID, + type: 'mount', + payload: ReactDOMFeatureFlags.useCreateElement ? + 'DIV' : + '

Hi.

', + }]); + }); + + it('gets recorded for composite roots', () => { + function Foo() { + return

Hi.

; + } + var node = document.createElement('div'); + ReactDOM.render(, node); + var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); + assertHistoryMatches([{ + instanceID: inst._debugID, + type: 'mount', + payload: ReactDOMFeatureFlags.useCreateElement ? + 'DIV' : + '
' + + '

Hi.

', + }]); + }); + + it('gets recorded for composite roots that return null', () => { + function Foo() { + return null; + } + var node = document.createElement('div'); + ReactDOM.render(, node); + var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); + assertHistoryMatches([{ + instanceID: inst._debugID, + type: 'mount', + payload: ReactDOMFeatureFlags.useCreateElement ? + '#comment' : + '', + }]); + }); + }); + + describe('update styles', () => { + it('gets recorded during mount', () => { + var node = document.createElement('div'); + ReactDOM.render(
, node); + var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); + + if (ReactDOMFeatureFlags.useCreateElement) { + assertHistoryMatches([{ + instanceID: inst._debugID, + type: 'update styles', + payload: { + color: 'red', + backgroundColor: 'yellow', + }, + }, { + instanceID: inst._debugID, + type: 'mount', + payload: 'DIV', + }]); + } else { + assertHistoryMatches([{ + instanceID: inst._debugID, + type: 'mount', + payload: '
', + }]); + } + }); + + it('gets recorded during an update', () => { + var node = document.createElement('div'); + ReactDOM.render(
, node); + var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); + + ReactNativeOperationHistoryDevtool.clearHistory(); + 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); + + ReactNativeOperationHistoryDevtool.clearHistory(); + 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'); + ReactDOM.render(
, node); + var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); + + if (ReactDOMFeatureFlags.useCreateElement) { + 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', + }]); + } else { + assertHistoryMatches([{ + instanceID: inst._debugID, + type: 'mount', + payload: '
', + }]); + } + }); + + it('gets recorded during an update', () => { + var node = document.createElement('div'); + ReactDOM.render(
, node); + var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); + + ReactNativeOperationHistoryDevtool.clearHistory(); + 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); + + ReactNativeOperationHistoryDevtool.clearHistory(); + 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'); + ReactDOM.render(
, node); + var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); + + if (ReactDOMFeatureFlags.useCreateElement) { + 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', + }]); + } else { + assertHistoryMatches([{ + instanceID: inst._debugID, + type: 'mount', + payload: '
', + }]); + } + }); + + it('gets recorded during an update', () => { + var node = document.createElement('div'); + ReactDOM.render(
, node); + var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); + + ReactNativeOperationHistoryDevtool.clearHistory(); + 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'); + ReactDOM.render(, node); + var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); + + if (ReactDOMFeatureFlags.useCreateElement) { + 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', + }]); + } else { + assertHistoryMatches([{ + instanceID: inst._debugID, + type: 'mount', + payload: '', + }]); + } + }); + + it('gets recorded during an update', () => { + var node = document.createElement('div'); + ReactDOM.render(, node); + var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); + + ReactNativeOperationHistoryDevtool.clearHistory(); + 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); + + ReactNativeOperationHistoryDevtool.clearHistory(); + 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); + + ReactNativeOperationHistoryDevtool.clearHistory(); + 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); + + ReactNativeOperationHistoryDevtool.clearHistory(); + 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); + + ReactNativeOperationHistoryDevtool.clearHistory(); + 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]); + + ReactNativeOperationHistoryDevtool.clearHistory(); + 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); + + ReactNativeOperationHistoryDevtool.clearHistory(); + 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); + + ReactNativeOperationHistoryDevtool.clearHistory(); + element = ; + ReactDOM.render(, node); + assertHistoryMatches([{ + instanceID: inst._debugID, + type: 'replace with', + payload: 'SPAN', + }]); + }); + + it('gets ignored if the type has not changed', () => { + var element; + function Foo() { + return element; + } + + var node = document.createElement('div'); + element =
; + ReactDOM.render(, node); + + ReactNativeOperationHistoryDevtool.clearHistory(); + element =
; + 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); + + ReactNativeOperationHistoryDevtool.clearHistory(); + 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); + + ReactNativeOperationHistoryDevtool.clearHistory(); + 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); + + ReactNativeOperationHistoryDevtool.clearHistory(); + 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 + ); + + ReactNativeOperationHistoryDevtool.clearHistory(); + 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); + + ReactNativeOperationHistoryDevtool.clearHistory(); + 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); + + ReactNativeOperationHistoryDevtool.clearHistory(); + 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); + + ReactNativeOperationHistoryDevtool.clearHistory(); + ReactDOM.render(
, node); + assertHistoryMatches([{ + instanceID: inst._debugID, + type: 'remove child', + payload: {fromIndex: 1}, + }]); + }); + }); +}); diff --git a/src/renderers/dom/client/ReactMount.js b/src/renderers/dom/client/ReactMount.js index 1984e146f3..2981814ead 100644 --- a/src/renderers/dom/client/ReactMount.js +++ b/src/renderers/dom/client/ReactMount.js @@ -693,6 +693,14 @@ var ReactMount = { setInnerHTML(container, markup); ReactDOMComponentTree.precacheNode(instance, container.firstChild); } + + if (__DEV__) { + ReactInstrumentation.debugTool.onNativeOperation( + ReactDOMComponentTree.getInstanceFromNode(container.firstChild)._debugID, + 'mount', + markup.toString() + ); + } }, }; diff --git a/src/renderers/dom/client/__tests__/ReactDOMIDOperations-test.js b/src/renderers/dom/client/__tests__/ReactDOMIDOperations-test.js index bdeb485e24..1e5c05a6aa 100644 --- a/src/renderers/dom/client/__tests__/ReactDOMIDOperations-test.js +++ b/src/renderers/dom/client/__tests__/ReactDOMIDOperations-test.js @@ -12,15 +12,18 @@ 'use strict'; describe('ReactDOMIDOperations', function() { + var ReactDOMComponentTree = require('ReactDOMComponentTree'); var ReactDOMIDOperations = require('ReactDOMIDOperations'); var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes'); it('should update innerHTML and preserve whitespace', function() { var stubNode = document.createElement('div'); - var html = '\n \t \n testContent \t \n \t'; + var stubInstance = {}; + ReactDOMComponentTree.precacheNode(stubInstance, stubNode); + var html = '\n \t \n testContent \t \n \t'; ReactDOMIDOperations.dangerouslyProcessChildrenUpdates( - {_nativeNode: stubNode}, + stubInstance, [{ type: ReactMultiChildUpdateTypes.SET_MARKUP, content: html, diff --git a/src/renderers/dom/client/utils/DOMChildrenOperations.js b/src/renderers/dom/client/utils/DOMChildrenOperations.js index f123aef6ba..3072a8d5b5 100644 --- a/src/renderers/dom/client/utils/DOMChildrenOperations.js +++ b/src/renderers/dom/client/utils/DOMChildrenOperations.js @@ -14,6 +14,8 @@ var DOMLazyTree = require('DOMLazyTree'); var Danger = require('Danger'); var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes'); +var ReactDOMComponentTree = require('ReactDOMComponentTree'); +var ReactInstrumentation = require('ReactInstrumentation'); var ReactPerf = require('ReactPerf'); var createMicrosoftUnsafeLocalFunction = require('createMicrosoftUnsafeLocalFunction'); @@ -120,6 +122,26 @@ function replaceDelimitedText(openingComment, closingComment, stringText) { removeDelimitedText(parentNode, openingComment, closingComment); } } + + if (__DEV__) { + ReactInstrumentation.debugTool.onNativeOperation( + ReactDOMComponentTree.getInstanceFromNode(openingComment)._debugID, + 'replace text', + stringText + ); + } +} + +var dangerouslyReplaceNodeWithMarkup = Danger.dangerouslyReplaceNodeWithMarkup; +if (__DEV__) { + dangerouslyReplaceNodeWithMarkup = function(oldChild, markup, prevInstance) { + Danger.dangerouslyReplaceNodeWithMarkup(oldChild, markup); + ReactInstrumentation.debugTool.onNativeOperation( + prevInstance._debugID, + 'replace with', + markup.toString() + ); + }; } /** @@ -127,7 +149,7 @@ function replaceDelimitedText(openingComment, closingComment, stringText) { */ var DOMChildrenOperations = { - dangerouslyReplaceNodeWithMarkup: Danger.dangerouslyReplaceNodeWithMarkup, + dangerouslyReplaceNodeWithMarkup: dangerouslyReplaceNodeWithMarkup, replaceDelimitedText: replaceDelimitedText, @@ -139,6 +161,11 @@ var DOMChildrenOperations = { * @internal */ processUpdates: function(parentNode, updates) { + if (__DEV__) { + var parentNodeDebugID = + ReactDOMComponentTree.getInstanceFromNode(parentNode)._debugID; + } + for (var k = 0; k < updates.length; k++) { var update = updates[k]; switch (update.type) { @@ -148,6 +175,13 @@ var DOMChildrenOperations = { update.content, getNodeAfter(parentNode, update.afterNode) ); + if (__DEV__) { + ReactInstrumentation.debugTool.onNativeOperation( + parentNodeDebugID, + 'insert child', + {toIndex: update.toIndex, content: update.content.toString()} + ); + } break; case ReactMultiChildUpdateTypes.MOVE_EXISTING: moveChild( @@ -155,21 +189,49 @@ var DOMChildrenOperations = { update.fromNode, getNodeAfter(parentNode, update.afterNode) ); + if (__DEV__) { + ReactInstrumentation.debugTool.onNativeOperation( + parentNodeDebugID, + 'move child', + {fromIndex: update.fromIndex, toIndex: update.toIndex} + ); + } break; case ReactMultiChildUpdateTypes.SET_MARKUP: setInnerHTML( parentNode, update.content ); + if (__DEV__) { + ReactInstrumentation.debugTool.onNativeOperation( + parentNodeDebugID, + 'replace children', + update.content.toString() + ); + } break; case ReactMultiChildUpdateTypes.TEXT_CONTENT: setTextContent( parentNode, update.content ); + if (__DEV__) { + ReactInstrumentation.debugTool.onNativeOperation( + parentNodeDebugID, + 'replace text', + update.content.toString() + ); + } break; case ReactMultiChildUpdateTypes.REMOVE_NODE: removeChild(parentNode, update.fromNode); + if (__DEV__) { + ReactInstrumentation.debugTool.onNativeOperation( + parentNodeDebugID, + 'remove child', + {fromIndex: update.fromIndex} + ); + } break; } } diff --git a/src/renderers/dom/client/utils/DOMLazyTree.js b/src/renderers/dom/client/utils/DOMLazyTree.js index 8212d8751e..269f5c3714 100644 --- a/src/renderers/dom/client/utils/DOMLazyTree.js +++ b/src/renderers/dom/client/utils/DOMLazyTree.js @@ -108,12 +108,17 @@ function queueText(tree, text) { } } +function toString() { + return this.node.nodeName; +} + function DOMLazyTree(node) { return { node: node, children: [], html: null, text: null, + toString, }; } diff --git a/src/renderers/dom/shared/CSSPropertyOperations.js b/src/renderers/dom/shared/CSSPropertyOperations.js index aac1b75f87..721fb72a0f 100644 --- a/src/renderers/dom/shared/CSSPropertyOperations.js +++ b/src/renderers/dom/shared/CSSPropertyOperations.js @@ -13,6 +13,7 @@ var CSSProperty = require('CSSProperty'); var ExecutionEnvironment = require('ExecutionEnvironment'); +var ReactInstrumentation = require('ReactInstrumentation'); var ReactPerf = require('ReactPerf'); var camelizeStyleName = require('camelizeStyleName'); @@ -192,6 +193,14 @@ var CSSPropertyOperations = { * @param {ReactDOMComponent} component */ setValueForStyles: function(node, styles, component) { + if (__DEV__) { + ReactInstrumentation.debugTool.onNativeOperation( + component._debugID, + 'update styles', + styles + ); + } + var style = node.style; for (var styleName in styles) { if (!styles.hasOwnProperty(styleName)) { diff --git a/src/renderers/dom/shared/DOMPropertyOperations.js b/src/renderers/dom/shared/DOMPropertyOperations.js index 1ce9e29b75..9c67c50cfb 100644 --- a/src/renderers/dom/shared/DOMPropertyOperations.js +++ b/src/renderers/dom/shared/DOMPropertyOperations.js @@ -12,7 +12,9 @@ 'use strict'; var DOMProperty = require('DOMProperty'); +var ReactDOMComponentTree = require('ReactDOMComponentTree'); var ReactDOMInstrumentation = require('ReactDOMInstrumentation'); +var ReactInstrumentation = require('ReactInstrumentation'); var ReactPerf = require('ReactPerf'); var quoteAttributeValueForBrowser = require('quoteAttributeValueForBrowser'); @@ -134,9 +136,6 @@ var DOMPropertyOperations = { * @param {*} value */ setValueForProperty: function(node, name, value) { - if (__DEV__) { - ReactDOMInstrumentation.debugTool.onSetValueForProperty(node, name, value); - } var propertyInfo = DOMProperty.properties.hasOwnProperty(name) ? DOMProperty.properties[name] : null; if (propertyInfo) { @@ -145,6 +144,7 @@ var DOMPropertyOperations = { mutationMethod(node, value); } else if (shouldIgnoreValue(propertyInfo, value)) { this.deleteValueForProperty(node, name); + return; } else if (propertyInfo.mustUseProperty) { var propName = propertyInfo.propertyName; // Must explicitly cast values for HAS_SIDE_EFFECTS-properties to the @@ -171,6 +171,18 @@ var DOMPropertyOperations = { } } else if (DOMProperty.isCustomAttribute(name)) { DOMPropertyOperations.setValueForAttribute(node, name, value); + return; + } + + if (__DEV__) { + ReactDOMInstrumentation.debugTool.onSetValueForProperty(node, name, value); + var payload = {}; + payload[name] = value; + ReactInstrumentation.debugTool.onNativeOperation( + ReactDOMComponentTree.getInstanceFromNode(node)._debugID, + 'update attribute', + payload + ); } }, @@ -183,6 +195,16 @@ var DOMPropertyOperations = { } else { node.setAttribute(name, '' + value); } + + if (__DEV__) { + var payload = {}; + payload[name] = value; + ReactInstrumentation.debugTool.onNativeOperation( + ReactDOMComponentTree.getInstanceFromNode(node)._debugID, + 'update attribute', + payload + ); + } }, /** @@ -192,9 +214,6 @@ var DOMPropertyOperations = { * @param {string} name */ deleteValueForProperty: function(node, name) { - if (__DEV__) { - ReactDOMInstrumentation.debugTool.onDeleteValueForProperty(node, name); - } var propertyInfo = DOMProperty.properties.hasOwnProperty(name) ? DOMProperty.properties[name] : null; if (propertyInfo) { @@ -218,6 +237,15 @@ var DOMPropertyOperations = { } else if (DOMProperty.isCustomAttribute(name)) { node.removeAttribute(name); } + + if (__DEV__) { + ReactDOMInstrumentation.debugTool.onDeleteValueForProperty(node, name); + ReactInstrumentation.debugTool.onNativeOperation( + ReactDOMComponentTree.getInstanceFromNode(node)._debugID, + 'remove attribute', + name + ); + } }, }; diff --git a/src/renderers/dom/shared/__tests__/DOMPropertyOperations-test.js b/src/renderers/dom/shared/__tests__/DOMPropertyOperations-test.js index 14b72282ae..08bb3e455e 100644 --- a/src/renderers/dom/shared/__tests__/DOMPropertyOperations-test.js +++ b/src/renderers/dom/shared/__tests__/DOMPropertyOperations-test.js @@ -14,6 +14,7 @@ describe('DOMPropertyOperations', function() { var DOMPropertyOperations; var DOMProperty; + var ReactDOMComponentTree; beforeEach(function() { jest.resetModuleRegistry(); @@ -22,6 +23,7 @@ describe('DOMPropertyOperations', function() { DOMPropertyOperations = require('DOMPropertyOperations'); DOMProperty = require('DOMProperty'); + ReactDOMComponentTree = require('ReactDOMComponentTree'); }); describe('createMarkupForProperty', function() { @@ -175,6 +177,7 @@ describe('DOMPropertyOperations', function() { beforeEach(function() { stubNode = document.createElement('div'); + ReactDOMComponentTree.precacheNode({}, stubNode); }); it('should set values as properties by default', function() { @@ -223,6 +226,8 @@ describe('DOMPropertyOperations', function() { it('should not remove empty attributes for special properties', function() { stubNode = document.createElement('input'); + ReactDOMComponentTree.precacheNode({}, stubNode); + DOMPropertyOperations.setValueForProperty(stubNode, 'value', ''); // JSDOM does not behave correctly for attributes/properties //expect(stubNode.getAttribute('value')).toBe(''); @@ -346,6 +351,7 @@ describe('DOMPropertyOperations', function() { beforeEach(function() { stubNode = document.createElement('div'); + ReactDOMComponentTree.precacheNode({}, stubNode); }); it('should remove attributes for normal properties', function() { @@ -361,6 +367,8 @@ describe('DOMPropertyOperations', function() { it('should not remove attributes for special properties', function() { stubNode = document.createElement('input'); + ReactDOMComponentTree.precacheNode({}, stubNode); + stubNode.setAttribute('value', 'foo'); DOMPropertyOperations.deleteValueForProperty(stubNode, 'value'); @@ -371,6 +379,8 @@ describe('DOMPropertyOperations', function() { it('should not leave all options selected when deleting multiple', function() { stubNode = document.createElement('select'); + ReactDOMComponentTree.precacheNode({}, stubNode); + stubNode.multiple = true; stubNode.appendChild(document.createElement('option')); stubNode.appendChild(document.createElement('option')); diff --git a/src/renderers/shared/reconciler/ReactCompositeComponent.js b/src/renderers/shared/reconciler/ReactCompositeComponent.js index 0f8574545a..f27cbdbe80 100644 --- a/src/renderers/shared/reconciler/ReactCompositeComponent.js +++ b/src/renderers/shared/reconciler/ReactCompositeComponent.js @@ -888,7 +888,11 @@ var ReactCompositeComponentMixin = { } } - this._replaceNodeWithMarkup(oldNativeNode, nextMarkup); + this._replaceNodeWithMarkup( + oldNativeNode, + nextMarkup, + prevComponentInstance + ); } }, @@ -897,10 +901,11 @@ var ReactCompositeComponentMixin = { * * @protected */ - _replaceNodeWithMarkup: function(oldNativeNode, nextMarkup) { + _replaceNodeWithMarkup: function(oldNativeNode, nextMarkup, prevInstance) { ReactComponentEnvironment.replaceNodeWithMarkup( oldNativeNode, - nextMarkup + nextMarkup, + prevInstance ); }, From 78aa706491282863e59da12e6c61ba9c23e7b1c1 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Fri, 29 Apr 2016 15:35:53 +0100 Subject: [PATCH 21/32] Merge pull request #6046 from gaearon/new-perf Add new ReactPerf (cherry picked from commit 98a8f49068df73d32b9395f75095574134b0d558) --- grunt/tasks/npm-react-addons.js | 2 +- src/addons/ReactWithAddons.js | 2 +- src/isomorphic/ReactDebugTool.js | 156 +++++++- src/isomorphic/ReactPerfAnalysis.js | 361 ++++++++++++++++++ .../ReactComponentTreeDevtool-test.js | 8 - ...ReactNativeOperationHistoryDevtool-test.js | 8 - src/renderers/dom/client/ReactMount.js | 5 + src/renderers/dom/shared/ReactDOMComponent.js | 8 +- .../dom/shared/ReactDOMTextComponent.js | 5 +- .../reconciler/ReactCompositeComponent.js | 202 +++++++++- .../shared/reconciler/ReactReconciler.js | 65 +++- .../shared/reconciler/ReactUpdates.js | 9 + 12 files changed, 792 insertions(+), 39 deletions(-) create mode 100644 src/isomorphic/ReactPerfAnalysis.js diff --git a/grunt/tasks/npm-react-addons.js b/grunt/tasks/npm-react-addons.js index 948acebf1c..1cb2cf1c30 100644 --- a/grunt/tasks/npm-react-addons.js +++ b/grunt/tasks/npm-react-addons.js @@ -16,7 +16,7 @@ var addons = { docs: 'two-way-binding-helpers', }, Perf: { - module: 'ReactDefaultPerf', + module: 'ReactPerfAnalysis', name: 'perf', docs: 'perf', }, diff --git a/src/addons/ReactWithAddons.js b/src/addons/ReactWithAddons.js index bd13ea495f..89e4e84482 100644 --- a/src/addons/ReactWithAddons.js +++ b/src/addons/ReactWithAddons.js @@ -34,7 +34,7 @@ React.addons = { }; if (__DEV__) { - React.addons.Perf = require('ReactDefaultPerf'); + React.addons.Perf = require('ReactPerfAnalysis'); React.addons.TestUtils = require('ReactTestUtils'); } diff --git a/src/isomorphic/ReactDebugTool.js b/src/isomorphic/ReactDebugTool.js index cd0a3a0c45..ca9d77157d 100644 --- a/src/isomorphic/ReactDebugTool.js +++ b/src/isomorphic/ReactDebugTool.js @@ -11,7 +11,9 @@ 'use strict'; -var ReactInvalidSetStateWarningDevTool = require('ReactInvalidSetStateWarningDevTool'); +var ExecutionEnvironment = require('ExecutionEnvironment'); + +var performanceNow = require('performanceNow'); var warning = require('warning'); var eventHandlers = []; @@ -37,6 +39,56 @@ function emitEvent(handlerFunctionName, arg1, arg2, arg3, arg4, arg5) { } } +var isProfiling = false; +var flushHistory = []; +var currentFlushNesting = 0; +var currentFlushMeasurements = null; +var currentFlushStartTime = null; +var currentTimerDebugID = null; +var currentTimerStartTime = null; +var currentTimerType = null; + +function resetMeasurements() { + if (__DEV__) { + if (!isProfiling || currentFlushNesting === 0) { + currentFlushStartTime = null; + currentFlushMeasurements = null; + return; + } + + var previousStartTime = currentFlushStartTime; + var previousMeasurements = currentFlushMeasurements || []; + var previousOperations = ReactNativeOperationHistoryDevtool.getHistory(); + + if (previousMeasurements.length || previousOperations.length) { + var registeredIDs = ReactComponentTreeDevtool.getRegisteredIDs(); + flushHistory.push({ + duration: performanceNow() - previousStartTime, + measurements: previousMeasurements || [], + operations: previousOperations || [], + treeSnapshot: registeredIDs.reduce((tree, id) => { + var ownerID = ReactComponentTreeDevtool.getOwnerID(id); + var parentID = ReactComponentTreeDevtool.getParentID(id); + tree[id] = { + displayName: ReactComponentTreeDevtool.getDisplayName(id), + text: ReactComponentTreeDevtool.getText(id), + childIDs: ReactComponentTreeDevtool.getChildIDs(id), + // Text nodes don't have owners but this is close enough. + ownerID: ownerID || ReactComponentTreeDevtool.getOwnerID(parentID), + parentID, + }; + return tree; + }, {}), + }); + } + + currentFlushStartTime = performanceNow(); + currentFlushMeasurements = []; + ReactComponentTreeDevtool.purgeUnmountedComponents(); + ReactNativeOperationHistoryDevtool.clearHistory(); + } +} + var ReactDebugTool = { addDevtool(devtool) { eventHandlers.push(devtool); @@ -49,6 +101,95 @@ var ReactDebugTool = { } } }, + beginProfiling() { + if (__DEV__) { + if (isProfiling) { + return; + } + + isProfiling = true; + flushHistory.length = 0; + resetMeasurements(); + } + }, + endProfiling() { + if (__DEV__) { + if (!isProfiling) { + return; + } + + isProfiling = false; + resetMeasurements(); + } + }, + getFlushHistory() { + if (__DEV__) { + return flushHistory; + } + }, + onBeginFlush() { + if (__DEV__) { + currentFlushNesting++; + resetMeasurements(); + } + emitEvent('onBeginFlush'); + }, + onEndFlush() { + if (__DEV__) { + resetMeasurements(); + currentFlushNesting--; + } + emitEvent('onEndFlush'); + }, + onBeginLifeCycleTimer(debugID, timerType) { + emitEvent('onBeginLifeCycleTimer', debugID, timerType); + if (__DEV__) { + if (isProfiling) { + warning( + !currentTimerType, + 'There is an internal error in the React performance measurement code. ' + + 'Did not expect %s timer to start while %s timer is still in ' + + 'progress for %s instance.', + timerType, + currentTimerType || 'no', + (debugID === currentTimerDebugID) ? 'the same' : 'another' + ); + currentTimerStartTime = performanceNow(); + currentTimerDebugID = debugID; + currentTimerType = timerType; + } + } + }, + onEndLifeCycleTimer(debugID, timerType) { + if (__DEV__) { + if (isProfiling) { + warning( + currentTimerType === timerType, + 'There is an internal error in the React performance measurement code. ' + + 'We did not expect %s timer to stop while %s timer is still in ' + + 'progress for %s instance. Please report this as a bug in React.', + timerType, + currentTimerType || 'no', + (debugID === currentTimerDebugID) ? 'the same' : 'another' + ); + currentFlushMeasurements.push({ + timerType, + instanceID: debugID, + duration: performance.now() - currentTimerStartTime, + }); + currentTimerStartTime = null; + currentTimerDebugID = null; + currentTimerType = null; + } + } + emitEvent('onEndLifeCycleTimer', debugID, timerType); + }, + onBeginReconcilerTimer(debugID, timerType) { + emitEvent('onBeginReconcilerTimer', debugID, timerType); + }, + onEndReconcilerTimer(debugID, timerType) { + emitEvent('onEndReconcilerTimer', debugID, timerType); + }, onBeginProcessingChildContext() { emitEvent('onBeginProcessingChildContext'); }, @@ -87,6 +228,17 @@ var ReactDebugTool = { }, }; -ReactDebugTool.addDevtool(ReactInvalidSetStateWarningDevTool); +if (__DEV__) { + var ReactInvalidSetStateWarningDevTool = require('ReactInvalidSetStateWarningDevTool'); + var ReactNativeOperationHistoryDevtool = require('ReactNativeOperationHistoryDevtool'); + var ReactComponentTreeDevtool = require('ReactComponentTreeDevtool'); + ReactDebugTool.addDevtool(ReactInvalidSetStateWarningDevTool); + ReactDebugTool.addDevtool(ReactComponentTreeDevtool); + ReactDebugTool.addDevtool(ReactNativeOperationHistoryDevtool); + var url = (ExecutionEnvironment.canUseDOM && window.location.href) || ''; + if ((/[?&]react_perf\b/).test(url)) { + ReactDebugTool.beginProfiling(); + } +} module.exports = ReactDebugTool; diff --git a/src/isomorphic/ReactPerfAnalysis.js b/src/isomorphic/ReactPerfAnalysis.js new file mode 100644 index 0000000000..f32d6a9b4d --- /dev/null +++ b/src/isomorphic/ReactPerfAnalysis.js @@ -0,0 +1,361 @@ +/** + * 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. + * + * @providesModule ReactPerfAnalysis + */ + +'use strict'; + +var ReactDebugTool = require('ReactDebugTool'); +var warning = require('warning'); + +function roundFloat(val, base = 2) { + var n = Math.pow(10, base); + return Math.floor(val * n) / n; +} + +function getFlushHistory() { + return ReactDebugTool.getFlushHistory(); +} + +function getExclusive(flushHistory = getFlushHistory()) { + var aggregatedStats = {}; + var affectedIDs = {}; + + function updateAggregatedStats(treeSnapshot, instanceID, applyUpdate) { + var {displayName} = treeSnapshot[instanceID]; + + var key = displayName; + var stats = aggregatedStats[key]; + + if (!stats) { + affectedIDs[key] = {}; + stats = aggregatedStats[key] = { + key, + instanceCount: 0, + counts: {}, + durations: {}, + totalDuration: 0, + }; + } + + affectedIDs[key][instanceID] = true; + applyUpdate(stats); + } + + flushHistory.forEach(flush => { + var {measurements, treeSnapshot} = flush; + measurements.forEach(measurement => { + var {duration, instanceID, timerType} = measurement; + updateAggregatedStats(treeSnapshot, instanceID, stats => { + stats.totalDuration += duration; + + if (!stats.durations[timerType]) { + stats.durations[timerType] = 0; + } + stats.durations[timerType] += duration; + + if (!stats.counts[timerType]) { + stats.counts[timerType] = 0; + } + stats.counts[timerType]++; + }); + }); + }); + + return Object.keys(aggregatedStats) + .map(key => ({ + ...aggregatedStats[key], + instanceCount: Object.keys(affectedIDs[key]).length, + })) + .sort((a, b) => b.totalDuration - a.totalDuration); +} + +function getInclusive(flushHistory = getFlushHistory(), wastedOnly) { + var aggregatedStats = {}; + var affectedIDs = {}; + + function updateAggregatedStats(treeSnapshot, instanceID, applyUpdate) { + var {displayName, ownerID} = treeSnapshot[instanceID]; + + var owner = treeSnapshot[ownerID]; + var key = `${owner ? owner.displayName : '(no owner)'} > ${displayName}`; + var stats = aggregatedStats[key]; + + if (!stats) { + affectedIDs[key] = {}; + stats = aggregatedStats[key] = { + key, + instanceCount: 0, + inclusiveRenderDuration: 0, + renderCount: 0, + }; + } + + affectedIDs[key][instanceID] = true; + applyUpdate(stats); + } + + var hasRenderedByID = {}; + flushHistory.forEach(flush => { + var {measurements} = flush; + measurements.forEach(measurement => { + var {instanceID, timerType} = measurement; + if (timerType !== 'render') { + return; + } + hasRenderedByID[instanceID] = true; + }); + }); + + flushHistory.forEach(flush => { + var {measurements, treeSnapshot} = flush; + measurements.forEach(measurement => { + var {duration, instanceID, timerType} = measurement; + if (timerType !== 'render') { + return; + } + updateAggregatedStats(treeSnapshot, instanceID, stats => { + stats.renderCount++; + }); + var nextParentID = instanceID; + while (nextParentID) { + if (hasRenderedByID[nextParentID]) { + updateAggregatedStats(treeSnapshot, nextParentID, stats => { + stats.inclusiveRenderDuration += duration; + }); + } + nextParentID = treeSnapshot[nextParentID].parentID; + } + }); + }); + + return Object.keys(aggregatedStats) + .map(key => ({ + ...aggregatedStats[key], + instanceCount: Object.keys(affectedIDs[key]).length, + })) + .sort((a, b) => b.inclusiveRenderDuration - a.inclusiveRenderDuration); +} + +function getWasted(flushHistory = getFlushHistory()) { + var aggregatedStats = {}; + var affectedIDs = {}; + + function updateAggregatedStats(treeSnapshot, instanceID, applyUpdate) { + var {displayName, ownerID} = treeSnapshot[instanceID]; + + var owner = treeSnapshot[ownerID]; + var key = `${owner ? owner.displayName : '(no owner)'} > ${displayName}`; + var stats = aggregatedStats[key]; + + if (!stats) { + affectedIDs[key] = {}; + stats = aggregatedStats[key] = { + key, + instanceCount: 0, + inclusiveRenderDuration: 0, + renderCount: 0, + }; + } + + affectedIDs[key][instanceID] = true; + applyUpdate(stats); + } + + flushHistory.forEach(flush => { + var {measurements, treeSnapshot, operations} = flush; + var dirtyInstanceIDs = {}; + + operations.forEach(operation => { + var {instanceID} = operation; + + var nextParentID = instanceID; + while (nextParentID) { + dirtyInstanceIDs[nextParentID] = true; + nextParentID = treeSnapshot[nextParentID].parentID; + } + }); + + var renderedCompositeIDs = {}; + measurements.forEach(measurement => { + var {instanceID, timerType} = measurement; + if (timerType !== 'render') { + return; + } + renderedCompositeIDs[instanceID] = true; + }); + + measurements.forEach(measurement => { + var {duration, instanceID, timerType} = measurement; + if (timerType !== 'render') { + return; + } + var { updateCount } = treeSnapshot[instanceID]; + if (dirtyInstanceIDs[instanceID] || updateCount === 0) { + return; + } + updateAggregatedStats(treeSnapshot, instanceID, stats => { + stats.renderCount++; + }); + var nextParentID = instanceID; + while (nextParentID) { + if (!renderedCompositeIDs[nextParentID]) { + break; + } + updateAggregatedStats(treeSnapshot, nextParentID, stats => { + stats.inclusiveRenderDuration += duration; + }); + nextParentID = treeSnapshot[nextParentID].parentID; + } + }); + }); + + return Object.keys(aggregatedStats) + .map(key => ({ + ...aggregatedStats[key], + instanceCount: Object.keys(affectedIDs[key]).length, + })) + .sort((a, b) => b.inclusiveRenderDuration - a.inclusiveRenderDuration); +} + +function getOperations(flushHistory = getFlushHistory()) { + var stats = []; + flushHistory.forEach((flush, flushIndex) => { + var {operations, treeSnapshot} = flush; + operations.forEach(operation => { + var {instanceID, type, payload} = operation; + var {displayName, ownerID} = treeSnapshot[instanceID]; + var owner = treeSnapshot[ownerID]; + var key = `${(owner ? owner.displayName : '(no owner)')} > ${displayName}`; + + stats.push({ + flushIndex, + instanceID, + key, + type, + ownerID, + payload, + }); + }); + }); + return stats; +} + +function printExclusive(flushHistory) { + var stats = getExclusive(flushHistory); + var table = stats.map(item => { + var {key, instanceCount, totalDuration} = item; + var renderCount = item.counts.render || 0; + var renderDuration = item.durations.render || 0; + return { + 'Component': key, + 'Total time (ms)': roundFloat(totalDuration), + 'Instance count': instanceCount, + 'Total render time (ms)': roundFloat(renderDuration), + 'Average render time (ms)': renderCount ? + roundFloat(renderDuration / renderCount) : + undefined, + 'Render count': renderCount, + 'Total lifecycle time (ms)': roundFloat(totalDuration - renderDuration), + }; + }); + console.table(table); +} + +function printInclusive(flushHistory) { + var stats = getInclusive(flushHistory); + var table = stats.map(item => { + var {key, instanceCount, inclusiveRenderDuration, renderCount} = item; + return { + 'Owner > Component': key, + 'Inclusive render time (ms)': roundFloat(inclusiveRenderDuration), + 'Instance count': instanceCount, + 'Render count': renderCount, + }; + }); + console.table(table); +} + +function printWasted(flushHistory) { + var stats = getWasted(flushHistory); + var table = stats.map(item => { + var {key, instanceCount, inclusiveRenderDuration, renderCount} = item; + return { + 'Owner > Component': key, + 'Inclusive wasted time (ms)': roundFloat(inclusiveRenderDuration), + 'Instance count': instanceCount, + 'Render count': renderCount, + }; + }); + console.table(table); +} + +function printOperations(flushHistory) { + var stats = getOperations(flushHistory); + var table = stats.map(stat => ({ + 'Owner > Node': stat.key, + 'Operation': stat.type, + 'Payload': typeof stat.payload === 'object' ? + JSON.stringify(stat.payload) : + stat.payload, + 'Flush index': stat.flushIndex, + 'Owner Component ID': stat.ownerID, + 'DOM Component ID': stat.instanceID, + })); + console.table(table); +} + +var warnedAboutPrintDOM = false; +function printDOM(measurements) { + warning( + warnedAboutPrintDOM, + '`ReactPerf.printDOM(...)` is deprecated. Use ' + + '`ReactPerf.printOperations(...)` instead.' + ); + warnedAboutPrintDOM = true; + return printOperations(measurements); +} + +var warnedAboutGetMeasurementsSummaryMap = false; +function getMeasurementsSummaryMap(measurements) { + warning( + warnedAboutGetMeasurementsSummaryMap, + '`ReactPerf.getMeasurementsSummaryMap(...)` is deprecated. Use ' + + '`ReactPerf.getWasted(...)` instead.' + ); + warnedAboutGetMeasurementsSummaryMap = true; + return getWasted(measurements); +} + +function start() { + ReactDebugTool.beginProfiling(); +} + +function stop() { + ReactDebugTool.endProfiling(); +} + +var ReactPerfAnalysis = { + getFlushHistory, + getExclusive, + getInclusive, + getWasted, + getOperations, + printExclusive, + printInclusive, + printWasted, + printOperations, + start, + stop, + // Deprecated: + printDOM, + getMeasurementsSummaryMap, +}; + +module.exports = ReactPerfAnalysis; diff --git a/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js b/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js index 48f3b9d3d9..7ac5be4b98 100644 --- a/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js +++ b/src/isomorphic/devtools/__tests__/ReactComponentTreeDevtool-test.js @@ -13,7 +13,6 @@ describe('ReactComponentTreeDevtool', () => { var React; - var ReactDebugTool; var ReactDOM; var ReactDOMServer; var ReactInstanceMap; @@ -23,17 +22,10 @@ describe('ReactComponentTreeDevtool', () => { jest.resetModuleRegistry(); React = require('React'); - ReactDebugTool = require('ReactDebugTool'); ReactDOM = require('ReactDOM'); ReactDOMServer = require('ReactDOMServer'); ReactInstanceMap = require('ReactInstanceMap'); ReactComponentTreeDevtool = require('ReactComponentTreeDevtool'); - - ReactDebugTool.addDevtool(ReactComponentTreeDevtool); - }); - - afterEach(() => { - ReactDebugTool.removeDevtool(ReactComponentTreeDevtool); }); function getRootDisplayNames() { diff --git a/src/isomorphic/devtools/__tests__/ReactNativeOperationHistoryDevtool-test.js b/src/isomorphic/devtools/__tests__/ReactNativeOperationHistoryDevtool-test.js index 3b5924aae5..667e0f3981 100644 --- a/src/isomorphic/devtools/__tests__/ReactNativeOperationHistoryDevtool-test.js +++ b/src/isomorphic/devtools/__tests__/ReactNativeOperationHistoryDevtool-test.js @@ -13,7 +13,6 @@ describe('ReactNativeOperationHistoryDevtool', () => { var React; - var ReactDebugTool; var ReactDOM; var ReactDOMComponentTree; var ReactDOMFeatureFlags; @@ -23,17 +22,10 @@ describe('ReactNativeOperationHistoryDevtool', () => { jest.resetModuleRegistry(); React = require('React'); - ReactDebugTool = require('ReactDebugTool'); ReactDOM = require('ReactDOM'); ReactDOMComponentTree = require('ReactDOMComponentTree'); ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); ReactNativeOperationHistoryDevtool = require('ReactNativeOperationHistoryDevtool'); - - ReactDebugTool.addDevtool(ReactNativeOperationHistoryDevtool); - }); - - afterEach(() => { - ReactDebugTool.removeDevtool(ReactNativeOperationHistoryDevtool); }); function assertHistoryMatches(expectedHistory) { diff --git a/src/renderers/dom/client/ReactMount.js b/src/renderers/dom/client/ReactMount.js index 2981814ead..d7c8b3aed5 100644 --- a/src/renderers/dom/client/ReactMount.js +++ b/src/renderers/dom/client/ReactMount.js @@ -308,6 +308,10 @@ var ReactMount = { shouldReuseMarkup, context ) { + if (__DEV__) { + ReactInstrumentation.debugTool.onBeginFlush(); + } + // Various parts of our code (such as ReactCompositeComponent's // _renderValidatedComponent) assume that calls to render aren't nested; // verify that that's the case. @@ -359,6 +363,7 @@ var ReactMount = { ReactInstrumentation.debugTool.onMountRootComponent( componentInstance._renderedComponent._debugID ); + ReactInstrumentation.debugTool.onEndFlush(); } return componentInstance; diff --git a/src/renderers/dom/shared/ReactDOMComponent.js b/src/renderers/dom/shared/ReactDOMComponent.js index f728339c29..ec26f7788e 100644 --- a/src/renderers/dom/shared/ReactDOMComponent.js +++ b/src/renderers/dom/shared/ReactDOMComponent.js @@ -1104,9 +1104,11 @@ ReactDOMComponent.Mixin = { this._domID = null; this._wrapperState = null; - if (this._contentDebugID) { - ReactInstrumentation.debugTool.onUnmountComponent(this._contentDebugID); - this._contentDebugID = null; + if (__DEV__) { + if (this._contentDebugID) { + ReactInstrumentation.debugTool.onUnmountComponent(this._contentDebugID); + this._contentDebugID = null; + } } }, diff --git a/src/renderers/dom/shared/ReactDOMTextComponent.js b/src/renderers/dom/shared/ReactDOMTextComponent.js index 5041706287..97ed919d78 100644 --- a/src/renderers/dom/shared/ReactDOMTextComponent.js +++ b/src/renderers/dom/shared/ReactDOMTextComponent.js @@ -145,7 +145,10 @@ Object.assign(ReactDOMTextComponent.prototype, { ); if (__DEV__) { - ReactInstrumentation.debugTool.onSetText(this._debugID, nextStringText); + ReactInstrumentation.debugTool.onSetText( + this._debugID, + nextStringText + ); } } } diff --git a/src/renderers/shared/reconciler/ReactCompositeComponent.js b/src/renderers/shared/reconciler/ReactCompositeComponent.js index f27cbdbe80..0b5982e349 100644 --- a/src/renderers/shared/reconciler/ReactCompositeComponent.js +++ b/src/renderers/shared/reconciler/ReactCompositeComponent.js @@ -60,6 +60,40 @@ function warnIfInvalidElement(Component, element) { } } +function invokeComponentDidMountWithTimer() { + var publicInstance = this._instance; + if (this._debugID !== 0) { + ReactInstrumentation.debugTool.onBeginLifeCycleTimer( + this._debugID, + 'componentDidMount' + ); + } + publicInstance.componentDidMount(); + if (this._debugID !== 0) { + ReactInstrumentation.debugTool.onEndLifeCycleTimer( + this._debugID, + 'componentDidMount' + ); + } +} + +function invokeComponentDidUpdateWithTimer(prevProps, prevState, prevContext) { + var publicInstance = this._instance; + if (this._debugID !== 0) { + ReactInstrumentation.debugTool.onBeginLifeCycleTimer( + this._debugID, + 'componentDidUpdate' + ); + } + publicInstance.componentDidUpdate(prevProps, prevState, prevContext); + if (this._debugID !== 0) { + ReactInstrumentation.debugTool.onEndLifeCycleTimer( + this._debugID, + 'componentDidUpdate' + ); + } +} + function shouldConstruct(Component) { return Component.prototype && Component.prototype.isReactComponent; } @@ -302,7 +336,11 @@ var ReactCompositeComponentMixin = { } if (inst.componentDidMount) { - transaction.getReactMountReady().enqueue(inst.componentDidMount, inst); + if (__DEV__) { + transaction.getReactMountReady().enqueue(invokeComponentDidMountWithTimer, this); + } else { + transaction.getReactMountReady().enqueue(inst.componentDidMount, inst); + } } return markup; @@ -323,11 +361,47 @@ var ReactCompositeComponentMixin = { _constructComponentWithoutOwner: function(publicProps, publicContext) { var Component = this._currentElement.type; + var instanceOrElement; if (shouldConstruct(Component)) { - return new Component(publicProps, publicContext, ReactUpdateQueue); + if (__DEV__) { + if (this._debugID !== 0) { + ReactInstrumentation.debugTool.onBeginLifeCycleTimer( + this._debugID, + 'ctor' + ); + } + } + instanceOrElement = new Component(publicProps, publicContext, ReactUpdateQueue); + if (__DEV__) { + if (this._debugID !== 0) { + ReactInstrumentation.debugTool.onEndLifeCycleTimer( + this._debugID, + 'ctor' + ); + } + } } else { - return Component(publicProps, publicContext, ReactUpdateQueue); + // This can still be an instance in case of factory components + // but we'll count this as time spent rendering as the more common case. + if (__DEV__) { + if (this._debugID !== 0) { + ReactInstrumentation.debugTool.onBeginLifeCycleTimer( + this._debugID, + 'render' + ); + } + } + instanceOrElement = Component(publicProps, publicContext, ReactUpdateQueue); + if (__DEV__) { + if (this._debugID !== 0) { + ReactInstrumentation.debugTool.onBeginLifeCycleTimer( + this._debugID, + 'render' + ); + } + } } + return instanceOrElement; }, performInitialMountWithErrorHandling: function( @@ -363,7 +437,23 @@ var ReactCompositeComponentMixin = { performInitialMount: function(renderedElement, nativeParent, nativeContainerInfo, transaction, context) { var inst = this._instance; if (inst.componentWillMount) { + if (__DEV__) { + if (this._debugID !== 0) { + ReactInstrumentation.debugTool.onBeginLifeCycleTimer( + this._debugID, + 'componentWillMount' + ); + } + } inst.componentWillMount(); + if (__DEV__) { + if (this._debugID !== 0) { + ReactInstrumentation.debugTool.onEndLifeCycleTimer( + this._debugID, + 'componentWillMount' + ); + } + } // When mounting, calls to `setState` by `componentWillMount` will set // `this._pendingStateQueue` without triggering a re-render. if (this._pendingStateQueue) { @@ -421,12 +511,28 @@ var ReactCompositeComponentMixin = { if (inst.componentWillUnmount && !inst._calledComponentWillUnmount) { inst._calledComponentWillUnmount = true; + if (__DEV__) { + if (this._debugID !== 0) { + ReactInstrumentation.debugTool.onBeginLifeCycleTimer( + this._debugID, + 'componentWillUnmount' + ); + } + } if (safely) { var name = this.getName() + '.componentWillUnmount()'; ReactErrorUtils.invokeGuardedCallback(name, inst.componentWillUnmount.bind(inst)); } else { inst.componentWillUnmount(); } + if (__DEV__) { + if (this._debugID !== 0) { + ReactInstrumentation.debugTool.onEndLifeCycleTimer( + this._debugID, + 'componentWillUnmount' + ); + } + } } if (this._renderedComponent) { @@ -721,15 +827,47 @@ var ReactCompositeComponentMixin = { // _pendingStateQueue which will ensure that any state updates gets // immediately reconciled instead of waiting for the next batch. if (willReceive && inst.componentWillReceiveProps) { + if (__DEV__) { + if (this._debugID !== 0) { + ReactInstrumentation.debugTool.onBeginLifeCycleTimer( + this._debugID, + 'componentWillReceiveProps' + ); + } + } inst.componentWillReceiveProps(nextProps, nextContext); + if (__DEV__) { + if (this._debugID !== 0) { + ReactInstrumentation.debugTool.onEndLifeCycleTimer( + this._debugID, + 'componentWillReceiveProps' + ); + } + } } var nextState = this._processPendingState(nextProps, nextContext); + var shouldUpdate = true; - var shouldUpdate = - this._pendingForceUpdate || - !inst.shouldComponentUpdate || - inst.shouldComponentUpdate(nextProps, nextState, nextContext); + if (!this._pendingForceUpdate && inst.shouldComponentUpdate) { + if (__DEV__) { + if (this._debugID !== 0) { + ReactInstrumentation.debugTool.onBeginLifeCycleTimer( + this._debugID, + 'shouldComponentUpdate' + ); + } + } + shouldUpdate = inst.shouldComponentUpdate(nextProps, nextState, nextContext); + if (__DEV__) { + if (this._debugID !== 0) { + ReactInstrumentation.debugTool.onEndLifeCycleTimer( + this._debugID, + 'shouldComponentUpdate' + ); + } + } + } if (__DEV__) { warning( @@ -824,7 +962,23 @@ var ReactCompositeComponentMixin = { } if (inst.componentWillUpdate) { + if (__DEV__) { + if (this._debugID !== 0) { + ReactInstrumentation.debugTool.onBeginLifeCycleTimer( + this._debugID, + 'componentWillUpdate' + ); + } + } inst.componentWillUpdate(nextProps, nextState, nextContext); + if (__DEV__) { + if (this._debugID !== 0) { + ReactInstrumentation.debugTool.onEndLifeCycleTimer( + this._debugID, + 'componentWillUpdate' + ); + } + } } this._currentElement = nextElement; @@ -836,10 +990,17 @@ var ReactCompositeComponentMixin = { this._updateRenderedComponent(transaction, unmaskedContext); if (hasComponentDidUpdate) { - transaction.getReactMountReady().enqueue( - inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext), - inst - ); + if (__DEV__) { + transaction.getReactMountReady().enqueue( + invokeComponentDidUpdateWithTimer.bind(this, prevProps, prevState, prevContext), + this + ); + } else { + transaction.getReactMountReady().enqueue( + inst.componentDidUpdate.bind(inst, prevProps, prevState, prevContext), + inst + ); + } } }, @@ -914,7 +1075,25 @@ var ReactCompositeComponentMixin = { */ _renderValidatedComponentWithoutOwnerOrContext: function() { var inst = this._instance; + + if (__DEV__) { + if (this._debugID !== 0) { + ReactInstrumentation.debugTool.onBeginLifeCycleTimer( + this._debugID, + 'render' + ); + } + } var renderedComponent = inst.render(); + if (__DEV__) { + if (this._debugID !== 0) { + ReactInstrumentation.debugTool.onEndLifeCycleTimer( + this._debugID, + 'render' + ); + } + } + if (__DEV__) { // We allow auto-mocks to proceed as if they're returning null. if (renderedComponent === undefined && @@ -948,6 +1127,7 @@ var ReactCompositeComponentMixin = { 'returned undefined, an array or some other invalid object.', this.getName() || 'ReactCompositeComponent' ); + return renderedComponent; }, diff --git a/src/renderers/shared/reconciler/ReactReconciler.js b/src/renderers/shared/reconciler/ReactReconciler.js index d2a2dbcac8..cef23ca850 100644 --- a/src/renderers/shared/reconciler/ReactReconciler.js +++ b/src/renderers/shared/reconciler/ReactReconciler.js @@ -42,6 +42,14 @@ var ReactReconciler = { nativeContainerInfo, context ) { + if (__DEV__) { + if (internalInstance._debugID !== 0) { + ReactInstrumentation.debugTool.onBeginReconcilerTimer( + internalInstance._debugID, + 'mountComponent' + ); + } + } var markup = internalInstance.mountComponent( transaction, nativeParent, @@ -54,7 +62,13 @@ var ReactReconciler = { } if (__DEV__) { if (internalInstance._debugID !== 0) { - ReactInstrumentation.debugTool.onMountComponent(internalInstance._debugID); + ReactInstrumentation.debugTool.onEndReconcilerTimer( + internalInstance._debugID, + 'mountComponent' + ); + ReactInstrumentation.debugTool.onMountComponent( + internalInstance._debugID + ); } } return markup; @@ -75,11 +89,25 @@ var ReactReconciler = { * @internal */ unmountComponent: function(internalInstance, safely) { + if (__DEV__) { + if (internalInstance._debugID !== 0) { + ReactInstrumentation.debugTool.onBeginReconcilerTimer( + internalInstance._debugID, + 'unmountComponent' + ); + } + } ReactRef.detachRefs(internalInstance, internalInstance._currentElement); internalInstance.unmountComponent(safely); if (__DEV__) { if (internalInstance._debugID !== 0) { - ReactInstrumentation.debugTool.onUnmountComponent(internalInstance._debugID); + ReactInstrumentation.debugTool.onEndReconcilerTimer( + internalInstance._debugID, + 'unmountComponent' + ); + ReactInstrumentation.debugTool.onUnmountComponent( + internalInstance._debugID + ); } } }, @@ -114,6 +142,15 @@ var ReactReconciler = { return; } + if (__DEV__) { + if (internalInstance._debugID !== 0) { + ReactInstrumentation.debugTool.onBeginReconcilerTimer( + internalInstance._debugID, + 'receiveComponent' + ); + } + } + var refsChanged = ReactRef.shouldUpdateRefs( prevElement, nextElement @@ -133,7 +170,13 @@ var ReactReconciler = { if (__DEV__) { if (internalInstance._debugID !== 0) { - ReactInstrumentation.debugTool.onUpdateComponent(internalInstance._debugID); + ReactInstrumentation.debugTool.onEndReconcilerTimer( + internalInstance._debugID, + 'receiveComponent' + ); + ReactInstrumentation.debugTool.onUpdateComponent( + internalInstance._debugID + ); } } }, @@ -149,10 +192,24 @@ var ReactReconciler = { internalInstance, transaction ) { + if (__DEV__) { + if (internalInstance._debugID !== 0) { + ReactInstrumentation.debugTool.onBeginReconcilerTimer( + internalInstance._debugID, + 'performUpdateIfNecessary' + ); + } + } internalInstance.performUpdateIfNecessary(transaction); if (__DEV__) { if (internalInstance._debugID !== 0) { - ReactInstrumentation.debugTool.onUpdateComponent(internalInstance._debugID); + ReactInstrumentation.debugTool.onEndReconcilerTimer( + internalInstance._debugID, + 'performUpdateIfNecessary' + ); + ReactInstrumentation.debugTool.onUpdateComponent( + internalInstance._debugID + ); } } }, diff --git a/src/renderers/shared/reconciler/ReactUpdates.js b/src/renderers/shared/reconciler/ReactUpdates.js index 6fd1d2f3b5..ff593be663 100644 --- a/src/renderers/shared/reconciler/ReactUpdates.js +++ b/src/renderers/shared/reconciler/ReactUpdates.js @@ -14,6 +14,7 @@ var CallbackQueue = require('CallbackQueue'); var PooledClass = require('PooledClass'); var ReactFeatureFlags = require('ReactFeatureFlags'); +var ReactInstrumentation = require('ReactInstrumentation'); var ReactPerf = require('ReactPerf'); var ReactReconciler = require('ReactReconciler'); var Transaction = require('Transaction'); @@ -184,6 +185,10 @@ function runBatchedUpdates(transaction) { } var flushBatchedUpdates = function() { + if (__DEV__) { + ReactInstrumentation.debugTool.onBeginFlush(); + } + // ReactUpdatesFlushTransaction's wrappers will clear the dirtyComponents // array and perform any updates enqueued by mount-ready handlers (i.e., // componentDidUpdate) but we need to check here too in order to catch @@ -203,6 +208,10 @@ var flushBatchedUpdates = function() { CallbackQueue.release(queue); } } + + if (__DEV__) { + ReactInstrumentation.debugTool.onEndFlush(); + } }; flushBatchedUpdates = ReactPerf.measure( 'ReactUpdates', From 09c6d53e64cf67db145fc4eb7466a28d94df9630 Mon Sep 17 00:00:00 2001 From: Christoph Pojer Date: Thu, 28 Apr 2016 15:03:20 +0900 Subject: [PATCH 22/32] Merge pull request #6620 from cpojer/master Update to Jest 12. Codemod to new Jest APIs. (cherry picked from commit d07b554291d85f07b6c1dc76b7f3ebb612ff0195) --- grunt/tasks/jest.js | 2 +- package.json | 2 +- .../__tests__/ReactErrorBoundaries-test.js | 4 ++-- .../classic/class/__tests__/ReactBind-test.js | 8 ++++---- .../class/__tests__/ReactBindOptout-test.js | 10 +++++----- .../classic/class/__tests__/ReactClass-test.js | 2 +- .../class/__tests__/ReactClassMixin-test.js | 10 +++++----- .../__tests__/ReactBrowserEventEmitter-test.js | 6 +++--- .../__tests__/ReactEventListener-test.js | 2 +- .../dom/client/__tests__/ReactMount-test.js | 4 ++-- .../__tests__/SelectEventPlugin-test.js | 2 +- .../__tests__/DisabledInputUtil-test.js | 2 +- .../wrappers/__tests__/ReactDOMInput-test.js | 18 +++++++++--------- .../wrappers/__tests__/ReactDOMSelect-test.js | 2 +- .../__tests__/ReactDOMTextarea-test.js | 2 +- .../__tests__/ReactServerRendering-test.js | 4 ++-- .../__tests__/DOMPropertyOperations-test.js | 2 +- .../shared/__tests__/ReactDOMComponent-test.js | 18 +++++++++--------- src/renderers/native/__mocks__/UIManager.js | 8 ++++---- .../ReactNativeAttributePayload-test.js | 12 ++++++------ .../event/__tests__/EventPluginHub-test.js | 2 +- .../__tests__/ReactComponent-test.js | 2 +- .../ReactCompositeComponentNestedState-test.js | 2 +- .../ReactCompositeComponentState-test.js | 2 +- .../__tests__/ReactMultiChild-test.js | 18 +++++++++--------- .../utils/__tests__/accumulateInto-test.js | 2 +- src/test/__tests__/ReactTestUtils-test.js | 2 +- 27 files changed, 75 insertions(+), 75 deletions(-) diff --git a/grunt/tasks/jest.js b/grunt/tasks/jest.js index da406e9111..fb8c1113ab 100644 --- a/grunt/tasks/jest.js +++ b/grunt/tasks/jest.js @@ -77,7 +77,7 @@ function writeTempConfig(callback) { function run(done, configPath) { grunt.log.writeln('running jest (this may take a while)'); - var args = ['--harmony', path.join('node_modules', 'jest-cli', 'bin', 'jest')]; + var args = ['--harmony', path.join('node_modules', 'jest-cli', 'bin', 'jest'), '--runInBand']; if (configPath) { args.push('--config', configPath); } diff --git a/package.json b/package.json index 6616bd76be..c08a55119b 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "gulp-babel": "^6.0.0", "gulp-flatten": "^0.2.0", "gzip-js": "~0.3.2", - "jest-cli": "^0.9.0", + "jest-cli": "^12.0.2", "loose-envify": "^1.1.0", "object-assign": "^4.1.0", "platform": "^1.1.0", diff --git a/src/core/__tests__/ReactErrorBoundaries-test.js b/src/core/__tests__/ReactErrorBoundaries-test.js index 6272c89c58..c61d1f524d 100644 --- a/src/core/__tests__/ReactErrorBoundaries-test.js +++ b/src/core/__tests__/ReactErrorBoundaries-test.js @@ -52,7 +52,7 @@ describe('ReactErrorBoundaries', function() { var EventPluginHub = require('EventPluginHub'); var container = document.createElement('div'); - EventPluginHub.putListener = jest.genMockFn(); + EventPluginHub.putListener = jest.fn(); ReactDOM.render(, container); expect(EventPluginHub.putListener).not.toBeCalled(); }); @@ -157,7 +157,7 @@ describe('ReactErrorBoundaries', function() { var EventPluginHub = require('EventPluginHub'); var container = document.createElement('div'); - EventPluginHub.putListener = jest.genMockFn(); + EventPluginHub.putListener = jest.fn(); ReactDOM.render(, container); expect(EventPluginHub.putListener).toBeCalled(); }); diff --git a/src/isomorphic/classic/class/__tests__/ReactBind-test.js b/src/isomorphic/classic/class/__tests__/ReactBind-test.js index 63aa641a9c..bbf9d0c7da 100644 --- a/src/isomorphic/classic/class/__tests__/ReactBind-test.js +++ b/src/isomorphic/classic/class/__tests__/ReactBind-test.js @@ -20,9 +20,9 @@ describe('autobinding', function() { it('Holds reference to instance', function() { - var mouseDidEnter = jest.genMockFn(); - var mouseDidLeave = jest.genMockFn(); - var mouseDidClick = jest.genMockFn(); + var mouseDidEnter = jest.fn(); + var mouseDidLeave = jest.fn(); + var mouseDidClick = jest.fn(); var TestBindComponent = React.createClass({ getInitialState: function() { @@ -95,7 +95,7 @@ describe('autobinding', function() { }); it('works with mixins', function() { - var mouseDidClick = jest.genMockFn(); + var mouseDidClick = jest.fn(); var TestMixin = { onClick: mouseDidClick, diff --git a/src/isomorphic/classic/class/__tests__/ReactBindOptout-test.js b/src/isomorphic/classic/class/__tests__/ReactBindOptout-test.js index 395e345770..54d3c11f5c 100644 --- a/src/isomorphic/classic/class/__tests__/ReactBindOptout-test.js +++ b/src/isomorphic/classic/class/__tests__/ReactBindOptout-test.js @@ -20,9 +20,9 @@ describe('autobind optout', function() { it('should work with manual binding', function() { - var mouseDidEnter = jest.genMockFn(); - var mouseDidLeave = jest.genMockFn(); - var mouseDidClick = jest.genMockFn(); + var mouseDidEnter = jest.fn(); + var mouseDidLeave = jest.fn(); + var mouseDidClick = jest.fn(); var TestBindComponent = React.createClass({ autobind: false, @@ -138,7 +138,7 @@ describe('autobind optout', function() { }); it('works with mixins that have not opted out of autobinding', function() { - var mouseDidClick = jest.genMockFn(); + var mouseDidClick = jest.fn(); var TestMixin = { onClick: mouseDidClick, @@ -164,7 +164,7 @@ describe('autobind optout', function() { }); it('works with mixins that have opted out of autobinding', function() { - var mouseDidClick = jest.genMockFn(); + var mouseDidClick = jest.fn(); var TestMixin = { autobind: false, diff --git a/src/isomorphic/classic/class/__tests__/ReactClass-test.js b/src/isomorphic/classic/class/__tests__/ReactClass-test.js index 07a1e4bcb3..9a5587f7b7 100644 --- a/src/isomorphic/classic/class/__tests__/ReactClass-test.js +++ b/src/isomorphic/classic/class/__tests__/ReactClass-test.js @@ -43,7 +43,7 @@ describe('ReactClass-spec', function() { }); it('should copy prop types onto the Constructor', function() { - var propValidator = jest.genMockFn(); + var propValidator = jest.fn(); var TestComponent = React.createClass({ propTypes: { value: propValidator, diff --git a/src/isomorphic/classic/class/__tests__/ReactClassMixin-test.js b/src/isomorphic/classic/class/__tests__/ReactClassMixin-test.js index a65c68ff07..f32af1e046 100644 --- a/src/isomorphic/classic/class/__tests__/ReactClassMixin-test.js +++ b/src/isomorphic/classic/class/__tests__/ReactClassMixin-test.js @@ -25,8 +25,8 @@ describe('ReactClass-mixin', function() { beforeEach(function() { React = require('React'); ReactTestUtils = require('ReactTestUtils'); - mixinPropValidator = jest.genMockFn(); - componentPropValidator = jest.genMockFn(); + mixinPropValidator = jest.fn(); + componentPropValidator = jest.fn(); var MixinA = { propTypes: { @@ -107,7 +107,7 @@ describe('ReactClass-mixin', function() { }); it('should support merging propTypes and statics', function() { - var listener = jest.genMockFn(); + var listener = jest.fn(); var instance = ; instance = ReactTestUtils.renderIntoDocument(instance); @@ -122,7 +122,7 @@ describe('ReactClass-mixin', function() { }); it('should support chaining delegate functions', function() { - var listener = jest.genMockFn(); + var listener = jest.fn(); var instance = ; instance = ReactTestUtils.renderIntoDocument(instance); @@ -135,7 +135,7 @@ describe('ReactClass-mixin', function() { }); it('should chain functions regardless of spec property order', function() { - var listener = jest.genMockFn(); + var listener = jest.fn(); var instance = ; instance = ReactTestUtils.renderIntoDocument(instance); diff --git a/src/renderers/dom/client/__tests__/ReactBrowserEventEmitter-test.js b/src/renderers/dom/client/__tests__/ReactBrowserEventEmitter-test.js index 5dbf91c2cd..9de6b2e8e2 100644 --- a/src/renderers/dom/client/__tests__/ReactBrowserEventEmitter-test.js +++ b/src/renderers/dom/client/__tests__/ReactBrowserEventEmitter-test.js @@ -35,7 +35,7 @@ var recordIDAndReturnFalse = function(id, event) { recordID(id); return false; }; -var LISTENER = jest.genMockFn(); +var LISTENER = jest.fn(); var ON_CLICK_KEY = keyOf({onClick: null}); var ON_TOUCH_TAP_KEY = keyOf({onTouchTap: null}); var ON_CHANGE_KEY = keyOf({onChange: null}); @@ -284,7 +284,7 @@ describe('ReactBrowserEventEmitter', function() { */ it('should invoke handlers that were removed while bubbling', function() { - var handleParentClick = jest.genMockFn(); + var handleParentClick = jest.fn(); var handleChildClick = function(event) { EventPluginHub.deleteAllListeners(getInternal(PARENT)); }; @@ -303,7 +303,7 @@ describe('ReactBrowserEventEmitter', function() { }); it('should not invoke newly inserted handlers while bubbling', function() { - var handleParentClick = jest.genMockFn(); + var handleParentClick = jest.fn(); var handleChildClick = function(event) { EventPluginHub.putListener( getInternal(PARENT), diff --git a/src/renderers/dom/client/__tests__/ReactEventListener-test.js b/src/renderers/dom/client/__tests__/ReactEventListener-test.js index 49d4f195d5..049284f8f7 100644 --- a/src/renderers/dom/client/__tests__/ReactEventListener-test.js +++ b/src/renderers/dom/client/__tests__/ReactEventListener-test.js @@ -30,7 +30,7 @@ describe('ReactEventListener', function() { ReactEventListener = require('ReactEventListener'); ReactTestUtils = require('ReactTestUtils'); - handleTopLevel = jest.genMockFn(); + handleTopLevel = jest.fn(); ReactEventListener._handleTopLevel = handleTopLevel; }); diff --git a/src/renderers/dom/client/__tests__/ReactMount-test.js b/src/renderers/dom/client/__tests__/ReactMount-test.js index 06b1c16415..96ca4aef33 100644 --- a/src/renderers/dom/client/__tests__/ReactMount-test.js +++ b/src/renderers/dom/client/__tests__/ReactMount-test.js @@ -87,8 +87,8 @@ describe('ReactMount', function() { it('should unmount and remount if the key changes', function() { var container = document.createElement('container'); - var mockMount = jest.genMockFn(); - var mockUnmount = jest.genMockFn(); + var mockMount = jest.fn(); + var mockUnmount = jest.fn(); var Component = React.createClass({ componentDidMount: mockMount, diff --git a/src/renderers/dom/client/eventPlugins/__tests__/SelectEventPlugin-test.js b/src/renderers/dom/client/eventPlugins/__tests__/SelectEventPlugin-test.js index 0f032902d4..46e21b96ce 100644 --- a/src/renderers/dom/client/eventPlugins/__tests__/SelectEventPlugin-test.js +++ b/src/renderers/dom/client/eventPlugins/__tests__/SelectEventPlugin-test.js @@ -66,7 +66,7 @@ describe('SelectEventPlugin', function() { }, }); - var cb = jest.genMockFn(); + var cb = jest.fn(); var rendered = ReactTestUtils.renderIntoDocument( diff --git a/src/renderers/dom/client/wrappers/__tests__/DisabledInputUtil-test.js b/src/renderers/dom/client/wrappers/__tests__/DisabledInputUtil-test.js index 0e4c77d556..a99546ecc2 100644 --- a/src/renderers/dom/client/wrappers/__tests__/DisabledInputUtil-test.js +++ b/src/renderers/dom/client/wrappers/__tests__/DisabledInputUtil-test.js @@ -36,7 +36,7 @@ describe('DisabledInputUtils', function() { return element; } - var onClick = jest.genMockFn(); + var onClick = jest.fn(); elements.forEach(function(tagName) { diff --git a/src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js b/src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js index c749675262..a1ab23d9e8 100644 --- a/src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js +++ b/src/renderers/dom/client/wrappers/__tests__/ReactDOMInput-test.js @@ -236,7 +236,7 @@ describe('ReactDOMInput', function() { }); it('should support ReactLink', function() { - var link = new ReactLink('yolo', jest.genMockFn()); + var link = new ReactLink('yolo', jest.fn()); var instance = ; instance = ReactTestUtils.renderIntoDocument(instance); @@ -253,7 +253,7 @@ describe('ReactDOMInput', function() { }); it('should warn with value and no onChange handler', function() { - var link = new ReactLink('yolo', jest.genMockFn()); + var link = new ReactLink('yolo', jest.fn()); ReactTestUtils.renderIntoDocument(); expect(console.error.argsForCall.length).toBe(1); expect(console.error.argsForCall[0][0]).toContain( @@ -261,7 +261,7 @@ describe('ReactDOMInput', function() { ); ReactTestUtils.renderIntoDocument( - + ); expect(console.error.argsForCall.length).toBe(1); ReactTestUtils.renderIntoDocument(); @@ -293,7 +293,7 @@ describe('ReactDOMInput', function() { it('should throw if both value and valueLink are provided', function() { var node = document.createElement('div'); - var link = new ReactLink('yolo', jest.genMockFn()); + var link = new ReactLink('yolo', jest.fn()); var instance = ; expect(() => ReactDOM.render(instance, node)).not.toThrow(); @@ -313,7 +313,7 @@ describe('ReactDOMInput', function() { }); it('should support checkedLink', function() { - var link = new ReactLink(true, jest.genMockFn()); + var link = new ReactLink(true, jest.fn()); var instance = ; instance = ReactTestUtils.renderIntoDocument(instance); @@ -331,7 +331,7 @@ describe('ReactDOMInput', function() { it('should warn with checked and no onChange handler', function() { var node = document.createElement('div'); - var link = new ReactLink(true, jest.genMockFn()); + var link = new ReactLink(true, jest.fn()); ReactDOM.render(, node); expect(console.error.argsForCall.length).toBe(1); expect(console.error.argsForCall[0][0]).toContain( @@ -342,7 +342,7 @@ describe('ReactDOMInput', function() { ); expect(console.error.argsForCall.length).toBe(1); @@ -370,7 +370,7 @@ describe('ReactDOMInput', function() { it('should throw if both checked and checkedLink are provided', function() { var node = document.createElement('div'); - var link = new ReactLink(true, jest.genMockFn()); + var link = new ReactLink(true, jest.fn()); var instance = ; expect(() => ReactDOM.render(instance, node)).not.toThrow(); @@ -392,7 +392,7 @@ describe('ReactDOMInput', function() { it('should throw if both checkedLink and valueLink are provided', function() { var node = document.createElement('div'); - var link = new ReactLink(true, jest.genMockFn()); + var link = new ReactLink(true, jest.fn()); var instance = ; expect(() => ReactDOM.render(instance, node)).not.toThrow(); diff --git a/src/renderers/dom/client/wrappers/__tests__/ReactDOMSelect-test.js b/src/renderers/dom/client/wrappers/__tests__/ReactDOMSelect-test.js index 0a3f530283..c4276c143f 100644 --- a/src/renderers/dom/client/wrappers/__tests__/ReactDOMSelect-test.js +++ b/src/renderers/dom/client/wrappers/__tests__/ReactDOMSelect-test.js @@ -348,7 +348,7 @@ describe('ReactDOMSelect', function() { }); it('should support ReactLink', function() { - var link = new ReactLink('giraffe', jest.genMockFn()); + var link = new ReactLink('giraffe', jest.fn()); var stub =