Skip to content

Commit

Permalink
Use createRoot in ReactDOMComponentTree-test (#28112)
Browse files Browse the repository at this point in the history
  • Loading branch information
jackpope authored Jan 26, 2024
1 parent 38997cf commit 4c41c09
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 98 deletions.
185 changes: 87 additions & 98 deletions packages/react-dom/src/__tests__/ReactDOMComponentTree-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@

describe('ReactDOMComponentTree', () => {
let React;
let ReactDOM;
let ReactDOMClient;
let act;
let container;

beforeEach(() => {
React = require('react');
ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
act = require('internal-test-utils').act;

container = document.createElement('div');
document.body.appendChild(container);
});
Expand All @@ -26,25 +29,24 @@ describe('ReactDOMComponentTree', () => {
container = null;
});

it('finds nodes for instances on events', () => {
it('finds nodes for instances on events', async () => {
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 => {
function Component() {
const handler = e => {
currentTargetID = e.currentTarget.id;
};
render() {
return (
<div id={mouseOverID} onMouseOver={this.handler}>
<div id={clickID} onClick={this.handler} />
</div>
);
}

return (
<div id={mouseOverID} onMouseOver={handler}>
<div id={clickID} onClick={handler} />
</div>
);
}

function simulateMouseEvent(elem, type) {
Expand All @@ -54,34 +56,35 @@ describe('ReactDOMComponentTree', () => {
elem.dispatchEvent(event);
}

const component = <Component />;
ReactDOM.render(component, container);
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<Component />);
});
expect(currentTargetID).toBe(null);
simulateMouseEvent(document.getElementById(mouseOverID), 'mouseover');
expect(currentTargetID).toBe(mouseOverID);
simulateMouseEvent(document.getElementById(clickID), 'click');
expect(currentTargetID).toBe(clickID);
});

it('finds closest instance for node when an event happens', () => {
it('finds closest instance for node when an event happens', async () => {
const nonReactElemID = 'aID';
const innerHTML = {__html: `<div id="${nonReactElemID}"></div>`};
const closestInstanceID = 'closestInstance';
let currentTargetID = null;

class ClosestInstance extends React.Component {
_onClick = e => {
function ClosestInstance() {
const onClick = e => {
currentTargetID = e.currentTarget.id;
};
render() {
return (
<div
id={closestInstanceID}
onClick={this._onClick}
dangerouslySetInnerHTML={innerHTML}
/>
);
}

return (
<div
id={closestInstanceID}
onClick={onClick}
dangerouslySetInnerHTML={innerHTML}
/>
);
}

function simulateClick(elem) {
Expand All @@ -91,16 +94,22 @@ describe('ReactDOMComponentTree', () => {
elem.dispatchEvent(event);
}

const component = <ClosestInstance />;
ReactDOM.render(<section>{component}</section>, container);
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(
<section>
<ClosestInstance />
</section>,
);
});
expect(currentTargetID).toBe(null);
simulateClick(document.getElementById(nonReactElemID));
expect(currentTargetID).toBe(closestInstanceID);
});

it('updates event handlers from fiber props', () => {
it('updates event handlers from fiber props', async () => {
let action = '';
let instance;
let flip;
const handlerA = () => (action = 'A');
const handlerB = () => (action = 'B');

Expand All @@ -111,55 +120,56 @@ describe('ReactDOMComponentTree', () => {
target.dispatchEvent(event);
}

class HandlerFlipper extends React.Component {
state = {flip: false};
flip() {
this.setState({flip: true});
}
render() {
return (
<div
id="update"
onMouseOver={this.state.flip ? handlerB : handlerA}
/>
);
}
function HandlerFlipper() {
const [flipVal, setFlipVal] = React.useState(false);
flip = () => setFlipVal(true);

return <div id="update" onMouseOver={flipVal ? handlerB : handlerA} />;
}

ReactDOM.render(
<HandlerFlipper key="1" ref={n => (instance = n)} />,
container,
);
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<HandlerFlipper key="1" />);
});
const node = container.firstChild;
simulateMouseOver(node);

await act(() => {
simulateMouseOver(node);
});
expect(action).toEqual('A');
action = '';

// Render with the other event handler.
instance.flip();
simulateMouseOver(node);
await act(() => {
flip();
});
await act(() => {
simulateMouseOver(node);
});
expect(action).toEqual('B');
});

it('finds a controlled instance from node and gets its current fiber props', () => {
it('finds a controlled instance from node and gets its current fiber props', async () => {
let inputRef;
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 (
<input
id={inputID}
type="text"
ref={n => (this.a = n)}
value={this.state.value}
onChange={this._onChange}
/>
);
}
function Controlled() {
const [state, setState] = React.useState(startValue);
const ref = React.useRef();
inputRef = ref;
const onChange = e => setState(e.currentTarget.value);

return (
<input
id={inputID}
type="text"
ref={ref}
value={state}
onChange={onChange}
/>
);
}

const setUntrackedInputValue = Object.getOwnPropertyDescriptor(
Expand All @@ -175,9 +185,17 @@ describe('ReactDOMComponentTree', () => {
elem.dispatchEvent(inputEvent);
}

const component = <Controlled />;
const instance = ReactDOM.render(component, container);
expect(() => simulateInput(instance.a, finishValue)).toErrorDev(
const root = ReactDOMClient.createRoot(container);
await act(() => {
root.render(<Controlled />);
});

await expect(
async () =>
await act(() => {
simulateInput(inputRef.current, finishValue);
}),
).toErrorDev(
'Warning: A component is changing an uncontrolled input to be controlled. ' +
'This is likely caused by the value changing from undefined to ' +
'a defined value, which should not happen. ' +
Expand All @@ -186,33 +204,4 @@ describe('ReactDOMComponentTree', () => {
'https://reactjs.org/link/controlled-components',
);
});

it('finds instance of node that is attempted to be unmounted', () => {
const component = <div />;
const node = ReactDOM.render(<div>{component}</div>, container);
expect(() => ReactDOM.unmountComponentAtNode(node)).toErrorDev(
"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.',
{withoutStack: true},
);
});

it('finds instance from node to stop rendering over other react rendered components', () => {
const component = (
<div>
<span>Hello</span>
</div>
);
const anotherComponent = <div />;
const instance = ReactDOM.render(component, container);
expect(() => ReactDOM.render(anotherComponent, instance)).toErrorDev(
'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.',
{withoutStack: true},
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
*/

'use strict';

describe('ReactDOMComponentTree', () => {
let React;
let ReactDOM;
let container;

beforeEach(() => {
React = require('react');
ReactDOM = require('react-dom');

container = document.createElement('div');
document.body.appendChild(container);
});

afterEach(() => {
document.body.removeChild(container);
container = null;
});

it('finds instance of node that is attempted to be unmounted', () => {
const component = <div />;
const node = ReactDOM.render(<div>{component}</div>, container);
expect(() => ReactDOM.unmountComponentAtNode(node)).toErrorDev(
"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.',
{withoutStack: true},
);
});

it('finds instance from node to stop rendering over other react rendered components', () => {
const component = (
<div>
<span>Hello</span>
</div>
);
const anotherComponent = <div />;
const instance = ReactDOM.render(component, container);
expect(() => ReactDOM.render(anotherComponent, instance)).toErrorDev(
'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.',
{withoutStack: true},
);
});
});

0 comments on commit 4c41c09

Please sign in to comment.