mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Add ReactNativeOperationHistoryDevtool to track native operations
This commit is contained in:
@@ -55,6 +55,9 @@ var ReactDebugTool = {
|
||||
onEndProcessingChildContext() {
|
||||
emitEvent('onEndProcessingChildContext');
|
||||
},
|
||||
onNativeOperation(debugID, type, payload) {
|
||||
emitEvent('onNativeOperation', debugID, type, payload);
|
||||
},
|
||||
onSetState() {
|
||||
emitEvent('onSetState');
|
||||
},
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright 2016-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @providesModule ReactNativeOperationHistoryDevtool
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var history = [];
|
||||
|
||||
var ReactNativeOperationHistoryDevtool = {
|
||||
onNativeOperation(debugID, type, payload) {
|
||||
history.push({
|
||||
instanceID: debugID,
|
||||
type,
|
||||
payload,
|
||||
});
|
||||
},
|
||||
|
||||
clearHistory() {
|
||||
history = [];
|
||||
},
|
||||
|
||||
getHistory() {
|
||||
return history;
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = ReactNativeOperationHistoryDevtool;
|
||||
@@ -0,0 +1,654 @@
|
||||
/**
|
||||
* Copyright 2016-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
describe('ReactNativeOperationHistoryDevtool', () => {
|
||||
var React;
|
||||
var ReactDebugTool;
|
||||
var ReactDOM;
|
||||
var ReactDOMComponentTree;
|
||||
var ReactDOMFeatureFlags;
|
||||
var ReactNativeOperationHistoryDevtool;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModuleRegistry();
|
||||
|
||||
React = require('React');
|
||||
ReactDebugTool = require('ReactDebugTool');
|
||||
ReactDOM = require('ReactDOM');
|
||||
ReactDOMComponentTree = require('ReactDOMComponentTree');
|
||||
ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
|
||||
ReactNativeOperationHistoryDevtool = require('ReactNativeOperationHistoryDevtool');
|
||||
|
||||
ReactDebugTool.addDevtool(ReactNativeOperationHistoryDevtool);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
ReactDebugTool.removeDevtool(ReactNativeOperationHistoryDevtool);
|
||||
});
|
||||
|
||||
function assertHistoryMatches(expectedHistory) {
|
||||
var actualHistory = ReactNativeOperationHistoryDevtool.getHistory();
|
||||
expect(actualHistory).toEqual(expectedHistory);
|
||||
}
|
||||
|
||||
describe('mount', () => {
|
||||
it('gets recorded for native roots', () => {
|
||||
var node = document.createElement('div');
|
||||
ReactDOM.render(<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');
|
||||
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 recorded for composite roots that return null', () => {
|
||||
function Foo() {
|
||||
return null;
|
||||
}
|
||||
var node = document.createElement('div');
|
||||
ReactDOM.render(<Foo />, node);
|
||||
var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild);
|
||||
assertHistoryMatches([{
|
||||
instanceID: inst._debugID,
|
||||
type: 'mount',
|
||||
payload: ReactDOMFeatureFlags.useCreateElement ?
|
||||
'#comment' :
|
||||
'<!-- react-empty: 1 -->',
|
||||
}]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update styles', () => {
|
||||
it('gets recorded during mount', () => {
|
||||
var node = document.createElement('div');
|
||||
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.clearHistory();
|
||||
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.clearHistory();
|
||||
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');
|
||||
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.clearHistory();
|
||||
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.clearHistory();
|
||||
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');
|
||||
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.clearHistory();
|
||||
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');
|
||||
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.clearHistory();
|
||||
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.clearHistory();
|
||||
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.clearHistory();
|
||||
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.clearHistory();
|
||||
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.clearHistory();
|
||||
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.clearHistory();
|
||||
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.clearHistory();
|
||||
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);
|
||||
|
||||
ReactNativeOperationHistoryDevtool.clearHistory();
|
||||
element = <span />;
|
||||
ReactDOM.render(<Foo />, node);
|
||||
assertHistoryMatches([{
|
||||
instanceID: inst._debugID,
|
||||
type: 'replace with',
|
||||
payload: 'SPAN',
|
||||
}]);
|
||||
});
|
||||
|
||||
it('gets ignored if the type has not changed', () => {
|
||||
var element;
|
||||
function Foo() {
|
||||
return element;
|
||||
}
|
||||
|
||||
var node = document.createElement('div');
|
||||
element = <div />;
|
||||
ReactDOM.render(<Foo />, node);
|
||||
|
||||
ReactNativeOperationHistoryDevtool.clearHistory();
|
||||
element = <div />;
|
||||
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.clearHistory();
|
||||
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.clearHistory();
|
||||
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.clearHistory();
|
||||
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.clearHistory();
|
||||
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.clearHistory();
|
||||
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.clearHistory();
|
||||
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.clearHistory();
|
||||
ReactDOM.render(<div><span key="a" /></div>, node);
|
||||
assertHistoryMatches([{
|
||||
instanceID: inst._debugID,
|
||||
type: 'remove child',
|
||||
payload: {fromIndex: 1},
|
||||
}]);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -693,6 +693,14 @@ var ReactMount = {
|
||||
setInnerHTML(container, markup);
|
||||
ReactDOMComponentTree.precacheNode(instance, container.firstChild);
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
ReactInstrumentation.debugTool.onNativeOperation(
|
||||
ReactDOMComponentTree.getInstanceFromNode(container.firstChild)._debugID,
|
||||
'mount',
|
||||
markup.toString()
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
@@ -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 = {};
|
||||
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,
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
var DOMLazyTree = require('DOMLazyTree');
|
||||
var Danger = require('Danger');
|
||||
var ReactMultiChildUpdateTypes = require('ReactMultiChildUpdateTypes');
|
||||
var ReactDOMComponentTree = require('ReactDOMComponentTree');
|
||||
var ReactInstrumentation = require('ReactInstrumentation');
|
||||
var ReactPerf = require('ReactPerf');
|
||||
|
||||
var createMicrosoftUnsafeLocalFunction = require('createMicrosoftUnsafeLocalFunction');
|
||||
@@ -120,6 +122,26 @@ function replaceDelimitedText(openingComment, closingComment, stringText) {
|
||||
removeDelimitedText(parentNode, openingComment, closingComment);
|
||||
}
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
ReactInstrumentation.debugTool.onNativeOperation(
|
||||
ReactDOMComponentTree.getInstanceFromNode(openingComment)._debugID,
|
||||
'replace text',
|
||||
stringText
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var dangerouslyReplaceNodeWithMarkup = Danger.dangerouslyReplaceNodeWithMarkup;
|
||||
if (__DEV__) {
|
||||
dangerouslyReplaceNodeWithMarkup = function(oldChild, markup, prevInstance) {
|
||||
Danger.dangerouslyReplaceNodeWithMarkup(oldChild, markup);
|
||||
ReactInstrumentation.debugTool.onNativeOperation(
|
||||
prevInstance._debugID,
|
||||
'replace with',
|
||||
markup.toString()
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -127,7 +149,7 @@ function replaceDelimitedText(openingComment, closingComment, stringText) {
|
||||
*/
|
||||
var DOMChildrenOperations = {
|
||||
|
||||
dangerouslyReplaceNodeWithMarkup: Danger.dangerouslyReplaceNodeWithMarkup,
|
||||
dangerouslyReplaceNodeWithMarkup: dangerouslyReplaceNodeWithMarkup,
|
||||
|
||||
replaceDelimitedText: replaceDelimitedText,
|
||||
|
||||
@@ -139,6 +161,11 @@ var DOMChildrenOperations = {
|
||||
* @internal
|
||||
*/
|
||||
processUpdates: function(parentNode, updates) {
|
||||
if (__DEV__) {
|
||||
var parentNodeDebugID =
|
||||
ReactDOMComponentTree.getInstanceFromNode(parentNode)._debugID;
|
||||
}
|
||||
|
||||
for (var k = 0; k < updates.length; k++) {
|
||||
var update = updates[k];
|
||||
switch (update.type) {
|
||||
@@ -148,6 +175,13 @@ var DOMChildrenOperations = {
|
||||
update.content,
|
||||
getNodeAfter(parentNode, update.afterNode)
|
||||
);
|
||||
if (__DEV__) {
|
||||
ReactInstrumentation.debugTool.onNativeOperation(
|
||||
parentNodeDebugID,
|
||||
'insert child',
|
||||
{toIndex: update.toIndex, content: update.content.toString()}
|
||||
);
|
||||
}
|
||||
break;
|
||||
case ReactMultiChildUpdateTypes.MOVE_EXISTING:
|
||||
moveChild(
|
||||
@@ -155,21 +189,49 @@ var DOMChildrenOperations = {
|
||||
update.fromNode,
|
||||
getNodeAfter(parentNode, update.afterNode)
|
||||
);
|
||||
if (__DEV__) {
|
||||
ReactInstrumentation.debugTool.onNativeOperation(
|
||||
parentNodeDebugID,
|
||||
'move child',
|
||||
{fromIndex: update.fromIndex, toIndex: update.toIndex}
|
||||
);
|
||||
}
|
||||
break;
|
||||
case ReactMultiChildUpdateTypes.SET_MARKUP:
|
||||
setInnerHTML(
|
||||
parentNode,
|
||||
update.content
|
||||
);
|
||||
if (__DEV__) {
|
||||
ReactInstrumentation.debugTool.onNativeOperation(
|
||||
parentNodeDebugID,
|
||||
'replace children',
|
||||
update.content.toString()
|
||||
);
|
||||
}
|
||||
break;
|
||||
case ReactMultiChildUpdateTypes.TEXT_CONTENT:
|
||||
setTextContent(
|
||||
parentNode,
|
||||
update.content
|
||||
);
|
||||
if (__DEV__) {
|
||||
ReactInstrumentation.debugTool.onNativeOperation(
|
||||
parentNodeDebugID,
|
||||
'replace text',
|
||||
update.content.toString()
|
||||
);
|
||||
}
|
||||
break;
|
||||
case ReactMultiChildUpdateTypes.REMOVE_NODE:
|
||||
removeChild(parentNode, update.fromNode);
|
||||
if (__DEV__) {
|
||||
ReactInstrumentation.debugTool.onNativeOperation(
|
||||
parentNodeDebugID,
|
||||
'remove child',
|
||||
{fromIndex: update.fromIndex}
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,12 +96,17 @@ function queueText(tree, text) {
|
||||
}
|
||||
}
|
||||
|
||||
function toString() {
|
||||
return this.node.nodeName;
|
||||
}
|
||||
|
||||
function DOMLazyTree(node) {
|
||||
return {
|
||||
node: node,
|
||||
children: [],
|
||||
html: null,
|
||||
text: null,
|
||||
toString,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
var CSSProperty = require('CSSProperty');
|
||||
var ExecutionEnvironment = require('ExecutionEnvironment');
|
||||
var ReactInstrumentation = require('ReactInstrumentation');
|
||||
var ReactPerf = require('ReactPerf');
|
||||
|
||||
var camelizeStyleName = require('camelizeStyleName');
|
||||
@@ -192,6 +193,14 @@ var CSSPropertyOperations = {
|
||||
* @param {ReactDOMComponent} component
|
||||
*/
|
||||
setValueForStyles: function(node, styles, component) {
|
||||
if (__DEV__) {
|
||||
ReactInstrumentation.debugTool.onNativeOperation(
|
||||
component._debugID,
|
||||
'update styles',
|
||||
styles
|
||||
);
|
||||
}
|
||||
|
||||
var style = node.style;
|
||||
for (var styleName in styles) {
|
||||
if (!styles.hasOwnProperty(styleName)) {
|
||||
|
||||
@@ -12,7 +12,9 @@
|
||||
'use strict';
|
||||
|
||||
var DOMProperty = require('DOMProperty');
|
||||
var ReactDOMComponentTree = require('ReactDOMComponentTree');
|
||||
var ReactDOMInstrumentation = require('ReactDOMInstrumentation');
|
||||
var ReactInstrumentation = require('ReactInstrumentation');
|
||||
var ReactPerf = require('ReactPerf');
|
||||
|
||||
var quoteAttributeValueForBrowser = require('quoteAttributeValueForBrowser');
|
||||
@@ -134,9 +136,6 @@ var DOMPropertyOperations = {
|
||||
* @param {*} value
|
||||
*/
|
||||
setValueForProperty: function(node, name, value) {
|
||||
if (__DEV__) {
|
||||
ReactDOMInstrumentation.debugTool.onSetValueForProperty(node, name, value);
|
||||
}
|
||||
var propertyInfo = DOMProperty.properties.hasOwnProperty(name) ?
|
||||
DOMProperty.properties[name] : null;
|
||||
if (propertyInfo) {
|
||||
@@ -145,6 +144,7 @@ var DOMPropertyOperations = {
|
||||
mutationMethod(node, value);
|
||||
} else if (shouldIgnoreValue(propertyInfo, value)) {
|
||||
this.deleteValueForProperty(node, name);
|
||||
return;
|
||||
} else if (propertyInfo.mustUseProperty) {
|
||||
var propName = propertyInfo.propertyName;
|
||||
// Must explicitly cast values for HAS_SIDE_EFFECTS-properties to the
|
||||
@@ -171,6 +171,18 @@ var DOMPropertyOperations = {
|
||||
}
|
||||
} else if (DOMProperty.isCustomAttribute(name)) {
|
||||
DOMPropertyOperations.setValueForAttribute(node, name, value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
ReactDOMInstrumentation.debugTool.onSetValueForProperty(node, name, value);
|
||||
var payload = {};
|
||||
payload[name] = value;
|
||||
ReactInstrumentation.debugTool.onNativeOperation(
|
||||
ReactDOMComponentTree.getInstanceFromNode(node)._debugID,
|
||||
'update attribute',
|
||||
payload
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -183,6 +195,16 @@ var DOMPropertyOperations = {
|
||||
} else {
|
||||
node.setAttribute(name, '' + value);
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
var payload = {};
|
||||
payload[name] = value;
|
||||
ReactInstrumentation.debugTool.onNativeOperation(
|
||||
ReactDOMComponentTree.getInstanceFromNode(node)._debugID,
|
||||
'update attribute',
|
||||
payload
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -192,9 +214,6 @@ var DOMPropertyOperations = {
|
||||
* @param {string} name
|
||||
*/
|
||||
deleteValueForProperty: function(node, name) {
|
||||
if (__DEV__) {
|
||||
ReactDOMInstrumentation.debugTool.onDeleteValueForProperty(node, name);
|
||||
}
|
||||
var propertyInfo = DOMProperty.properties.hasOwnProperty(name) ?
|
||||
DOMProperty.properties[name] : null;
|
||||
if (propertyInfo) {
|
||||
@@ -218,6 +237,15 @@ var DOMPropertyOperations = {
|
||||
} else if (DOMProperty.isCustomAttribute(name)) {
|
||||
node.removeAttribute(name);
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
ReactDOMInstrumentation.debugTool.onDeleteValueForProperty(node, name);
|
||||
ReactInstrumentation.debugTool.onNativeOperation(
|
||||
ReactDOMComponentTree.getInstanceFromNode(node)._debugID,
|
||||
'remove attribute',
|
||||
name
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
describe('DOMPropertyOperations', function() {
|
||||
var DOMPropertyOperations;
|
||||
var DOMProperty;
|
||||
var ReactDOMComponentTree;
|
||||
|
||||
beforeEach(function() {
|
||||
jest.resetModuleRegistry();
|
||||
@@ -22,6 +23,7 @@ describe('DOMPropertyOperations', function() {
|
||||
|
||||
DOMPropertyOperations = require('DOMPropertyOperations');
|
||||
DOMProperty = require('DOMProperty');
|
||||
ReactDOMComponentTree = require('ReactDOMComponentTree');
|
||||
});
|
||||
|
||||
describe('createMarkupForProperty', function() {
|
||||
@@ -175,6 +177,7 @@ describe('DOMPropertyOperations', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
stubNode = document.createElement('div');
|
||||
ReactDOMComponentTree.precacheNode({}, stubNode);
|
||||
});
|
||||
|
||||
it('should set values as properties by default', function() {
|
||||
@@ -223,6 +226,8 @@ describe('DOMPropertyOperations', function() {
|
||||
|
||||
it('should not remove empty attributes for special properties', function() {
|
||||
stubNode = document.createElement('input');
|
||||
ReactDOMComponentTree.precacheNode({}, stubNode);
|
||||
|
||||
DOMPropertyOperations.setValueForProperty(stubNode, 'value', '');
|
||||
// JSDOM does not behave correctly for attributes/properties
|
||||
//expect(stubNode.getAttribute('value')).toBe('');
|
||||
@@ -346,6 +351,7 @@ describe('DOMPropertyOperations', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
stubNode = document.createElement('div');
|
||||
ReactDOMComponentTree.precacheNode({}, stubNode);
|
||||
});
|
||||
|
||||
it('should remove attributes for normal properties', function() {
|
||||
@@ -361,6 +367,8 @@ describe('DOMPropertyOperations', function() {
|
||||
|
||||
it('should not remove attributes for special properties', function() {
|
||||
stubNode = document.createElement('input');
|
||||
ReactDOMComponentTree.precacheNode({}, stubNode);
|
||||
|
||||
stubNode.setAttribute('value', 'foo');
|
||||
|
||||
DOMPropertyOperations.deleteValueForProperty(stubNode, 'value');
|
||||
@@ -371,6 +379,8 @@ describe('DOMPropertyOperations', function() {
|
||||
|
||||
it('should not leave all options selected when deleting multiple', function() {
|
||||
stubNode = document.createElement('select');
|
||||
ReactDOMComponentTree.precacheNode({}, stubNode);
|
||||
|
||||
stubNode.multiple = true;
|
||||
stubNode.appendChild(document.createElement('option'));
|
||||
stubNode.appendChild(document.createElement('option'));
|
||||
|
||||
@@ -888,7 +888,11 @@ var ReactCompositeComponentMixin = {
|
||||
}
|
||||
}
|
||||
|
||||
this._replaceNodeWithMarkup(oldNativeNode, nextMarkup);
|
||||
this._replaceNodeWithMarkup(
|
||||
oldNativeNode,
|
||||
nextMarkup,
|
||||
prevComponentInstance
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -897,10 +901,11 @@ var ReactCompositeComponentMixin = {
|
||||
*
|
||||
* @protected
|
||||
*/
|
||||
_replaceNodeWithMarkup: function(oldNativeNode, nextMarkup) {
|
||||
_replaceNodeWithMarkup: function(oldNativeNode, nextMarkup, prevInstance) {
|
||||
ReactComponentEnvironment.replaceNodeWithMarkup(
|
||||
oldNativeNode,
|
||||
nextMarkup
|
||||
nextMarkup,
|
||||
prevInstance
|
||||
);
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user