mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Convert ReactComponent to createRoot (#28171)
This commit is contained in:
+185
-82
@@ -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 = <div ref="badDiv" />;
|
||||
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(<div ref="badDiv" />);
|
||||
}),
|
||||
).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] = <p key={1} />; // Mutation is illegal
|
||||
return <div>{props.children}</div>;
|
||||
}
|
||||
if (__DEV__) {
|
||||
expect(() => {
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await expect(
|
||||
act(() => {
|
||||
root.render(
|
||||
<Wrapper>
|
||||
<span key={0} />
|
||||
<span key={1} />
|
||||
<span key={2} />
|
||||
</Wrapper>,
|
||||
);
|
||||
}),
|
||||
).rejects.toThrowError(/Cannot assign to read only property.*/);
|
||||
} else {
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
|
||||
await act(() => {
|
||||
root.render(
|
||||
<Wrapper>
|
||||
<span key={0} />
|
||||
<span key={1} />
|
||||
<span key={2} />
|
||||
</Wrapper>,
|
||||
);
|
||||
}).toThrowError(/Cannot assign to read only property.*/);
|
||||
} else {
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<Wrapper>
|
||||
<span key={0} />
|
||||
<span key={1} />
|
||||
<span key={2} />
|
||||
</Wrapper>,
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
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] = <p key={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(
|
||||
<Wrapper>
|
||||
<span key={0} />
|
||||
<span key={1} />
|
||||
<span key={2} />
|
||||
</Wrapper>,
|
||||
);
|
||||
}),
|
||||
).rejects.toThrowError(/Cannot assign to read only property.*/);
|
||||
} else {
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
|
||||
await act(() => {
|
||||
root.render(
|
||||
<Wrapper>
|
||||
<span key={0} />
|
||||
<span key={1} />
|
||||
<span key={2} />
|
||||
</Wrapper>,
|
||||
);
|
||||
}).toThrowError(/Cannot assign to read only property.*/);
|
||||
} else {
|
||||
ReactTestUtils.renderIntoDocument(
|
||||
<Wrapper>
|
||||
<span key={0} />
|
||||
<span key={1} />
|
||||
<span key={2} />
|
||||
</Wrapper>,
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
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(<Component />);
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
root.render(<Component />);
|
||||
});
|
||||
}).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(<Parent child={<span />} />);
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await act(() => {
|
||||
root.render(<Parent child={<span />} />);
|
||||
});
|
||||
});
|
||||
|
||||
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(<Component />);
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await act(() => {
|
||||
root.render(<Component />);
|
||||
});
|
||||
|
||||
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(<Component />);
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await act(() => {
|
||||
root.render(<Component />);
|
||||
});
|
||||
|
||||
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(<Component />);
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
|
||||
await act(() => {
|
||||
root.render(<Component />);
|
||||
});
|
||||
|
||||
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(<Outer />, el);
|
||||
const root = ReactDOMClient.createRoot(el);
|
||||
await act(() => {
|
||||
root.render(<Outer />);
|
||||
});
|
||||
log.push('start update');
|
||||
ReactDOM.render(<Outer />, el);
|
||||
await act(() => {
|
||||
root.render(<Outer />);
|
||||
});
|
||||
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(<div />, 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(<X />)).toErrorDev(
|
||||
let container = document.createElement('div');
|
||||
let root = ReactDOMClient.createRoot(container);
|
||||
await expect(
|
||||
expect(async () => {
|
||||
await act(() => {
|
||||
root.render(<X />);
|
||||
});
|
||||
}).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(<Y />)).toErrorDev(
|
||||
container = document.createElement('div');
|
||||
root = ReactDOMClient.createRoot(container);
|
||||
await expect(
|
||||
expect(async () => {
|
||||
await act(() => {
|
||||
root.render(<Y />);
|
||||
});
|
||||
}).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 <Bar />;
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
expect(() => ReactTestUtils.renderIntoDocument(<Foo />)).toErrorDev(
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await expect(
|
||||
expect(async () => {
|
||||
await act(() => {
|
||||
root.render(<Foo />);
|
||||
});
|
||||
}).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: <span />,
|
||||
y: <span />,
|
||||
@@ -478,15 +550,18 @@ describe('ReactComponent', () => {
|
||||
};
|
||||
const element = <div>{[children]}</div>;
|
||||
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(<Foo />, container);
|
||||
}).toThrowError(
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await expect(
|
||||
act(() => {
|
||||
root.render(<Foo />);
|
||||
}),
|
||||
).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(<Foo />, container)).toErrorDev(
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
root.render(<Foo />);
|
||||
});
|
||||
}).toErrorDev(
|
||||
'Warning: Functions are not valid as a React child. This may happen if ' +
|
||||
'you return a Component instead of <Component /> 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(<Foo />, container)).toErrorDev(
|
||||
await expect(async () => {
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
|
||||
await act(() => {
|
||||
root.render(<Foo />);
|
||||
});
|
||||
}).toErrorDev(
|
||||
'Warning: Functions are not valid as a React child. This may happen if ' +
|
||||
'you return a Component instead of <Component /> 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 (
|
||||
<div>
|
||||
@@ -582,7 +671,12 @@ describe('ReactComponent', () => {
|
||||
);
|
||||
}
|
||||
const container = document.createElement('div');
|
||||
expect(() => ReactDOM.render(<Foo />, container)).toErrorDev(
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
root.render(<Foo />);
|
||||
});
|
||||
}).toErrorDev(
|
||||
'Warning: Functions are not valid as a React child. This may happen if ' +
|
||||
'you return a Component instead of <Component /> 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 <Bar>{() => 'Hello'}</Bar>;
|
||||
}
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(<Foo />, container);
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await act(() => {
|
||||
root.render(<Foo />);
|
||||
});
|
||||
|
||||
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(<Foo />, container);
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
root.render(<Foo ref={current => (component = current)} />);
|
||||
});
|
||||
}).toErrorDev([
|
||||
'Warning: Functions are not valid as a React child. This may happen if ' +
|
||||
'you return a Component instead of <Component /> 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'});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user