Merge pull request #4987 from spicyj/perf

Add unit test to ensure DOM mutations are instrumented
This commit is contained in:
Ben Alpert 2015-09-28 11:48:18 -07:00
commit c512603a8c
9 changed files with 231 additions and 5 deletions

View File

@ -16,6 +16,7 @@ var EventConstants = require('EventConstants');
var EventPluginHub = require('EventPluginHub');
var EventPluginRegistry = require('EventPluginRegistry');
var ReactEventEmitterMixin = require('ReactEventEmitterMixin');
var ReactPerf = require('ReactPerf');
var ViewportMetrics = require('ViewportMetrics');
var assign = require('Object.assign');
@ -373,4 +374,9 @@ var ReactBrowserEventEmitter = assign({}, ReactEventEmitterMixin, {
});
ReactPerf.measureMethods(ReactBrowserEventEmitter, 'ReactBrowserEventEmitter', {
putListener: 'putListener',
deleteListener: 'deleteListener',
});
module.exports = ReactBrowserEventEmitter;

View File

@ -92,7 +92,6 @@ var ReactDOMIDOperations = {
};
ReactPerf.measureMethods(ReactDOMIDOperations, 'ReactDOMIDOperations', {
updatePropertyByID: 'updatePropertyByID',
dangerouslyReplaceNodeWithMarkupByID: 'dangerouslyReplaceNodeWithMarkupByID',
dangerouslyProcessChildrenUpdates: 'dangerouslyProcessChildrenUpdates',
});

View File

@ -440,6 +440,9 @@ TopLevelWrapper.prototype.render = function() {
* Inside of `container`, the first element rendered is the "reactRoot".
*/
var ReactMount = {
TopLevelWrapper: TopLevelWrapper,
/** Exposed for debugging purposes **/
_instancesByReactRootID: instancesByReactRootID,

View File

@ -14,6 +14,7 @@
var Danger = require('Danger');
var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes');
var ReactPerf = require('ReactPerf');
var setInnerHTML = require('setInnerHTML');
var setTextContent = require('setTextContent');
@ -151,4 +152,8 @@ var DOMChildrenOperations = {
};
ReactPerf.measureMethods(DOMChildrenOperations, 'DOMChildrenOperations', {
updateTextContent: 'updateTextContent',
});
module.exports = DOMChildrenOperations;

View File

@ -14,6 +14,7 @@
var CSSProperty = require('CSSProperty');
var ExecutionEnvironment = require('ExecutionEnvironment');
var ReactPerf = require('ReactPerf');
var camelizeStyleName = require('camelizeStyleName');
var dangerousStyleValue = require('dangerousStyleValue');
@ -185,4 +186,8 @@ var CSSPropertyOperations = {
};
ReactPerf.measureMethods(CSSPropertyOperations, 'CSSPropertyOperations', {
setValueForStyles: 'setValueForStyles',
});
module.exports = CSSPropertyOperations;

View File

@ -13,6 +13,7 @@
'use strict';
var DOMProperty = require('DOMProperty');
var ReactPerf = require('ReactPerf');
var quoteAttributeValueForBrowser = require('quoteAttributeValueForBrowser');
var warning = require('warning');
@ -247,4 +248,10 @@ var DOMPropertyOperations = {
};
ReactPerf.measureMethods(DOMPropertyOperations, 'DOMPropertyOperations', {
setValueForProperty: 'setValueForProperty',
setValueForAttribute: 'setValueForAttribute',
deleteValueForProperty: 'deleteValueForProperty',
});
module.exports = DOMPropertyOperations;

View File

@ -165,7 +165,11 @@ var ReactDefaultPerf = {
].totalTime = performanceNow() - start;
return rv;
} else if (fnName === '_mountImageIntoNode' ||
moduleName === 'ReactDOMIDOperations') {
moduleName === 'ReactBrowserEventEmitter' ||
moduleName === 'ReactDOMIDOperations' ||
moduleName === 'CSSPropertyOperations' ||
moduleName === 'DOMChildrenOperations' ||
moduleName === 'DOMPropertyOperations') {
start = performanceNow();
rv = func.apply(this, args);
totalTime = performanceNow() - start;
@ -198,8 +202,12 @@ var ReactDefaultPerf = {
});
} else {
// basic format
var id = args[0];
if (typeof id === 'object') {
id = ReactMount.getID(args[0]);
}
ReactDefaultPerf._recordWrite(
args[0],
id,
fnName,
totalTime,
Array.prototype.slice.call(args, 1)
@ -211,7 +219,7 @@ var ReactDefaultPerf = {
fnName === 'updateComponent' || // TODO: receiveComponent()?
fnName === '_renderValidatedComponent')) {
if (typeof this._currentElement.type === 'string') {
if (this._currentElement.type === ReactMount.TopLevelWrapper) {
return func.apply(this, args);
}

View File

@ -22,7 +22,9 @@ var DOM_OPERATION_TYPES = {
REMOVE_NODE: 'remove',
SET_MARKUP: 'set innerHTML',
TEXT_CONTENT: 'set textContent',
'updatePropertyByID': 'update attribute',
'setValueForProperty': 'update attribute',
'setValueForAttribute': 'update attribute',
'deleteValueForProperty': 'remove attribute',
'dangerouslyReplaceNodeWithMarkupByID': 'replace',
};

View File

@ -0,0 +1,191 @@
/**
* 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.
*
* @emails react-core
*/
'use strict';
describe('ReactDefaultPerf', function() {
var React;
var ReactDOM;
var ReactDefaultPerf;
var ReactTestUtils;
var App;
var Box;
var Div;
beforeEach(function() {
var now = 0;
require('mock-modules').setMock('performanceNow', function() {
return now++;
});
React = require('React');
ReactDOM = require('ReactDOM');
ReactDefaultPerf = require('ReactDefaultPerf');
ReactTestUtils = require('ReactTestUtils');
App = React.createClass({
render: function() {
return <div><Box /><Box /></div>;
},
});
Box = React.createClass({
render: function() {
return <div><input /></div>;
},
});
// ReactPerf only measures composites, so we put everything in one.
Div = React.createClass({
render: function() {
return <div {...this.props} />;
},
});
});
function measure(fn) {
ReactDefaultPerf.start();
fn();
ReactDefaultPerf.stop();
return ReactDefaultPerf.getLastMeasurements();
}
it('should count no-op update as waste', function() {
var container = document.createElement('div');
ReactDOM.render(<App />, container);
var measurements = measure(() => {
ReactDOM.render(<App />, container);
});
var summary = ReactDefaultPerf.getMeasurementsSummaryMap(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 */
});
function expectNoWaste(fn) {
var measurements = measure(fn);
var summary = ReactDefaultPerf.getMeasurementsSummaryMap(measurements);
expect(summary).toEqual([]);
}
it('should not count initial render as waste', function() {
expectNoWaste(() => {
ReactTestUtils.renderIntoDocument(<App />);
});
});
it('should not count unmount as waste', function() {
var container = document.createElement('div');
ReactDOM.render(<Div>hello</Div>, container);
expectNoWaste(() => {
ReactDOM.unmountComponentAtNode(container);
});
});
it('should not count content update as waste', function() {
var container = document.createElement('div');
ReactDOM.render(<Div>hello</Div>, container);
expectNoWaste(() => {
ReactDOM.render(<Div>hello world</Div>, container);
});
});
it('should not count child addition as waste', function() {
var container = document.createElement('div');
ReactDOM.render(<Div><span /></Div>, container);
expectNoWaste(() => {
ReactDOM.render(<Div><span /><span /></Div>, container);
});
});
it('should not count child removal as waste', function() {
var container = document.createElement('div');
ReactDOM.render(<Div><span /><span /></Div>, container);
expectNoWaste(() => {
ReactDOM.render(<Div><span /></Div>, container);
});
});
it('should not count property update as waste', function() {
var container = document.createElement('div');
ReactDOM.render(<Div className="yellow">hey</Div>, container);
expectNoWaste(() => {
ReactDOM.render(<Div className="blue">hey</Div>, container);
});
});
it('should not count style update as waste', function() {
var container = document.createElement('div');
ReactDOM.render(<Div style={{color: 'yellow'}}>hey</Div>, container);
expectNoWaste(() => {
ReactDOM.render(<Div style={{color: 'blue'}}>hey</Div>, container);
});
});
it('should not count listener update as waste', function() {
var container = document.createElement('div');
ReactDOM.render(<Div onClick={function() {}}>hey</Div>, container);
expectNoWaste(() => {
ReactDOM.render(<Div onClick={function() {}}>hey</Div>, container);
});
});
it('should not count property removal as waste', function() {
var container = document.createElement('div');
ReactDOM.render(<Div className="yellow">hey</Div>, container);
expectNoWaste(() => {
ReactDOM.render(<Div>hey</Div>, container);
});
});
it('should not count raw HTML update as waste', function() {
var container = document.createElement('div');
ReactDOM.render(
<Div dangerouslySetInnerHTML={{__html: 'me'}} />,
container
);
expectNoWaste(() => {
ReactDOM.render(
<Div dangerouslySetInnerHTML={{__html: 'you'}} />,
container
);
});
});
it('should not count child reordering as waste', function() {
var container = document.createElement('div');
ReactDOM.render(<Div><div key="A" /><div key="B" /></Div>, container);
expectNoWaste(() => {
ReactDOM.render(<Div><div key="B" /><div key="A" /></Div>, container);
});
});
it('should not count text update as waste', function() {
var container = document.createElement('div');
ReactDOM.render(<Div>{'hello'}{'world'}</Div>, container);
expectNoWaste(() => {
ReactDOM.render(<Div>{'hello'}{'friend'}</Div>, container);
});
});
});