. See Foo > table > #text.'
);
});
diff --git a/src/renderers/dom/shared/__tests__/ReactDOMTextComponent-test.js b/src/renderers/dom/shared/__tests__/ReactDOMTextComponent-test.js
index c15b5174d10b4..5c3e9a2e62ddd 100644
--- a/src/renderers/dom/shared/__tests__/ReactDOMTextComponent-test.js
+++ b/src/renderers/dom/shared/__tests__/ReactDOMTextComponent-test.js
@@ -13,26 +13,96 @@
var React;
var ReactDOM;
+var ReactDOMServer;
describe('ReactDOMTextComponent', function() {
beforeEach(function() {
React = require('React');
ReactDOM = require('ReactDOM');
+ ReactDOMServer = require('ReactDOMServer');
});
it('updates a mounted text component in place', function() {
var el = document.createElement('div');
- var inst = ReactDOM.render({'foo'}{'bar'}
, el);
-
- var foo = ReactDOM.findDOMNode(inst).children[0];
- var bar = ReactDOM.findDOMNode(inst).children[1];
- expect(foo.tagName).toBe('SPAN');
- expect(bar.tagName).toBe('SPAN');
-
- inst = ReactDOM.render({'baz'}{'qux'}
, el);
- // After the update, the spans should have stayed in place (as opposed to
- // getting unmounted and remounted)
- expect(ReactDOM.findDOMNode(inst).children[0]).toBe(foo);
- expect(ReactDOM.findDOMNode(inst).children[1]).toBe(bar);
+ var inst = ReactDOM.render({'foo'}{'bar'}
, el);
+
+ var foo = ReactDOM.findDOMNode(inst).childNodes[2];
+ var bar = ReactDOM.findDOMNode(inst).childNodes[5];
+ expect(foo.data).toBe('foo');
+ expect(bar.data).toBe('bar');
+
+ inst = ReactDOM.render({'baz'}{'qux'}
, el);
+ // After the update, the text nodes should have stayed in place (as opposed
+ // to getting unmounted and remounted)
+ expect(ReactDOM.findDOMNode(inst).childNodes[2]).toBe(foo);
+ expect(ReactDOM.findDOMNode(inst).childNodes[5]).toBe(bar);
+ expect(foo.data).toBe('baz');
+ expect(bar.data).toBe('qux');
+ });
+
+ it('can be toggled in and out of the markup', function() {
+ var el = document.createElement('div');
+ var inst = ReactDOM.render(, el);
+
+ var container = ReactDOM.findDOMNode(inst);
+ var childDiv = container.childNodes[3];
+ var childNodes;
+
+ inst = ReactDOM.render(, el);
+ container = ReactDOM.findDOMNode(inst);
+ childNodes = container.childNodes;
+ expect(childNodes.length).toBe(1);
+ expect(childNodes[0]).toBe(childDiv);
+
+ inst = ReactDOM.render(, el);
+ container = ReactDOM.findDOMNode(inst);
+ childNodes = container.childNodes;
+ expect(childNodes.length).toBe(7);
+ expect(childNodes[1].data).toBe('foo');
+ expect(childNodes[3]).toBe(childDiv);
+ expect(childNodes[5].data).toBe('bar');
+ });
+
+ it('can reconcile text merged by Node.normalize()', function() {
+ var el = document.createElement('div');
+ var inst = ReactDOM.render({'foo'}{'bar'}{'baz'}
, el);
+
+ var container = ReactDOM.findDOMNode(inst);
+ container.normalize();
+
+ inst = ReactDOM.render({'bar'}{'baz'}{'qux'}
, el);
+ container = ReactDOM.findDOMNode(inst);
+ expect(container.textContent).toBe('barbazqux');
+ });
+
+ it('can reconcile text from pre-rendered markup', function() {
+ var el = document.createElement('div');
+ var reactEl = {'foo'}{'bar'}{'baz'}
;
+ el.innerHTML = ReactDOMServer.renderToString(reactEl);
+
+ ReactDOM.render(reactEl, el);
+ expect(el.textContent).toBe('foobarbaz');
+
+ reactEl = {''}{''}{''}
;
+ el.innerHTML = ReactDOMServer.renderToString(reactEl);
+
+ ReactDOM.render(reactEl, el);
+ expect(el.textContent).toBe('');
+ });
+
+ it('can reconcile text arbitrarily split into multiple nodes', function() {
+ var el = document.createElement('div');
+ var inst = ReactDOM.render({'foobarbaz'}
, el);
+
+ var container = ReactDOM.findDOMNode(inst);
+ var childNodes = container.childNodes;
+ var textNode = childNodes[2];
+ textNode.textContent = 'foo';
+ container.insertBefore(document.createTextNode('bar'), childNodes[3]);
+ container.insertBefore(document.createTextNode('baz'), childNodes[3]);
+
+ inst = ReactDOM.render({'barbazqux'}
, el);
+ container = ReactDOM.findDOMNode(inst);
+ expect(container.textContent).toBe('barbazqux');
});
});
diff --git a/src/renderers/shared/reconciler/__tests__/ReactMultiChildText-test.js b/src/renderers/shared/reconciler/__tests__/ReactMultiChildText-test.js
index f5129ff417b4e..bb409c30ab0ed 100644
--- a/src/renderers/shared/reconciler/__tests__/ReactMultiChildText-test.js
+++ b/src/renderers/shared/reconciler/__tests__/ReactMultiChildText-test.js
@@ -49,24 +49,40 @@ var expectChildren = function(d, children) {
expect(textNode.data).toBe('' + children);
}
} else {
- expect(outerNode.childNodes.length).toBe(children.length);
+ var openingCommentNode;
+ var closingCommentNode;
+ var mountIndex = 0;
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (typeof child === 'string') {
- textNode = outerNode.childNodes[i].firstChild;
+ openingCommentNode = outerNode.childNodes[mountIndex];
+
+ expect(openingCommentNode.nodeType).toBe(8);
+ expect(openingCommentNode.nodeValue).toMatch(' react-text: [0-9]+ ');
if (child === '') {
- expect(textNode).toBe(null);
+ textNode = null;
+ closingCommentNode = openingCommentNode.nextSibling;
+ mountIndex += 2;
} else {
- expect(textNode).not.toBe(null);
+ textNode = openingCommentNode.nextSibling;
+ closingCommentNode = textNode.nextSibling;
+ mountIndex += 3;
+ }
+
+ if (textNode) {
expect(textNode.nodeType).toBe(3);
expect(textNode.data).toBe('' + child);
}
+
+ expect(closingCommentNode.nodeType).toBe(8);
+ expect(closingCommentNode.nodeValue).toBe(' /react-text ');
} else {
- var elementDOMNode = outerNode.childNodes[i];
+ var elementDOMNode = outerNode.childNodes[mountIndex];
expect(elementDOMNode.tagName).toBe('DIV');
+ mountIndex++;
}
}
}
@@ -194,4 +210,38 @@ describe('ReactMultiChildText', function() {
ReactTestUtils.renderIntoDocument({['A', 'B']}
);
}).not.toThrow();
});
+
+ it('should reorder keyed text nodes', function() {
+ spyOn(console, 'error');
+
+ var container = document.createElement('div');
+ ReactDOM.render(
+ {new Map([['a', 'alpha'], ['b', 'beta']])}
,
+ container
+ );
+
+ var childNodes = container.firstChild.childNodes;
+ var alpha1 = childNodes[0];
+ var alpha2 = childNodes[1];
+ var alpha3 = childNodes[2];
+ var beta1 = childNodes[3];
+ var beta2 = childNodes[4];
+ var beta3 = childNodes[5];
+
+ ReactDOM.render(
+ {new Map([['b', 'beta'], ['a', 'alpha']])}
,
+ container
+ );
+
+ childNodes = container.firstChild.childNodes;
+ expect(childNodes[0]).toBe(beta1);
+ expect(childNodes[1]).toBe(beta2);
+ expect(childNodes[2]).toBe(beta3);
+ expect(childNodes[3]).toBe(alpha1);
+ expect(childNodes[4]).toBe(alpha2);
+ expect(childNodes[5]).toBe(alpha3);
+
+ // Using Maps as children gives a single warning
+ expect(console.error.calls.length).toBe(1);
+ });
});
diff --git a/src/test/ReactDefaultPerf.js b/src/test/ReactDefaultPerf.js
index 185541172d1f9..9671e3d4f4529 100644
--- a/src/test/ReactDefaultPerf.js
+++ b/src/test/ReactDefaultPerf.js
@@ -27,7 +27,7 @@ function addValue(obj, key, val) {
obj[key] = (obj[key] || 0) + val;
}
-// Composites don't have any built-in ID: we have to make our own
+// Composite/text components don't have any built-in ID: we have to make our own
var compositeIDMap;
var compositeIDCounter = 17000;
function getIDOfComposite(inst) {
@@ -43,6 +43,14 @@ function getIDOfComposite(inst) {
}
}
+function getID(inst) {
+ if (inst.hasOwnProperty('_rootNodeID')) {
+ return inst._rootNodeID;
+ } else {
+ return getIDOfComposite(inst);
+ }
+}
+
var ReactDefaultPerf = {
_allMeasurements: [], // last item in the list is the current one
_mountStack: [0],
@@ -224,8 +232,10 @@ var ReactDefaultPerf = {
} else if (fnName === 'replaceNodeWithMarkup') {
// Old node is already unmounted; can't get its instance
id = ReactDOMComponentTree.getInstanceFromNode(args[1].node)._rootNodeID;
+ } else if (fnName === 'replaceDelimitedText') {
+ id = getID(ReactDOMComponentTree.getInstanceFromNode(args[1]));
} else if (typeof id === 'object') {
- id = ReactDOMComponentTree.getInstanceFromNode(args[0])._rootNodeID;
+ id = getID(ReactDOMComponentTree.getInstanceFromNode(args[0]));
}
ReactDefaultPerf._recordWrite(
id,
@@ -291,7 +301,7 @@ var ReactDefaultPerf = {
fnName === 'receiveComponent')) {
rv = func.apply(this, args);
- entry.hierarchy[this._rootNodeID] =
+ entry.hierarchy[getID(this)] =
ReactDefaultPerf._compositeStack.slice();
return rv;
} else {
diff --git a/src/test/ReactDefaultPerfAnalysis.js b/src/test/ReactDefaultPerfAnalysis.js
index ab707e298a756..80452c4ddfe37 100644
--- a/src/test/ReactDefaultPerfAnalysis.js
+++ b/src/test/ReactDefaultPerfAnalysis.js
@@ -27,6 +27,7 @@ var DOM_OPERATION_TYPES = {
'deleteValueForProperty': 'remove attribute',
'setValueForStyles': 'update styles',
'replaceNodeWithMarkup': 'replace',
+ 'replaceDelimitedText': 'replace',
'updateTextContent': 'set textContent',
};