Repository: facebook/react
Pull request: Use createRoot in ReactEmptyComponent-test


Changed files:
filename: packages/react-dom/src/__tests__/ReactEmptyComponent-test.js
patch: @@ -11,31 +11,38 @@
 
 let React;
 let ReactDOM;
-let ReactTestUtils;
+let ReactDOMClient;
 let TogglingComponent;
+let act;
+let Scheduler;
+let assertLog;
 
-let log;
+let container;
 
 describe('ReactEmptyComponent', () => {
   beforeEach(() => {
     jest.resetModules();
 
     React = require('react');
     ReactDOM = require('react-dom');
-    ReactTestUtils = require('react-dom/test-utils');
+    ReactDOMClient = require('react-dom/client');
+    Scheduler = require('scheduler');
+    const InternalTestUtils = require('internal-test-utils');
+    act = InternalTestUtils.act;
+    assertLog = InternalTestUtils.assertLog;
 
-    log = jest.fn();
+    container = document.createElement('div');
 
     TogglingComponent = class extends React.Component {
       state = {component: this.props.firstComponent};
 
       componentDidMount() {
-        log(ReactDOM.findDOMNode(this));
+        Scheduler.log('mount ' + ReactDOM.findDOMNode(this)?.nodeName);
         this.setState({component: this.props.secondComponent});
       }
 
       componentDidUpdate() {
-        log(ReactDOM.findDOMNode(this));
+        Scheduler.log('update ' + ReactDOM.findDOMNode(this)?.nodeName);
       }
 
       render() {
@@ -47,40 +54,44 @@ describe('ReactEmptyComponent', () => {
 
   describe.each([null, undefined])('when %s', nullORUndefined => {
     it('should not throw when rendering', () => {
-      class Component extends React.Component {
-        render() {
-          return nullORUndefined;
-        }
+      function EmptyComponent() {
+        return nullORUndefined;
       }
 
-      expect(function () {
-        ReactTestUtils.renderIntoDocument(<Component />);
+      const root = ReactDOMClient.createRoot(container);
+
+      expect(() => {
+        ReactDOM.flushSync(() => {
+          root.render(<EmptyComponent />);
+        });
       }).not.toThrowError();
     });
 
-    it('should not produce child DOM nodes for nullish and false', () => {
-      class Component1 extends React.Component {
-        render() {
-          return nullORUndefined;
-        }
+    it('should not produce child DOM nodes for nullish and false', async () => {
+      function Component1() {
+        return nullORUndefined;
       }
 
-      class Component2 extends React.Component {
-        render() {
-          return false;
-        }
+      function Component2() {
+        return false;
       }
 
       const container1 = document.createElement('div');
-      ReactDOM.render(<Component1 />, container1);
+      const root1 = ReactDOMClient.createRoot(container1);
+      await act(() => {
+        root1.render(<Component1 />);
+      });
       expect(container1.children.length).toBe(0);
 
       const container2 = document.createElement('div');
-      ReactDOM.render(<Component2 />, container2);
+      const root2 = ReactDOMClient.createRoot(container2);
+      await act(() => {
+        root2.render(<Component2 />);
+      });
       expect(container2.children.length).toBe(0);
     });
 
-    it('should be able to switch between rendering nullish and a normal tag', () => {
+    it('should be able to switch between rendering nullish and a normal tag', async () => {
       const instance1 = (
         <TogglingComponent
           firstComponent={nullORUndefined}
@@ -94,54 +105,52 @@ describe('ReactEmptyComponent', () => {
         />
       );
 
-      ReactTestUtils.renderIntoDocument(instance1);
-      ReactTestUtils.renderIntoDocument(instance2);
-
-      expect(log).toHaveBeenCalledTimes(4);
-      expect(log).toHaveBeenNthCalledWith(1, null);
-      expect(log).toHaveBeenNthCalledWith(
-        2,
-        expect.objectContaining({tagName: 'DIV'}),
-      );
-      expect(log).toHaveBeenNthCalledWith(
-        3,
-        expect.objectContaining({tagName: 'DIV'}),
-      );
-      expect(log).toHaveBeenNthCalledWith(4, null);
+      const container2 = document.createElement('div');
+      const root1 = ReactDOMClient.createRoot(container);
+      await act(() => {
+        root1.render(instance1);
+      });
+
+      const root2 = ReactDOMClient.createRoot(container2);
+      await act(() => {
+        root2.render(instance2);
+      });
+
+      assertLog([
+        'mount undefined',
+        'update DIV',
+        'mount DIV',
+        'update undefined',
+      ]);
     });
 
-    it('should be able to switch in a list of children', () => {
+    it('should be able to switch in a list of children', async () => {
       const instance1 = (
         <TogglingComponent
           firstComponent={nullORUndefined}
           secondComponent={'div'}
         />
       );
 
-      ReactTestUtils.renderIntoDocument(
-        <div>
-          {instance1}
-          {instance1}
-          {instance1}
-        </div>,
-      );
-
-      expect(log).toHaveBeenCalledTimes(6);
-      expect(log).toHaveBeenNthCalledWith(1, null);
-      expect(log).toHaveBeenNthCalledWith(2, null);
-      expect(log).toHaveBeenNthCalledWith(3, null);
-      expect(log).toHaveBeenNthCalledWith(
-        4,
-        expect.objectContaining({tagName: 'DIV'}),
-      );
-      expect(log).toHaveBeenNthCalledWith(
-        5,
-        expect.objectContaining({tagName: 'DIV'}),
-      );
-      expect(log).toHaveBeenNthCalledWith(
-        6,
-        expect.objectContaining({tagName: 'DIV'}),
-      );
+      const root = ReactDOMClient.createRoot(container);
+      await act(() => {
+        root.render(
+          <div>
+            {instance1}
+            {instance1}
+            {instance1}
+          </div>,
+        );
+      });
+
+      assertLog([
+        'mount undefined',
+        'mount undefined',
+        'mount undefined',
+        'update DIV',
+        'update DIV',
+        'update DIV',
+      ]);
     });
 
     it('should distinguish between a script placeholder and an actual script tag', () => {
@@ -158,40 +167,39 @@ describe('ReactEmptyComponent', () => {
         />
       );
 
-      expect(function () {
-        ReactTestUtils.renderIntoDocument(instance1);
+      const root1 = ReactDOMClient.createRoot(container);
+      expect(() => {
+        ReactDOM.flushSync(() => {
+          root1.render(instance1);
+        });
       }).not.toThrow();
-      expect(function () {
-        ReactTestUtils.renderIntoDocument(instance2);
+
+      const container2 = document.createElement('div');
+      const root2 = ReactDOMClient.createRoot(container2);
+      expect(() => {
+        ReactDOM.flushSync(() => {
+          root2.render(instance2);
+        });
       }).not.toThrow();
 
-      expect(log).toHaveBeenCalledTimes(4);
-      expect(log).toHaveBeenNthCalledWith(1, null);
-      expect(log).toHaveBeenNthCalledWith(
-        2,
-        expect.objectContaining({tagName: 'SCRIPT'}),
-      );
-      expect(log).toHaveBeenNthCalledWith(
-        3,
-        expect.objectContaining({tagName: 'SCRIPT'}),
-      );
-      expect(log).toHaveBeenNthCalledWith(4, null);
+      assertLog([
+        'mount undefined',
+        'update SCRIPT',
+        'mount SCRIPT',
+        'update undefined',
+      ]);
     });
 
     it(
       'should have findDOMNode return null when multiple layers of composite ' +
         'components render to the same nullish placeholder',
       () => {
-        class GrandChild extends React.Component {
-          render() {
-            return nullORUndefined;
-          }
+        function GrandChild() {
+          return nullORUndefined;
         }
 
-        class Child extends React.Component {
-          render() {
-            return <GrandChild />;
-          }
+        function Child() {
+          return <GrandChild />;
         }
 
         const instance1 = (
@@ -201,29 +209,32 @@ describe('ReactEmptyComponent', () => {
           <TogglingComponent firstComponent={Child} secondComponent={'div'} />
         );
 
-        expect(function () {
-          ReactTestUtils.renderIntoDocument(instance1);
+        const root1 = ReactDOMClient.createRoot(container);
+        expect(() => {
+          ReactDOM.flushSync(() => {
+            root1.render(instance1);
+          });
         }).not.toThrow();
-        expect(function () {
-          ReactTestUtils.renderIntoDocument(instance2);
+
+        const container2 = document.createElement('div');
+        const root2 = ReactDOMClient.createRoot(container2);
+        expect(() => {
+          ReactDOM.flushSync(() => {
+            root2.render(instance2);
+          });
         }).not.toThrow();
 
-        expect(log).toHaveBeenCalledTimes(4);
-        expect(log).toHaveBeenNthCalledWith(
-          1,
-          expect.objectContaining({tagName: 'DIV'}),
-        );
-        expect(log).toHaveBeenNthCalledWith(2, null);
-        expect(log).toHaveBeenNthCalledWith(3, null);
-        expect(log).toHaveBeenNthCalledWith(
-          4,
-          expect.objectContaining({tagName: 'DIV'}),
-        );
+        assertLog([
+          'mount DIV',
+          'update undefined',
+          'mount undefined',
+          'update DIV',
+        ]);
       },
     );
 
-    it('works when switching components', () => {
-      let assertions = 0;
+    it('works when switching components', async () => {
+      let innerRef;
 
       class Inner extends React.Component {
         render() {
@@ -234,44 +245,51 @@ describe('ReactEmptyComponent', () => {
           // Make sure the DOM node resolves properly even if we're replacing a
           // `null` component
           expect(ReactDOM.findDOMNode(this)).not.toBe(null);
-          assertions++;
         }
 
         componentWillUnmount() {
           // Even though we're getting replaced by `null`, we haven't been
           // replaced yet!
           expect(ReactDOM.findDOMNode(this)).not.toBe(null);
-          assertions++;
         }
       }
 
-      class Wrapper extends React.Component {
-        render() {
-          return this.props.showInner ? <Inner /> : nullORUndefined;
-        }
+      function Wrapper({showInner}) {
+        innerRef = React.createRef(null);
+        return showInner ? <Inner ref={innerRef} /> : nullORUndefined;
       }
 
       const el = document.createElement('div');
-      let component;
 
       // Render the <Inner /> component...
-      component = ReactDOM.render(<Wrapper showInner={true} />, el);
-      expect(ReactDOM.findDOMNode(component)).not.toBe(null);
+      const root = ReactDOMClient.createRoot(el);
+      await act(() => {
+        root.render(<Wrapper showInner={true} />);
+      });
+      expect(innerRef.current).not.toBe(null);
 
       // Switch to null...
-      component = ReactDOM.render(<Wrapper showInner={false} />, el);
-      expect(ReactDOM.findDOMNode(component)).toBe(null);
+      await act(() => {
+        root.render(<Wrapper showInner={false} />);
+      });
+      expect(innerRef.current).toBe(null);
 
       // ...then switch back.
-      component = ReactDOM.render(<Wrapper showInner={true} />, el);
-      expect(ReactDOM.findDOMNode(component)).not.toBe(null);
+      await act(() => {
+        root.render(<Wrapper showInner={true} />);
+      });
+      expect(innerRef.current).not.toBe(null);
 
-      expect(assertions).toBe(3);
+      expect.assertions(6);
     });
 
-    it('can render nullish at the top level', () => {
+    it('can render nullish at the top level', async () => {
       const div = document.createElement('div');
-      ReactDOM.render(nullORUndefined, div);
+      const root = ReactDOMClient.createRoot(div);
+
+      await act(() => {
+        root.render(nullORUndefined);
+      });
       expect(div.innerHTML).toBe('');
     });
 
@@ -308,26 +326,30 @@ describe('ReactEmptyComponent', () => {
         }
       }
 
-      expect(function () {
-        ReactTestUtils.renderIntoDocument(<Parent />);
+      const root = ReactDOMClient.createRoot(container);
+      expect(() => {
+        ReactDOM.flushSync(() => {
+          root.render(<Parent />);
+        });
       }).not.toThrow();
     });
 
-    it('preserves the dom node during updates', () => {
-      class Empty extends React.Component {
-        render() {
-          return nullORUndefined;
-        }
+    it('preserves the dom node during updates', async () => {
+      function Empty() {
+        return nullORUndefined;
       }
 
-      const container = document.createElement('div');
-
-      ReactDOM.render(<Empty />, container);
+      const root = ReactDOMClient.createRoot(container);
+      await act(() => {
+        root.render(<Empty />);
+      });
       const noscript1 = container.firstChild;
       expect(noscript1).toBe(null);
 
       // This update shouldn't create a DOM node
-      ReactDOM.render(<Empty />, container);
+      await act(() => {
+        root.render(<Empty />);
+      });
       const noscript2 = container.firstChild;
       expect(noscript2).toBe(null);
     });
@@ -338,8 +360,11 @@ describe('ReactEmptyComponent', () => {
       };
       const EmptyForwardRef = React.forwardRef(Empty);
 
+      const root = ReactDOMClient.createRoot(container);
       expect(() => {
-        ReactTestUtils.renderIntoDocument(<EmptyForwardRef />);
+        ReactDOM.flushSync(() => {
+          root.render(<EmptyForwardRef />);
+        });
       }).not.toThrowError();
     });
 
@@ -349,8 +374,11 @@ describe('ReactEmptyComponent', () => {
       };
       const EmptyMemo = React.memo(Empty);
 
+      const root = ReactDOMClient.createRoot(container);
       expect(() => {
-        ReactTestUtils.renderIntoDocument(<EmptyMemo />);
+        ReactDOM.flushSync(() => {
+          root.render(<EmptyMemo />);
+        });
       }).not.toThrowError();
     });
   });