/** * Copyright 2016-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * * @emails react-core */ 'use strict'; describe('ReactNativeOperationHistoryDevtool', () => { var React; var ReactDebugTool; var ReactDOM; var ReactDOMComponentTree; var ReactDOMFeatureFlags; var ReactNativeOperationHistoryDevtool; beforeEach(() => { jest.resetModuleRegistry(); React = require('React'); ReactDebugTool = require('ReactDebugTool'); ReactDOM = require('ReactDOM'); ReactDOMComponentTree = require('ReactDOMComponentTree'); ReactDOMFeatureFlags = require('ReactDOMFeatureFlags'); ReactNativeOperationHistoryDevtool = require('ReactNativeOperationHistoryDevtool'); ReactDebugTool.addDevtool(ReactNativeOperationHistoryDevtool); }); afterEach(() => { ReactDebugTool.removeDevtool(ReactNativeOperationHistoryDevtool); }); function assertHistoryMatches(expectedHistory) { var actualHistory = ReactNativeOperationHistoryDevtool.getHistory(); expect(actualHistory).toEqual(expectedHistory); } describe('mount', () => { it('gets recorded for native roots', () => { var node = document.createElement('div'); ReactDOM.render(

Hi.

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

Hi.

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

Hi.

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

Hi.

', }]); }); it('gets recorded for composite roots that return null', () => { function Foo() { return null; } var node = document.createElement('div'); ReactDOM.render(, node); var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); assertHistoryMatches([{ instanceID: inst._debugID, type: 'mount', payload: ReactDOMFeatureFlags.useCreateElement ? '#comment' : '', }]); }); }); describe('update styles', () => { it('gets recorded during mount', () => { var node = document.createElement('div'); ReactDOM.render(
, node); var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); if (ReactDOMFeatureFlags.useCreateElement) { assertHistoryMatches([{ instanceID: inst._debugID, type: 'update styles', payload: { color: 'red', backgroundColor: 'yellow', }, }, { instanceID: inst._debugID, type: 'mount', payload: 'DIV', }]); } else { assertHistoryMatches([{ instanceID: inst._debugID, type: 'mount', payload: '
', }]); } }); it('gets recorded during an update', () => { var node = document.createElement('div'); ReactDOM.render(
, node); var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); ReactNativeOperationHistoryDevtool.clearHistory(); ReactDOM.render(
, node); ReactDOM.render(
, node); ReactDOM.render(
, node); ReactDOM.render(
, node); assertHistoryMatches([{ instanceID: inst._debugID, type: 'update styles', payload: { color: 'red' }, }, { instanceID: inst._debugID, type: 'update styles', payload: { color: 'blue', backgroundColor: 'yellow' }, }, { instanceID: inst._debugID, type: 'update styles', payload: { color: '', backgroundColor: 'green' }, }, { instanceID: inst._debugID, type: 'update styles', payload: { backgroundColor: '' }, }]); }); it('gets ignored if the styles are shallowly equal', () => { var node = document.createElement('div'); ReactDOM.render(
, node); var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); ReactNativeOperationHistoryDevtool.clearHistory(); ReactDOM.render(
, node); ReactDOM.render(
, node); assertHistoryMatches([{ instanceID: inst._debugID, type: 'update styles', payload: { color: 'red', backgroundColor: 'yellow', }, }]); }); }); describe('update attribute', () => { describe('simple attribute', () => { it('gets recorded during mount', () => { var node = document.createElement('div'); ReactDOM.render(
, node); var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); if (ReactDOMFeatureFlags.useCreateElement) { assertHistoryMatches([{ instanceID: inst._debugID, type: 'update attribute', payload: { className: 'rad' }, }, { instanceID: inst._debugID, type: 'update attribute', payload: { tabIndex: 42 }, }, { instanceID: inst._debugID, type: 'mount', payload: 'DIV', }]); } else { assertHistoryMatches([{ instanceID: inst._debugID, type: 'mount', payload: '
', }]); } }); it('gets recorded during an update', () => { var node = document.createElement('div'); ReactDOM.render(
, node); var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); ReactNativeOperationHistoryDevtool.clearHistory(); ReactDOM.render(
, node); ReactDOM.render(
, node); ReactDOM.render(
, node); assertHistoryMatches([{ instanceID: inst._debugID, type: 'update attribute', payload: { className: 'rad' }, }, { instanceID: inst._debugID, type: 'update attribute', payload: { className: 'mad' }, }, { instanceID: inst._debugID, type: 'update attribute', payload: { tabIndex: 42 }, }, { instanceID: inst._debugID, type: 'remove attribute', payload: 'className', }, { instanceID: inst._debugID, type: 'update attribute', payload: { tabIndex: 43 }, }]); }); }); describe('attribute that gets removed with certain values', () => { it('gets recorded as a removal during an update', () => { var node = document.createElement('div'); ReactDOM.render(
, node); var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); ReactNativeOperationHistoryDevtool.clearHistory(); ReactDOM.render(
, node); ReactDOM.render(
, node); assertHistoryMatches([{ instanceID: inst._debugID, type: 'update attribute', payload: { disabled: true }, }, { instanceID: inst._debugID, type: 'remove attribute', payload: 'disabled', }]); }); }); describe('custom attribute', () => { it('gets recorded during mount', () => { var node = document.createElement('div'); ReactDOM.render(
, node); var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); if (ReactDOMFeatureFlags.useCreateElement) { assertHistoryMatches([{ instanceID: inst._debugID, type: 'update attribute', payload: { 'data-x': 'rad' }, }, { instanceID: inst._debugID, type: 'update attribute', payload: { 'data-y': 42 }, }, { instanceID: inst._debugID, type: 'mount', payload: 'DIV', }]); } else { assertHistoryMatches([{ instanceID: inst._debugID, type: 'mount', payload: '
', }]); } }); it('gets recorded during an update', () => { var node = document.createElement('div'); ReactDOM.render(
, node); var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); ReactNativeOperationHistoryDevtool.clearHistory(); ReactDOM.render(
, node); ReactDOM.render(
, node); ReactDOM.render(
, node); assertHistoryMatches([{ instanceID: inst._debugID, type: 'update attribute', payload: { 'data-x': 'rad' }, }, { instanceID: inst._debugID, type: 'update attribute', payload: { 'data-x': 'mad' }, }, { instanceID: inst._debugID, type: 'update attribute', payload: { 'data-y': 42 }, }, { instanceID: inst._debugID, type: 'remove attribute', payload: 'data-x', }, { instanceID: inst._debugID, type: 'update attribute', payload: { 'data-y': 43 }, }]); }); }); describe('attribute on a web component', () => { it('gets recorded during mount', () => { var node = document.createElement('div'); ReactDOM.render(, node); var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); if (ReactDOMFeatureFlags.useCreateElement) { assertHistoryMatches([{ instanceID: inst._debugID, type: 'update attribute', payload: { className: 'rad' }, }, { instanceID: inst._debugID, type: 'update attribute', payload: { tabIndex: 42 }, }, { instanceID: inst._debugID, type: 'mount', payload: 'MY-COMPONENT', }]); } else { assertHistoryMatches([{ instanceID: inst._debugID, type: 'mount', payload: '', }]); } }); it('gets recorded during an update', () => { var node = document.createElement('div'); ReactDOM.render(, node); var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); ReactNativeOperationHistoryDevtool.clearHistory(); ReactDOM.render(, node); ReactDOM.render(, node); ReactDOM.render(, node); assertHistoryMatches([{ instanceID: inst._debugID, type: 'update attribute', payload: { className: 'rad' }, }, { instanceID: inst._debugID, type: 'update attribute', payload: { className: 'mad' }, }, { instanceID: inst._debugID, type: 'update attribute', payload: { tabIndex: 42 }, }, { instanceID: inst._debugID, type: 'remove attribute', payload: 'className', }, { instanceID: inst._debugID, type: 'update attribute', payload: { tabIndex: 43 }, }]); }); }); }); describe('replace text', () => { describe('text content', () => { it('gets recorded during an update from text content', () => { var node = document.createElement('div'); ReactDOM.render(
Hi.
, node); var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); ReactNativeOperationHistoryDevtool.clearHistory(); ReactDOM.render(
Bye.
, node); assertHistoryMatches([{ instanceID: inst._debugID, type: 'replace text', payload: 'Bye.', }]); }); it('gets recorded during an update from html', () => { var node = document.createElement('div'); ReactDOM.render(
, node); var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); ReactNativeOperationHistoryDevtool.clearHistory(); ReactDOM.render(
Bye.
, node); assertHistoryMatches([{ instanceID: inst._debugID, type: 'replace text', payload: 'Bye.', }]); }); it('gets recorded during an update from children', () => { var node = document.createElement('div'); ReactDOM.render(

, node); var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); ReactNativeOperationHistoryDevtool.clearHistory(); ReactDOM.render(
Bye.
, node); assertHistoryMatches([{ instanceID: inst._debugID, type: 'remove child', payload: {fromIndex: 0}, }, { instanceID: inst._debugID, type: 'remove child', payload: {fromIndex: 1}, }, { instanceID: inst._debugID, type: 'replace text', payload: 'Bye.', }]); }); it('gets ignored if new text is equal', () => { var node = document.createElement('div'); ReactDOM.render(
Hi.
, node); ReactNativeOperationHistoryDevtool.clearHistory(); ReactDOM.render(
Hi.
, node); assertHistoryMatches([]); }); }); describe('text node', () => { it('gets recorded during an update', () => { var node = document.createElement('div'); ReactDOM.render(
{'Hi.'}{42}
, node); var inst1 = ReactDOMComponentTree.getInstanceFromNode(node.firstChild.childNodes[0]); var inst2 = ReactDOMComponentTree.getInstanceFromNode(node.firstChild.childNodes[3]); ReactNativeOperationHistoryDevtool.clearHistory(); ReactDOM.render(
{'Bye.'}{43}
, node); assertHistoryMatches([{ instanceID: inst1._debugID, type: 'replace text', payload: 'Bye.', }, { instanceID: inst2._debugID, type: 'replace text', payload: '43', }]); }); it('gets ignored if new text is equal', () => { var node = document.createElement('div'); ReactDOM.render(
{'Hi.'}{42}
, node); ReactNativeOperationHistoryDevtool.clearHistory(); ReactDOM.render(
{'Hi.'}{42}
, node); assertHistoryMatches([]); }); }); }); describe('replace with', () => { it('gets recorded when composite renders to a different type', () => { var element; function Foo() { return element; } var node = document.createElement('div'); element =
; ReactDOM.render(, node); var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); ReactNativeOperationHistoryDevtool.clearHistory(); element = ; ReactDOM.render(, node); assertHistoryMatches([{ instanceID: inst._debugID, type: 'replace with', payload: 'SPAN', }]); }); it('gets ignored if the type has not changed', () => { var element; function Foo() { return element; } var node = document.createElement('div'); element =
; ReactDOM.render(, node); ReactNativeOperationHistoryDevtool.clearHistory(); element =
; ReactDOM.render(, node); assertHistoryMatches([]); }); }); describe('replace children', () => { it('gets recorded during an update from text content', () => { var node = document.createElement('div'); ReactDOM.render(
Hi.
, node); var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); ReactNativeOperationHistoryDevtool.clearHistory(); ReactDOM.render(
, node ); assertHistoryMatches([{ instanceID: inst._debugID, type: 'replace children', payload: 'Bye.', }]); }); it('gets recorded during an update from html', () => { var node = document.createElement('div'); ReactDOM.render(
, node ); var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); ReactNativeOperationHistoryDevtool.clearHistory(); ReactDOM.render(
, node ); assertHistoryMatches([{ instanceID: inst._debugID, type: 'replace children', payload: 'Bye.', }]); }); it('gets recorded during an update from children', () => { var node = document.createElement('div'); ReactDOM.render(

, node); var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); ReactNativeOperationHistoryDevtool.clearHistory(); ReactDOM.render(
, node ); assertHistoryMatches([{ instanceID: inst._debugID, type: 'remove child', payload: {fromIndex: 0}, }, { instanceID: inst._debugID, type: 'remove child', payload: {fromIndex: 1}, }, { instanceID: inst._debugID, type: 'replace children', payload: 'Hi.', }]); }); it('gets ignored if new html is equal', () => { var node = document.createElement('div'); ReactDOM.render(
, node ); ReactNativeOperationHistoryDevtool.clearHistory(); ReactDOM.render(
, node ); assertHistoryMatches([]); }); }); describe('insert child', () => { it('gets reported when a child is inserted', () => { var node = document.createElement('div'); ReactDOM.render(
, node); var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); ReactNativeOperationHistoryDevtool.clearHistory(); ReactDOM.render(

, node); assertHistoryMatches([{ instanceID: inst._debugID, type: 'insert child', payload: {toIndex: 1, content: 'P'}, }]); }); }); describe('move child', () => { it('gets reported when a child is inserted', () => { var node = document.createElement('div'); ReactDOM.render(

, node); var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); ReactNativeOperationHistoryDevtool.clearHistory(); ReactDOM.render(

, node); assertHistoryMatches([{ instanceID: inst._debugID, type: 'move child', payload: {fromIndex: 0, toIndex: 1}, }]); }); }); describe('remove child', () => { it('gets reported when a child is removed', () => { var node = document.createElement('div'); ReactDOM.render(

, node); var inst = ReactDOMComponentTree.getInstanceFromNode(node.firstChild); ReactNativeOperationHistoryDevtool.clearHistory(); ReactDOM.render(
, node); assertHistoryMatches([{ instanceID: inst._debugID, type: 'remove child', payload: {fromIndex: 1}, }]); }); }); });