Merge pull request #2537 from sebmarkbage/hidedomcomponentinternals

Wrap every DOM node in a Composite Component
This commit is contained in:
Sebastian Markbåge
2014-11-16 15:35:04 -08:00
18 changed files with 80 additions and 61 deletions
+3 -4
View File
@@ -15,7 +15,6 @@
var React = require('React');
var ReactDOM = require('ReactDOM');
var ReactMount = require('ReactMount');
var ReactTestUtils = require('ReactTestUtils');
var div = React.createFactory('div');
@@ -53,7 +52,7 @@ describe('ReactDOM', function() {
var argDiv = ReactTestUtils.renderIntoDocument(
div(null, 'child')
);
var argNode = ReactMount.getNode(argDiv._rootNodeID);
var argNode = argDiv.getDOMNode();
expect(argNode.innerHTML).toBe('child');
});
@@ -61,7 +60,7 @@ describe('ReactDOM', function() {
var conflictDiv = ReactTestUtils.renderIntoDocument(
div({children: 'fakechild'}, 'child')
);
var conflictNode = ReactMount.getNode(conflictDiv._rootNodeID);
var conflictNode = conflictDiv.getDOMNode();
expect(conflictNode.innerHTML).toBe('child');
});
@@ -103,7 +102,7 @@ describe('ReactDOM', function() {
theBird: <div className="bird" />
}
});
var root = ReactMount.getNode(myDiv._rootNodeID);
var root = myDiv.getDOMNode();
var dog = root.childNodes[0];
expect(dog.className).toBe('bigdog');
});
+1 -4
View File
@@ -15,7 +15,6 @@
var CSSPropertyOperations = require('CSSPropertyOperations');
var DOMProperty = require('DOMProperty');
var DOMPropertyOperations = require('DOMPropertyOperations');
var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
var ReactComponent = require('ReactComponent');
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
var ReactMount = require('ReactMount');
@@ -148,7 +147,6 @@ function validateDangerousTag(tag) {
function ReactDOMComponent(tag) {
validateDangerousTag(tag);
this._tag = tag;
this.tagName = tag.toUpperCase();
}
ReactDOMComponent.displayName = 'ReactDOMComponent';
@@ -488,8 +486,7 @@ assign(
ReactDOMComponent.prototype,
ReactComponent.Mixin,
ReactDOMComponent.Mixin,
ReactMultiChild.Mixin,
ReactBrowserComponentMixin
ReactMultiChild.Mixin
);
module.exports = ReactDOMComponent;
+1 -7
View File
@@ -135,13 +135,7 @@ function getNode(id) {
* @internal
*/
function getNodeFromInstance(instance) {
// This instance can currently be either a public or private instance since
// native nodes are still public.
var id = instance._rootNodeID;
// TODO: Once these are only public instances, remove this conditional.
if (id == null) {
id = ReactInstanceMap.get(instance)._rootNodeID;
}
var id = ReactInstanceMap.get(instance)._rootNodeID;
if (ReactEmptyComponent.isNullComponentID(id)) {
return null;
}
@@ -39,6 +39,7 @@ var mouseListenerNames = keyMirror({
*/
var ReactDOMButton = ReactClass.createClass({
displayName: 'ReactDOMButton',
tagName: 'BUTTON',
mixins: [AutoFocusMixin, ReactBrowserComponentMixin],
@@ -27,6 +27,7 @@ var form = ReactElement.createFactory('form');
*/
var ReactDOMForm = ReactClass.createClass({
displayName: 'ReactDOMForm',
tagName: 'FORM',
mixins: [ReactBrowserComponentMixin, LocalEventTrapMixin],
@@ -52,6 +52,7 @@ function forceUpdateIfMounted() {
*/
var ReactDOMInput = ReactClass.createClass({
displayName: 'ReactDOMInput',
tagName: 'INPUT',
mixins: [AutoFocusMixin, LinkedValueUtils.Mixin, ReactBrowserComponentMixin],
@@ -24,6 +24,7 @@ var option = ReactElement.createFactory('option');
*/
var ReactDOMOption = ReactClass.createClass({
displayName: 'ReactDOMOption',
tagName: 'OPTION',
mixins: [ReactBrowserComponentMixin],
@@ -109,6 +109,7 @@ function updateOptions(component, propValue) {
*/
var ReactDOMSelect = ReactClass.createClass({
displayName: 'ReactDOMSelect',
tagName: 'SELECT',
mixins: [AutoFocusMixin, LinkedValueUtils.Mixin, ReactBrowserComponentMixin],
@@ -50,6 +50,7 @@ function forceUpdateIfMounted() {
*/
var ReactDOMTextarea = ReactClass.createClass({
displayName: 'ReactDOMTextarea',
tagName: 'TEXTAREA',
mixins: [AutoFocusMixin, LinkedValueUtils.Mixin, ReactBrowserComponentMixin],
+5 -1
View File
@@ -453,7 +453,11 @@ var ReactComponent = {
* @internal
*/
getPublicInstance: function() {
return this;
invariant(
false,
'getPublicInstance should never be called on the base class. It must ' +
'be overriden.'
);
}
}
};
+20 -6
View File
@@ -11,6 +11,9 @@
"use strict";
var ReactClass = require('ReactClass');
var ReactElement = require('ReactElement');
var assign = require('Object.assign');
var invariant = require('invariant');
@@ -37,6 +40,22 @@ var ReactNativeComponentInjection = {
}
};
function autoGenerateWrapperClass(type) {
return ReactClass.createClass({
tagName: type.toUpperCase(),
render: function() {
return new ReactElement(
type,
null,
null,
null,
null,
this.props
);
}
});
}
/**
* Create an internal class for a specific tag.
*
@@ -47,12 +66,7 @@ var ReactNativeComponentInjection = {
function createInstanceForTag(tag, props, parentType) {
var componentClass = tagToComponentClass[tag];
if (componentClass == null) {
invariant(
genericComponentClass,
'There is no registered component for the tag %s',
tag
);
return new genericComponentClass(tag, props);
tagToComponentClass[tag] = componentClass = autoGenerateWrapperClass(tag);
}
if (parentType === tag) {
// Avoid recursion
+3 -7
View File
@@ -26,10 +26,6 @@ describe('ReactComponent', function() {
reactComponentExpect = require('reactComponentExpect');
getMountDepth = function(instance) {
if (instance.mountComponent) {
// Native instance
return instance._mountDepth;
}
return ReactInstanceMap.get(instance)._mountDepth;
};
});
@@ -306,9 +302,9 @@ describe('ReactComponent', function() {
expect(getMountDepth(root)).toBe(0);
expect(getMountDepth(root.refs.switcher)).toBe(1);
expect(getMountDepth(root.refs.switcher.refs.box)).toBe(2);
expect(getMountDepth(root.refs.switcher.refs.switcherDiv)).toBe(4);
expect(getMountDepth(root.refs.child)).toBe(5);
expect(getMountDepth(root.refs.switcher.refs.switcherDiv)).toBe(5);
expect(getMountDepth(root.refs.child)).toBe(7);
expect(getMountDepth(root.refs.switcher.refs.box.refs.boxDiv)).toBe(3);
expect(getMountDepth(root.refs.child.refs.span)).toBe(6);
expect(getMountDepth(root.refs.child.refs.span)).toBe(8);
});
});
@@ -66,8 +66,8 @@ describe('ReactCompositeComponent', function() {
* reallocated again.
*/
ChildUpdates = React.createClass({
getAnchorID: function() {
return this.refs.anch._rootNodeID;
getAnchor: function() {
return this.refs.anch;
},
render: function() {
var className = cx({'anchorClass': this.props.anchorClassOn});
@@ -182,8 +182,8 @@ describe('ReactCompositeComponent', function() {
instance.setProps({renderAnchor: false}); // Clear out the anchor
// rerender
instance.setProps({renderAnchor: true, anchorClassOn: false});
var anchorID = instance.getAnchorID();
var actualDOMAnchorNode = ReactMount.getNode(anchorID);
var anchor = instance.getAnchor();
var actualDOMAnchorNode = anchor.getDOMNode();
expect(actualDOMAnchorNode.className).toBe('');
});
@@ -779,9 +779,11 @@ describe('ReactCompositeComponent', function() {
React.unmountComponentAtNode(container);
expect(innerUnmounted).toBe(true);
// <Component />, <Inner />, and both <div /> elements each call
// unmountIDFromEnvironment which calls purgeID, for a total of 4.
expect(ReactMount.purgeID.callCount).toBe(4);
// <Component />, <Inner />, and both <div /> elements and their wrappers
// each call unmountIDFromEnvironment which calls purgeID, for a total of 6.
// TODO: Test the effect of this. E.g. does the node cache get repopulated
// after a getDOMNode call?
expect(ReactMount.purgeID.callCount).toBe(6);
});
it('should warn when shouldComponentUpdate() returns undefined', function() {
@@ -26,8 +26,8 @@ describe('ReactCompositeComponent-error', function() {
}
});
var instance = <Component />;
instance = ReactTestUtils.renderIntoDocument(instance);
new Component();
expect(ReactErrorUtils.guard.mostRecentCall.args[1])
.toEqual('Component.someHandler');
});
@@ -12,6 +12,7 @@
"use strict";
var React = require('React');
var ReactInstanceMap = require('ReactInstanceMap');
var ReactTestUtils = require('ReactTestUtils');
var ReactMount = require('ReactMount');
@@ -66,8 +67,7 @@ describe('ReactInstanceHandles', function() {
if (instance === null) {
return '';
}
var internal = ReactTestUtils.getInternalRepresentation(instance);
return internal._rootNodeID;
return ReactInstanceMap.get(instance)._rootNodeID;
}
beforeEach(function() {
@@ -87,7 +87,10 @@ var FriendsStatusDisplay = React.createClass({
// TODO: Update this to a better test that doesn't rely so much on internal
// implementation details.
var statusDisplays =
ReactInstanceMap.get(this)._renderedComponent._renderedChildren;
ReactInstanceMap.get(this)
._renderedComponent
._renderedComponent
._renderedChildren;
for (name in statusDisplays) {
var child = statusDisplays[name];
var isPresent = !!child;
+4 -9
View File
@@ -99,14 +99,6 @@ var ReactTestUtils = {
(inst.constructor === type));
},
getInternalRepresentation: function(inst) {
// TODO: Remove this duck check once we have a separate DOM/Native instance
if (typeof inst.mountComponent === 'function') {
return inst;
}
return ReactInstanceMap.get(inst);
},
getRenderedChildOfCompositeComponent: function(inst) {
if (!ReactTestUtils.isCompositeComponent(inst)) {
return null;
@@ -121,7 +113,10 @@ var ReactTestUtils = {
}
var ret = test(inst) ? [inst] : [];
if (ReactTestUtils.isDOMComponent(inst)) {
var renderedChildren = inst._renderedChildren;
var internalInstance = ReactInstanceMap.get(inst);
var renderedChildren = internalInstance
._renderedComponent
._renderedChildren;
var key;
for (key in renderedChildren) {
if (!renderedChildren.hasOwnProperty(key)) {
+20 -11
View File
@@ -12,6 +12,7 @@
"use strict";
var ReactInstanceMap = require('ReactInstanceMap');
var ReactTestUtils = require('ReactTestUtils');
var assign = require('Object.assign');
@@ -27,14 +28,20 @@ function reactComponentExpect(instance) {
expect(instance).not.toBeNull();
this._instance = ReactTestUtils.getInternalRepresentation(instance);
var internalInstance = ReactInstanceMap.get(instance);
expect(typeof instance).toBe('object');
expect(typeof instance.constructor).toBe('function');
expect(ReactTestUtils.isElement(instance)).toBe(false);
expect(typeof internalInstance).toBe('object');
expect(typeof internalInstance.constructor).toBe('function');
expect(ReactTestUtils.isElement(internalInstance)).toBe(false);
return new reactComponentExpectInternal(internalInstance);
}
assign(reactComponentExpect.prototype, {
function reactComponentExpectInternal(internalInstance) {
this._instance = internalInstance;
}
assign(reactComponentExpectInternal.prototype, {
// Getters -------------------------------------------------------------------
/**
@@ -62,7 +69,7 @@ assign(reactComponentExpect.prototype, {
this.toBeCompositeComponent();
var child = this._instance._renderedComponent;
// TODO: Hide ReactEmptyComponent instances here?
return new reactComponentExpect(child);
return new reactComponentExpectInternal(child);
},
/**
@@ -72,14 +79,15 @@ assign(reactComponentExpect.prototype, {
// Currently only dom components have arrays of children, but that will
// change soon.
this.toBeDOMComponent();
var renderedChildren = this._instance._renderedChildren || {};
var renderedChildren =
this._instance._renderedComponent._renderedChildren || {};
for (var name in renderedChildren) {
if (!renderedChildren.hasOwnProperty(name)) {
continue;
}
if (renderedChildren[name]) {
if (renderedChildren[name]._mountIndex === childIndex) {
return new reactComponentExpect(renderedChildren[name]);
return new reactComponentExpectInternal(renderedChildren[name]);
}
}
}
@@ -88,15 +96,16 @@ assign(reactComponentExpect.prototype, {
toBeDOMComponentWithChildCount: function(n) {
this.toBeDOMComponent();
expect(this._instance._renderedChildren).toBeTruthy();
var len = Object.keys(this._instance._renderedChildren).length;
expect(this._instance._renderedComponent._renderedChildren).toBeTruthy();
var len = Object.keys(this._instance._renderedComponent._renderedChildren)
.length;
expect(len).toBe(n);
return this;
},
toBeDOMComponentWithNoChildren: function() {
this.toBeDOMComponent();
expect(this._instance._renderedChildren).toBeFalsy();
expect(this._instance._renderedComponent._renderedChildren).toBeFalsy();
return this;
},