diff --git a/src/isomorphic/__tests__/ReactDebugTool-test.js b/src/isomorphic/__tests__/ReactDebugTool-test.js index 103634b670..b90c6bd350 100644 --- a/src/isomorphic/__tests__/ReactDebugTool-test.js +++ b/src/isomorphic/__tests__/ReactDebugTool-test.js @@ -12,36 +12,6 @@ 'use strict'; describe('ReactDebugTool', () => { - - - - - - - - - - - - - /* - TODO: - * Make sure update and unmount paths are tested - * Make sure server rendering works the same way - * Enable all existing tests - * Remove ReactInstanceMap usage - */ - - - - - - - - - - - var React; var ReactDebugTool; var ReactDOM; @@ -59,7 +29,25 @@ describe('ReactDebugTool', () => { update(tree[debugID]); } - var devtool = { + function getTree(debugID, includeOwner) { + var item = tree[debugID]; + var result = { + isComposite: item.isComposite, + displayName: item.displayName, + }; + result.children = item.childDebugIDs.map(childDebugID => + getTree(childDebugID, includeOwner) + ); + if (item.text != null) { + result.text = item.text; + } + if (includeOwner && item.ownerDebugID) { + result.ownerDisplayName = tree[item.ownerDebugID].displayName; + } + return result; + } + + return { onSetIsComposite(debugID, isComposite) { updateTree(debugID, item => item.isComposite = isComposite); }, @@ -75,26 +63,10 @@ describe('ReactDebugTool', () => { onSetText(debugID, text) { updateTree(debugID, item => item.text = text); }, - getTree(debugID, includeOwner) { - var item = tree[debugID]; - var result = { - isComposite: item.isComposite, - displayName: item.displayName, - }; - result.children = item.childDebugIDs.map(childDebugID => - devtool.getTree(childDebugID, includeOwner) - ); - if (item.text != null) { - result.text = item.text; - } - if (includeOwner && item.ownerDebugID) { - result.ownerDisplayName = tree[item.ownerDebugID].displayName; - } - return result; - } + getTree(rootDebugID, includeOwner) { + return getTree(rootDebugID, includeOwner); + }, }; - - return devtool; } beforeEach(() => { @@ -113,436 +85,246 @@ describe('ReactDebugTool', () => { ReactDebugTool.removeDevtool(devtool); }); - function assertTreeMatches(element, expectedTree, includeOwner) { + function assertTreeMatches(pairs, includeOwner) { + if (!Array.isArray(pairs[0])) { + pairs = [pairs]; + } + + var currentElement; class Wrapper extends React.Component { render() { - return element; + return currentElement; } } var node = document.createElement('div'); - var rootPublicInstance = ReactDOM.render(, node); - var rootInstance = ReactInstanceMap.get(rootPublicInstance); - var actualTree = devtool.getTree( - rootInstance._renderedComponent._debugID, - includeOwner - ); - expect(actualTree).toEqual(expectedTree); + + pairs.forEach(([element, expectedTree]) => { + currentElement = element; + + var rootPublicInstance = ReactDOM.render(, node); + var rootInstance = ReactInstanceMap.get(rootPublicInstance); + var actualTree = devtool.getTree( + rootInstance._renderedComponent._debugID, + includeOwner + ); + expect(actualTree).toEqual(expectedTree); + }); } - it('uses displayName or Unknown for classic components', () => { - var Foo = React.createClass({ - render() { - return null; - }, - }); - Foo.displayName = 'Bar'; - var Baz = React.createClass({ - render() { - return null; - }, - }); - var Qux = React.createClass({ - render() { - return null; - }, - }); - delete Qux.displayName; - - var element =
; - var expectedTree = { - isComposite: false, - displayName: 'div', - children: [{ - isComposite: true, - displayName: 'Bar', - children: [], - }, { - isComposite: true, - displayName: 'Baz', - children: [], - }, { - isComposite: true, - displayName: 'Unknown', - children: [], - }], - }; - assertTreeMatches(element, expectedTree); - }); - - it('uses displayName, name, or ReactComponent for modern components', () => { - class Foo extends React.Component { - render() { - return null; - } - } - Foo.displayName = 'Bar'; - class Baz extends React.Component { - render() { - return null; - } - } - class Qux extends React.Component { - render() { - return null; - } - } - delete Qux.name; - - var element =
; - var expectedTree = { - isComposite: false, - displayName: 'div', - children: [{ - isComposite: true, - displayName: 'Bar', - children: [], - }, { - isComposite: true, - displayName: 'Baz', - children: [], - }, { - isComposite: true, - // Note: Ideally fallback name should be consistent (e.g. "Unknown") - displayName: 'ReactComponent', - children: [], - }], - }; - assertTreeMatches(element, expectedTree); - }); - - it('uses displayName, name, or Object for factory components', () => { - function Foo() { - return { + describe('mount', () => { + it('uses displayName or Unknown for classic components', () => { + var Foo = React.createClass({ render() { return null; }, - }; - } - Foo.displayName = 'Bar'; - function Baz() { - return { + }); + Foo.displayName = 'Bar'; + var Baz = React.createClass({ render() { return null; }, - }; - } - function Qux() { - return { + }); + var Qux = React.createClass({ render() { return null; }, - }; - } - delete Qux.name; + }); + delete Qux.displayName; - var element =
; - var expectedTree = { - isComposite: false, - displayName: 'div', - children: [{ - isComposite: true, - displayName: 'Bar', - children: [], - }, { - isComposite: true, - displayName: 'Baz', - children: [], - }, { - isComposite: true, - displayName: 'Unknown', - children: [], - }], - }; - assertTreeMatches(element, expectedTree); - }); - - it('uses displayName, name, or StatelessComponent for functional components', () => { - function Foo() { - return null; - } - Foo.displayName = 'Bar'; - function Baz() { - return null; - } - function Qux() { - return null; - } - delete Qux.name; - - var element =
; - var expectedTree = { - isComposite: false, - displayName: 'div', - children: [{ - isComposite: true, - displayName: 'Bar', - children: [], - }, { - isComposite: true, - displayName: 'Baz', - children: [], - }, { - isComposite: true, - displayName: 'Unknown', - children: [], - }], - }; - assertTreeMatches(element, expectedTree); - }); - - it('reports a native tree correctly', () => { - var element = ( -
-

- - Hi! - - Wow. -

-
-
- ); - var expectedTree = { - isComposite: false, - displayName: 'div', - children: [{ - isComposite: false, - displayName: 'p', - children: [{ - isComposite: false, - displayName: 'span', - children: [{ - isComposite: false, - displayName: '#text', - text: 'Hi!', - children: [], - }], - }, { - isComposite: false, - displayName: '#text', - text: 'Wow.', - children: [], - }], - }, { - isComposite: false, - displayName: 'hr', - children: [], - }], - }; - assertTreeMatches(element, expectedTree); - }); - - it('reports a simple tree with composites correctly', () => { - class Foo extends React.Component { - render() { - return
; - } - } - - var element = ; - var expectedTree = { - isComposite: true, - displayName: 'Foo', - children: [{ - isComposite: false, - displayName: 'div', - children: [], - }], - }; - assertTreeMatches(element, expectedTree); - }); - - it('reports a tree with composites correctly', () => { - var Qux = React.createClass({ - render() { - return null; - }, - }); - function Foo() { - return { - render() { - return ; - }, - }; - } - function Bar({children}) { - return

{children}

; - } - class Baz extends React.Component { - render() { - return ( -
- - - Hi, - Mom - - Click me. -
- ); - } - } - - var element = ; - var expectedTree = { - isComposite: true, - displayName: 'Baz', - children: [{ + var element =
; + var tree = { isComposite: false, displayName: 'div', children: [{ - isComposite: true, - displayName: 'Foo', - children: [{ - isComposite: true, - displayName: 'Qux', - children: [], - }], - }, { isComposite: true, displayName: 'Bar', + children: [], + }, { + isComposite: true, + displayName: 'Baz', + children: [], + }, { + isComposite: true, + displayName: 'Unknown', + children: [], + }], + }; + assertTreeMatches([element, tree]); + }); + + it('uses displayName, name, or ReactComponent for modern components', () => { + class Foo extends React.Component { + render() { + return null; + } + } + Foo.displayName = 'Bar'; + class Baz extends React.Component { + render() { + return null; + } + } + class Qux extends React.Component { + render() { + return null; + } + } + delete Qux.name; + + var element =
; + var tree = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: true, + displayName: 'Bar', + children: [], + }, { + isComposite: true, + displayName: 'Baz', + children: [], + }, { + isComposite: true, + // Note: Ideally fallback name should be consistent (e.g. "Unknown") + displayName: 'ReactComponent', + children: [], + }], + }; + assertTreeMatches([element, tree]); + }); + + it('uses displayName, name, or Object for factory components', () => { + function Foo() { + return { + render() { + return null; + }, + }; + } + Foo.displayName = 'Bar'; + function Baz() { + return { + render() { + return null; + }, + }; + } + function Qux() { + return { + render() { + return null; + }, + }; + } + delete Qux.name; + + var element =
; + var tree = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: true, + displayName: 'Bar', + children: [], + }, { + isComposite: true, + displayName: 'Baz', + children: [], + }, { + isComposite: true, + displayName: 'Unknown', + children: [], + }], + }; + assertTreeMatches([element, tree]); + }); + + it('uses displayName, name, or StatelessComponent for functional components', () => { + function Foo() { + return null; + } + Foo.displayName = 'Bar'; + function Baz() { + return null; + } + function Qux() { + return null; + } + delete Qux.name; + + var element =
; + var tree = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: true, + displayName: 'Bar', + children: [], + }, { + isComposite: true, + displayName: 'Baz', + children: [], + }, { + isComposite: true, + displayName: 'Unknown', + children: [], + }], + }; + assertTreeMatches([element, tree]); + }); + + it('reports a native tree correctly', () => { + var element = ( +
+

+ + Hi! + + Wow. +

+
+
+ ); + var tree = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: 'p', children: [{ isComposite: false, - displayName: 'h1', + displayName: 'span', children: [{ - isComposite: false, - displayName: 'span', - children: [{ - isComposite: false, - displayName: '#text', - text: 'Hi,', - children: [], - }], - }, { isComposite: false, displayName: '#text', - text: 'Mom', + text: 'Hi!', children: [], }], + }, { + isComposite: false, + displayName: '#text', + text: 'Wow.', + children: [], }], }, { isComposite: false, - displayName: 'a', - children: [{ - isComposite: false, - displayName: '#text', - text: 'Click me.', - children: [], - }], + displayName: 'hr', + children: [], }], - }], - }; - assertTreeMatches(element, expectedTree); - }); + }; + assertTreeMatches([element, tree]); + }); - it('ignores null children', () => { - class Foo extends React.Component { - render() { - return null; + it('reports a simple tree with composites correctly', () => { + class Foo extends React.Component { + render() { + return
; + } } - } - var element = ; - var expectedTree = { - isComposite: true, - displayName: 'Foo', - children: [], - }; - assertTreeMatches(element, expectedTree); - }); - it('ignores false children', () => { - class Foo extends React.Component { - render() { - return false; - } - } - var element = ; - var expectedTree = { - isComposite: true, - displayName: 'Foo', - children: [], - }; - assertTreeMatches(element, expectedTree); - }); - - it('reports text nodes as children', () => { - var element =
{'1'}{2}
; - var expectedTree = { - isComposite: false, - displayName: 'div', - children: [{ - isComposite: false, - displayName: '#text', - text: '1', - children: [], - }, { - isComposite: false, - displayName: '#text', - text: 2, - children: [], - }], - }; - assertTreeMatches(element, expectedTree); - }); - - it('reports a single text node as a child', () => { - var element =
{'1'}
; - var expectedTree = { - isComposite: false, - displayName: 'div', - children: [{ - isComposite: false, - displayName: '#text', - text: '1', - children: [], - }], - }; - assertTreeMatches(element, expectedTree); - }); - - it('reports a single number node as a child', () => { - var element =
{42}
; - var expectedTree = { - isComposite: false, - displayName: 'div', - children: [{ - isComposite: false, - displayName: '#text', - text: 42, - children: [], - }], - }; - assertTreeMatches(element, expectedTree); - }); - - it('skips empty nodes for multiple children', () => { - function Foo() { - return
; - } - var element = ( -
- {'hi'} - {false} - {42} - {null} - -
- ); - var expectedTree = { - isComposite: false, - displayName: 'div', - children: [{ - isComposite: false, - displayName: '#text', - text: 'hi', - children: [], - }, { - isComposite: false, - displayName: '#text', - text: 42, - children: [], - }, { + var element = ; + var tree = { isComposite: true, displayName: 'Foo', children: [{ @@ -550,9 +332,1334 @@ describe('ReactDebugTool', () => { displayName: 'div', children: [], }], - }], - }; - assertTreeMatches(element, expectedTree); + }; + assertTreeMatches([element, tree]); + }); + + it('reports a tree with composites correctly', () => { + var Qux = React.createClass({ + render() { + return null; + }, + }); + function Foo() { + return { + render() { + return ; + }, + }; + } + function Bar({children}) { + return

{children}

; + } + class Baz extends React.Component { + render() { + return ( +
+ + + Hi, + Mom + + Click me. +
+ ); + } + } + + var element = ; + var tree = { + isComposite: true, + displayName: 'Baz', + children: [{ + isComposite: false, + displayName: 'div', + children: [{ + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: true, + displayName: 'Qux', + children: [], + }], + }, { + isComposite: true, + displayName: 'Bar', + children: [{ + isComposite: false, + displayName: 'h1', + children: [{ + isComposite: false, + displayName: 'span', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi,', + children: [], + }], + }, { + isComposite: false, + displayName: '#text', + text: 'Mom', + children: [], + }], + }], + }, { + isComposite: false, + displayName: 'a', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Click me.', + children: [], + }], + }], + }], + }; + assertTreeMatches([element, tree]); + }); + + it('ignores null children', () => { + class Foo extends React.Component { + render() { + return null; + } + } + var element = ; + var tree = { + isComposite: true, + displayName: 'Foo', + children: [], + }; + assertTreeMatches([element, tree]); + }); + + it('ignores false children', () => { + class Foo extends React.Component { + render() { + return false; + } + } + var element = ; + var tree = { + isComposite: true, + displayName: 'Foo', + children: [], + }; + assertTreeMatches([element, tree]); + }); + + it('reports text nodes as children', () => { + var element =
{'1'}{2}
; + var tree = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: '1', + children: [], + }, { + isComposite: false, + displayName: '#text', + text: '2', + children: [], + }], + }; + assertTreeMatches([element, tree]); + }); + + it('reports a single text node as a child', () => { + var element =
{'1'}
; + var tree = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: '1', + children: [], + }], + }; + assertTreeMatches([element, tree]); + }); + + it('reports a single number node as a child', () => { + var element =
{42}
; + var tree = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: '42', + children: [], + }], + }; + assertTreeMatches([element, tree]); + }); + + it('reports a zero as a child', () => { + var element =
{0}
; + var tree = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: '0', + children: [], + }], + }; + assertTreeMatches([element, tree]); + }); + + it('skips empty nodes for multiple children', () => { + function Foo() { + return
; + } + var element = ( +
+ {'hi'} + {false} + {42} + {null} + +
+ ); + var tree = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'hi', + children: [], + }, { + isComposite: false, + displayName: '#text', + text: '42', + children: [], + }, { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }], + }; + assertTreeMatches([element, tree]); + }); + }); + + describe('update', () => { + describe('native component', () => { + it('updates text of a single text child', () => { + var elementBefore =
Hi.
; + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }], + }; + + var elementAfter =
Bye.
; + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Bye.', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from no children to a single text child', () => { + var elementBefore =
; + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [], + }; + + var elementAfter =
Hi.
; + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from a single text child to no children', () => { + var elementBefore =
Hi.
; + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }], + }; + + var elementAfter =
; + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from no children to multiple text children', () => { + var elementBefore =
; + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [], + }; + + var elementAfter =
{'Hi.'}{'Bye.'}
; + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }, { + isComposite: false, + displayName: '#text', + text: 'Bye.', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from multiple text children to no children', () => { + var elementBefore =
{'Hi.'}{'Bye.'}
; + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }, { + isComposite: false, + displayName: '#text', + text: 'Bye.', + children: [], + }], + }; + + var elementAfter =
; + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from one text child to multiple text children', () => { + var elementBefore =
Hi.
; + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }], + }; + + var elementAfter =
{'Hi.'}{'Bye.'}
; + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }, { + isComposite: false, + displayName: '#text', + text: 'Bye.', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from multiple text children to one text child', () => { + var elementBefore =
{'Hi.'}{'Bye.'}
; + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }, { + isComposite: false, + displayName: '#text', + text: 'Bye.', + children: [], + }], + }; + + var elementAfter =
Hi.
; + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }], + }; + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates text nodes when reordering', () => { + var elementBefore =
{'Hi.'}{'Bye.'}
; + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }, { + isComposite: false, + displayName: '#text', + text: 'Bye.', + children: [], + }], + }; + + var elementAfter =
{'Bye.'}{'Hi.'}
; + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Bye.', + children: [], + }, { + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }], + }; + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates native nodes when reordering with keys', () => { + var elementBefore = ( +
+
Hi.
+
Bye.
+
+ ); + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }], + }, { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Bye.', + children: [], + }], + }], + }; + + var elementAfter = ( +
+
Bye.
+
Hi.
+
+ ); + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Bye.', + children: [], + }], + }, { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates native nodes when reordering without keys', () => { + var elementBefore = ( +
+
Hi.
+
Bye.
+
+ ); + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }], + }, { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Bye.', + children: [], + }], + }], + }; + + var elementAfter = ( +
+
Bye.
+
Hi.
+
+ ); + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Bye.', + children: [], + }], + }, { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'Hi.', + children: [], + }], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates a single composite child of a different type', () => { + function Foo() { + return null; + } + + function Bar() { + return null; + } + + var elementBefore =
; + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: true, + displayName: 'Foo', + children: [], + }], + }; + + var elementAfter =
; + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: true, + displayName: 'Bar', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates a single composite child of the same type', () => { + function Foo({ children }) { + return children; + } + + var elementBefore =
; + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }], + }; + + var elementAfter =
; + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'span', + children: [], + }], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from no children to a single composite child', () => { + function Foo() { + return null; + } + + var elementBefore =
; + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [], + }; + + var elementAfter =
; + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: true, + displayName: 'Foo', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from a single composite child to no children', () => { + function Foo() { + return null; + } + + var elementBefore =
; + var treeBefore = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: true, + displayName: 'Foo', + children: [], + }], + }; + + var elementAfter =
; + var treeAfter = { + isComposite: false, + displayName: 'div', + children: [], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates mixed children', () => { + function Foo() { + return
; + } + var element1 = ( +
+ {'hi'} + {false} + {42} + {null} + +
+ ); + var tree1 = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: false, + displayName: '#text', + text: 'hi', + children: [], + }, { + isComposite: false, + displayName: '#text', + text: '42', + children: [], + }, { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }], + }; + + var element2 = ( +
+ + {false} + {'hi'} + {null} +
+ ); + var tree2 = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }, { + isComposite: false, + displayName: '#text', + text: 'hi', + children: [], + }], + }; + + var element3 = ( +
+ +
+ ); + var tree3 = { + isComposite: false, + displayName: 'div', + children: [{ + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }], + }; + + assertTreeMatches([ + [element1, tree1], + [element2, tree2], + [element3, tree3], + ]); + }); + }); + + describe('functional component', () => { + it('updates with a native child', () => { + function Foo({ children }) { + return children; + } + + var elementBefore =
; + var treeBefore = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }; + + var elementAfter = ; + var treeAfter = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'span', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from null to a native child', () => { + function Foo({ children }) { + return children; + } + + var elementBefore = {null}; + var treeBefore = { + isComposite: true, + displayName: 'Foo', + children: [], + }; + + var elementAfter =
; + var treeAfter = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from a native child to null', () => { + function Foo({ children }) { + return children; + } + + var elementBefore =
; + var treeBefore = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }; + + var elementAfter = {null}; + var treeAfter = { + isComposite: true, + displayName: 'Foo', + children: [], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from a native child to a composite child', () => { + function Bar() { + return null; + } + + function Foo({ children }) { + return children; + } + + var elementBefore =
; + var treeBefore = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }; + + var elementAfter = ; + var treeAfter = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: true, + displayName: 'Bar', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from a composite child to a native child', () => { + function Bar() { + return null; + } + + function Foo({ children }) { + return children; + } + + var elementBefore = ; + var treeBefore = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: true, + displayName: 'Bar', + children: [], + }], + }; + + var elementAfter =
; + var treeAfter = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from null to a composite child', () => { + function Bar() { + return null; + } + + function Foo({ children }) { + return children; + } + + var elementBefore = {null}; + var treeBefore = { + isComposite: true, + displayName: 'Foo', + children: [], + }; + + var elementAfter = ; + var treeAfter = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: true, + displayName: 'Bar', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from a composite child to null', () => { + function Bar() { + return null; + } + + function Foo({ children }) { + return children; + } + + var elementBefore = ; + var treeBefore = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: true, + displayName: 'Bar', + children: [], + }], + }; + + var elementAfter = {null}; + var treeAfter = { + isComposite: true, + displayName: 'Foo', + children: [], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + }); + + describe('class component', () => { + it('updates with a native child', () => { + var Foo = React.createClass({ + render() { + return this.props.children; + }, + }); + + var elementBefore =
; + var treeBefore = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }; + + var elementAfter = ; + var treeAfter = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'span', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from null to a native child', () => { + var Foo = React.createClass({ + render() { + return this.props.children; + }, + }); + + var elementBefore = {null}; + var treeBefore = { + isComposite: true, + displayName: 'Foo', + children: [], + }; + + var elementAfter =
; + var treeAfter = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from a native child to null', () => { + var Foo = React.createClass({ + render() { + return this.props.children; + }, + }); + + var elementBefore =
; + var treeBefore = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }; + + var elementAfter = {null}; + var treeAfter = { + isComposite: true, + displayName: 'Foo', + children: [], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from a native child to a composite child', () => { + var Bar = React.createClass({ + render() { + return null; + }, + }); + + var Foo = React.createClass({ + render() { + return this.props.children; + }, + }); + + var elementBefore =
; + var treeBefore = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }; + + var elementAfter = ; + var treeAfter = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: true, + displayName: 'Bar', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from a composite child to a native child', () => { + var Bar = React.createClass({ + render() { + return null; + }, + }); + + var Foo = React.createClass({ + render() { + return this.props.children; + }, + }); + + var elementBefore = ; + var treeBefore = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: true, + displayName: 'Bar', + children: [], + }], + }; + + var elementAfter =
; + var treeAfter = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: false, + displayName: 'div', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from null to a composite child', () => { + var Bar = React.createClass({ + render() { + return null; + }, + }); + + var Foo = React.createClass({ + render() { + return this.props.children; + }, + }); + + var elementBefore = {null}; + var treeBefore = { + isComposite: true, + displayName: 'Foo', + children: [], + }; + + var elementAfter = ; + var treeAfter = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: true, + displayName: 'Bar', + children: [], + }], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + + it('updates from a composite child to null', () => { + var Bar = React.createClass({ + render() { + return null; + }, + }); + + var Foo = React.createClass({ + render() { + return this.props.children; + }, + }); + + var elementBefore = ; + var treeBefore = { + isComposite: true, + displayName: 'Foo', + children: [{ + isComposite: true, + displayName: 'Bar', + children: [], + }], + }; + + var elementAfter = {null}; + var treeAfter = { + isComposite: true, + displayName: 'Foo', + children: [], + }; + + assertTreeMatches([ + [elementBefore, treeBefore], + [elementAfter, treeAfter], + ]); + }); + }); }); it('tracks owner correctly', () => { @@ -568,7 +1675,7 @@ describe('ReactDebugTool', () => { // Note that owner is not calculated for text nodes // because they are not created from real elements. var element =
; - var expectedTree = { + var tree = { isComposite: false, displayName: 'article', children: [{ @@ -602,6 +1709,6 @@ describe('ReactDebugTool', () => { }], }], }; - assertTreeMatches(element, expectedTree, true); + assertTreeMatches([element, tree], true); }); }); diff --git a/src/renderers/dom/shared/ReactDOMComponent.js b/src/renderers/dom/shared/ReactDOMComponent.js index 9a47a17637..681efb7cf2 100644 --- a/src/renderers/dom/shared/ReactDOMComponent.js +++ b/src/renderers/dom/shared/ReactDOMComponent.js @@ -763,7 +763,7 @@ ReactDOMComponent.Mixin = { ReactInstrumentation.debugTool.onSetIsComposite(inlinedTextDebugID, false); ReactInstrumentation.debugTool.onSetDisplayName(inlinedTextDebugID, '#text'); ReactInstrumentation.debugTool.onSetChildren(inlinedTextDebugID, []); - ReactInstrumentation.debugTool.onSetText(inlinedTextDebugID, contentToUse); + ReactInstrumentation.debugTool.onSetText(inlinedTextDebugID, '' + contentToUse); } DOMLazyTree.queueText(lazyTree, contentToUse); } else if (childrenToUse != null) { @@ -1012,11 +1012,22 @@ 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__) { + var inlinedTextDebugID = this._debugID + '#text'; + ReactInstrumentation.debugTool.onSetChildren(this._debugID, [inlinedTextDebugID]); + ReactInstrumentation.debugTool.onSetIsComposite(inlinedTextDebugID, false); + ReactInstrumentation.debugTool.onSetDisplayName(inlinedTextDebugID, '#text'); + ReactInstrumentation.debugTool.onSetChildren(inlinedTextDebugID, []); + ReactInstrumentation.debugTool.onSetText(inlinedTextDebugID, '' + nextContent); + } } } else if (nextHtml != null) { if (lastHtml !== nextHtml) { diff --git a/src/renderers/dom/shared/ReactDOMTextComponent.js b/src/renderers/dom/shared/ReactDOMTextComponent.js index 9a69c7f977..5041706287 100644 --- a/src/renderers/dom/shared/ReactDOMTextComponent.js +++ b/src/renderers/dom/shared/ReactDOMTextComponent.js @@ -14,6 +14,7 @@ var DOMChildrenOperations = require('DOMChildrenOperations'); var DOMLazyTree = require('DOMLazyTree'); var ReactDOMComponentTree = require('ReactDOMComponentTree'); +var ReactInstrumentation = require('ReactInstrumentation'); var ReactPerf = require('ReactPerf'); var escapeTextContentForBrowser = require('escapeTextContentForBrowser'); @@ -67,6 +68,8 @@ Object.assign(ReactDOMTextComponent.prototype, { context ) { if (__DEV__) { + ReactInstrumentation.debugTool.onSetText(this._debugID, this._stringText); + var parentInfo; if (nativeParent != null) { parentInfo = nativeParent._ancestorInfo; @@ -140,6 +143,10 @@ Object.assign(ReactDOMTextComponent.prototype, { commentNodes[1], nextStringText ); + + if (__DEV__) { + ReactInstrumentation.debugTool.onSetText(this._debugID, nextStringText); + } } } }, diff --git a/src/renderers/shared/reconciler/ReactCompositeComponent.js b/src/renderers/shared/reconciler/ReactCompositeComponent.js index ade9291548..103e21d89e 100644 --- a/src/renderers/shared/reconciler/ReactCompositeComponent.js +++ b/src/renderers/shared/reconciler/ReactCompositeComponent.js @@ -378,12 +378,12 @@ var ReactCompositeComponentMixin = { renderedElement ); if (__DEV__) { - if (this._renderedComponent._debugID) { - ReactInstrumentation.debugTool.onSetChildren( - this._debugID, + ReactInstrumentation.debugTool.onSetChildren( + this._debugID, + this._renderedNodeType === ReactNodeTypes.EMPTY ? + [] : [this._renderedComponent._debugID] - ); - } + ); } var markup = ReactReconciler.mountComponent( @@ -861,6 +861,15 @@ var ReactCompositeComponentMixin = { this._renderedComponent = this._instantiateReactComponent( nextRenderedElement ); + if (__DEV__) { + ReactInstrumentation.debugTool.onSetChildren( + this._debugID, + this._renderedNodeType === ReactNodeTypes.EMPTY ? + [] : + [this._renderedComponent._debugID] + ); + } + var nextMarkup = ReactReconciler.mountComponent( this._renderedComponent, transaction, diff --git a/src/renderers/shared/reconciler/ReactMultiChild.js b/src/renderers/shared/reconciler/ReactMultiChild.js index 6ca70e95a6..2a7ccb70e3 100644 --- a/src/renderers/shared/reconciler/ReactMultiChild.js +++ b/src/renderers/shared/reconciler/ReactMultiChild.js @@ -216,13 +216,6 @@ var ReactMultiChild = { ); this._renderedChildren = children; - if (__DEV__) { - ReactInstrumentation.debugTool.onSetChildren( - this._debugID, - Object.keys(children).map(key => children[key]._debugID) - ); - } - var mountImages = []; var index = 0; for (var name in children) { @@ -239,6 +232,16 @@ var ReactMultiChild = { mountImages.push(mountImage); } } + + if (__DEV__) { + ReactInstrumentation.debugTool.onSetChildren( + this._debugID, + children ? + Object.keys(children).map(key => children[key]._debugID) : + [] + ); + } + return mountImages; }, @@ -366,6 +369,15 @@ var ReactMultiChild = { processQueue(this, updates); } this._renderedChildren = nextChildren; + + if (__DEV__) { + ReactInstrumentation.debugTool.onSetChildren( + this._debugID, + nextChildren ? + Object.keys(nextChildren).map(key => nextChildren[key]._debugID) : + [] + ); + } }, /** diff --git a/src/renderers/shared/reconciler/instantiateReactComponent.js b/src/renderers/shared/reconciler/instantiateReactComponent.js index e76d1c8bd0..f5cf2d95d5 100644 --- a/src/renderers/shared/reconciler/instantiateReactComponent.js +++ b/src/renderers/shared/reconciler/instantiateReactComponent.js @@ -42,18 +42,18 @@ function getDeclarationErrorAddendum(owner) { } function getDisplayName(instance) { - var element = instance._currentElement; - if (element == null) { - return '#empty'; - } else if (typeof element === 'string' || typeof element === 'number') { - return '#text'; - } else if (typeof element.type === 'string') { - return element.type; - } else if (instance.getName) { - return instance.getName() || 'Unknown'; - } else { - return element.type.displayName || element.type.name || 'Unknown'; - } + var element = instance._currentElement; + if (element == null) { + return '#empty'; + } else if (typeof element === 'string' || typeof element === 'number') { + return '#text'; + } else if (typeof element.type === 'string') { + return element.type; + } else if (instance.getName) { + return instance.getName() || 'Unknown'; + } else { + return element.type.displayName || element.type.name || 'Unknown'; + } } /** @@ -85,7 +85,6 @@ function instantiateReactComponent(node) { var instance; var isEmpty = false; - var isText = false; var isComposite = false; if (node === null || node === false) { @@ -115,7 +114,6 @@ function instantiateReactComponent(node) { instance = new ReactCompositeComponentWrapper(element); } } else if (typeof node === 'string' || typeof node === 'number') { - isText = true; instance = ReactNativeComponent.createInstanceForText(node); } else { invariant( @@ -156,9 +154,6 @@ function instantiateReactComponent(node) { if (owner) { ReactInstrumentation.debugTool.onSetOwner(instance._debugID, owner._debugID); } - if (isText) { - ReactInstrumentation.debugTool.onSetText(instance._debugID, node); - } } // Internal instances should fully constructed at this point, so they should