diff --git a/packages/react-reconciler/src/__tests__/Activity-test.js b/packages/react-reconciler/src/__tests__/Activity-test.js
index cc5c4b921ce98..191e72e3cb14d 100644
--- a/packages/react-reconciler/src/__tests__/Activity-test.js
+++ b/packages/react-reconciler/src/__tests__/Activity-test.js
@@ -14,6 +14,7 @@ let startTransition;
let waitForPaint;
let waitFor;
let assertLog;
+let assertConsoleErrorDev;
describe('Activity', () => {
beforeEach(() => {
@@ -37,6 +38,7 @@ describe('Activity', () => {
waitForPaint = InternalTestUtils.waitForPaint;
waitFor = InternalTestUtils.waitFor;
assertLog = InternalTestUtils.assertLog;
+ assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
});
function Text(props) {
@@ -784,11 +786,14 @@ describe('Activity', () => {
// would be null because it was nulled out when it was deleted, but there
// was no null check before we accessed it. A weird edge case but we must
// account for it.
- expect(() => {
- setState('Updated');
- }).toErrorDev(
- "Can't perform a React state update on a component that hasn't mounted yet",
- );
+ setState('Updated');
+ 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' +
+ ' in Child (at **)',
+ ]);
});
expect(root).toMatchRenderedOutput(null);
});
diff --git a/packages/react-reconciler/src/__tests__/ReactActWarnings-test.js b/packages/react-reconciler/src/__tests__/ReactActWarnings-test.js
index 561741c108622..82827406557dc 100644
--- a/packages/react-reconciler/src/__tests__/ReactActWarnings-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactActWarnings-test.js
@@ -18,6 +18,7 @@ let Suspense;
let startTransition;
let getCacheForType;
let caches;
+let assertConsoleErrorDev;
// These tests are mostly concerned with concurrent roots. The legacy root
// behavior is covered by other older test suites and is unchanged from
@@ -38,6 +39,7 @@ describe('act warnings', () => {
const InternalTestUtils = require('internal-test-utils');
waitForAll = InternalTestUtils.waitForAll;
assertLog = InternalTestUtils.assertLog;
+ assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
});
function createTextCache() {
@@ -171,9 +173,21 @@ describe('act warnings', () => {
// Flag is true. Warn.
await withActEnvironment(true, async () => {
- expect(() => setState(2)).toErrorDev(
- 'An update to App inside a test was not wrapped in act',
- );
+ setState(2);
+ 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 **)',
+ ]);
await waitForAll([2]);
expect(root).toMatchRenderedOutput('2');
});
@@ -202,12 +216,11 @@ describe('act warnings', () => {
// Default behavior. Flag is undefined. Warn.
expect(global.IS_REACT_ACT_ENVIRONMENT).toBe(undefined);
- expect(() => {
- act(() => {
- setState(1);
- });
- }).toErrorDev(
- 'The current testing environment is not configured to support act(...)',
+ act(() => {
+ setState(1);
+ });
+ assertConsoleErrorDev(
+ ['The current testing environment is not configured to support act(...)'],
{withoutStack: true},
);
assertLog([1]);
@@ -224,12 +237,13 @@ describe('act warnings', () => {
// Flag is false. Warn.
await withActEnvironment(false, () => {
- expect(() => {
- act(() => {
- setState(1);
- });
- }).toErrorDev(
- 'The current testing environment is not configured to support act(...)',
+ act(() => {
+ setState(1);
+ });
+ assertConsoleErrorDev(
+ [
+ 'The current testing environment is not configured to support act(...)',
+ ],
{withoutStack: true},
);
assertLog([1]);
@@ -240,10 +254,23 @@ describe('act warnings', () => {
it('warns if root update is not wrapped', async () => {
await withActEnvironment(true, () => {
const root = ReactNoop.createRoot();
- expect(() => root.render('Hi')).toErrorDev(
- // TODO: Better error message that doesn't make it look like "Root" is
- // the name of a custom component
- 'An update to Root inside a test was not wrapped in act(...)',
+ root.render('Hi');
+ assertConsoleErrorDev(
+ [
+ // TODO: Better error message that doesn't make it look like "Root" is
+ // the name of a custom component
+ 'An update to Root 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',
+ ],
{withoutStack: true},
);
});
@@ -265,9 +292,21 @@ describe('act warnings', () => {
act(() => {
root.render();
});
- expect(() => app.setState({count: 1})).toErrorDev(
- 'An update to App inside a test was not wrapped in act(...)',
- );
+ app.setState({count: 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 **)',
+ ]);
});
});
@@ -288,9 +327,21 @@ describe('act warnings', () => {
// Even though this update is synchronous, we should still fire a warning,
// because it could have spawned additional asynchronous work
- expect(() => ReactNoop.flushSync(() => setState(1))).toErrorDev(
- 'An update to App inside a test was not wrapped in act(...)',
- );
+ ReactNoop.flushSync(() => 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 **)',
+ ]);
assertLog([1]);
expect(root).toMatchRenderedOutput('1');
@@ -322,12 +373,36 @@ describe('act warnings', () => {
expect(root).toMatchRenderedOutput('Loading...');
// This is a retry, not a ping, because we already showed a fallback.
- expect(() => resolveText('Async')).toErrorDev(
+ resolveText('Async');
+ assertConsoleErrorDev(
[
- 'A suspended resource finished loading inside a test, but the event ' +
- 'was not wrapped in act(...)',
-
- ...(gate('enableSiblingPrerendering') ? ['not wrapped in act'] : []),
+ 'A suspended resource finished loading inside a test, but the event was not wrapped in act(...).\n' +
+ '\n' +
+ 'When testing, code that resolves suspended data should be wrapped into act(...):\n' +
+ '\n' +
+ 'act(() => {\n' +
+ ' /* finish loading suspended data */\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',
+
+ ...(gate('enableSiblingPrerendering')
+ ? [
+ 'A suspended resource finished loading inside a test, but the event was not wrapped in act(...).\n' +
+ '\n' +
+ 'When testing, code that resolves suspended data should be wrapped into act(...):\n' +
+ '\n' +
+ 'act(() => {\n' +
+ ' /* finish loading suspended data */\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',
+ ]
+ : []),
],
{withoutStack: true},
@@ -363,9 +438,21 @@ describe('act warnings', () => {
expect(root).toMatchRenderedOutput('(empty)');
// This is a ping, not a retry, because no fallback is showing.
- expect(() => resolveText('Async')).toErrorDev(
- 'A suspended resource finished loading inside a test, but the event ' +
- 'was not wrapped in act(...)',
+ resolveText('Async');
+ assertConsoleErrorDev(
+ [
+ 'A suspended resource finished loading inside a test, but the event was not wrapped in act(...).\n' +
+ '\n' +
+ 'When testing, code that resolves suspended data should be wrapped into act(...):\n' +
+ '\n' +
+ 'act(() => {\n' +
+ ' /* finish loading suspended data */\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',
+ ],
{withoutStack: true},
);
});
diff --git a/packages/react-reconciler/src/__tests__/ReactAsyncActions-test.js b/packages/react-reconciler/src/__tests__/ReactAsyncActions-test.js
index 843d5b2dd3fe6..43e071d4141cf 100644
--- a/packages/react-reconciler/src/__tests__/ReactAsyncActions-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactAsyncActions-test.js
@@ -7,6 +7,7 @@ let useTransition;
let useState;
let useOptimistic;
let textCache;
+let assertConsoleErrorDev;
describe('ReactAsyncActions', () => {
beforeEach(() => {
@@ -21,6 +22,8 @@ describe('ReactAsyncActions', () => {
Scheduler = require('scheduler');
act = require('internal-test-utils').act;
assertLog = require('internal-test-utils').assertLog;
+ assertConsoleErrorDev =
+ require('internal-test-utils').assertConsoleErrorDev;
useTransition = React.useTransition;
useState = React.useState;
useOptimistic = React.useOptimistic;
@@ -1231,15 +1234,16 @@ describe('ReactAsyncActions', () => {
assertLog(['A']);
expect(root).toMatchRenderedOutput(
A
);
- await expect(async () => {
- await act(() => {
- setLoadingProgress('25%');
- startTransition(() => setText('B'));
- });
- }).toErrorDev(
- 'An optimistic state update occurred outside a transition or ' +
- 'action. To fix, move the update to an action, or wrap ' +
- 'with startTransition.',
+ await act(() => {
+ setLoadingProgress('25%');
+ startTransition(() => setText('B'));
+ });
+ assertConsoleErrorDev(
+ [
+ 'An optimistic state update occurred outside a transition or ' +
+ 'action. To fix, move the update to an action, or wrap ' +
+ 'with startTransition.',
+ ],
{withoutStack: true},
);
assertLog(['Loading... (25%)', 'A', 'B']);
diff --git a/packages/react-reconciler/src/__tests__/ReactFragment-test.js b/packages/react-reconciler/src/__tests__/ReactFragment-test.js
index fe1d1f4f953db..559ede0dd9103 100644
--- a/packages/react-reconciler/src/__tests__/ReactFragment-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactFragment-test.js
@@ -12,6 +12,7 @@
let React;
let ReactNoop;
let waitForAll;
+let assertConsoleErrorDev;
describe('ReactFragment', () => {
beforeEach(function () {
@@ -22,6 +23,7 @@ describe('ReactFragment', () => {
const InternalTestUtils = require('internal-test-utils');
waitForAll = InternalTestUtils.waitForAll;
+ assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
});
it('should render a single child via noop renderer', async () => {
@@ -740,9 +742,22 @@ describe('ReactFragment', () => {
await waitForAll([]);
ReactNoop.render();
- await expect(async () => await waitForAll([])).toErrorDev(
- 'Each child in a list should have a unique "key" prop.',
- );
+ await waitForAll([]);
+ assertConsoleErrorDev([
+ gate('enableOwnerStacks')
+ ? 'Each child in a list should have a unique "key" prop.\n' +
+ '\n' +
+ 'Check the render method of `div`. ' +
+ 'It was passed a child from Foo. ' +
+ 'See https://react.dev/link/warning-keys for more information.\n' +
+ ' in Foo (at **)'
+ : 'Each child in a list should have a unique "key" prop.\n' +
+ '\n' +
+ 'Check the render method of `Foo`. ' +
+ 'See https://react.dev/link/warning-keys for more information.\n' +
+ ' in Stateful (at **)\n' +
+ ' in Foo (at **)',
+ ]);
expect(ops).toEqual([]);
expect(ReactNoop).toMatchRenderedOutput(
@@ -937,9 +952,16 @@ describe('ReactFragment', () => {
}
ReactNoop.render();
- await expect(async () => await waitForAll([])).toErrorDev(
- 'Each child in a list should have a unique "key" prop.',
- );
+ await waitForAll([]);
+ assertConsoleErrorDev([
+ 'Each child in a list should have a unique "key" prop.\n' +
+ '\n' +
+ 'Check the top-level render call using . ' +
+ 'It was passed a child from Foo. ' +
+ 'See https://react.dev/link/warning-keys for more information.\n' +
+ ' in span (at **)\n' +
+ ' in Foo (at **)',
+ ]);
ReactNoop.render();
// The key warning gets deduped because it's in the same component.
diff --git a/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js b/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js
index 8b0e903a839e9..367d83f38a57c 100644
--- a/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js
@@ -239,17 +239,18 @@ describe('ReactHooks', () => {
await waitForAll(['Count: 0']);
expect(root).toMatchRenderedOutput('0');
- await expect(async () => {
- await act(() =>
- setCounter(1, () => {
- throw new Error('Expected to ignore the callback.');
- }),
- );
- }).toErrorDev(
- 'State updates from the useState() and useReducer() Hooks ' +
- "don't support the second callback argument. " +
- 'To execute a side effect after rendering, ' +
- 'declare it in the component body with useEffect().',
+ await act(() =>
+ setCounter(1, () => {
+ throw new Error('Expected to ignore the callback.');
+ }),
+ );
+ assertConsoleErrorDev(
+ [
+ 'State updates from the useState() and useReducer() Hooks ' +
+ "don't support the second callback argument. " +
+ 'To execute a side effect after rendering, ' +
+ 'declare it in the component body with useEffect().',
+ ],
{withoutStack: true},
);
assertLog(['Count: 1']);
@@ -273,17 +274,18 @@ describe('ReactHooks', () => {
await waitForAll(['Count: 0']);
expect(root).toMatchRenderedOutput('0');
- await expect(async () => {
- await act(() =>
- dispatch(1, () => {
- throw new Error('Expected to ignore the callback.');
- }),
- );
- }).toErrorDev(
- 'State updates from the useState() and useReducer() Hooks ' +
- "don't support the second callback argument. " +
- 'To execute a side effect after rendering, ' +
- 'declare it in the component body with useEffect().',
+ await act(() =>
+ dispatch(1, () => {
+ throw new Error('Expected to ignore the callback.');
+ }),
+ );
+ assertConsoleErrorDev(
+ [
+ 'State updates from the useState() and useReducer() Hooks ' +
+ "don't support the second callback argument. " +
+ 'To execute a side effect after rendering, ' +
+ 'declare it in the component body with useEffect().',
+ ],
{withoutStack: true},
);
assertLog(['Count: 1']);
@@ -581,16 +583,17 @@ describe('ReactHooks', () => {
});
});
assertLog(['Did commit: A']);
- await expect(async () => {
- await act(() => {
- root.update();
- });
- }).toErrorDev([
+ await act(() => {
+ root.update();
+ });
+ assertConsoleErrorDev([
'The final argument passed to useLayoutEffect changed size ' +
'between renders. The order and size of this array must remain ' +
- 'constant.\n\n' +
+ 'constant.\n' +
+ '\n' +
'Previous: [A]\n' +
- 'Incoming: [A, B]\n',
+ 'Incoming: [A, B]\n' +
+ ' in App (at **)',
]);
});
@@ -617,14 +620,14 @@ describe('ReactHooks', () => {
assertLog(['Compute']);
expect(root).toMatchRenderedOutput('HELLO');
- await expect(async () => {
- await act(() => {
- root.update();
- });
- }).toErrorDev([
+ await act(() => {
+ root.update();
+ });
+ assertConsoleErrorDev([
'useMemo received a final argument during this render, but ' +
'not during the previous render. Even though the final argument is ' +
- 'optional, its type cannot change between renders.',
+ 'optional, its type cannot change between renders.\n' +
+ ' in App (at **)',
]);
});
@@ -639,53 +642,62 @@ describe('ReactHooks', () => {
return null;
}
- await expect(async () => {
- await act(() => {
- ReactTestRenderer.create(, {
- unstable_isConcurrent: true,
- });
+ await act(() => {
+ ReactTestRenderer.create(, {
+ unstable_isConcurrent: true,
});
- }).toErrorDev([
+ });
+ assertConsoleErrorDev([
'useEffect received a final argument that is not an array (instead, received `string`). ' +
- 'When specified, the final argument must be an array.',
+ 'When specified, the final argument must be an array.\n' +
+ ' in App (at **)',
'useLayoutEffect received a final argument that is not an array (instead, received `string`). ' +
- 'When specified, the final argument must be an array.',
+ 'When specified, the final argument must be an array.\n' +
+ ' in App (at **)',
'useMemo received a final argument that is not an array (instead, received `string`). ' +
- 'When specified, the final argument must be an array.',
+ 'When specified, the final argument must be an array.\n' +
+ ' in App (at **)',
'useCallback received a final argument that is not an array (instead, received `string`). ' +
- 'When specified, the final argument must be an array.',
+ 'When specified, the final argument must be an array.\n' +
+ ' in App (at **)',
]);
- await expect(async () => {
- await act(() => {
- ReactTestRenderer.create(, {
- unstable_isConcurrent: true,
- });
+ await act(() => {
+ ReactTestRenderer.create(, {
+ unstable_isConcurrent: true,
});
- }).toErrorDev([
+ });
+ assertConsoleErrorDev([
'useEffect received a final argument that is not an array (instead, received `number`). ' +
- 'When specified, the final argument must be an array.',
+ 'When specified, the final argument must be an array.\n' +
+ ' in App (at **)',
'useLayoutEffect received a final argument that is not an array (instead, received `number`). ' +
- 'When specified, the final argument must be an array.',
+ 'When specified, the final argument must be an array.\n' +
+ ' in App (at **)',
'useMemo received a final argument that is not an array (instead, received `number`). ' +
- 'When specified, the final argument must be an array.',
+ 'When specified, the final argument must be an array.\n' +
+ ' in App (at **)',
'useCallback received a final argument that is not an array (instead, received `number`). ' +
- 'When specified, the final argument must be an array.',
+ 'When specified, the final argument must be an array.\n' +
+ ' in App (at **)',
]);
- await expect(async () => {
- await act(() => {
- ReactTestRenderer.create(, {
- unstable_isConcurrent: true,
- });
+ await act(() => {
+ ReactTestRenderer.create(, {
+ unstable_isConcurrent: true,
});
- }).toErrorDev([
+ });
+ assertConsoleErrorDev([
'useEffect received a final argument that is not an array (instead, received `object`). ' +
- 'When specified, the final argument must be an array.',
+ 'When specified, the final argument must be an array.\n' +
+ ' in App (at **)',
'useLayoutEffect received a final argument that is not an array (instead, received `object`). ' +
- 'When specified, the final argument must be an array.',
+ 'When specified, the final argument must be an array.\n' +
+ ' in App (at **)',
'useMemo received a final argument that is not an array (instead, received `object`). ' +
- 'When specified, the final argument must be an array.',
+ 'When specified, the final argument must be an array.\n' +
+ ' in App (at **)',
'useCallback received a final argument that is not an array (instead, received `object`). ' +
- 'When specified, the final argument must be an array.',
+ 'When specified, the final argument must be an array.\n' +
+ ' in App (at **)',
]);
await act(() => {
@@ -710,15 +722,15 @@ describe('ReactHooks', () => {
});
App.displayName = 'App';
- await expect(async () => {
- await act(() => {
- ReactTestRenderer.create(, {
- unstable_isConcurrent: true,
- });
+ await act(() => {
+ ReactTestRenderer.create(, {
+ unstable_isConcurrent: true,
});
- }).toErrorDev([
+ });
+ assertConsoleErrorDev([
'useImperativeHandle received a final argument that is not an array (instead, received `string`). ' +
- 'When specified, the final argument must be an array.',
+ 'When specified, the final argument must be an array.\n' +
+ ' in App (at **)',
]);
await act(() => {
ReactTestRenderer.create(, {
@@ -817,17 +829,18 @@ describe('ReactHooks', () => {
}
await expect(async () => {
- await expect(async () => {
- await act(() => {
- ReactTestRenderer.create(, {unstable_isConcurrent: true});
- });
- }).rejects.toThrow('create is not a function');
- }).toErrorDev([
+ await act(() => {
+ ReactTestRenderer.create(, {unstable_isConcurrent: true});
+ });
+ }).rejects.toThrow('create is not a function');
+ assertConsoleErrorDev([
+ 'Expected useImperativeHandle() second argument to be a function ' +
+ 'that creates a handle. Instead received: undefined.\n' +
+ ' in App (at **)',
'Expected useImperativeHandle() first argument to either be a ' +
'ref callback or React.createRef() object. ' +
- 'Instead received: an object with keys {focus}.',
- 'Expected useImperativeHandle() second argument to be a function ' +
- 'that creates a handle. Instead received: undefined.',
+ 'Instead received: an object with keys {focus}.\n' +
+ ' in App (at **)',
]);
});
@@ -841,13 +854,13 @@ describe('ReactHooks', () => {
});
App.displayName = 'App';
- await expect(async () => {
- await act(() => {
- ReactTestRenderer.create(, {unstable_isConcurrent: true});
- });
- }).toErrorDev([
+ await act(() => {
+ ReactTestRenderer.create(, {unstable_isConcurrent: true});
+ });
+ assertConsoleErrorDev([
'Expected useImperativeHandle() second argument to be a function ' +
- 'that creates a handle. Instead received: object.',
+ 'that creates a handle. Instead received: object.\n' +
+ ' in App (at **)',
]);
});
@@ -933,13 +946,15 @@ describe('ReactHooks', () => {
});
return null;
}
- await expect(async () => {
- await act(() => {
- ReactTestRenderer.create(, {unstable_isConcurrent: true});
- });
- }).toErrorDev(
- 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks.',
- );
+ await act(() => {
+ ReactTestRenderer.create(, {unstable_isConcurrent: true});
+ });
+ assertConsoleErrorDev([
+ 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks. ' +
+ 'You can only call Hooks at the top level of your React function. ' +
+ 'For more information, see https://react.dev/link/rules-of-hooks\n' +
+ ' in App (at **)',
+ ]);
});
it('warns when reading context inside useMemo', async () => {
@@ -954,11 +969,16 @@ describe('ReactHooks', () => {
}, []);
}
- await expect(async () => {
- await act(() => {
- ReactTestRenderer.create(, {unstable_isConcurrent: true});
- });
- }).toErrorDev('Context can only be read while React is rendering');
+ await act(() => {
+ ReactTestRenderer.create(, {unstable_isConcurrent: true});
+ });
+ assertConsoleErrorDev([
+ 'Context can only be read while React is rendering. ' +
+ 'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
+ 'In function components, you can read it directly in the function body, ' +
+ 'but not inside Hooks like useReducer() or useMemo().\n' +
+ ' in App (at **)',
+ ]);
});
it('warns when reading context inside useMemo after reading outside it', async () => {
@@ -977,11 +997,16 @@ describe('ReactHooks', () => {
}, []);
}
- await expect(async () => {
- await act(() => {
- ReactTestRenderer.create(, {unstable_isConcurrent: true});
- });
- }).toErrorDev('Context can only be read while React is rendering');
+ await act(() => {
+ ReactTestRenderer.create(, {unstable_isConcurrent: true});
+ });
+ assertConsoleErrorDev([
+ 'Context can only be read while React is rendering. ' +
+ 'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
+ 'In function components, you can read it directly in the function body, ' +
+ 'but not inside Hooks like useReducer() or useMemo().\n' +
+ ' in App (at **)',
+ ]);
expect(firstRead).toBe('light');
expect(secondRead).toBe('light');
});
@@ -1048,11 +1073,16 @@ describe('ReactHooks', () => {
return null;
}
- await expect(async () => {
- await act(() => {
- ReactTestRenderer.create(, {unstable_isConcurrent: true});
- });
- }).toErrorDev(['Context can only be read while React is rendering']);
+ await act(() => {
+ ReactTestRenderer.create(, {unstable_isConcurrent: true});
+ });
+ assertConsoleErrorDev([
+ 'Context can only be read while React is rendering. ' +
+ 'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
+ 'In function components, you can read it directly in the function body, ' +
+ 'but not inside Hooks like useReducer() or useMemo().\n' +
+ ' in App (at **)',
+ ]);
});
// Edge case.
@@ -1078,19 +1108,25 @@ describe('ReactHooks', () => {
}
}
- await expect(async () => {
- await act(() => {
- ReactTestRenderer.create(
- <>
-
-
- >,
- {unstable_isConcurrent: true},
- );
- });
- }).toErrorDev([
- 'Context can only be read while React is rendering',
- 'Cannot update a component (`Fn`) while rendering a different component (`Cls`).',
+ await act(() => {
+ ReactTestRenderer.create(
+ <>
+
+
+ >,
+ {unstable_isConcurrent: true},
+ );
+ });
+ assertConsoleErrorDev([
+ 'Context can only be read while React is rendering. ' +
+ 'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
+ 'In function components, you can read it directly in the function body, ' +
+ 'but not inside Hooks like useReducer() or useMemo().\n' +
+ ' in Cls (at **)',
+ 'Cannot update a component (`Fn`) while rendering a different component (`Cls`). ' +
+ 'To locate the bad setState() call inside `Cls`, ' +
+ 'follow the stack trace as described in https://react.dev/link/setstate-in-render\n' +
+ ' in Cls (at **)',
]);
});
@@ -1110,24 +1146,32 @@ describe('ReactHooks', () => {
}
await expect(async () => {
- await expect(async () => {
- await act(() => {
- ReactTestRenderer.create(, {unstable_isConcurrent: true});
- });
- }).rejects.toThrow(
- 'Update hook called on initial render. This is likely a bug in React. Please file an issue.',
- );
- }).toErrorDev([
- 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks',
+ await act(() => {
+ ReactTestRenderer.create(, {unstable_isConcurrent: true});
+ });
+ }).rejects.toThrow(
+ 'Update hook called on initial render. This is likely a bug in React. Please file an issue.',
+ );
+ assertConsoleErrorDev([
+ 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks. ' +
+ 'You can only call Hooks at the top level of your React function. ' +
+ 'For more information, see https://react.dev/link/rules-of-hooks\n' +
+ ' in App (at **)',
'React has detected a change in the order of Hooks called by App. ' +
'This will lead to bugs and errors if not fixed. For more information, ' +
- 'read the Rules of Hooks: https://react.dev/link/rules-of-hooks\n\n' +
+ 'read the Rules of Hooks: https://react.dev/link/rules-of-hooks\n' +
+ '\n' +
' Previous render Next render\n' +
' ------------------------------------------------------\n' +
'1. useReducer useReducer\n' +
'2. useState useRef\n' +
- ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n',
- 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks',
+ ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' +
+ '\n' +
+ ' in App (at **)',
+ 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks. ' +
+ 'You can only call Hooks at the top level of your React function. ' +
+ 'For more information, see https://react.dev/link/rules-of-hooks\n' +
+ ' in App (at **)',
]);
});
@@ -1140,13 +1184,15 @@ describe('ReactHooks', () => {
});
return null;
}
- await expect(async () => {
- await act(() => {
- ReactTestRenderer.create(, {unstable_isConcurrent: true});
- });
- }).toErrorDev(
- 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks.',
- );
+ await act(() => {
+ ReactTestRenderer.create(, {unstable_isConcurrent: true});
+ });
+ assertConsoleErrorDev([
+ 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks. ' +
+ 'You can only call Hooks at the top level of your React function. ' +
+ 'For more information, see https://react.dev/link/rules-of-hooks\n' +
+ ' in App (at **)',
+ ]);
});
it('resets warning internal state when interrupted by an error', async () => {
@@ -1177,21 +1223,37 @@ describe('ReactHooks', () => {
}
}
- await expect(async () => {
- await act(() => {
- ReactTestRenderer.create(
-
-
- ,
- {unstable_isConcurrent: true},
- );
- });
- }).toErrorDev([
- 'Context can only be read while React is rendering',
- 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks',
-
- 'Context can only be read while React is rendering',
- 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks',
+ await act(() => {
+ ReactTestRenderer.create(
+
+
+ ,
+ {unstable_isConcurrent: true},
+ );
+ });
+ assertConsoleErrorDev([
+ 'Context can only be read while React is rendering. ' +
+ 'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
+ 'In function components, you can read it directly in the function body, ' +
+ 'but not inside Hooks like useReducer() or useMemo().\n' +
+ ' in App (at **)' +
+ (gate('enableOwnerStacks') ? '' : '\n in Boundary (at **)'),
+ 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks. ' +
+ 'You can only call Hooks at the top level of your React function. ' +
+ 'For more information, see https://react.dev/link/rules-of-hooks\n' +
+ ' in App (at **)' +
+ (gate('enableOwnerStacks') ? '' : '\n in Boundary (at **)'),
+ 'Context can only be read while React is rendering. ' +
+ 'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
+ 'In function components, you can read it directly in the function body, ' +
+ 'but not inside Hooks like useReducer() or useMemo().\n' +
+ ' in App (at **)' +
+ (gate('enableOwnerStacks') ? '' : '\n in Boundary (at **)'),
+ 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks. ' +
+ 'You can only call Hooks at the top level of your React function. ' +
+ 'For more information, see https://react.dev/link/rules-of-hooks\n' +
+ ' in App (at **)' +
+ (gate('enableOwnerStacks') ? '' : '\n in Boundary (at **)'),
]);
function Valid() {
@@ -1218,21 +1280,37 @@ describe('ReactHooks', () => {
});
// Verify warnings don't get permanently disabled.
- await expect(async () => {
- await act(() => {
- ReactTestRenderer.create(
-
-
- ,
- {unstable_isConcurrent: true},
- );
- });
- }).toErrorDev([
- 'Context can only be read while React is rendering',
- 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks',
-
- 'Context can only be read while React is rendering',
- 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks',
+ await act(() => {
+ ReactTestRenderer.create(
+
+
+ ,
+ {unstable_isConcurrent: true},
+ );
+ });
+ assertConsoleErrorDev([
+ 'Context can only be read while React is rendering. ' +
+ 'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
+ 'In function components, you can read it directly in the function body, ' +
+ 'but not inside Hooks like useReducer() or useMemo().\n' +
+ ' in App (at **)' +
+ (gate('enableOwnerStacks') ? '' : '\n in Boundary (at **)'),
+ 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks. ' +
+ 'You can only call Hooks at the top level of your React function. ' +
+ 'For more information, see https://react.dev/link/rules-of-hooks\n' +
+ ' in App (at **)' +
+ (gate('enableOwnerStacks') ? '' : '\n in Boundary (at **)'),
+ 'Context can only be read while React is rendering. ' +
+ 'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
+ 'In function components, you can read it directly in the function body, ' +
+ 'but not inside Hooks like useReducer() or useMemo().\n' +
+ ' in App (at **)' +
+ (gate('enableOwnerStacks') ? '' : '\n in Boundary (at **)'),
+ 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks. ' +
+ 'You can only call Hooks at the top level of your React function. ' +
+ 'For more information, see https://react.dev/link/rules-of-hooks\n' +
+ ' in App (at **)' +
+ (gate('enableOwnerStacks') ? '' : '\n in Boundary (at **)'),
]);
});
@@ -1569,24 +1647,26 @@ describe('ReactHooks', () => {
unstable_isConcurrent: true,
});
});
- await expect(async () => {
- try {
- await act(() => {
- root.update();
- });
- } catch (error) {
- // Swapping certain types of hooks will cause runtime errors.
- // This is okay as far as this test is concerned.
- // We just want to verify that warnings are always logged.
- }
- }).toErrorDev([
+ try {
+ await act(() => {
+ root.update();
+ });
+ } catch (error) {
+ // Swapping certain types of hooks will cause runtime errors.
+ // This is okay as far as this test is concerned.
+ // We just want to verify that warnings are always logged.
+ }
+ assertConsoleErrorDev([
'React has detected a change in the order of Hooks called by App. ' +
'This will lead to bugs and errors if not fixed. For more information, ' +
- 'read the Rules of Hooks: https://react.dev/link/rules-of-hooks\n\n' +
+ 'read the Rules of Hooks: https://react.dev/link/rules-of-hooks\n' +
+ '\n' +
' Previous render Next render\n' +
' ------------------------------------------------------\n' +
`1. ${formatHookNamesToMatchErrorMessage(hookNameA, hookNameB)}\n` +
- ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n',
+ ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' +
+ '\n' +
+ ' in App (at **)',
]);
// further warnings for this component are silenced
@@ -1618,25 +1698,27 @@ describe('ReactHooks', () => {
});
});
- await expect(async () => {
- try {
- await act(() => {
- root.update();
- });
- } catch (error) {
- // Swapping certain types of hooks will cause runtime errors.
- // This is okay as far as this test is concerned.
- // We just want to verify that warnings are always logged.
- }
- }).toErrorDev([
+ try {
+ await act(() => {
+ root.update();
+ });
+ } catch (error) {
+ // Swapping certain types of hooks will cause runtime errors.
+ // This is okay as far as this test is concerned.
+ // We just want to verify that warnings are always logged.
+ }
+ assertConsoleErrorDev([
'React has detected a change in the order of Hooks called by App. ' +
'This will lead to bugs and errors if not fixed. For more information, ' +
- 'read the Rules of Hooks: https://react.dev/link/rules-of-hooks\n\n' +
+ 'read the Rules of Hooks: https://react.dev/link/rules-of-hooks\n' +
+ '\n' +
' Previous render Next render\n' +
' ------------------------------------------------------\n' +
`1. ${formatHookNamesToMatchErrorMessage(hookNameA, hookNameA)}\n` +
`2. undefined use${hookNameB}\n` +
- ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n',
+ ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' +
+ '\n' +
+ ' in App (at **)',
]);
});
});
@@ -1701,24 +1783,26 @@ describe('ReactHooks', () => {
unstable_isConcurrent: true,
});
});
- await expect(async () => {
- await act(() => {
- root.update();
- }).catch(e => {});
- // Swapping certain types of hooks will cause runtime errors.
- // This is okay as far as this test is concerned.
- // We just want to verify that warnings are always logged.
- }).toErrorDev([
+ await act(() => {
+ root.update();
+ }).catch(e => {});
+ // Swapping certain types of hooks will cause runtime errors.
+ // This is okay as far as this test is concerned.
+ // We just want to verify that warnings are always logged.
+ assertConsoleErrorDev([
'React has detected a change in the order of Hooks called by App. ' +
'This will lead to bugs and errors if not fixed. For more information, ' +
- 'read the Rules of Hooks: https://react.dev/link/rules-of-hooks\n\n' +
+ 'read the Rules of Hooks: https://react.dev/link/rules-of-hooks\n' +
+ '\n' +
' Previous render Next render\n' +
' ------------------------------------------------------\n' +
`1. ${formatHookNamesToMatchErrorMessage(
'ImperativeHandle',
'Memo',
)}\n` +
- ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n',
+ ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' +
+ '\n' +
+ ' in App (at **)',
]);
// further warnings for this component are silenced
@@ -1751,19 +1835,21 @@ describe('ReactHooks', () => {
});
});
await expect(async () => {
- await expect(async () => {
- await act(() => {
- root.update();
- });
- }).rejects.toThrow('custom error');
- }).toErrorDev([
+ await act(() => {
+ root.update();
+ });
+ }).rejects.toThrow('custom error');
+ assertConsoleErrorDev([
'React has detected a change in the order of Hooks called by App. ' +
'This will lead to bugs and errors if not fixed. For more information, ' +
- 'read the Rules of Hooks: https://react.dev/link/rules-of-hooks\n\n' +
+ 'read the Rules of Hooks: https://react.dev/link/rules-of-hooks\n' +
+ '\n' +
' Previous render Next render\n' +
' ------------------------------------------------------\n' +
'1. useReducer useState\n' +
- ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n',
+ ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' +
+ '\n' +
+ ' in App (at **)',
]);
});
});
@@ -1798,8 +1884,10 @@ describe('ReactHooks', () => {
});
}).rejects.toThrow('Hello');
assertConsoleErrorDev([
- 'Cannot update a component (`A`) while rendering ' +
- 'a different component (`B`).',
+ 'Cannot update a component (`A`) while rendering a different component (`B`). ' +
+ 'To locate the bad setState() call inside `B`, ' +
+ 'follow the stack trace as described in https://react.dev/link/setstate-in-render\n' +
+ ' in B (at **)',
]);
});
diff --git a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
index dcb362669928e..e4febdb3e28f1 100644
--- a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
@@ -42,6 +42,7 @@ let waitForThrow;
let waitForPaint;
let assertLog;
let useResourceEffect;
+let assertConsoleErrorDev;
describe('ReactHooksWithNoopRenderer', () => {
beforeEach(() => {
@@ -52,6 +53,8 @@ describe('ReactHooksWithNoopRenderer', () => {
ReactNoop = require('react-noop-renderer');
Scheduler = require('scheduler');
act = require('internal-test-utils').act;
+ assertConsoleErrorDev =
+ require('internal-test-utils').assertConsoleErrorDev;
useState = React.useState;
useReducer = React.useReducer;
useEffect = React.useEffect;
@@ -232,17 +235,18 @@ describe('ReactHooksWithNoopRenderer', () => {
});
it('throws when called outside the render phase', async () => {
- expect(() => {
- expect(() => useState(0)).toThrow(
- "Cannot read property 'useState' of null",
- );
- }).toErrorDev(
- 'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
- ' one of the following reasons:\n' +
- '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
- '2. You might be breaking the Rules of Hooks\n' +
- '3. You might have more than one copy of React in the same app\n' +
- 'See https://react.dev/link/invalid-hook-call for tips about how to debug and fix this problem.',
+ expect(() => useState(0)).toThrow(
+ "Cannot read property 'useState' of null",
+ );
+ assertConsoleErrorDev(
+ [
+ 'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
+ ' one of the following reasons:\n' +
+ '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
+ '2. You might be breaking the Rules of Hooks\n' +
+ '3. You might have more than one copy of React in the same app\n' +
+ 'See https://react.dev/link/invalid-hook-call for tips about how to debug and fix this problem.',
+ ],
{withoutStack: true},
);
});
@@ -459,11 +463,12 @@ describe('ReactHooksWithNoopRenderer', () => {
>,
);
- await expect(
- async () => await waitForAll(['Foo [0]', 'Bar', 'Foo [1]']),
- ).toErrorDev([
- 'Cannot update a component (`Foo`) while rendering a ' +
- 'different component (`Bar`). To locate the bad setState() call inside `Bar`',
+ await waitForAll(['Foo [0]', 'Bar', 'Foo [1]']);
+ assertConsoleErrorDev([
+ 'Cannot update a component (`Foo`) while rendering a different component (`Bar`). ' +
+ 'To locate the bad setState() call inside `Bar`, ' +
+ 'follow the stack trace as described in https://react.dev/link/setstate-in-render\n' +
+ ' in Bar (at **)',
]);
// It should not warn again (deduplication).
@@ -1645,6 +1650,12 @@ describe('ReactHooksWithNoopRenderer', () => {
updateCount(props.count);
});
assertLog([`Schedule update [${props.count}]`]);
+ assertConsoleErrorDev([
+ 'flushSync was called from inside a lifecycle method. ' +
+ 'React cannot flush when React is already rendering. ' +
+ 'Consider moving this call to a scheduler task or micro task.\n' +
+ ' in Counter (at **)',
+ ]);
// This shouldn't flush synchronously.
expect(ReactNoop).not.toMatchRenderedOutput(
,
@@ -1652,17 +1663,14 @@ describe('ReactHooksWithNoopRenderer', () => {
}, [props.count]);
return ;
}
- await expect(async () => {
- await act(async () => {
- ReactNoop.render(, () =>
- Scheduler.log('Sync effect'),
- );
- await waitFor(['Count: (empty)', 'Sync effect']);
- expect(ReactNoop).toMatchRenderedOutput(
- ,
- );
- });
- }).toErrorDev('flushSync was called from inside a lifecycle method');
+ await act(async () => {
+ ReactNoop.render(, () =>
+ Scheduler.log('Sync effect'),
+ );
+ await waitFor(['Count: (empty)', 'Sync effect']);
+ expect(ReactNoop).toMatchRenderedOutput();
+ });
+
assertLog([`Count: 0`]);
expect(ReactNoop).toMatchRenderedOutput();
});
@@ -2506,35 +2514,47 @@ describe('ReactHooksWithNoopRenderer', () => {
}
const root1 = ReactNoop.createRoot();
- await expect(async () => {
- await act(() => {
- root1.render();
- });
- }).toErrorDev([
- 'useEffect must not return anything besides a ' +
- 'function, which is used for clean-up. You returned: 17',
+ await act(() => {
+ root1.render();
+ });
+ assertConsoleErrorDev([
+ 'useEffect must not return anything besides a function, ' +
+ 'which is used for clean-up. You returned: 17\n' +
+ ' in App (at **)',
]);
const root2 = ReactNoop.createRoot();
- await expect(async () => {
- await act(() => {
- root2.render();
- });
- }).toErrorDev([
- 'useEffect must not return anything besides a ' +
- 'function, which is used for clean-up. You returned null. If your ' +
- 'effect does not require clean up, return undefined (or nothing).',
+ await act(() => {
+ root2.render();
+ });
+ assertConsoleErrorDev([
+ 'useEffect must not return anything besides a function, ' +
+ 'which is used for clean-up. You returned null. ' +
+ 'If your effect does not require clean up, return undefined (or nothing).\n' +
+ ' in App (at **)',
]);
const root3 = ReactNoop.createRoot();
- await expect(async () => {
- await act(() => {
- root3.render();
- });
- }).toErrorDev([
- 'useEffect must not return anything besides a ' +
- 'function, which is used for clean-up.\n\n' +
- 'It looks like you wrote useEffect(async () => ...) or returned a Promise.',
+ await act(() => {
+ root3.render();
+ });
+ assertConsoleErrorDev([
+ 'useEffect must not return anything besides a function, which is used for clean-up.\n' +
+ '\n' +
+ 'It looks like you wrote useEffect(async () => ...) or returned a Promise. ' +
+ 'Instead, write the async function inside your effect and call it immediately:\n' +
+ '\n' +
+ 'useEffect(() => {\n' +
+ ' async function fetchData() {\n' +
+ ' // You can await here\n' +
+ ' const response = await MyAPI.getData(someId);\n' +
+ ' // ...\n' +
+ ' }\n' +
+ ' fetchData();\n' +
+ "}, [someId]); // Or [] if effect doesn't need props or state\n" +
+ '\n' +
+ 'Learn more about data fetching with Hooks: https://react.dev/link/hooks-data-fetching\n' +
+ ' in App (at **)',
]);
// Error on unmount because React assumes the value is a function
@@ -2895,35 +2915,48 @@ describe('ReactHooksWithNoopRenderer', () => {
}
const root1 = ReactNoop.createRoot();
- await expect(async () => {
- await act(() => {
- root1.render();
- });
- }).toErrorDev([
- 'useInsertionEffect must not return anything besides a ' +
- 'function, which is used for clean-up. You returned: 17',
+ await act(() => {
+ root1.render();
+ });
+ assertConsoleErrorDev([
+ 'useInsertionEffect must not return anything besides a function, ' +
+ 'which is used for clean-up. You returned: 17\n' +
+ ' in App (at **)',
]);
const root2 = ReactNoop.createRoot();
- await expect(async () => {
- await act(() => {
- root2.render();
- });
- }).toErrorDev([
- 'useInsertionEffect must not return anything besides a ' +
- 'function, which is used for clean-up. You returned null. If your ' +
- 'effect does not require clean up, return undefined (or nothing).',
+ await act(() => {
+ root2.render();
+ });
+ assertConsoleErrorDev([
+ 'useInsertionEffect must not return anything besides a function, ' +
+ 'which is used for clean-up. You returned null. ' +
+ 'If your effect does not require clean up, return undefined (or nothing).\n' +
+ ' in App (at **)',
]);
const root3 = ReactNoop.createRoot();
- await expect(async () => {
- await act(() => {
- root3.render();
- });
- }).toErrorDev([
+ await act(() => {
+ root3.render();
+ });
+ assertConsoleErrorDev([
'useInsertionEffect must not return anything besides a ' +
- 'function, which is used for clean-up.\n\n' +
- 'It looks like you wrote useInsertionEffect(async () => ...) or returned a Promise.',
+ 'function, which is used for clean-up.\n' +
+ '\n' +
+ 'It looks like you wrote useInsertionEffect(async () => ...) or returned a Promise. ' +
+ 'Instead, write the async function inside your effect and call it immediately:\n' +
+ '\n' +
+ 'useInsertionEffect(() => {\n' +
+ ' async function fetchData() {\n' +
+ ' // You can await here\n' +
+ ' const response = await MyAPI.getData(someId);\n' +
+ ' // ...\n' +
+ ' }\n' +
+ ' fetchData();\n' +
+ "}, [someId]); // Or [] if effect doesn't need props or state\n" +
+ '\n' +
+ 'Learn more about data fetching with Hooks: https://react.dev/link/hooks-data-fetching\n' +
+ ' in App (at **)',
]);
// Error on unmount because React assumes the value is a function
@@ -2946,11 +2979,13 @@ describe('ReactHooksWithNoopRenderer', () => {
}
const root = ReactNoop.createRoot();
- await expect(async () => {
- await act(() => {
- root.render();
- });
- }).toErrorDev(['useInsertionEffect must not schedule updates.']);
+ await act(() => {
+ root.render();
+ });
+ assertConsoleErrorDev([
+ 'useInsertionEffect must not schedule updates.\n' +
+ ' in App (at **)',
+ ]);
await act(async () => {
root.render();
@@ -2988,11 +3023,13 @@ describe('ReactHooksWithNoopRenderer', () => {
await act(() => {
root.render();
});
- await expect(async () => {
- await act(() => {
- root.render();
- });
- }).toErrorDev(['useInsertionEffect must not schedule updates.']);
+ await act(() => {
+ root.render();
+ });
+ assertConsoleErrorDev([
+ 'useInsertionEffect must not schedule updates.\n' +
+ ' in App (at **)',
+ ]);
await act(async () => {
root.render();
@@ -3036,19 +3073,17 @@ describe('ReactHooksWithNoopRenderer', () => {
);
});
- if (gate(flags => flags.enableHiddenSubtreeInsertionEffectCleanup)) {
- await expect(async () => {
- await act(() => {
- root.render();
- });
- }).toErrorDev(['useInsertionEffect must not schedule updates.']);
- } else {
- await expect(async () => {
- await act(() => {
- root.render();
- });
- }).toErrorDev([]);
- }
+ await act(() => {
+ root.render();
+ });
+ assertConsoleErrorDev(
+ gate('enableHiddenSubtreeInsertionEffectCleanup')
+ ? [
+ 'useInsertionEffect must not schedule updates.\n' +
+ ' in App (at **)',
+ ]
+ : [],
+ );
// Should not warn for regular effects after throw.
function NotInsertion() {
@@ -3225,35 +3260,47 @@ describe('ReactHooksWithNoopRenderer', () => {
}
const root1 = ReactNoop.createRoot();
- await expect(async () => {
- await act(() => {
- root1.render();
- });
- }).toErrorDev([
+ await act(() => {
+ root1.render();
+ });
+ assertConsoleErrorDev([
'useLayoutEffect must not return anything besides a ' +
- 'function, which is used for clean-up. You returned: 17',
+ 'function, which is used for clean-up. You returned: 17\n' +
+ ' in App (at **)',
]);
const root2 = ReactNoop.createRoot();
- await expect(async () => {
- await act(() => {
- root2.render();
- });
- }).toErrorDev([
+ await act(() => {
+ root2.render();
+ });
+ assertConsoleErrorDev([
'useLayoutEffect must not return anything besides a ' +
'function, which is used for clean-up. You returned null. If your ' +
- 'effect does not require clean up, return undefined (or nothing).',
+ 'effect does not require clean up, return undefined (or nothing).\n' +
+ ' in App (at **)',
]);
const root3 = ReactNoop.createRoot();
- await expect(async () => {
- await act(() => {
- root3.render();
- });
- }).toErrorDev([
+ await act(() => {
+ root3.render();
+ });
+ assertConsoleErrorDev([
'useLayoutEffect must not return anything besides a ' +
'function, which is used for clean-up.\n\n' +
- 'It looks like you wrote useLayoutEffect(async () => ...) or returned a Promise.',
+ 'It looks like you wrote useLayoutEffect(async () => ...) or returned a Promise. ' +
+ 'Instead, write the async function inside your effect and call it immediately:\n' +
+ '\n' +
+ 'useLayoutEffect(() => {\n' +
+ ' async function fetchData() {\n' +
+ ' // You can await here\n' +
+ ' const response = await MyAPI.getData(someId);\n' +
+ ' // ...\n' +
+ ' }\n' +
+ ' fetchData();\n' +
+ "}, [someId]); // Or [] if effect doesn't need props or state\n" +
+ '\n' +
+ 'Learn more about data fetching with Hooks: https://react.dev/link/hooks-data-fetching\n' +
+ ' in App (at **)',
]);
// Error on unmount because React assumes the value is a function
@@ -3300,13 +3347,14 @@ describe('ReactHooksWithNoopRenderer', () => {
return null;
}
- await expect(async () => {
- await act(() => {
- ReactNoop.render();
- });
- }).toErrorDev(
- 'useResourceEffect must provide a callback which returns a resource. ' +
- 'If a managed resource is not needed here, use useEffect. Received undefined',
+ await act(() => {
+ ReactNoop.render();
+ });
+ assertConsoleErrorDev(
+ [
+ 'useResourceEffect must provide a callback which returns a resource. ' +
+ 'If a managed resource is not needed here, use useEffect. Received undefined',
+ ],
{withoutStack: true},
);
});
@@ -3328,14 +3376,14 @@ describe('ReactHooksWithNoopRenderer', () => {
return null;
}
- await expect(async () => {
- await act(() => {
- ReactNoop.render();
- });
- }).toErrorDev(
+ await act(() => {
+ ReactNoop.render();
+ });
+ assertConsoleErrorDev([
'useResourceEffect received a dependency array with no dependencies. ' +
- 'When specified, the dependency array must have at least one dependency.',
- );
+ 'When specified, the dependency array must have at least one dependency.\n' +
+ ' in App (at **)',
+ ]);
});
// @gate enableUseResourceEffectHook
@@ -4472,21 +4520,23 @@ describe('ReactHooksWithNoopRenderer', () => {
);
ReactNoop.render();
- await expect(async () => {
- await waitForThrow(
- 'Rendered more hooks than during the previous render.',
- );
- assertLog([]);
- }).toErrorDev([
+ await waitForThrow(
+ 'Rendered more hooks than during the previous render.',
+ );
+ assertLog([]);
+ assertConsoleErrorDev([
'React has detected a change in the order of Hooks called by App. ' +
'This will lead to bugs and errors if not fixed. For more information, ' +
- 'read the Rules of Hooks: https://react.dev/link/rules-of-hooks\n\n' +
+ 'read the Rules of Hooks: https://react.dev/link/rules-of-hooks\n' +
+ '\n' +
' Previous render Next render\n' +
' ------------------------------------------------------\n' +
'1. useState useState\n' +
'2. useState useState\n' +
'3. undefined useState\n' +
- ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n',
+ ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' +
+ '\n' +
+ ' in App (at **)',
]);
// Uncomment if/when we support this again
@@ -4569,20 +4619,22 @@ describe('ReactHooksWithNoopRenderer', () => {
await act(async () => {
ReactNoop.render();
- await expect(async () => {
- await waitForThrow(
- 'Rendered more hooks than during the previous render.',
- );
- assertLog(['Unmount A']);
- }).toErrorDev([
+ await waitForThrow(
+ 'Rendered more hooks than during the previous render.',
+ );
+ assertLog(['Unmount A']);
+ assertConsoleErrorDev([
'React has detected a change in the order of Hooks called by App. ' +
'This will lead to bugs and errors if not fixed. For more information, ' +
- 'read the Rules of Hooks: https://react.dev/link/rules-of-hooks\n\n' +
+ 'read the Rules of Hooks: https://react.dev/link/rules-of-hooks\n' +
+ '\n' +
' Previous render Next render\n' +
' ------------------------------------------------------\n' +
'1. useEffect useEffect\n' +
'2. undefined useEffect\n' +
- ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n',
+ ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' +
+ '\n' +
+ ' in App (at **)',
]);
});
diff --git a/packages/react-reconciler/src/__tests__/ReactIncremental-test.js b/packages/react-reconciler/src/__tests__/ReactIncremental-test.js
index 50ed1ba5ee9b6..22b1db7adb216 100644
--- a/packages/react-reconciler/src/__tests__/ReactIncremental-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactIncremental-test.js
@@ -1798,9 +1798,18 @@ describe('ReactIncremental', () => {
'ShowBoth {"locale":"fr"}',
]);
assertConsoleErrorDev([
- 'Intl uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
- 'ShowLocale uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
- 'ShowBoth uses the legacy contextTypes API which will be removed soon. Use React.createContext() with React.useContext() instead.',
+ 'Intl uses the legacy childContextTypes API which will soon be removed. ' +
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
+ ' in Intl (at **)',
+ 'ShowLocale 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 ShowLocale (at **)\n' +
+ ' in Intl (at **)',
+ 'ShowBoth 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 ShowBoth (at **)\n' +
+ ' in div (at **)\n' +
+ ' in Intl (at **)',
]);
ReactNoop.render(
@@ -1853,8 +1862,16 @@ describe('ReactIncremental', () => {
'ShowBoth {"locale":"en"}',
]);
assertConsoleErrorDev([
- 'Router uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
- 'ShowRoute uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
+ 'Router uses the legacy childContextTypes API which will soon be removed. ' +
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
+ ' in Router (at **)\n' +
+ ' in Intl (at **)',
+ 'ShowRoute 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 ShowRoute (at **)\n' +
+ ' in Indirection (at **)\n' +
+ ' in Router (at **)\n' +
+ ' in Intl (at **)',
]);
});
@@ -1890,8 +1907,12 @@ describe('ReactIncremental', () => {
'Recurse {"n":0}',
]);
assertConsoleErrorDev([
- 'Recurse uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
- 'Recurse uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
+ 'Recurse uses the legacy childContextTypes API which will soon be removed. ' +
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
+ ' in Recurse (at **)',
+ 'Recurse 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 Recurse (at **)',
]);
});
@@ -1943,8 +1964,13 @@ describe('ReactIncremental', () => {
'ShowLocale {"locale":"fr"}',
]);
assertConsoleErrorDev([
- 'Intl uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
- 'ShowLocale uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
+ 'Intl uses the legacy childContextTypes API which will soon be removed. ' +
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
+ ' in Intl (at **)',
+ 'ShowLocale 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 ShowLocale (at **)' +
+ (gate('enableOwnerStacks') ? '' : '\n in Intl (at **)'),
]);
await waitForAll([
@@ -2034,9 +2060,23 @@ describe('ReactIncremental', () => {
'ShowLocaleFn:read {"locale":"fr"}',
]);
assertConsoleErrorDev([
- 'Intl uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
- 'ShowLocaleClass uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
- 'ShowLocaleFn uses the legacy contextTypes API which will be removed soon. Use React.createContext() with React.useContext() instead.',
+ 'Intl uses the legacy childContextTypes API which will soon be removed. ' +
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
+ ' in Intl (at **)',
+ 'ShowLocaleClass 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 ShowLocaleClass (at **)\n' +
+ ' in Stateful (at **)\n' +
+ ' in IndirectionClass (at **)\n' +
+ ' in IndirectionFn (at **)\n' +
+ ' in Intl (at **)',
+ 'ShowLocaleFn 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 ShowLocaleFn (at **)\n' +
+ ' in Stateful (at **)\n' +
+ ' in IndirectionClass (at **)\n' +
+ ' in IndirectionFn (at **)\n' +
+ ' in Intl (at **)',
]);
statefulInst.setState({x: 1});
@@ -2125,9 +2165,24 @@ describe('ReactIncremental', () => {
]);
assertConsoleErrorDev([
- 'Intl uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
- 'ShowLocaleClass uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
- 'ShowLocaleFn uses the legacy contextTypes API which will be removed soon. Use React.createContext() with React.useContext() instead.',
+ 'Intl uses the legacy childContextTypes API which will soon be removed. ' +
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
+ ' in Intl (at **)\n' +
+ ' in Stateful (at **)',
+ 'ShowLocaleClass 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 ShowLocaleClass (at **)\n' +
+ ' in IndirectionClass (at **)\n' +
+ ' in IndirectionFn (at **)\n' +
+ ' in Intl (at **)\n' +
+ ' in Stateful (at **)',
+ 'ShowLocaleFn 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 ShowLocaleFn (at **)\n' +
+ ' in IndirectionClass (at **)\n' +
+ ' in IndirectionFn (at **)\n' +
+ ' in Intl (at **)\n' +
+ ' in Stateful (at **)',
]);
statefulInst.setState({locale: 'gr'});
@@ -2187,7 +2242,11 @@ describe('ReactIncremental', () => {
await waitForAll([]);
assertConsoleErrorDev([
- 'Child 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. (https://react.dev/link/legacy-context)\n' +
+ (gate('enableOwnerStacks') ? '' : ' in Child (at **)\n') +
+ ' in Middle (at **)\n' +
+ ' in Root (at **)',
]);
// Trigger an update in the middle of the tree
@@ -2235,8 +2294,12 @@ describe('ReactIncremental', () => {
// Init
ReactNoop.render();
- await expect(async () => await waitForAll([])).toErrorDev([
- 'ContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
+ await waitForAll([]);
+ assertConsoleErrorDev([
+ 'ContextProvider uses the legacy childContextTypes API which will soon be removed. ' +
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
+ (gate('enableOwnerStacks') ? '' : ' in ContextProvider (at **)\n') +
+ ' in Root (at **)',
]);
// Trigger an update in the middle of the tree
@@ -2244,9 +2307,12 @@ describe('ReactIncremental', () => {
instance.setState({
throwError: true,
});
- await expect(async () => await waitForAll([])).toErrorDev(
- 'Error boundaries should implement getDerivedStateFromError()',
- );
+ await waitForAll([]);
+ assertConsoleErrorDev([
+ 'Root: Error boundaries should implement getDerivedStateFromError(). ' +
+ 'In that method, return a state update to display an error message or fallback UI.\n' +
+ ' in Root (at **)',
+ ]);
});
// @gate !disableLegacyContext || !__DEV__
@@ -2292,7 +2358,9 @@ describe('ReactIncremental', () => {
]);
assertConsoleErrorDev([
- 'MyComponent uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
+ 'MyComponent 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 MyComponent (at **)',
]);
});
@@ -2427,8 +2495,15 @@ describe('ReactIncremental', () => {
await waitForAll(['count:0']);
assertConsoleErrorDev([
- 'TopContextProvider 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.',
+ 'TopContextProvider uses the legacy childContextTypes API which will soon be removed. ' +
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
+ ' in TopContextProvider (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 TopContextProvider (at **)'),
]);
instance.updateCount();
await waitForAll(['count:1']);
@@ -2487,9 +2562,22 @@ describe('ReactIncremental', () => {
await waitForAll(['count:0']);
assertConsoleErrorDev([
- 'TopContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
- 'MiddleContextProvider 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.',
+ 'TopContextProvider uses the legacy childContextTypes API which will soon be removed. ' +
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
+ ' in TopContextProvider (at **)',
+ 'MiddleContextProvider uses the legacy childContextTypes API which will soon be removed. ' +
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
+ ' in MiddleContextProvider (at **)' +
+ (gate('enableOwnerStacks')
+ ? ''
+ : '\n in TopContextProvider (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 MiddleContextProvider (at **)' +
+ '\n in TopContextProvider (at **)'),
]);
instance.updateCount();
await waitForAll(['count:1']);
@@ -2557,9 +2645,24 @@ describe('ReactIncremental', () => {
await waitForAll(['count:0']);
assertConsoleErrorDev([
- 'TopContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
- 'MiddleContextProvider 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.',
+ 'TopContextProvider uses the legacy childContextTypes API which will soon be removed. ' +
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
+ ' in TopContextProvider (at **)',
+ 'MiddleContextProvider uses the legacy childContextTypes API which will soon be removed. ' +
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
+ ' in MiddleContextProvider (at **)' +
+ (gate('enableOwnerStacks')
+ ? ''
+ : '\n in MiddleScu (at **)' +
+ '\n in TopContextProvider (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 MiddleContextProvider (at **)' +
+ '\n in MiddleScu (at **)' +
+ '\n in TopContextProvider (at **)'),
]);
instance.updateCount();
await waitForAll([]);
@@ -2637,9 +2740,24 @@ describe('ReactIncremental', () => {
await waitForAll(['count:0, name:brian']);
assertConsoleErrorDev([
- 'TopContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
- 'MiddleContextProvider 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.',
+ 'TopContextProvider uses the legacy childContextTypes API which will soon be removed. ' +
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
+ ' in TopContextProvider (at **)',
+ 'MiddleContextProvider uses the legacy childContextTypes API which will soon be removed. ' +
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
+ ' in MiddleContextProvider (at **)' +
+ (gate('enableOwnerStacks')
+ ? ''
+ : '\n in MiddleScu (at **)' +
+ '\n in TopContextProvider (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 MiddleContextProvider (at **)' +
+ '\n in MiddleScu (at **)' +
+ '\n in TopContextProvider (at **)'),
]);
topInstance.updateCount();
await waitForAll([]);
@@ -2743,11 +2861,19 @@ describe('ReactIncremental', () => {
,
);
- await expect(async () => {
- await waitForAll([]);
- }).toErrorDev([
- 'Boundary uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
- 'Legacy context API has been detected within a strict-mode tree',
+ await waitForAll([]);
+ assertConsoleErrorDev([
+ 'Boundary 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 Boundary (at **)',
+ 'Legacy context API has been detected within a strict-mode tree.\n' +
+ '\n' +
+ 'The old API will be supported in all 16.x releases, but applications using it should migrate to the new version.\n' +
+ '\n' +
+ 'Please update the following components: Boundary\n' +
+ '\n' +
+ 'Learn more about this warning here: https://react.dev/link/legacy-context\n' +
+ ' in Boundary (at **)',
]);
}
diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js b/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js
index 2f0f23dca895b..028f80d3de41a 100644
--- a/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js
@@ -1212,12 +1212,18 @@ describe('ReactIncrementalErrorHandling', () => {
,
);
- await expect(async () => {
- await waitForAll([]);
- }).toErrorDev([
- 'Provider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
- 'Provider uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
- 'Connector uses the legacy contextTypes API which will be removed soon. Use React.createContext() with React.useContext() instead.',
+ await waitForAll([]);
+ assertConsoleErrorDev([
+ 'Provider uses the legacy childContextTypes API which will soon be removed. ' +
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
+ ' in Provider (at **)',
+ 'Provider 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 Provider (at **)',
+ 'Connector 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 Connector (at **)' +
+ (gate('enableOwnerStacks') ? '' : '\n in Provider (at **)'),
]);
// If the context stack does not unwind, span will get 'abcde'
@@ -1250,9 +1256,19 @@ describe('ReactIncrementalErrorHandling', () => {
await waitForAll([]);
if (gate(flags => !flags.enableOwnerStacks)) {
assertConsoleErrorDev([
- 'React.jsx: type is invalid -- expected a string',
+ '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.\n' +
+ ' in BrokenRender (at **)\n' +
+ ' in ErrorBoundary (at **)',
// React retries once on error
- 'React.jsx: type is invalid -- expected a string',
+ '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.\n' +
+ ' in BrokenRender (at **)\n' +
+ ' in ErrorBoundary (at **)',
]);
}
@@ -1305,9 +1321,19 @@ describe('ReactIncrementalErrorHandling', () => {
await waitForAll([]);
if (gate(flags => !flags.enableOwnerStacks)) {
assertConsoleErrorDev([
- 'React.jsx: type is invalid -- expected a string',
+ '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.\n' +
+ ' in BrokenRender (at **)\n' +
+ ' in ErrorBoundary (at **)',
// React retries once on error
- 'React.jsx: type is invalid -- expected a string',
+ '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.\n' +
+ ' in BrokenRender (at **)\n' +
+ ' in ErrorBoundary (at **)',
]);
}
expect(ReactNoop).toMatchRenderedOutput(
@@ -1330,7 +1356,12 @@ describe('ReactIncrementalErrorHandling', () => {
ReactNoop.render();
if (gate(flags => !flags.enableOwnerStacks)) {
assertConsoleErrorDev(
- ['React.jsx: type is invalid -- expected a string'],
+ [
+ '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},
);
}
diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalErrorLogging-test.js b/packages/react-reconciler/src/__tests__/ReactIncrementalErrorLogging-test.js
index 6aa4034636c31..75ca5f6b131dc 100644
--- a/packages/react-reconciler/src/__tests__/ReactIncrementalErrorLogging-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactIncrementalErrorLogging-test.js
@@ -33,7 +33,7 @@ describe('ReactIncrementalErrorLogging', () => {
waitForAll = InternalTestUtils.waitForAll;
});
- // Note: in this test file we won't be using toErrorDev() matchers
+ // Note: in this test file we won't be using assertConsoleDev() matchers
// because they filter out precisely the messages we want to test for.
let oldConsoleWarn;
let oldConsoleError;
diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js b/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js
index 4d4ab4f7c929e..b7a2d6b661f0a 100644
--- a/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js
@@ -18,6 +18,7 @@ let act;
let waitForAll;
let waitFor;
let assertLog;
+let assertConsoleErrorDev;
describe('ReactIncrementalUpdates', () => {
beforeEach(() => {
@@ -34,6 +35,7 @@ describe('ReactIncrementalUpdates', () => {
waitForAll = InternalTestUtils.waitForAll;
waitFor = InternalTestUtils.waitFor;
assertLog = InternalTestUtils.assertLog;
+ assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
});
function Text({text}) {
@@ -366,20 +368,21 @@ describe('ReactIncrementalUpdates', () => {
return {a: 'a'};
});
- await expect(
- async () =>
- await waitForAll([
- 'setState updater',
- // Updates in the render phase receive the currently rendering
- // lane, so the update flushes immediately in the same render.
- 'render',
- ]),
- ).toErrorDev(
+ await waitForAll([
+ 'setState updater',
+ // Updates in the render phase receive the currently rendering
+ // lane, so the update flushes immediately in the same render.
+ 'render',
+ ]);
+ assertConsoleErrorDev([
'An update (setState, replaceState, or forceUpdate) was scheduled ' +
'from inside an update function. Update functions should be pure, ' +
'with zero side-effects. Consider using componentDidUpdate or a ' +
- 'callback.\n\nPlease update the following component: Foo',
- );
+ 'callback.\n' +
+ '\n' +
+ 'Please update the following component: Foo\n' +
+ ' in Foo (at **)',
+ ]);
expect(instance.state).toEqual({a: 'a', b: 'b'});
// Test deduplication (no additional warnings expected)
diff --git a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
index 1b665b2b8bc7f..748bfff6b0e7a 100644
--- a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
@@ -721,12 +721,13 @@ describe('ReactLazy', () => {
}
T.defaultProps = {inner: 'Hi'};
const LazyText = lazy(() => fakeImport(T));
- expect(() => {
- LazyText.defaultProps = {outer: 'Bye'};
- }).toErrorDev(
- 'It is not supported to assign `defaultProps` to ' +
- 'a lazy component import. Either specify them where the component ' +
- 'is defined, or create a wrapping component around it.',
+ LazyText.defaultProps = {outer: 'Bye'};
+ assertConsoleErrorDev(
+ [
+ 'It is not supported to assign `defaultProps` to ' +
+ 'a lazy component import. Either specify them where the component ' +
+ 'is defined, or create a wrapping component around it.',
+ ],
{withoutStack: true},
);
@@ -742,14 +743,15 @@ describe('ReactLazy', () => {
await waitForAll(['Loading...']);
expect(root).not.toMatchRenderedOutput('Hi Bye');
- await expect(async () => {
- await act(() => resolveFakeImport(T));
- assertLog(['Hi Bye']);
- }).toErrorDev(
+ await act(() => resolveFakeImport(T));
+ assertLog(['Hi Bye']);
+ assertConsoleErrorDev([
'T: Support for defaultProps ' +
'will be removed from function components in a future major ' +
- 'release. Use JavaScript default parameters instead.',
- );
+ 'release. Use JavaScript default parameters instead.\n' +
+ ' in T (at **)\n' +
+ ' in Suspense (at **)',
+ ]);
expect(root).toMatchRenderedOutput('Hi Bye');
@@ -1026,11 +1028,13 @@ describe('ReactLazy', () => {
expect(root).not.toMatchRenderedOutput('Inner default text');
// Mount
- await expect(async () => {
- await act(() => resolveFakeImport(T));
- assertLog(['Inner default text']);
- }).toErrorDev([
- 'T: Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.',
+ await act(() => resolveFakeImport(T));
+ assertLog(['Inner default text']);
+ assertConsoleErrorDev([
+ 'T: Support for defaultProps will be removed from function components in a future major release. ' +
+ 'Use JavaScript default parameters instead.\n' +
+ ' in T (at **)\n' +
+ ' in Suspense (at **)',
]);
expect(root).toMatchRenderedOutput('Inner default text');
@@ -1063,13 +1067,19 @@ describe('ReactLazy', () => {
await waitForAll(['Started loading', 'Loading...']);
expect(root).not.toMatchRenderedOutput(AB
);
- await expect(async () => {
- await act(() => resolveFakeImport(Foo));
- assertLog(['A', 'B']);
- }).toErrorDev(
- (gate(flags => flags.enableOwnerStacks) ? '' : ' in Text (at **)\n') +
- ' in Foo (at **)',
- );
+ await act(() => resolveFakeImport(Foo));
+ assertLog(['A', 'B']);
+ assertConsoleErrorDev([
+ 'Each child in a list should have a unique "key" prop.\n' +
+ '\n' +
+ 'Check the render method of `Foo`. ' +
+ 'See https://react.dev/link/warning-keys for more information.\n' +
+ (gate(flags => flags.enableOwnerStacks)
+ ? ' in Foo (at **)'
+ : ' in Text (at **)\n' +
+ ' in Foo (at **)\n' +
+ ' in Suspense (at **)'),
+ ]);
expect(root).toMatchRenderedOutput(AB
);
});
@@ -1143,11 +1153,12 @@ describe('ReactLazy', () => {
expect(root).not.toMatchRenderedOutput('4');
// Mount
- await expect(async () => {
- await act(() => resolveFakeImport(Add));
- }).toErrorDev(
- 'Unknown: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
- );
+ await act(() => resolveFakeImport(Add));
+ assertConsoleErrorDev([
+ 'Unknown: Support for defaultProps will be removed from memo components in a future major release. ' +
+ 'Use JavaScript default parameters instead.\n' +
+ ' in Suspense (at **)',
+ ]);
expect(root).toMatchRenderedOutput('4');
// Update (shallowly equal)
@@ -1231,11 +1242,14 @@ describe('ReactLazy', () => {
expect(root).not.toMatchRenderedOutput('4');
// Mount
- await expect(async () => {
- await act(() => resolveFakeImport(Add));
- }).toErrorDev([
- 'Memo: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
- 'Unknown: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
+ await act(() => resolveFakeImport(Add));
+ assertConsoleErrorDev([
+ 'Memo: Support for defaultProps will be removed from memo components in a future major release. ' +
+ 'Use JavaScript default parameters instead.\n' +
+ ' in Suspense (at **)',
+ 'Unknown: Support for defaultProps will be removed from memo components in a future major release. ' +
+ 'Use JavaScript default parameters instead.\n' +
+ ' in Suspense (at **)',
]);
expect(root).toMatchRenderedOutput('4');
diff --git a/packages/react-reconciler/src/__tests__/ReactMemo-test.js b/packages/react-reconciler/src/__tests__/ReactMemo-test.js
index 5fdf14af4d37b..2a0c21d8dd9a4 100644
--- a/packages/react-reconciler/src/__tests__/ReactMemo-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactMemo-test.js
@@ -19,6 +19,7 @@ let Scheduler;
let act;
let waitForAll;
let assertLog;
+let assertConsoleErrorDev;
describe('memo', () => {
beforeEach(() => {
@@ -33,6 +34,7 @@ describe('memo', () => {
const InternalTestUtils = require('internal-test-utils');
waitForAll = InternalTestUtils.waitForAll;
assertLog = InternalTestUtils.assertLog;
+ assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
});
function Text(props) {
@@ -397,17 +399,19 @@ describe('memo', () => {
// The final layer uses memo() from test fixture (which might be lazy).
Counter = memo(Counter);
- await expect(async () => {
- await act(() => {
- ReactNoop.render(
- }>
-
- ,
- );
- });
- assertLog(['Loading...', 15]);
- }).toErrorDev([
- 'Counter: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
+ await act(() => {
+ ReactNoop.render(
+ }>
+
+ ,
+ );
+ });
+ assertLog(['Loading...', 15]);
+ assertConsoleErrorDev([
+ 'Counter: Support for defaultProps will be removed from memo components in a future major release. ' +
+ 'Use JavaScript default parameters instead.\n' +
+ (label === 'lazy' ? '' : ' in Indirection (at **)\n') +
+ ' in Suspense (at **)',
]);
expect(ReactNoop).toMatchRenderedOutput();
@@ -431,17 +435,23 @@ describe('memo', () => {
});
it('warns if the first argument is undefined', () => {
- expect(() => memo()).toErrorDev(
- 'memo: The first argument must be a component. Instead ' +
- 'received: undefined',
+ memo();
+ assertConsoleErrorDev(
+ [
+ 'memo: The first argument must be a component. Instead ' +
+ 'received: undefined',
+ ],
{withoutStack: true},
);
});
it('warns if the first argument is null', () => {
- expect(() => memo(null)).toErrorDev(
- 'memo: The first argument must be a component. Instead ' +
- 'received: null',
+ memo(null);
+ assertConsoleErrorDev(
+ [
+ 'memo: The first argument must be a component. Instead ' +
+ 'received: null',
+ ],
{withoutStack: true},
);
});
@@ -458,16 +468,18 @@ describe('memo', () => {
Outer.defaultProps = {outer: 100};
const root = ReactNoop.createRoot();
- await expect(async () => {
- await act(() => {
- root.render(
-
-
-
,
- );
- });
- }).toErrorDev([
- 'Support for defaultProps will be removed from memo component',
+ await act(() => {
+ root.render(
+
+
+
,
+ );
+ });
+ assertConsoleErrorDev([
+ 'Inner: ' +
+ 'Support for defaultProps will be removed from memo components in a future major release. ' +
+ 'Use JavaScript default parameters instead.\n' +
+ ' in div (at **)',
]);
expect(root).toMatchRenderedOutput(111
);
@@ -559,14 +571,15 @@ describe('memo', () => {
,
);
- await expect(async () => {
- await waitForAll([]);
- }).toErrorDev(
+ await waitForAll([]);
+ assertConsoleErrorDev([
'Each child in a list should have a unique "key" prop. ' +
'See https://react.dev/link/warning-keys for more information.\n' +
' in span (at **)\n' +
- ' in ',
- );
+ (gate('enableOwnerStacks')
+ ? ' in **/ReactMemo-test.js:**:** (at **)'
+ : ' in p (at **)'),
+ ]);
});
it('should use the inner function name for the stack', async () => {
@@ -578,16 +591,15 @@ describe('memo', () => {
,
);
- await expect(async () => {
- await waitForAll([]);
- }).toErrorDev(
+ await waitForAll([]);
+ assertConsoleErrorDev([
'Each child in a list should have a unique "key" prop.' +
'\n\nCheck the top-level render call using . It was passed a child from Inner. ' +
'See https://react.dev/link/warning-keys for more information.\n' +
' in span (at **)\n' +
' in Inner (at **)' +
(gate(flags => flags.enableOwnerStacks) ? '' : '\n in p (at **)'),
- );
+ ]);
});
it('should use the inner name in the stack', async () => {
@@ -601,16 +613,15 @@ describe('memo', () => {
,
);
- await expect(async () => {
- await waitForAll([]);
- }).toErrorDev(
+ await waitForAll([]);
+ assertConsoleErrorDev([
'Each child in a list should have a unique "key" prop.' +
'\n\nCheck the top-level render call using . It was passed a child from Inner. ' +
'See https://react.dev/link/warning-keys for more information.\n' +
' in span (at **)\n' +
' in Inner (at **)' +
(gate(flags => flags.enableOwnerStacks) ? '' : '\n in p (at **)'),
- );
+ ]);
});
it('can use the outer displayName in the stack', async () => {
@@ -623,16 +634,15 @@ describe('memo', () => {
,
);
- await expect(async () => {
- await waitForAll([]);
- }).toErrorDev(
+ await waitForAll([]);
+ assertConsoleErrorDev([
'Each child in a list should have a unique "key" prop.' +
'\n\nCheck the top-level render call using . It was passed a child from Outer. ' +
'See https://react.dev/link/warning-keys for more information.\n' +
' in span (at **)\n' +
' in Outer (at **)' +
(gate(flags => flags.enableOwnerStacks) ? '' : '\n in p (at **)'),
- );
+ ]);
});
it('should prefer the inner to the outer displayName in the stack', async () => {
@@ -647,16 +657,15 @@ describe('memo', () => {
,
);
- await expect(async () => {
- await waitForAll([]);
- }).toErrorDev(
+ await waitForAll([]);
+ assertConsoleErrorDev([
'Each child in a list should have a unique "key" prop.' +
'\n\nCheck the top-level render call using . It was passed a child from Inner. ' +
'See https://react.dev/link/warning-keys for more information.\n' +
' in span (at **)\n' +
' in Inner (at **)' +
(gate(flags => flags.enableOwnerStacks) ? '' : '\n in p (at **)'),
- );
+ ]);
});
}
});
diff --git a/packages/react-reconciler/src/__tests__/ReactNewContext-test.js b/packages/react-reconciler/src/__tests__/ReactNewContext-test.js
index b8fbca8339f5b..58cb2f9e648b5 100644
--- a/packages/react-reconciler/src/__tests__/ReactNewContext-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactNewContext-test.js
@@ -861,8 +861,11 @@ describe('ReactNewContext', () => {
,
);
- await expect(async () => await waitForAll([])).toErrorDev(
- 'The `value` prop is required for the ``. Did you misspell it or forget to pass it?',
+ await waitForAll([]);
+ assertConsoleErrorDev(
+ [
+ 'The `value` prop is required for the ``. Did you misspell it or forget to pass it?',
+ ],
{
withoutStack: true,
},
@@ -1036,7 +1039,9 @@ describe('ReactNewContext', () => {
);
await waitForAll(['LegacyProvider', 'App', 'Child']);
assertConsoleErrorDev([
- 'LegacyProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
+ 'LegacyProvider uses the legacy childContextTypes API which will soon be removed. ' +
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
+ ' in LegacyProvider (at **)',
]);
expect(ReactNoop).toMatchRenderedOutput();
@@ -1320,9 +1325,16 @@ describe('ReactNewContext', () => {
}
ReactNoop.render();
- await expect(async () => await waitForAll([])).toErrorDev([
- 'Context can only be read while React is rendering',
- 'Cannot update during an existing state transition',
+ await waitForAll([]);
+ 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 Cls (at **)',
+ 'Context can only be read while React is rendering. ' +
+ 'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
+ 'In function components, you can read it directly in the function body, ' +
+ 'but not inside Hooks like useReducer() or useMemo().\n' +
+ ' in Cls (at **)',
]);
});
});
@@ -1353,10 +1365,12 @@ describe('ReactNewContext', () => {
return useContext(Context.Consumer);
}
ReactNoop.render();
- await expect(async () => await waitForAll([])).toErrorDev(
+ await waitForAll([]);
+ assertConsoleErrorDev([
'Calling useContext(Context.Consumer) is not supported and will cause bugs. ' +
- 'Did you mean to call useContext(Context) instead?',
- );
+ 'Did you mean to call useContext(Context) instead?\n' +
+ ' in Foo (at **)',
+ ]);
});
// Context consumer bails out on propagating "deep" updates when `value` hasn't changed.
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseCallback-test.js b/packages/react-reconciler/src/__tests__/ReactSuspenseCallback-test.js
index 6da6ef7573fd9..921c9666a935e 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspenseCallback-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspenseCallback-test.js
@@ -13,6 +13,7 @@ let React;
let ReactNoop;
let waitForAll;
let act;
+let assertConsoleErrorDev;
describe('ReactSuspense', () => {
beforeEach(() => {
@@ -24,6 +25,7 @@ describe('ReactSuspense', () => {
const InternalTestUtils = require('internal-test-utils');
waitForAll = InternalTestUtils.waitForAll;
act = InternalTestUtils.act;
+ assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
});
function createThenable() {
@@ -57,10 +59,10 @@ describe('ReactSuspense', () => {
);
ReactNoop.render(elementBadType);
- await expect(async () => await waitForAll([])).toErrorDev(
- ['Unexpected type for suspenseCallback.'],
- {withoutStack: true},
- );
+ await waitForAll([]);
+ assertConsoleErrorDev(['Unexpected type for suspenseCallback.'], {
+ withoutStack: true,
+ });
const elementMissingCallback = (
@@ -69,7 +71,8 @@ describe('ReactSuspense', () => {
);
ReactNoop.render(elementMissingCallback);
- await expect(async () => await waitForAll([])).toErrorDev([]);
+ await waitForAll([]);
+ assertConsoleErrorDev([]);
});
// @gate enableSuspenseCallback
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseList-test.js b/packages/react-reconciler/src/__tests__/ReactSuspenseList-test.js
index 63c398a657b09..7cfe72dfc4ec8 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspenseList-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspenseList-test.js
@@ -18,6 +18,7 @@ let SuspenseList;
let waitForAll;
let assertLog;
let waitFor;
+let assertConsoleErrorDev;
describe('ReactSuspenseList', () => {
beforeEach(() => {
@@ -37,6 +38,7 @@ describe('ReactSuspenseList', () => {
assertLog = InternalTestUtils.assertLog;
waitFor = InternalTestUtils.waitFor;
act = InternalTestUtils.act;
+ assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
});
function Text(props) {
@@ -72,11 +74,10 @@ describe('ReactSuspenseList', () => {
);
}
- await expect(async () => {
- await act(() => {
- ReactNoop.render();
- });
- }).toErrorDev([
+ await act(() => {
+ ReactNoop.render();
+ });
+ assertConsoleErrorDev([
'"something" is not a supported revealOrder on ' +
'. Did you mean "together", "forwards" or "backwards"?' +
'\n in SuspenseList (at **)' +
@@ -94,11 +95,10 @@ describe('ReactSuspenseList', () => {
);
}
- await expect(async () => {
- await act(() => {
- ReactNoop.render();
- });
- }).toErrorDev([
+ await act(() => {
+ ReactNoop.render();
+ });
+ assertConsoleErrorDev([
'"TOGETHER" is not a valid value for revealOrder on ' +
'. Use lowercase "together" instead.' +
'\n in SuspenseList (at **)' +
@@ -116,11 +116,10 @@ describe('ReactSuspenseList', () => {
);
}
- await expect(async () => {
- await act(() => {
- ReactNoop.render();
- });
- }).toErrorDev([
+ await act(() => {
+ ReactNoop.render();
+ });
+ assertConsoleErrorDev([
'"forward" is not a valid value for revealOrder on ' +
'. React uses the -s suffix in the spelling. ' +
'Use "forwards" instead.' +
@@ -147,15 +146,14 @@ describe('ReactSuspenseList', () => {
// No warning
await waitForAll([]);
- await expect(async () => {
- await act(() => {
- ReactNoop.render(
-
- Child
- ,
- );
- });
- }).toErrorDev([
+ await act(() => {
+ ReactNoop.render(
+
+ Child
+ ,
+ );
+ });
+ assertConsoleErrorDev([
'A single row was passed to a . ' +
'This is not useful since it needs multiple rows. ' +
'Did you mean to pass multiple children or an array?' +
@@ -174,11 +172,10 @@ describe('ReactSuspenseList', () => {
);
}
- await expect(async () => {
- await act(() => {
- ReactNoop.render();
- });
- }).toErrorDev([
+ await act(() => {
+ ReactNoop.render();
+ });
+ assertConsoleErrorDev([
'A single row was passed to a . ' +
'This is not useful since it needs multiple rows. ' +
'Did you mean to pass multiple children or an array?' +
@@ -202,11 +199,10 @@ describe('ReactSuspenseList', () => {
);
}
- await expect(async () => {
- await act(() => {
- ReactNoop.render();
- });
- }).toErrorDev([
+ await act(() => {
+ ReactNoop.render();
+ });
+ assertConsoleErrorDev([
'A nested array was passed to row #0 in . ' +
'Wrap it in an additional SuspenseList to configure its revealOrder: ' +
' ... ' +
@@ -1555,11 +1551,10 @@ describe('ReactSuspenseList', () => {
);
}
- await expect(async () => {
- await act(() => {
- ReactNoop.render();
- });
- }).toErrorDev([
+ await act(() => {
+ ReactNoop.render();
+ });
+ assertConsoleErrorDev([
'"collapse" is not a supported value for tail on ' +
'. Did you mean "collapsed" or "hidden"?' +
'\n in SuspenseList (at **)' +
@@ -1577,11 +1572,10 @@ describe('ReactSuspenseList', () => {
);
}
- await expect(async () => {
- await act(() => {
- ReactNoop.render();
- });
- }).toErrorDev([
+ await act(() => {
+ ReactNoop.render();
+ });
+ assertConsoleErrorDev([
' is only valid if ' +
'revealOrder is "forwards" or "backwards". ' +
'Did you mean to specify revealOrder="forwards"?' +
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js
index 0dcf0eb4705ee..79968949e3d99 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js
@@ -1979,7 +1979,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
ReactNoop.render();
});
- // TODO: assert toErrorDev() when the warning is implemented again.
+ // TODO: assertConsoleErrorDev() when the warning is implemented again.
await act(() => {
ReactNoop.flushSync(() => _setShow(true));
});
@@ -2006,7 +2006,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
ReactNoop.render();
});
- // TODO: assert toErrorDev() when the warning is implemented again.
+ // TODO: assertConsoleErrorDev() when the warning is implemented again.
await act(() => {
ReactNoop.flushSync(() => show());
});
@@ -2076,7 +2076,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
ReactNoop.render();
});
- // TODO: assert toErrorDev() when the warning is implemented again.
+ // TODO: assertConsoleErrorDev() when the warning is implemented again.
await act(() => {
ReactNoop.flushSync(() => _setShow(true));
});
diff --git a/packages/react-reconciler/src/__tests__/ReactTransitionTracing-test.js b/packages/react-reconciler/src/__tests__/ReactTransitionTracing-test.js
index 7a850325e9f2c..64d51a1ff3cd9 100644
--- a/packages/react-reconciler/src/__tests__/ReactTransitionTracing-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactTransitionTracing-test.js
@@ -14,6 +14,7 @@ let Scheduler;
let act;
let waitForAll;
let assertLog;
+let assertConsoleErrorDev;
let getCacheForType;
let useState;
@@ -43,11 +44,11 @@ describe('ReactInteractionTracing', () => {
ReactNoop = require('react-noop-renderer');
Scheduler = require('scheduler');
- act = require('internal-test-utils').act;
-
const InternalTestUtils = require('internal-test-utils');
+ act = InternalTestUtils.act;
waitForAll = InternalTestUtils.waitForAll;
assertLog = InternalTestUtils.assertLog;
+ assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
useState = React.useState;
startTransition = React.startTransition;
@@ -1412,14 +1413,12 @@ describe('ReactInteractionTracing', () => {
root.render();
ReactNoop.expire(1000);
await advanceTimers(1000);
- await expect(
- async () =>
- await waitForAll([
- 'Suspend [Page Two]',
- 'Loading...',
- 'onMarkerIncomplete(transition one, marker one, 1000, [{endTime: 3000, name: marker one, newName: marker two, type: marker}])',
- ]),
- ).toErrorDev('');
+
+ await waitForAll([
+ 'Suspend [Page Two]',
+ 'Loading...',
+ 'onMarkerIncomplete(transition one, marker one, 1000, [{endTime: 3000, name: marker one, newName: marker two, type: marker}])',
+ ]);
resolveText('Page Two');
ReactNoop.expire(1000);
@@ -2220,17 +2219,18 @@ describe('ReactInteractionTracing', () => {
);
ReactNoop.expire(1000);
await advanceTimers(1000);
- await expect(async () => {
- // onMarkerComplete shouldn't be called for transitions with
- // new keys
- await waitForAll([
- 'two',
- 'onTransitionStart(transition two, 1000)',
- 'onTransitionComplete(transition two, 1000, 2000)',
- ]);
- }).toErrorDev(
- 'Changing the name of a tracing marker after mount is not supported.',
- );
+ // onMarkerComplete shouldn't be called for transitions with
+ // new keys
+ await waitForAll([
+ 'two',
+ 'onTransitionStart(transition two, 1000)',
+ 'onTransitionComplete(transition two, 1000, 2000)',
+ ]);
+ assertConsoleErrorDev([
+ 'Changing the name of a tracing marker after mount is not supported. ' +
+ 'To remount the tracing marker, pass it a new key.\n' +
+ ' in App (at **)',
+ ]);
startTransition(
() => root.render(),
{