mirror of
https://github.com/facebook/react.git
synced 2026-02-23 20:23:02 +00:00
[assert helpers] react-dom (pt3) (#31983)
moar assert helpers this finishes all of react-dom except the server integration tests which are tricky to convert
This commit is contained in:
@@ -14,12 +14,15 @@ describe('ReactDOMInvalidARIAHook', () => {
|
||||
let ReactDOMClient;
|
||||
let mountComponent;
|
||||
let act;
|
||||
let assertConsoleErrorDev;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactDOMClient = require('react-dom/client');
|
||||
act = require('internal-test-utils').act;
|
||||
assertConsoleErrorDev =
|
||||
require('internal-test-utils').assertConsoleErrorDev;
|
||||
|
||||
mountComponent = async function (props) {
|
||||
const container = document.createElement('div');
|
||||
@@ -35,46 +38,52 @@ describe('ReactDOMInvalidARIAHook', () => {
|
||||
await mountComponent({'aria-label': 'Bumble bees'});
|
||||
});
|
||||
it('should warn for one invalid aria-* prop', async () => {
|
||||
await expect(() => mountComponent({'aria-badprop': 'maybe'})).toErrorDev(
|
||||
await mountComponent({'aria-badprop': 'maybe'});
|
||||
assertConsoleErrorDev([
|
||||
'Invalid aria prop `aria-badprop` on <div> tag. ' +
|
||||
'For details, see https://react.dev/link/invalid-aria-props',
|
||||
);
|
||||
'For details, see https://react.dev/link/invalid-aria-props\n' +
|
||||
' in div (at **)',
|
||||
]);
|
||||
});
|
||||
it('should warn for many invalid aria-* props', async () => {
|
||||
await expect(() =>
|
||||
mountComponent({
|
||||
'aria-badprop': 'Very tall trees',
|
||||
'aria-malprop': 'Turbulent seas',
|
||||
}),
|
||||
).toErrorDev(
|
||||
await mountComponent({
|
||||
'aria-badprop': 'Very tall trees',
|
||||
'aria-malprop': 'Turbulent seas',
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'Invalid aria props `aria-badprop`, `aria-malprop` on <div> ' +
|
||||
'tag. For details, see https://react.dev/link/invalid-aria-props',
|
||||
);
|
||||
'tag. For details, see https://react.dev/link/invalid-aria-props\n' +
|
||||
' in div (at **)',
|
||||
]);
|
||||
});
|
||||
it('should warn for an improperly cased aria-* prop', async () => {
|
||||
// The valid attribute name is aria-haspopup.
|
||||
await expect(() => mountComponent({'aria-hasPopup': 'true'})).toErrorDev(
|
||||
await mountComponent({'aria-hasPopup': 'true'});
|
||||
assertConsoleErrorDev([
|
||||
'Unknown ARIA attribute `aria-hasPopup`. ' +
|
||||
'Did you mean `aria-haspopup`?',
|
||||
);
|
||||
'Did you mean `aria-haspopup`?\n' +
|
||||
' in div (at **)',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should warn for use of recognized camel case aria attributes', async () => {
|
||||
// The valid attribute name is aria-haspopup.
|
||||
await expect(() => mountComponent({ariaHasPopup: 'true'})).toErrorDev(
|
||||
await mountComponent({ariaHasPopup: 'true'});
|
||||
assertConsoleErrorDev([
|
||||
'Invalid ARIA attribute `ariaHasPopup`. ' +
|
||||
'Did you mean `aria-haspopup`?',
|
||||
);
|
||||
'Did you mean `aria-haspopup`?\n' +
|
||||
' in div (at **)',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should warn for use of unrecognized camel case aria attributes', async () => {
|
||||
// The valid attribute name is aria-haspopup.
|
||||
await expect(() =>
|
||||
mountComponent({ariaSomethingInvalid: 'true'}),
|
||||
).toErrorDev(
|
||||
await mountComponent({ariaSomethingInvalid: 'true'});
|
||||
assertConsoleErrorDev([
|
||||
'Invalid ARIA attribute `ariaSomethingInvalid`. ARIA ' +
|
||||
'attributes follow the pattern aria-* and must be lowercase.',
|
||||
);
|
||||
'attributes follow the pattern aria-* and must be lowercase.\n' +
|
||||
' in div (at **)',
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,10 +13,13 @@ describe('ReactDOMComponentTree', () => {
|
||||
let React;
|
||||
let ReactDOM;
|
||||
let container;
|
||||
let assertConsoleErrorDev;
|
||||
|
||||
beforeEach(() => {
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
assertConsoleErrorDev =
|
||||
require('internal-test-utils').assertConsoleErrorDev;
|
||||
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
@@ -31,11 +34,14 @@ describe('ReactDOMComponentTree', () => {
|
||||
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.',
|
||||
ReactDOM.unmountComponentAtNode(node);
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
"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},
|
||||
);
|
||||
});
|
||||
@@ -49,11 +55,14 @@ describe('ReactDOMComponentTree', () => {
|
||||
);
|
||||
const anotherComponent = <div />;
|
||||
const instance = ReactDOM.render(component, container);
|
||||
expect(() => ReactDOM.render(anotherComponent, instance)).toErrorDev(
|
||||
'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.',
|
||||
ReactDOM.render(anotherComponent, instance);
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
'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},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -13,11 +13,14 @@ const React = require('react');
|
||||
const ReactDOM = require('react-dom');
|
||||
const PropTypes = require('prop-types');
|
||||
let act;
|
||||
let assertConsoleErrorDev;
|
||||
describe('ReactDOMLegacyFiber', () => {
|
||||
let container;
|
||||
|
||||
beforeEach(() => {
|
||||
act = require('internal-test-utils').act;
|
||||
assertConsoleErrorDev =
|
||||
require('internal-test-utils').assertConsoleErrorDev;
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
});
|
||||
@@ -786,9 +789,8 @@ describe('ReactDOMLegacyFiber', () => {
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
ReactDOM.render(<Parent />, container);
|
||||
}).toErrorDev([
|
||||
ReactDOM.render(<Parent />, container);
|
||||
assertConsoleErrorDev([
|
||||
'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
|
||||
'Component uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
|
||||
]);
|
||||
@@ -834,10 +836,8 @@ describe('ReactDOMLegacyFiber', () => {
|
||||
}
|
||||
}
|
||||
|
||||
let instance;
|
||||
expect(() => {
|
||||
instance = ReactDOM.render(<Parent />, container);
|
||||
}).toErrorDev([
|
||||
const instance = ReactDOM.render(<Parent />, container);
|
||||
assertConsoleErrorDev([
|
||||
'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
|
||||
'Component uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
|
||||
]);
|
||||
@@ -882,9 +882,8 @@ describe('ReactDOMLegacyFiber', () => {
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
ReactDOM.render(<Parent bar="initial" />, container);
|
||||
}).toErrorDev([
|
||||
ReactDOM.render(<Parent bar="initial" />, container);
|
||||
assertConsoleErrorDev([
|
||||
'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
|
||||
'Component uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
|
||||
]);
|
||||
@@ -1117,11 +1116,12 @@ describe('ReactDOMLegacyFiber', () => {
|
||||
return <div onClick="woops" />;
|
||||
}
|
||||
}
|
||||
expect(() => ReactDOM.render(<Example />, container)).toErrorDev(
|
||||
ReactDOM.render(<Example />, container);
|
||||
assertConsoleErrorDev([
|
||||
'Expected `onClick` listener to be a function, instead got a value of `string` type.\n' +
|
||||
' in div (at **)\n' +
|
||||
' in Example (at **)',
|
||||
);
|
||||
]);
|
||||
});
|
||||
|
||||
// @gate !disableLegacyMode
|
||||
@@ -1131,13 +1131,14 @@ describe('ReactDOMLegacyFiber', () => {
|
||||
return <div onClick={false} />;
|
||||
}
|
||||
}
|
||||
expect(() => ReactDOM.render(<Example />, container)).toErrorDev(
|
||||
ReactDOM.render(<Example />, container);
|
||||
assertConsoleErrorDev([
|
||||
'Expected `onClick` listener to be a function, instead got `false`.\n\n' +
|
||||
'If you used to conditionally omit it with onClick={condition && value}, ' +
|
||||
'pass onClick={condition ? value : undefined} instead.\n' +
|
||||
' in div (at **)\n' +
|
||||
' in Example (at **)',
|
||||
);
|
||||
]);
|
||||
});
|
||||
|
||||
// @gate !disableLegacyMode
|
||||
@@ -1270,17 +1271,18 @@ describe('ReactDOMLegacyFiber', () => {
|
||||
container.innerHTML = '<div>MEOW.</div>';
|
||||
|
||||
await expect(async () => {
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
ReactDOM.render(<div key="2">baz</div>, container);
|
||||
});
|
||||
}).rejects.toThrow('The node to be removed is not a child of this node.');
|
||||
}).toErrorDev(
|
||||
'' +
|
||||
'It looks like the React-rendered content of this container was ' +
|
||||
'removed without using React. This is not supported and will ' +
|
||||
'cause errors. Instead, call ReactDOM.unmountComponentAtNode ' +
|
||||
'to empty a container.',
|
||||
await act(() => {
|
||||
ReactDOM.render(<div key="2">baz</div>, container);
|
||||
});
|
||||
}).rejects.toThrow('The node to be removed is not a child of this node.');
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
'' +
|
||||
'It looks like the React-rendered content of this container was ' +
|
||||
'removed without using React. This is not supported and will ' +
|
||||
'cause errors. Instead, call ReactDOM.unmountComponentAtNode ' +
|
||||
'to empty a container.',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
@@ -1293,12 +1295,15 @@ describe('ReactDOMLegacyFiber', () => {
|
||||
expect(container.innerHTML).toBe('<div>bar</div>');
|
||||
// then we mess with the DOM before an update
|
||||
container.innerHTML = '<div>MEOW.</div>';
|
||||
expect(() => ReactDOM.render(<div>baz</div>, container)).toErrorDev(
|
||||
'' +
|
||||
'It looks like the React-rendered content of this container was ' +
|
||||
'removed without using React. This is not supported and will ' +
|
||||
'cause errors. Instead, call ReactDOM.unmountComponentAtNode ' +
|
||||
'to empty a container.',
|
||||
ReactDOM.render(<div>baz</div>, container);
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
'' +
|
||||
'It looks like the React-rendered content of this container was ' +
|
||||
'removed without using React. This is not supported and will ' +
|
||||
'cause errors. Instead, call ReactDOM.unmountComponentAtNode ' +
|
||||
'to empty a container.',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
@@ -1311,12 +1316,15 @@ describe('ReactDOMLegacyFiber', () => {
|
||||
expect(container.innerHTML).toBe('<div>bar</div>');
|
||||
// then we mess with the DOM before an update
|
||||
container.innerHTML = '';
|
||||
expect(() => ReactDOM.render(<div>baz</div>, container)).toErrorDev(
|
||||
'' +
|
||||
'It looks like the React-rendered content of this container was ' +
|
||||
'removed without using React. This is not supported and will ' +
|
||||
'cause errors. Instead, call ReactDOM.unmountComponentAtNode ' +
|
||||
'to empty a container.',
|
||||
ReactDOM.render(<div>baz</div>, container);
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
'' +
|
||||
'It looks like the React-rendered content of this container was ' +
|
||||
'removed without using React. This is not supported and will ' +
|
||||
'cause errors. Instead, call ReactDOM.unmountComponentAtNode ' +
|
||||
'to empty a container.',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@ describe('ReactDOMOption', () => {
|
||||
let ReactDOMClient;
|
||||
let ReactDOMServer;
|
||||
let act;
|
||||
let assertConsoleErrorDev;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
@@ -21,6 +22,8 @@ describe('ReactDOMOption', () => {
|
||||
ReactDOMClient = require('react-dom/client');
|
||||
ReactDOMServer = require('react-dom/server');
|
||||
act = require('internal-test-utils').act;
|
||||
assertConsoleErrorDev =
|
||||
require('internal-test-utils').assertConsoleErrorDev;
|
||||
});
|
||||
|
||||
async function renderIntoDocument(children) {
|
||||
@@ -47,10 +50,8 @@ describe('ReactDOMOption', () => {
|
||||
{1} <div /> {2}
|
||||
</option>
|
||||
);
|
||||
let container;
|
||||
await expect(async () => {
|
||||
container = await renderIntoDocument(el);
|
||||
}).toErrorDev(
|
||||
const container = await renderIntoDocument(el);
|
||||
assertConsoleErrorDev([
|
||||
'In HTML, <div> cannot be a child of <option>.\n' +
|
||||
'This will cause a hydration error.\n' +
|
||||
'\n' +
|
||||
@@ -62,7 +63,7 @@ describe('ReactDOMOption', () => {
|
||||
(gate(flags => flags.enableOwnerStacks)
|
||||
? ''
|
||||
: '\n in option (at **)'),
|
||||
);
|
||||
]);
|
||||
expect(container.firstChild.innerHTML).toBe('1 <div></div> 2');
|
||||
await renderIntoDocument(el);
|
||||
});
|
||||
@@ -76,13 +77,12 @@ describe('ReactDOMOption', () => {
|
||||
{1} <Foo /> {3}
|
||||
</option>
|
||||
);
|
||||
let container;
|
||||
await expect(async () => {
|
||||
container = await renderIntoDocument(el);
|
||||
}).toErrorDev(
|
||||
const container = await renderIntoDocument(el);
|
||||
assertConsoleErrorDev([
|
||||
'Cannot infer the option value of complex children. ' +
|
||||
'Pass a `value` prop or use a plain string as children to <option>.',
|
||||
);
|
||||
'Pass a `value` prop or use a plain string as children to <option>.\n' +
|
||||
' in option (at **)',
|
||||
]);
|
||||
expect(container.firstChild.innerHTML).toBe('1 2 3');
|
||||
await renderIntoDocument(el);
|
||||
});
|
||||
@@ -187,13 +187,11 @@ describe('ReactDOMOption', () => {
|
||||
|
||||
it('should be able to use dangerouslySetInnerHTML on option', async () => {
|
||||
const stub = <option dangerouslySetInnerHTML={{__html: 'foobar'}} />;
|
||||
let container;
|
||||
await expect(async () => {
|
||||
container = await renderIntoDocument(stub);
|
||||
}).toErrorDev(
|
||||
const container = await renderIntoDocument(stub);
|
||||
assertConsoleErrorDev([
|
||||
'Pass a `value` prop if you set dangerouslyInnerHTML so React knows which value should be selected.\n' +
|
||||
' in option (at **)',
|
||||
);
|
||||
]);
|
||||
|
||||
expect(container.firstChild.innerHTML).toBe('foobar');
|
||||
});
|
||||
@@ -267,13 +265,12 @@ describe('ReactDOMOption', () => {
|
||||
expect(option.textContent).toBe('BarFooBaz');
|
||||
expect(option.selected).toBe(true);
|
||||
|
||||
await expect(async () => {
|
||||
await act(async () => {
|
||||
ReactDOMClient.hydrateRoot(container, children, {
|
||||
onRecoverableError: () => {},
|
||||
});
|
||||
await act(async () => {
|
||||
ReactDOMClient.hydrateRoot(container, children, {
|
||||
onRecoverableError: () => {},
|
||||
});
|
||||
}).toErrorDev(
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'In HTML, <div> cannot be a child of <option>.\n' +
|
||||
'This will cause a hydration error.\n' +
|
||||
'\n' +
|
||||
@@ -285,8 +282,8 @@ describe('ReactDOMOption', () => {
|
||||
' in div (at **)' +
|
||||
(gate(flags => flags.enableOwnerStacks)
|
||||
? ''
|
||||
: '\n in option (at **)'),
|
||||
);
|
||||
: '\n in option (at **)' + '\n in select (at **)'),
|
||||
]);
|
||||
option = container.firstChild.firstChild;
|
||||
|
||||
expect(option.textContent).toBe('BarFooBaz');
|
||||
|
||||
@@ -18,6 +18,7 @@ let act;
|
||||
let useEffect;
|
||||
let assertLog;
|
||||
let waitForAll;
|
||||
let assertConsoleErrorDev;
|
||||
|
||||
describe('ReactDOMRoot', () => {
|
||||
let container;
|
||||
@@ -31,6 +32,8 @@ describe('ReactDOMRoot', () => {
|
||||
ReactDOMServer = require('react-dom/server');
|
||||
Scheduler = require('scheduler');
|
||||
act = require('internal-test-utils').act;
|
||||
assertConsoleErrorDev =
|
||||
require('internal-test-utils').assertConsoleErrorDev;
|
||||
useEffect = React.useEffect;
|
||||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
@@ -48,9 +51,12 @@ describe('ReactDOMRoot', () => {
|
||||
it('warns if a callback parameter is provided to render', async () => {
|
||||
const callback = jest.fn();
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
expect(() => root.render(<div>Hi</div>, callback)).toErrorDev(
|
||||
'does not support the second callback argument. ' +
|
||||
'To execute a side effect after rendering, declare it in a component body with useEffect().',
|
||||
root.render(<div>Hi</div>, callback);
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
'does not support the second callback argument. ' +
|
||||
'To execute a side effect after rendering, declare it in a component body with useEffect().',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
await waitForAll([]);
|
||||
@@ -63,9 +69,12 @@ describe('ReactDOMRoot', () => {
|
||||
}
|
||||
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
expect(() => root.render(<App />, {})).toErrorDev(
|
||||
'You passed a second argument to root.render(...) but it only accepts ' +
|
||||
'one argument.',
|
||||
root.render(<App />, {});
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
'You passed a second argument to root.render(...) but it only accepts ' +
|
||||
'one argument.',
|
||||
],
|
||||
{
|
||||
withoutStack: true,
|
||||
},
|
||||
@@ -78,10 +87,13 @@ describe('ReactDOMRoot', () => {
|
||||
}
|
||||
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
expect(() => root.render(<App />, container)).toErrorDev(
|
||||
'You passed a container to the second argument of root.render(...). ' +
|
||||
"You don't need to pass it again since you already passed it to create " +
|
||||
'the root.',
|
||||
root.render(<App />, container);
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
'You passed a container to the second argument of root.render(...). ' +
|
||||
"You don't need to pass it again since you already passed it to create " +
|
||||
'the root.',
|
||||
],
|
||||
{
|
||||
withoutStack: true,
|
||||
},
|
||||
@@ -92,9 +104,12 @@ describe('ReactDOMRoot', () => {
|
||||
const callback = jest.fn();
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
root.render(<div>Hi</div>);
|
||||
expect(() => root.unmount(callback)).toErrorDev(
|
||||
'does not support a callback argument. ' +
|
||||
'To execute a side effect after rendering, declare it in a component body with useEffect().',
|
||||
root.unmount(callback);
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
'does not support a callback argument. ' +
|
||||
'To execute a side effect after rendering, declare it in a component body with useEffect().',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
await waitForAll([]);
|
||||
@@ -148,8 +163,27 @@ describe('ReactDOMRoot', () => {
|
||||
<span />
|
||||
</div>,
|
||||
);
|
||||
await expect(async () => await waitForAll([])).toErrorDev(
|
||||
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties.",
|
||||
await waitForAll([]);
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. " +
|
||||
"This won't be patched up. This can happen if a SSR-ed Client Component used:\n" +
|
||||
'\n' +
|
||||
"- A server/client branch `if (typeof window !== 'undefined')`.\n" +
|
||||
"- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n" +
|
||||
"- Date formatting in a user's locale which doesn't match the server.\n" +
|
||||
'- External changing data without sending a snapshot of it along with the HTML.\n' +
|
||||
'- Invalid HTML tag nesting.\n' +
|
||||
'\n' +
|
||||
'It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.\n' +
|
||||
'\n' +
|
||||
'https://react.dev/link/hydration-mismatch\n' +
|
||||
'\n' +
|
||||
' <div>\n' +
|
||||
' <span\n' +
|
||||
'- className="extra"\n' +
|
||||
' >\n',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
@@ -183,12 +217,13 @@ describe('ReactDOMRoot', () => {
|
||||
|
||||
it('warns when creating two roots managing the same container', () => {
|
||||
ReactDOMClient.createRoot(container);
|
||||
expect(() => {
|
||||
ReactDOMClient.createRoot(container);
|
||||
}).toErrorDev(
|
||||
'You are calling ReactDOMClient.createRoot() on a container that ' +
|
||||
'has already been passed to createRoot() before. Instead, call ' +
|
||||
'root.render() on the existing root instead if you want to update it.',
|
||||
ReactDOMClient.createRoot(container);
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
'You are calling ReactDOMClient.createRoot() on a container that ' +
|
||||
'has already been passed to createRoot() before. Instead, call ' +
|
||||
'root.render() on the existing root instead if you want to update it.',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
@@ -352,14 +387,15 @@ describe('ReactDOMRoot', () => {
|
||||
});
|
||||
expect(container1.textContent).toEqual('Hi');
|
||||
|
||||
expect(() => {
|
||||
ReactDOM.flushSync(() => {
|
||||
root1.render(<App step={2} />);
|
||||
});
|
||||
}).toErrorDev(
|
||||
'Attempted to synchronously unmount a root while React was ' +
|
||||
'already rendering.',
|
||||
);
|
||||
ReactDOM.flushSync(() => {
|
||||
root1.render(<App step={2} />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'Attempted to synchronously unmount a root while React was already rendering. ' +
|
||||
'React cannot finish unmounting the root until the current render has completed, ' +
|
||||
'which may lead to a race condition.\n' +
|
||||
' in App (at **)',
|
||||
]);
|
||||
});
|
||||
|
||||
// @gate disableCommentsAsDOMContainers
|
||||
@@ -378,8 +414,12 @@ describe('ReactDOMRoot', () => {
|
||||
});
|
||||
|
||||
it('warn if no children passed to hydrateRoot', async () => {
|
||||
expect(() => ReactDOMClient.hydrateRoot(container)).toErrorDev(
|
||||
'Must provide initial children as second argument to hydrateRoot.',
|
||||
ReactDOMClient.hydrateRoot(container);
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
'Must provide initial children as second argument to hydrateRoot. ' +
|
||||
'Example usage: hydrateRoot(domContainer, <App />)',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
@@ -389,9 +429,15 @@ describe('ReactDOMRoot', () => {
|
||||
return 'Child';
|
||||
}
|
||||
|
||||
expect(() => ReactDOMClient.createRoot(container, <App />)).toErrorDev(
|
||||
'You passed a JSX element to createRoot. You probably meant to call ' +
|
||||
'root.render instead',
|
||||
ReactDOMClient.createRoot(container, <App />);
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
'You passed a JSX element to createRoot. You probably meant to call root.render instead. ' +
|
||||
'Example usage:\n' +
|
||||
'\n' +
|
||||
' let root = createRoot(domContainer);\n' +
|
||||
' root.render(<App />);',
|
||||
],
|
||||
{
|
||||
withoutStack: true,
|
||||
},
|
||||
@@ -405,15 +451,16 @@ describe('ReactDOMRoot', () => {
|
||||
|
||||
const root = ReactDOMClient.createRoot(document.createElement('div'));
|
||||
|
||||
expect(() => {
|
||||
ReactDOM.flushSync(() => {
|
||||
root.render(Component);
|
||||
});
|
||||
}).toErrorDev(
|
||||
'Functions are not valid as a React child. ' +
|
||||
'This may happen if you return Component instead of <Component /> from render. ' +
|
||||
'Or maybe you meant to call this function rather than return it.\n' +
|
||||
' root.render(Component)',
|
||||
ReactDOM.flushSync(() => {
|
||||
root.render(Component);
|
||||
});
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
'Functions are not valid as a React child. ' +
|
||||
'This may happen if you return Component instead of <Component /> from render. ' +
|
||||
'Or maybe you meant to call this function rather than return it.\n' +
|
||||
' root.render(Component)',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
@@ -421,13 +468,14 @@ describe('ReactDOMRoot', () => {
|
||||
it('warns when given a symbol', () => {
|
||||
const root = ReactDOMClient.createRoot(document.createElement('div'));
|
||||
|
||||
expect(() => {
|
||||
ReactDOM.flushSync(() => {
|
||||
root.render(Symbol('foo'));
|
||||
});
|
||||
}).toErrorDev(
|
||||
'Symbols are not valid as a React child.\n' +
|
||||
' root.render(Symbol(foo))',
|
||||
ReactDOM.flushSync(() => {
|
||||
root.render(Symbol('foo'));
|
||||
});
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
'Symbols are not valid as a React child.\n' +
|
||||
' root.render(Symbol(foo))',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -13,12 +13,16 @@ const ReactDOMServerIntegrationUtils = require('./utils/ReactDOMServerIntegratio
|
||||
|
||||
let React;
|
||||
let ReactDOMServer;
|
||||
let assertConsoleErrorDev;
|
||||
let assertConsoleWarnDev;
|
||||
|
||||
function initModules() {
|
||||
// Reset warning cache.
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactDOMServer = require('react-dom/server');
|
||||
assertConsoleErrorDev = require('internal-test-utils').assertConsoleErrorDev;
|
||||
assertConsoleWarnDev = require('internal-test-utils').assertConsoleWarnDev;
|
||||
|
||||
// Make them available to the helpers.
|
||||
return {
|
||||
@@ -115,9 +119,17 @@ describe('ReactDOMServerLifecycles', () => {
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => ReactDOMServer.renderToString(<Component />)).toErrorDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.',
|
||||
);
|
||||
ReactDOMServer.renderToString(<Component />);
|
||||
assertConsoleErrorDev([
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.\n' +
|
||||
'\n' +
|
||||
'Component uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
|
||||
' UNSAFE_componentWillMount\n' +
|
||||
'\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://react.dev/link/unsafe-component-lifecycles\n' +
|
||||
' in Component (at **)',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should update instance.state with value returned from getDerivedStateFromProps', () => {
|
||||
@@ -182,10 +194,12 @@ describe('ReactDOMServerLifecycles', () => {
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => ReactDOMServer.renderToString(<Component />)).toErrorDev(
|
||||
ReactDOMServer.renderToString(<Component />);
|
||||
assertConsoleErrorDev([
|
||||
'Component.getDerivedStateFromProps(): A valid state object (or null) must ' +
|
||||
'be returned. You have returned undefined.',
|
||||
);
|
||||
'be returned. You have returned undefined.\n' +
|
||||
' in Component (at **)',
|
||||
]);
|
||||
|
||||
// De-duped
|
||||
ReactDOMServer.renderToString(<Component />);
|
||||
@@ -201,12 +215,14 @@ describe('ReactDOMServerLifecycles', () => {
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => ReactDOMServer.renderToString(<Component />)).toErrorDev(
|
||||
ReactDOMServer.renderToString(<Component />);
|
||||
assertConsoleErrorDev([
|
||||
'`Component` uses `getDerivedStateFromProps` but its initial state is ' +
|
||||
'undefined. This is not recommended. Instead, define the initial state by ' +
|
||||
'assigning an object to `this.state` in the constructor of `Component`. ' +
|
||||
'This ensures that `getDerivedStateFromProps` arguments have a consistent shape.',
|
||||
);
|
||||
'This ensures that `getDerivedStateFromProps` arguments have a consistent shape.\n' +
|
||||
' in Component (at **)',
|
||||
]);
|
||||
|
||||
// De-duped
|
||||
ReactDOMServer.renderToString(<Component />);
|
||||
@@ -227,9 +243,16 @@ describe('ReactDOMServerLifecycles', () => {
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => ReactDOMServer.renderToString(<Component />)).toWarnDev(
|
||||
'componentWillMount has been renamed',
|
||||
);
|
||||
ReactDOMServer.renderToString(<Component />);
|
||||
assertConsoleWarnDev([
|
||||
'componentWillMount has been renamed, and is not recommended for use. ' +
|
||||
'See https://react.dev/link/unsafe-component-lifecycles for details.\n' +
|
||||
'\n' +
|
||||
'* Move code from componentWillMount to componentDidMount (preferred in most cases) or the constructor.\n' +
|
||||
'\n' +
|
||||
'Please update the following components: Component\n' +
|
||||
' in Component (at **)',
|
||||
]);
|
||||
expect(log).toEqual(['componentWillMount', 'UNSAFE_componentWillMount']);
|
||||
});
|
||||
|
||||
@@ -254,17 +277,18 @@ describe('ReactDOMServerLifecycles', () => {
|
||||
return <div>{this.props.children + '-' + this.state.x}</div>;
|
||||
}
|
||||
}
|
||||
expect(() => {
|
||||
// Shouldn't be 1-3.
|
||||
expect(ReactDOMServer.renderToStaticMarkup(<Outer />)).toBe(
|
||||
'<div>1-2</div>',
|
||||
);
|
||||
}).toErrorDev(
|
||||
// Shouldn't be 1-3.
|
||||
expect(ReactDOMServer.renderToStaticMarkup(<Outer />)).toBe(
|
||||
'<div>1-2</div>',
|
||||
);
|
||||
assertConsoleErrorDev([
|
||||
'Can only update a mounting component. This ' +
|
||||
'usually means you called setState() outside componentWillMount() on ' +
|
||||
'the server. This is a no-op.\n\n' +
|
||||
'Please check the code for the Outer component.',
|
||||
);
|
||||
'Please check the code for the Outer component.\n' +
|
||||
(gate('enableOwnerStacks') ? '' : ' in Inner (at **)\n') +
|
||||
' in Outer (at **)',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not invoke cWM if static gDSFP is present', () => {
|
||||
@@ -281,9 +305,17 @@ describe('ReactDOMServerLifecycles', () => {
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => ReactDOMServer.renderToString(<Component />)).toErrorDev(
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.',
|
||||
);
|
||||
ReactDOMServer.renderToString(<Component />);
|
||||
assertConsoleErrorDev([
|
||||
'Unsafe legacy lifecycles will not be called for components using new component APIs.\n' +
|
||||
'\n' +
|
||||
'Component uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
|
||||
' componentWillMount\n' +
|
||||
'\n' +
|
||||
'The above lifecycles should be removed. Learn more about this warning here:\n' +
|
||||
'https://react.dev/link/unsafe-component-lifecycles\n' +
|
||||
' in Component (at **)',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should warn about deprecated lifecycle hooks', () => {
|
||||
@@ -294,11 +326,16 @@ describe('ReactDOMServerLifecycles', () => {
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => ReactDOMServer.renderToString(<MyComponent />)).toWarnDev(
|
||||
'componentWillMount has been renamed, and is not recommended for use. See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
|
||||
'* Move code from componentWillMount to componentDidMount (preferred in most cases) or the constructor.\n\n' +
|
||||
'Please update the following components: MyComponent',
|
||||
);
|
||||
ReactDOMServer.renderToString(<MyComponent />);
|
||||
assertConsoleWarnDev([
|
||||
'componentWillMount has been renamed, and is not recommended for use. ' +
|
||||
'See https://react.dev/link/unsafe-component-lifecycles for details.\n' +
|
||||
'\n' +
|
||||
'* Move code from componentWillMount to componentDidMount (preferred in most cases) or the constructor.\n' +
|
||||
'\n' +
|
||||
'Please update the following components: MyComponent\n' +
|
||||
' in MyComponent (at **)',
|
||||
]);
|
||||
|
||||
// De-duped
|
||||
ReactDOMServer.renderToString(<MyComponent />);
|
||||
|
||||
@@ -26,6 +26,7 @@ let waitForAll;
|
||||
let waitFor;
|
||||
let waitForPaint;
|
||||
let assertLog;
|
||||
let assertConsoleErrorDev;
|
||||
|
||||
function normalizeError(msg) {
|
||||
// Take the first sentence to make it easier to assert on.
|
||||
@@ -124,6 +125,7 @@ describe('ReactDOMServerPartialHydration', () => {
|
||||
assertLog = InternalTestUtils.assertLog;
|
||||
waitForPaint = InternalTestUtils.waitForPaint;
|
||||
waitFor = InternalTestUtils.waitFor;
|
||||
assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
|
||||
|
||||
IdleEventPriority = require('react-reconciler/constants').IdleEventPriority;
|
||||
});
|
||||
@@ -1916,9 +1918,18 @@ describe('ReactDOMServerPartialHydration', () => {
|
||||
|
||||
// While we're part way through the hydration, we update the state.
|
||||
// This will schedule an update on the children of the suspense boundary.
|
||||
expect(() => updateText('Hi')).toErrorDev(
|
||||
"Can't perform a React state update on a component that hasn't mounted yet.",
|
||||
);
|
||||
updateText('Hi');
|
||||
assertConsoleErrorDev([
|
||||
"Can't perform a React state update on a component that hasn't mounted yet. " +
|
||||
'This indicates that you have a side-effect in your render function that ' +
|
||||
'asynchronously later calls tries to update the component. Move this work to useEffect instead.\n' +
|
||||
(gate('enableOwnerStacks')
|
||||
? ''
|
||||
: ' in Child (at **)\n' +
|
||||
' in Suspense (at **)\n' +
|
||||
' in div (at **)\n') +
|
||||
' in App (at **)',
|
||||
]);
|
||||
|
||||
// This will throw it away and rerender.
|
||||
await waitForAll(['Child']);
|
||||
|
||||
@@ -14,6 +14,7 @@ describe('ReactDOMShorthandCSSPropertyCollision', () => {
|
||||
|
||||
let React;
|
||||
let ReactDOMClient;
|
||||
let assertConsoleErrorDev;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
@@ -21,6 +22,8 @@ describe('ReactDOMShorthandCSSPropertyCollision', () => {
|
||||
act = require('internal-test-utils').act;
|
||||
React = require('react');
|
||||
ReactDOMClient = require('react-dom/client');
|
||||
assertConsoleErrorDev =
|
||||
require('internal-test-utils').assertConsoleErrorDev;
|
||||
});
|
||||
|
||||
it('should warn for conflicting CSS shorthand updates', async () => {
|
||||
@@ -29,18 +32,17 @@ describe('ReactDOMShorthandCSSPropertyCollision', () => {
|
||||
await act(() => {
|
||||
root.render(<div style={{font: 'foo', fontStyle: 'bar'}} />);
|
||||
});
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
root.render(<div style={{font: 'foo'}} />);
|
||||
});
|
||||
}).toErrorDev(
|
||||
await act(() => {
|
||||
root.render(<div style={{font: 'foo'}} />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'Removing a style property during rerender (fontStyle) ' +
|
||||
'when a conflicting property is set (font) can lead to styling ' +
|
||||
"bugs. To avoid this, don't mix shorthand and non-shorthand " +
|
||||
'properties for the same value; instead, replace the shorthand ' +
|
||||
'with separate values.' +
|
||||
'\n in div (at **)',
|
||||
);
|
||||
]);
|
||||
|
||||
// These updates are OK and don't warn:
|
||||
await act(() => {
|
||||
@@ -50,30 +52,28 @@ describe('ReactDOMShorthandCSSPropertyCollision', () => {
|
||||
root.render(<div style={{font: 'foo', fontStyle: 'baz'}} />);
|
||||
});
|
||||
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
root.render(<div style={{font: 'qux', fontStyle: 'baz'}} />);
|
||||
});
|
||||
}).toErrorDev(
|
||||
await act(() => {
|
||||
root.render(<div style={{font: 'qux', fontStyle: 'baz'}} />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'Updating a style property during rerender (font) when ' +
|
||||
'a conflicting property is set (fontStyle) can lead to styling ' +
|
||||
"bugs. To avoid this, don't mix shorthand and non-shorthand " +
|
||||
'properties for the same value; instead, replace the shorthand ' +
|
||||
'with separate values.' +
|
||||
'\n in div (at **)',
|
||||
);
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
root.render(<div style={{fontStyle: 'baz'}} />);
|
||||
});
|
||||
}).toErrorDev(
|
||||
]);
|
||||
await act(() => {
|
||||
root.render(<div style={{fontStyle: 'baz'}} />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'Removing a style property during rerender (font) when ' +
|
||||
'a conflicting property is set (fontStyle) can lead to styling ' +
|
||||
"bugs. To avoid this, don't mix shorthand and non-shorthand " +
|
||||
'properties for the same value; instead, replace the shorthand ' +
|
||||
'with separate values.' +
|
||||
'\n in div (at **)',
|
||||
);
|
||||
]);
|
||||
|
||||
// A bit of a special case: backgroundPosition isn't technically longhand
|
||||
// (it expands to backgroundPosition{X,Y} but so does background)
|
||||
@@ -82,18 +82,17 @@ describe('ReactDOMShorthandCSSPropertyCollision', () => {
|
||||
<div style={{background: 'yellow', backgroundPosition: 'center'}} />,
|
||||
);
|
||||
});
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
root.render(<div style={{background: 'yellow'}} />);
|
||||
});
|
||||
}).toErrorDev(
|
||||
await act(() => {
|
||||
root.render(<div style={{background: 'yellow'}} />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'Removing a style property during rerender ' +
|
||||
'(backgroundPosition) when a conflicting property is set ' +
|
||||
"(background) can lead to styling bugs. To avoid this, don't mix " +
|
||||
'shorthand and non-shorthand properties for the same value; ' +
|
||||
'instead, replace the shorthand with separate values.' +
|
||||
'\n in div (at **)',
|
||||
);
|
||||
]);
|
||||
await act(() => {
|
||||
root.render(
|
||||
<div style={{background: 'yellow', backgroundPosition: 'center'}} />,
|
||||
@@ -105,18 +104,17 @@ describe('ReactDOMShorthandCSSPropertyCollision', () => {
|
||||
<div style={{background: 'green', backgroundPosition: 'top'}} />,
|
||||
);
|
||||
});
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
root.render(<div style={{backgroundPosition: 'top'}} />);
|
||||
});
|
||||
}).toErrorDev(
|
||||
await act(() => {
|
||||
root.render(<div style={{backgroundPosition: 'top'}} />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'Removing a style property during rerender (background) ' +
|
||||
'when a conflicting property is set (backgroundPosition) can lead ' +
|
||||
"to styling bugs. To avoid this, don't mix shorthand and " +
|
||||
'non-shorthand properties for the same value; instead, replace the ' +
|
||||
'shorthand with separate values.' +
|
||||
'\n in div (at **)',
|
||||
);
|
||||
]);
|
||||
|
||||
// A bit of an even more special case: borderLeft and borderStyle overlap.
|
||||
await act(() => {
|
||||
@@ -124,49 +122,46 @@ describe('ReactDOMShorthandCSSPropertyCollision', () => {
|
||||
<div style={{borderStyle: 'dotted', borderLeft: '1px solid red'}} />,
|
||||
);
|
||||
});
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
root.render(<div style={{borderLeft: '1px solid red'}} />);
|
||||
});
|
||||
}).toErrorDev(
|
||||
await act(() => {
|
||||
root.render(<div style={{borderLeft: '1px solid red'}} />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'Removing a style property during rerender (borderStyle) ' +
|
||||
'when a conflicting property is set (borderLeft) can lead to ' +
|
||||
"styling bugs. To avoid this, don't mix shorthand and " +
|
||||
'non-shorthand properties for the same value; instead, replace the ' +
|
||||
'shorthand with separate values.' +
|
||||
'\n in div (at **)',
|
||||
);
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
root.render(
|
||||
<div style={{borderStyle: 'dashed', borderLeft: '1px solid red'}} />,
|
||||
);
|
||||
});
|
||||
}).toErrorDev(
|
||||
]);
|
||||
await act(() => {
|
||||
root.render(
|
||||
<div style={{borderStyle: 'dashed', borderLeft: '1px solid red'}} />,
|
||||
);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'Updating a style property during rerender (borderStyle) ' +
|
||||
'when a conflicting property is set (borderLeft) can lead to ' +
|
||||
"styling bugs. To avoid this, don't mix shorthand and " +
|
||||
'non-shorthand properties for the same value; instead, replace the ' +
|
||||
'shorthand with separate values.' +
|
||||
'\n in div (at **)',
|
||||
);
|
||||
]);
|
||||
// But setting them at the same time is OK:
|
||||
await act(() => {
|
||||
root.render(
|
||||
<div style={{borderStyle: 'dotted', borderLeft: '2px solid red'}} />,
|
||||
);
|
||||
});
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
root.render(<div style={{borderStyle: 'dotted'}} />);
|
||||
});
|
||||
}).toErrorDev(
|
||||
await act(() => {
|
||||
root.render(<div style={{borderStyle: 'dotted'}} />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'Removing a style property during rerender (borderLeft) ' +
|
||||
'when a conflicting property is set (borderStyle) can lead to ' +
|
||||
"styling bugs. To avoid this, don't mix shorthand and " +
|
||||
'non-shorthand properties for the same value; instead, replace the ' +
|
||||
'shorthand with separate values.' +
|
||||
'\n in div (at **)',
|
||||
);
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,6 +23,7 @@ let buffer = '';
|
||||
let hasErrored = false;
|
||||
let fatalError = undefined;
|
||||
let waitForAll;
|
||||
let assertConsoleErrorDev;
|
||||
|
||||
function normalizeError(msg) {
|
||||
// Take the first sentence to make it easier to assert on.
|
||||
@@ -45,6 +46,7 @@ describe('ReactDOM HostSingleton', () => {
|
||||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
waitForAll = InternalTestUtils.waitForAll;
|
||||
assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
|
||||
|
||||
// Test Environment
|
||||
const jsdom = new JSDOM(
|
||||
@@ -162,11 +164,16 @@ describe('ReactDOM HostSingleton', () => {
|
||||
<body />
|
||||
</html>,
|
||||
);
|
||||
await expect(async () => {
|
||||
await waitForAll([]);
|
||||
}).toErrorDev(
|
||||
'You are mounting a new head component when a previous one has not first unmounted. It is an error to render more than one head component at a time and attributes and children of these components will likely fail in unpredictable ways. Please only render a single instance of <head> and if you need to mount a new one, ensure any previous ones have unmounted first',
|
||||
);
|
||||
await waitForAll([]);
|
||||
assertConsoleErrorDev([
|
||||
'You are mounting a new head component when a previous one has not first unmounted. ' +
|
||||
'It is an error to render more than one head component at a time and attributes and ' +
|
||||
'children of these components will likely fail in unpredictable ways. ' +
|
||||
'Please only render a single instance of <head> and if you need to mount a new one, ' +
|
||||
'ensure any previous ones have unmounted first.\n' +
|
||||
' in head (at **)' +
|
||||
(gate('enableOwnerStacks') ? '' : '\n in html (at **)'),
|
||||
]);
|
||||
expect(getVisibleChildren(document)).toEqual(
|
||||
<html>
|
||||
<head lang="es" data-foo="foo">
|
||||
@@ -540,10 +547,31 @@ describe('ReactDOM HostSingleton', () => {
|
||||
},
|
||||
);
|
||||
expect(hydrationErrors).toEqual([]);
|
||||
await expect(async () => {
|
||||
await waitForAll([]);
|
||||
}).toErrorDev(
|
||||
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties.",
|
||||
await waitForAll([]);
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. " +
|
||||
"This won't be patched up. This can happen if a SSR-ed Client Component used:\n" +
|
||||
'\n' +
|
||||
"- A server/client branch `if (typeof window !== 'undefined')`.\n" +
|
||||
"- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n" +
|
||||
"- Date formatting in a user's locale which doesn't match the server.\n" +
|
||||
'- External changing data without sending a snapshot of it along with the HTML.\n' +
|
||||
'- Invalid HTML tag nesting.\n\nIt can also happen if the client has a browser extension installed ' +
|
||||
'which messes with the HTML before React loaded.\n' +
|
||||
'\n' +
|
||||
'https://react.dev/link/hydration-mismatch\n' +
|
||||
'\n' +
|
||||
' <html\n' +
|
||||
'+ data-client-foo="foo"\n' +
|
||||
'- data-client-foo={null}\n' +
|
||||
' >\n' +
|
||||
' <head>\n' +
|
||||
' <body\n' +
|
||||
'+ data-client-baz="baz"\n' +
|
||||
'- data-client-baz={null}\n' +
|
||||
' >\n',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
expect(persistentElements).toEqual([
|
||||
|
||||
@@ -16,6 +16,7 @@ describe('ReactDOMTextarea', () => {
|
||||
let ReactDOMClient;
|
||||
let ReactDOMServer;
|
||||
let act;
|
||||
let assertConsoleErrorDev;
|
||||
|
||||
let renderTextarea;
|
||||
|
||||
@@ -28,6 +29,8 @@ describe('ReactDOMTextarea', () => {
|
||||
ReactDOMClient = require('react-dom/client');
|
||||
ReactDOMServer = require('react-dom/server');
|
||||
act = require('internal-test-utils').act;
|
||||
assertConsoleErrorDev =
|
||||
require('internal-test-utils').assertConsoleErrorDev;
|
||||
|
||||
renderTextarea = async function (component, container, root) {
|
||||
await act(() => {
|
||||
@@ -330,12 +333,12 @@ describe('ReactDOMTextarea', () => {
|
||||
);
|
||||
});
|
||||
};
|
||||
await expect(() =>
|
||||
expect(test).rejects.toThrowError(new TypeError('prod message')),
|
||||
).toErrorDev(
|
||||
await expect(test).rejects.toThrowError(new TypeError('prod message'));
|
||||
assertConsoleErrorDev([
|
||||
'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
|
||||
'strings, not TemporalLike. This value must be coerced to a string before using it here.',
|
||||
);
|
||||
'strings, not TemporalLike. This value must be coerced to a string before using it here.\n' +
|
||||
' in textarea (at **',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should take updates to `defaultValue` for uncontrolled textarea', async () => {
|
||||
@@ -437,17 +440,15 @@ describe('ReactDOMTextarea', () => {
|
||||
it('should ignore children content', async () => {
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
let node;
|
||||
|
||||
await expect(async () => {
|
||||
node = await renderTextarea(
|
||||
<textarea>giraffe</textarea>,
|
||||
container,
|
||||
root,
|
||||
);
|
||||
}).toErrorDev(
|
||||
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
|
||||
const node = await renderTextarea(
|
||||
<textarea>giraffe</textarea>,
|
||||
container,
|
||||
root,
|
||||
);
|
||||
assertConsoleErrorDev([
|
||||
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.\n' +
|
||||
' in textarea (at **)',
|
||||
]);
|
||||
expect(node.value).toBe('');
|
||||
|
||||
await act(() => {
|
||||
@@ -462,17 +463,16 @@ describe('ReactDOMTextarea', () => {
|
||||
it('should receive defaultValue and still ignore children content', async () => {
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
let node;
|
||||
|
||||
await expect(async () => {
|
||||
node = await renderTextarea(
|
||||
<textarea defaultValue="dragon">monkey</textarea>,
|
||||
container,
|
||||
root,
|
||||
);
|
||||
}).toErrorDev(
|
||||
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
|
||||
const node = await renderTextarea(
|
||||
<textarea defaultValue="dragon">monkey</textarea>,
|
||||
container,
|
||||
root,
|
||||
);
|
||||
assertConsoleErrorDev([
|
||||
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.\n' +
|
||||
' in textarea (at **)',
|
||||
]);
|
||||
expect(node.value).toBe('dragon');
|
||||
});
|
||||
}
|
||||
@@ -481,17 +481,16 @@ describe('ReactDOMTextarea', () => {
|
||||
it('should treat children like `defaultValue`', async () => {
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
let node;
|
||||
|
||||
await expect(async () => {
|
||||
node = await renderTextarea(
|
||||
<textarea>giraffe</textarea>,
|
||||
container,
|
||||
root,
|
||||
);
|
||||
}).toErrorDev(
|
||||
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
|
||||
const node = await renderTextarea(
|
||||
<textarea>giraffe</textarea>,
|
||||
container,
|
||||
root,
|
||||
);
|
||||
assertConsoleErrorDev([
|
||||
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.\n' +
|
||||
' in textarea (at **)',
|
||||
]);
|
||||
|
||||
expect(node.value).toBe('giraffe');
|
||||
|
||||
@@ -549,12 +548,15 @@ describe('ReactDOMTextarea', () => {
|
||||
it('should ignore numbers as children', async () => {
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
let node;
|
||||
await expect(async () => {
|
||||
node = await renderTextarea(<textarea>{17}</textarea>, container, root);
|
||||
}).toErrorDev(
|
||||
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
|
||||
const node = await renderTextarea(
|
||||
<textarea>{17}</textarea>,
|
||||
container,
|
||||
root,
|
||||
);
|
||||
assertConsoleErrorDev([
|
||||
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.\n' +
|
||||
' in textarea (at **)',
|
||||
]);
|
||||
expect(node.value).toBe('');
|
||||
});
|
||||
}
|
||||
@@ -563,12 +565,15 @@ describe('ReactDOMTextarea', () => {
|
||||
it('should allow numbers as children', async () => {
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
let node;
|
||||
await expect(async () => {
|
||||
node = await renderTextarea(<textarea>{17}</textarea>, container, root);
|
||||
}).toErrorDev(
|
||||
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
|
||||
const node = await renderTextarea(
|
||||
<textarea>{17}</textarea>,
|
||||
container,
|
||||
root,
|
||||
);
|
||||
assertConsoleErrorDev([
|
||||
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.\n' +
|
||||
' in textarea (at **)',
|
||||
]);
|
||||
expect(node.value).toBe('17');
|
||||
});
|
||||
}
|
||||
@@ -577,16 +582,15 @@ describe('ReactDOMTextarea', () => {
|
||||
it('should ignore booleans as children', async () => {
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
let node;
|
||||
await expect(async () => {
|
||||
node = await renderTextarea(
|
||||
<textarea>{false}</textarea>,
|
||||
container,
|
||||
root,
|
||||
);
|
||||
}).toErrorDev(
|
||||
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
|
||||
const node = await renderTextarea(
|
||||
<textarea>{false}</textarea>,
|
||||
container,
|
||||
root,
|
||||
);
|
||||
assertConsoleErrorDev([
|
||||
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.\n' +
|
||||
' in textarea (at **)',
|
||||
]);
|
||||
expect(node.value).toBe('');
|
||||
});
|
||||
}
|
||||
@@ -595,16 +599,15 @@ describe('ReactDOMTextarea', () => {
|
||||
it('should allow booleans as children', async () => {
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
let node;
|
||||
await expect(async () => {
|
||||
node = await renderTextarea(
|
||||
<textarea>{false}</textarea>,
|
||||
container,
|
||||
root,
|
||||
);
|
||||
}).toErrorDev(
|
||||
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
|
||||
const node = await renderTextarea(
|
||||
<textarea>{false}</textarea>,
|
||||
container,
|
||||
root,
|
||||
);
|
||||
assertConsoleErrorDev([
|
||||
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.\n' +
|
||||
' in textarea (at **)',
|
||||
]);
|
||||
expect(node.value).toBe('false');
|
||||
});
|
||||
}
|
||||
@@ -618,16 +621,15 @@ describe('ReactDOMTextarea', () => {
|
||||
return 'sharkswithlasers';
|
||||
},
|
||||
};
|
||||
let node;
|
||||
await expect(async () => {
|
||||
node = await renderTextarea(
|
||||
<textarea>{obj}</textarea>,
|
||||
container,
|
||||
root,
|
||||
);
|
||||
}).toErrorDev(
|
||||
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
|
||||
const node = await renderTextarea(
|
||||
<textarea>{obj}</textarea>,
|
||||
container,
|
||||
root,
|
||||
);
|
||||
assertConsoleErrorDev([
|
||||
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.\n' +
|
||||
' in textarea (at **)',
|
||||
]);
|
||||
expect(node.value).toBe('');
|
||||
});
|
||||
}
|
||||
@@ -641,16 +643,15 @@ describe('ReactDOMTextarea', () => {
|
||||
return 'sharkswithlasers';
|
||||
},
|
||||
};
|
||||
let node;
|
||||
await expect(async () => {
|
||||
node = await renderTextarea(
|
||||
<textarea>{obj}</textarea>,
|
||||
container,
|
||||
root,
|
||||
);
|
||||
}).toErrorDev(
|
||||
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
|
||||
const node = await renderTextarea(
|
||||
<textarea>{obj}</textarea>,
|
||||
container,
|
||||
root,
|
||||
);
|
||||
assertConsoleErrorDev([
|
||||
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.\n' +
|
||||
' in textarea (at **)',
|
||||
]);
|
||||
expect(node.value).toBe('sharkswithlasers');
|
||||
});
|
||||
}
|
||||
@@ -660,35 +661,36 @@ describe('ReactDOMTextarea', () => {
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await expect(async () => {
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
root.render(
|
||||
<textarea>
|
||||
{'hello'}
|
||||
{'there'}
|
||||
</textarea>,
|
||||
);
|
||||
});
|
||||
}).rejects.toThrow('<textarea> can only have at most one child');
|
||||
}).toErrorDev([
|
||||
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
|
||||
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
|
||||
await act(() => {
|
||||
root.render(
|
||||
<textarea>
|
||||
{'hello'}
|
||||
{'there'}
|
||||
</textarea>,
|
||||
);
|
||||
});
|
||||
}).rejects.toThrow('<textarea> can only have at most one child');
|
||||
assertConsoleErrorDev([
|
||||
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.\n' +
|
||||
' in textarea (at **)',
|
||||
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.\n' +
|
||||
' in textarea (at **)',
|
||||
]);
|
||||
|
||||
let node;
|
||||
await expect(async () => {
|
||||
await expect(
|
||||
(async () =>
|
||||
(node = await renderTextarea(
|
||||
<textarea>
|
||||
<strong />
|
||||
</textarea>,
|
||||
container,
|
||||
root,
|
||||
)))(),
|
||||
).resolves.not.toThrow();
|
||||
}).toErrorDev([
|
||||
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
|
||||
await expect(
|
||||
(async () =>
|
||||
(node = await renderTextarea(
|
||||
<textarea>
|
||||
<strong />
|
||||
</textarea>,
|
||||
container,
|
||||
root,
|
||||
)))(),
|
||||
).resolves.not.toThrow();
|
||||
assertConsoleErrorDev([
|
||||
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.\n' +
|
||||
' in textarea (at **)',
|
||||
]);
|
||||
|
||||
expect(node.value).toBe('[object Object]');
|
||||
@@ -710,15 +712,15 @@ describe('ReactDOMTextarea', () => {
|
||||
it('should warn if value is null', async () => {
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
root.render(<textarea value={null} />);
|
||||
});
|
||||
}).toErrorDev(
|
||||
await act(() => {
|
||||
root.render(<textarea value={null} />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'`value` prop on `textarea` should not be null. ' +
|
||||
'Consider using an empty string to clear the component or `undefined` ' +
|
||||
'for uncontrolled components.',
|
||||
);
|
||||
'for uncontrolled components.\n' +
|
||||
' in textarea (at **)',
|
||||
]);
|
||||
|
||||
await act(() => {
|
||||
root.render(<textarea value={null} />);
|
||||
@@ -731,18 +733,19 @@ describe('ReactDOMTextarea', () => {
|
||||
);
|
||||
let container = document.createElement('div');
|
||||
let root = ReactDOMClient.createRoot(container);
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
root.render(<InvalidComponent />);
|
||||
});
|
||||
}).toErrorDev(
|
||||
await act(() => {
|
||||
root.render(<InvalidComponent />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'InvalidComponent contains a textarea with both value and defaultValue props. ' +
|
||||
'Textarea elements must be either controlled or uncontrolled ' +
|
||||
'(specify either the value prop, or the defaultValue prop, but not ' +
|
||||
'both). Decide between using a controlled or uncontrolled textarea ' +
|
||||
'and remove one of these props. More info: ' +
|
||||
'https://react.dev/link/controlled-components',
|
||||
);
|
||||
'https://react.dev/link/controlled-components\n' +
|
||||
' in textarea (at **)\n' +
|
||||
' in InvalidComponent (at **)',
|
||||
]);
|
||||
|
||||
container = document.createElement('div');
|
||||
root = ReactDOMClient.createRoot(container);
|
||||
@@ -819,13 +822,15 @@ describe('ReactDOMTextarea', () => {
|
||||
it('treats initial Symbol value as an empty string', async () => {
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
root.render(
|
||||
<textarea value={Symbol('foobar')} onChange={() => {}} />,
|
||||
);
|
||||
});
|
||||
}).toErrorDev('Invalid value for prop `value`');
|
||||
await act(() => {
|
||||
root.render(<textarea value={Symbol('foobar')} onChange={() => {}} />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'Invalid value for prop `value` on <textarea> tag. ' +
|
||||
'Either remove it from the element, or pass a string or number value to keep it in the DOM. ' +
|
||||
'For details, see https://react.dev/link/attribute-behavior \n' +
|
||||
' in textarea (at **)',
|
||||
]);
|
||||
const node = container.firstChild;
|
||||
|
||||
expect(node.value).toBe('');
|
||||
@@ -834,11 +839,13 @@ describe('ReactDOMTextarea', () => {
|
||||
it('treats initial Symbol children as an empty string', async () => {
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
root.render(<textarea onChange={() => {}}>{Symbol('foo')}</textarea>);
|
||||
});
|
||||
}).toErrorDev('Use the `defaultValue` or `value` props');
|
||||
await act(() => {
|
||||
root.render(<textarea onChange={() => {}}>{Symbol('foo')}</textarea>);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.\n' +
|
||||
' in textarea (at **)',
|
||||
]);
|
||||
const node = container.firstChild;
|
||||
|
||||
expect(node.value).toBe('');
|
||||
@@ -852,11 +859,15 @@ describe('ReactDOMTextarea', () => {
|
||||
root.render(<textarea value="foo" onChange={() => {}} />);
|
||||
});
|
||||
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
root.render(<textarea value={Symbol('foo')} onChange={() => {}} />);
|
||||
});
|
||||
}).toErrorDev('Invalid value for prop `value`');
|
||||
await act(() => {
|
||||
root.render(<textarea value={Symbol('foo')} onChange={() => {}} />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'Invalid value for prop `value` on <textarea> tag. ' +
|
||||
'Either remove it from the element, or pass a string or number value to keep it in the DOM. ' +
|
||||
'For details, see https://react.dev/link/attribute-behavior \n' +
|
||||
' in textarea (at **)',
|
||||
]);
|
||||
const node = container.firstChild;
|
||||
|
||||
expect(node.value).toBe('');
|
||||
@@ -897,13 +908,17 @@ describe('ReactDOMTextarea', () => {
|
||||
describe('When given a function value', () => {
|
||||
it('treats initial function value as an empty string', async () => {
|
||||
const container = document.createElement('div');
|
||||
await expect(async () => {
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
|
||||
await act(() => {
|
||||
root.render(<textarea value={() => {}} onChange={() => {}} />);
|
||||
});
|
||||
}).toErrorDev('Invalid value for prop `value`');
|
||||
await act(() => {
|
||||
root.render(<textarea value={() => {}} onChange={() => {}} />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'Invalid value for prop `value` on <textarea> tag. ' +
|
||||
'Either remove it from the element, or pass a string or number value to keep it in the DOM. ' +
|
||||
'For details, see https://react.dev/link/attribute-behavior \n' +
|
||||
' in textarea (at **)',
|
||||
]);
|
||||
const node = container.firstChild;
|
||||
|
||||
expect(node.value).toBe('');
|
||||
@@ -911,13 +926,15 @@ describe('ReactDOMTextarea', () => {
|
||||
|
||||
it('treats initial function children as an empty string', async () => {
|
||||
const container = document.createElement('div');
|
||||
await expect(async () => {
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
|
||||
await act(() => {
|
||||
root.render(<textarea onChange={() => {}}>{() => {}}</textarea>);
|
||||
});
|
||||
}).toErrorDev('Use the `defaultValue` or `value` props');
|
||||
await act(() => {
|
||||
root.render(<textarea onChange={() => {}}>{() => {}}</textarea>);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.\n' +
|
||||
' in textarea (at **)',
|
||||
]);
|
||||
const node = container.firstChild;
|
||||
|
||||
expect(node.value).toBe('');
|
||||
@@ -931,11 +948,15 @@ describe('ReactDOMTextarea', () => {
|
||||
root.render(<textarea value="foo" onChange={() => {}} />);
|
||||
});
|
||||
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
root.render(<textarea value={() => {}} onChange={() => {}} />);
|
||||
});
|
||||
}).toErrorDev('Invalid value for prop `value`');
|
||||
await act(() => {
|
||||
root.render(<textarea value={() => {}} onChange={() => {}} />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'Invalid value for prop `value` on <textarea> tag. ' +
|
||||
'Either remove it from the element, or pass a string or number value to keep it in the DOM. ' +
|
||||
'For details, see https://react.dev/link/attribute-behavior \n' +
|
||||
' in textarea (at **)',
|
||||
]);
|
||||
const node = container.firstChild;
|
||||
|
||||
expect(node.value).toBe('');
|
||||
@@ -1054,60 +1075,60 @@ describe('ReactDOMTextarea', () => {
|
||||
it('should warn about missing onChange if value is false', async () => {
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
root.render(<textarea value={false} />);
|
||||
});
|
||||
}).toErrorDev(
|
||||
await act(() => {
|
||||
root.render(<textarea value={false} />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'You provided a `value` prop to a form ' +
|
||||
'field without an `onChange` handler. This will render a read-only ' +
|
||||
'field. If the field should be mutable use `defaultValue`. ' +
|
||||
'Otherwise, set either `onChange` or `readOnly`.',
|
||||
);
|
||||
'Otherwise, set either `onChange` or `readOnly`.\n' +
|
||||
' in textarea (at **)',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should warn about missing onChange if value is 0', async () => {
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
root.render(<textarea value={0} />);
|
||||
});
|
||||
}).toErrorDev(
|
||||
await act(() => {
|
||||
root.render(<textarea value={0} />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'You provided a `value` prop to a form ' +
|
||||
'field without an `onChange` handler. This will render a read-only ' +
|
||||
'field. If the field should be mutable use `defaultValue`. ' +
|
||||
'Otherwise, set either `onChange` or `readOnly`.',
|
||||
);
|
||||
'Otherwise, set either `onChange` or `readOnly`.\n' +
|
||||
' in textarea (at **)',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should warn about missing onChange if value is "0"', async () => {
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
root.render(<textarea value="0" />);
|
||||
});
|
||||
}).toErrorDev(
|
||||
await act(() => {
|
||||
root.render(<textarea value="0" />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'You provided a `value` prop to a form ' +
|
||||
'field without an `onChange` handler. This will render a read-only ' +
|
||||
'field. If the field should be mutable use `defaultValue`. ' +
|
||||
'Otherwise, set either `onChange` or `readOnly`.',
|
||||
);
|
||||
'Otherwise, set either `onChange` or `readOnly`.\n' +
|
||||
' in textarea (at **)',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should warn about missing onChange if value is ""', async () => {
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
root.render(<textarea value="" />);
|
||||
});
|
||||
}).toErrorDev(
|
||||
await act(() => {
|
||||
root.render(<textarea value="" />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'You provided a `value` prop to a form ' +
|
||||
'field without an `onChange` handler. This will render a read-only ' +
|
||||
'field. If the field should be mutable use `defaultValue`. ' +
|
||||
'Otherwise, set either `onChange` or `readOnly`.',
|
||||
);
|
||||
'Otherwise, set either `onChange` or `readOnly`.\n' +
|
||||
' in textarea (at **)',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -896,7 +896,9 @@ describe('ReactErrorBoundaries', () => {
|
||||
);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'BrokenComponentWillMountWithContext uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
|
||||
'BrokenComponentWillMountWithContext uses the legacy childContextTypes API which will soon be removed. ' +
|
||||
'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
|
||||
' in BrokenComponentWillMountWithContext (at **)',
|
||||
]);
|
||||
expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
|
||||
});
|
||||
@@ -2366,44 +2368,53 @@ describe('ReactErrorBoundaries', () => {
|
||||
it('discards a bad root if the root component fails', async () => {
|
||||
const X = null;
|
||||
const Y = undefined;
|
||||
let err1;
|
||||
let err2;
|
||||
|
||||
try {
|
||||
await expect(async () => {
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await expect(
|
||||
async () =>
|
||||
await act(async () => {
|
||||
root.render(<X />, container);
|
||||
}),
|
||||
).toErrorDev(
|
||||
'React.createElement: type is invalid -- expected a string ' +
|
||||
'(for built-in components) or a class/function ' +
|
||||
'(for composite components) but got: null.',
|
||||
await act(async () => {
|
||||
root.render(<X />);
|
||||
});
|
||||
}).rejects.toThrow(
|
||||
'Element type is invalid: ' +
|
||||
'expected a string (for built-in components) or a ' +
|
||||
'class/function (for composite components) but got: null.',
|
||||
);
|
||||
|
||||
if (!gate('enableOwnerStacks')) {
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
'React.jsx: type is invalid -- expected a string ' +
|
||||
'(for built-in components) or a class/function ' +
|
||||
'(for composite components) but got: null.',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
} catch (err) {
|
||||
err1 = err;
|
||||
}
|
||||
try {
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await expect(
|
||||
async () =>
|
||||
await act(async () => {
|
||||
root.render(<Y />, container);
|
||||
}),
|
||||
).toErrorDev(
|
||||
'React.createElement: type is invalid -- expected a string ' +
|
||||
'(for built-in components) or a class/function ' +
|
||||
'(for composite components) but got: undefined.',
|
||||
);
|
||||
} catch (err) {
|
||||
err2 = err;
|
||||
}
|
||||
|
||||
expect(err1.message).toMatch(/got: null/);
|
||||
expect(err2.message).toMatch(/got: undefined/);
|
||||
await expect(async () => {
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await act(async () => {
|
||||
root.render(<Y />);
|
||||
});
|
||||
}).rejects.toThrow(
|
||||
'Element type is invalid: ' +
|
||||
'expected a string (for built-in components) or a ' +
|
||||
'class/function (for composite components) but got: undefined.',
|
||||
);
|
||||
if (!gate('enableOwnerStacks')) {
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
'React.jsx: type is invalid -- expected a string ' +
|
||||
'(for built-in components) or a class/function ' +
|
||||
'(for composite components) but got: undefined. ' +
|
||||
"You likely forgot to export your component from the file it's defined in, " +
|
||||
'or you might have mixed up default and named imports.',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('renders empty output if error boundary does not handle the error', async () => {
|
||||
@@ -2570,18 +2581,18 @@ describe('ReactErrorBoundaries', () => {
|
||||
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await expect(async () => {
|
||||
await act(async () => {
|
||||
root.render(
|
||||
<InvalidErrorBoundary>
|
||||
<Throws />
|
||||
</InvalidErrorBoundary>,
|
||||
);
|
||||
});
|
||||
}).toErrorDev(
|
||||
await act(async () => {
|
||||
root.render(
|
||||
<InvalidErrorBoundary>
|
||||
<Throws />
|
||||
</InvalidErrorBoundary>,
|
||||
);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'InvalidErrorBoundary: Error boundaries should implement getDerivedStateFromError(). ' +
|
||||
'In that method, return a state update to display an error message or fallback UI.',
|
||||
);
|
||||
'In that method, return a state update to display an error message or fallback UI.\n' +
|
||||
' in InvalidErrorBoundary (at **)',
|
||||
]);
|
||||
expect(container.textContent).toBe('');
|
||||
});
|
||||
|
||||
|
||||
@@ -111,8 +111,14 @@ describe('ReactFunctionComponent', () => {
|
||||
});
|
||||
|
||||
assertConsoleErrorDev([
|
||||
'GrandParent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
|
||||
'Child uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
|
||||
'GrandParent uses the legacy childContextTypes API which will soon be removed. ' +
|
||||
'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
|
||||
' in GrandParent (at **)',
|
||||
'Child uses the legacy contextTypes API which will soon be removed. ' +
|
||||
'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
|
||||
(gate('enableOwnerStacks') ? '' : ' in Child (at **)\n') +
|
||||
' in Parent (at **)\n' +
|
||||
' in GrandParent (at **)',
|
||||
]);
|
||||
|
||||
expect(el.textContent).toBe('test');
|
||||
@@ -132,15 +138,15 @@ describe('ReactFunctionComponent', () => {
|
||||
|
||||
const container = document.createElement('div');
|
||||
|
||||
await expect(async () => {
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await act(() => {
|
||||
root.render(<FunctionComponentWithChildContext />);
|
||||
});
|
||||
}).toErrorDev(
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await act(() => {
|
||||
root.render(<FunctionComponentWithChildContext />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'FunctionComponentWithChildContext: Function ' +
|
||||
'components do not support getDerivedStateFromProps.',
|
||||
);
|
||||
'components do not support getDerivedStateFromProps.\n' +
|
||||
' in FunctionComponentWithChildContext (at **)',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should warn for childContextTypes on a function component', async () => {
|
||||
@@ -154,14 +160,16 @@ describe('ReactFunctionComponent', () => {
|
||||
|
||||
const container = document.createElement('div');
|
||||
|
||||
await expect(async () => {
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await act(() => {
|
||||
root.render(<FunctionComponentWithChildContext name="A" />);
|
||||
});
|
||||
}).toErrorDev(
|
||||
'childContextTypes cannot ' + 'be defined on a function component.',
|
||||
);
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await act(() => {
|
||||
root.render(<FunctionComponentWithChildContext name="A" />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'childContextTypes cannot ' +
|
||||
'be defined on a function component.\n' +
|
||||
' FunctionComponentWithChildContext.childContextTypes = ...\n' +
|
||||
' in FunctionComponentWithChildContext (at **)',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not throw when stateless component returns undefined', async () => {
|
||||
@@ -184,16 +192,18 @@ describe('ReactFunctionComponent', () => {
|
||||
return <div>{[<span />]}</div>;
|
||||
}
|
||||
|
||||
await expect(async () => {
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await act(() => {
|
||||
root.render(<Child />);
|
||||
});
|
||||
}).toErrorDev(
|
||||
'Each child in a list should have a unique "key" prop.\n\n' +
|
||||
'Check the render method of `Child`.',
|
||||
);
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await act(() => {
|
||||
root.render(<Child />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'Each child in a list should have a unique "key" prop.\n' +
|
||||
'\n' +
|
||||
'Check the render method of `Child`. See https://react.dev/link/warning-keys for more information.\n' +
|
||||
' in span (at **)\n' +
|
||||
' in Child (at **)',
|
||||
]);
|
||||
});
|
||||
|
||||
// @gate !disableDefaultPropsExceptForClasses
|
||||
@@ -203,16 +213,17 @@ describe('ReactFunctionComponent', () => {
|
||||
}
|
||||
Child.defaultProps = {test: 2};
|
||||
|
||||
await expect(async () => {
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
|
||||
await act(() => {
|
||||
root.render(<Child />);
|
||||
});
|
||||
expect(container.textContent).toBe('2');
|
||||
}).toErrorDev([
|
||||
'Child: Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.',
|
||||
await act(() => {
|
||||
root.render(<Child />);
|
||||
});
|
||||
expect(container.textContent).toBe('2');
|
||||
assertConsoleErrorDev([
|
||||
'Child: Support for defaultProps will be removed from function components in a future major release. ' +
|
||||
'Use JavaScript default parameters instead.\n' +
|
||||
' in Child (at **)',
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -244,8 +255,13 @@ describe('ReactFunctionComponent', () => {
|
||||
root.render(<Parent />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
|
||||
'Child uses the legacy contextTypes API which will be removed soon. Use React.createContext() with React.useContext() instead.',
|
||||
'Parent uses the legacy childContextTypes API which will soon be removed. ' +
|
||||
'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
|
||||
' in Parent (at **)',
|
||||
'Child uses the legacy contextTypes API which will be removed soon. ' +
|
||||
'Use React.createContext() with React.useContext() instead. (https://react.dev/link/legacy-context)\n' +
|
||||
' in Child (at **)\n' +
|
||||
' in Parent (at **)',
|
||||
]);
|
||||
expect(el.textContent).toBe('en');
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
let React;
|
||||
let ReactDOMClient;
|
||||
let act;
|
||||
let assertConsoleErrorDev;
|
||||
|
||||
describe('ReactIdentity', () => {
|
||||
beforeEach(() => {
|
||||
@@ -19,6 +20,8 @@ describe('ReactIdentity', () => {
|
||||
React = require('react');
|
||||
ReactDOMClient = require('react-dom/client');
|
||||
act = require('internal-test-utils').act;
|
||||
assertConsoleErrorDev =
|
||||
require('internal-test-utils').assertConsoleErrorDev;
|
||||
});
|
||||
|
||||
it('should allow key property to express identity', async () => {
|
||||
@@ -313,17 +316,18 @@ describe('ReactIdentity', () => {
|
||||
|
||||
const el = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(el);
|
||||
await expect(() =>
|
||||
expect(() => {
|
||||
root.render(
|
||||
<div>
|
||||
<span key={new TemporalLike()} />
|
||||
</div>,
|
||||
);
|
||||
}).toThrowError(new TypeError('prod message')),
|
||||
).toErrorDev(
|
||||
'The provided key is an unsupported type TemporalLike.' +
|
||||
' This value must be coerced to a string before using it here.',
|
||||
await expect(() => {
|
||||
root.render(
|
||||
<div>
|
||||
<span key={new TemporalLike()} />
|
||||
</div>,
|
||||
);
|
||||
}).toThrowError(new TypeError('prod message'));
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
'The provided key is an unsupported type TemporalLike.' +
|
||||
' This value must be coerced to a string before using it here.',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -50,15 +50,12 @@ describe('ReactLegacyCompositeComponent', () => {
|
||||
return <div />;
|
||||
}
|
||||
}
|
||||
|
||||
let instance;
|
||||
|
||||
expect(() => {
|
||||
instance = ReactDOM.render(<Component />, container);
|
||||
}).toErrorDev(
|
||||
const instance = ReactDOM.render(<Component />, container);
|
||||
assertConsoleErrorDev([
|
||||
'Cannot update during an existing state transition (such as within ' +
|
||||
'`render`). Render methods should be a pure function of props and state.',
|
||||
);
|
||||
'`render`). Render methods should be a pure function of props and state.\n' +
|
||||
' in Component (at **)',
|
||||
]);
|
||||
|
||||
// The setState call is queued and then executed as a second pass. This
|
||||
// behavior is undefined though so we're free to change it to suit the
|
||||
@@ -122,8 +119,16 @@ describe('ReactLegacyCompositeComponent', () => {
|
||||
root.render(<Parent ref={current => (component = current)} />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'Child uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
|
||||
'Grandchild uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
|
||||
'Child uses the legacy childContextTypes API which will soon be removed. ' +
|
||||
'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
|
||||
(gate('enableOwnerStacks') ? '' : ' in Child (at **)\n') +
|
||||
' in Parent (at **)',
|
||||
'Grandchild uses the legacy contextTypes API which will soon be removed. ' +
|
||||
'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
|
||||
(gate('enableOwnerStacks')
|
||||
? ''
|
||||
: ' in Grandchild (at **)\n' + ' in Child (at **)\n') +
|
||||
' in Parent (at **)',
|
||||
]);
|
||||
expect(findDOMNode(component).innerHTML).toBe('bar');
|
||||
});
|
||||
@@ -190,8 +195,15 @@ describe('ReactLegacyCompositeComponent', () => {
|
||||
expect(childInstance.context).toEqual({foo: 'bar', flag: false});
|
||||
|
||||
assertConsoleErrorDev([
|
||||
'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
|
||||
'Child uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
|
||||
'Parent uses the legacy childContextTypes API which will soon be removed. ' +
|
||||
'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
|
||||
' in Parent (at **)',
|
||||
'Child uses the legacy contextTypes API which will soon be removed. ' +
|
||||
'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
|
||||
' in Child (at **)' +
|
||||
(gate('enableOwnerStacks')
|
||||
? ''
|
||||
: '\n in Middle (at **)' + '\n in Parent (at **)'),
|
||||
]);
|
||||
|
||||
await act(() => {
|
||||
@@ -254,8 +266,18 @@ describe('ReactLegacyCompositeComponent', () => {
|
||||
});
|
||||
|
||||
assertConsoleErrorDev([
|
||||
'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
|
||||
'Child uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
|
||||
'Parent uses the legacy childContextTypes API which will soon be removed. ' +
|
||||
'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
|
||||
(gate('enableOwnerStacks') ? '' : ' in Parent (at **)\n') +
|
||||
' in Wrapper (at **)',
|
||||
'Child uses the legacy contextTypes API which will soon be removed. ' +
|
||||
'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
|
||||
(gate('enableOwnerStacks')
|
||||
? ''
|
||||
: ' in Child (at **)\n' +
|
||||
' in div (at **)\n' +
|
||||
' in Parent (at **)\n') +
|
||||
' in Wrapper (at **)',
|
||||
]);
|
||||
|
||||
expect(wrapper.parentRef.current.state.flag).toEqual(true);
|
||||
@@ -334,10 +356,22 @@ describe('ReactLegacyCompositeComponent', () => {
|
||||
});
|
||||
|
||||
assertConsoleErrorDev([
|
||||
'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
|
||||
'Child uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
|
||||
'Child uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
|
||||
'Grandchild uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
|
||||
'Parent uses the legacy childContextTypes API which will soon be removed. ' +
|
||||
'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
|
||||
' in Parent (at **)',
|
||||
'Child uses the legacy childContextTypes API which will soon be removed. ' +
|
||||
'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
|
||||
(gate('enableOwnerStacks') ? '' : ' in Child (at **)\n') +
|
||||
' in Parent (at **)',
|
||||
'Child uses the legacy contextTypes API which will soon be removed. ' +
|
||||
'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
|
||||
(gate('enableOwnerStacks') ? '' : ' in Child (at **)\n') +
|
||||
' in Parent (at **)',
|
||||
'Grandchild uses the legacy contextTypes API which will soon be removed. ' +
|
||||
'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
|
||||
(gate('enableOwnerStacks') ? '' : ' in Grandchild (at **)\n') +
|
||||
' in Child (at **)\n' +
|
||||
' in Parent (at **)',
|
||||
]);
|
||||
|
||||
expect(childInstance.context).toEqual({foo: 'bar', depth: 0});
|
||||
@@ -393,7 +427,9 @@ describe('ReactLegacyCompositeComponent', () => {
|
||||
root.render(<Parent ref={current => (parentInstance = current)} />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
|
||||
'Parent uses the legacy childContextTypes API which will soon be removed. ' +
|
||||
'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
|
||||
' in Parent (at **)',
|
||||
]);
|
||||
|
||||
expect(childInstance).toBeNull();
|
||||
@@ -403,7 +439,10 @@ describe('ReactLegacyCompositeComponent', () => {
|
||||
parentInstance.setState({flag: true});
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'Child uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
|
||||
'Child uses the legacy contextTypes API which will soon be removed. ' +
|
||||
'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
|
||||
(gate('enableOwnerStacks') ? '' : ' in Child (at **)\n') +
|
||||
' in Parent (at **)',
|
||||
]);
|
||||
|
||||
expect(parentInstance.state.flag).toBe(true);
|
||||
@@ -465,11 +504,15 @@ describe('ReactLegacyCompositeComponent', () => {
|
||||
}
|
||||
|
||||
const div = document.createElement('div');
|
||||
expect(() => {
|
||||
ReactDOM.render(<Parent cntxt="noise" />, div);
|
||||
}).toErrorDev([
|
||||
'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
|
||||
'Leaf uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
|
||||
ReactDOM.render(<Parent cntxt="noise" />, div);
|
||||
assertConsoleErrorDev([
|
||||
'Parent uses the legacy childContextTypes API which will soon be removed. ' +
|
||||
'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
|
||||
' in Parent (at **)',
|
||||
'Leaf uses the legacy contextTypes API which will soon be removed. ' +
|
||||
'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
|
||||
' in Intermediary (at **)\n' +
|
||||
' in Parent (at **)',
|
||||
]);
|
||||
expect(div.children[0].innerHTML).toBe('noise');
|
||||
div.children[0].innerHTML = 'aliens';
|
||||
@@ -572,25 +615,30 @@ describe('ReactLegacyCompositeComponent', () => {
|
||||
const div = document.createElement('div');
|
||||
|
||||
let parentInstance = null;
|
||||
expect(() => {
|
||||
ReactDOM.render(
|
||||
<Parent ref={inst => (parentInstance = inst)}>
|
||||
<ChildWithoutContext>
|
||||
A1
|
||||
<GrandChild>A2</GrandChild>
|
||||
</ChildWithoutContext>
|
||||
ReactDOM.render(
|
||||
<Parent ref={inst => (parentInstance = inst)}>
|
||||
<ChildWithoutContext>
|
||||
A1
|
||||
<GrandChild>A2</GrandChild>
|
||||
</ChildWithoutContext>
|
||||
|
||||
<ChildWithContext>
|
||||
B1
|
||||
<GrandChild>B2</GrandChild>
|
||||
</ChildWithContext>
|
||||
</Parent>,
|
||||
div,
|
||||
);
|
||||
}).toErrorDev([
|
||||
'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
|
||||
'GrandChild uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
|
||||
'ChildWithContext uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
|
||||
<ChildWithContext>
|
||||
B1
|
||||
<GrandChild>B2</GrandChild>
|
||||
</ChildWithContext>
|
||||
</Parent>,
|
||||
div,
|
||||
);
|
||||
assertConsoleErrorDev([
|
||||
'Parent uses the legacy childContextTypes API which will soon be removed. ' +
|
||||
'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
|
||||
' in Parent (at **)',
|
||||
'GrandChild uses the legacy contextTypes API which will soon be removed. ' +
|
||||
'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
|
||||
' in GrandChild (at **)',
|
||||
'ChildWithContext uses the legacy contextTypes API which will soon be removed. ' +
|
||||
'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
|
||||
' in ChildWithContext (at **)',
|
||||
]);
|
||||
|
||||
parentInstance.setState({
|
||||
@@ -774,16 +822,19 @@ describe('ReactLegacyCompositeComponent', () => {
|
||||
}
|
||||
|
||||
const div = document.createElement('div');
|
||||
expect(() => {
|
||||
ReactDOM.render(
|
||||
<Parent>
|
||||
<Component />
|
||||
</Parent>,
|
||||
div,
|
||||
);
|
||||
}).toErrorDev([
|
||||
'Parent uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
|
||||
'Component uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
|
||||
ReactDOM.render(
|
||||
<Parent>
|
||||
<Component />
|
||||
</Parent>,
|
||||
div,
|
||||
);
|
||||
assertConsoleErrorDev([
|
||||
'Parent uses the legacy childContextTypes API which will soon be removed. ' +
|
||||
'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
|
||||
' in Parent (at **)',
|
||||
'Component uses the legacy contextTypes API which will soon be removed. ' +
|
||||
'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
|
||||
' in Component (at **)',
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ let ReactDOMClient;
|
||||
let ReactDOMServer;
|
||||
let ReactFeatureFlags;
|
||||
let act;
|
||||
let assertConsoleErrorDev;
|
||||
|
||||
describe('ReactLegacyContextDisabled', () => {
|
||||
beforeEach(() => {
|
||||
@@ -25,6 +26,8 @@ describe('ReactLegacyContextDisabled', () => {
|
||||
ReactFeatureFlags = require('shared/ReactFeatureFlags');
|
||||
ReactFeatureFlags.disableLegacyContext = true;
|
||||
act = require('internal-test-utils').act;
|
||||
assertConsoleErrorDev =
|
||||
require('internal-test-utils').assertConsoleErrorDev;
|
||||
});
|
||||
|
||||
function formatValue(val) {
|
||||
@@ -84,25 +87,33 @@ describe('ReactLegacyContextDisabled', () => {
|
||||
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
root.render(
|
||||
<LegacyProvider>
|
||||
<span>
|
||||
<LegacyClsConsumer />
|
||||
<LegacyFnConsumer />
|
||||
<RegularFn />
|
||||
</span>
|
||||
</LegacyProvider>,
|
||||
);
|
||||
});
|
||||
}).toErrorDev([
|
||||
await act(() => {
|
||||
root.render(
|
||||
<LegacyProvider>
|
||||
<span>
|
||||
<LegacyClsConsumer />
|
||||
<LegacyFnConsumer />
|
||||
<RegularFn />
|
||||
</span>
|
||||
</LegacyProvider>,
|
||||
);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'LegacyProvider uses the legacy childContextTypes API which was removed in React 19. ' +
|
||||
'Use React.createContext() instead.',
|
||||
'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
|
||||
' in LegacyProvider (at **)',
|
||||
'LegacyClsConsumer uses the legacy contextTypes API which was removed in React 19. ' +
|
||||
'Use React.createContext() with static contextType instead.',
|
||||
'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
|
||||
' in LegacyClsConsumer (at **)' +
|
||||
(gate('enableOwnerStacks')
|
||||
? ''
|
||||
: '\n' + ' in span (at **)\n' + ' in LegacyProvider (at **)'),
|
||||
'LegacyFnConsumer uses the legacy contextTypes API which was removed in React 19. ' +
|
||||
'Use React.createContext() with React.useContext() instead.',
|
||||
'Use React.createContext() with React.useContext() instead. (https://react.dev/link/legacy-context)\n' +
|
||||
' in LegacyFnConsumer (at **)' +
|
||||
(gate('enableOwnerStacks')
|
||||
? ''
|
||||
: '\n' + ' in span (at **)\n' + ' in LegacyProvider (at **)'),
|
||||
]);
|
||||
expect(container.textContent).toBe('{}undefinedundefined');
|
||||
expect(lifecycleContextLog).toEqual([]);
|
||||
@@ -124,25 +135,32 @@ describe('ReactLegacyContextDisabled', () => {
|
||||
root.unmount();
|
||||
|
||||
// test server path.
|
||||
let text;
|
||||
expect(() => {
|
||||
text = ReactDOMServer.renderToString(
|
||||
<LegacyProvider>
|
||||
<span>
|
||||
<LegacyClsConsumer />
|
||||
<LegacyFnConsumer />
|
||||
<RegularFn />
|
||||
</span>
|
||||
</LegacyProvider>,
|
||||
container,
|
||||
);
|
||||
}).toErrorDev([
|
||||
const text = ReactDOMServer.renderToString(
|
||||
<LegacyProvider>
|
||||
<span>
|
||||
<LegacyClsConsumer />
|
||||
<LegacyFnConsumer />
|
||||
<RegularFn />
|
||||
</span>
|
||||
</LegacyProvider>,
|
||||
container,
|
||||
);
|
||||
assertConsoleErrorDev([
|
||||
'LegacyProvider uses the legacy childContextTypes API which was removed in React 19. ' +
|
||||
'Use React.createContext() instead.',
|
||||
'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
|
||||
' in LegacyProvider (at **)',
|
||||
'LegacyClsConsumer uses the legacy contextTypes API which was removed in React 19. ' +
|
||||
'Use React.createContext() with static contextType instead.',
|
||||
'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
|
||||
' in LegacyClsConsumer (at **)' +
|
||||
(gate('enableOwnerStacks')
|
||||
? ''
|
||||
: '\n' + ' in span (at **)\n' + ' in LegacyProvider (at **)'),
|
||||
'LegacyFnConsumer uses the legacy contextTypes API which was removed in React 19. ' +
|
||||
'Use React.createContext() with React.useContext() instead.',
|
||||
'Use React.createContext() with React.useContext() instead. (https://react.dev/link/legacy-context)\n' +
|
||||
' in LegacyFnConsumer (at **)' +
|
||||
(gate('enableOwnerStacks')
|
||||
? ''
|
||||
: '\n' + ' in span (at **)\n' + ' in LegacyProvider (at **)'),
|
||||
]);
|
||||
expect(text).toBe('<span>{}<!-- -->undefined<!-- -->undefined</span>');
|
||||
expect(lifecycleContextLog).toEqual([{}, {}, {}]);
|
||||
|
||||
@@ -850,7 +850,9 @@ describe('ReactLegacyErrorBoundaries', () => {
|
||||
container,
|
||||
);
|
||||
assertConsoleErrorDev([
|
||||
'BrokenComponentWillMountWithContext uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
|
||||
'BrokenComponentWillMountWithContext uses the legacy childContextTypes API which will soon be removed. ' +
|
||||
'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
|
||||
' in BrokenComponentWillMountWithContext (at **)',
|
||||
]);
|
||||
expect(container.firstChild.textContent).toBe('Caught an error: Hello.');
|
||||
});
|
||||
@@ -2142,19 +2144,20 @@ describe('ReactLegacyErrorBoundaries', () => {
|
||||
// @gate !disableLegacyMode
|
||||
it('renders empty output if error boundary does not handle the error', async () => {
|
||||
const container = document.createElement('div');
|
||||
expect(() => {
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
Sibling
|
||||
<NoopErrorBoundary>
|
||||
<BrokenRender />
|
||||
</NoopErrorBoundary>
|
||||
</div>,
|
||||
container,
|
||||
);
|
||||
}).toErrorDev(
|
||||
'ErrorBoundary: Error boundaries should implement getDerivedStateFromError()',
|
||||
ReactDOM.render(
|
||||
<div>
|
||||
Sibling
|
||||
<NoopErrorBoundary>
|
||||
<BrokenRender />
|
||||
</NoopErrorBoundary>
|
||||
</div>,
|
||||
container,
|
||||
);
|
||||
assertConsoleErrorDev([
|
||||
'NoopErrorBoundary: Error boundaries should implement getDerivedStateFromError(). ' +
|
||||
'In that method, return a state update to display an error message or fallback UI.\n' +
|
||||
' in NoopErrorBoundary (at **)',
|
||||
]);
|
||||
expect(container.firstChild.textContent).toBe('Sibling');
|
||||
expect(log).toEqual([
|
||||
'NoopErrorBoundary constructor',
|
||||
|
||||
@@ -15,6 +15,7 @@ let React;
|
||||
let ReactDOM;
|
||||
let ReactDOMClient;
|
||||
let waitForAll;
|
||||
let assertConsoleErrorDev;
|
||||
|
||||
describe('ReactMount', () => {
|
||||
beforeEach(() => {
|
||||
@@ -26,6 +27,7 @@ describe('ReactMount', () => {
|
||||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
waitForAll = InternalTestUtils.waitForAll;
|
||||
assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
|
||||
});
|
||||
|
||||
describe('unmountComponentAtNode', () => {
|
||||
@@ -63,14 +65,15 @@ describe('ReactMount', () => {
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(Component, container);
|
||||
}).toErrorDev(
|
||||
'Functions are not valid as a React child. ' +
|
||||
'This may happen if you return Component instead of <Component /> from render. ' +
|
||||
'Or maybe you meant to call this function rather than return it.\n' +
|
||||
' root.render(Component)',
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(Component, container);
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
'Functions are not valid as a React child. ' +
|
||||
'This may happen if you return Component instead of <Component /> from render. ' +
|
||||
'Or maybe you meant to call this function rather than return it.\n' +
|
||||
' root.render(Component)',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
@@ -168,11 +171,14 @@ describe('ReactMount', () => {
|
||||
// Test that blasting away children throws a warning
|
||||
const rootNode = container.firstChild;
|
||||
|
||||
expect(() => ReactDOM.render(<span />, rootNode)).toErrorDev(
|
||||
'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.',
|
||||
ReactDOM.render(<span />, rootNode);
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
'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},
|
||||
);
|
||||
});
|
||||
@@ -197,9 +203,12 @@ describe('ReactMount', () => {
|
||||
// Make sure ReactDOM and ReactDOMOther are different copies
|
||||
expect(ReactDOM).not.toEqual(ReactDOMOther);
|
||||
|
||||
expect(() => ReactDOMOther.unmountComponentAtNode(container)).toErrorDev(
|
||||
"unmountComponentAtNode(): The node you're attempting to unmount " +
|
||||
'was rendered by another copy of React.',
|
||||
ReactDOMOther.unmountComponentAtNode(container);
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
"unmountComponentAtNode(): The node you're attempting to unmount " +
|
||||
'was rendered by another copy of React.',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
|
||||
@@ -351,16 +360,18 @@ describe('ReactMount', () => {
|
||||
root.render(<div>Hi</div>);
|
||||
await waitForAll([]);
|
||||
expect(container.textContent).toEqual('Hi');
|
||||
expect(() => {
|
||||
ReactDOM.render(<div>Bye</div>, container);
|
||||
}).toErrorDev(
|
||||
ReactDOM.render(<div>Bye</div>, container);
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
// We care about this warning:
|
||||
'You are calling ReactDOM.render() on a container that was previously ' +
|
||||
'passed to ReactDOMClient.createRoot(). This is not supported. ' +
|
||||
'Did you mean to call root.render(element)?',
|
||||
// This is more of a symptom but restructuring the code to avoid it isn't worth it:
|
||||
'Replacing React-rendered children with a new root component.',
|
||||
'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},
|
||||
);
|
||||
@@ -376,16 +387,16 @@ describe('ReactMount', () => {
|
||||
root.render(<div>Hi</div>);
|
||||
await waitForAll([]);
|
||||
expect(container.textContent).toEqual('Hi');
|
||||
let unmounted = false;
|
||||
expect(() => {
|
||||
unmounted = ReactDOM.unmountComponentAtNode(container);
|
||||
}).toErrorDev(
|
||||
const unmounted = ReactDOM.unmountComponentAtNode(container);
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
// We care about this warning:
|
||||
'You are calling ReactDOM.unmountComponentAtNode() on a container that was previously ' +
|
||||
'passed to ReactDOMClient.createRoot(). This is not supported. Did you mean to call root.unmount()?',
|
||||
// This is more of a symptom but restructuring the code to avoid it isn't worth it:
|
||||
"The node you're attempting to unmount was rendered by React and is not a top-level container.",
|
||||
'unmountComponentAtNode(): ' +
|
||||
"The node you're attempting to unmount was rendered by React and is not a top-level container. " +
|
||||
'Instead, have the parent component update its state and rerender in order to remove this component.',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
@@ -407,14 +418,16 @@ describe('ReactMount', () => {
|
||||
root.render(<div>Hi</div>);
|
||||
await waitForAll([]);
|
||||
expect(container.textContent).toEqual('Hi');
|
||||
let unmounted = false;
|
||||
expect(() => {
|
||||
unmounted = ReactDOM.unmountComponentAtNode(container);
|
||||
}).toErrorDev(
|
||||
const unmounted = ReactDOM.unmountComponentAtNode(container);
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
'Did you mean to call root.unmount()?',
|
||||
'You are calling ReactDOM.unmountComponentAtNode() on a container ' +
|
||||
'that was previously passed to ReactDOMClient.createRoot(). ' +
|
||||
'This is not supported. Did you mean to call root.unmount()?',
|
||||
// This is more of a symptom but restructuring the code to avoid it isn't worth it:
|
||||
"The node you're attempting to unmount was rendered by React and is not a top-level container.",
|
||||
'unmountComponentAtNode(): ' +
|
||||
"The node you're attempting to unmount was rendered by React and is not a top-level container. " +
|
||||
'Instead, have the parent component update its state and rerender in order to remove this component.',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
@@ -430,11 +443,12 @@ describe('ReactMount', () => {
|
||||
it('warns when passing legacy container to createRoot()', () => {
|
||||
const container = document.createElement('div');
|
||||
ReactDOM.render(<div>Hi</div>, container);
|
||||
expect(() => {
|
||||
ReactDOMClient.createRoot(container);
|
||||
}).toErrorDev(
|
||||
'You are calling ReactDOMClient.createRoot() on a container that was previously ' +
|
||||
'passed to ReactDOM.render(). This is not supported.',
|
||||
ReactDOMClient.createRoot(container);
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
'You are calling ReactDOMClient.createRoot() on a container that was previously ' +
|
||||
'passed to ReactDOM.render(). This is not supported.',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@ let findDOMNode;
|
||||
let act;
|
||||
let Scheduler;
|
||||
let assertLog;
|
||||
let assertConsoleErrorDev;
|
||||
|
||||
// Copy of ReactUpdates using ReactDOM.render and ReactDOM.unstable_batchedUpdates.
|
||||
// Can be deleted when we remove both.
|
||||
@@ -27,6 +28,8 @@ describe('ReactLegacyUpdates', () => {
|
||||
ReactDOM.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE
|
||||
.findDOMNode;
|
||||
act = require('internal-test-utils').act;
|
||||
assertConsoleErrorDev =
|
||||
require('internal-test-utils').assertConsoleErrorDev;
|
||||
Scheduler = require('scheduler');
|
||||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
@@ -906,35 +909,37 @@ describe('ReactLegacyUpdates', () => {
|
||||
let component = ReactDOM.render(<A />, container);
|
||||
|
||||
await expect(async () => {
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
component.setState({}, 'no');
|
||||
});
|
||||
}).rejects.toThrowError(
|
||||
'Invalid argument passed as callback. Expected a function. Instead ' +
|
||||
'received: no',
|
||||
);
|
||||
}).toErrorDev(
|
||||
'Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: no.',
|
||||
{withoutStack: 1},
|
||||
await act(() => {
|
||||
component.setState({}, 'no');
|
||||
});
|
||||
}).rejects.toThrowError(
|
||||
'Invalid argument passed as callback. Expected a function. ' +
|
||||
'Instead received: no',
|
||||
);
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
'Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: no.',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
|
||||
container = document.createElement('div');
|
||||
component = ReactDOM.render(<A />, container);
|
||||
await expect(async () => {
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
component.setState({}, {foo: 'bar'});
|
||||
});
|
||||
}).rejects.toThrowError(
|
||||
'Invalid argument passed as callback. Expected a function. Instead ' +
|
||||
'received: [object Object]',
|
||||
);
|
||||
}).toErrorDev(
|
||||
'Expected the last optional `callback` argument to be ' +
|
||||
"a function. Instead received: { foo: 'bar' }.",
|
||||
{withoutStack: 1},
|
||||
await act(() => {
|
||||
component.setState({}, {foo: 'bar'});
|
||||
});
|
||||
}).rejects.toThrowError(
|
||||
'Invalid argument passed as callback. Expected a function. Instead ' +
|
||||
'received: [object Object]',
|
||||
);
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
'Expected the last optional `callback` argument to be a function. ' +
|
||||
"Instead received: { foo: 'bar' }.",
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
// Make sure the warning is deduplicated and doesn't fire again
|
||||
container = document.createElement('div');
|
||||
@@ -968,34 +973,36 @@ describe('ReactLegacyUpdates', () => {
|
||||
let component = ReactDOM.render(<A />, container);
|
||||
|
||||
await expect(async () => {
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
component.forceUpdate('no');
|
||||
});
|
||||
}).rejects.toThrowError(
|
||||
'Invalid argument passed as callback. Expected a function. Instead ' +
|
||||
'received: no',
|
||||
);
|
||||
}).toErrorDev(
|
||||
'Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: no.',
|
||||
{withoutStack: 1},
|
||||
await act(() => {
|
||||
component.forceUpdate('no');
|
||||
});
|
||||
}).rejects.toThrowError(
|
||||
'Invalid argument passed as callback. Expected a function. Instead ' +
|
||||
'received: no',
|
||||
);
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
'Expected the last optional `callback` argument to be a function. ' +
|
||||
'Instead received: no.',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
container = document.createElement('div');
|
||||
component = ReactDOM.render(<A />, container);
|
||||
await expect(async () => {
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
component.forceUpdate({foo: 'bar'});
|
||||
});
|
||||
}).rejects.toThrowError(
|
||||
'Invalid argument passed as callback. Expected a function. Instead ' +
|
||||
'received: [object Object]',
|
||||
);
|
||||
}).toErrorDev(
|
||||
'Expected the last optional `callback` argument to be ' +
|
||||
"a function. Instead received: { foo: 'bar' }.",
|
||||
{withoutStack: 1},
|
||||
await act(() => {
|
||||
component.forceUpdate({foo: 'bar'});
|
||||
});
|
||||
}).rejects.toThrowError(
|
||||
'Invalid argument passed as callback. Expected a function. Instead ' +
|
||||
'received: [object Object]',
|
||||
);
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
'Expected the last optional `callback` argument to be a function. ' +
|
||||
"Instead received: { foo: 'bar' }.",
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
// Make sure the warning is deduplicated and doesn't fire again
|
||||
container = document.createElement('div');
|
||||
@@ -1318,9 +1325,12 @@ describe('ReactLegacyUpdates', () => {
|
||||
}
|
||||
|
||||
const container = document.createElement('div');
|
||||
expect(() => ReactDOM.render(<Foo />, container)).toErrorDev(
|
||||
'Cannot update during an existing state transition',
|
||||
);
|
||||
ReactDOM.render(<Foo />, container);
|
||||
assertConsoleErrorDev([
|
||||
'Cannot update during an existing state transition (such as within `render`). ' +
|
||||
'Render methods should be a pure function of props and state.\n' +
|
||||
' in Foo (at **)',
|
||||
]);
|
||||
expect(ops).toEqual(['base: 0, memoized: 0', 'base: 1, memoized: 1']);
|
||||
});
|
||||
|
||||
|
||||
@@ -13,6 +13,8 @@ const React = require('react');
|
||||
const ReactDOM = require('react-dom');
|
||||
const ReactDOMClient = require('react-dom/client');
|
||||
const act = require('internal-test-utils').act;
|
||||
const assertConsoleErrorDev =
|
||||
require('internal-test-utils').assertConsoleErrorDev;
|
||||
|
||||
describe('ReactMount', () => {
|
||||
it('should destroy a react root upon request', async () => {
|
||||
@@ -63,11 +65,14 @@ describe('ReactMount', () => {
|
||||
|
||||
// Test that unmounting at a root node gives a helpful warning
|
||||
const rootDiv = mainContainerDiv.firstChild;
|
||||
expect(() => ReactDOM.unmountComponentAtNode(rootDiv)).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.',
|
||||
ReactDOM.unmountComponentAtNode(rootDiv);
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
"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},
|
||||
);
|
||||
});
|
||||
@@ -88,11 +93,14 @@ describe('ReactMount', () => {
|
||||
|
||||
// Test that unmounting at a non-root node gives a different warning
|
||||
const nonRootDiv = mainContainerDiv.firstChild.firstChild;
|
||||
expect(() => ReactDOM.unmountComponentAtNode(nonRootDiv)).toErrorDev(
|
||||
"unmountComponentAtNode(): The node you're attempting to " +
|
||||
'unmount was rendered by React and is not a top-level container. ' +
|
||||
'Instead, have the parent component update its state and rerender in ' +
|
||||
'order to remove this component.',
|
||||
ReactDOM.unmountComponentAtNode(nonRootDiv);
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
"unmountComponentAtNode(): The node you're attempting to " +
|
||||
'unmount was rendered by React and is not a top-level container. ' +
|
||||
'Instead, have the parent component update its state and rerender in ' +
|
||||
'order to remove this component.',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -13,12 +13,15 @@ describe('ReactMultiChild', () => {
|
||||
let React;
|
||||
let ReactDOMClient;
|
||||
let act;
|
||||
let assertConsoleErrorDev;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactDOMClient = require('react-dom/client');
|
||||
act = require('internal-test-utils').act;
|
||||
assertConsoleErrorDev =
|
||||
require('internal-test-utils').assertConsoleErrorDev;
|
||||
});
|
||||
|
||||
describe('reconciliation', () => {
|
||||
@@ -216,12 +219,10 @@ describe('ReactMultiChild', () => {
|
||||
root.render(<Parent>{[<div key="1" />]}</Parent>);
|
||||
});
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await act(async () => {
|
||||
root.render(<Parent>{[<div key="1" />, <div key="1" />]}</Parent>);
|
||||
}),
|
||||
).toErrorDev(
|
||||
await act(async () => {
|
||||
root.render(<Parent>{[<div key="1" />, <div key="1" />]}</Parent>);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'Encountered two children with the same key, `1`. ' +
|
||||
'Keys should be unique so that components maintain their identity ' +
|
||||
'across updates. Non-unique keys may cause children to be ' +
|
||||
@@ -234,7 +235,7 @@ describe('ReactMultiChild', () => {
|
||||
'\n in WrapperComponent (at **)' +
|
||||
'\n in div (at **)' +
|
||||
'\n in Parent (at **)'),
|
||||
);
|
||||
]);
|
||||
});
|
||||
|
||||
it('should warn for duplicated iterable keys with component stack info', async () => {
|
||||
@@ -278,16 +279,12 @@ describe('ReactMultiChild', () => {
|
||||
root.render(<Parent>{createIterable([<div key="1" />])}</Parent>);
|
||||
});
|
||||
|
||||
await expect(
|
||||
async () =>
|
||||
await act(async () => {
|
||||
root.render(
|
||||
<Parent>
|
||||
{createIterable([<div key="1" />, <div key="1" />])}
|
||||
</Parent>,
|
||||
);
|
||||
}),
|
||||
).toErrorDev(
|
||||
await act(async () => {
|
||||
root.render(
|
||||
<Parent>{createIterable([<div key="1" />, <div key="1" />])}</Parent>,
|
||||
);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'Encountered two children with the same key, `1`. ' +
|
||||
'Keys should be unique so that components maintain their identity ' +
|
||||
'across updates. Non-unique keys may cause children to be ' +
|
||||
@@ -300,7 +297,7 @@ describe('ReactMultiChild', () => {
|
||||
'\n in WrapperComponent (at **)' +
|
||||
'\n in div (at **)' +
|
||||
'\n in Parent (at **)'),
|
||||
);
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -321,17 +318,15 @@ describe('ReactMultiChild', () => {
|
||||
}
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await expect(
|
||||
async () =>
|
||||
await act(async () => {
|
||||
root.render(<Parent />);
|
||||
}),
|
||||
).toErrorDev(
|
||||
await act(async () => {
|
||||
root.render(<Parent />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'Using Maps as children is not supported. ' +
|
||||
'Use an array of keyed ReactElements instead.\n' +
|
||||
' in div (at **)\n' +
|
||||
' in Parent (at **)',
|
||||
);
|
||||
]);
|
||||
});
|
||||
|
||||
it('should NOT warn for using generator functions as components', async () => {
|
||||
@@ -362,11 +357,10 @@ describe('ReactMultiChild', () => {
|
||||
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await expect(async () => {
|
||||
await act(async () => {
|
||||
root.render(<Foo />);
|
||||
});
|
||||
}).toErrorDev(
|
||||
await act(async () => {
|
||||
root.render(<Foo />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'Using Iterators as children is unsupported and will likely yield ' +
|
||||
'unexpected results because enumerating a generator mutates it. ' +
|
||||
'You may convert it to an array with `Array.from()` or the ' +
|
||||
@@ -374,7 +368,7 @@ describe('ReactMultiChild', () => {
|
||||
'Iterable that can iterate multiple times over the same items.\n' +
|
||||
' in div (at **)\n' +
|
||||
' in Foo (at **)',
|
||||
);
|
||||
]);
|
||||
|
||||
expect(container.textContent).toBe('HelloWorld');
|
||||
|
||||
@@ -407,18 +401,17 @@ describe('ReactMultiChild', () => {
|
||||
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await expect(async () => {
|
||||
await act(async () => {
|
||||
root.render(<Foo />);
|
||||
});
|
||||
}).toErrorDev(
|
||||
await act(async () => {
|
||||
root.render(<Foo />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'Using Iterators as children is unsupported and will likely yield ' +
|
||||
'unexpected results because enumerating a generator mutates it. ' +
|
||||
'You may convert it to an array with `Array.from()` or the ' +
|
||||
'`[...spread]` operator before rendering. You can also use an ' +
|
||||
'Iterable that can iterate multiple times over the same items.\n' +
|
||||
' in Foo (at **)',
|
||||
);
|
||||
]);
|
||||
|
||||
expect(container.textContent).toBe('HelloWorld');
|
||||
|
||||
|
||||
@@ -80,98 +80,103 @@ describe('ReactMultiChildText', () => {
|
||||
jest.setTimeout(30000);
|
||||
|
||||
it('should correctly handle all possible children for render and update', async () => {
|
||||
await expect(async () => {
|
||||
// prettier-ignore
|
||||
await testAllPermutations([
|
||||
// basic values
|
||||
undefined, [],
|
||||
null, [],
|
||||
false, [],
|
||||
true, [],
|
||||
0, '0',
|
||||
1.2, '1.2',
|
||||
'', [],
|
||||
'foo', 'foo',
|
||||
|
||||
[], [],
|
||||
[undefined], [],
|
||||
[null], [],
|
||||
[false], [],
|
||||
[true], [],
|
||||
[0], ['0'],
|
||||
[1.2], ['1.2'],
|
||||
[''], [],
|
||||
['foo'], ['foo'],
|
||||
[<div />], [<div />],
|
||||
|
||||
// two adjacent values
|
||||
[true, 0], ['0'],
|
||||
[0, 0], ['0', '0'],
|
||||
[1.2, 0], ['1.2', '0'],
|
||||
[0, ''], ['0', ''],
|
||||
['foo', 0], ['foo', '0'],
|
||||
[0, <div />], ['0', <div />],
|
||||
|
||||
[true, 1.2], ['1.2'],
|
||||
[1.2, 0], ['1.2', '0'],
|
||||
[1.2, 1.2], ['1.2', '1.2'],
|
||||
[1.2, ''], ['1.2', ''],
|
||||
['foo', 1.2], ['foo', '1.2'],
|
||||
[1.2, <div />], ['1.2', <div />],
|
||||
|
||||
[true, ''], [''],
|
||||
['', 0], ['', '0'],
|
||||
[1.2, ''], ['1.2', ''],
|
||||
['', ''], ['', ''],
|
||||
['foo', ''], ['foo', ''],
|
||||
['', <div />], ['', <div />],
|
||||
|
||||
[true, 'foo'], ['foo'],
|
||||
['foo', 0], ['foo', '0'],
|
||||
[1.2, 'foo'], ['1.2', 'foo'],
|
||||
['foo', ''], ['foo', ''],
|
||||
['foo', 'foo'], ['foo', 'foo'],
|
||||
['foo', <div />], ['foo', <div />],
|
||||
|
||||
// values separated by an element
|
||||
[true, <div />, true], [<div />],
|
||||
[1.2, <div />, 1.2], ['1.2', <div />, '1.2'],
|
||||
['', <div />, ''], ['', <div />, ''],
|
||||
['foo', <div />, 'foo'], ['foo', <div />, 'foo'],
|
||||
|
||||
[true, 1.2, <div />, '', 'foo'], ['1.2', <div />, '', 'foo'],
|
||||
[1.2, '', <div />, 'foo', true], ['1.2', '', <div />, 'foo'],
|
||||
['', 'foo', <div />, true, 1.2], ['', 'foo', <div />, '1.2'],
|
||||
|
||||
[true, 1.2, '', <div />, 'foo', true, 1.2], ['1.2', '', <div />, 'foo', '1.2'],
|
||||
['', 'foo', true, <div />, 1.2, '', 'foo'], ['', 'foo', <div />, '1.2', '', 'foo'],
|
||||
|
||||
// values inside arrays
|
||||
[[true], [true]], [],
|
||||
[[1.2], [1.2]], ['1.2', '1.2'],
|
||||
[[''], ['']], ['', ''],
|
||||
[['foo'], ['foo']], ['foo', 'foo'],
|
||||
[[<div />], [<div />]], [<div />, <div />],
|
||||
|
||||
[[true, 1.2, <div />], '', 'foo'], ['1.2', <div />, '', 'foo'],
|
||||
[1.2, '', [<div />, 'foo', true]], ['1.2', '', <div />, 'foo'],
|
||||
['', ['foo', <div />, true], 1.2], ['', 'foo', <div />, '1.2'],
|
||||
|
||||
[true, [1.2, '', <div />, 'foo'], true, 1.2], ['1.2', '', <div />, 'foo', '1.2'],
|
||||
['', 'foo', [true, <div />, 1.2, ''], 'foo'], ['', 'foo', <div />, '1.2', '', 'foo'],
|
||||
|
||||
// values inside elements
|
||||
[<div>{true}{1.2}{<div />}</div>, '', 'foo'], [<div />, '', 'foo'],
|
||||
[1.2, '', <div>{<div />}{'foo'}{true}</div>], ['1.2', '', <div />],
|
||||
['', <div>{'foo'}{<div />}{true}</div>, 1.2], ['', <div />, '1.2'],
|
||||
|
||||
[true, <div>{1.2}{''}{<div />}{'foo'}</div>, true, 1.2], [<div />, '1.2'],
|
||||
['', 'foo', <div>{true}{<div />}{1.2}{''}</div>, 'foo'], ['', 'foo', <div />, 'foo'],
|
||||
]);
|
||||
}).toErrorDev([
|
||||
'Each child in a list should have a unique "key" prop.',
|
||||
'Each child in a list should have a unique "key" prop.',
|
||||
spyOnDev(console, 'error').mockImplementation(() => {});
|
||||
// prettier-ignore
|
||||
await testAllPermutations([
|
||||
// basic values
|
||||
undefined, [],
|
||||
null, [],
|
||||
false, [],
|
||||
true, [],
|
||||
0, '0',
|
||||
1.2, '1.2',
|
||||
'', [],
|
||||
'foo', 'foo',
|
||||
|
||||
[], [],
|
||||
[undefined], [],
|
||||
[null], [],
|
||||
[false], [],
|
||||
[true], [],
|
||||
[0], ['0'],
|
||||
[1.2], ['1.2'],
|
||||
[''], [],
|
||||
['foo'], ['foo'],
|
||||
[<div />], [<div />],
|
||||
|
||||
// two adjacent values
|
||||
[true, 0], ['0'],
|
||||
[0, 0], ['0', '0'],
|
||||
[1.2, 0], ['1.2', '0'],
|
||||
[0, ''], ['0', ''],
|
||||
['foo', 0], ['foo', '0'],
|
||||
[0, <div />], ['0', <div />],
|
||||
|
||||
[true, 1.2], ['1.2'],
|
||||
[1.2, 0], ['1.2', '0'],
|
||||
[1.2, 1.2], ['1.2', '1.2'],
|
||||
[1.2, ''], ['1.2', ''],
|
||||
['foo', 1.2], ['foo', '1.2'],
|
||||
[1.2, <div />], ['1.2', <div />],
|
||||
|
||||
[true, ''], [''],
|
||||
['', 0], ['', '0'],
|
||||
[1.2, ''], ['1.2', ''],
|
||||
['', ''], ['', ''],
|
||||
['foo', ''], ['foo', ''],
|
||||
['', <div />], ['', <div />],
|
||||
|
||||
[true, 'foo'], ['foo'],
|
||||
['foo', 0], ['foo', '0'],
|
||||
[1.2, 'foo'], ['1.2', 'foo'],
|
||||
['foo', ''], ['foo', ''],
|
||||
['foo', 'foo'], ['foo', 'foo'],
|
||||
['foo', <div />], ['foo', <div />],
|
||||
|
||||
// values separated by an element
|
||||
[true, <div />, true], [<div />],
|
||||
[1.2, <div />, 1.2], ['1.2', <div />, '1.2'],
|
||||
['', <div />, ''], ['', <div />, ''],
|
||||
['foo', <div />, 'foo'], ['foo', <div />, 'foo'],
|
||||
|
||||
[true, 1.2, <div />, '', 'foo'], ['1.2', <div />, '', 'foo'],
|
||||
[1.2, '', <div />, 'foo', true], ['1.2', '', <div />, 'foo'],
|
||||
['', 'foo', <div />, true, 1.2], ['', 'foo', <div />, '1.2'],
|
||||
|
||||
[true, 1.2, '', <div />, 'foo', true, 1.2], ['1.2', '', <div />, 'foo', '1.2'],
|
||||
['', 'foo', true, <div />, 1.2, '', 'foo'], ['', 'foo', <div />, '1.2', '', 'foo'],
|
||||
|
||||
// values inside arrays
|
||||
[[true], [true]], [],
|
||||
[[1.2], [1.2]], ['1.2', '1.2'],
|
||||
[[''], ['']], ['', ''],
|
||||
[['foo'], ['foo']], ['foo', 'foo'],
|
||||
[[<div />], [<div />]], [<div />, <div />],
|
||||
|
||||
[[true, 1.2, <div />], '', 'foo'], ['1.2', <div />, '', 'foo'],
|
||||
[1.2, '', [<div />, 'foo', true]], ['1.2', '', <div />, 'foo'],
|
||||
['', ['foo', <div />, true], 1.2], ['', 'foo', <div />, '1.2'],
|
||||
|
||||
[true, [1.2, '', <div />, 'foo'], true, 1.2], ['1.2', '', <div />, 'foo', '1.2'],
|
||||
['', 'foo', [true, <div />, 1.2, ''], 'foo'], ['', 'foo', <div />, '1.2', '', 'foo'],
|
||||
|
||||
// values inside elements
|
||||
[<div>{true}{1.2}{<div />}</div>, '', 'foo'], [<div />, '', 'foo'],
|
||||
[1.2, '', <div>{<div />}{'foo'}{true}</div>], ['1.2', '', <div />],
|
||||
['', <div>{'foo'}{<div />}{true}</div>, 1.2], ['', <div />, '1.2'],
|
||||
|
||||
[true, <div>{1.2}{''}{<div />}{'foo'}</div>, true, 1.2], [<div />, '1.2'],
|
||||
['', 'foo', <div>{true}{<div />}{1.2}{''}</div>, 'foo'], ['', 'foo', <div />, 'foo'],
|
||||
]);
|
||||
if (__DEV__) {
|
||||
expect(console.error).toHaveBeenCalledTimes(2);
|
||||
expect(console.error.mock.calls[0][0]).toMatch(
|
||||
'Each child in a list should have a unique "key" prop.',
|
||||
);
|
||||
expect(console.error.mock.calls[1][0]).toMatch(
|
||||
'Each child in a list should have a unique "key" prop.',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it('should correctly handle bigint children for render and update', async () => {
|
||||
|
||||
@@ -16,6 +16,7 @@ let ReactDOMServer;
|
||||
let act;
|
||||
let Scheduler;
|
||||
let assertLog;
|
||||
let assertConsoleErrorDev;
|
||||
|
||||
function getTestDocument(markup) {
|
||||
const doc = document.implementation.createHTMLDocument('');
|
||||
@@ -48,6 +49,8 @@ describe('rendering React components at document', () => {
|
||||
act = require('internal-test-utils').act;
|
||||
assertLog = require('internal-test-utils').assertLog;
|
||||
Scheduler = require('scheduler');
|
||||
assertConsoleErrorDev =
|
||||
require('internal-test-utils').assertConsoleErrorDev;
|
||||
});
|
||||
|
||||
describe('with new explicit hydration API', () => {
|
||||
@@ -269,30 +272,46 @@ describe('rendering React components at document', () => {
|
||||
const favorSafetyOverHydrationPerf = gate(
|
||||
flags => flags.favorSafetyOverHydrationPerf,
|
||||
);
|
||||
expect(() => {
|
||||
ReactDOM.flushSync(() => {
|
||||
ReactDOMClient.hydrateRoot(
|
||||
testDocument,
|
||||
<Component text="Hello world" />,
|
||||
{
|
||||
onRecoverableError: error => {
|
||||
Scheduler.log(
|
||||
'onRecoverableError: ' + normalizeError(error.message),
|
||||
);
|
||||
if (error.cause) {
|
||||
Scheduler.log(
|
||||
'Cause: ' + normalizeError(error.cause.message),
|
||||
);
|
||||
}
|
||||
},
|
||||
ReactDOM.flushSync(() => {
|
||||
ReactDOMClient.hydrateRoot(
|
||||
testDocument,
|
||||
<Component text="Hello world" />,
|
||||
{
|
||||
onRecoverableError: error => {
|
||||
Scheduler.log(
|
||||
'onRecoverableError: ' + normalizeError(error.message),
|
||||
);
|
||||
if (error.cause) {
|
||||
Scheduler.log('Cause: ' + normalizeError(error.cause.message));
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}).toErrorDev(
|
||||
},
|
||||
);
|
||||
});
|
||||
assertConsoleErrorDev(
|
||||
favorSafetyOverHydrationPerf
|
||||
? []
|
||||
: [
|
||||
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties.",
|
||||
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. " +
|
||||
"This won't be patched up. This can happen if a SSR-ed Client Component used:\n" +
|
||||
'\n' +
|
||||
"- A server/client branch `if (typeof window !== 'undefined')`.\n" +
|
||||
"- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n" +
|
||||
"- Date formatting in a user's locale which doesn't match the server.\n" +
|
||||
'- External changing data without sending a snapshot of it along with the HTML.\n' +
|
||||
'- Invalid HTML tag nesting.\n\nIt can also happen if the client has a browser extension ' +
|
||||
'installed which messes with the HTML before React loaded.\n' +
|
||||
'\n' +
|
||||
'https://react.dev/link/hydration-mismatch\n' +
|
||||
'\n' +
|
||||
' <Component text="Hello world">\n' +
|
||||
' <html>\n' +
|
||||
' <head>\n' +
|
||||
' <body>\n' +
|
||||
'+ Hello world\n' +
|
||||
'- Goodbye world\n' +
|
||||
'+ Hello world\n' +
|
||||
'- Goodbye world\n',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
|
||||
@@ -14,6 +14,7 @@ let React;
|
||||
let ReactDOMServer;
|
||||
let PropTypes;
|
||||
let ReactSharedInternals;
|
||||
let assertConsoleErrorDev;
|
||||
|
||||
describe('ReactDOMServer', () => {
|
||||
beforeEach(() => {
|
||||
@@ -21,6 +22,8 @@ describe('ReactDOMServer', () => {
|
||||
React = require('react');
|
||||
PropTypes = require('prop-types');
|
||||
ReactDOMServer = require('react-dom/server');
|
||||
assertConsoleErrorDev =
|
||||
require('internal-test-utils').assertConsoleErrorDev;
|
||||
ReactSharedInternals =
|
||||
React.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
|
||||
});
|
||||
@@ -159,15 +162,18 @@ describe('ReactDOMServer', () => {
|
||||
});
|
||||
|
||||
it('should not crash on poisoned hasOwnProperty', () => {
|
||||
let html;
|
||||
expect(
|
||||
() =>
|
||||
(html = ReactDOMServer.renderToString(
|
||||
<div hasOwnProperty="poison">
|
||||
<span unknown="test" />
|
||||
</div>,
|
||||
)),
|
||||
).toErrorDev(['React does not recognize the `hasOwnProperty` prop']);
|
||||
const html = ReactDOMServer.renderToString(
|
||||
<div hasOwnProperty="poison">
|
||||
<span unknown="test" />
|
||||
</div>,
|
||||
);
|
||||
assertConsoleErrorDev([
|
||||
'React does not recognize the `hasOwnProperty` prop on a DOM element. ' +
|
||||
'If you intentionally want it to appear in the DOM as a custom attribute, ' +
|
||||
'spell it as lowercase `hasownproperty` instead. ' +
|
||||
'If you accidentally passed it from a parent component, remove it from the DOM element.\n' +
|
||||
' in div (at **)',
|
||||
]);
|
||||
expect(html).toContain('<span unknown="test">');
|
||||
});
|
||||
});
|
||||
@@ -371,16 +377,18 @@ describe('ReactDOMServer', () => {
|
||||
text: PropTypes.string,
|
||||
};
|
||||
|
||||
let markup;
|
||||
expect(() => {
|
||||
markup = ReactDOMServer.renderToStaticMarkup(
|
||||
<ContextProvider>
|
||||
<Component />
|
||||
</ContextProvider>,
|
||||
);
|
||||
}).toErrorDev([
|
||||
'ContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
|
||||
'Component uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
|
||||
const markup = ReactDOMServer.renderToStaticMarkup(
|
||||
<ContextProvider>
|
||||
<Component />
|
||||
</ContextProvider>,
|
||||
);
|
||||
assertConsoleErrorDev([
|
||||
'ContextProvider uses the legacy childContextTypes API which will soon be removed. ' +
|
||||
'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
|
||||
' in ContextProvider (at **)',
|
||||
'Component uses the legacy contextTypes API which will soon be removed. ' +
|
||||
'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
|
||||
' in Component (at **)',
|
||||
]);
|
||||
expect(markup).toContain('hello, world');
|
||||
});
|
||||
@@ -597,10 +605,15 @@ describe('ReactDOMServer', () => {
|
||||
}
|
||||
|
||||
ReactDOMServer.renderToString(<Foo />);
|
||||
expect(() => jest.runOnlyPendingTimers()).toErrorDev(
|
||||
'Can only update a mounting component.' +
|
||||
' This usually means you called setState() outside componentWillMount() on the server.' +
|
||||
' This is a no-op.\n\nPlease check the code for the Foo component.',
|
||||
jest.runOnlyPendingTimers();
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
'Can only update a mounting component. ' +
|
||||
'This usually means you called setState() outside componentWillMount() on the server. ' +
|
||||
'This is a no-op.\n' +
|
||||
'\n' +
|
||||
'Please check the code for the Foo component.',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
|
||||
@@ -625,10 +638,15 @@ describe('ReactDOMServer', () => {
|
||||
}
|
||||
|
||||
ReactDOMServer.renderToString(<Baz />);
|
||||
expect(() => jest.runOnlyPendingTimers()).toErrorDev(
|
||||
'Can only update a mounting component. ' +
|
||||
'This usually means you called forceUpdate() outside componentWillMount() on the server. ' +
|
||||
'This is a no-op.\n\nPlease check the code for the Baz component.',
|
||||
jest.runOnlyPendingTimers();
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
'Can only update a mounting component. ' +
|
||||
'This usually means you called forceUpdate() outside componentWillMount() on the server. ' +
|
||||
'This is a no-op.\n' +
|
||||
'\n' +
|
||||
'Please check the code for the Baz component.',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
const markup = ReactDOMServer.renderToStaticMarkup(<Baz />);
|
||||
@@ -722,41 +740,50 @@ describe('ReactDOMServer', () => {
|
||||
// Make sure namespace passes through composites
|
||||
return <g>{props.children}</g>;
|
||||
}
|
||||
expect(() =>
|
||||
ReactDOMServer.renderToStaticMarkup(
|
||||
<div>
|
||||
<inPUT />
|
||||
<svg>
|
||||
<CompositeG>
|
||||
<linearGradient />
|
||||
<foreignObject>
|
||||
{/* back to HTML */}
|
||||
<iFrame />
|
||||
</foreignObject>
|
||||
</CompositeG>
|
||||
</svg>
|
||||
</div>,
|
||||
),
|
||||
).toErrorDev([
|
||||
ReactDOMServer.renderToStaticMarkup(
|
||||
<div>
|
||||
<inPUT />
|
||||
<svg>
|
||||
<CompositeG>
|
||||
<linearGradient />
|
||||
<foreignObject>
|
||||
{/* back to HTML */}
|
||||
<iFrame />
|
||||
</foreignObject>
|
||||
</CompositeG>
|
||||
</svg>
|
||||
</div>,
|
||||
);
|
||||
assertConsoleErrorDev([
|
||||
'<inPUT /> is using incorrect casing. ' +
|
||||
'Use PascalCase for React components, ' +
|
||||
'or lowercase for HTML elements.',
|
||||
'or lowercase for HTML elements.\n' +
|
||||
' in inPUT (at **)' +
|
||||
(gate('enableOwnerStacks') ? '' : '\n in div (at **)'),
|
||||
// linearGradient doesn't warn
|
||||
'<iFrame /> is using incorrect casing. ' +
|
||||
'Use PascalCase for React components, ' +
|
||||
'or lowercase for HTML elements.',
|
||||
'or lowercase for HTML elements.\n' +
|
||||
' in iFrame (at **)' +
|
||||
(gate('enableOwnerStacks')
|
||||
? ''
|
||||
: '\n in foreignObject (at **)' +
|
||||
'\n in g (at **)' +
|
||||
'\n in CompositeG (at **)' +
|
||||
'\n in svg (at **)' +
|
||||
'\n in div (at **)'),
|
||||
]);
|
||||
});
|
||||
|
||||
it('should warn about contentEditable and children', () => {
|
||||
expect(() =>
|
||||
ReactDOMServer.renderToString(<div contentEditable={true} children="" />),
|
||||
).toErrorDev(
|
||||
ReactDOMServer.renderToString(<div contentEditable={true} children="" />);
|
||||
assertConsoleErrorDev([
|
||||
'A component is `contentEditable` and contains `children` ' +
|
||||
'managed by React. It is now your responsibility to guarantee that ' +
|
||||
'none of those nodes are unexpectedly modified or duplicated. This ' +
|
||||
'is probably not intentional.\n in div (at **)',
|
||||
);
|
||||
'is probably not intentional.\n' +
|
||||
' in div (at **)',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should warn when server rendering a class with a render method that does not extend React.Component', () => {
|
||||
@@ -766,15 +793,15 @@ describe('ReactDOMServer', () => {
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
expect(() =>
|
||||
ReactDOMServer.renderToString(<ClassWithRenderNotExtended />),
|
||||
).toThrow(TypeError);
|
||||
}).toErrorDev(
|
||||
expect(() =>
|
||||
ReactDOMServer.renderToString(<ClassWithRenderNotExtended />),
|
||||
).toThrow(TypeError);
|
||||
assertConsoleErrorDev([
|
||||
'The <ClassWithRenderNotExtended /> component appears to have a render method, ' +
|
||||
"but doesn't extend React.Component. This is likely to cause errors. " +
|
||||
'Change ClassWithRenderNotExtended to extend React.Component instead.',
|
||||
);
|
||||
'Change ClassWithRenderNotExtended to extend React.Component instead.\n' +
|
||||
' in ClassWithRenderNotExtended (at **)',
|
||||
]);
|
||||
|
||||
// Test deduplication
|
||||
expect(() => {
|
||||
@@ -839,7 +866,8 @@ describe('ReactDOMServer', () => {
|
||||
);
|
||||
}
|
||||
|
||||
expect(() => ReactDOMServer.renderToString(<App />)).toErrorDev([
|
||||
ReactDOMServer.renderToString(<App />);
|
||||
assertConsoleErrorDev([
|
||||
'Invalid ARIA attribute `ariaTypo`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
|
||||
(gate(flags => flags.enableOwnerStacks)
|
||||
? ' in span (at **)\n' +
|
||||
@@ -897,7 +925,8 @@ describe('ReactDOMServer', () => {
|
||||
);
|
||||
}
|
||||
|
||||
expect(() => ReactDOMServer.renderToString(<App />)).toErrorDev([
|
||||
ReactDOMServer.renderToString(<App />);
|
||||
assertConsoleErrorDev([
|
||||
// ReactDOMServer(App > div > span)
|
||||
'Invalid ARIA attribute `ariaTypo`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
|
||||
(gate(flags => flags.enableOwnerStacks)
|
||||
@@ -907,12 +936,23 @@ describe('ReactDOMServer', () => {
|
||||
' in App (at **)'),
|
||||
// ReactDOMServer(App > div > Child) >>> ReactDOMServer(App2) >>> ReactDOMServer(blink)
|
||||
'Invalid ARIA attribute `ariaTypo2`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
|
||||
' in blink (at **)',
|
||||
(gate(flags => flags.enableOwnerStacks)
|
||||
? ' in blink (at **)\n' +
|
||||
' in App2 (at **)\n' +
|
||||
' in Child (at **)\n' +
|
||||
' in App (at **)'
|
||||
: ' in blink (at **)'),
|
||||
// ReactDOMServer(App > div > Child) >>> ReactDOMServer(App2 > Child2 > span)
|
||||
'Invalid ARIA attribute `ariaTypo3`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
|
||||
' in span (at **)\n' +
|
||||
' in Child2 (at **)\n' +
|
||||
' in App2 (at **)',
|
||||
(gate(flags => flags.enableOwnerStacks)
|
||||
? ' in span (at **)\n' +
|
||||
' in Child2 (at **)\n' +
|
||||
' in App2 (at **)\n' +
|
||||
' in Child (at **)\n' +
|
||||
' in App (at **)'
|
||||
: ' in span (at **)\n' +
|
||||
' in Child2 (at **)\n' +
|
||||
' in App2 (at **)'),
|
||||
// ReactDOMServer(App > div > Child > span)
|
||||
'Invalid ARIA attribute `ariaTypo4`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
|
||||
(gate(flags => flags.enableOwnerStacks)
|
||||
@@ -943,13 +983,13 @@ describe('ReactDOMServer', () => {
|
||||
}
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
ReactDOMServer.renderToString(<ComponentA />);
|
||||
}).toErrorDev(
|
||||
ReactDOMServer.renderToString(<ComponentA />);
|
||||
assertConsoleErrorDev([
|
||||
'ComponentA defines an invalid contextType. ' +
|
||||
'contextType should point to the Context object returned by React.createContext(). ' +
|
||||
'Did you accidentally pass the Context.Consumer instead?',
|
||||
);
|
||||
'Did you accidentally pass the Context.Consumer instead?\n' +
|
||||
' in ComponentA (at **)',
|
||||
]);
|
||||
|
||||
// Warnings should be deduped by component type
|
||||
ReactDOMServer.renderToString(<ComponentA />);
|
||||
@@ -988,17 +1028,17 @@ describe('ReactDOMServer', () => {
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
expect(() => {
|
||||
ReactDOMServer.renderToString(<Foo />);
|
||||
}).toThrow("Cannot read property 'world' of undefined");
|
||||
}).toErrorDev(
|
||||
ReactDOMServer.renderToString(<Foo />);
|
||||
}).toThrow("Cannot read property 'world' of undefined");
|
||||
assertConsoleErrorDev([
|
||||
'Foo defines an invalid contextType. ' +
|
||||
'contextType should point to the Context object returned by React.createContext(). ' +
|
||||
'However, it is set to undefined. ' +
|
||||
'This can be caused by a typo or by mixing up named and default imports. ' +
|
||||
'This can also happen due to a circular dependency, ' +
|
||||
'so try moving the createContext() call to a separate file.',
|
||||
);
|
||||
'so try moving the createContext() call to a separate file.\n' +
|
||||
' in Foo (at **)',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should warn when class contextType is an object', () => {
|
||||
@@ -1014,14 +1054,14 @@ describe('ReactDOMServer', () => {
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
expect(() => {
|
||||
ReactDOMServer.renderToString(<Foo />);
|
||||
}).toThrow("Cannot read property 'hello' of undefined");
|
||||
}).toErrorDev(
|
||||
ReactDOMServer.renderToString(<Foo />);
|
||||
}).toThrow("Cannot read property 'hello' of undefined");
|
||||
assertConsoleErrorDev([
|
||||
'Foo defines an invalid contextType. ' +
|
||||
'contextType should point to the Context object returned by React.createContext(). ' +
|
||||
'However, it is set to an object with keys {x, y}.',
|
||||
);
|
||||
'However, it is set to an object with keys {x, y}.\n' +
|
||||
' in Foo (at **)',
|
||||
]);
|
||||
});
|
||||
|
||||
it('should warn when class contextType is a primitive', () => {
|
||||
@@ -1033,14 +1073,14 @@ describe('ReactDOMServer', () => {
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
expect(() => {
|
||||
ReactDOMServer.renderToString(<Foo />);
|
||||
}).toThrow("Cannot read property 'world' of undefined");
|
||||
}).toErrorDev(
|
||||
ReactDOMServer.renderToString(<Foo />);
|
||||
}).toThrow("Cannot read property 'world' of undefined");
|
||||
assertConsoleErrorDev([
|
||||
'Foo defines an invalid contextType. ' +
|
||||
'contextType should point to the Context object returned by React.createContext(). ' +
|
||||
'However, it is set to a string.',
|
||||
);
|
||||
'However, it is set to a string.\n' +
|
||||
' in Foo (at **)',
|
||||
]);
|
||||
});
|
||||
|
||||
describe('custom element server rendering', () => {
|
||||
|
||||
@@ -17,6 +17,8 @@ let ReactDOMServer;
|
||||
let ReactDOMServerBrowser;
|
||||
let waitForAll;
|
||||
let act;
|
||||
let assertConsoleErrorDev;
|
||||
let assertConsoleWarnDev;
|
||||
|
||||
// These tests rely both on ReactDOMServer and ReactDOM.
|
||||
// If a test only needs ReactDOMServer, put it in ReactServerRendering-test instead.
|
||||
@@ -32,6 +34,8 @@ describe('ReactDOMServerHydration', () => {
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
waitForAll = InternalTestUtils.waitForAll;
|
||||
act = InternalTestUtils.act;
|
||||
assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
|
||||
assertConsoleWarnDev = InternalTestUtils.assertConsoleWarnDev;
|
||||
});
|
||||
|
||||
it('should have the correct mounting behavior', async () => {
|
||||
@@ -126,26 +130,40 @@ describe('ReactDOMServerHydration', () => {
|
||||
const favorSafetyOverHydrationPerf = gate(
|
||||
flags => flags.favorSafetyOverHydrationPerf,
|
||||
);
|
||||
await expect(async () => {
|
||||
root = await act(() => {
|
||||
return ReactDOMClient.hydrateRoot(
|
||||
element,
|
||||
<TestComponent
|
||||
name="y"
|
||||
ref={current => {
|
||||
instance = current;
|
||||
}}
|
||||
/>,
|
||||
{
|
||||
onRecoverableError: error => {},
|
||||
},
|
||||
);
|
||||
});
|
||||
}).toErrorDev(
|
||||
root = await act(() => {
|
||||
return ReactDOMClient.hydrateRoot(
|
||||
element,
|
||||
<TestComponent
|
||||
name="y"
|
||||
ref={current => {
|
||||
instance = current;
|
||||
}}
|
||||
/>,
|
||||
{
|
||||
onRecoverableError: error => {},
|
||||
},
|
||||
);
|
||||
});
|
||||
assertConsoleErrorDev(
|
||||
favorSafetyOverHydrationPerf
|
||||
? []
|
||||
: [
|
||||
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties.",
|
||||
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. " +
|
||||
"This won't be patched up. This can happen if a SSR-ed Client Component used:\n" +
|
||||
'\n' +
|
||||
"- A server/client branch `if (typeof window !== 'undefined')`.\n" +
|
||||
"- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n" +
|
||||
"- Date formatting in a user's locale which doesn't match the server.\n" +
|
||||
'- External changing data without sending a snapshot of it along with the HTML.\n' +
|
||||
'- Invalid HTML tag nesting.\n\nIt can also happen if the client has a browser extension ' +
|
||||
'installed which messes with the HTML before React loaded.\n' +
|
||||
'\n' +
|
||||
'https://react.dev/link/hydration-mismatch\n' +
|
||||
'\n' +
|
||||
' <TestComponent name="y" ref={function ref}>\n' +
|
||||
' <span ref={{current:null}} onClick={function}>\n' +
|
||||
'+ y\n' +
|
||||
'- x\n',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
@@ -224,21 +242,34 @@ describe('ReactDOMServerHydration', () => {
|
||||
const favorSafetyOverHydrationPerf = gate(
|
||||
flags => flags.favorSafetyOverHydrationPerf,
|
||||
);
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
ReactDOMClient.hydrateRoot(
|
||||
element,
|
||||
<button autoFocus={false} onFocus={onFocusAfterHydration}>
|
||||
client
|
||||
</button>,
|
||||
{onRecoverableError: error => {}},
|
||||
);
|
||||
});
|
||||
}).toErrorDev(
|
||||
await act(() => {
|
||||
ReactDOMClient.hydrateRoot(
|
||||
element,
|
||||
<button autoFocus={false} onFocus={onFocusAfterHydration}>
|
||||
client
|
||||
</button>,
|
||||
{onRecoverableError: error => {}},
|
||||
);
|
||||
});
|
||||
assertConsoleErrorDev(
|
||||
favorSafetyOverHydrationPerf
|
||||
? []
|
||||
: [
|
||||
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties.",
|
||||
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. " +
|
||||
"This won't be patched up. This can happen if a SSR-ed Client Component used:\n" +
|
||||
'\n' +
|
||||
"- A server/client branch `if (typeof window !== 'undefined')`.\n" +
|
||||
"- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n" +
|
||||
"- Date formatting in a user's locale which doesn't match the server.\n" +
|
||||
'- External changing data without sending a snapshot of it along with the HTML.\n' +
|
||||
'- Invalid HTML tag nesting.\n\nIt can also happen if the client has a browser extension ' +
|
||||
'installed which messes with the HTML before React loaded.\n' +
|
||||
'\n' +
|
||||
'https://react.dev/link/hydration-mismatch\n' +
|
||||
'\n' +
|
||||
' <button autoFocus={false} onFocus={function mockConstructor}>\n' +
|
||||
'+ client\n' +
|
||||
'- server\n',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
@@ -255,17 +286,37 @@ describe('ReactDOMServerHydration', () => {
|
||||
expect(element.firstChild.style.textDecoration).toBe('none');
|
||||
expect(element.firstChild.style.color).toBe('black');
|
||||
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
ReactDOMClient.hydrateRoot(
|
||||
element,
|
||||
<div
|
||||
style={{textDecoration: 'none', color: 'white', height: '10px'}}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
}).toErrorDev(
|
||||
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties.",
|
||||
await act(() => {
|
||||
ReactDOMClient.hydrateRoot(
|
||||
element,
|
||||
<div
|
||||
style={{textDecoration: 'none', color: 'white', height: '10px'}}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. " +
|
||||
"This won't be patched up. This can happen if a SSR-ed Client Component used:\n" +
|
||||
'\n' +
|
||||
"- A server/client branch `if (typeof window !== 'undefined')`.\n" +
|
||||
"- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n" +
|
||||
"- Date formatting in a user's locale which doesn't match the server.\n" +
|
||||
'- External changing data without sending a snapshot of it along with the HTML.\n' +
|
||||
'- Invalid HTML tag nesting.\n\nIt can also happen if the client has a browser extension ' +
|
||||
'installed which messes with the HTML before React loaded.\n' +
|
||||
'\n' +
|
||||
'https://react.dev/link/hydration-mismatch\n' +
|
||||
'\n' +
|
||||
' <div\n style={{\n+ textDecoration: "none"\n' +
|
||||
'+ color: "white"\n' +
|
||||
'- color: "black"\n' +
|
||||
'+ height: "10px"\n' +
|
||||
'- height: "10px"\n' +
|
||||
'- text-decoration: "none"\n' +
|
||||
' }}\n' +
|
||||
' >\n',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
@@ -303,17 +354,39 @@ describe('ReactDOMServerHydration', () => {
|
||||
element.innerHTML =
|
||||
'<div style="text-decoration: none; color: black; height: 10px;"></div>';
|
||||
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
ReactDOMClient.hydrateRoot(
|
||||
element,
|
||||
<div
|
||||
style={{textDecoration: 'none', color: 'black', height: '10px'}}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
}).toErrorDev(
|
||||
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties.",
|
||||
await act(() => {
|
||||
ReactDOMClient.hydrateRoot(
|
||||
element,
|
||||
<div
|
||||
style={{textDecoration: 'none', color: 'black', height: '10px'}}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. " +
|
||||
"This won't be patched up. This can happen if a SSR-ed Client Component used:\n" +
|
||||
'\n' +
|
||||
"- A server/client branch `if (typeof window !== 'undefined')`.\n" +
|
||||
"- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n" +
|
||||
"- Date formatting in a user's locale which doesn't match the server.\n" +
|
||||
'- External changing data without sending a snapshot of it along with the HTML.\n' +
|
||||
'- Invalid HTML tag nesting.\n\nIt can also happen if the client has a browser extension ' +
|
||||
'installed which messes with the HTML before React loaded.\n' +
|
||||
'\n' +
|
||||
'https://react.dev/link/hydration-mismatch\n' +
|
||||
'\n' +
|
||||
' <div\n' +
|
||||
' style={{\n' +
|
||||
'+ textDecoration: "none"\n' +
|
||||
'+ color: "black"\n' +
|
||||
'- color: "black"\n' +
|
||||
'+ height: "10px"\n' +
|
||||
'- height: "10px"\n' +
|
||||
'- text-decoration: "none"\n' +
|
||||
' }}\n' +
|
||||
' >\n',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
});
|
||||
@@ -347,18 +420,38 @@ describe('ReactDOMServerHydration', () => {
|
||||
);
|
||||
|
||||
const element = document.createElement('div');
|
||||
expect(() => {
|
||||
element.innerHTML = ReactDOMServer.renderToString(markup);
|
||||
}).toWarnDev('componentWillMount has been renamed');
|
||||
element.innerHTML = ReactDOMServer.renderToString(markup);
|
||||
assertConsoleWarnDev([
|
||||
'componentWillMount has been renamed, and is not recommended for use. ' +
|
||||
'See https://react.dev/link/unsafe-component-lifecycles for details.\n' +
|
||||
'\n' +
|
||||
'* Move code from componentWillMount to componentDidMount (preferred in most cases) or the constructor.\n' +
|
||||
'\n' +
|
||||
'Please update the following components: ComponentWithWarning\n' +
|
||||
' in ComponentWithWarning (at **)',
|
||||
]);
|
||||
expect(element.textContent).toBe('Hi');
|
||||
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
ReactDOMClient.hydrateRoot(element, markup);
|
||||
});
|
||||
}).toWarnDev('componentWillMount has been renamed', {
|
||||
withoutStack: true,
|
||||
await act(() => {
|
||||
ReactDOMClient.hydrateRoot(element, markup);
|
||||
});
|
||||
assertConsoleWarnDev(
|
||||
[
|
||||
'componentWillMount has been renamed, and is not recommended for use. ' +
|
||||
'See https://react.dev/link/unsafe-component-lifecycles for details.\n' +
|
||||
'\n' +
|
||||
'* Move code with side effects to componentDidMount, and set initial state in the constructor.\n' +
|
||||
'* Rename componentWillMount to UNSAFE_componentWillMount to suppress this warning in non-strict mode. ' +
|
||||
'In React 18.x, only the UNSAFE_ name will work. ' +
|
||||
'To rename all deprecated lifecycles to their new names, ' +
|
||||
'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n' +
|
||||
'\n' +
|
||||
'Please update the following components: ComponentWithWarning',
|
||||
],
|
||||
{
|
||||
withoutStack: true,
|
||||
},
|
||||
);
|
||||
expect(element.textContent).toBe('Hi');
|
||||
});
|
||||
|
||||
@@ -531,21 +624,35 @@ describe('ReactDOMServerHydration', () => {
|
||||
const favorSafetyOverHydrationPerf = gate(
|
||||
flags => flags.favorSafetyOverHydrationPerf,
|
||||
);
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
ReactDOMClient.hydrateRoot(
|
||||
domElement,
|
||||
<div dangerouslySetInnerHTML={undefined}>
|
||||
<p>client</p>
|
||||
</div>,
|
||||
{onRecoverableError: error => {}},
|
||||
);
|
||||
});
|
||||
}).toErrorDev(
|
||||
await act(() => {
|
||||
ReactDOMClient.hydrateRoot(
|
||||
domElement,
|
||||
<div dangerouslySetInnerHTML={undefined}>
|
||||
<p>client</p>
|
||||
</div>,
|
||||
{onRecoverableError: error => {}},
|
||||
);
|
||||
});
|
||||
assertConsoleErrorDev(
|
||||
favorSafetyOverHydrationPerf
|
||||
? []
|
||||
: [
|
||||
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties.",
|
||||
"A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. " +
|
||||
"This won't be patched up. This can happen if a SSR-ed Client Component used:\n" +
|
||||
'\n' +
|
||||
"- A server/client branch `if (typeof window !== 'undefined')`.\n" +
|
||||
"- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.\n" +
|
||||
"- Date formatting in a user's locale which doesn't match the server.\n" +
|
||||
'- External changing data without sending a snapshot of it along with the HTML.\n' +
|
||||
'- Invalid HTML tag nesting.\n\nIt can also happen if the client has a browser extension ' +
|
||||
'installed which messes with the HTML before React loaded.\n' +
|
||||
'\n' +
|
||||
'https://react.dev/link/hydration-mismatch\n' +
|
||||
'\n' +
|
||||
' <div dangerouslySetInnerHTML={undefined}>\n' +
|
||||
' <p>\n' +
|
||||
'+ client\n' +
|
||||
'- server\n',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
@@ -593,13 +700,13 @@ describe('ReactDOMServerHydration', () => {
|
||||
const jsx = React.createElement('my-custom-element', props);
|
||||
const element = document.createElement('div');
|
||||
element.innerHTML = ReactDOMServer.renderToString(jsx);
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
ReactDOMClient.hydrateRoot(element, jsx);
|
||||
});
|
||||
}).toErrorDev(
|
||||
`Assignment to read-only property will result in a no-op: \`${readOnlyProperty}\``,
|
||||
);
|
||||
await act(() => {
|
||||
ReactDOMClient.hydrateRoot(element, jsx);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
`Assignment to read-only property will result in a no-op: \`${readOnlyProperty}\`
|
||||
in my-custom-element (at **)`,
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ let Scheduler;
|
||||
let act;
|
||||
let container;
|
||||
let assertLog;
|
||||
let assertConsoleErrorDev;
|
||||
|
||||
jest.useRealTimers();
|
||||
|
||||
@@ -88,6 +89,7 @@ function runActTests(render, unmount, rerender) {
|
||||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
assertLog = InternalTestUtils.assertLog;
|
||||
assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
|
||||
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
@@ -205,8 +207,20 @@ function runActTests(render, unmount, rerender) {
|
||||
render(<App />, container);
|
||||
});
|
||||
|
||||
expect(() => setValue(1)).toErrorDev([
|
||||
'An update to App inside a test was not wrapped in act(...).',
|
||||
setValue(1);
|
||||
assertConsoleErrorDev([
|
||||
'An update to App inside a test was not wrapped in act(...).\n' +
|
||||
'\n' +
|
||||
'When testing, code that causes React state updates should be wrapped into act(...):\n' +
|
||||
'\n' +
|
||||
'act(() => {\n' +
|
||||
' /* fire events that update state */\n' +
|
||||
'});\n' +
|
||||
'/* assert on the output */\n' +
|
||||
'\n' +
|
||||
"This ensures that you're testing the behavior the user would see in the browser. " +
|
||||
'Learn more at https://react.dev/link/wrap-tests-with-act\n' +
|
||||
' in App (at **)',
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -232,8 +246,20 @@ function runActTests(render, unmount, rerender) {
|
||||
rerender(<App defaultValue={0} />, container);
|
||||
});
|
||||
|
||||
expect(() => setValue(1)).toErrorDev([
|
||||
'An update to App inside a test was not wrapped in act(...).',
|
||||
setValue(1);
|
||||
assertConsoleErrorDev([
|
||||
'An update to App inside a test was not wrapped in act(...).\n' +
|
||||
'\n' +
|
||||
'When testing, code that causes React state updates should be wrapped into act(...):\n' +
|
||||
'\n' +
|
||||
'act(() => {\n' +
|
||||
' /* fire events that update state */\n' +
|
||||
'});\n' +
|
||||
'/* assert on the output */\n' +
|
||||
'\n' +
|
||||
"This ensures that you're testing the behavior the user would see in the browser. " +
|
||||
'Learn more at https://react.dev/link/wrap-tests-with-act\n' +
|
||||
' in App (at **)',
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -251,9 +277,21 @@ function runActTests(render, unmount, rerender) {
|
||||
});
|
||||
|
||||
// First show that it does warn
|
||||
expect(() => setState(1)).toErrorDev(
|
||||
'An update to App inside a test was not wrapped in act(...)',
|
||||
);
|
||||
setState(1);
|
||||
assertConsoleErrorDev([
|
||||
'An update to App inside a test was not wrapped in act(...).\n' +
|
||||
'\n' +
|
||||
'When testing, code that causes React state updates should be wrapped into act(...):\n' +
|
||||
'\n' +
|
||||
'act(() => {\n' +
|
||||
' /* fire events that update state */\n' +
|
||||
'});\n' +
|
||||
'/* assert on the output */\n' +
|
||||
'\n' +
|
||||
"This ensures that you're testing the behavior the user would see in the browser. " +
|
||||
'Learn more at https://react.dev/link/wrap-tests-with-act\n' +
|
||||
' in App (at **)',
|
||||
]);
|
||||
|
||||
// Now do the same thing again, but disable with the environment flag
|
||||
const prevIsActEnvironment = global.IS_REACT_ACT_ENVIRONMENT;
|
||||
@@ -266,9 +304,21 @@ function runActTests(render, unmount, rerender) {
|
||||
|
||||
// When the flag is restored to its previous value, it should start
|
||||
// warning again. This shows that React reads the flag each time.
|
||||
expect(() => setState(3)).toErrorDev(
|
||||
'An update to App inside a test was not wrapped in act(...)',
|
||||
);
|
||||
setState(3);
|
||||
assertConsoleErrorDev([
|
||||
'An update to App inside a test was not wrapped in act(...).\n' +
|
||||
'\n' +
|
||||
'When testing, code that causes React state updates should be wrapped into act(...):\n' +
|
||||
'\n' +
|
||||
'act(() => {\n' +
|
||||
' /* fire events that update state */\n' +
|
||||
'});\n' +
|
||||
'/* assert on the output */\n' +
|
||||
'\n' +
|
||||
"This ensures that you're testing the behavior the user would see in the browser. " +
|
||||
'Learn more at https://react.dev/link/wrap-tests-with-act\n' +
|
||||
' in App (at **)',
|
||||
]);
|
||||
});
|
||||
|
||||
describe('fake timers', () => {
|
||||
|
||||
@@ -18,6 +18,7 @@ let Scheduler;
|
||||
let waitForAll;
|
||||
let waitFor;
|
||||
let assertLog;
|
||||
let assertConsoleErrorDev;
|
||||
|
||||
describe('ReactUpdates', () => {
|
||||
beforeEach(() => {
|
||||
@@ -29,6 +30,8 @@ describe('ReactUpdates', () => {
|
||||
ReactDOM.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE
|
||||
.findDOMNode;
|
||||
act = require('internal-test-utils').act;
|
||||
assertConsoleErrorDev =
|
||||
require('internal-test-utils').assertConsoleErrorDev;
|
||||
Scheduler = require('scheduler');
|
||||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
@@ -1039,38 +1042,42 @@ describe('ReactUpdates', () => {
|
||||
root.render(<A ref={current => (component = current)} />);
|
||||
});
|
||||
|
||||
await expect(
|
||||
expect(async () => {
|
||||
await act(() => {
|
||||
component.setState({}, 'no');
|
||||
});
|
||||
}).toErrorDev(
|
||||
'Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: no.',
|
||||
),
|
||||
).rejects.toThrowError(
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
component.setState({}, 'no');
|
||||
});
|
||||
}).rejects.toThrowError(
|
||||
'Invalid argument passed as callback. Expected a function. Instead ' +
|
||||
'received: no',
|
||||
);
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
'Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: no.',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
container = document.createElement('div');
|
||||
root = ReactDOMClient.createRoot(container);
|
||||
await act(() => {
|
||||
root.render(<A ref={current => (component = current)} />);
|
||||
});
|
||||
|
||||
await expect(
|
||||
expect(async () => {
|
||||
await act(() => {
|
||||
component.setState({}, {foo: 'bar'});
|
||||
});
|
||||
}).toErrorDev(
|
||||
'Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: [object Object].',
|
||||
),
|
||||
).rejects.toThrowError(
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
component.setState({}, {foo: 'bar'});
|
||||
});
|
||||
}).rejects.toThrowError(
|
||||
'Invalid argument passed as callback. Expected a function. Instead ' +
|
||||
'received: [object Object]',
|
||||
);
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
'Expected the last optional `callback` argument to be ' +
|
||||
"a function. Instead received: { foo: 'bar' }.",
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
container = document.createElement('div');
|
||||
root = ReactDOMClient.createRoot(container);
|
||||
await act(() => {
|
||||
@@ -1108,38 +1115,42 @@ describe('ReactUpdates', () => {
|
||||
root.render(<A ref={current => (component = current)} />);
|
||||
});
|
||||
|
||||
await expect(
|
||||
expect(async () => {
|
||||
await act(() => {
|
||||
component.forceUpdate('no');
|
||||
});
|
||||
}).toErrorDev(
|
||||
'Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: no.',
|
||||
),
|
||||
).rejects.toThrowError(
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
component.forceUpdate('no');
|
||||
});
|
||||
}).rejects.toThrowError(
|
||||
'Invalid argument passed as callback. Expected a function. Instead ' +
|
||||
'received: no',
|
||||
);
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
'Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: no.',
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
container = document.createElement('div');
|
||||
root = ReactDOMClient.createRoot(container);
|
||||
await act(() => {
|
||||
root.render(<A ref={current => (component = current)} />);
|
||||
});
|
||||
|
||||
await expect(
|
||||
expect(async () => {
|
||||
await act(() => {
|
||||
component.forceUpdate({foo: 'bar'});
|
||||
});
|
||||
}).toErrorDev(
|
||||
'Expected the last optional `callback` argument to be ' +
|
||||
'a function. Instead received: [object Object].',
|
||||
),
|
||||
).rejects.toThrowError(
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
component.forceUpdate({foo: 'bar'});
|
||||
});
|
||||
}).rejects.toThrowError(
|
||||
'Invalid argument passed as callback. Expected a function. Instead ' +
|
||||
'received: [object Object]',
|
||||
);
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
'Expected the last optional `callback` argument to be ' +
|
||||
"a function. Instead received: { foo: 'bar' }.",
|
||||
],
|
||||
{withoutStack: true},
|
||||
);
|
||||
// Make sure the warning is deduplicated and doesn't fire again
|
||||
container = document.createElement('div');
|
||||
root = ReactDOMClient.createRoot(container);
|
||||
@@ -1351,11 +1362,14 @@ describe('ReactUpdates', () => {
|
||||
|
||||
const container = document.createElement('div');
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
root.render(<Foo />);
|
||||
});
|
||||
}).toErrorDev('Cannot update during an existing state transition');
|
||||
await act(() => {
|
||||
root.render(<Foo />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'Cannot update during an existing state transition (such as within `render`). ' +
|
||||
'Render methods should be a pure function of props and state.\n' +
|
||||
' in Foo (at **)',
|
||||
]);
|
||||
|
||||
assertLog(['base: 0, memoized: 0', 'base: 1, memoized: 1']);
|
||||
});
|
||||
@@ -1798,12 +1812,14 @@ describe('ReactUpdates', () => {
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
|
||||
await expect(async () => {
|
||||
await expect(async () => {
|
||||
await act(() => ReactDOM.flushSync(() => root.render(<App />)));
|
||||
}).rejects.toThrow('Maximum update depth exceeded');
|
||||
}).toErrorDev(
|
||||
'Cannot update a component (`App`) while rendering a different component (`Child`)',
|
||||
);
|
||||
await act(() => ReactDOM.flushSync(() => root.render(<App />)));
|
||||
}).rejects.toThrow('Maximum update depth exceeded');
|
||||
assertConsoleErrorDev([
|
||||
'Cannot update a component (`App`) while rendering a different component (`Child`). ' +
|
||||
'To locate the bad setState() call inside `Child`, ' +
|
||||
'follow the stack trace as described in https://react.dev/link/setstate-in-render\n' +
|
||||
' in App (at **)',
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not infinite loop if there's an async render phase update on another component", async () => {
|
||||
@@ -1827,18 +1843,17 @@ describe('ReactUpdates', () => {
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
|
||||
await expect(async () => {
|
||||
let error;
|
||||
try {
|
||||
await act(() => {
|
||||
React.startTransition(() => root.render(<App />));
|
||||
});
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
expect(error.message).toMatch('Maximum update depth exceeded');
|
||||
}).toErrorDev(
|
||||
'Cannot update a component (`App`) while rendering a different component (`Child`)',
|
||||
);
|
||||
await act(() => {
|
||||
React.startTransition(() => root.render(<App />));
|
||||
});
|
||||
}).rejects.toThrow('Maximum update depth exceeded');
|
||||
|
||||
assertConsoleErrorDev([
|
||||
'Cannot update a component (`App`) while rendering a different component (`Child`). ' +
|
||||
'To locate the bad setState() call inside `Child`, ' +
|
||||
'follow the stack trace as described in https://react.dev/link/setstate-in-render\n' +
|
||||
' in App (at **)',
|
||||
]);
|
||||
});
|
||||
|
||||
// TODO: Replace this branch with @gate pragmas
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
const React = require('react');
|
||||
const ReactDOM = require('react-dom');
|
||||
const ReactDOMClient = require('react-dom/client');
|
||||
const assertConsoleErrorDev =
|
||||
require('internal-test-utils').assertConsoleErrorDev;
|
||||
|
||||
function expectWarnings(tags, warnings = [], withoutStack = 0) {
|
||||
tags = [...tags];
|
||||
@@ -31,13 +33,13 @@ function expectWarnings(tags, warnings = [], withoutStack = 0) {
|
||||
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
if (warnings.length) {
|
||||
expect(() => {
|
||||
ReactDOM.flushSync(() => {
|
||||
root.render(element);
|
||||
});
|
||||
}).toErrorDev(warnings, {
|
||||
withoutStack,
|
||||
ReactDOM.flushSync(() => {
|
||||
root.render(element);
|
||||
});
|
||||
assertConsoleErrorDev(
|
||||
warnings,
|
||||
withoutStack > 0 ? {withoutStack} : undefined,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +166,8 @@ describe('validateDOMNesting', () => {
|
||||
' in body (at **)\n' +
|
||||
' in foreignObject (at **)',
|
||||
'You are mounting a new body component when a previous one has not first unmounted. It is an error to render more than one body component at a time and attributes and children of these components will likely fail in unpredictable ways. Please only render a single instance of <body> and if you need to mount a new one, ensure any previous ones have unmounted first.\n' +
|
||||
' in body (at **)',
|
||||
' in body (at **)\n' +
|
||||
' in foreignObject (at **)',
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user