mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[assert helpers] react-reconciler (#31986)
Based off: https://github.com/facebook/react/pull/31984
This commit is contained in:
+10
-5
@@ -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);
|
||||
});
|
||||
|
||||
+119
-32
@@ -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(<App />);
|
||||
});
|
||||
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(...)',
|
||||
'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') ? ['not wrapped in 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},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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(<div>A</div>);
|
||||
|
||||
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']);
|
||||
|
||||
@@ -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(<Foo condition={false} />);
|
||||
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(<Foo condition={true} />);
|
||||
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 <Foo>. ' +
|
||||
'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(<Foo condition={false} />);
|
||||
// The key warning gets deduped because it's in the same component.
|
||||
|
||||
@@ -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(<App dependencies={['A', 'B']} />);
|
||||
});
|
||||
}).toErrorDev([
|
||||
await act(() => {
|
||||
root.update(<App dependencies={['A', 'B']} />);
|
||||
});
|
||||
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(<App text="Hello" hasDeps={false} />);
|
||||
});
|
||||
}).toErrorDev([
|
||||
await act(() => {
|
||||
root.update(<App text="Hello" hasDeps={false} />);
|
||||
});
|
||||
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(<App deps={'hello'} />, {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
await act(() => {
|
||||
ReactTestRenderer.create(<App deps={'hello'} />, {
|
||||
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(<App deps={100500} />, {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
await act(() => {
|
||||
ReactTestRenderer.create(<App deps={100500} />, {
|
||||
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(<App deps={{}} />, {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
await act(() => {
|
||||
ReactTestRenderer.create(<App deps={{}} />, {
|
||||
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(<App deps={'hello'} />, {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
await act(() => {
|
||||
ReactTestRenderer.create(<App deps={'hello'} />, {
|
||||
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(<App deps={null} />, {
|
||||
@@ -817,17 +829,18 @@ describe('ReactHooks', () => {
|
||||
}
|
||||
|
||||
await expect(async () => {
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
|
||||
});
|
||||
}).rejects.toThrow('create is not a function');
|
||||
}).toErrorDev([
|
||||
await act(() => {
|
||||
ReactTestRenderer.create(<App />, {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(<App />, {unstable_isConcurrent: true});
|
||||
});
|
||||
}).toErrorDev([
|
||||
await act(() => {
|
||||
ReactTestRenderer.create(<App />, {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(<App />, {unstable_isConcurrent: true});
|
||||
});
|
||||
}).toErrorDev(
|
||||
'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks.',
|
||||
);
|
||||
await act(() => {
|
||||
ReactTestRenderer.create(<App />, {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(<App />, {unstable_isConcurrent: true});
|
||||
});
|
||||
}).toErrorDev('Context can only be read while React is rendering');
|
||||
await act(() => {
|
||||
ReactTestRenderer.create(<App />, {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(<App />, {unstable_isConcurrent: true});
|
||||
});
|
||||
}).toErrorDev('Context can only be read while React is rendering');
|
||||
await act(() => {
|
||||
ReactTestRenderer.create(<App />, {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(<App />, {unstable_isConcurrent: true});
|
||||
});
|
||||
}).toErrorDev(['Context can only be read while React is rendering']);
|
||||
await act(() => {
|
||||
ReactTestRenderer.create(<App />, {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(
|
||||
<>
|
||||
<Fn />
|
||||
<Cls />
|
||||
</>,
|
||||
{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(
|
||||
<>
|
||||
<Fn />
|
||||
<Cls />
|
||||
</>,
|
||||
{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(<App />, {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(<App />, {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(<App />, {unstable_isConcurrent: true});
|
||||
});
|
||||
}).toErrorDev(
|
||||
'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks.',
|
||||
);
|
||||
await act(() => {
|
||||
ReactTestRenderer.create(<App />, {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(
|
||||
<Boundary>
|
||||
<App />
|
||||
</Boundary>,
|
||||
{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(
|
||||
<Boundary>
|
||||
<App />
|
||||
</Boundary>,
|
||||
{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(
|
||||
<Boundary>
|
||||
<App />
|
||||
</Boundary>,
|
||||
{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(
|
||||
<Boundary>
|
||||
<App />
|
||||
</Boundary>,
|
||||
{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(<App update={true} />);
|
||||
});
|
||||
} 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(<App update={true} />);
|
||||
});
|
||||
} 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(<App update={true} />);
|
||||
});
|
||||
} 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(<App update={true} />);
|
||||
});
|
||||
} 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(<App update={true} />);
|
||||
}).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(<App update={true} />);
|
||||
}).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(<App update={true} />);
|
||||
});
|
||||
}).rejects.toThrow('custom error');
|
||||
}).toErrorDev([
|
||||
await act(() => {
|
||||
root.update(<App update={true} />);
|
||||
});
|
||||
}).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 **)',
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
+195
-143
@@ -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', () => {
|
||||
<Bar triggerUpdate={true} />
|
||||
</>,
|
||||
);
|
||||
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(
|
||||
<span prop={`Count: ${props.count}`} />,
|
||||
@@ -1652,17 +1663,14 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
}, [props.count]);
|
||||
return <Text text={'Count: ' + count} />;
|
||||
}
|
||||
await expect(async () => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(<Counter count={0} />, () =>
|
||||
Scheduler.log('Sync effect'),
|
||||
);
|
||||
await waitFor(['Count: (empty)', 'Sync effect']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<span prop="Count: (empty)" />,
|
||||
);
|
||||
});
|
||||
}).toErrorDev('flushSync was called from inside a lifecycle method');
|
||||
await act(async () => {
|
||||
ReactNoop.render(<Counter count={0} />, () =>
|
||||
Scheduler.log('Sync effect'),
|
||||
);
|
||||
await waitFor(['Count: (empty)', 'Sync effect']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: (empty)" />);
|
||||
});
|
||||
|
||||
assertLog([`Count: 0`]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
|
||||
});
|
||||
@@ -2506,35 +2514,47 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
}
|
||||
|
||||
const root1 = ReactNoop.createRoot();
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
root1.render(<App return={17} />);
|
||||
});
|
||||
}).toErrorDev([
|
||||
'useEffect must not return anything besides a ' +
|
||||
'function, which is used for clean-up. You returned: 17',
|
||||
await act(() => {
|
||||
root1.render(<App return={17} />);
|
||||
});
|
||||
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(<App return={null} />);
|
||||
});
|
||||
}).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(<App return={null} />);
|
||||
});
|
||||
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(<App return={Promise.resolve()} />);
|
||||
});
|
||||
}).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(<App return={Promise.resolve()} />);
|
||||
});
|
||||
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(<App return={17} />);
|
||||
});
|
||||
}).toErrorDev([
|
||||
'useInsertionEffect must not return anything besides a ' +
|
||||
'function, which is used for clean-up. You returned: 17',
|
||||
await act(() => {
|
||||
root1.render(<App return={17} />);
|
||||
});
|
||||
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(<App return={null} />);
|
||||
});
|
||||
}).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(<App return={null} />);
|
||||
});
|
||||
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(<App return={Promise.resolve()} />);
|
||||
});
|
||||
}).toErrorDev([
|
||||
await act(() => {
|
||||
root3.render(<App return={Promise.resolve()} />);
|
||||
});
|
||||
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(<App />);
|
||||
});
|
||||
}).toErrorDev(['useInsertionEffect must not schedule updates.']);
|
||||
await act(() => {
|
||||
root.render(<App />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'useInsertionEffect must not schedule updates.\n' +
|
||||
' in App (at **)',
|
||||
]);
|
||||
|
||||
await act(async () => {
|
||||
root.render(<App throw={true} />);
|
||||
@@ -2988,11 +3023,13 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
await act(() => {
|
||||
root.render(<App foo="hello" />);
|
||||
});
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
root.render(<App foo="goodbye" />);
|
||||
});
|
||||
}).toErrorDev(['useInsertionEffect must not schedule updates.']);
|
||||
await act(() => {
|
||||
root.render(<App foo="goodbye" />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'useInsertionEffect must not schedule updates.\n' +
|
||||
' in App (at **)',
|
||||
]);
|
||||
|
||||
await act(async () => {
|
||||
root.render(<App throw={true} />);
|
||||
@@ -3036,19 +3073,17 @@ describe('ReactHooksWithNoopRenderer', () => {
|
||||
);
|
||||
});
|
||||
|
||||
if (gate(flags => flags.enableHiddenSubtreeInsertionEffectCleanup)) {
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
root.render(<Activity mode="hidden" />);
|
||||
});
|
||||
}).toErrorDev(['useInsertionEffect must not schedule updates.']);
|
||||
} else {
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
root.render(<Activity mode="hidden" />);
|
||||
});
|
||||
}).toErrorDev([]);
|
||||
}
|
||||
await act(() => {
|
||||
root.render(<Activity mode="hidden" />);
|
||||
});
|
||||
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(<App return={17} />);
|
||||
});
|
||||
}).toErrorDev([
|
||||
await act(() => {
|
||||
root1.render(<App return={17} />);
|
||||
});
|
||||
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(<App return={null} />);
|
||||
});
|
||||
}).toErrorDev([
|
||||
await act(() => {
|
||||
root2.render(<App return={null} />);
|
||||
});
|
||||
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(<App return={Promise.resolve()} />);
|
||||
});
|
||||
}).toErrorDev([
|
||||
await act(() => {
|
||||
root3.render(<App return={Promise.resolve()} />);
|
||||
});
|
||||
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(<App id={1} />);
|
||||
});
|
||||
}).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(<App id={1} />);
|
||||
});
|
||||
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(<App id={1} />);
|
||||
});
|
||||
}).toErrorDev(
|
||||
await act(() => {
|
||||
ReactNoop.render(<App id={1} />);
|
||||
});
|
||||
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(<App loadC={true} />);
|
||||
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(<App showMore={true} />);
|
||||
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 **)',
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
+175
-38
@@ -1798,9 +1798,19 @@ 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 **)' +
|
||||
(gate('enableOwnerStacks') ? '' : '\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 **)' +
|
||||
(gate('enableOwnerStacks')
|
||||
? ''
|
||||
: '\n in div (at **)' + '\n in Intl (at **)'),
|
||||
]);
|
||||
|
||||
ReactNoop.render(
|
||||
@@ -1853,8 +1863,18 @@ 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 **)' +
|
||||
(gate('enableOwnerStacks') ? '' : '\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' +
|
||||
(gate('enableOwnerStacks')
|
||||
? ' in Indirection (at **)'
|
||||
: ' in ShowRoute (at **)\n' +
|
||||
' in Indirection (at **)\n' +
|
||||
' in Router (at **)\n' +
|
||||
' in Intl (at **)'),
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -1890,8 +1910,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 +1967,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 +2063,27 @@ 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 **)' +
|
||||
(gate('enableOwnerStacks')
|
||||
? ''
|
||||
: '\n in Stateful (at **)' +
|
||||
'\n in IndirectionClass (at **)' +
|
||||
'\n in IndirectionFn (at **)' +
|
||||
' 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 **)' +
|
||||
(gate('enableOwnerStacks')
|
||||
? ''
|
||||
: '\n in Stateful (at **)' +
|
||||
'\n in IndirectionClass (at **)' +
|
||||
'\n in IndirectionFn (at **)' +
|
||||
' in Intl (at **)'),
|
||||
]);
|
||||
|
||||
statefulInst.setState({x: 1});
|
||||
@@ -2125,9 +2172,28 @@ 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' +
|
||||
(gate('enableOwnerStacks') ? '' : ' 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 **)' +
|
||||
(gate('enableOwnerStacks')
|
||||
? ''
|
||||
: '\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 **)' +
|
||||
(gate('enableOwnerStacks')
|
||||
? ''
|
||||
: '\n in IndirectionClass (at **)' +
|
||||
'\n in IndirectionFn (at **)' +
|
||||
'\n in Intl (at **)' +
|
||||
'\n in Stateful (at **)'),
|
||||
]);
|
||||
|
||||
statefulInst.setState({locale: 'gr'});
|
||||
@@ -2187,7 +2253,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 +2305,12 @@ describe('ReactIncremental', () => {
|
||||
|
||||
// Init
|
||||
ReactNoop.render(<Root />);
|
||||
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 +2318,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 +2369,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 +2506,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 +2573,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 +2656,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 +2751,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 +2872,19 @@ describe('ReactIncremental', () => {
|
||||
<Boundary />
|
||||
</React.StrictMode>,
|
||||
);
|
||||
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 **)',
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
+42
-11
@@ -1212,12 +1212,18 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||
</Provider>,
|
||||
);
|
||||
|
||||
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(<InvalidType />);
|
||||
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},
|
||||
);
|
||||
}
|
||||
|
||||
+1
-1
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(<div>AB</div>);
|
||||
|
||||
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(<div>AB</div>);
|
||||
});
|
||||
|
||||
@@ -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');
|
||||
|
||||
|
||||
+57
-48
@@ -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(
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
<Counter e={5} />
|
||||
</Suspense>,
|
||||
);
|
||||
});
|
||||
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(
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
<Counter e={5} />
|
||||
</Suspense>,
|
||||
);
|
||||
});
|
||||
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(<span prop={15} />);
|
||||
|
||||
@@ -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(
|
||||
<div>
|
||||
<Outer />
|
||||
</div>,
|
||||
);
|
||||
});
|
||||
}).toErrorDev([
|
||||
'Support for defaultProps will be removed from memo component',
|
||||
await act(() => {
|
||||
root.render(
|
||||
<div>
|
||||
<Outer />
|
||||
</div>,
|
||||
);
|
||||
});
|
||||
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(<div>111</div>);
|
||||
|
||||
@@ -559,14 +571,15 @@ describe('memo', () => {
|
||||
<MemoComponent />
|
||||
</p>,
|
||||
);
|
||||
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', () => {
|
||||
<MemoComponent />
|
||||
</p>,
|
||||
);
|
||||
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 <Inner>. 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', () => {
|
||||
<MemoComponent />
|
||||
</p>,
|
||||
);
|
||||
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 <Inner>. 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', () => {
|
||||
<MemoComponent />
|
||||
</p>,
|
||||
);
|
||||
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 <Outer>. 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', () => {
|
||||
<MemoComponent />
|
||||
</p>,
|
||||
);
|
||||
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 <Inner>. 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 **)'),
|
||||
);
|
||||
]);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -861,8 +861,11 @@ describe('ReactNewContext', () => {
|
||||
<Context.Provider anyPropNameOtherThanValue="value could be anything" />,
|
||||
);
|
||||
|
||||
await expect(async () => await waitForAll([])).toErrorDev(
|
||||
'The `value` prop is required for the `<Context.Provider>`. Did you misspell it or forget to pass it?',
|
||||
await waitForAll([]);
|
||||
assertConsoleErrorDev(
|
||||
[
|
||||
'The `value` prop is required for the `<Context.Provider>`. 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(<span prop="Child" />);
|
||||
|
||||
@@ -1320,9 +1325,16 @@ describe('ReactNewContext', () => {
|
||||
}
|
||||
|
||||
ReactNoop.render(<Cls />);
|
||||
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(<Foo />);
|
||||
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.
|
||||
|
||||
@@ -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 = (
|
||||
<React.Suspense fallback={'Waiting'}>
|
||||
@@ -69,7 +71,8 @@ describe('ReactSuspense', () => {
|
||||
);
|
||||
|
||||
ReactNoop.render(elementMissingCallback);
|
||||
await expect(async () => await waitForAll([])).toErrorDev([]);
|
||||
await waitForAll([]);
|
||||
assertConsoleErrorDev([]);
|
||||
});
|
||||
|
||||
// @gate enableSuspenseCallback
|
||||
|
||||
@@ -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(<Foo />);
|
||||
});
|
||||
}).toErrorDev([
|
||||
await act(() => {
|
||||
ReactNoop.render(<Foo />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'"something" is not a supported revealOrder on ' +
|
||||
'<SuspenseList />. Did you mean "together", "forwards" or "backwards"?' +
|
||||
'\n in SuspenseList (at **)' +
|
||||
@@ -94,11 +95,10 @@ describe('ReactSuspenseList', () => {
|
||||
);
|
||||
}
|
||||
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
ReactNoop.render(<Foo />);
|
||||
});
|
||||
}).toErrorDev([
|
||||
await act(() => {
|
||||
ReactNoop.render(<Foo />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'"TOGETHER" is not a valid value for revealOrder on ' +
|
||||
'<SuspenseList />. Use lowercase "together" instead.' +
|
||||
'\n in SuspenseList (at **)' +
|
||||
@@ -116,11 +116,10 @@ describe('ReactSuspenseList', () => {
|
||||
);
|
||||
}
|
||||
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
ReactNoop.render(<Foo />);
|
||||
});
|
||||
}).toErrorDev([
|
||||
await act(() => {
|
||||
ReactNoop.render(<Foo />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'"forward" is not a valid value for revealOrder on ' +
|
||||
'<SuspenseList />. 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(
|
||||
<Foo>
|
||||
<Suspense fallback="Loading">Child</Suspense>
|
||||
</Foo>,
|
||||
);
|
||||
});
|
||||
}).toErrorDev([
|
||||
await act(() => {
|
||||
ReactNoop.render(
|
||||
<Foo>
|
||||
<Suspense fallback="Loading">Child</Suspense>
|
||||
</Foo>,
|
||||
);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'A single row was passed to a <SuspenseList revealOrder="forwards" />. ' +
|
||||
'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(<Foo />);
|
||||
});
|
||||
}).toErrorDev([
|
||||
await act(() => {
|
||||
ReactNoop.render(<Foo />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'A single row was passed to a <SuspenseList revealOrder="backwards" />. ' +
|
||||
'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(<Foo items={['A', 'B']} />);
|
||||
});
|
||||
}).toErrorDev([
|
||||
await act(() => {
|
||||
ReactNoop.render(<Foo items={['A', 'B']} />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'A nested array was passed to row #0 in <SuspenseList />. ' +
|
||||
'Wrap it in an additional SuspenseList to configure its revealOrder: ' +
|
||||
'<SuspenseList revealOrder=...> ... ' +
|
||||
@@ -1555,11 +1551,10 @@ describe('ReactSuspenseList', () => {
|
||||
);
|
||||
}
|
||||
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
ReactNoop.render(<Foo />);
|
||||
});
|
||||
}).toErrorDev([
|
||||
await act(() => {
|
||||
ReactNoop.render(<Foo />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'"collapse" is not a supported value for tail on ' +
|
||||
'<SuspenseList />. Did you mean "collapsed" or "hidden"?' +
|
||||
'\n in SuspenseList (at **)' +
|
||||
@@ -1577,11 +1572,10 @@ describe('ReactSuspenseList', () => {
|
||||
);
|
||||
}
|
||||
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
ReactNoop.render(<Foo />);
|
||||
});
|
||||
}).toErrorDev([
|
||||
await act(() => {
|
||||
ReactNoop.render(<Foo />);
|
||||
});
|
||||
assertConsoleErrorDev([
|
||||
'<SuspenseList tail="collapsed" /> is only valid if ' +
|
||||
'revealOrder is "forwards" or "backwards". ' +
|
||||
'Did you mean to specify revealOrder="forwards"?' +
|
||||
|
||||
+3
-3
@@ -1979,7 +1979,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
ReactNoop.render(<App />);
|
||||
});
|
||||
|
||||
// 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(<App />);
|
||||
});
|
||||
|
||||
// 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(<App />);
|
||||
});
|
||||
|
||||
// TODO: assert toErrorDev() when the warning is implemented again.
|
||||
// TODO: assertConsoleErrorDev() when the warning is implemented again.
|
||||
await act(() => {
|
||||
ReactNoop.flushSync(() => _setShow(true));
|
||||
});
|
||||
|
||||
@@ -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(<App navigate={true} markerName="marker two" />);
|
||||
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(<App markerName="three" markerKey="new key" />),
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user