diff --git a/packages/react-dom/src/__tests__/ReactDOMComponentTree-test.js b/packages/react-dom/src/__tests__/ReactDOMComponentTree-test.js
index f1873882f5c7d..93c081d8d48c6 100644
--- a/packages/react-dom/src/__tests__/ReactDOMComponentTree-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMComponentTree-test.js
@@ -10,114 +10,217 @@
'use strict';
describe('ReactDOMComponentTree', () => {
- var React;
- var ReactDOM;
- var ReactDOMComponentTree;
- var ReactDOMServer;
-
- function renderMarkupIntoDocument(elt) {
- var container = document.createElement('div');
- // Force server-rendering path:
- container.innerHTML = ReactDOMServer.renderToString(elt);
- return ReactDOM.hydrate(elt, container);
- }
-
- function getTypeOf(instance) {
- return instance.type;
- }
-
- function getTextOf(instance) {
- return instance.memoizedProps;
- }
+ let React;
+ let ReactDOM;
+ let container;
beforeEach(() => {
React = require('react');
ReactDOM = require('react-dom');
- // TODO: can we express this test with only public API?
- ReactDOMComponentTree = require('../client/ReactDOMComponentTree');
- ReactDOMServer = require('react-dom/server');
+ container = document.createElement('div');
+ document.body.appendChild(container);
});
- it('finds nodes for instances', () => {
- // This is a little hard to test directly. But refs rely on it -- so we
- // check that we can find a ref at arbitrary points in the tree, even if
- // other nodes don't have a ref.
+ afterEach(() => {
+ document.body.removeChild(container);
+ container = null;
+ });
+
+ it('finds nodes for instances on events', () => {
+ const mouseOverID = 'mouseOverID';
+ const clickID = 'clickID';
+ let currentTargetID = null;
+ // the current target of an event is set to result of getNodeFromInstance
+ // when an event is dispatched so we can test behavior by invoking
+ // events on elements in the tree and confirming the expected node is
+ // set as the current target
class Component extends React.Component {
+ handler = e => {
+ currentTargetID = e.currentTarget.id;
+ };
render() {
- var toRef = this.props.toRef;
return (
-
+
);
}
}
- function renderAndQuery(sel) {
- var root = renderMarkupIntoDocument(
-
-
- ,
- );
- return sel ? root.querySelector(sel) : root;
+ function simulateClick(elem) {
+ const event = new MouseEvent('click', {
+ bubbles: true,
+ });
+ elem.dispatchEvent(event);
+ }
+
+ const component = ;
+ ReactDOM.render({component}, container);
+ expect(currentTargetID).toBe(null);
+ simulateClick(document.getElementById(nonReactElemID));
+ expect(currentTargetID).toBe(closestInstanceID);
+ });
+
+ it('updates event handlers from fiber props', () => {
+ let action = '';
+ let instance;
+ const handlerA = () => (action = 'A');
+ const handlerB = () => (action = 'B');
+
+ function simulateMouseOver(target) {
+ const event = new MouseEvent('mouseover', {
+ bubbles: true,
+ });
+ target.dispatchEvent(event);
+ }
+
+ class HandlerFlipper extends React.Component {
+ state = {flip: false};
+ flip() {
+ this.setState({flip: true});
+ }
+ render() {
+ return (
+
+ );
+ }
}
- function renderAndGetInstance(sel) {
- return ReactDOMComponentTree.getInstanceFromNode(renderAndQuery(sel));
+ ReactDOM.render(
+ (instance = n)} />,
+ container,
+ );
+ const node = container.firstChild;
+ simulateMouseOver(node);
+ expect(action).toEqual('A');
+ action = '';
+ // Render with the other event handler.
+ instance.flip();
+ simulateMouseOver(node);
+ expect(action).toEqual('B');
+ });
+
+ it('finds a controlled instance from node and gets its current fiber props', () => {
+ const inputID = 'inputID';
+ const startValue = undefined;
+ const finishValue = 'finish';
+
+ class Controlled extends React.Component {
+ state = {value: startValue};
+ a = null;
+ _onChange = e => this.setState({value: e.currentTarget.value});
+ render() {
+ return (
+ (this.a = n)}
+ value={this.state.value}
+ onChange={this._onChange}
+ />
+ );
+ }
}
- function renderAndGetClosest(sel) {
- return ReactDOMComponentTree.getClosestInstanceFromNode(
- renderAndQuery(sel),
- );
+ const setUntrackedInputValue = Object.getOwnPropertyDescriptor(
+ HTMLInputElement.prototype,
+ 'value',
+ ).set;
+
+ function simulateInput(elem, value) {
+ const inputEvent = new Event('input', {
+ bubbles: true,
+ });
+ setUntrackedInputValue.call(elem, value);
+ elem.dispatchEvent(inputEvent);
}
- expect(getTypeOf(renderAndGetInstance(null))).toBe('section');
- expect(getTypeOf(renderAndGetInstance('div'))).toBe('div');
- expect(getTypeOf(renderAndGetInstance('h1'))).toBe('h1');
- expect(getTypeOf(renderAndGetInstance('p'))).toBe('p');
- expect(getTypeOf(renderAndGetInstance('input'))).toBe('input');
- expect(getTypeOf(renderAndGetInstance('main'))).toBe('main');
-
- // This one's a text component!
- var root = renderAndQuery(null);
- var inst = ReactDOMComponentTree.getInstanceFromNode(
- root.children[0].childNodes[2],
+ const component = ;
+ const instance = ReactDOM.render(component, container);
+ spyOn(console, 'error');
+ expectDev(console.error.calls.count()).toBe(0);
+ simulateInput(instance.a, finishValue);
+ expectDev(console.error.calls.count()).toBe(1);
+ expectDev(console.error.calls.argsFor(0)[0]).toContain(
+ 'Warning: A component is changing an uncontrolled input of ' +
+ 'type text to be controlled. Input elements should not ' +
+ 'switch from uncontrolled to controlled (or vice versa). ' +
+ 'Decide between using a controlled or uncontrolled input ' +
+ 'element for the lifetime of the component. More info: ' +
+ 'https://fb.me/react-controlled-components',
);
- expect(getTextOf(inst)).toBe('goodbye.');
+ });
- expect(getTypeOf(renderAndGetClosest('b'))).toBe('main');
- expect(getTypeOf(renderAndGetClosest('img'))).toBe('main');
+ it('finds instance of node that is attempted to be unmounted', () => {
+ spyOn(console, 'error');
+ const component = ;
+ const node = ReactDOM.render(
{component}
, container);
+ ReactDOM.unmountComponentAtNode(node);
+ expectDev(console.error.calls.count()).toBe(1);
+ expectDev(console.error.calls.argsFor(0)[0]).toContain(
+ "unmountComponentAtNode(): The node you're attempting to unmount " +
+ 'was rendered by React and is not a top-level container. You may ' +
+ 'have accidentally passed in a React root node instead of its ' +
+ 'container.',
+ );
+ });
+
+ it('finds instance from node to stop rendering over other react rendered components', () => {
+ spyOn(console, 'error');
+ const component = (
+
+ Hello
+
+ );
+ const anotherComponent = ;
+ const instance = ReactDOM.render(component, container);
+ ReactDOM.render(anotherComponent, instance);
+ expectDev(console.error.calls.count()).toBe(1);
+ expectDev(console.error.calls.argsFor(0)[0]).toContain(
+ 'render(...): Replacing React-rendered children with a new root ' +
+ 'component. If you intended to update the children of this node, ' +
+ 'you should instead have the existing children update their state ' +
+ 'and render the new components instead of calling ReactDOM.render.',
+ );
});
});