Use incrementing numerical IDs to identify DOM components

This commit is contained in:
Ben Alpert committed 2015-11-04 11:39:44 -08:00
1 parent f06ddf7905
commit e48c8be556
14 files changed
+136 -245

No files matched your search

@@ -77,7 +77,7 @@ function precacheChildNodes(inst, node) {
continue;
}
var childInst = children[name];
var childID = childInst._rootNodeID;
var childID = getRenderedNativeOrTextFromComponent(childInst)._domID;
if (childID == null) {
// We're currently unmounting this child in ReactMultiChild; skip it.
continue;
@@ -85,7 +85,7 @@ function precacheChildNodes(inst, node) {
// We assume the child nodes are in the same order as the child instances.
for (; childNode !== null; childNode = childNode.nextSibling) {
if (childNode.nodeType === 1 &&
childNode.getAttribute(ATTR_NAME) === childID) {
childNode.getAttribute(ATTR_NAME) === String(childID)) {
precacheNode(childInst, childNode);
continue outer;
}
+38 -112
View File
@@ -11,7 +11,6 @@
'use strict';
var ClientReactRootIndex = require('ClientReactRootIndex');
var DOMLazyTree = require('DOMLazyTree');
var DOMProperty = require('DOMProperty');
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
@@ -20,7 +19,6 @@ var ReactDOMComponentTree = require('ReactDOMComponentTree');
var ReactDOMContainerInfo = require('ReactDOMContainerInfo');
var ReactDOMFeatureFlags = require('ReactDOMFeatureFlags');
var ReactElement = require('ReactElement');
var ReactInstanceHandles = require('ReactInstanceHandles');
var ReactMarkupChecksum = require('ReactMarkupChecksum');
var ReactPerf = require('ReactPerf');
var ReactReconciler = require('ReactReconciler');
@@ -35,23 +33,12 @@ var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
var warning = require('warning');
var ATTR_NAME = DOMProperty.ID_ATTRIBUTE_NAME;
var ROOT_ATTR_NAME = DOMProperty.ROOT_ATTRIBUTE_NAME;
var ELEMENT_NODE_TYPE = 1;
var DOC_NODE_TYPE = 9;
var DOCUMENT_FRAGMENT_NODE_TYPE = 11;
/** Mapping from reactRootID to React component instance. */
var instancesByReactRootID = {};
/** Mapping from reactRootID to `container` nodes. */
var containersByReactRootID = {};
if (__DEV__) {
/** __DEV__-only mapping from reactRootID to root elements. */
var rootElementsByReactRootID = {};
}
/**
* Finds the index of the first character
* that's not common between the two given strings.
@@ -85,20 +72,11 @@ function getReactRootElementInContainer(container) {
}
}
/**
* @param {DOMElement} container DOM element that may contain a React component.
* @return {?string} A "reactRoot" ID, if a React component is rendered.
*/
function getReactRootID(container) {
var rootElement = getReactRootElementInContainer(container);
return rootElement && internalGetID(rootElement);
}
function internalGetID(node) {
// If node is something like a window, document, or text node, none of
// which support attributes or a .getAttribute method, gracefully return
// the empty string, as if the attribute were missing.
return node && node.getAttribute && node.getAttribute(ATTR_NAME) || '';
return node.getAttribute && node.getAttribute(ATTR_NAME) || '';
}
/**
@@ -111,7 +89,7 @@ function internalGetID(node) {
* @param {boolean} shouldReuseMarkup If true, do not insert markup
*/
function mountComponentIntoNode(
componentInstance,
wrapperInstance,
rootID,
container,
transaction,
@@ -119,18 +97,18 @@ function mountComponentIntoNode(
context
) {
var markup = ReactReconciler.mountComponent(
componentInstance,
wrapperInstance,
rootID,
transaction,
null,
ReactDOMContainerInfo(container),
ReactDOMContainerInfo(wrapperInstance, container),
context
);
componentInstance._renderedComponent._topLevelWrapper = componentInstance;
wrapperInstance._renderedComponent._topLevelWrapper = wrapperInstance;
ReactMount._mountImageIntoNode(
markup,
container,
componentInstance,
wrapperInstance,
shouldReuseMarkup,
transaction
);
@@ -200,10 +178,21 @@ function unmountComponentFromNode(instance, container) {
* rendered by React but is not a root element.
* @internal
*/
function hasNonRootReactChild(node) {
var reactRootID = getReactRootID(node);
return reactRootID ? reactRootID !==
ReactInstanceHandles.getReactRootIDFromNodeID(reactRootID) : false;
function hasNonRootReactChild(container) {
var rootElement = getReactRootElementInContainer(container);
return !!(
rootElement &&
internalGetID(rootElement) &&
!rootElement.hasAttribute(ROOT_ATTR_NAME)
);
}
function getTopLevelWrapperInContainer(container) {
var rootEl = getReactRootElementInContainer(container);
var prevNativeInstance =
rootEl && ReactDOMComponentTree.getInstanceFromNode(rootEl);
return !prevNativeInstance || prevNativeInstance._nativeParent ? null :
prevNativeInstance._nativeContainerInfo._topLevelWrapper;
}
/**
@@ -243,9 +232,6 @@ var ReactMount = {
TopLevelWrapper: TopLevelWrapper,
/** Exposed for debugging purposes **/
_instancesByReactRootID: instancesByReactRootID,
/**
* This is a hook provided to support rendering React components while
* ensuring that the apparent scroll position of its `container` does not
@@ -277,39 +263,9 @@ var ReactMount = {
}
});
if (__DEV__) {
// Record the root element in case it later gets transplanted.
rootElementsByReactRootID[getReactRootID(container)] =
getReactRootElementInContainer(container);
}
return prevComponent;
},
/**
* Register a component into the instance map and starts scroll value
* monitoring
* @param {ReactComponent} nextComponent component instance to render
* @param {DOMElement} container container to render into
* @return {string} reactRoot ID prefix
*/
_registerComponent: function(nextComponent, container) {
invariant(
container && (
container.nodeType === ELEMENT_NODE_TYPE ||
container.nodeType === DOC_NODE_TYPE ||
container.nodeType === DOCUMENT_FRAGMENT_NODE_TYPE
),
'_registerComponent(...): Target container is not a DOM element.'
);
ReactBrowserEventEmitter.ensureScrollValueMonitoring();
var reactRootID = ReactMount.registerContainer(container);
instancesByReactRootID[reactRootID] = nextComponent;
return reactRootID;
},
/**
* Render a new component into the DOM.
* @param {ReactElement} nextElement element to render
@@ -336,12 +292,18 @@ var ReactMount = {
'ReactCompositeComponent'
);
var componentInstance = instantiateReactComponent(nextElement, null);
var reactRootID = ReactMount._registerComponent(
componentInstance,
container
invariant(
container && (
container.nodeType === ELEMENT_NODE_TYPE ||
container.nodeType === DOC_NODE_TYPE ||
container.nodeType === DOCUMENT_FRAGMENT_NODE_TYPE
),
'_registerComponent(...): Target container is not a DOM element.'
);
ReactBrowserEventEmitter.ensureScrollValueMonitoring();
var componentInstance = instantiateReactComponent(nextElement, null);
// The initial render is synchronous but any updates that happen during
// rendering, in componentWillMount or componentDidMount, will be batched
// according to the current batching strategy.
@@ -349,18 +311,12 @@ var ReactMount = {
ReactUpdates.batchedUpdates(
batchedMountComponentIntoNode,
componentInstance,
reactRootID,
'',
container,
shouldReuseMarkup,
context
);
if (__DEV__) {
// Record the root element in case it later gets transplanted.
rootElementsByReactRootID[reactRootID] =
getReactRootElementInContainer(container);
}
return componentInstance;
},
@@ -429,7 +385,7 @@ var ReactMount = {
nextElement
);
var prevComponent = instancesByReactRootID[getReactRootID(container)];
var prevComponent = getTopLevelWrapperInContainer(container);
if (prevComponent) {
var prevWrappedElement = prevComponent._currentElement;
@@ -519,27 +475,6 @@ var ReactMount = {
return ReactMount._renderSubtreeIntoContainer(null, nextElement, container, callback);
},
/**
* Registers a container node into which React components will be rendered.
* This also creates the "reactRoot" ID that will be assigned to the element
* rendered within.
*
* @param {DOMElement} container DOM element to register as a container.
* @return {string} The "reactRoot" ID of elements rendered within.
*/
registerContainer: function(container) {
var id = getReactRootID(container);
// If one exists, make sure it is a valid "reactRoot" ID.
if (!id || id !== ReactInstanceHandles.getReactRootIDFromNodeID(id)) {
// No valid "reactRoot" ID found, create one.
id = ReactInstanceHandles.createReactRootID(
ClientReactRootIndex.createReactRootIndex()
);
}
containersByReactRootID[id] = container;
return id;
},
/**
* Unmounts and destroys the React component rendered in the `container`.
*
@@ -571,19 +506,15 @@ var ReactMount = {
'unmountComponentAtNode(...): Target container is not a DOM element.'
);
var reactRootID = getReactRootID(container);
var component = instancesByReactRootID[reactRootID];
if (!component) {
var prevComponent = getTopLevelWrapperInContainer(container);
if (!prevComponent) {
// Check if the node being unmounted was rendered by React, but isn't a
// root node.
var containerHasNonRootReactChild = hasNonRootReactChild(container);
// Check if the container itself is a React root node.
var containerID = internalGetID(container);
var isContainerReactRoot =
containerID &&
containerID ===
ReactInstanceHandles.getReactRootIDFromNodeID(containerID);
container.nodeType === 1 && container.hasAttribute(ROOT_ATTR_NAME);
if (__DEV__) {
warning(
@@ -604,14 +535,9 @@ var ReactMount = {
}
ReactUpdates.batchedUpdates(
unmountComponentFromNode,
component,
prevComponent,
container
);
delete instancesByReactRootID[reactRootID];
delete containersByReactRootID[reactRootID];
if (__DEV__) {
delete rootElementsByReactRootID[reactRootID];
}
return true;
},
@@ -33,14 +33,6 @@ describe('rendering React components at document', function() {
beforeEach(function() {
jest.resetModuleRegistry();
// Negative integer creator. So they won't get confused with
// the Client positive ids.
var ServerReactRootIndex = require('ServerReactRootIndex');
var serverId = -1;
ServerReactRootIndex.createReactRootIndex.mockImplementation(function() {
return serverId--;
});
React = require('React');
ReactDOM = require('ReactDOM');
ReactDOMServer = require('ReactDOMServer');
@@ -221,8 +213,8 @@ describe('rendering React components at document', function() {
'quirks by rendering at the document root. You should look for ' +
'environment dependent code in your components and ensure ' +
'the props are the same client and server side:\n' +
' (client) ata-reactid=".-1.1">Hello world</body></\n' +
' (server) ata-reactid=".-1.1">Goodbye world</body>'
' (client) dy data-reactid="4">Hello world</body></\n' +
' (server) dy data-reactid="4">Goodbye world</body>'
);
});
@@ -11,15 +11,14 @@
*/
'use strict';
var ReactDOMContainerInfo = require('ReactDOMContainerInfo');
var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy');
var ReactElement = require('ReactElement');
var ReactInstanceHandles = require('ReactInstanceHandles');
var ReactMarkupChecksum = require('ReactMarkupChecksum');
var ReactServerBatchingStrategy = require('ReactServerBatchingStrategy');
var ReactServerRenderingTransaction =
require('ReactServerRenderingTransaction');
var ReactUpdates = require('ReactUpdates');
var ServerReactRootIndex = require('ServerReactRootIndex');
var emptyObject = require('emptyObject');
var instantiateReactComponent = require('instantiateReactComponent');
@@ -39,18 +38,15 @@ function renderToStringImpl(element, makeStaticMarkup) {
try {
ReactUpdates.injection.injectBatchingStrategy(ReactServerBatchingStrategy);
var id = ReactInstanceHandles.createReactRootID(
ServerReactRootIndex.createReactRootIndex()
);
transaction = ReactServerRenderingTransaction.getPooled(makeStaticMarkup);
return transaction.perform(function() {
var componentInstance = instantiateReactComponent(element, null);
var markup = componentInstance.mountComponent(
id,
'',
transaction,
null,
null,
ReactDOMContainerInfo(),
emptyObject
);
if (!makeStaticMarkup) {
@@ -20,6 +20,7 @@ var ReactTestUtils;
var ReactServerRendering;
var ID_ATTRIBUTE_NAME;
var ROOT_ATTRIBUTE_NAME;
describe('ReactServerRendering', function() {
beforeEach(function() {
@@ -36,7 +37,7 @@ describe('ReactServerRendering', function() {
var DOMProperty = require('DOMProperty');
ID_ATTRIBUTE_NAME = DOMProperty.ID_ATTRIBUTE_NAME;
spyOn(console, 'error');
ROOT_ATTRIBUTE_NAME = DOMProperty.ROOT_ATTRIBUTE_NAME;
});
describe('renderToString', function() {
@@ -45,7 +46,8 @@ describe('ReactServerRendering', function() {
<span>hello world</span>
);
expect(response).toMatch(
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+" ' +
'<span ' + ROOT_ATTRIBUTE_NAME + '="" ' +
ID_ATTRIBUTE_NAME + '="[^"]+" ' +
ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+">hello world</span>'
);
});
@@ -55,7 +57,8 @@ describe('ReactServerRendering', function() {
<img />
);
expect(response).toMatch(
'<img ' + ID_ATTRIBUTE_NAME + '="[^"]+" ' +
'<img ' + ROOT_ATTRIBUTE_NAME + '="" ' +
ID_ATTRIBUTE_NAME + '="[^"]+" ' +
ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+"/>'
);
});
@@ -65,7 +68,8 @@ describe('ReactServerRendering', function() {
<img data-attr=">" />
);
expect(response).toMatch(
'<img data-attr="&gt;" ' + ID_ATTRIBUTE_NAME + '="[^"]+" ' +
'<img data-attr="&gt;" ' + ROOT_ATTRIBUTE_NAME + '="" ' +
ID_ATTRIBUTE_NAME + '="[^"]+" ' +
ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+"/>'
);
});
@@ -95,7 +99,8 @@ describe('ReactServerRendering', function() {
<Parent />
);
expect(response).toMatch(
'<div ' + ID_ATTRIBUTE_NAME + '="[^"]+" ' +
'<div ' + ROOT_ATTRIBUTE_NAME + '="" ' +
ID_ATTRIBUTE_NAME + '="[^"]+" ' +
ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+">' +
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+">' +
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+">My name is </span>' +
@@ -145,7 +150,8 @@ describe('ReactServerRendering', function() {
);
expect(response).toMatch(
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+" ' +
'<span ' + ROOT_ATTRIBUTE_NAME + '="" ' +
ID_ATTRIBUTE_NAME + '="[^"]+" ' +
ReactMarkupChecksum.CHECKSUM_ATTR_NAME + '="[^"]+">' +
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+">Component name: </span>' +
'<span ' + ID_ATTRIBUTE_NAME + '="[^"]+">TestComponent</span>' +
@@ -225,6 +231,7 @@ describe('ReactServerRendering', function() {
// Now simulate a situation where the app is not idempotent. React should
// warn but do the right thing.
element.innerHTML = lastMarkup;
spyOn(console, 'error');
var instance = ReactDOM.render(<TestComponent name="y" />, element);
expect(mountCount).toEqual(4);
expect(console.error.argsForCall.length).toBe(1);
+1
View File
@@ -167,6 +167,7 @@ var defaultValueCache = {};
var DOMProperty = {
ID_ATTRIBUTE_NAME: 'data-reactid',
ROOT_ATTRIBUTE_NAME: 'data-reactroot',
/**
* Map from property "standard name" to an object with info about how to set
@@ -125,6 +125,14 @@ var DOMPropertyOperations = {
node.setAttribute(DOMProperty.ID_ATTRIBUTE_NAME, id);
},
createMarkupForRoot: function() {
return DOMProperty.ROOT_ATTRIBUTE_NAME + '=""';
},
setAttributeForRoot: function(node) {
node.setAttribute(DOMProperty.ROOT_ATTRIBUTE_NAME, '');
},
/**
* Creates markup for a property.
*
+19 -8
View File
@@ -301,11 +301,11 @@ function enqueuePutListener(inst, registrationName, listener, transaction) {
);
}
var containerInfo = inst._nativeContainerInfo;
if (!containerInfo) {
var doc = containerInfo._ownerDocument;
if (!doc) {
// Server rendering.
return;
}
var doc = containerInfo._ownerDocument;
listenTo(registrationName, doc);
transaction.getReactMountReady().enqueue(putListener, {
inst: inst,
@@ -494,6 +494,8 @@ function isCustomComponent(tagName, props) {
return tagName.indexOf('-') >= 0 || props.is != null;
}
var globalIdCounter = 1;
/**
* Creates a new React class that is idempotent and capable of containing other
* React components. It accepts event listeners and DOM properties that are
@@ -518,6 +520,7 @@ function ReactDOMComponent(tag) {
this._nativeNode = null;
this._nativeParent = null;
this._rootNodeID = null;
this._domID = null;
this._nativeContainerInfo = null;
this._wrapperState = null;
this._topLevelWrapper = null;
@@ -554,7 +557,8 @@ ReactDOMComponent.Mixin = {
nativeContainerInfo,
context
) {
this._rootNodeID = rootID;
this._rootNodeID = globalIdCounter++;
this._domID = nativeContainerInfo._idCounter++;
this._nativeParent = nativeParent;
this._nativeContainerInfo = nativeContainerInfo;
@@ -604,7 +608,7 @@ ReactDOMComponent.Mixin = {
if (nativeParent != null) {
namespaceURI = nativeParent._namespaceURI;
parentTag = nativeParent._tag;
} else if (nativeContainerInfo != null) {
} else if (nativeContainerInfo._tag) {
namespaceURI = nativeContainerInfo._namespaceURI;
parentTag = nativeContainerInfo._tag;
}
@@ -625,7 +629,7 @@ ReactDOMComponent.Mixin = {
var parentInfo;
if (nativeParent != null) {
parentInfo = nativeParent._ancestorInfo;
} else if (nativeContainerInfo != null) {
} else if (nativeContainerInfo._tag) {
parentInfo = nativeContainerInfo._ancestorInfo;
}
if (parentInfo) {
@@ -660,7 +664,10 @@ ReactDOMComponent.Mixin = {
}
ReactDOMComponentTree.precacheNode(this, el);
this._flags |= Flags.hasCachedChildNodes;
DOMPropertyOperations.setAttributeForID(el, this._rootNodeID);
if (!this._nativeParent) {
DOMPropertyOperations.setAttributeForRoot(el);
}
DOMPropertyOperations.setAttributeForID(el, this._domID);
this._updateDOMProperties(null, props, transaction);
var lazyTree = DOMLazyTree(el);
this._createInitialChildren(transaction, props, context, lazyTree);
@@ -757,8 +764,11 @@ ReactDOMComponent.Mixin = {
return ret;
}
var markupForID = DOMPropertyOperations.createMarkupForID(this._rootNodeID);
return ret + ' ' + markupForID;
if (!this._nativeParent) {
ret += ' ' + DOMPropertyOperations.createMarkupForRoot();
}
ret += ' ' + DOMPropertyOperations.createMarkupForID(this._domID);
return ret;
},
/**
@@ -1145,6 +1155,7 @@ ReactDOMComponent.Mixin = {
EventPluginHub.deleteAllListeners(this);
ReactComponentBrowserEnvironment.unmountIDFromEnvironment(this._rootNodeID);
this._rootNodeID = null;
this._domID = null;
this._wrapperState = null;
},
@@ -15,15 +15,19 @@ var validateDOMNesting = require('validateDOMNesting');
var DOC_NODE_TYPE = 9;
function ReactDOMContainerInfo(node) {
function ReactDOMContainerInfo(topLevelWrapper, node) {
var info = {
_ownerDocument: node.nodeType === DOC_NODE_TYPE ? node : node.ownerDocument,
_tag: node.nodeName.toLowerCase(),
_namespaceURI: node.namespaceURI,
_topLevelWrapper: topLevelWrapper,
_idCounter: 1,
_ownerDocument: node ?
node.nodeType === DOC_NODE_TYPE ? node : node.ownerDocument :
null,
_tag: node ? node.nodeName.toLowerCase() : null,
_namespaceURI: node ? node.namespaceURI : null,
};
if (__DEV__) {
info._ancestorInfo =
validateDOMNesting.updatedAncestorInfo(null, info._tag, null);
info._ancestorInfo = node ?
validateDOMNesting.updatedAncestorInfo(null, info._tag, null) : null;
}
return info;
}
@@ -15,8 +15,6 @@
var DOMChildrenOperations = require('DOMChildrenOperations');
var DOMLazyTree = require('DOMLazyTree');
var DOMPropertyOperations = require('DOMPropertyOperations');
var ReactComponentBrowserEnvironment =
require('ReactComponentBrowserEnvironment');
var ReactDOMComponentTree = require('ReactDOMComponentTree');
var ReactPerf = require('ReactPerf');
@@ -60,7 +58,7 @@ assign(ReactDOMTextComponent.prototype, {
this._nativeParent = null;
// Properties
this._rootNodeID = null;
this._domID = null;
this._mountIndex = 0;
},
@@ -94,13 +92,14 @@ assign(ReactDOMTextComponent.prototype, {
}
}
this._rootNodeID = rootID;
var domID = nativeContainerInfo._idCounter++;
this._domID = domID;
this._nativeParent = nativeParent;
if (transaction.useCreateElement) {
var ownerDocument = nativeContainerInfo._ownerDocument;
var el = ownerDocument.createElement('span');
ReactDOMComponentTree.precacheNode(this, el);
DOMPropertyOperations.setAttributeForID(el, rootID);
DOMPropertyOperations.setAttributeForID(el, domID);
var lazyTree = DOMLazyTree(el);
DOMLazyTree.queueText(lazyTree, this._stringText);
return lazyTree;
@@ -115,7 +114,7 @@ assign(ReactDOMTextComponent.prototype, {
}
return (
'<span ' + DOMPropertyOperations.createMarkupForID(rootID) + '>' +
'<span ' + DOMPropertyOperations.createMarkupForID(domID) + '>' +
escapedText +
'</span>'
);
@@ -148,8 +147,7 @@ assign(ReactDOMTextComponent.prototype, {
},
unmountComponent: function() {
this._nativeNode = null;
ReactComponentBrowserEnvironment.unmountIDFromEnvironment(this._rootNodeID);
ReactDOMComponentTree.uncacheNode(this);
},
});
@@ -13,22 +13,11 @@
var React;
var ReactDOM;
var ReactDOMServer;
describe('ReactDOMTextComponent', function() {
beforeEach(function() {
React = require('React');
ReactDOM = require('ReactDOM');
ReactDOMServer = require('ReactDOMServer');
});
it('should escape the rootID', function() {
var ThisThingShouldBeEscaped = '">>> LULZ <<<"';
var ThisThingWasBeEscaped = '&quot;&gt;&gt;&gt; LULZ &lt;&lt;&lt;&quot;';
var thing = <div><span key={ThisThingShouldBeEscaped}>LULZ</span></div>;
var html = ReactDOMServer.renderToString(thing);
expect(html).not.toContain(ThisThingShouldBeEscaped);
expect(html).toContain(ThisThingWasBeEscaped);
});
it('updates a mounted text component in place', function() {
@@ -249,11 +249,9 @@ var ReactMultiChild = {
for (var name in children) {
if (children.hasOwnProperty(name)) {
var child = children[name];
// Inlined for performance, see `ReactInstanceHandles.createReactID`.
var rootID = this._rootNodeID + name;
var mountImage = ReactReconciler.mountComponent(
child,
rootID,
name,
transaction,
this,
this._nativeContainerInfo,
@@ -510,11 +508,9 @@ var ReactMultiChild = {
index,
transaction,
context) {
// Inlined for performance, see `ReactInstanceHandles.createReactID`.
var rootID = this._rootNodeID + name;
var mountImage = ReactReconciler.mountComponent(
child,
rootID,
name,
transaction,
this,
this._nativeContainerInfo,
@@ -13,7 +13,6 @@
var React;
var ReactDOM;
var ReactDOMComponentTree;
var ReactFragment;
var ReactTestUtils;
@@ -23,85 +22,52 @@ describe('ReactIdentity', function() {
jest.resetModuleRegistry();
React = require('React');
ReactDOM = require('ReactDOM');
ReactDOMComponentTree = require('ReactDOMComponentTree');
ReactFragment = require('ReactFragment');
ReactTestUtils = require('ReactTestUtils');
});
var idExp = /^\.[^.]+(.*)$/;
function checkID(child, expectedID) {
// TODO: Node IDs are not public API; rewrite these tests.
var rootID = ReactDOMComponentTree.getInstanceFromNode(child)._rootNodeID;
var actual = idExp.exec(rootID);
var expected = idExp.exec(expectedID);
expect(actual).toBeTruthy();
expect(expected).toBeTruthy();
expect(actual[1]).toEqual(expected[1]);
}
function frag(obj) {
return ReactFragment.create(obj);
}
it('should allow keyed objects to express identity', function() {
var instance =
<div>
{frag({
first: <div />,
second: <div />,
})}
</div>;
instance = ReactDOM.render(instance, document.createElement('div'));
var node = ReactDOM.findDOMNode(instance);
expect(node.childNodes.length).toBe(2);
checkID(node.childNodes[0], '.0.$first/=10');
checkID(node.childNodes[1], '.0.$second/=10');
});
it('should allow key property to express identity', function() {
var instance =
<div>
<div key="apple" />
<div key="banana" />
<div key={0} />
<div key={123} />
var node;
var Component = (props) =>
<div ref={(c) => node = c}>
<div key={props.swap ? 'banana' : 'apple'} />
<div key={props.swap ? 'apple' : 'banana'} />
</div>;
instance = ReactDOM.render(instance, document.createElement('div'));
var node = ReactDOM.findDOMNode(instance);
expect(node.childNodes.length).toBe(4);
checkID(node.childNodes[0], '.0.$apple');
checkID(node.childNodes[1], '.0.$banana');
checkID(node.childNodes[2], '.0.$0');
checkID(node.childNodes[3], '.0.$123');
var container = document.createElement('div');
ReactDOM.render(<Component />, container);
var origChildren = Array.from(node.childNodes);
ReactDOM.render(<Component swap={true} />, container);
var newChildren = Array.from(node.childNodes);
expect(origChildren[0]).toBe(newChildren[1]);
expect(origChildren[1]).toBe(newChildren[0]);
});
it('should use instance identity', function() {
it('should use composite identity', function() {
var Wrapper = React.createClass({
render: function() {
return <a key="i_get_overwritten">{this.props.children}</a>;
return <a>{this.props.children}</a>;
},
});
var instance =
<div>
<Wrapper key="wrap1"><span key="squirrel" /></Wrapper>
<Wrapper key="wrap2"><span key="bunny" /></Wrapper>
<Wrapper><span key="chipmunk" /></Wrapper>
</div>;
var container = document.createElement('div');
var node1;
var node2;
ReactDOM.render(
<Wrapper key="wrap1"><span ref={(c) => node1 = c} /></Wrapper>,
container
);
ReactDOM.render(
<Wrapper key="wrap2"><span ref={(c) => node2 = c} /></Wrapper>,
container
);
instance = ReactDOM.render(instance, document.createElement('div'));
var node = ReactDOM.findDOMNode(instance);
expect(node.childNodes.length).toBe(3);
checkID(node.childNodes[0], '.0.$wrap1');
checkID(node.childNodes[0].firstChild, '.0.$wrap1.$squirrel');
checkID(node.childNodes[1], '.0.$wrap2');
checkID(node.childNodes[1].firstChild, '.0.$wrap2.$bunny');
checkID(node.childNodes[2], '.0.2');
checkID(node.childNodes[2].firstChild, '.0.2.$chipmunk');
expect(node1).not.toBe(node2);
});
function renderAComponentWithKeyIntoContainer(key, container) {
@@ -125,11 +91,6 @@ describe('ReactIdentity', function() {
expect(ReactDOM.findDOMNode(span1)).not.toBe(null);
expect(ReactDOM.findDOMNode(span2)).not.toBe(null);
key = key.replace(/=/g, '=0');
checkID(ReactDOM.findDOMNode(span1), '.0.$' + key);
key = key.replace(/\//g, '//');
checkID(ReactDOM.findDOMNode(span2), '.0.1:$' + key + '/=10');
}
it('should allow any character as a key, in a detached parent', function() {
@@ -122,7 +122,7 @@ var FriendsStatusDisplay = React.createClass({
});
function getInteralStateByUserName(statusDisplays) {
function getInternalStateByUserName(statusDisplays) {
return mapObject(statusDisplays, function(statusDisplay, key) {
return statusDisplay.getInternalState();
});
@@ -201,7 +201,9 @@ function verifyDomOrderingAccurate(parentInstance, statusDisplays) {
continue;
}
var statusDisplay = statusDisplays[username];
orderedLogicalIDs.push(ReactInstanceMap.get(statusDisplay)._rootNodeID);
orderedLogicalIDs.push(
ReactInstanceMap.get(statusDisplay)._renderedComponent._rootNodeID
);
}
expect(orderedDomIDs).toEqual(orderedLogicalIDs);
}
@@ -217,7 +219,7 @@ function testPropsSequence(sequence) {
container
);
var statusDisplays = parentInstance.getStatusDisplays();
var lastInternalStates = getInteralStateByUserName(statusDisplays);
var lastInternalStates = getInternalStateByUserName(statusDisplays);
verifyStatuses(statusDisplays, sequence[0]);
for (i = 1; i < sequence.length; i++) {
@@ -230,7 +232,7 @@ function testPropsSequence(sequence) {
verifyStatesPreserved(lastInternalStates, statusDisplays);
verifyDomOrderingAccurate(parentInstance, statusDisplays);
lastInternalStates = getInteralStateByUserName(statusDisplays);
lastInternalStates = getInternalStateByUserName(statusDisplays);
}
}