diff --git a/packages/react-dom/src/__tests__/ReactComponent-test.js b/packages/react-dom/src/__tests__/ReactComponent-test.js index 2d467f82c0d7c..a66f115624e65 100644 --- a/packages/react-dom/src/__tests__/ReactComponent-test.js +++ b/packages/react-dom/src/__tests__/ReactComponent-test.js @@ -11,8 +11,9 @@ let React; let ReactDOM; +let ReactDOMClient; let ReactDOMServer; -let ReactTestUtils; +let act; describe('ReactComponent', () => { beforeEach(() => { @@ -20,11 +21,12 @@ describe('ReactComponent', () => { React = require('react'); ReactDOM = require('react-dom'); + ReactDOMClient = require('react-dom/client'); ReactDOMServer = require('react-dom/server'); - ReactTestUtils = require('react-dom/test-utils'); + act = require('internal-test-utils').act; }); - it('should throw on invalid render targets', () => { + it('should throw on invalid render targets in legacy roots', () => { const container = document.createElement('div'); // jQuery objects are basically arrays; people often pass them in by mistake expect(function () { @@ -36,40 +38,52 @@ describe('ReactComponent', () => { }).toThrowError(/Target container is not a DOM element./); }); - it('should throw when supplying a string ref outside of render method', () => { - let instance =
; - expect(function () { - instance = ReactTestUtils.renderIntoDocument(instance); - }).toThrow(); + it('should throw when supplying a string ref outside of render method', async () => { + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + await expect( + act(() => { + root.render(
); + }), + ).rejects.toThrow(); }); - it('should throw (in dev) when children are mutated during render', () => { + it('should throw (in dev) when children are mutated during render', async () => { function Wrapper(props) { props.children[1] =

; // Mutation is illegal return

{props.children}
; } if (__DEV__) { - expect(() => { - ReactTestUtils.renderIntoDocument( + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + await expect( + act(() => { + root.render( + + + + + , + ); + }), + ).rejects.toThrowError(/Cannot assign to read only property.*/); + } else { + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + + await act(() => { + root.render( , ); - }).toThrowError(/Cannot assign to read only property.*/); - } else { - ReactTestUtils.renderIntoDocument( - - - - - , - ); + }); } }); - it('should throw (in dev) when children are mutated during update', () => { + it('should throw (in dev) when children are mutated during update', async () => { class Wrapper extends React.Component { componentDidMount() { this.props.children[1] =

; // Mutation is illegal @@ -82,27 +96,36 @@ describe('ReactComponent', () => { } if (__DEV__) { - expect(() => { - ReactTestUtils.renderIntoDocument( + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + await expect( + act(() => { + root.render( + + + + + , + ); + }), + ).rejects.toThrowError(/Cannot assign to read only property.*/); + } else { + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + + await act(() => { + root.render( , ); - }).toThrowError(/Cannot assign to read only property.*/); - } else { - ReactTestUtils.renderIntoDocument( - - - - - , - ); + }); } }); - it('should support string refs on owned components', () => { + it('should support string refs on owned components', async () => { const innerObj = {}; const outerObj = {}; @@ -133,8 +156,12 @@ describe('ReactComponent', () => { } } - expect(() => { - ReactTestUtils.renderIntoDocument(); + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + await expect(async () => { + await act(() => { + root.render(); + }); }).toErrorDev([ 'Warning: Component "div" contains the string ref "inner". ' + 'Support for string refs will be removed in a future major release. ' + @@ -151,7 +178,7 @@ describe('ReactComponent', () => { ]); }); - it('should not have string refs on unmounted components', () => { + it('should not have string refs on unmounted components', async () => { class Parent extends React.Component { render() { return ( @@ -172,10 +199,14 @@ describe('ReactComponent', () => { } } - ReactTestUtils.renderIntoDocument(} />); + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render(} />); + }); }); - it('should support callback-style refs', () => { + it('should support callback-style refs', async () => { const innerObj = {}; const outerObj = {}; @@ -211,11 +242,16 @@ describe('ReactComponent', () => { } } - ReactTestUtils.renderIntoDocument(); + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render(); + }); + expect(mounted).toBe(true); }); - it('should support object-style refs', () => { + it('should support object-style refs', async () => { const innerObj = {}; const outerObj = {}; @@ -254,11 +290,16 @@ describe('ReactComponent', () => { } } - ReactTestUtils.renderIntoDocument(); + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render(); + }); + expect(mounted).toBe(true); }); - it('should support new-style refs with mixed-up owners', () => { + it('should support new-style refs with mixed-up owners', async () => { class Wrapper extends React.Component { getTitle = () => { return this.props.title; @@ -296,11 +337,17 @@ describe('ReactComponent', () => { } } - ReactTestUtils.renderIntoDocument(); + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + + await act(() => { + root.render(); + }); + expect(mounted).toBe(true); }); - it('should call refs at the correct time', () => { + it('should call refs at the correct time', async () => { const log = []; class Inner extends React.Component { @@ -358,11 +405,18 @@ describe('ReactComponent', () => { // mount, update, unmount const el = document.createElement('div'); log.push('start mount'); - ReactDOM.render(, el); + const root = ReactDOMClient.createRoot(el); + await act(() => { + root.render(); + }); log.push('start update'); - ReactDOM.render(, el); + await act(() => { + root.render(); + }); log.push('start unmount'); - ReactDOM.unmountComponentAtNode(el); + await act(() => { + root.unmount(); + }); /* eslint-disable indent */ expect(log).toEqual([ @@ -396,7 +450,7 @@ describe('ReactComponent', () => { /* eslint-enable indent */ }); - it('fires the callback after a component is rendered', () => { + it('fires the callback after a component is rendered in legacy roots', () => { const callback = jest.fn(); const container = document.createElement('div'); ReactDOM.render(

, container, callback); @@ -407,14 +461,20 @@ describe('ReactComponent', () => { expect(callback).toHaveBeenCalledTimes(3); }); - it('throws usefully when rendering badly-typed elements', () => { + it('throws usefully when rendering badly-typed elements', async () => { const X = undefined; - expect(() => { - expect(() => ReactTestUtils.renderIntoDocument()).toErrorDev( + let container = document.createElement('div'); + let root = ReactDOMClient.createRoot(container); + await expect( + expect(async () => { + await act(() => { + root.render(); + }); + }).toErrorDev( 'React.createElement: type is invalid -- expected a string (for built-in components) ' + 'or a class/function (for composite components) but got: undefined.', - ); - }).toThrowError( + ), + ).rejects.toThrowError( 'Element type is invalid: expected a string (for built-in components) ' + 'or a class/function (for composite components) but got: undefined.' + (__DEV__ @@ -424,18 +484,24 @@ describe('ReactComponent', () => { ); const Y = null; - expect(() => { - expect(() => ReactTestUtils.renderIntoDocument()).toErrorDev( + container = document.createElement('div'); + root = ReactDOMClient.createRoot(container); + await expect( + expect(async () => { + await act(() => { + root.render(); + }); + }).toErrorDev( 'React.createElement: type is invalid -- expected a string (for built-in components) ' + 'or a class/function (for composite components) but got: null.', - ); - }).toThrowError( + ), + ).rejects.toThrowError( 'Element type is invalid: expected a string (for built-in components) ' + 'or a class/function (for composite components) but got: null.', ); }); - it('includes owner name in the error about badly-typed elements', () => { + it('includes owner name in the error about badly-typed elements', async () => { const X = undefined; function Indirection(props) { @@ -454,12 +520,18 @@ describe('ReactComponent', () => { return ; } - expect(() => { - expect(() => ReactTestUtils.renderIntoDocument()).toErrorDev( + const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + await expect( + expect(async () => { + await act(() => { + root.render(); + }); + }).toErrorDev( 'React.createElement: type is invalid -- expected a string (for built-in components) ' + 'or a class/function (for composite components) but got: undefined.', - ); - }).toThrowError( + ), + ).rejects.toThrowError( 'Element type is invalid: expected a string (for built-in components) ' + 'or a class/function (for composite components) but got: undefined.' + (__DEV__ @@ -470,7 +542,7 @@ describe('ReactComponent', () => { ); }); - it('throws if a plain object is used as a child', () => { + it('throws if a plain object is used as a child', async () => { const children = { x: , y: , @@ -478,15 +550,18 @@ describe('ReactComponent', () => { }; const element =
{[children]}
; const container = document.createElement('div'); - expect(() => { - ReactDOM.render(element, container); - }).toThrowError( + const root = ReactDOMClient.createRoot(container); + await expect( + act(() => { + root.render(element); + }), + ).rejects.toThrowError( 'Objects are not valid as a React child (found: object with keys {x, y, z}). ' + 'If you meant to render a collection of children, use an array instead.', ); }); - it('throws if a plain object even if it is in an owner', () => { + it('throws if a plain object even if it is in an owner', async () => { class Foo extends React.Component { render() { const children = { @@ -498,9 +573,12 @@ describe('ReactComponent', () => { } } const container = document.createElement('div'); - expect(() => { - ReactDOM.render(, container); - }).toThrowError( + const root = ReactDOMClient.createRoot(container); + await expect( + act(() => { + root.render(); + }), + ).rejects.toThrowError( 'Objects are not valid as a React child (found: object with keys {a, b, c}).' + ' If you meant to render a collection of children, use an array ' + 'instead.', @@ -545,12 +623,17 @@ describe('ReactComponent', () => { }); describe('with new features', () => { - it('warns on function as a return value from a function', () => { + it('warns on function as a return value from a function', async () => { function Foo() { return Foo; } const container = document.createElement('div'); - expect(() => ReactDOM.render(, container)).toErrorDev( + const root = ReactDOMClient.createRoot(container); + await expect(async () => { + await act(() => { + root.render(); + }); + }).toErrorDev( 'Warning: Functions are not valid as a React child. This may happen if ' + 'you return a Component instead of from render. ' + 'Or maybe you meant to call this function rather than return it.\n' + @@ -558,14 +641,20 @@ describe('ReactComponent', () => { ); }); - it('warns on function as a return value from a class', () => { + it('warns on function as a return value from a class', async () => { class Foo extends React.Component { render() { return Foo; } } const container = document.createElement('div'); - expect(() => ReactDOM.render(, container)).toErrorDev( + await expect(async () => { + const root = ReactDOMClient.createRoot(container); + + await act(() => { + root.render(); + }); + }).toErrorDev( 'Warning: Functions are not valid as a React child. This may happen if ' + 'you return a Component instead of from render. ' + 'Or maybe you meant to call this function rather than return it.\n' + @@ -573,7 +662,7 @@ describe('ReactComponent', () => { ); }); - it('warns on function as a child to host component', () => { + it('warns on function as a child to host component', async () => { function Foo() { return (
@@ -582,7 +671,12 @@ describe('ReactComponent', () => { ); } const container = document.createElement('div'); - expect(() => ReactDOM.render(, container)).toErrorDev( + const root = ReactDOMClient.createRoot(container); + await expect(async () => { + await act(() => { + root.render(); + }); + }).toErrorDev( 'Warning: Functions are not valid as a React child. This may happen if ' + 'you return a Component instead of from render. ' + 'Or maybe you meant to call this function rather than return it.\n' + @@ -592,7 +686,7 @@ describe('ReactComponent', () => { ); }); - it('does not warn for function-as-a-child that gets resolved', () => { + it('does not warn for function-as-a-child that gets resolved', async () => { function Bar(props) { return props.children(); } @@ -600,11 +694,15 @@ describe('ReactComponent', () => { return {() => 'Hello'}; } const container = document.createElement('div'); - ReactDOM.render(, container); + const root = ReactDOMClient.createRoot(container); + await act(() => { + root.render(); + }); + expect(container.innerHTML).toBe('Hello'); }); - it('deduplicates function type warnings based on component type', () => { + it('deduplicates function type warnings based on component type', async () => { class Foo extends React.PureComponent { constructor() { super(); @@ -624,9 +722,12 @@ describe('ReactComponent', () => { } } const container = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); let component; - expect(() => { - component = ReactDOM.render(, container); + await expect(async () => { + await act(() => { + root.render( (component = current)} />); + }); }).toErrorDev([ 'Warning: Functions are not valid as a React child. This may happen if ' + 'you return a Component instead of from render. ' + @@ -640,7 +741,9 @@ describe('ReactComponent', () => { ' in div (at **)\n' + ' in Foo (at **)', ]); - component.setState({type: 'portobello mushrooms'}); + await act(() => { + component.setState({type: 'portobello mushrooms'}); + }); }); }); });