Repository: facebook/react
Pull request: Convert ReactTestUtils to createRoot

it'll be removed soon anyway but until then let's unblock moving legacy APIs behind a flag.

Changed files:
filename: packages/react-dom/src/__tests__/ReactTestUtils-test.js
patch: @@ -9,8 +9,9 @@
 
 'use strict';
 
+import {act} from 'internal-test-utils';
 import * as React from 'react';
-import * as ReactDOM from 'react-dom';
+import * as ReactDOMClient from 'react-dom/client';
 import * as ReactDOMServer from 'react-dom/server';
 import * as ReactTestUtils from 'react-dom/test-utils';
 
@@ -30,7 +31,7 @@ describe('ReactTestUtils', () => {
     expect(Object.keys(ReactTestUtils.Simulate).sort()).toMatchSnapshot();
   });
 
-  it('gives Jest mocks a passthrough implementation with mockComponent()', () => {
+  it('gives Jest mocks a passthrough implementation with mockComponent()', async () => {
     class MockedComponent extends React.Component {
       render() {
         throw new Error('Should not get here.');
@@ -51,11 +52,15 @@ describe('ReactTestUtils', () => {
     ReactTestUtils.mockComponent(MockedComponent);
 
     const container = document.createElement('div');
-    ReactDOM.render(<MockedComponent>Hello</MockedComponent>, container);
+    const root = ReactDOMClient.createRoot(container);
+    await act(() => {
+      root.render(<MockedComponent>Hello</MockedComponent>);
+    });
+
     expect(container.textContent).toBe('Hello');
   });
 
-  it('can scryRenderedComponentsWithType', () => {
+  it('can scryRenderedComponentsWithType', async () => {
     class Child extends React.Component {
       render() {
         return null;
@@ -70,15 +75,20 @@ describe('ReactTestUtils', () => {
         );
       }
     }
-    const renderedComponent = ReactTestUtils.renderIntoDocument(<Wrapper />);
+    const container = document.createElement('div');
+    const root = ReactDOMClient.createRoot(container);
+    let renderedComponent;
+    await act(() => {
+      root.render(<Wrapper ref={current => (renderedComponent = current)} />);
+    });
     const scryResults = ReactTestUtils.scryRenderedComponentsWithType(
       renderedComponent,
       Child,
     );
     expect(scryResults.length).toBe(1);
   });
 
-  it('can scryRenderedDOMComponentsWithClass with TextComponent', () => {
+  it('can scryRenderedDOMComponentsWithClass with TextComponent', async () => {
     class Wrapper extends React.Component {
       render() {
         return (
@@ -89,15 +99,20 @@ describe('ReactTestUtils', () => {
       }
     }
 
-    const renderedComponent = ReactTestUtils.renderIntoDocument(<Wrapper />);
+    const container = document.createElement('div');
+    const root = ReactDOMClient.createRoot(container);
+    let renderedComponent;
+    await act(() => {
+      root.render(<Wrapper ref={current => (renderedComponent = current)} />);
+    });
     const scryResults = ReactTestUtils.scryRenderedDOMComponentsWithClass(
       renderedComponent,
       'NonExistentClass',
     );
     expect(scryResults.length).toBe(0);
   });
 
-  it('can scryRenderedDOMComponentsWithClass with className contains \\n', () => {
+  it('can scryRenderedDOMComponentsWithClass with className contains \\n', async () => {
     class Wrapper extends React.Component {
       render() {
         return (
@@ -108,15 +123,20 @@ describe('ReactTestUtils', () => {
       }
     }
 
-    const renderedComponent = ReactTestUtils.renderIntoDocument(<Wrapper />);
+    const container = document.createElement('div');
+    const root = ReactDOMClient.createRoot(container);
+    let renderedComponent;
+    await act(() => {
+      root.render(<Wrapper ref={current => (renderedComponent = current)} />);
+    });
     const scryResults = ReactTestUtils.scryRenderedDOMComponentsWithClass(
       renderedComponent,
       'x',
     );
     expect(scryResults.length).toBe(1);
   });
 
-  it('can scryRenderedDOMComponentsWithClass with multiple classes', () => {
+  it('can scryRenderedDOMComponentsWithClass with multiple classes', async () => {
     class Wrapper extends React.Component {
       render() {
         return (
@@ -127,7 +147,12 @@ describe('ReactTestUtils', () => {
       }
     }
 
-    const renderedComponent = ReactTestUtils.renderIntoDocument(<Wrapper />);
+    const container = document.createElement('div');
+    const root = ReactDOMClient.createRoot(container);
+    let renderedComponent;
+    await act(() => {
+      root.render(<Wrapper ref={current => (renderedComponent = current)} />);
+    });
     const scryResults1 = ReactTestUtils.scryRenderedDOMComponentsWithClass(
       renderedComponent,
       'x y',
@@ -162,41 +187,45 @@ describe('ReactTestUtils', () => {
     expect(scryResults5.length).toBe(0);
   });
 
-  it('traverses children in the correct order', () => {
+  it('traverses children in the correct order', async () => {
     class Wrapper extends React.Component {
       render() {
         return <div>{this.props.children}</div>;
       }
     }
 
     const container = document.createElement('div');
-    ReactDOM.render(
-      <Wrapper>
-        {null}
-        <div>purple</div>
-      </Wrapper>,
-      container,
-    );
-    const tree = ReactDOM.render(
-      <Wrapper>
-        <div>orange</div>
-        <div>purple</div>
-      </Wrapper>,
-      container,
-    );
+    const root = ReactDOMClient.createRoot(container);
+    await act(() => {
+      root.render(
+        <Wrapper>
+          {null}
+          <div>purple</div>
+        </Wrapper>,
+      );
+    });
+    let tree;
+    await act(() => {
+      root.render(
+        <Wrapper ref={current => (tree = current)}>
+          <div>orange</div>
+          <div>purple</div>
+        </Wrapper>,
+      );
+    });
 
     const log = [];
     ReactTestUtils.findAllInRenderedTree(tree, function (child) {
       if (ReactTestUtils.isDOMComponent(child)) {
-        log.push(ReactDOM.findDOMNode(child).textContent);
+        log.push(child.textContent);
       }
     });
 
     // Should be document order, not mount order (which would be purple, orange)
     expect(log).toEqual(['orangepurple', 'orange', 'purple']);
   });
 
-  it('should support injected wrapper components as DOM components', () => {
+  it('should support injected wrapper components as DOM components', async () => {
     const injectedDOMComponents = [
       'button',
       'form',
@@ -208,13 +237,22 @@ describe('ReactTestUtils', () => {
       'textarea',
     ];
 
-    injectedDOMComponents.forEach(function (type) {
-      const testComponent = ReactTestUtils.renderIntoDocument(
-        React.createElement(type),
-      );
+    // eslint-disable-next-line no-for-of-loops/no-for-of-loops
+    for (const type of injectedDOMComponents) {
+      const container = document.createElement('div');
+      const root = ReactDOMClient.createRoot(container);
+      let testComponent;
+      await act(() => {
+        root.render(
+          React.createElement(type, {
+            ref: current => (testComponent = current),
+          }),
+        );
+      });
+
       expect(testComponent.tagName).toBe(type.toUpperCase());
       expect(ReactTestUtils.isDOMComponent(testComponent)).toBe(true);
-    });
+    }
 
     // Full-page components (html, head, body) can't be rendered into a div
     // directly...
@@ -237,7 +275,13 @@ describe('ReactTestUtils', () => {
 
     const markup = ReactDOMServer.renderToString(<Root />);
     const testDocument = getTestDocument(markup);
-    const component = ReactDOM.hydrate(<Root />, testDocument);
+    let component;
+    await act(() => {
+      ReactDOMClient.hydrateRoot(
+        testDocument,
+        <Root ref={current => (component = current)} />,
+      );
+    });
 
     expect(component.htmlRef.current.tagName).toBe('HTML');
     expect(component.headRef.current.tagName).toBe('HEAD');
@@ -247,7 +291,7 @@ describe('ReactTestUtils', () => {
     expect(ReactTestUtils.isDOMComponent(component.bodyRef.current)).toBe(true);
   });
 
-  it('can scry with stateless components involved', () => {
+  it('can scry with stateless components involved', async () => {
     const Function = () => (
       <div>
         <hr />
@@ -265,7 +309,13 @@ describe('ReactTestUtils', () => {
       }
     }
 
-    const inst = ReactTestUtils.renderIntoDocument(<SomeComponent />);
+    const container = document.createElement('div');
+    const root = ReactDOMClient.createRoot(container);
+    let inst;
+    await act(() => {
+      root.render(<SomeComponent ref={current => (inst = current)} />);
+    });
+
     const hrs = ReactTestUtils.scryRenderedDOMComponentsWithTag(inst, 'hr');
     expect(hrs.length).toBe(2);
   });
@@ -327,18 +377,19 @@ describe('ReactTestUtils', () => {
   });
 
   describe('Simulate', () => {
-    it('should change the value of an input field', () => {
+    it('should change the value of an input field', async () => {
       const obj = {
         handler: function (e) {
           e.persist();
         },
       };
       spyOnDevAndProd(obj, 'handler');
       const container = document.createElement('div');
-      const node = ReactDOM.render(
-        <input type="text" onChange={obj.handler} />,
-        container,
-      );
+      const root = ReactDOMClient.createRoot(container);
+      await act(() => {
+        root.render(<input type="text" onChange={obj.handler} />);
+      });
+      const node = container.firstChild;
 
       node.value = 'giraffe';
       ReactTestUtils.Simulate.change(node);
@@ -348,7 +399,7 @@ describe('ReactTestUtils', () => {
       );
     });
 
-    it('should change the value of an input field in a component', () => {
+    it('should change the value of an input field in a component', async () => {
       class SomeComponent extends React.Component {
         inputRef = React.createRef();
         render() {
@@ -371,10 +422,16 @@ describe('ReactTestUtils', () => {
       };
       spyOnDevAndProd(obj, 'handler');
       const container = document.createElement('div');
-      const instance = ReactDOM.render(
-        <SomeComponent handleChange={obj.handler} />,
-        container,
-      );
+      const root = ReactDOMClient.createRoot(container);
+      let instance;
+      await act(() => {
+        root.render(
+          <SomeComponent
+            handleChange={obj.handler}
+            ref={current => (instance = current)}
+          />,
+        );
+      });
 
       const node = instance.inputRef.current;
       node.value = 'zebra';
@@ -385,44 +442,53 @@ describe('ReactTestUtils', () => {
       );
     });
 
-    it('should not warn when used with extra properties', () => {
+    it('should not warn when used with extra properties', async () => {
       const CLIENT_X = 100;
 
       class Component extends React.Component {
+        childRef = React.createRef();
         handleClick = e => {
           expect(e.clientX).toBe(CLIENT_X);
         };
 
         render() {
-          return <div onClick={this.handleClick} />;
+          return <div onClick={this.handleClick} ref={this.childRef} />;
         }
       }
 
       const element = document.createElement('div');
-      const instance = ReactDOM.render(<Component />, element);
-      ReactTestUtils.Simulate.click(ReactDOM.findDOMNode(instance), {
+      const root = ReactDOMClient.createRoot(element);
+      let instance;
+      await act(() => {
+        root.render(<Component ref={current => (instance = current)} />);
+      });
+
+      ReactTestUtils.Simulate.click(instance.childRef.current, {
         clientX: CLIENT_X,
       });
     });
 
-    it('should set the type of the event', () => {
+    it('should set the type of the event', async () => {
       let event;
       const stub = jest.fn().mockImplementation(e => {
         e.persist();
         event = e;
       });
 
       const container = document.createElement('div');
-      const instance = ReactDOM.render(<div onKeyDown={stub} />, container);
-      const node = ReactDOM.findDOMNode(instance);
+      const root = ReactDOMClient.createRoot(container);
+      let node;
+      await act(() => {
+        root.render(<div onKeyDown={stub} ref={current => (node = current)} />);
+      });
 
       ReactTestUtils.Simulate.keyDown(node);
 
       expect(event.type).toBe('keydown');
       expect(event.nativeEvent.type).toBe('keydown');
     });
 
-    it('should work with renderIntoDocument', () => {
+    it('should work with renderIntoDocument', async () => {
       const onChange = jest.fn();
 
       class MyComponent extends React.Component {
@@ -435,7 +501,13 @@ describe('ReactTestUtils', () => {
         }
       }
 
-      const instance = ReactTestUtils.renderIntoDocument(<MyComponent />);
+      const container = document.createElement('div');
+      const root = ReactDOMClient.createRoot(container);
+      let instance;
+      await act(() => {
+        root.render(<MyComponent ref={current => (instance = current)} />);
+      });
+
       const input = ReactTestUtils.findRenderedDOMComponentWithTag(
         instance,
         'input',
@@ -449,7 +521,7 @@ describe('ReactTestUtils', () => {
     });
   });
 
-  it('should call setState callback with no arguments', () => {
+  it('should call setState callback with no arguments', async () => {
     let mockArgs;
     class Component extends React.Component {
       componentDidMount() {
@@ -460,17 +532,28 @@ describe('ReactTestUtils', () => {
       }
     }
 
-    ReactTestUtils.renderIntoDocument(<Component />);
+    const container = document.createElement('div');
+    const root = ReactDOMClient.createRoot(container);
+    await act(() => {
+      root.render(<Component />);
+    });
+
     expect(mockArgs.length).toEqual(0);
   });
-  it('should find rendered component with type in document', () => {
+  it('should find rendered component with type in document', async () => {
     class MyComponent extends React.Component {
       render() {
         return true;
       }
     }
 
-    const instance = ReactTestUtils.renderIntoDocument(<MyComponent />);
+    const container = document.createElement('div');
+    const root = ReactDOMClient.createRoot(container);
+    let instance;
+    await act(() => {
+      root.render(<MyComponent ref={current => (instance = current)} />);
+    });
+
     const renderedComponentType = ReactTestUtils.findRenderedComponentWithType(
       instance,
       MyComponent,