Merge branch '15.1.0-dev' into 15-stable

This commit is contained in:
Paul O’Shannessy
2016-05-20 15:41:45 -07:00
123 changed files with 6733 additions and 2007 deletions
+24
View File
@@ -1,3 +1,27 @@
## 15.1.0 (May 20, 2016)
### React
- Ensure we're using the latest `object-assign`, which has protection against a non-spec-compliant native `Object.assign`. ([@zpao](https://github.com/zpao) in [#6681](https://github.com/facebook/react/pull/6681))
- Add a new warning to communicate that `props` objects passed to `createElement` must be plain objects. ([@richardscarrott](https://github.com/richardscarrott) in [#6134](https://github.com/facebook/react/pull/6134))
- Fix a batching bug resulting in some lifecycle methods incorrectly being called multiple times. ([@spicyj](https://github.com/spicyj) in [#6650](https://github.com/facebook/react/pull/6650))
### React DOM
- Fix regression in custom elements support. ([@jscissr](https://github.com/jscissr) in [#6570](https://github.com/facebook/react/pull/6570))
- Stop incorrectly warning about using `onScroll` event handler with server rendering. ([@Aweary](https://github.com/Aweary) in [#6678](https://github.com/facebook/react/pull/6678))
- Fix grammar in the controlled input warning. ([@jakeboone02](https://github.com/jakeboone02) in [#6657](https://github.com/facebook/react/pull/6657))
- Fix issue preventing `<object>` nodes from being able to read `<param>` nodes in IE. ([@syranide](https://github.com/syranide) in [#6691](https://github.com/facebook/react/pull/6691))
- Fix issue resulting in crash when using experimental error boundaries with server rendering. ([@jimfb](https://github.com/jimfb) in [#6694](https://github.com/facebook/react/pull/6694))
- Add additional information to the controlled input warning. ([@borisyankov](https://github.com/borisyankov) in [#6341](https://github.com/facebook/react/pull/6341))
### React Perf Add-on
- Completely rewritten to collect data more accurately and to be easier to maintain. ([@gaearon](https://github.com/gaearon) in [#6647](https://github.com/facebook/react/pull/6647), [#6046](https://github.com/facebook/react/pull/6046))
### React Native Renderer
- Remove some special cases for platform specific branching. ([@sebmarkbage](https://github.com/sebmarkbage) in [#6660](https://github.com/facebook/react/pull/6660))
- Remove use of `merge` utility. ([@sebmarkbage](https://github.com/sebmarkbage) in [#6634](https://github.com/facebook/react/pull/6634))
- Renamed some modules to better indicate usage ([@javache](https://github.com/javache) in [#6643](https://github.com/facebook/react/pull/6643))
## 15.0.2 (April 29, 2016)
### React
+5 -5
View File
@@ -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",
+1 -1
View File
@@ -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 + '/'},
],
},
};
+1 -1
View File
@@ -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);
}
+1 -1
View File
@@ -16,7 +16,7 @@ var addons = {
docs: 'two-way-binding-helpers',
},
Perf: {
module: 'ReactDefaultPerf',
module: 'ReactPerf',
name: 'perf',
docs: 'perf',
},
-5
View File
@@ -39,12 +39,7 @@ var whiteListNames = [
'deepFreezeAndThrowOnMutationInDev',
'flattenStyle',
'InitializeJavaScriptAppEngine',
'InteractionManager',
'JSTimersExecution',
'merge',
'Platform',
'RCTEventEmitter',
'RCTLog',
'TextInputState',
'UIManager',
'View',
+547 -542
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "react-build",
"private": true,
"version": "15.0.2",
"version": "15.1.0-alpha.1",
"devDependencies": {
"async": "^1.5.0",
"babel-cli": "^6.6.5",
@@ -48,9 +48,9 @@
"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.0.1",
"object-assign": "^4.1.0",
"platform": "^1.1.0",
"run-sequence": "^1.1.4",
"through2": "^2.0.0",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "react-addons-template",
"version": "15.0.2",
"version": "15.1.0-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.1.0-alpha.1"
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "react-dom",
"version": "15.0.2",
"version": "15.1.0-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.1.0-alpha.1"
}
}
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "react-native-renderer",
"version": "15.0.2",
"version": "15.1.0-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.1.0-alpha.1"
}
}
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "react",
"description": "React is a JavaScript library for building user interfaces.",
"version": "15.0.2",
"version": "15.1.0-alpha.1",
"keywords": [
"react"
],
@@ -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": [
+1 -1
View File
@@ -11,4 +11,4 @@
'use strict';
module.exports = '15.0.2';
module.exports = '15.1.0-alpha.1';
@@ -36,6 +36,8 @@ var shallowCompare = require('shallowCompare');
* complex data structures this mixin may have false-negatives for deeper
* differences. Only mixin to components which have simple props and state, or
* use `forceUpdate()` when you know deep data structures have changed.
*
* See https://facebook.github.io/react/docs/pure-render-mixin.html
*/
var ReactComponentWithPureRenderMixin = {
shouldComponentUpdate: function(nextProps, nextState) {
+5 -2
View File
@@ -31,8 +31,11 @@ var numericPropertyRegex = /^\d+$/;
var warnedAboutNumeric = false;
var ReactFragment = {
// Wrap a keyed object in an opaque proxy that warns you if you access any
// of its properties.
/**
* Wrap a keyed object in an opaque proxy that warns you if you access any
* of its properties.
* See https://facebook.github.io/react/docs/create-fragment.html
*/
create: function(object) {
if (typeof object !== 'object' || !object || Array.isArray(object)) {
warning(
+1 -1
View File
@@ -34,7 +34,7 @@ React.addons = {
};
if (__DEV__) {
React.addons.Perf = require('ReactDefaultPerf');
React.addons.Perf = require('ReactPerf');
React.addons.TestUtils = require('ReactTestUtils');
}
+1
View File
@@ -16,6 +16,7 @@ var ReactStateSetters = require('ReactStateSetters');
/**
* A simple mixin around ReactLink.forState().
* See https://facebook.github.io/react/docs/two-way-binding-helpers.html
*/
var LinkedStateMixin = {
/**
+3
View File
@@ -37,6 +37,9 @@
var React = require('React');
/**
* Deprecated: An an easy way to express two-way binding with React.
* See https://facebook.github.io/react/docs/two-way-binding-helpers.html
*
* @param {*} value current value of the link
* @param {function} requestChange callback to request a change
*/
+1
View File
@@ -16,6 +16,7 @@ var shallowEqual = require('shallowEqual');
/**
* Does a shallow comparison for props and state.
* See ReactComponentWithPureRenderMixin
* See also https://facebook.github.io/react/docs/shallow-compare.html
*/
function shallowCompare(instance, nextProps, nextState) {
return (
@@ -41,6 +41,11 @@ function createTransitionTimeoutPropValidator(transitionType) {
};
}
/**
* An easy way to perform CSS transitions and animations when a React component
* enters or leaves the DOM.
* See https://facebook.github.io/react/docs/animation.html#high-level-api-reactcsstransitiongroup
*/
var ReactCSSTransitionGroup = React.createClass({
displayName: 'ReactCSSTransitionGroup',
@@ -16,6 +16,11 @@ var ReactTransitionChildMapping = require('ReactTransitionChildMapping');
var emptyFunction = require('emptyFunction');
/**
* A basis for animatins. When children are declaratively added or removed,
* special lifecycle hooks are called.
* See https://facebook.github.io/react/docs/animation.html#low-level-api-reacttransitiongroup
*/
var ReactTransitionGroup = React.createClass({
displayName: 'ReactTransitionGroup',
+4
View File
@@ -66,6 +66,10 @@ function invariantArrayCase(value, spec, command) {
);
}
/**
* Returns a updated shallow copy of an object without mutating the original.
* See https://facebook.github.io/react/docs/update.html for details.
*/
function update(value, spec) {
invariant(
typeof spec === 'object',
@@ -13,11 +13,13 @@
var React;
var ReactDOM;
var ReactDOMServer;
describe('ReactErrorBoundaries', function() {
beforeEach(function() {
ReactDOM = require('ReactDOM');
ReactDOMServer = require('ReactDOMServer');
React = require('React');
});
@@ -50,11 +52,46 @@ describe('ReactErrorBoundaries', function() {
var EventPluginHub = require('EventPluginHub');
var container = document.createElement('div');
EventPluginHub.putListener = jest.genMockFn();
EventPluginHub.putListener = jest.fn();
ReactDOM.render(<Boundary />, container);
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 (<div><button onClick={this.onClick}>ClickMe</button><Angry /></div>);
} else {
return (<div>Happy Birthday!</div>);
}
}
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(<Boundary />);
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() {
@@ -120,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(<Boundary />, container);
expect(EventPluginHub.putListener).toBeCalled();
});
-124
View File
@@ -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;
+204 -10
View File
@@ -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,70 @@ 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 clearHistory() {
ReactComponentTreeDevtool.purgeUnmountedComponents();
ReactNativeOperationHistoryDevtool.clearHistory();
}
function getTreeSnapshot(registeredIDs) {
return registeredIDs.reduce((tree, id) => {
var ownerID = ReactComponentTreeDevtool.getOwnerID(id);
var parentID = ReactComponentTreeDevtool.getParentID(id);
tree[id] = {
displayName: ReactComponentTreeDevtool.getDisplayName(id),
text: ReactComponentTreeDevtool.getText(id),
updateCount: ReactComponentTreeDevtool.getUpdateCount(id),
childIDs: ReactComponentTreeDevtool.getChildIDs(id),
// Text nodes don't have owners but this is close enough.
ownerID: ownerID || ReactComponentTreeDevtool.getOwnerID(parentID),
parentID,
};
return tree;
}, {});
}
function resetMeasurements() {
if (__DEV__) {
var previousStartTime = currentFlushStartTime;
var previousMeasurements = currentFlushMeasurements || [];
var previousOperations = ReactNativeOperationHistoryDevtool.getHistory();
if (!isProfiling || currentFlushNesting === 0) {
currentFlushStartTime = null;
currentFlushMeasurements = null;
clearHistory();
return;
}
if (previousMeasurements.length || previousOperations.length) {
var registeredIDs = ReactComponentTreeDevtool.getRegisteredIDs();
flushHistory.push({
duration: performanceNow() - previousStartTime,
measurements: previousMeasurements || [],
operations: previousOperations || [],
treeSnapshot: getTreeSnapshot(registeredIDs),
});
}
clearHistory();
currentFlushStartTime = performanceNow();
currentFlushMeasurements = [];
}
}
function checkDebugID(debugID) {
warning(debugID, 'ReactDebugTool: debugID may not be empty.');
}
var ReactDebugTool = {
addDevtool(devtool) {
eventHandlers.push(devtool);
@@ -49,29 +115,157 @@ 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) {
checkDebugID(debugID);
emitEvent('onBeginLifeCycleTimer', debugID, timerType);
if (__DEV__) {
if (isProfiling && currentFlushNesting > 0) {
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) {
checkDebugID(debugID);
if (__DEV__) {
if (isProfiling && currentFlushNesting > 0) {
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: performanceNow() - currentTimerStartTime,
});
currentTimerStartTime = null;
currentTimerDebugID = null;
currentTimerType = null;
}
}
emitEvent('onEndLifeCycleTimer', debugID, timerType);
},
onBeginReconcilerTimer(debugID, timerType) {
checkDebugID(debugID);
emitEvent('onBeginReconcilerTimer', debugID, timerType);
},
onEndReconcilerTimer(debugID, timerType) {
checkDebugID(debugID);
emitEvent('onEndReconcilerTimer', debugID, timerType);
},
onBeginProcessingChildContext() {
emitEvent('onBeginProcessingChildContext');
},
onEndProcessingChildContext() {
emitEvent('onEndProcessingChildContext');
},
onNativeOperation(debugID, type, payload) {
checkDebugID(debugID);
emitEvent('onNativeOperation', debugID, type, payload);
},
onSetState() {
emitEvent('onSetState');
},
onMountRootComponent(internalInstance) {
emitEvent('onMountRootComponent', internalInstance);
onSetDisplayName(debugID, displayName) {
checkDebugID(debugID);
emitEvent('onSetDisplayName', debugID, displayName);
},
onMountComponent(internalInstance) {
emitEvent('onMountComponent', internalInstance);
onSetChildren(debugID, childDebugIDs) {
checkDebugID(debugID);
emitEvent('onSetChildren', debugID, childDebugIDs);
},
onUpdateComponent(internalInstance) {
emitEvent('onUpdateComponent', internalInstance);
onSetOwner(debugID, ownerDebugID) {
checkDebugID(debugID);
emitEvent('onSetOwner', debugID, ownerDebugID);
},
onUnmountComponent(internalInstance) {
emitEvent('onUnmountComponent', internalInstance);
onSetText(debugID, text) {
checkDebugID(debugID);
emitEvent('onSetText', debugID, text);
},
onMountRootComponent(debugID) {
checkDebugID(debugID);
emitEvent('onMountRootComponent', debugID);
},
onMountComponent(debugID) {
checkDebugID(debugID);
emitEvent('onMountComponent', debugID);
},
onUpdateComponent(debugID) {
checkDebugID(debugID);
emitEvent('onUpdateComponent', debugID);
},
onUnmountComponent(debugID) {
checkDebugID(debugID);
emitEvent('onUnmountComponent', debugID);
},
};
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;
+371
View File
@@ -0,0 +1,371 @@
/**
* 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 ReactPerf
*/
'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, timerType, 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,
};
}
if (!stats.durations[timerType]) {
stats.durations[timerType] = 0;
}
if (!stats.counts[timerType]) {
stats.counts[timerType] = 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, timerType, stats => {
stats.totalDuration += duration;
stats.durations[timerType] += duration;
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()) {
var aggregatedStats = {};
var affectedIDs = {};
function updateAggregatedStats(treeSnapshot, instanceID, applyUpdate) {
var {displayName, ownerID} = treeSnapshot[instanceID];
var owner = treeSnapshot[ownerID];
var key = (owner ? owner.displayName + ' > ' : '') + 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 isCompositeByID = {};
flushHistory.forEach(flush => {
var {measurements} = flush;
measurements.forEach(measurement => {
var {instanceID, timerType} = measurement;
if (timerType !== 'render') {
return;
}
isCompositeByID[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) {
// As we traverse parents, only count inclusive time towards composites.
// We know something is a composite if its render() was called.
if (isCompositeByID[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 + ' > ' : '') + 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 isDefinitelyNotWastedByID = {};
// Find native components associated with an operation in this batch.
// Mark all components in their parent tree as definitely not wasted.
operations.forEach(operation => {
var {instanceID} = operation;
var nextParentID = instanceID;
while (nextParentID) {
isDefinitelyNotWastedByID[nextParentID] = true;
nextParentID = treeSnapshot[nextParentID].parentID;
}
});
// Find composite components that rendered in this batch.
// These are potential candidates for being wasted renders.
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;
}
// If there was a DOM update below this component, or it has just been
// mounted, its render() is not considered wasted.
var { updateCount } = treeSnapshot[instanceID];
if (isDefinitelyNotWastedByID[instanceID] || updateCount === 0) {
return;
}
// We consider this render() wasted.
updateAggregatedStats(treeSnapshot, instanceID, stats => {
stats.renderCount++;
});
var nextParentID = instanceID;
while (nextParentID) {
// Any parents rendered during this batch are considered wasted
// unless we previously marked them as dirty.
var isWasted =
renderedCompositeIDs[nextParentID] &&
!isDefinitelyNotWastedByID[nextParentID];
if (isWasted) {
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 + ' > ' : '') + 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 = {
getLastMeasurements: getFlushHistory,
getExclusive,
getInclusive,
getWasted,
getOperations,
printExclusive,
printInclusive,
printWasted,
printOperations,
start,
stop,
// Deprecated:
printDOM,
getMeasurementsSummaryMap,
};
module.exports = ReactPerfAnalysis;
@@ -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 <div />;
}
}
spyOn(console, 'error');
var warningCount = 0;
var div = document.createElement('div');
var publicInst = ReactDOM.render(<Foo />, 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.'
);
}
});
});
@@ -11,13 +11,11 @@
'use strict';
describe('ReactDefaultPerf', function() {
describe('ReactPerf', function() {
var React;
var ReactDOM;
var ReactDOMFeatureFlags;
var ReactDefaultPerf;
var ReactPerf;
var ReactTestUtils;
var ReactDefaultPerfAnalysis;
var App;
var Box;
@@ -36,10 +34,8 @@ describe('ReactDefaultPerf', function() {
React = require('React');
ReactDOM = require('ReactDOM');
ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
ReactDefaultPerf = require('ReactDefaultPerf');
ReactPerf = require('ReactPerf');
ReactTestUtils = require('ReactTestUtils');
ReactDefaultPerfAnalysis = require('ReactDefaultPerfAnalysis');
App = React.createClass({
render: function() {
@@ -68,10 +64,17 @@ describe('ReactDefaultPerf', function() {
});
function measure(fn) {
ReactDefaultPerf.start();
ReactPerf.start();
fn();
ReactDefaultPerf.stop();
return ReactDefaultPerf.getLastMeasurements().__unstable_this_format_will_change;
ReactPerf.stop();
// Make sure none of the methods crash.
ReactPerf.getWasted();
ReactPerf.getInclusive();
ReactPerf.getExclusive();
ReactPerf.getOperations();
return ReactPerf.getLastMeasurements();
}
it('should count no-op update as waste', function() {
@@ -81,20 +84,18 @@ describe('ReactDefaultPerf', function() {
ReactDOM.render(<App />, container);
});
var summary = ReactDefaultPerf.getWasted(measurements);
expect(summary.length).toBe(2);
/*eslint-disable dot-notation */
expect(summary[0]['Owner > component']).toBe('<root> > App');
expect(summary[0]['Wasted time (ms)']).not.toBe(0);
expect(summary[0]['Instances']).toBe(1);
expect(summary[1]['Owner > component']).toBe('App > Box');
expect(summary[1]['Wasted time (ms)']).not.toBe(0);
expect(summary[1]['Instances']).toBe(2);
/*eslint-enable dot-notation */
var summary = ReactPerf.getWasted(measurements);
expect(summary).toEqual([{
key: 'App',
instanceCount: 1,
inclusiveRenderDuration: 3,
renderCount: 1,
}, {
key: 'App > Box',
instanceCount: 2,
inclusiveRenderDuration: 2,
renderCount: 2,
}]);
});
it('should count no-op update in child as waste', function() {
@@ -107,21 +108,18 @@ describe('ReactDefaultPerf', function() {
ReactDOM.render(<App flipSecond={true} />, container);
});
var summary = ReactDefaultPerf.getWasted(measurements);
expect(summary.length).toBe(1);
/*eslint-disable dot-notation */
expect(summary[0]['Owner > component']).toBe('App > Box');
expect(summary[0]['Wasted time (ms)']).not.toBe(0);
expect(summary[0]['Instances']).toBe(1);
/*eslint-enable dot-notation */
var summary = ReactPerf.getWasted(measurements);
expect(summary).toEqual([{
key: 'App > Box',
instanceCount: 1,
inclusiveRenderDuration: 1,
renderCount: 1,
}]);
});
function expectNoWaste(fn) {
var measurements = measure(fn);
var summary = ReactDefaultPerf.getWasted(measurements);
var summary = ReactPerf.getWasted(measurements);
expect(summary).toEqual([]);
}
@@ -217,83 +215,72 @@ describe('ReactDefaultPerf', function() {
});
});
it('putListener should not be instrumented', function() {
it('should not count replacing null with a native as waste', function() {
var element = null;
function Foo() {
return element;
}
var container = document.createElement('div');
ReactDOM.render(<Div onClick={function() {}}>hey</Div>, container);
var measurements = measure(() => {
ReactDOM.render(<Div onClick={function() {}}>hey</Div>, container);
});
var summary = ReactDefaultPerfAnalysis.getDOMSummary(measurements);
expect(summary).toEqual([]);
});
it('deleteListener should not be instrumented', function() {
var container = document.createElement('div');
ReactDOM.render(<Div onClick={function() {}}>hey</Div>, container);
var measurements = measure(() => {
ReactDOM.render(<Div>hey</Div>, container);
});
var summary = ReactDefaultPerfAnalysis.getDOMSummary(measurements);
expect(summary).toEqual([]);
});
it('should not fail on input change events', function() {
var container = document.createElement('div');
var onChange = () => {};
var input = ReactDOM.render(
<input checked={true} onChange={onChange} />,
container
);
ReactDOM.render(<Foo />, container);
expectNoWaste(() => {
ReactTestUtils.Simulate.change(input);
element = <div />;
ReactDOM.render(<Foo />, container);
});
});
it('should print a table after calling printOperations', function() {
it('should not count replacing a native with null as waste', function() {
var element = <div />;
function Foo() {
return element;
}
var container = document.createElement('div');
ReactDOM.render(<Foo />, container);
expectNoWaste(() => {
element = null;
ReactDOM.render(<Foo />, container);
});
});
it('should include stats for components unmounted during measurement', function() {
var container = document.createElement('div');
var measurements = measure(() => {
ReactDOM.render(<Div>hey</Div>, container);
ReactDOM.render(<Div><Div key="a" /></Div>, container);
ReactDOM.render(<Div><Div key="b" /></Div>, container);
});
spyOn(console, 'table');
ReactDefaultPerf.printOperations(measurements);
expect(console.table.calls.length).toBe(1);
expect(console.table.argsForCall[0][0]).toEqual([{
'data-reactid': '',
type: 'set innerHTML',
args: ReactDOMFeatureFlags.useCreateElement ?
'{"node":"<not serializable>","children":[],"html":null,"text":null}' :
'"<div data-reactroot=\\"\\" data-reactid=\\"1\\">hey</div>"',
expect(ReactPerf.getExclusive(measurements)).toEqual([{
key: 'Div',
instanceCount: 3,
counts: { ctor: 3, render: 4 },
durations: { ctor: 3, render: 4 },
totalDuration: 7,
}]);
});
it('warns once when using getMeasurementsSummaryMap', function() {
var measurements = measure(() => {});
spyOn(console, 'error');
ReactDefaultPerf.getMeasurementsSummaryMap(measurements);
ReactPerf.getMeasurementsSummaryMap(measurements);
expect(console.error.calls.length).toBe(1);
expect(console.error.argsForCall[0][0]).toContain(
'`ReactPerf.getMeasurementsSummaryMap(...)` is deprecated. Use ' +
'`ReactPerf.getWasted(...)` instead.'
);
ReactDefaultPerf.getMeasurementsSummaryMap(measurements);
ReactPerf.getMeasurementsSummaryMap(measurements);
expect(console.error.calls.length).toBe(1);
});
it('warns once when using printDOM', function() {
var measurements = measure(() => {});
spyOn(console, 'error');
ReactDefaultPerf.printDOM(measurements);
ReactPerf.printDOM(measurements);
expect(console.error.calls.length).toBe(1);
expect(console.error.argsForCall[0][0]).toContain(
'`ReactPerf.printDOM(...)` is deprecated. Use ' +
'`ReactPerf.printOperations(...)` instead.'
);
ReactDefaultPerf.printDOM(measurements);
ReactPerf.printDOM(measurements);
expect(console.error.calls.length).toBe(1);
});
});
+9 -1
View File
@@ -55,6 +55,8 @@ function forEachSingleChild(bookKeeping, child, name) {
/**
* Iterates through children that are typically specified as `props.children`.
*
* See https://facebook.github.io/react/docs/top-level-api.html#react.children.foreach
*
* The provided forEachFunc(child, index) will be called for each
* leaf child.
*
@@ -146,7 +148,9 @@ function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) {
/**
* Maps children that are typically specified as `props.children`.
*
* The provided mapFunction(child, index) will be called for each
* See https://facebook.github.io/react/docs/top-level-api.html#react.children.map
*
* The provided mapFunction(child, key, index) will be called for each
* leaf child.
*
* @param {?*} children Children tree container.
@@ -173,6 +177,8 @@ function forEachSingleChildDummy(traverseContext, child, name) {
* Count the number of children that are typically specified as
* `props.children`.
*
* See https://facebook.github.io/react/docs/top-level-api.html#react.children.count
*
* @param {?*} children Children tree container.
* @return {number} The number of children.
*/
@@ -184,6 +190,8 @@ function countChildren(children, context) {
/**
* Flatten a children object (typically specified as `props.children`) and
* return an array with appropriately re-keyed children.
*
* See https://facebook.github.io/react/docs/top-level-api.html#react.children.toarray
*/
function toArray(children) {
var result = [];
+7 -4
View File
@@ -16,10 +16,13 @@ var invariant = require('invariant');
/**
* Returns the first child in a collection of children and verifies that there
* is only one child in the collection. The current implementation of this
* function assumes that a single child gets passed without a wrapper, but the
* purpose of this helper function is to abstract away the particular structure
* of children.
* is only one child in the collection.
*
* See https://facebook.github.io/react/docs/top-level-api.html#react.children.only
*
* The current implementation of this function assumes that a single child gets
* passed without a wrapper, but the purpose of this helper function is to
* abstract away the particular structure of children.
*
* @param {?object} children Child collection structure.
* @return {ReactElement} The first and only `ReactElement` contained in the
@@ -740,6 +740,7 @@ var ReactClass = {
/**
* Creates a composite component class given a class specification.
* See https://facebook.github.io/react/docs/top-level-api.html#react.createclass
*
* @param {object} spec Class specification (which must define `render`).
* @return {function} Component constructor function.
@@ -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,
@@ -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,
@@ -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,
@@ -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 = <TestComponent listener={listener} />;
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 = <TestComponent listener={listener} />;
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 = <TestComponentWithReverseSpec listener={listener} />;
instance = ReactTestUtils.renderIntoDocument(instance);
@@ -113,6 +113,10 @@ var ReactElement = function(type, key, ref, self, source, owner, props) {
return element;
};
/**
* Create and return a new ReactElement of the given type.
* See https://facebook.github.io/react/docs/top-level-api.html#react.createelement
*/
ReactElement.createElement = function(type, config, children) {
var propName;
@@ -126,6 +130,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') ||
@@ -223,6 +234,10 @@ ReactElement.createElement = function(type, config, children) {
);
};
/**
* Return a function that produces ReactElements of a given type.
* See https://facebook.github.io/react/docs/top-level-api.html#react.createfactory
*/
ReactElement.createFactory = function(type) {
var factory = ReactElement.createElement.bind(null, type);
// Expose the type on the factory and the prototype so that it can be
@@ -248,6 +263,10 @@ ReactElement.cloneAndReplaceKey = function(oldElement, newKey) {
return newElement;
};
/**
* Clone and return a new ReactElement using element as the starting point.
* See https://facebook.github.io/react/docs/top-level-api.html#react.cloneelement
*/
ReactElement.cloneElement = function(element, config, children) {
var propName;
@@ -268,6 +287,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;
@@ -319,6 +347,8 @@ ReactElement.cloneElement = function(element, config, children) {
};
/**
* Verifies the object is a ReactElement.
* See https://facebook.github.io/react/docs/top-level-api.html#react.isvalidelement
* @param {?object} object
* @return {boolean} True if `object` is a valid component.
* @final
@@ -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',
@@ -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() {
@@ -0,0 +1,163 @@
/**
* 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,
updateCount: 0,
};
}
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);
},
onUpdateComponent(id) {
updateTree(id, item => item.updateCount++);
},
onUnmountComponent(id) {
updateTree(id, item => item.isMounted = false);
rootIDs = rootIDs.filter(rootID => rootID !== id);
},
purgeUnmountedComponents() {
if (ReactComponentTreeDevtool._preventPurging) {
// Should only be used for testing.
return;
}
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;
},
getUpdateCount(id) {
var item = tree[id];
return item ? item.updateCount : 0;
},
getRootIDs() {
return rootIDs;
},
getRegisteredIDs() {
return Object.keys(tree);
},
};
module.exports = ReactComponentTreeDevtool;
@@ -0,0 +1,39 @@
/**
* 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() {
if (ReactNativeOperationHistoryDevtool._preventClearing) {
// Should only be used for tests.
return;
}
history = [];
},
getHistory() {
return history;
},
};
module.exports = ReactNativeOperationHistoryDevtool;
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,724 @@
/**
* 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 ReactDOM;
var ReactDOMComponentTree;
var ReactDOMFeatureFlags;
var ReactNativeOperationHistoryDevtool;
beforeEach(() => {
jest.resetModuleRegistry();
React = require('React');
ReactDOM = require('ReactDOM');
ReactDOMComponentTree = require('ReactDOMComponentTree');
ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
ReactNativeOperationHistoryDevtool = require('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');
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div><p>Hi.</p></div>, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
assertHistoryMatches([{
instanceID: inst._debugID,
type: 'mount',
payload: ReactDOMFeatureFlags.useCreateElement ?
'DIV' :
'<div data-reactroot="" data-reactid="1"><p data-reactid="2">Hi.</p></div>',
}]);
});
it('gets recorded for composite roots', () => {
function Foo() {
return <div><p>Hi.</p></div>;
}
var node = document.createElement('div');
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<Foo />, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
assertHistoryMatches([{
instanceID: inst._debugID,
type: 'mount',
payload: ReactDOMFeatureFlags.useCreateElement ?
'DIV' :
'<div data-reactroot="" data-reactid="1">' +
'<p data-reactid="2">Hi.</p></div>',
}]);
});
it('gets ignored for composite roots that return null', () => {
function Foo() {
return null;
}
var node = document.createElement('div');
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<Foo />, node);
// Empty DOM components should be invisible to devtools.
assertHistoryMatches([]);
});
it('gets recorded when a native is mounted deeply instead of null', () => {
var element;
function Foo() {
return element;
}
ReactNativeOperationHistoryDevtool._preventClearing = true;
var node = document.createElement('div');
element = null;
ReactDOM.render(<Foo />, node);
element = <span />;
ReactDOM.render(<Foo />, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
// Since empty components should be invisible to devtools,
// we record a "mount" event rather than a "replace with".
assertHistoryMatches([{
instanceID: inst._debugID,
type: 'mount',
payload: 'SPAN',
}]);
});
});
describe('update styles', () => {
it('gets recorded during mount', () => {
var node = document.createElement('div');
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div style={{
color: 'red',
backgroundColor: 'yellow',
}} />, 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: '<div style="color:red;background-color:yellow;" ' +
'data-reactroot="" data-reactid="1"></div>',
}]);
}
});
it('gets recorded during an update', () => {
var node = document.createElement('div');
ReactDOM.render(<div />, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div style={{ color: 'red' }} />, node);
ReactDOM.render(<div style={{
color: 'blue',
backgroundColor: 'yellow',
}} />, node);
ReactDOM.render(<div style={{ backgroundColor: 'green' }} />, node);
ReactDOM.render(<div />, 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(<div />, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div style={{
color: 'red',
backgroundColor: 'yellow',
}} />, node);
ReactDOM.render(<div style={{
color: 'red',
backgroundColor: 'yellow',
}} />, 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');
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div className="rad" tabIndex={42} />, 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: '<div class="rad" tabindex="42" data-reactroot="" ' +
'data-reactid="1"></div>',
}]);
}
});
it('gets recorded during an update', () => {
var node = document.createElement('div');
ReactDOM.render(<div />, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div className="rad" />, node);
ReactDOM.render(<div className="mad" tabIndex={42} />, node);
ReactDOM.render(<div tabIndex={43} />, 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(<div />, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div disabled={true} />, node);
ReactDOM.render(<div disabled={false} />, 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');
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div data-x="rad" data-y={42} />, 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: '<div data-x="rad" data-y="42" data-reactroot="" ' +
'data-reactid="1"></div>',
}]);
}
});
it('gets recorded during an update', () => {
var node = document.createElement('div');
ReactDOM.render(<div />, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div data-x="rad" />, node);
ReactDOM.render(<div data-x="mad" data-y={42} />, node);
ReactDOM.render(<div data-y={43} />, 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');
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<my-component className="rad" tabIndex={42} />, 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: '<my-component className="rad" tabIndex="42" ' +
'data-reactroot="" data-reactid="1"></my-component>',
}]);
}
});
it('gets recorded during an update', () => {
var node = document.createElement('div');
ReactDOM.render(<my-component />, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<my-component className="rad" />, node);
ReactDOM.render(<my-component className="mad" tabIndex={42} />, node);
ReactDOM.render(<my-component tabIndex={43} />, 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(<div>Hi.</div>, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div>Bye.</div>, 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(<div dangerouslySetInnerHTML={{__html: 'Hi.'}} />, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div>Bye.</div>, 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(<div><span /><p /></div>, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div>Bye.</div>, 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(<div>Hi.</div>, node);
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div>Hi.</div>, node);
assertHistoryMatches([]);
});
});
describe('text node', () => {
it('gets recorded during an update', () => {
var node = document.createElement('div');
ReactDOM.render(<div>{'Hi.'}{42}</div>, node);
var inst1 = ReactDOMComponentTree.getInstanceFromNode(node.firstChild.childNodes[0]);
var inst2 = ReactDOMComponentTree.getInstanceFromNode(node.firstChild.childNodes[3]);
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div>{'Bye.'}{43}</div>, 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(<div>{'Hi.'}{42}</div>, node);
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div>{'Hi.'}{42}</div>, 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 = <div />;
ReactDOM.render(<Foo />, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
element = <span />;
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<Foo />, node);
assertHistoryMatches([{
instanceID: inst._debugID,
type: 'replace with',
payload: 'SPAN',
}]);
});
it('gets recorded when composite renders to null after a native', () => {
var element;
function Foo() {
return element;
}
var node = document.createElement('div');
element = <span />;
ReactDOM.render(<Foo />, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
element = null;
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<Foo />, node);
assertHistoryMatches([{
instanceID: inst._debugID,
type: 'replace with',
payload: '#comment',
}]);
});
it('gets ignored if the type has not changed', () => {
var element;
function Foo() {
return element;
}
var node = document.createElement('div');
element = <div />;
ReactDOM.render(<Foo />, node);
element = <div />;
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<Foo />, node);
assertHistoryMatches([]);
});
});
describe('replace children', () => {
it('gets recorded during an update from text content', () => {
var node = document.createElement('div');
ReactDOM.render(<div>Hi.</div>, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(
<div dangerouslySetInnerHTML={{__html: 'Bye.'}} />,
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(
<div dangerouslySetInnerHTML={{__html: 'Hi.'}} />,
node
);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(
<div dangerouslySetInnerHTML={{__html: 'Bye.'}} />,
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(<div><span /><p /></div>, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(
<div dangerouslySetInnerHTML={{__html: 'Hi.'}} />,
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(
<div dangerouslySetInnerHTML={{__html: 'Hi.'}} />,
node
);
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(
<div dangerouslySetInnerHTML={{__html: 'Hi.'}} />,
node
);
assertHistoryMatches([]);
});
});
describe('insert child', () => {
it('gets reported when a child is inserted', () => {
var node = document.createElement('div');
ReactDOM.render(<div><span /></div>, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div><span /><p /></div>, 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(<div><span key="a" /><p key="b" /></div>, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div><p key="b" /><span key="a" /></div>, 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(<div><span key="a" /><p key="b" /></div>, node);
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
ReactNativeOperationHistoryDevtool._preventClearing = true;
ReactDOM.render(<div><span key="a" /></div>, node);
assertHistoryMatches([{
instanceID: inst._debugID,
type: 'remove child',
payload: {fromIndex: 1},
}]);
});
});
});
+1 -4
View File
@@ -16,7 +16,6 @@
var ReactDOMComponentTree = require('ReactDOMComponentTree');
var ReactDefaultInjection = require('ReactDefaultInjection');
var ReactMount = require('ReactMount');
var ReactPerf = require('ReactPerf');
var ReactReconciler = require('ReactReconciler');
var ReactUpdates = require('ReactUpdates');
var ReactVersion = require('ReactVersion');
@@ -28,11 +27,9 @@ var warning = require('warning');
ReactDefaultInjection.inject();
var render = ReactPerf.measure('React', 'render', ReactMount.render);
var React = {
findDOMNode: findDOMNode,
render: render,
render: ReactMount.render,
unmountComponentAtNode: ReactMount.unmountComponentAtNode,
version: ReactVersion,
@@ -13,7 +13,6 @@
var DOMChildrenOperations = require('DOMChildrenOperations');
var ReactDOMComponentTree = require('ReactDOMComponentTree');
var ReactPerf = require('ReactPerf');
/**
* Operations used to process updates to DOM nodes.
@@ -32,8 +31,4 @@ var ReactDOMIDOperations = {
},
};
ReactPerf.measureMethods(ReactDOMIDOperations, 'ReactDOMIDOperations', {
dangerouslyProcessChildrenUpdates: 'dangerouslyProcessChildrenUpdates',
});
module.exports = ReactDOMIDOperations;
+28 -7
View File
@@ -22,7 +22,6 @@ var ReactElement = require('ReactElement');
var ReactFeatureFlags = require('ReactFeatureFlags');
var ReactInstrumentation = require('ReactInstrumentation');
var ReactMarkupChecksum = require('ReactMarkupChecksum');
var ReactPerf = require('ReactPerf');
var ReactReconciler = require('ReactReconciler');
var ReactUpdateQueue = require('ReactUpdateQueue');
var ReactUpdates = require('ReactUpdates');
@@ -308,6 +307,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.
@@ -333,6 +336,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 +358,11 @@ 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
);
ReactInstrumentation.debugTool.onEndFlush();
}
return componentInstance;
@@ -497,6 +510,7 @@ var ReactMount = {
/**
* Renders a React component into the DOM in the supplied `container`.
* See https://facebook.github.io/react/docs/top-level-api.html#reactdom.render
*
* If the React component was previously rendered into `container`, this will
* perform an update on it and only mutate the DOM as necessary to reflect the
@@ -513,6 +527,7 @@ var ReactMount = {
/**
* Unmounts and destroys the React component rendered in the `container`.
* See https://facebook.github.io/react/docs/top-level-api.html#reactdom.unmountcomponentatnode
*
* @param {DOMElement} container DOM element containing a React component.
* @return {boolean} True if a component was found in and unmounted from
@@ -684,12 +699,18 @@ var ReactMount = {
setInnerHTML(container, markup);
ReactDOMComponentTree.precacheNode(instance, container.firstChild);
}
if (__DEV__) {
var nativeNode = ReactDOMComponentTree.getInstanceFromNode(container.firstChild);
if (nativeNode._debugID !== 0) {
ReactInstrumentation.debugTool.onNativeOperation(
nativeNode._debugID,
'mount',
markup.toString()
);
}
}
},
};
ReactPerf.measureMethods(ReactMount, 'ReactMount', {
_renderNewRootComponent: '_renderNewRootComponent',
_mountImageIntoNode: '_mountImageIntoNode',
});
module.exports = ReactMount;
@@ -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),
@@ -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 <span> \n testContent \t </span> \n \t';
var stubInstance = {_debugID: 1};
ReactDOMComponentTree.precacheNode(stubInstance, stubNode);
var html = '\n \t <span> \n testContent \t </span> \n \t';
ReactDOMIDOperations.dangerouslyProcessChildrenUpdates(
{_nativeNode: stubNode},
stubInstance,
[{
type: ReactMultiChildUpdateTypes.SET_MARKUP,
content: html,
@@ -30,7 +30,7 @@ describe('ReactEventListener', function() {
ReactEventListener = require('ReactEventListener');
ReactTestUtils = require('ReactTestUtils');
handleTopLevel = jest.genMockFn();
handleTopLevel = jest.fn();
ReactEventListener._handleTopLevel = handleTopLevel;
});
@@ -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,
@@ -66,7 +66,7 @@ describe('SelectEventPlugin', function() {
},
});
var cb = jest.genMockFn();
var cb = jest.fn();
var rendered = ReactTestUtils.renderIntoDocument(
<WithSelect onSelect={cb} />
+2
View File
@@ -22,6 +22,8 @@ var warning = require('warning');
/**
* Returns the DOM node rendered by this element.
*
* See https://facebook.github.io/react/docs/top-level-api.html#reactdom.finddomnode
*
* @param {ReactComponent|DOMElement} componentOrElement
* @return {?DOMElement} The root node of this element.
*/
@@ -14,7 +14,8 @@
var DOMLazyTree = require('DOMLazyTree');
var Danger = require('Danger');
var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes');
var ReactPerf = require('ReactPerf');
var ReactDOMComponentTree = require('ReactDOMComponentTree');
var ReactInstrumentation = require('ReactInstrumentation');
var createMicrosoftUnsafeLocalFunction = require('createMicrosoftUnsafeLocalFunction');
var setInnerHTML = require('setInnerHTML');
@@ -120,6 +121,37 @@ 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);
if (prevInstance._debugID !== 0) {
ReactInstrumentation.debugTool.onNativeOperation(
prevInstance._debugID,
'replace with',
markup.toString()
);
} else {
var nextInstance = ReactDOMComponentTree.getInstanceFromNode(markup.node);
if (nextInstance._debugID !== 0) {
ReactInstrumentation.debugTool.onNativeOperation(
nextInstance._debugID,
'mount',
markup.toString()
);
}
}
};
}
/**
@@ -127,7 +159,7 @@ function replaceDelimitedText(openingComment, closingComment, stringText) {
*/
var DOMChildrenOperations = {
dangerouslyReplaceNodeWithMarkup: Danger.dangerouslyReplaceNodeWithMarkup,
dangerouslyReplaceNodeWithMarkup: dangerouslyReplaceNodeWithMarkup,
replaceDelimitedText: replaceDelimitedText,
@@ -139,6 +171,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 +185,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 +199,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;
}
}
@@ -177,8 +249,4 @@ var DOMChildrenOperations = {
};
ReactPerf.measureMethods(DOMChildrenOperations, 'DOMChildrenOperations', {
replaceDelimitedText: 'replaceDelimitedText',
});
module.exports = DOMChildrenOperations;
+19 -2
View File
@@ -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 <object> plugins (like Flash Player) will read
// <param> nodes immediately upon insertion into the DOM, so <object>
// 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 {
@@ -96,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,
};
}
@@ -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;
}
@@ -169,7 +177,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',
@@ -36,7 +36,7 @@ describe('DisabledInputUtils', function() {
return element;
}
var onClick = jest.genMockFn();
var onClick = jest.fn();
elements.forEach(function(tagName) {
@@ -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 = <input type="text" valueLink={link} />;
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(<input type="text" valueLink={link} />);
expect(console.error.argsForCall.length).toBe(1);
expect(console.error.argsForCall[0][0]).toContain(
@@ -261,7 +261,7 @@ describe('ReactDOMInput', function() {
);
ReactTestUtils.renderIntoDocument(
<input type="text" value="zoink" onChange={jest.genMockFn()} />
<input type="text" value="zoink" onChange={jest.fn()} />
);
expect(console.error.argsForCall.length).toBe(1);
ReactTestUtils.renderIntoDocument(<input type="text" value="zoink" />);
@@ -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 = <input type="text" valueLink={link} />;
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 = <input type="checkbox" checkedLink={link} />;
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(<input type="checkbox" checkedLink={link} />, node);
expect(console.error.argsForCall.length).toBe(1);
expect(console.error.argsForCall[0][0]).toContain(
@@ -342,7 +342,7 @@ describe('ReactDOMInput', function() {
<input
type="checkbox"
checked="false"
onChange={jest.genMockFn()}
onChange={jest.fn()}
/>
);
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 = <input type="checkbox" checkedLink={link} />;
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 = <input type="checkbox" checkedLink={link} />;
expect(() => ReactDOM.render(instance, node)).not.toThrow();
@@ -422,6 +422,7 @@ describe('ReactDOMInput', function() {
<input type="radio" checked={true} defaultChecked={true} readOnly={true} />
);
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() {
<input type="text" value="foo" defaultValue="bar" readOnly={true} />
);
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 ' +
@@ -488,7 +490,7 @@ describe('ReactDOMInput', function() {
ReactDOM.render(<input type="text" value="controlled" />, 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 +532,7 @@ describe('ReactDOMInput', function() {
ReactDOM.render(<input type="checkbox" checked={true} />, 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 +574,7 @@ describe('ReactDOMInput', function() {
ReactDOM.render(<input type="radio" checked={true} />, 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'
@@ -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 =
<select valueLink={link}>
<option value="monkey">A monkey!</option>
@@ -237,7 +237,7 @@ describe('ReactDOMTextarea', function() {
});
it('should support ReactLink', function() {
var link = new ReactLink('yolo', jest.genMockFn());
var link = new ReactLink('yolo', jest.fn());
var instance = <textarea valueLink={link} />;
spyOn(console, 'error');
@@ -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');
@@ -35,13 +37,23 @@ function renderToStringImpl(element, makeStaticMarkup) {
transaction = ReactServerRenderingTransaction.getPooled(makeStaticMarkup);
return transaction.perform(function() {
if (__DEV__) {
ReactInstrumentation.debugTool.onBeginFlush();
}
var componentInstance = instantiateReactComponent(element);
var markup = componentInstance.mountComponent(
var markup = ReactReconciler.mountComponent(
componentInstance,
transaction,
null,
ReactDOMContainerInfo(),
emptyObject
);
if (__DEV__) {
ReactInstrumentation.debugTool.onUnmountComponent(
componentInstance._debugID
);
ReactInstrumentation.debugTool.onEndFlush();
}
if (!makeStaticMarkup) {
markup = ReactMarkupChecksum.addChecksumToMarkup(markup);
}
@@ -55,6 +67,11 @@ function renderToStringImpl(element, makeStaticMarkup) {
}
}
/**
* Render a ReactElement to its initial HTML. This should only be used on the
* server.
* See https://facebook.github.io/react/docs/top-level-api.html#reactdomserver.rendertostring
*/
function renderToString(element) {
invariant(
ReactElement.isValidElement(element),
@@ -63,6 +80,11 @@ function renderToString(element) {
return renderToStringImpl(element, false);
}
/**
* Similar to renderToString, except this doesn't create extra DOM attributes
* such as data-react-id that React uses internally.
* See https://facebook.github.io/react/docs/top-level-api.html#reactdomserver.rendertostaticmarkup
*/
function renderToStaticMarkup(element) {
invariant(
ReactElement.isValidElement(element),
@@ -60,6 +60,12 @@ var Mixin = {
*/
destructor: function() {
},
checkpoint: function() {
},
rollback: function() {
},
};
@@ -86,7 +86,7 @@ describe('ReactServerRendering', function() {
it('should not register event listeners', function() {
var EventPluginHub = require('EventPluginHub');
var cb = jest.genMockFn();
var cb = jest.fn();
ReactServerRendering.renderToString(
<span onClick={cb}>hello world</span>
@@ -303,7 +303,7 @@ describe('ReactServerRendering', function() {
it('should not register event listeners', function() {
var EventPluginHub = require('EventPluginHub');
var cb = jest.genMockFn();
var cb = jest.fn();
ReactServerRendering.renderToString(
<span onClick={cb}>hello world</span>
@@ -13,7 +13,7 @@
var CSSProperty = require('CSSProperty');
var ExecutionEnvironment = require('ExecutionEnvironment');
var ReactPerf = require('ReactPerf');
var ReactInstrumentation = require('ReactInstrumentation');
var camelizeStyleName = require('camelizeStyleName');
var dangerousStyleValue = require('dangerousStyleValue');
@@ -192,6 +192,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)) {
@@ -229,8 +237,4 @@ var CSSPropertyOperations = {
};
ReactPerf.measureMethods(CSSPropertyOperations, 'CSSPropertyOperations', {
setValueForStyles: 'setValueForStyles',
});
module.exports = CSSPropertyOperations;
@@ -12,8 +12,9 @@
'use strict';
var DOMProperty = require('DOMProperty');
var ReactDOMComponentTree = require('ReactDOMComponentTree');
var ReactDOMInstrumentation = require('ReactDOMInstrumentation');
var ReactPerf = require('ReactPerf');
var ReactInstrumentation = require('ReactInstrumentation');
var quoteAttributeValueForBrowser = require('quoteAttributeValueForBrowser');
var warning = require('warning');
@@ -134,9 +135,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 +143,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 +170,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 +194,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 +213,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,14 +236,17 @@ 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
);
}
},
};
ReactPerf.measureMethods(DOMPropertyOperations, 'DOMPropertyOperations', {
setValueForProperty: 'setValueForProperty',
setValueForAttribute: 'setValueForAttribute',
deleteValueForProperty: 'deleteValueForProperty',
});
module.exports = DOMPropertyOperations;
@@ -13,7 +13,6 @@
var DOMChildrenOperations = require('DOMChildrenOperations');
var ReactDOMIDOperations = require('ReactDOMIDOperations');
var ReactPerf = require('ReactPerf');
/**
* Abstracts away all functionality of the reconciler that requires knowledge of
@@ -40,12 +39,4 @@ var ReactComponentBrowserEnvironment = {
};
ReactPerf.measureMethods(
ReactComponentBrowserEnvironment,
'ReactComponentBrowserEnvironment',
{
replaceNodeWithMarkup: 'replaceNodeWithMarkup',
}
);
module.exports = ReactComponentBrowserEnvironment;
+51 -11
View File
@@ -32,9 +32,11 @@ 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');
@@ -207,6 +209,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 +223,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,
@@ -244,6 +245,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 +461,7 @@ function ReactDOMComponent(element) {
this._flags = 0;
if (__DEV__) {
this._ancestorInfo = null;
this._contentDebugID = null;
}
}
@@ -569,7 +584,7 @@ ReactDOMComponent.Mixin = {
div.innerHTML = `<${type}></${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(
@@ -710,6 +725,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 +767,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 +1017,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 +1102,13 @@ ReactDOMComponent.Mixin = {
this._rootNodeID = null;
this._domID = null;
this._wrapperState = null;
if (__DEV__) {
if (this._contentDebugID) {
ReactInstrumentation.debugTool.onUnmountComponent(this._contentDebugID);
this._contentDebugID = null;
}
}
},
getPublicInstance: function() {
@@ -1072,11 +1117,6 @@ ReactDOMComponent.Mixin = {
};
ReactPerf.measureMethods(ReactDOMComponent.Mixin, 'ReactDOMComponent', {
mountComponent: 'mountComponent',
receiveComponent: 'receiveComponent',
});
Object.assign(
ReactDOMComponent.prototype,
ReactDOMComponent.Mixin,
@@ -14,7 +14,7 @@
var DOMChildrenOperations = require('DOMChildrenOperations');
var DOMLazyTree = require('DOMLazyTree');
var ReactDOMComponentTree = require('ReactDOMComponentTree');
var ReactPerf = require('ReactPerf');
var ReactInstrumentation = require('ReactInstrumentation');
var escapeTextContentForBrowser = require('escapeTextContentForBrowser');
var invariant = require('invariant');
@@ -67,6 +67,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 +142,13 @@ Object.assign(ReactDOMTextComponent.prototype, {
commentNodes[1],
nextStringText
);
if (__DEV__) {
ReactInstrumentation.debugTool.onSetText(
this._debugID,
nextStringText
);
}
}
}
},
@@ -178,13 +187,4 @@ Object.assign(ReactDOMTextComponent.prototype, {
});
ReactPerf.measureMethods(
ReactDOMTextComponent.prototype,
'ReactDOMTextComponent',
{
mountComponent: 'mountComponent',
receiveComponent: 'receiveComponent',
}
);
module.exports = ReactDOMTextComponent;
@@ -15,7 +15,6 @@ var BeforeInputEventPlugin = require('BeforeInputEventPlugin');
var ChangeEventPlugin = require('ChangeEventPlugin');
var DefaultEventPluginOrder = require('DefaultEventPluginOrder');
var EnterLeaveEventPlugin = require('EnterLeaveEventPlugin');
var ExecutionEnvironment = require('ExecutionEnvironment');
var HTMLDOMPropertyConfig = require('HTMLDOMPropertyConfig');
var ReactComponentBrowserEnvironment =
require('ReactComponentBrowserEnvironment');
@@ -91,14 +90,6 @@ function inject() {
);
ReactInjection.Component.injectEnvironment(ReactComponentBrowserEnvironment);
if (__DEV__) {
var url = (ExecutionEnvironment.canUseDOM && window.location.href) || '';
if ((/[?&]react_perf\b/).test(url)) {
var ReactDefaultPerf = require('ReactDefaultPerf');
ReactDefaultPerf.start();
}
}
}
module.exports = {
@@ -19,7 +19,6 @@ var ReactClass = require('ReactClass');
var ReactEmptyComponent = require('ReactEmptyComponent');
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
var ReactNativeComponent = require('ReactNativeComponent');
var ReactPerf = require('ReactPerf');
var ReactUpdates = require('ReactUpdates');
var ReactInjection = {
@@ -31,7 +30,6 @@ var ReactInjection = {
EventPluginUtils: EventPluginUtils.injection,
EventEmitter: ReactBrowserEventEmitter.injection,
NativeComponent: ReactNativeComponent.injection,
Perf: ReactPerf.injection,
Updates: ReactUpdates.injection,
};
@@ -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() {
@@ -172,9 +174,12 @@ describe('DOMPropertyOperations', function() {
describe('setValueForProperty', function() {
var stubNode;
var stubInstance;
beforeEach(function() {
stubNode = document.createElement('div');
stubInstance = {_debugID: 1};
ReactDOMComponentTree.precacheNode(stubInstance, stubNode);
});
it('should set values as properties by default', function() {
@@ -223,6 +228,8 @@ describe('DOMPropertyOperations', function() {
it('should not remove empty attributes for special properties', function() {
stubNode = document.createElement('input');
ReactDOMComponentTree.precacheNode(stubInstance, stubNode);
DOMPropertyOperations.setValueForProperty(stubNode, 'value', '');
// JSDOM does not behave correctly for attributes/properties
//expect(stubNode.getAttribute('value')).toBe('');
@@ -254,7 +261,7 @@ describe('DOMPropertyOperations', function() {
});
it('should use mutation method where applicable', function() {
var foobarSetter = jest.genMockFn();
var foobarSetter = jest.fn();
// inject foobar DOM property
DOMProperty.injection.injectDOMPropertyConfig({
Properties: {foobar: null},
@@ -343,9 +350,12 @@ describe('DOMPropertyOperations', function() {
describe('deleteValueForProperty', function() {
var stubNode;
var stubInstance;
beforeEach(function() {
stubNode = document.createElement('div');
stubInstance = {_debugID: 1};
ReactDOMComponentTree.precacheNode(stubInstance, stubNode);
});
it('should remove attributes for normal properties', function() {
@@ -361,6 +371,8 @@ describe('DOMPropertyOperations', function() {
it('should not remove attributes for special properties', function() {
stubNode = document.createElement('input');
ReactDOMComponentTree.precacheNode(stubInstance, stubNode);
stubNode.setAttribute('value', 'foo');
DOMPropertyOperations.deleteValueForProperty(stubNode, 'value');
@@ -371,6 +383,8 @@ describe('DOMPropertyOperations', function() {
it('should not leave all options selected when deleting multiple', function() {
stubNode = document.createElement('select');
ReactDOMComponentTree.precacheNode(stubInstance, stubNode);
stubNode.multiple = true;
stubNode.appendChild(document.createElement('option'));
stubNode.appendChild(document.createElement('option'));
@@ -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');
});
@@ -448,11 +450,11 @@ describe('ReactDOMComponent', function() {
var node = container.firstChild;
var nodeSetAttribute = node.setAttribute;
node.setAttribute = jest.genMockFn();
node.setAttribute = jest.fn();
node.setAttribute.mockImpl(nodeSetAttribute);
var nodeRemoveAttribute = node.removeAttribute;
node.removeAttribute = jest.genMockFn();
node.removeAttribute = jest.fn();
node.removeAttribute.mockImpl(nodeRemoveAttribute);
ReactDOM.render(<div id="" />, container);
@@ -486,7 +488,7 @@ describe('ReactDOMComponent', function() {
var node = container.firstChild;
var nodeValue = ''; // node.value always returns undefined
var nodeValueSetter = jest.genMockFn();
var nodeValueSetter = jest.fn();
Object.defineProperty(node, 'value', {
get: function() {
return nodeValue;
@@ -517,7 +519,7 @@ describe('ReactDOMComponent', function() {
var node = container.firstChild;
var nodeValue = true;
var nodeValueSetter = jest.genMockFn();
var nodeValueSetter = jest.fn();
Object.defineProperty(node, 'checked', {
get: function() {
return nodeValue;
@@ -550,7 +552,7 @@ describe('ReactDOMComponent', function() {
var container = document.createElement('div');
var node = ReactDOM.render(<div />, container);
var setter = jest.genMockFn();
var setter = jest.fn();
node.setAttribute = setter;
ReactDOM.render(<div dir={null} />, container);
@@ -819,8 +821,8 @@ describe('ReactDOMComponent', function() {
it('should execute custom event plugin listening behavior', function() {
var SimpleEventPlugin = require('SimpleEventPlugin');
SimpleEventPlugin.didPutListener = jest.genMockFn();
SimpleEventPlugin.willDeleteListener = jest.genMockFn();
SimpleEventPlugin.didPutListener = jest.fn();
SimpleEventPlugin.willDeleteListener = jest.fn();
var container = document.createElement('div');
ReactDOM.render(
@@ -838,8 +840,8 @@ describe('ReactDOMComponent', function() {
it('should handle null and missing properly with event hooks', function() {
var SimpleEventPlugin = require('SimpleEventPlugin');
SimpleEventPlugin.didPutListener = jest.genMockFn();
SimpleEventPlugin.willDeleteListener = jest.genMockFn();
SimpleEventPlugin.didPutListener = jest.fn();
SimpleEventPlugin.willDeleteListener = jest.fn();
var container = document.createElement('div');
ReactDOM.render(<div onClick={false} />, container);
@@ -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(<div is="custom-div" />, container);
expect(document.createElement).toHaveBeenCalledWith('div', 'custom-div');
} else {
expect(ReactDOMServer.renderToString(<div is="custom-div" />)).toContain('is="custom-div"');
}
});
});
describe('updateComponent', function() {
@@ -1050,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(<div onScroll={() => {}}/>);
expect(console.error).not.toHaveBeenCalled();
});
});
describe('tag sanitization', function() {
@@ -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
@@ -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;
@@ -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
@@ -9,8 +9,11 @@
'use strict';
// TODO: Figure out a way to drop this dependency
var createReactNativeComponentClass = require('createReactNativeComponentClass');
var InteractionManager = {};
var View = createReactNativeComponentClass({
validAttributes: {},
uiViewClassName: 'View',
});
module.exports = InteractionManager;
module.exports = View;
@@ -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;
@@ -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<NestedNode> | 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 = {};
}
@@ -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';
@@ -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;
@@ -36,9 +35,9 @@ for (var directTypeName in customDirectEventTypes) {
allTypesByEventName[directTypeName] = customDirectEventTypes[directTypeName];
}
var IOSNativeBridgeEventPlugin = {
var ReactNativeBridgeEventPlugin = {
eventTypes: merge(customBubblingEventTypes, customDirectEventTypes),
eventTypes: { ...customBubblingEventTypes, ...customDirectEventTypes },
/**
* @see {EventPluginHub.extractEvents}
@@ -68,4 +67,4 @@ var IOSNativeBridgeEventPlugin = {
},
};
module.exports = IOSNativeBridgeEventPlugin;
module.exports = ReactNativeBridgeEventPlugin;
@@ -13,7 +13,6 @@
var ReactNativeComponentTree = require('ReactNativeComponentTree');
var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes');
var ReactPerf = require('ReactPerf');
var UIManager = require('UIManager');
/**
@@ -71,12 +70,7 @@ var dangerouslyProcessChildrenUpdates = function(inst, childrenUpdates) {
* `ReactComponent.DOMIDOperations`.
*/
var ReactNativeDOMIDOperations = {
dangerouslyProcessChildrenUpdates: ReactPerf.measure(
// FIXME(frantic): #4441289 Hack to avoid modifying react-tools
'ReactDOMIDOperations',
'dangerouslyProcessChildrenUpdates',
dangerouslyProcessChildrenUpdates
),
dangerouslyProcessChildrenUpdates,
/**
* Replaces a view that exists in the document with markup.
@@ -84,14 +78,10 @@ var ReactNativeDOMIDOperations = {
* @param {string} id ID of child to be replaced.
* @param {string} markup Mount image to replace child with id.
*/
dangerouslyReplaceNodeWithMarkupByID: ReactPerf.measure(
'ReactDOMIDOperations',
'dangerouslyReplaceNodeWithMarkupByID',
function(id, mountImage) {
var oldTag = id;
UIManager.replaceExistingNonRootView(oldTag, mountImage);
}
),
dangerouslyReplaceNodeWithMarkupByID: function(id, mountImage) {
var oldTag = id;
UIManager.replaceExistingNonRootView(oldTag, mountImage);
},
};
module.exports = ReactNativeDOMIDOperations;
@@ -15,39 +15,42 @@
* 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 IOSDefaultEventPluginOrder = require('IOSDefaultEventPluginOrder');
var IOSNativeBridgeEventPlugin = require('IOSNativeBridgeEventPlugin');
var ReactElement = require('ReactElement');
var RCTEventEmitter = require('RCTEventEmitter');
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 ReactNativeEventEmitter = require('ReactNativeEventEmitter');
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');
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.
*/
EventPluginHub.injection.injectEventPluginOrder(IOSDefaultEventPluginOrder);
EventPluginHub.injection.injectEventPluginOrder(ReactNativeEventPluginOrder);
EventPluginUtils.injection.injectComponentTree(ReactNativeComponentTree);
EventPluginUtils.injection.injectTreeTraversal(ReactNativeTreeTraversal);
@@ -61,7 +64,7 @@ function inject() {
*/
EventPluginHub.injection.injectEventPluginsByName({
'ResponderEventPlugin': ResponderEventPlugin,
'IOSNativeBridgeEventPlugin': IOSNativeBridgeEventPlugin,
'ReactNativeBridgeEventPlugin': ReactNativeBridgeEventPlugin,
});
ReactUpdates.injection.injectReconcileTransaction(
@@ -11,15 +11,14 @@
*/
'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 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;
@@ -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;
@@ -12,9 +12,9 @@
'use strict';
var ReactElement = require('ReactElement');
var ReactInstrumentation = require('ReactInstrumentation');
var ReactNativeContainerInfo = require('ReactNativeContainerInfo');
var ReactNativeTagHandles = require('ReactNativeTagHandles');
var ReactPerf = require('ReactPerf');
var ReactReconciler = require('ReactReconciler');
var ReactUpdateQueue = require('ReactUpdateQueue');
var ReactUpdates = require('ReactUpdates');
@@ -138,6 +138,16 @@ var ReactNativeMount = {
var instance = instantiateReactComponent(nextWrappedElement);
ReactNativeMount._instancesByContainerID[containerTag] = instance;
if (__DEV__) {
// Mute future events from the top level wrapper.
// It is an implementation detail that devtools should not know about.
instance._debugID = 0;
if (__DEV__) {
ReactInstrumentation.debugTool.onBeginFlush();
}
}
// 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.
@@ -147,6 +157,13 @@ var ReactNativeMount = {
instance,
containerTag
);
if (__DEV__) {
// The instance here is TopLevelWrapper so we report mount for its child.
ReactInstrumentation.debugTool.onMountRootComponent(
instance._renderedComponent._debugID
);
ReactInstrumentation.debugTool.onEndFlush();
}
var component = instance.getPublicInstance();
if (callback) {
callback.call(component);
@@ -158,20 +175,15 @@ var ReactNativeMount = {
* @param {View} view View tree image.
* @param {number} containerViewID View to insert sub-view into.
*/
_mountImageIntoNode: ReactPerf.measure(
// FIXME(frantic): #4441289 Hack to avoid modifying react-tools
'ReactComponentBrowserEnvironment',
'mountImageIntoNode',
function(mountImage, containerID) {
// Since we now know that the `mountImage` has been mounted, we can
// mark it as such.
var childTag = mountImage;
UIManager.setChildren(
containerID,
[childTag]
);
}
),
_mountImageIntoNode: function(mountImage, containerID) {
// Since we now know that the `mountImage` has been mounted, we can
// mark it as such.
var childTag = mountImage;
UIManager.setChildren(
containerID,
[childTag]
);
},
/**
* Standard unmounting of the component that is rendered into `containerID`,
@@ -204,8 +216,14 @@ var ReactNativeMount = {
if (!instance) {
return false;
}
if (__DEV__) {
ReactInstrumentation.debugTool.onBeginFlush();
}
ReactNativeMount.unmountComponentFromNode(instance, containerTag);
delete ReactNativeMount._instancesByContainerID[containerTag];
if (__DEV__) {
ReactInstrumentation.debugTool.onEndFlush();
}
return true;
},
@@ -229,10 +247,4 @@ var ReactNativeMount = {
};
ReactNativeMount.renderComponent = ReactPerf.measure(
'ReactMount',
'_renderNewRootComponent',
ReactNativeMount.renderComponent
);
module.exports = ReactNativeMount;
@@ -11,6 +11,7 @@
'use strict';
var ReactInstrumentation = require('ReactInstrumentation');
var ReactNativeComponentTree = require('ReactNativeComponentTree');
var ReactNativeTagHandles = require('ReactNativeTagHandles');
var UIManager = require('UIManager');
@@ -28,6 +29,10 @@ var ReactNativeTextComponent = function(text) {
Object.assign(ReactNativeTextComponent.prototype, {
mountComponent: function(transaction, nativeParent, nativeContainerInfo, context) {
if (__DEV__) {
ReactInstrumentation.debugTool.onSetText(this._debugID, this._stringText);
}
// TODO: nativeParent should have this context already. Stop abusing context.
invariant(
context.isInAParentText,
@@ -65,6 +70,12 @@ Object.assign(ReactNativeTextComponent.prototype, {
'RCTRawText',
{text: this._stringText}
);
if (__DEV__) {
ReactInstrumentation.debugTool.onSetText(
this._debugID,
nextStringText
);
}
}
}
},
@@ -11,4 +11,4 @@
// Noop
// TODO: Move all initialization callers back into react-native
// TODO #10932517: Move all initialization callers back into react-native
@@ -9,6 +9,8 @@
'use strict';
// Noop
var RCTEventEmitter = {
register: jest.fn(),
};
// TODO: Move all initialization callers back into react-native
module.exports = RCTEventEmitter;
@@ -12,10 +12,12 @@
// Mock of the Native Hooks
var RCTUIManager = {
createView: jest.genMockFunction(),
setChildren: jest.genMockFunction(),
manageChildren: jest.genMockFunction(),
updateView: jest.genMockFunction(),
createView: jest.fn(),
setChildren: jest.fn(),
manageChildren: jest.fn(),
updateView: jest.fn(),
removeSubviewsFromContainerWithID: jest.fn(),
replaceExistingNonRootView: jest.fn(),
};
module.exports = RCTUIManager;

Some files were not shown because too many files have changed in this diff Show More