diff --git a/.eslintrc.js b/.eslintrc.js
index 4e023cd9d3..2c9ad7a4c9 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -608,6 +608,7 @@ module.exports = {
symbol: 'readonly',
SyntheticEvent: 'readonly',
SyntheticMouseEvent: 'readonly',
+ SyntheticPointerEvent: 'readonly',
Thenable: 'readonly',
TimeoutID: 'readonly',
WheelEventHandler: 'readonly',
diff --git a/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js b/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js
index 1690f20ede..7cb3ef0495 100644
--- a/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js
+++ b/packages/eslint-plugin-react-hooks/__tests__/ESLintRulesOfHooks-test.js
@@ -1324,6 +1324,34 @@ const allTests = {
`,
errors: [asyncComponentHookError('use')],
},
+ {
+ code: normalizeIndent`
+ function App({p1, p2}) {
+ try {
+ use(p1);
+ } catch (error) {
+ console.error(error);
+ }
+ use(p2);
+ return
App
;
+ }
+ `,
+ errors: [tryCatchUseError('use')],
+ },
+ {
+ code: normalizeIndent`
+ function App({p1, p2}) {
+ try {
+ doSomething();
+ } catch {
+ use(p1);
+ }
+ use(p2);
+ return App
;
+ }
+ `,
+ errors: [tryCatchUseError('use')],
+ },
],
};
@@ -1383,7 +1411,7 @@ if (__EXPERIMENTAL__) {
const onEvent = useEffectEvent((text) => {
console.log(text);
});
-
+
useEffect(() => {
onEvent('Hello world');
});
@@ -1421,7 +1449,7 @@ if (__EXPERIMENTAL__) {
});
return onClick()} />
}
-
+
// The useEffectEvent function shares an identifier name with the above
function MyLastComponent({theme}) {
const onClick = useEffectEvent(() => {
@@ -1573,6 +1601,12 @@ function asyncComponentHookError(fn) {
};
}
+function tryCatchUseError(fn) {
+ return {
+ message: `React Hook "${fn}" cannot be called in a try/catch block.`,
+ };
+}
+
// For easier local testing
if (!process.env.CI) {
let only = [];
diff --git a/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts b/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts
index 8d42b319b4..2132ee75ad 100644
--- a/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts
+++ b/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts
@@ -111,6 +111,16 @@ function isInsideDoWhileLoop(node: Node | undefined): node is DoWhileStatement {
return false;
}
+function isInsideTryCatch(node: Node | undefined): boolean {
+ while (node) {
+ if (node.type === 'TryStatement' || node.type === 'CatchClause') {
+ return true;
+ }
+ node = node.parent;
+ }
+ return false;
+}
+
function isUseEffectEventIdentifier(node: Node): boolean {
if (__EXPERIMENTAL__) {
return node.type === 'Identifier' && node.name === 'useEffectEvent';
@@ -532,6 +542,16 @@ const rule = {
continue;
}
+ // Report an error if use() is called inside try/catch/finally.
+ if (isUseIdentifier(hook) && isInsideTryCatch(hook)) {
+ context.report({
+ node: hook,
+ message: `React Hook "${getSourceCode().getText(
+ hook,
+ )}" cannot be called in a try/catch/finally block.`,
+ });
+ }
+
// Report an error if a hook may be called more then once.
// `use(...)` can be called in loops.
if (
diff --git a/packages/react-client/src/ReactFlightPerformanceTrack.js b/packages/react-client/src/ReactFlightPerformanceTrack.js
index 984408500d..2b35e82363 100644
--- a/packages/react-client/src/ReactFlightPerformanceTrack.js
+++ b/packages/react-client/src/ReactFlightPerformanceTrack.js
@@ -22,6 +22,8 @@ import {
addObjectToProperties,
} from 'shared/ReactPerformanceTrackProperties';
+import {getIODescription} from 'shared/ReactIODescription';
+
const supportsUserTiming =
enableProfilerTimer &&
typeof console !== 'undefined' &&
@@ -300,70 +302,6 @@ function getIOColor(
}
}
-function getIODescription(value: any): string {
- if (!__DEV__) {
- return '';
- }
- try {
- switch (typeof value) {
- case 'object':
- // Test the object for a bunch of common property names that are useful identifiers.
- // While we only have the return value here, it should ideally be a name that
- // describes the arguments requested.
- if (value === null) {
- return '';
- } else if (value instanceof Error) {
- // eslint-disable-next-line react-internal/safe-string-coercion
- return String(value.message);
- } else if (typeof value.url === 'string') {
- return value.url;
- } else if (typeof value.command === 'string') {
- return value.command;
- } else if (
- typeof value.request === 'object' &&
- typeof value.request.url === 'string'
- ) {
- return value.request.url;
- } else if (
- typeof value.response === 'object' &&
- typeof value.response.url === 'string'
- ) {
- return value.response.url;
- } else if (
- typeof value.id === 'string' ||
- typeof value.id === 'number' ||
- typeof value.id === 'bigint'
- ) {
- // eslint-disable-next-line react-internal/safe-string-coercion
- return String(value.id);
- } else if (typeof value.name === 'string') {
- return value.name;
- } else {
- const str = value.toString();
- if (str.startWith('[object ') || str.length < 5 || str.length > 500) {
- // This is probably not a useful description.
- return '';
- }
- return str;
- }
- case 'string':
- if (value.length < 5 || value.length > 500) {
- return '';
- }
- return value;
- case 'number':
- case 'bigint':
- // eslint-disable-next-line react-internal/safe-string-coercion
- return String(value);
- default:
- // Not useful descriptors.
- return '';
- }
- } catch (x) {
- return '';
- }
-}
-
function getIOLongName(
ioInfo: ReactIOInfo,
description: string,
diff --git a/packages/react-devtools-shared/src/__tests__/preprocessData-test.js b/packages/react-devtools-shared/src/__tests__/preprocessData-test.js
index 7a77ab20eb..1de972658c 100644
--- a/packages/react-devtools-shared/src/__tests__/preprocessData-test.js
+++ b/packages/react-devtools-shared/src/__tests__/preprocessData-test.js
@@ -857,7 +857,7 @@ describe('Timeline profiler', () => {
{
"batchUID": 0,
"depth": 0,
- "duration": 0.014,
+ "duration": 0.012,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.008,
"type": "render-idle",
@@ -873,25 +873,17 @@ describe('Timeline profiler', () => {
{
"batchUID": 0,
"depth": 0,
- "duration": 0.010,
+ "duration": 0.008,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.012,
"type": "commit",
},
- {
- "batchUID": 0,
- "depth": 1,
- "duration": 0.001,
- "lanes": "0b0000000000000000000000000000101",
- "timestamp": 0.02,
- "type": "layout-effects",
- },
{
"batchUID": 0,
"depth": 0,
"duration": 0.004,
"lanes": "0b0000000000000000000000000000101",
- "timestamp": 0.023,
+ "timestamp": 0.021,
"type": "passive-effects",
},
],
@@ -899,9 +891,9 @@ describe('Timeline profiler', () => {
{
"batchUID": 1,
"depth": 0,
- "duration": 0.014,
+ "duration": 0.012,
"lanes": "0b0000000000000000000000000000101",
- "timestamp": 0.028,
+ "timestamp": 0.026,
"type": "render-idle",
},
{
@@ -909,31 +901,23 @@ describe('Timeline profiler', () => {
"depth": 0,
"duration": 0.003,
"lanes": "0b0000000000000000000000000000101",
- "timestamp": 0.028,
+ "timestamp": 0.026,
"type": "render",
},
{
"batchUID": 1,
"depth": 0,
- "duration": 0.010,
+ "duration": 0.008,
"lanes": "0b0000000000000000000000000000101",
- "timestamp": 0.032,
+ "timestamp": 0.03,
"type": "commit",
},
- {
- "batchUID": 1,
- "depth": 1,
- "duration": 0.001,
- "lanes": "0b0000000000000000000000000000101",
- "timestamp": 0.04,
- "type": "layout-effects",
- },
{
"batchUID": 1,
"depth": 0,
"duration": 0.003,
"lanes": "0b0000000000000000000000000000101",
- "timestamp": 0.043,
+ "timestamp": 0.039,
"type": "passive-effects",
},
],
@@ -949,26 +933,26 @@ describe('Timeline profiler', () => {
{
"componentName": "App",
"duration": 0.002,
- "timestamp": 0.024,
+ "timestamp": 0.022,
"type": "passive-effect-mount",
"warning": null,
},
{
"componentName": "App",
"duration": 0.001,
- "timestamp": 0.029,
+ "timestamp": 0.027,
"type": "render",
"warning": null,
},
{
"componentName": "App",
"duration": 0.001,
- "timestamp": 0.044,
+ "timestamp": 0.04,
"type": "passive-effect-mount",
"warning": null,
},
],
- "duration": 0.046,
+ "duration": 0.042,
"flamechart": [],
"internalModuleSourceToRanges": Map {
undefined => [
@@ -1031,7 +1015,7 @@ describe('Timeline profiler', () => {
{
"batchUID": 0,
"depth": 0,
- "duration": 0.014,
+ "duration": 0.012,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.008,
"type": "render-idle",
@@ -1047,33 +1031,25 @@ describe('Timeline profiler', () => {
{
"batchUID": 0,
"depth": 0,
- "duration": 0.010,
+ "duration": 0.008,
"lanes": "0b0000000000000000000000000000101",
"timestamp": 0.012,
"type": "commit",
},
- {
- "batchUID": 0,
- "depth": 1,
- "duration": 0.001,
- "lanes": "0b0000000000000000000000000000101",
- "timestamp": 0.02,
- "type": "layout-effects",
- },
{
"batchUID": 0,
"depth": 0,
"duration": 0.004,
"lanes": "0b0000000000000000000000000000101",
- "timestamp": 0.023,
+ "timestamp": 0.021,
"type": "passive-effects",
},
{
"batchUID": 1,
"depth": 0,
- "duration": 0.014,
+ "duration": 0.012,
"lanes": "0b0000000000000000000000000000101",
- "timestamp": 0.028,
+ "timestamp": 0.026,
"type": "render-idle",
},
{
@@ -1081,31 +1057,23 @@ describe('Timeline profiler', () => {
"depth": 0,
"duration": 0.003,
"lanes": "0b0000000000000000000000000000101",
- "timestamp": 0.028,
+ "timestamp": 0.026,
"type": "render",
},
{
"batchUID": 1,
"depth": 0,
- "duration": 0.010,
+ "duration": 0.008,
"lanes": "0b0000000000000000000000000000101",
- "timestamp": 0.032,
+ "timestamp": 0.03,
"type": "commit",
},
- {
- "batchUID": 1,
- "depth": 1,
- "duration": 0.001,
- "lanes": "0b0000000000000000000000000000101",
- "timestamp": 0.04,
- "type": "layout-effects",
- },
{
"batchUID": 1,
"depth": 0,
"duration": 0.003,
"lanes": "0b0000000000000000000000000000101",
- "timestamp": 0.043,
+ "timestamp": 0.039,
"type": "passive-effects",
},
],
@@ -1149,7 +1117,7 @@ describe('Timeline profiler', () => {
{
"componentName": "App",
"lanes": "0b0000000000000000000000000000101",
- "timestamp": 0.025,
+ "timestamp": 0.023,
"type": "schedule-state-update",
"warning": null,
},
@@ -1254,6 +1222,15 @@ describe('Timeline profiler', () => {
let promise = null;
let resolvedValue = null;
function readValue(value) {
+ if (React.use) {
+ if (promise === null) {
+ promise = Promise.resolve(true).then(() => {
+ return value;
+ });
+ promise.displayName = 'Testing displayName';
+ }
+ return React.use(promise);
+ }
if (resolvedValue !== null) {
return resolvedValue;
} else if (promise === null) {
@@ -1273,7 +1250,7 @@ describe('Timeline profiler', () => {
const testMarks = [creactCpuProfilerSample()];
const root = ReactDOMClient.createRoot(document.createElement('div'));
- utils.act(() =>
+ await utils.actAsync(() =>
root.render(
@@ -1823,6 +1800,14 @@ describe('Timeline profiler', () => {
let promise = null;
let resolvedValue = null;
function readValue(value) {
+ if (React.use) {
+ if (promise === null) {
+ promise = Promise.resolve(true).then(() => {
+ return value;
+ });
+ }
+ return React.use(promise);
+ }
if (resolvedValue !== null) {
return resolvedValue;
} else if (promise === null) {
@@ -1881,6 +1866,14 @@ describe('Timeline profiler', () => {
let promise = null;
let resolvedValue = null;
function readValue(value) {
+ if (React.use) {
+ if (promise === null) {
+ promise = Promise.resolve(true).then(() => {
+ return value;
+ });
+ }
+ return React.use(promise);
+ }
if (resolvedValue !== null) {
return resolvedValue;
} else if (promise === null) {
@@ -2192,14 +2185,6 @@ describe('Timeline profiler', () => {
"timestamp": 10,
"type": "commit",
},
- {
- "batchUID": 1,
- "depth": 1,
- "duration": 0,
- "lanes": "0b0000000000000000000000000100000",
- "timestamp": 10,
- "type": "layout-effects",
- },
{
"batchUID": 1,
"depth": 0,
@@ -2234,14 +2219,6 @@ describe('Timeline profiler', () => {
"timestamp": 10,
"type": "commit",
},
- {
- "batchUID": 2,
- "depth": 1,
- "duration": 0,
- "lanes": "0b0000000000000000000000000100000",
- "timestamp": 10,
- "type": "layout-effects",
- },
{
"batchUID": 2,
"depth": 0,
@@ -2292,8 +2269,8 @@ describe('Timeline profiler', () => {
8 => "InputContinuous",
16 => "DefaultHydration",
32 => "Default",
- 64 => "TransitionHydration",
- 128 => "Transition",
+ 64 => undefined,
+ 128 => "TransitionHydration",
256 => "Transition",
512 => "Transition",
1024 => "Transition",
@@ -2349,14 +2326,6 @@ describe('Timeline profiler', () => {
"timestamp": 10,
"type": "commit",
},
- {
- "batchUID": 1,
- "depth": 1,
- "duration": 0,
- "lanes": "0b0000000000000000000000000100000",
- "timestamp": 10,
- "type": "layout-effects",
- },
{
"batchUID": 1,
"depth": 0,
@@ -2389,14 +2358,6 @@ describe('Timeline profiler', () => {
"timestamp": 10,
"type": "commit",
},
- {
- "batchUID": 2,
- "depth": 1,
- "duration": 0,
- "lanes": "0b0000000000000000000000000100000",
- "timestamp": 10,
- "type": "layout-effects",
- },
{
"batchUID": 2,
"depth": 0,
diff --git a/packages/react-devtools-shared/src/__tests__/profilerStore-test.js b/packages/react-devtools-shared/src/__tests__/profilerStore-test.js
index cead054d31..c8384b2fa4 100644
--- a/packages/react-devtools-shared/src/__tests__/profilerStore-test.js
+++ b/packages/react-devtools-shared/src/__tests__/profilerStore-test.js
@@ -215,7 +215,11 @@ describe('ProfilerStore', () => {
it('should not throw while initializing context values for Fibers within a not-yet-mounted subtree', () => {
const promise = new Promise(resolve => {});
const SuspendingView = () => {
- throw promise;
+ if (React.use) {
+ React.use(promise);
+ } else {
+ throw promise;
+ }
};
const App = () => {
diff --git a/packages/react-devtools-shared/src/__tests__/profilingCache-test.js b/packages/react-devtools-shared/src/__tests__/profilingCache-test.js
index fb1fa6c5f2..c0f0804f41 100644
--- a/packages/react-devtools-shared/src/__tests__/profilingCache-test.js
+++ b/packages/react-devtools-shared/src/__tests__/profilingCache-test.js
@@ -682,6 +682,14 @@ describe('ProfilingCache', () => {
it('should calculate durations correctly for suspended views', async () => {
let data;
const getData = () => {
+ if (React.use) {
+ if (!data) {
+ data = new Promise(resolve => {
+ resolve('abc');
+ });
+ }
+ return React.use(data);
+ }
if (data) {
return data;
} else {
diff --git a/packages/react-devtools-shared/src/__tests__/store-test.js b/packages/react-devtools-shared/src/__tests__/store-test.js
index 93c7048b2b..e982ac5ecc 100644
--- a/packages/react-devtools-shared/src/__tests__/store-test.js
+++ b/packages/react-devtools-shared/src/__tests__/store-test.js
@@ -44,10 +44,10 @@ describe('Store', () => {
const {render, unmount, createContainer} = getVersionedRenderImplementation();
// @reactVersion >= 18.0
- it('should not allow a root node to be collapsed', () => {
+ it('should not allow a root node to be collapsed', async () => {
const Component = () => Hi
;
- act(() => render());
+ await act(() => render());
expect(store).toMatchInlineSnapshot(`
[root]
@@ -63,16 +63,16 @@ describe('Store', () => {
});
// @reactVersion >= 18.0
- it('should properly handle a root with no visible nodes', () => {
+ it('should properly handle a root with no visible nodes', async () => {
const Root = ({children}) => children;
- act(() => render({null}));
+ await act(() => render({null}));
expect(store).toMatchInlineSnapshot(`
[root]
`);
- act(() => render());
+ await act(() => render());
expect(store).toMatchInlineSnapshot(`[root]`);
});
@@ -82,20 +82,24 @@ describe('Store', () => {
// I'mnot yet sure of how to reduce the GitHub reported production case to a test though.
// See https://github.com/facebook/react/issues/21445
// @reactVersion >= 18.0
- it('should handle when a component mounts before its owner', () => {
+ it('should handle when a component mounts before its owner', async () => {
const promise = new Promise(resolve => {});
let Dynamic = null;
const Owner = () => {
Dynamic = ;
- throw promise;
+ if (React.use) {
+ React.use(promise);
+ } else {
+ throw promise;
+ }
};
const Parent = () => {
return Dynamic;
};
const Child = () => null;
- act(() =>
+ await act(() =>
render(
<>
@@ -114,11 +118,11 @@ describe('Store', () => {
});
// @reactVersion >= 18.0
- it('should handle multibyte character strings', () => {
+ it('should handle multibyte character strings', async () => {
const Component = () => null;
Component.displayName = '🟩💜🔵';
- act(() => render());
+ await act(() => render());
expect(store).toMatchInlineSnapshot(`
[root]
<🟩💜🔵>
@@ -126,7 +130,7 @@ describe('Store', () => {
});
describe('StrictMode compliance', () => {
- it('should mark strict root elements as strict', () => {
+ it('should mark strict root elements as strict', async () => {
const App = () => ;
const Component = () => null;
@@ -134,7 +138,7 @@ describe('Store', () => {
const root = ReactDOMClient.createRoot(container, {
unstable_strictMode: true,
});
- act(() => {
+ await act(() => {
root.render();
});
@@ -143,13 +147,13 @@ describe('Store', () => {
});
// @reactVersion >= 18.0
- it('should mark non strict root elements as not strict', () => {
+ it('should mark non strict root elements as not strict', async () => {
const App = () => ;
const Component = () => null;
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
- act(() => {
+ await act(() => {
root.render();
});
@@ -157,7 +161,7 @@ describe('Store', () => {
expect(store.getElementAtIndex(1).isStrictModeNonCompliant).toBe(true);
});
- it('should mark StrictMode subtree elements as strict', () => {
+ it('should mark StrictMode subtree elements as strict', async () => {
const App = () => (
@@ -167,7 +171,7 @@ describe('Store', () => {
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
- act(() => {
+ await act(() => {
root.render();
});
@@ -182,7 +186,7 @@ describe('Store', () => {
});
// @reactVersion >= 18.0
- it('should support mount and update operations', () => {
+ it('should support mount and update operations', async () => {
const Grandparent = ({count}) => (
@@ -193,7 +197,7 @@ describe('Store', () => {
new Array(count).fill(true).map((_, index) => );
const Child = () => Hi!
;
- act(() => render());
+ await act(() => render());
expect(store).toMatchInlineSnapshot(`
[root]
â–¾
@@ -209,7 +213,7 @@ describe('Store', () => {
`);
- act(() => render());
+ await act(() => render());
expect(store).toMatchInlineSnapshot(`
[root]
â–¾
@@ -221,14 +225,14 @@ describe('Store', () => {
`);
- act(() => unmount());
+ await act(() => unmount());
expect(store).toMatchInlineSnapshot(``);
});
// @reactVersion >= 18.0
// @reactVersion < 19
// @gate !disableLegacyMode
- it('should support mount and update operations for multiple roots (legacy render)', () => {
+ it('should support mount and update operations for multiple roots (legacy render)', async () => {
const Parent = ({count}) =>
new Array(count).fill(true).map((_, index) => );
const Child = () => Hi!
;
@@ -236,7 +240,7 @@ describe('Store', () => {
const containerA = document.createElement('div');
const containerB = document.createElement('div');
- act(() => {
+ await act(() => {
legacyRender(, containerA);
legacyRender(, containerB);
});
@@ -252,7 +256,7 @@ describe('Store', () => {
`);
- act(() => {
+ await act(() => {
legacyRender(, containerA);
legacyRender(, containerB);
});
@@ -268,7 +272,7 @@ describe('Store', () => {
`);
- act(() => ReactDOM.unmountComponentAtNode(containerB));
+ await act(() => ReactDOM.unmountComponentAtNode(containerB));
expect(store).toMatchInlineSnapshot(`
[root]
â–¾
@@ -278,12 +282,12 @@ describe('Store', () => {
`);
- act(() => ReactDOM.unmountComponentAtNode(containerA));
+ await act(() => ReactDOM.unmountComponentAtNode(containerA));
expect(store).toMatchInlineSnapshot(``);
});
// @reactVersion >= 18.0
- it('should support mount and update operations for multiple roots (createRoot)', () => {
+ it('should support mount and update operations for multiple roots (createRoot)', async () => {
const Parent = ({count}) =>
new Array(count).fill(true).map((_, index) => );
const Child = () => Hi!
;
@@ -294,7 +298,7 @@ describe('Store', () => {
const rootA = ReactDOMClient.createRoot(containerA);
const rootB = ReactDOMClient.createRoot(containerB);
- act(() => {
+ await act(() => {
rootA.render();
rootB.render();
});
@@ -310,7 +314,7 @@ describe('Store', () => {
`);
- act(() => {
+ await act(() => {
rootA.render();
rootB.render();
});
@@ -326,7 +330,7 @@ describe('Store', () => {
`);
- act(() => rootB.unmount());
+ await act(() => rootB.unmount());
expect(store).toMatchInlineSnapshot(`
[root]
â–¾
@@ -336,12 +340,12 @@ describe('Store', () => {
`);
- act(() => rootA.unmount());
+ await act(() => rootA.unmount());
expect(store).toMatchInlineSnapshot(``);
});
// @reactVersion >= 18.0
- it('should filter DOM nodes from the store tree', () => {
+ it('should filter DOM nodes from the store tree', async () => {
const Grandparent = () => (
@@ -357,7 +361,7 @@ describe('Store', () => {
);
const Child = () =>
Hi!
;
- act(() => render(
));
+ await act(() => render(
));
expect(store).toMatchInlineSnapshot(`
[root]
â–¾
@@ -369,10 +373,14 @@ describe('Store', () => {
});
// @reactVersion >= 18.0
- it('should display Suspense nodes properly in various states', () => {
+ it('should display Suspense nodes properly in various states', async () => {
const Loading = () => Loading...
;
const SuspendingComponent = () => {
- throw new Promise(() => {});
+ if (React.use) {
+ React.use(new Promise(() => {}));
+ } else {
+ throw new Promise(() => {});
+ }
};
const Component = () => {
return Hello
;
@@ -390,7 +398,7 @@ describe('Store', () => {
);
- act(() => render());
+ await act(() => render());
expect(store).toMatchInlineSnapshot(`
[root]
â–¾
@@ -399,7 +407,7 @@ describe('Store', () => {
`);
- act(() => {
+ await act(() => {
render();
});
expect(store).toMatchInlineSnapshot(`
@@ -412,11 +420,15 @@ describe('Store', () => {
});
// @reactVersion >= 18.0
- it('should support nested Suspense nodes', () => {
+ it('should support nested Suspense nodes', async () => {
const Component = () => null;
const Loading = () => Loading...
;
const Never = () => {
- throw new Promise(() => {});
+ if (React.use) {
+ React.use(new Promise(() => {}));
+ } else {
+ throw new Promise(() => {});
+ }
};
const Wrapper = ({
@@ -451,7 +463,7 @@ describe('Store', () => {
);
- act(() =>
+ await act(() =>
render(
{
`);
- act(() =>
+ await act(() =>
render(
{
`);
- act(() =>
+ await act(() =>
render(
{
`);
- act(() =>
+ await act(() =>
render(
{
`);
- act(() =>
+ await act(() =>
render(
{
â–¾
`);
- act(() =>
+ await act(() =>
render(
{
`);
- act(() =>
+ await act(() =>
render(
{
`);
const rendererID = getRendererID();
- act(() =>
+ await act(() =>
agent.overrideSuspense({
id: store.getElementIDAtIndex(4),
rendererID,
@@ -628,7 +640,7 @@ describe('Store', () => {
`);
- act(() =>
+ await act(() =>
agent.overrideSuspense({
id: store.getElementIDAtIndex(2),
rendererID,
@@ -642,7 +654,7 @@ describe('Store', () => {
â–¾
`);
- act(() =>
+ await act(() =>
render(
{
â–¾
`);
- act(() =>
+ await act(() =>
agent.overrideSuspense({
id: store.getElementIDAtIndex(2),
rendererID,
@@ -679,7 +691,7 @@ describe('Store', () => {
`);
- act(() =>
+ await act(() =>
agent.overrideSuspense({
id: store.getElementIDAtIndex(4),
rendererID,
@@ -700,7 +712,7 @@ describe('Store', () => {
`);
- act(() =>
+ await act(() =>
render(
{
`);
});
- it('should display a partially rendered SuspenseList', () => {
+ it('should display a partially rendered SuspenseList', async () => {
const Loading = () => Loading...
;
const SuspendingComponent = () => {
- throw new Promise(() => {});
+ if (React.use) {
+ React.use(new Promise(() => {}));
+ } else {
+ throw new Promise(() => {});
+ }
};
const Component = () => {
return Hello
;
@@ -747,7 +763,7 @@ describe('Store', () => {
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
- act(() => {
+ await act(() => {
root.render();
});
expect(store).toMatchInlineSnapshot(`
@@ -759,7 +775,7 @@ describe('Store', () => {
`);
- act(() => {
+ await act(() => {
root.render();
});
expect(store).toMatchInlineSnapshot(`
@@ -774,7 +790,7 @@ describe('Store', () => {
});
// @reactVersion >= 18.0
- it('should support collapsing parts of the tree', () => {
+ it('should support collapsing parts of the tree', async () => {
const Grandparent = ({count}) => (
@@ -785,7 +801,7 @@ describe('Store', () => {
new Array(count).fill(true).map((_, index) => );
const Child = () => Hi!
;
- act(() => render());
+ await act(() => render());
expect(store).toMatchInlineSnapshot(`
[root]
â–¾
@@ -801,7 +817,7 @@ describe('Store', () => {
const parentOneID = store.getElementIDAtIndex(1);
const parentTwoID = store.getElementIDAtIndex(4);
- act(() => store.toggleIsCollapsed(parentOneID, true));
+ await act(() => store.toggleIsCollapsed(parentOneID, true));
expect(store).toMatchInlineSnapshot(`
[root]
â–¾
@@ -811,7 +827,7 @@ describe('Store', () => {
`);
- act(() => store.toggleIsCollapsed(parentTwoID, true));
+ await act(() => store.toggleIsCollapsed(parentTwoID, true));
expect(store).toMatchInlineSnapshot(`
[root]
â–¾
@@ -819,7 +835,7 @@ describe('Store', () => {
â–¸
`);
- act(() => store.toggleIsCollapsed(parentOneID, false));
+ await act(() => store.toggleIsCollapsed(parentOneID, false));
expect(store).toMatchInlineSnapshot(`
[root]
â–¾
@@ -829,13 +845,13 @@ describe('Store', () => {
â–¸
`);
- act(() => store.toggleIsCollapsed(grandparentID, true));
+ await act(() => store.toggleIsCollapsed(grandparentID, true));
expect(store).toMatchInlineSnapshot(`
[root]
â–¸
`);
- act(() => store.toggleIsCollapsed(grandparentID, false));
+ await act(() => store.toggleIsCollapsed(grandparentID, false));
expect(store).toMatchInlineSnapshot(`
[root]
â–¾
@@ -847,7 +863,7 @@ describe('Store', () => {
});
// @reactVersion >= 18.0
- it('should support reordering of children', () => {
+ it('should support reordering of children', async () => {
const Root = ({children}) => children;
const Component = () => null;
@@ -856,7 +872,7 @@ describe('Store', () => {
const foo = ;
const bar = ;
- act(() => render({[foo, bar]}));
+ await act(() => render({[foo, bar]}));
expect(store).toMatchInlineSnapshot(`
[root]
â–¾
@@ -867,7 +883,7 @@ describe('Store', () => {
`);
- act(() => render({[bar, foo]}));
+ await act(() => render({[bar, foo]}));
expect(store).toMatchInlineSnapshot(`
[root]
â–¾
@@ -878,13 +894,17 @@ describe('Store', () => {
`);
- act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), true));
+ await act(() =>
+ store.toggleIsCollapsed(store.getElementIDAtIndex(0), true),
+ );
expect(store).toMatchInlineSnapshot(`
[root]
â–¸
`);
- act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), false));
+ await act(() =>
+ store.toggleIsCollapsed(store.getElementIDAtIndex(0), false),
+ );
expect(store).toMatchInlineSnapshot(`
[root]
â–¾
@@ -903,12 +923,12 @@ describe('Store', () => {
});
// @reactVersion >= 18.0
- it('should support mount and update operations', () => {
+ it('should support mount and update operations', async () => {
const Parent = ({count}) =>
new Array(count).fill(true).map((_, index) => );
const Child = () => Hi!
;
- act(() =>
+ await act(() =>
render(
@@ -922,7 +942,7 @@ describe('Store', () => {
â–¸
`);
- act(() =>
+ await act(() =>
render(
@@ -936,14 +956,14 @@ describe('Store', () => {
â–¸
`);
- act(() => unmount());
+ await act(() => unmount());
expect(store).toMatchInlineSnapshot(``);
});
// @reactVersion >= 18.0
// @reactVersion < 19
// @gate !disableLegacyMode
- it('should support mount and update operations for multiple roots (legacy render)', () => {
+ it('should support mount and update operations for multiple roots (legacy render)', async () => {
const Parent = ({count}) =>
new Array(count).fill(true).map((_, index) => );
const Child = () => Hi!
;
@@ -951,7 +971,7 @@ describe('Store', () => {
const containerA = document.createElement('div');
const containerB = document.createElement('div');
- act(() => {
+ await act(() => {
legacyRender(, containerA);
legacyRender(, containerB);
});
@@ -962,7 +982,7 @@ describe('Store', () => {
â–¸
`);
- act(() => {
+ await act(() => {
legacyRender(, containerA);
legacyRender(, containerB);
});
@@ -973,18 +993,18 @@ describe('Store', () => {
â–¸
`);
- act(() => ReactDOM.unmountComponentAtNode(containerB));
+ await act(() => ReactDOM.unmountComponentAtNode(containerB));
expect(store).toMatchInlineSnapshot(`
[root]
â–¸
`);
- act(() => ReactDOM.unmountComponentAtNode(containerA));
+ await act(() => ReactDOM.unmountComponentAtNode(containerA));
expect(store).toMatchInlineSnapshot(``);
});
// @reactVersion >= 18.0
- it('should support mount and update operations for multiple roots (createRoot)', () => {
+ it('should support mount and update operations for multiple roots (createRoot)', async () => {
const Parent = ({count}) =>
new Array(count).fill(true).map((_, index) => );
const Child = () => Hi!
;
@@ -995,7 +1015,7 @@ describe('Store', () => {
const rootA = ReactDOMClient.createRoot(containerA);
const rootB = ReactDOMClient.createRoot(containerB);
- act(() => {
+ await act(() => {
rootA.render();
rootB.render();
});
@@ -1006,7 +1026,7 @@ describe('Store', () => {
â–¸
`);
- act(() => {
+ await act(() => {
rootA.render();
rootB.render();
});
@@ -1017,18 +1037,18 @@ describe('Store', () => {
â–¸
`);
- act(() => rootB.unmount());
+ await act(() => rootB.unmount());
expect(store).toMatchInlineSnapshot(`
[root]
â–¸
`);
- act(() => rootA.unmount());
+ await act(() => rootA.unmount());
expect(store).toMatchInlineSnapshot(``);
});
// @reactVersion >= 18.0
- it('should filter DOM nodes from the store tree', () => {
+ it('should filter DOM nodes from the store tree', async () => {
const Grandparent = () => (
@@ -1044,13 +1064,15 @@ describe('Store', () => {
);
const Child = () =>
Hi!
;
- act(() => render(
));
+ await act(() => render(
));
expect(store).toMatchInlineSnapshot(`
[root]
â–¸
`);
- act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), false));
+ await act(() =>
+ store.toggleIsCollapsed(store.getElementIDAtIndex(0), false),
+ );
expect(store).toMatchInlineSnapshot(`
[root]
â–¾
@@ -1058,7 +1080,9 @@ describe('Store', () => {
â–¸
`);
- act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(1), false));
+ await act(() =>
+ store.toggleIsCollapsed(store.getElementIDAtIndex(1), false),
+ );
expect(store).toMatchInlineSnapshot(`
[root]
â–¾
@@ -1069,10 +1093,14 @@ describe('Store', () => {
});
// @reactVersion >= 18.0
- it('should display Suspense nodes properly in various states', () => {
+ it('should display Suspense nodes properly in various states', async () => {
const Loading = () => Loading...
;
const SuspendingComponent = () => {
- throw new Promise(() => {});
+ if (React.use) {
+ React.use(new Promise(() => {}));
+ } else {
+ throw new Promise(() => {});
+ }
};
const Component = () => {
return Hello
;
@@ -1090,15 +1118,19 @@ describe('Store', () => {
);
- act(() => render());
+ await act(() => render());
expect(store).toMatchInlineSnapshot(`
[root]
â–¸
`);
// This test isn't meaningful unless we expand the suspended tree
- act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), false));
- act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(2), false));
+ await act(() =>
+ store.toggleIsCollapsed(store.getElementIDAtIndex(0), false),
+ );
+ await act(() =>
+ store.toggleIsCollapsed(store.getElementIDAtIndex(2), false),
+ );
expect(store).toMatchInlineSnapshot(`
[root]
â–¾
@@ -1107,7 +1139,7 @@ describe('Store', () => {
`);
- act(() => {
+ await act(() => {
render();
});
expect(store).toMatchInlineSnapshot(`
@@ -1120,7 +1152,7 @@ describe('Store', () => {
});
// @reactVersion >= 18.0
- it('should support expanding parts of the tree', () => {
+ it('should support expanding parts of the tree', async () => {
const Grandparent = ({count}) => (
@@ -1131,7 +1163,7 @@ describe('Store', () => {
new Array(count).fill(true).map((_, index) => );
const Child = () => Hi!
;
- act(() => render());
+ await act(() => render());
expect(store).toMatchInlineSnapshot(`
[root]
â–¸
@@ -1139,7 +1171,7 @@ describe('Store', () => {
const grandparentID = store.getElementIDAtIndex(0);
- act(() => store.toggleIsCollapsed(grandparentID, false));
+ await act(() => store.toggleIsCollapsed(grandparentID, false));
expect(store).toMatchInlineSnapshot(`
[root]
â–¾
@@ -1150,7 +1182,7 @@ describe('Store', () => {
const parentOneID = store.getElementIDAtIndex(1);
const parentTwoID = store.getElementIDAtIndex(2);
- act(() => store.toggleIsCollapsed(parentOneID, false));
+ await act(() => store.toggleIsCollapsed(parentOneID, false));
expect(store).toMatchInlineSnapshot(`
[root]
â–¾
@@ -1160,7 +1192,7 @@ describe('Store', () => {
â–¸
`);
- act(() => store.toggleIsCollapsed(parentTwoID, false));
+ await act(() => store.toggleIsCollapsed(parentTwoID, false));
expect(store).toMatchInlineSnapshot(`
[root]
â–¾
@@ -1172,7 +1204,7 @@ describe('Store', () => {
`);
- act(() => store.toggleIsCollapsed(parentOneID, true));
+ await act(() => store.toggleIsCollapsed(parentOneID, true));
expect(store).toMatchInlineSnapshot(`
[root]
â–¾
@@ -1182,7 +1214,7 @@ describe('Store', () => {
`);
- act(() => store.toggleIsCollapsed(parentTwoID, true));
+ await act(() => store.toggleIsCollapsed(parentTwoID, true));
expect(store).toMatchInlineSnapshot(`
[root]
â–¾
@@ -1190,7 +1222,7 @@ describe('Store', () => {
â–¸
`);
- act(() => store.toggleIsCollapsed(grandparentID, true));
+ await act(() => store.toggleIsCollapsed(grandparentID, true));
expect(store).toMatchInlineSnapshot(`
[root]
â–¸
@@ -1198,7 +1230,7 @@ describe('Store', () => {
});
// @reactVersion >= 18.0
- it('should support expanding deep parts of the tree', () => {
+ it('should support expanding deep parts of the tree', async () => {
const Wrapper = ({forwardedRef}) => (
);
@@ -1211,7 +1243,7 @@ describe('Store', () => {
const ref = React.createRef();
- act(() => render());
+ await act(() => render());
expect(store).toMatchInlineSnapshot(`
[root]
â–¸
@@ -1219,7 +1251,7 @@ describe('Store', () => {
const deepestedNodeID = agent.getIDForHostInstance(ref.current);
- act(() => store.toggleIsCollapsed(deepestedNodeID, false));
+ await act(() => store.toggleIsCollapsed(deepestedNodeID, false));
expect(store).toMatchInlineSnapshot(`
[root]
â–¾
@@ -1231,13 +1263,13 @@ describe('Store', () => {
const rootID = store.getElementIDAtIndex(0);
- act(() => store.toggleIsCollapsed(rootID, true));
+ await act(() => store.toggleIsCollapsed(rootID, true));
expect(store).toMatchInlineSnapshot(`
[root]
â–¸
`);
- act(() => store.toggleIsCollapsed(rootID, false));
+ await act(() => store.toggleIsCollapsed(rootID, false));
expect(store).toMatchInlineSnapshot(`
[root]
â–¾
@@ -1249,14 +1281,14 @@ describe('Store', () => {
const id = store.getElementIDAtIndex(1);
- act(() => store.toggleIsCollapsed(id, true));
+ await act(() => store.toggleIsCollapsed(id, true));
expect(store).toMatchInlineSnapshot(`
[root]
â–¾
â–¸
`);
- act(() => store.toggleIsCollapsed(id, false));
+ await act(() => store.toggleIsCollapsed(id, false));
expect(store).toMatchInlineSnapshot(`
[root]
â–¾
@@ -1268,7 +1300,7 @@ describe('Store', () => {
});
// @reactVersion >= 18.0
- it('should support reordering of children', () => {
+ it('should support reordering of children', async () => {
const Root = ({children}) => children;
const Component = () => null;
@@ -1277,19 +1309,21 @@ describe('Store', () => {
const foo = ;
const bar = ;
- act(() => render({[foo, bar]}));
+ await act(() => render({[foo, bar]}));
expect(store).toMatchInlineSnapshot(`
[root]
â–¸
`);
- act(() => render({[bar, foo]}));
+ await act(() => render({[bar, foo]}));
expect(store).toMatchInlineSnapshot(`
[root]
â–¸
`);
- act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), false));
+ await act(() =>
+ store.toggleIsCollapsed(store.getElementIDAtIndex(0), false),
+ );
expect(store).toMatchInlineSnapshot(`
[root]
â–¾
@@ -1297,7 +1331,7 @@ describe('Store', () => {
â–¸
`);
- act(() => {
+ await act(() => {
store.toggleIsCollapsed(store.getElementIDAtIndex(2), false);
store.toggleIsCollapsed(store.getElementIDAtIndex(1), false);
});
@@ -1311,7 +1345,9 @@ describe('Store', () => {
`);
- act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), true));
+ await act(() =>
+ store.toggleIsCollapsed(store.getElementIDAtIndex(0), true),
+ );
expect(store).toMatchInlineSnapshot(`
[root]
â–¸
@@ -1319,7 +1355,7 @@ describe('Store', () => {
});
// @reactVersion >= 18.0
- it('should not add new nodes when suspense is toggled', () => {
+ it('should not add new nodes when suspense is toggled', async () => {
const SuspenseTree = () => {
return (
Loading outer}>
@@ -1332,14 +1368,18 @@ describe('Store', () => {
const Parent = () => ;
const Child = () => null;
- act(() => render());
+ await act(() => render());
expect(store).toMatchInlineSnapshot(`
[root]
â–¸
`);
- act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), false));
- act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(1), false));
+ await act(() =>
+ store.toggleIsCollapsed(store.getElementIDAtIndex(0), false),
+ );
+ await act(() =>
+ store.toggleIsCollapsed(store.getElementIDAtIndex(1), false),
+ );
expect(store).toMatchInlineSnapshot(`
[root]
â–¾
@@ -1350,7 +1390,7 @@ describe('Store', () => {
const rendererID = getRendererID();
const suspenseID = store.getElementIDAtIndex(1);
- act(() =>
+ await act(() =>
agent.overrideSuspense({
id: suspenseID,
rendererID,
@@ -1364,7 +1404,7 @@ describe('Store', () => {
`);
- act(() =>
+ await act(() =>
agent.overrideSuspense({
id: suspenseID,
rendererID,
@@ -1386,7 +1426,7 @@ describe('Store', () => {
});
// @reactVersion >= 18.0
- it('should support a single root with a single child', () => {
+ it('should support a single root with a single child', async () => {
const Grandparent = () => (
@@ -1396,7 +1436,7 @@ describe('Store', () => {
const Parent = () => ;
const Child = () => null;
- act(() => render());
+ await act(() => render());
for (let i = 0; i < store.numElements; i++) {
expect(store.getIndexOfElementID(store.getElementIDAtIndex(i))).toBe(i);
@@ -1404,12 +1444,12 @@ describe('Store', () => {
});
// @reactVersion >= 18.0
- it('should support multiple roots with one children each', () => {
+ it('should support multiple roots with one children each', async () => {
const Grandparent = () => ;
const Parent = () => ;
const Child = () => null;
- act(() => {
+ await act(() => {
render();
render();
});
@@ -1420,12 +1460,12 @@ describe('Store', () => {
});
// @reactVersion >= 18.0
- it('should support a single root with multiple top level children', () => {
+ it('should support a single root with multiple top level children', async () => {
const Grandparent = () => ;
const Parent = () => ;
const Child = () => null;
- act(() =>
+ await act(() =>
render(
@@ -1440,12 +1480,12 @@ describe('Store', () => {
});
// @reactVersion >= 18.0
- it('should support multiple roots with multiple top level children', () => {
+ it('should support multiple roots with multiple top level children', async () => {
const Grandparent = () => ;
const Parent = () => ;
const Child = () => null;
- act(() => {
+ await act(() => {
render(
@@ -1472,7 +1512,7 @@ describe('Store', () => {
// @reactVersion >= 18.0
// @reactVersion < 19
// @gate !disableLegacyMode
- it('detects and updates profiling support based on the attached roots (legacy render)', () => {
+ it('detects and updates profiling support based on the attached roots (legacy render)', async () => {
const Component = () => null;
const containerA = document.createElement('div');
@@ -1480,19 +1520,19 @@ describe('Store', () => {
expect(store.rootSupportsBasicProfiling).toBe(false);
- act(() => legacyRender(, containerA));
+ await act(() => legacyRender(, containerA));
expect(store.rootSupportsBasicProfiling).toBe(true);
- act(() => legacyRender(, containerB));
- act(() => ReactDOM.unmountComponentAtNode(containerA));
+ await act(() => legacyRender(, containerB));
+ await act(() => ReactDOM.unmountComponentAtNode(containerA));
expect(store.rootSupportsBasicProfiling).toBe(true);
- act(() => ReactDOM.unmountComponentAtNode(containerB));
+ await act(() => ReactDOM.unmountComponentAtNode(containerB));
expect(store.rootSupportsBasicProfiling).toBe(false);
});
// @reactVersion >= 18
- it('detects and updates profiling support based on the attached roots (createRoot)', () => {
+ it('detects and updates profiling support based on the attached roots (createRoot)', async () => {
const Component = () => null;
const containerA = document.createElement('div');
@@ -1503,26 +1543,26 @@ describe('Store', () => {
expect(store.rootSupportsBasicProfiling).toBe(false);
- act(() => rootA.render());
+ await act(() => rootA.render());
expect(store.rootSupportsBasicProfiling).toBe(true);
- act(() => rootB.render());
- act(() => rootA.unmount());
+ await act(() => rootB.render());
+ await act(() => rootA.unmount());
expect(store.rootSupportsBasicProfiling).toBe(true);
- act(() => rootB.unmount());
+ await act(() => rootB.unmount());
expect(store.rootSupportsBasicProfiling).toBe(false);
});
// @reactVersion >= 18.0
- it('should properly serialize non-string key values', () => {
+ it('should properly serialize non-string key values', async () => {
const Child = () => null;
// Bypass React element's automatic stringifying of keys intentionally.
// This is pretty hacky.
const fauxElement = Object.assign({}, , {key: 123});
- act(() => render([fauxElement]));
+ await act(() => render([fauxElement]));
expect(store).toMatchInlineSnapshot(`
[root]
@@ -1581,12 +1621,12 @@ describe('Store', () => {
);
// Render once to start fetching the lazy component
- act(() => render());
+ await act(() => render());
await Promise.resolve();
// Render again after it resolves
- act(() => render());
+ await act(() => render());
expect(store).toMatchInlineSnapshot(`
[root]
@@ -1638,7 +1678,7 @@ describe('Store', () => {
const container = document.createElement('div');
// Render once to start fetching the lazy component
- act(() => legacyRender(, container));
+ await act(() => legacyRender(, container));
expect(store).toMatchInlineSnapshot(`
[root]
@@ -1649,7 +1689,7 @@ describe('Store', () => {
await Promise.resolve();
// Render again after it resolves
- act(() => legacyRender(, container));
+ await act(() => legacyRender(, container));
expect(store).toMatchInlineSnapshot(`
[root]
@@ -1659,7 +1699,7 @@ describe('Store', () => {
`);
// Render again to unmount it
- act(() => legacyRender(, container));
+ await act(() => legacyRender(, container));
expect(store).toMatchInlineSnapshot(`
[root]
@@ -1673,7 +1713,7 @@ describe('Store', () => {
const root = ReactDOMClient.createRoot(container);
// Render once to start fetching the lazy component
- act(() => root.render());
+ await act(() => root.render());
expect(store).toMatchInlineSnapshot(`
[root]
@@ -1684,7 +1724,7 @@ describe('Store', () => {
await Promise.resolve();
// Render again after it resolves
- act(() => root.render());
+ await act(() => root.render());
expect(store).toMatchInlineSnapshot(`
[root]
@@ -1694,7 +1734,7 @@ describe('Store', () => {
`);
// Render again to unmount it
- act(() => root.render());
+ await act(() => root.render());
expect(store).toMatchInlineSnapshot(`
[root]
@@ -1709,7 +1749,7 @@ describe('Store', () => {
const container = document.createElement('div');
// Render once to start fetching the lazy component
- act(() => legacyRender(, container));
+ await act(() => legacyRender(, container));
expect(store).toMatchInlineSnapshot(`
[root]
@@ -1718,7 +1758,7 @@ describe('Store', () => {
`);
// Render again to unmount it before it finishes loading
- act(() => legacyRender(, container));
+ await act(() => legacyRender(, container));
expect(store).toMatchInlineSnapshot(`
[root]
@@ -1733,7 +1773,7 @@ describe('Store', () => {
const root = ReactDOMClient.createRoot(container);
// Render once to start fetching the lazy component
- act(() => root.render());
+ await act(() => root.render());
expect(store).toMatchInlineSnapshot(`
[root]
@@ -1742,7 +1782,7 @@ describe('Store', () => {
`);
// Render again to unmount it before it finishes loading
- act(() => root.render());
+ await act(() => root.render());
expect(store).toMatchInlineSnapshot(`
[root]
@@ -1753,15 +1793,15 @@ describe('Store', () => {
describe('inline errors and warnings', () => {
// @reactVersion >= 18.0
- it('during render are counted', () => {
+ it('during render are counted', async () => {
function Example() {
console.error('test-only: render error');
console.warn('test-only: render warning');
return null;
}
- withErrorsOrWarningsIgnored(['test-only:'], () => {
- act(() => render());
+ withErrorsOrWarningsIgnored(['test-only:'], async () => {
+ await act(() => render());
});
expect(store).toMatchInlineSnapshot(`
@@ -1770,8 +1810,8 @@ describe('Store', () => {
✕âš
`);
- withErrorsOrWarningsIgnored(['test-only:'], () => {
- act(() => render());
+ withErrorsOrWarningsIgnored(['test-only:'], async () => {
+ await act(() => render());
});
expect(store).toMatchInlineSnapshot(`
@@ -1782,7 +1822,7 @@ describe('Store', () => {
});
// @reactVersion >= 18.0
- it('during layout get counted', () => {
+ it('during layout get counted', async () => {
function Example() {
React.useLayoutEffect(() => {
console.error('test-only: layout error');
@@ -1791,8 +1831,8 @@ describe('Store', () => {
return null;
}
- withErrorsOrWarningsIgnored(['test-only:'], () => {
- act(() => render());
+ withErrorsOrWarningsIgnored(['test-only:'], async () => {
+ await act(() => render());
});
expect(store).toMatchInlineSnapshot(`
@@ -1801,8 +1841,8 @@ describe('Store', () => {
✕âš
`);
- withErrorsOrWarningsIgnored(['test-only:'], () => {
- act(() => render());
+ withErrorsOrWarningsIgnored(['test-only:'], async () => {
+ await act(() => render());
});
expect(store).toMatchInlineSnapshot(`
@@ -1818,7 +1858,7 @@ describe('Store', () => {
}
// @reactVersion >= 18.0
- it('are counted (after no delay)', () => {
+ it('are counted (after no delay)', async () => {
function Example() {
React.useEffect(() => {
console.error('test-only: passive error');
@@ -1827,8 +1867,8 @@ describe('Store', () => {
return null;
}
- withErrorsOrWarningsIgnored(['test-only:'], () => {
- act(() => {
+ withErrorsOrWarningsIgnored(['test-only:'], async () => {
+ await act(() => {
render();
}, false);
});
@@ -1839,12 +1879,12 @@ describe('Store', () => {
✕âš
`);
- act(() => unmount());
+ await act(() => unmount());
expect(store).toMatchInlineSnapshot(``);
});
// @reactVersion >= 18.0
- it('are flushed early when there is a new commit', () => {
+ it('are flushed early when there is a new commit', async () => {
function Example() {
React.useEffect(() => {
console.error('test-only: passive error');
@@ -1890,7 +1930,7 @@ describe('Store', () => {
`);
});
- act(() => unmount());
+ await act(() => unmount());
expect(store).toMatchInlineSnapshot(``);
});
});
@@ -1898,7 +1938,7 @@ describe('Store', () => {
// In React 19, JSX warnings were moved into the renderer - https://github.com/facebook/react/pull/29088
// The warning is moved to the Child instead of the Parent.
// @reactVersion >= 19.0.1
- it('from react get counted [React >= 19.0.1]', () => {
+ it('from react get counted [React >= 19.0.1]', async () => {
function Example() {
return [];
}
@@ -1923,7 +1963,7 @@ describe('Store', () => {
// @reactVersion >= 18.0
// @reactVersion < 19.0
- it('from react get counted [React 18.x]', () => {
+ it('from react get counted [React 18.x]', async () => {
function Example() {
return [];
}
@@ -1947,15 +1987,15 @@ describe('Store', () => {
});
// @reactVersion >= 18.0
- it('can be cleared for the whole app', () => {
+ it('can be cleared for the whole app', async () => {
function Example() {
console.error('test-only: render error');
console.warn('test-only: render warning');
return null;
}
- withErrorsOrWarningsIgnored(['test-only:'], () => {
- act(() =>
+ withErrorsOrWarningsIgnored(['test-only:'], async () => {
+ await act(() =>
render(
@@ -1988,15 +2028,15 @@ describe('Store', () => {
});
// @reactVersion >= 18.0
- it('can be cleared for particular Fiber (only warnings)', () => {
+ it('can be cleared for particular Fiber (only warnings)', async () => {
function Example() {
console.error('test-only: render error');
console.warn('test-only: render warning');
return null;
}
- withErrorsOrWarningsIgnored(['test-only:'], () => {
- act(() =>
+ withErrorsOrWarningsIgnored(['test-only:'], async () => {
+ await act(() =>
render(
@@ -2033,15 +2073,15 @@ describe('Store', () => {
});
// @reactVersion >= 18.0
- it('can be cleared for a particular Fiber (only errors)', () => {
+ it('can be cleared for a particular Fiber (only errors)', async () => {
function Example() {
console.error('test-only: render error');
console.warn('test-only: render warning');
return null;
}
- withErrorsOrWarningsIgnored(['test-only:'], () => {
- act(() =>
+ withErrorsOrWarningsIgnored(['test-only:'], async () => {
+ await act(() =>
render(
@@ -2078,7 +2118,7 @@ describe('Store', () => {
});
// @reactVersion >= 18.0
- it('are updated when fibers are removed from the tree', () => {
+ it('are updated when fibers are removed from the tree', async () => {
function ComponentWithWarning() {
console.warn('test-only: render warning');
return null;
@@ -2093,8 +2133,8 @@ describe('Store', () => {
return null;
}
- withErrorsOrWarningsIgnored(['test-only:'], () => {
- act(() =>
+ withErrorsOrWarningsIgnored(['test-only:'], async () => {
+ await act(() =>
render(
@@ -2112,8 +2152,8 @@ describe('Store', () => {
✕âš
`);
- withErrorsOrWarningsIgnored(['test-only:'], () => {
- act(() =>
+ withErrorsOrWarningsIgnored(['test-only:'], async () => {
+ await act(() =>
render(
@@ -2129,8 +2169,8 @@ describe('Store', () => {
✕âš
`);
- withErrorsOrWarningsIgnored(['test-only:'], () => {
- act(() =>
+ withErrorsOrWarningsIgnored(['test-only:'], async () => {
+ await act(() =>
render(
@@ -2144,8 +2184,8 @@ describe('Store', () => {
âš
`);
- withErrorsOrWarningsIgnored(['test-only:'], () => {
- act(() => render());
+ withErrorsOrWarningsIgnored(['test-only:'], async () => {
+ await act(() => render());
});
expect(store).toMatchInlineSnapshot(`[root]`);
expect(store.componentWithErrorCount).toBe(0);
diff --git a/packages/react-devtools-shared/src/__tests__/storeComponentFilters-test.js b/packages/react-devtools-shared/src/__tests__/storeComponentFilters-test.js
index 5f00af92bf..c0ab51a171 100644
--- a/packages/react-devtools-shared/src/__tests__/storeComponentFilters-test.js
+++ b/packages/react-devtools-shared/src/__tests__/storeComponentFilters-test.js
@@ -509,7 +509,11 @@ describe('Store component filters', () => {
const Component = ({shouldSuspend}) => {
if (shouldSuspend) {
- throw promise;
+ if (React.use) {
+ React.use(promise);
+ } else {
+ throw promise;
+ }
}
return null;
};
diff --git a/packages/react-devtools-shared/src/__tests__/storeStressSync-test.js b/packages/react-devtools-shared/src/__tests__/storeStressSync-test.js
index 585499fd81..759ce79590 100644
--- a/packages/react-devtools-shared/src/__tests__/storeStressSync-test.js
+++ b/packages/react-devtools-shared/src/__tests__/storeStressSync-test.js
@@ -522,7 +522,11 @@ describe('StoreStress (Legacy Mode)', () => {
];
const Never = () => {
- throw new Promise(() => {});
+ if (React.use) {
+ React.use(new Promise(() => {}));
+ } else {
+ throw new Promise(() => {});
+ }
};
const Root = ({children}) => {
@@ -1144,7 +1148,11 @@ describe('StoreStress (Legacy Mode)', () => {
];
const Never = () => {
- throw new Promise(() => {});
+ if (React.use) {
+ React.use(new Promise(() => {}));
+ } else {
+ throw new Promise(() => {});
+ }
};
const MaybeSuspend = ({children, suspend}) => {
diff --git a/packages/react-devtools-shared/src/__tests__/storeStressTestConcurrent-test.js b/packages/react-devtools-shared/src/__tests__/storeStressTestConcurrent-test.js
index 8dd4ce4284..4389f78cd2 100644
--- a/packages/react-devtools-shared/src/__tests__/storeStressTestConcurrent-test.js
+++ b/packages/react-devtools-shared/src/__tests__/storeStressTestConcurrent-test.js
@@ -38,7 +38,7 @@ describe('StoreStressConcurrent', () => {
// This is a stress test for the tree mount/update/unmount traversal.
// It renders different trees that should produce the same output.
// @reactVersion >= 18.0
- it('should handle a stress test with different tree operations (Concurrent Mode)', () => {
+ it('should handle a stress test with different tree operations (Concurrent Mode)', async () => {
let setShowX;
const A = () => 'a';
const B = () => 'b';
@@ -151,26 +151,26 @@ describe('StoreStressConcurrent', () => {
root = ReactDOMClient.createRoot(container);
// Verify mounting 'abcde'.
- act(() => root.render({cases[i]}));
+ await act(() => root.render({cases[i]}));
expect(container.textContent).toMatch('abcde');
expect(print(store)).toEqual(snapshotForABCDE);
// Verify switching to 'abxde'.
- act(() => {
+ await act(() => {
setShowX(true);
});
expect(container.textContent).toMatch('abxde');
expect(print(store)).toBe(snapshotForABXDE);
// Verify switching back to 'abcde'.
- act(() => {
+ await act(() => {
setShowX(false);
});
expect(container.textContent).toMatch('abcde');
expect(print(store)).toBe(snapshotForABCDE);
// Clean up.
- act(() => root.unmount());
+ await act(() => root.unmount());
expect(print(store)).toBe('');
}
@@ -180,19 +180,19 @@ describe('StoreStressConcurrent', () => {
root = ReactDOMClient.createRoot(container);
for (let i = 0; i < cases.length; i++) {
// Verify mounting 'abcde'.
- act(() => root.render({cases[i]}));
+ await act(() => root.render({cases[i]}));
expect(container.textContent).toMatch('abcde');
expect(print(store)).toEqual(snapshotForABCDE);
// Verify switching to 'abxde'.
- act(() => {
+ await act(() => {
setShowX(true);
});
expect(container.textContent).toMatch('abxde');
expect(print(store)).toBe(snapshotForABXDE);
// Verify switching back to 'abcde'.
- act(() => {
+ await act(() => {
setShowX(false);
});
expect(container.textContent).toMatch('abcde');
@@ -204,7 +204,7 @@ describe('StoreStressConcurrent', () => {
});
// @reactVersion >= 18.0
- it('should handle stress test with reordering (Concurrent Mode)', () => {
+ it('should handle stress test with reordering (Concurrent Mode)', async () => {
const A = () => 'a';
const B = () => 'b';
const C = () => 'c';
@@ -245,10 +245,10 @@ describe('StoreStressConcurrent', () => {
let container = document.createElement('div');
for (let i = 0; i < steps.length; i++) {
const root = ReactDOMClient.createRoot(container);
- act(() => root.render({steps[i]}));
+ await act(() => root.render({steps[i]}));
// We snapshot each step once so it doesn't regress.
snapshots.push(print(store));
- act(() => root.unmount());
+ await act(() => root.unmount());
expect(print(store)).toBe('');
}
@@ -316,13 +316,13 @@ describe('StoreStressConcurrent', () => {
for (let j = 0; j < steps.length; j++) {
container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
- act(() => root.render({steps[i]}));
+ await act(() => root.render({steps[i]}));
expect(print(store)).toMatch(snapshots[i]);
- act(() => root.render({steps[j]}));
+ await act(() => root.render({steps[j]}));
expect(print(store)).toMatch(snapshots[j]);
- act(() => root.render({steps[i]}));
+ await act(() => root.render({steps[i]}));
expect(print(store)).toMatch(snapshots[i]);
- act(() => root.unmount());
+ await act(() => root.unmount());
expect(print(store)).toBe('');
}
}
@@ -332,7 +332,7 @@ describe('StoreStressConcurrent', () => {
for (let j = 0; j < steps.length; j++) {
container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
- act(() =>
+ await act(() =>
root.render(
{steps[i]}
@@ -340,7 +340,7 @@ describe('StoreStressConcurrent', () => {
),
);
expect(print(store)).toMatch(snapshots[i]);
- act(() =>
+ await act(() =>
root.render(
{steps[j]}
@@ -348,7 +348,7 @@ describe('StoreStressConcurrent', () => {
),
);
expect(print(store)).toMatch(snapshots[j]);
- act(() =>
+ await act(() =>
root.render(
{steps[i]}
@@ -356,7 +356,7 @@ describe('StoreStressConcurrent', () => {
),
);
expect(print(store)).toMatch(snapshots[i]);
- act(() => root.unmount());
+ await act(() => root.unmount());
expect(print(store)).toBe('');
}
}
@@ -392,7 +392,11 @@ describe('StoreStressConcurrent', () => {
];
const Never = () => {
- throw new Promise(() => {});
+ if (React.use) {
+ React.use(new Promise(() => {}));
+ } else {
+ throw new Promise(() => {});
+ }
};
const Root = ({children}) => {
@@ -405,7 +409,7 @@ describe('StoreStressConcurrent', () => {
let container = document.createElement('div');
for (let i = 0; i < steps.length; i++) {
const root = ReactDOMClient.createRoot(container);
- act(() =>
+ await act(() =>
root.render(
@@ -416,7 +420,7 @@ describe('StoreStressConcurrent', () => {
);
// We snapshot each step once so it doesn't regress.d
snapshots.push(print(store));
- act(() => root.unmount());
+ await act(() => root.unmount());
expect(print(store)).toBe('');
}
@@ -507,7 +511,7 @@ describe('StoreStressConcurrent', () => {
// 2. Verify check Suspense can render same steps as initial fallback content.
for (let i = 0; i < steps.length; i++) {
const root = ReactDOMClient.createRoot(container);
- act(() =>
+ await act(() =>
root.render(
@@ -521,7 +525,7 @@ describe('StoreStressConcurrent', () => {
),
);
expect(print(store)).toEqual(snapshots[i]);
- act(() => root.unmount());
+ await act(() => root.unmount());
expect(print(store)).toBe('');
}
@@ -531,7 +535,7 @@ describe('StoreStressConcurrent', () => {
// Always start with a fresh container and steps[i].
container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
- act(() =>
+ await act(() =>
root.render(
@@ -542,7 +546,7 @@ describe('StoreStressConcurrent', () => {
);
expect(print(store)).toEqual(snapshots[i]);
// Re-render with steps[j].
- act(() =>
+ await act(() =>
root.render(
@@ -554,7 +558,7 @@ describe('StoreStressConcurrent', () => {
// Verify the successful transition to steps[j].
expect(print(store)).toEqual(snapshots[j]);
// Check that we can transition back again.
- act(() =>
+ await act(() =>
root.render(
@@ -565,7 +569,7 @@ describe('StoreStressConcurrent', () => {
);
expect(print(store)).toEqual(snapshots[i]);
// Clean up after every iteration.
- act(() => root.unmount());
+ await act(() => root.unmount());
expect(print(store)).toBe('');
}
}
@@ -576,7 +580,7 @@ describe('StoreStressConcurrent', () => {
// Always start with a fresh container and steps[i].
container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
- act(() =>
+ await act(() =>
root.render(
@@ -591,7 +595,7 @@ describe('StoreStressConcurrent', () => {
);
expect(print(store)).toEqual(snapshots[i]);
// Re-render with steps[j].
- act(() =>
+ await act(() =>
root.render(
@@ -607,7 +611,7 @@ describe('StoreStressConcurrent', () => {
// Verify the successful transition to steps[j].
expect(print(store)).toEqual(snapshots[j]);
// Check that we can transition back again.
- act(() =>
+ await act(() =>
root.render(
@@ -622,7 +626,7 @@ describe('StoreStressConcurrent', () => {
);
expect(print(store)).toEqual(snapshots[i]);
// Clean up after every iteration.
- act(() => root.unmount());
+ await act(() => root.unmount());
expect(print(store)).toBe('');
}
}
@@ -633,7 +637,7 @@ describe('StoreStressConcurrent', () => {
// Always start with a fresh container and steps[i].
container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
- act(() =>
+ await act(() =>
root.render(
@@ -644,7 +648,7 @@ describe('StoreStressConcurrent', () => {
);
expect(print(store)).toEqual(snapshots[i]);
// Re-render with steps[j].
- act(() =>
+ await act(() =>
root.render(
@@ -660,7 +664,7 @@ describe('StoreStressConcurrent', () => {
// Verify the successful transition to steps[j].
expect(print(store)).toEqual(snapshots[j]);
// Check that we can transition back again.
- act(() =>
+ await act(() =>
root.render(
@@ -671,7 +675,7 @@ describe('StoreStressConcurrent', () => {
);
expect(print(store)).toEqual(snapshots[i]);
// Clean up after every iteration.
- act(() => root.unmount());
+ await act(() => root.unmount());
expect(print(store)).toBe('');
}
}
@@ -682,7 +686,7 @@ describe('StoreStressConcurrent', () => {
// Always start with a fresh container and steps[i].
container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
- act(() =>
+ await act(() =>
root.render(
@@ -697,7 +701,7 @@ describe('StoreStressConcurrent', () => {
);
expect(print(store)).toEqual(snapshots[i]);
// Re-render with steps[j].
- act(() =>
+ await act(() =>
root.render(
@@ -709,7 +713,7 @@ describe('StoreStressConcurrent', () => {
// Verify the successful transition to steps[j].
expect(print(store)).toEqual(snapshots[j]);
// Check that we can transition back again.
- act(() =>
+ await act(() =>
root.render(
@@ -724,7 +728,7 @@ describe('StoreStressConcurrent', () => {
);
expect(print(store)).toEqual(snapshots[i]);
// Clean up after every iteration.
- act(() => root.unmount());
+ await act(() => root.unmount());
expect(print(store)).toBe('');
}
}
@@ -735,7 +739,7 @@ describe('StoreStressConcurrent', () => {
// Always start with a fresh container and steps[i].
container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
- act(() =>
+ await act(() =>
root.render(
@@ -772,7 +776,7 @@ describe('StoreStressConcurrent', () => {
expect(print(store)).toEqual(snapshots[i]);
// Trigger actual fallback.
- act(() =>
+ await act(() =>
root.render(
@@ -788,7 +792,7 @@ describe('StoreStressConcurrent', () => {
expect(print(store)).toEqual(snapshots[j]);
// Force fallback while we're in fallback mode.
- act(() => {
+ await act(() => {
bridge.send('overrideSuspense', {
id: suspenseID,
rendererID: store.getRendererIDForElement(suspenseID),
@@ -799,7 +803,7 @@ describe('StoreStressConcurrent', () => {
expect(print(store)).toEqual(snapshots[j]);
// Switch to primary mode.
- act(() =>
+ await act(() =>
root.render(
@@ -859,7 +863,11 @@ describe('StoreStressConcurrent', () => {
];
const Never = () => {
- throw new Promise(() => {});
+ if (React.use) {
+ React.use(new Promise(() => {}));
+ } else {
+ throw new Promise(() => {});
+ }
};
const MaybeSuspend = ({children, suspend}) => {
@@ -890,7 +898,7 @@ describe('StoreStressConcurrent', () => {
let container = document.createElement('div');
for (let i = 0; i < steps.length; i++) {
const root = ReactDOMClient.createRoot(container);
- act(() =>
+ await act(() =>
root.render(
@@ -903,7 +911,7 @@ describe('StoreStressConcurrent', () => {
);
// We snapshot each step once so it doesn't regress.
snapshots.push(print(store));
- act(() => root.unmount());
+ await act(() => root.unmount());
expect(print(store)).toBe('');
}
@@ -913,7 +921,7 @@ describe('StoreStressConcurrent', () => {
const fallbackSnapshots = [];
for (let i = 0; i < steps.length; i++) {
const root = ReactDOMClient.createRoot(container);
- act(() =>
+ await act(() =>
root.render(
@@ -928,7 +936,7 @@ describe('StoreStressConcurrent', () => {
);
// We snapshot each step once so it doesn't regress.
fallbackSnapshots.push(print(store));
- act(() => root.unmount());
+ await act(() => root.unmount());
expect(print(store)).toBe('');
}
@@ -1046,7 +1054,7 @@ describe('StoreStressConcurrent', () => {
// Always start with a fresh container and steps[i].
container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
- act(() =>
+ await act(() =>
root.render(
@@ -1059,7 +1067,7 @@ describe('StoreStressConcurrent', () => {
);
expect(print(store)).toEqual(snapshots[i]);
// Re-render with steps[j].
- act(() =>
+ await act(() =>
root.render(
@@ -1073,7 +1081,7 @@ describe('StoreStressConcurrent', () => {
// Verify the successful transition to steps[j].
expect(print(store)).toEqual(snapshots[j]);
// Check that we can transition back again.
- act(() =>
+ await act(() =>
root.render(
@@ -1086,7 +1094,7 @@ describe('StoreStressConcurrent', () => {
);
expect(print(store)).toEqual(snapshots[i]);
// Clean up after every iteration.
- act(() => root.unmount());
+ await act(() => root.unmount());
expect(print(store)).toBe('');
}
}
@@ -1097,7 +1105,7 @@ describe('StoreStressConcurrent', () => {
// Always start with a fresh container and steps[i].
container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
- act(() =>
+ await act(() =>
root.render(
@@ -1115,7 +1123,7 @@ describe('StoreStressConcurrent', () => {
);
expect(print(store)).toEqual(fallbackSnapshots[i]);
// Re-render with steps[j].
- act(() =>
+ await act(() =>
root.render(
@@ -1134,7 +1142,7 @@ describe('StoreStressConcurrent', () => {
// Verify the successful transition to steps[j].
expect(print(store)).toEqual(fallbackSnapshots[j]);
// Check that we can transition back again.
- act(() =>
+ await act(() =>
root.render(
@@ -1152,7 +1160,7 @@ describe('StoreStressConcurrent', () => {
);
expect(print(store)).toEqual(fallbackSnapshots[i]);
// Clean up after every iteration.
- act(() => root.unmount());
+ await act(() => root.unmount());
expect(print(store)).toBe('');
}
}
@@ -1163,7 +1171,7 @@ describe('StoreStressConcurrent', () => {
// Always start with a fresh container and steps[i].
container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
- act(() =>
+ await act(() =>
root.render(
@@ -1176,7 +1184,7 @@ describe('StoreStressConcurrent', () => {
);
expect(print(store)).toEqual(snapshots[i]);
// Re-render with steps[j].
- act(() =>
+ await act(() =>
root.render(
@@ -1190,7 +1198,7 @@ describe('StoreStressConcurrent', () => {
// Verify the successful transition to steps[j].
expect(print(store)).toEqual(fallbackSnapshots[j]);
// Check that we can transition back again.
- act(() =>
+ await act(() =>
root.render(
@@ -1203,7 +1211,7 @@ describe('StoreStressConcurrent', () => {
);
expect(print(store)).toEqual(snapshots[i]);
// Clean up after every iteration.
- act(() => root.unmount());
+ await act(() => root.unmount());
expect(print(store)).toBe('');
}
}
@@ -1214,7 +1222,7 @@ describe('StoreStressConcurrent', () => {
// Always start with a fresh container and steps[i].
container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
- act(() =>
+ await act(() =>
root.render(
@@ -1227,7 +1235,7 @@ describe('StoreStressConcurrent', () => {
);
expect(print(store)).toEqual(fallbackSnapshots[i]);
// Re-render with steps[j].
- act(() =>
+ await act(() =>
root.render(
@@ -1241,7 +1249,7 @@ describe('StoreStressConcurrent', () => {
// Verify the successful transition to steps[j].
expect(print(store)).toEqual(snapshots[j]);
// Check that we can transition back again.
- act(() =>
+ await act(() =>
root.render(
@@ -1254,7 +1262,7 @@ describe('StoreStressConcurrent', () => {
);
expect(print(store)).toEqual(fallbackSnapshots[i]);
// Clean up after every iteration.
- act(() => root.unmount());
+ await act(() => root.unmount());
expect(print(store)).toBe('');
}
}
@@ -1265,7 +1273,7 @@ describe('StoreStressConcurrent', () => {
// Always start with a fresh container and steps[i].
container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);
- act(() =>
+ await act(() =>
root.render(
@@ -1304,7 +1312,7 @@ describe('StoreStressConcurrent', () => {
expect(print(store)).toEqual(snapshots[i]);
// Trigger actual fallback.
- act(() =>
+ await act(() =>
root.render(
@@ -1318,7 +1326,7 @@ describe('StoreStressConcurrent', () => {
expect(print(store)).toEqual(fallbackSnapshots[j]);
// Force fallback while we're in fallback mode.
- act(() => {
+ await act(() => {
bridge.send('overrideSuspense', {
id: suspenseID,
rendererID: store.getRendererIDForElement(suspenseID),
@@ -1329,7 +1337,7 @@ describe('StoreStressConcurrent', () => {
expect(print(store)).toEqual(fallbackSnapshots[j]);
// Switch to primary mode.
- act(() =>
+ await act(() =>
root.render(
@@ -1355,7 +1363,7 @@ describe('StoreStressConcurrent', () => {
expect(print(store)).toEqual(snapshots[i]);
// Clean up after every iteration.
- act(() => root.unmount());
+ await act(() => root.unmount());
expect(print(store)).toBe('');
}
}
diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js
index 31db8a7433..b5b7fe9658 100644
--- a/packages/react-devtools-shared/src/backend/fiber/renderer.js
+++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js
@@ -105,6 +105,8 @@ import {componentInfoToComponentLogsMap} from '../shared/DevToolsServerComponent
import is from 'shared/objectIs';
import hasOwnProperty from 'shared/hasOwnProperty';
+import {getIODescription} from 'shared/ReactIODescription';
+
import {
getStackByFiberInDevAndProd,
getOwnerStackByFiberInDev,
@@ -4116,9 +4118,26 @@ export function attach(
parentInstance,
asyncInfo.owner,
);
+ const value: any = ioInfo.value;
+ let resolvedValue = undefined;
+ if (
+ typeof value === 'object' &&
+ value !== null &&
+ typeof value.then === 'function'
+ ) {
+ switch (value.status) {
+ case 'fulfilled':
+ resolvedValue = value.value;
+ break;
+ case 'rejected':
+ resolvedValue = value.reason;
+ break;
+ }
+ }
return {
awaited: {
name: ioInfo.name,
+ description: getIODescription(resolvedValue),
start: ioInfo.start,
end: ioInfo.end,
value: ioInfo.value == null ? null : ioInfo.value,
diff --git a/packages/react-devtools-shared/src/backend/types.js b/packages/react-devtools-shared/src/backend/types.js
index 979baa3e0a..6324e63da1 100644
--- a/packages/react-devtools-shared/src/backend/types.js
+++ b/packages/react-devtools-shared/src/backend/types.js
@@ -235,6 +235,7 @@ export type PathMatch = {
// Serialized version of ReactIOInfo
export type SerializedIOInfo = {
name: string,
+ description: string,
start: number,
end: number,
value: null | Promise,
diff --git a/packages/react-devtools-shared/src/backendAPI.js b/packages/react-devtools-shared/src/backendAPI.js
index 9f96215026..d6aa18cd31 100644
--- a/packages/react-devtools-shared/src/backendAPI.js
+++ b/packages/react-devtools-shared/src/backendAPI.js
@@ -218,6 +218,7 @@ function backendToFrontendSerializedAsyncInfo(
return {
awaited: {
name: ioInfo.name,
+ description: ioInfo.description,
start: ioInfo.start,
end: ioInfo.end,
value: ioInfo.value,
diff --git a/packages/react-devtools-shared/src/devtools/cache.js b/packages/react-devtools-shared/src/devtools/cache.js
index eda30ed56a..5ed2133b06 100644
--- a/packages/react-devtools-shared/src/devtools/cache.js
+++ b/packages/react-devtools-shared/src/devtools/cache.js
@@ -7,7 +7,12 @@
* @flow
*/
-import type {ReactContext, Thenable} from 'shared/ReactTypes';
+import type {
+ ReactContext,
+ Thenable,
+ FulfilledThenable,
+ RejectedThenable,
+} from 'shared/ReactTypes';
import * as React from 'react';
import {createContext} from 'react';
@@ -26,27 +31,6 @@ import {createContext} from 'react';
export type {Thenable};
-interface Suspender {
- then(resolve: () => mixed, reject: () => mixed): mixed;
-}
-
-type PendingResult = {
- status: 0,
- value: Suspender,
-};
-
-type ResolvedResult = {
- status: 1,
- value: Value,
-};
-
-type RejectedResult = {
- status: 2,
- value: mixed,
-};
-
-type Result = PendingResult | ResolvedResult | RejectedResult;
-
export type Resource = {
clear(): void,
invalidate(Key): void,
@@ -55,10 +39,6 @@ export type Resource = {
write(Key, Value): void,
};
-const Pending = 0;
-const Resolved = 1;
-const Rejected = 2;
-
let readContext;
if (typeof React.use === 'function') {
readContext = function (Context: ReactContext) {
@@ -115,33 +95,25 @@ function accessResult(
fetch: Input => Thenable,
input: Input,
key: Key,
-): Result {
+): Thenable {
const entriesForResource = getEntriesForResource(resource);
const entry = entriesForResource.get(key);
if (entry === undefined) {
const thenable = fetch(input);
thenable.then(
value => {
- if (newResult.status === Pending) {
- const resolvedResult: ResolvedResult = (newResult: any);
- resolvedResult.status = Resolved;
- resolvedResult.value = value;
- }
+ const fulfilledThenable: FulfilledThenable = (thenable: any);
+ fulfilledThenable.status = 'fulfilled';
+ fulfilledThenable.value = value;
},
error => {
- if (newResult.status === Pending) {
- const rejectedResult: RejectedResult = (newResult: any);
- rejectedResult.status = Rejected;
- rejectedResult.value = error;
- }
+ const rejectedThenable: RejectedThenable = (thenable: any);
+ rejectedThenable.status = 'rejected';
+ rejectedThenable.reason = error;
},
);
- const newResult: PendingResult = {
- status: Pending,
- value: thenable,
- };
- entriesForResource.set(key, newResult);
- return newResult;
+ entriesForResource.set(key, thenable);
+ return thenable;
} else {
return entry;
}
@@ -167,23 +139,22 @@ export function createResource(
readContext(CacheContext);
const key = hashInput(input);
- const result: Result = accessResult(resource, fetch, input, key);
+ const result: Thenable = accessResult(resource, fetch, input, key);
+ if (typeof React.use === 'function') {
+ return React.use(result);
+ }
+
switch (result.status) {
- case Pending: {
- const suspender = result.value;
- throw suspender;
- }
- case Resolved: {
+ case 'fulfilled': {
const value = result.value;
return value;
}
- case Rejected: {
- const error = result.value;
+ case 'rejected': {
+ const error = result.reason;
throw error;
}
default:
- // Should be unreachable
- return (undefined: any);
+ throw result;
}
},
@@ -198,12 +169,13 @@ export function createResource(
write(key: Key, value: Value): void {
const entriesForResource = getEntriesForResource(resource);
- const resolvedResult: ResolvedResult = {
- status: Resolved,
+ const fulfilledThenable: FulfilledThenable = (Promise.resolve(
value,
- };
+ ): any);
+ fulfilledThenable.status = 'fulfilled';
+ fulfilledThenable.value = value;
- entriesForResource.set(key, resolvedResult);
+ entriesForResource.set(key, fulfilledThenable);
},
};
diff --git a/packages/react-devtools-shared/src/devtools/views/Components/Components.css b/packages/react-devtools-shared/src/devtools/views/Components/Components.css
index 5624f23489..8df59f72f1 100644
--- a/packages/react-devtools-shared/src/devtools/views/Components/Components.css
+++ b/packages/react-devtools-shared/src/devtools/views/Components/Components.css
@@ -16,7 +16,6 @@
.TreeWrapper {
flex: 0 0 var(--horizontal-resize-percentage);
- overflow: auto;
}
.InspectedElementWrapper {
@@ -32,13 +31,20 @@
.ResizeBar {
position: absolute;
- left: -2px;
+ /*
+ * moving the bar out of its bounding box might cause its hitbox to overlap
+ * with another scrollbar creating disorienting UX where you both resize and scroll
+ * at the same time.
+ * If you adjust this value, double check that starting resize right on this edge
+ * doesn't also cause scroll
+ */
+ left: 1px;
width: 5px;
height: 100%;
cursor: ew-resize;
}
-@media screen and (max-width: 600px) {
+@container devtools (width < 600px) {
.Components {
flex-direction: column;
}
@@ -52,7 +58,7 @@
}
.ResizeBar {
- top: -2px;
+ top: 1px;
left: 0;
width: 100%;
height: 5px;
diff --git a/packages/react-devtools-shared/src/devtools/views/Components/Components.js b/packages/react-devtools-shared/src/devtools/views/Components/Components.js
index 1d99317ba0..b6278c2db9 100644
--- a/packages/react-devtools-shared/src/devtools/views/Components/Components.js
+++ b/packages/react-devtools-shared/src/devtools/views/Components/Components.js
@@ -29,7 +29,6 @@ type Orientation = 'horizontal' | 'vertical';
type ResizeActionType =
| 'ACTION_SET_DID_MOUNT'
- | 'ACTION_SET_IS_RESIZING'
| 'ACTION_SET_HORIZONTAL_PERCENTAGE'
| 'ACTION_SET_VERTICAL_PERCENTAGE';
@@ -40,7 +39,6 @@ type ResizeAction = {
type ResizeState = {
horizontalPercentage: number,
- isResizing: boolean,
verticalPercentage: number,
};
@@ -81,82 +79,81 @@ function Components(_: {}) {
return () => clearTimeout(timeoutID);
}, [horizontalPercentage, verticalPercentage]);
- const {isResizing} = state;
+ const onResizeStart = (event: SyntheticPointerEvent) => {
+ const element = event.currentTarget;
+ element.setPointerCapture(event.pointerId);
+ };
- const onResizeStart = () =>
- dispatch({type: 'ACTION_SET_IS_RESIZING', payload: true});
+ const onResizeEnd = (event: SyntheticPointerEvent) => {
+ const element = event.currentTarget;
+ element.releasePointerCapture(event.pointerId);
+ };
- let onResize;
- let onResizeEnd;
- if (isResizing) {
- onResizeEnd = () =>
- dispatch({type: 'ACTION_SET_IS_RESIZING', payload: false});
+ const onResize = (event: SyntheticPointerEvent) => {
+ const element = event.currentTarget;
+ const isResizing = element.hasPointerCapture(event.pointerId);
+ if (!isResizing) {
+ return;
+ }
- // $FlowFixMe[missing-local-annot]
- onResize = event => {
- const resizeElement = resizeElementRef.current;
- const wrapperElement = wrapperElementRef.current;
+ const resizeElement = resizeElementRef.current;
+ const wrapperElement = wrapperElementRef.current;
- if (!isResizing || wrapperElement === null || resizeElement === null) {
- return;
- }
+ if (wrapperElement === null || resizeElement === null) {
+ return;
+ }
- event.preventDefault();
+ event.preventDefault();
- const orientation = getOrientation(wrapperElement);
+ const orientation = getOrientation(wrapperElement);
- const {height, width, left, top} = wrapperElement.getBoundingClientRect();
+ const {height, width, left, top} = wrapperElement.getBoundingClientRect();
- const currentMousePosition =
+ const currentMousePosition =
+ orientation === 'horizontal' ? event.clientX - left : event.clientY - top;
+
+ const boundaryMin = MINIMUM_SIZE;
+ const boundaryMax =
+ orientation === 'horizontal'
+ ? width - MINIMUM_SIZE
+ : height - MINIMUM_SIZE;
+
+ const isMousePositionInBounds =
+ currentMousePosition > boundaryMin && currentMousePosition < boundaryMax;
+
+ if (isMousePositionInBounds) {
+ const resizedElementDimension =
+ orientation === 'horizontal' ? width : height;
+ const actionType =
orientation === 'horizontal'
- ? event.clientX - left
- : event.clientY - top;
+ ? 'ACTION_SET_HORIZONTAL_PERCENTAGE'
+ : 'ACTION_SET_VERTICAL_PERCENTAGE';
+ const percentage = (currentMousePosition / resizedElementDimension) * 100;
- const boundaryMin = MINIMUM_SIZE;
- const boundaryMax =
- orientation === 'horizontal'
- ? width - MINIMUM_SIZE
- : height - MINIMUM_SIZE;
+ setResizeCSSVariable(resizeElement, orientation, percentage);
- const isMousePositionInBounds =
- currentMousePosition > boundaryMin &&
- currentMousePosition < boundaryMax;
-
- if (isMousePositionInBounds) {
- const resizedElementDimension =
- orientation === 'horizontal' ? width : height;
- const actionType =
- orientation === 'horizontal'
- ? 'ACTION_SET_HORIZONTAL_PERCENTAGE'
- : 'ACTION_SET_VERTICAL_PERCENTAGE';
- const percentage =
- (currentMousePosition / resizedElementDimension) * 100;
-
- setResizeCSSVariable(resizeElement, orientation, percentage);
-
- dispatch({
- type: actionType,
- payload: currentMousePosition / resizedElementDimension,
- });
- }
- };
- }
+ dispatch({
+ type: actionType,
+ payload: currentMousePosition / resizedElementDimension,
+ });
+ }
+ };
return (
-
+
@@ -193,18 +190,12 @@ function initResizeState(): ResizeState {
return {
horizontalPercentage,
- isResizing: false,
verticalPercentage,
};
}
function resizeReducer(state: ResizeState, action: ResizeAction): ResizeState {
switch (action.type) {
- case 'ACTION_SET_IS_RESIZING':
- return {
- ...state,
- isResizing: action.payload,
- };
case 'ACTION_SET_HORIZONTAL_PERCENTAGE':
return {
...state,
diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementHooksTree.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementHooksTree.js
index c782199122..44950acddf 100644
--- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementHooksTree.js
+++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementHooksTree.js
@@ -20,7 +20,7 @@ import {getMetaValueLabel, serializeHooksForCopy} from '../utils';
import Store from '../../store';
import styles from './InspectedElementHooksTree.css';
import {meta} from '../../../hydration';
-import {getHookSourceLocationKey} from 'react-devtools-shared/src/hookNamesCache';
+import {getHookSourceLocationKey} from 'react-devtools-shared/src/hookSourceLocation';
import HookNamesModuleLoaderContext from 'react-devtools-shared/src/devtools/views/Components/HookNamesModuleLoaderContext';
import isArray from 'react-devtools-shared/src/isArray';
diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSharedStyles.css b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSharedStyles.css
index c0d3c95bec..41e510b7c1 100644
--- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSharedStyles.css
+++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSharedStyles.css
@@ -75,11 +75,25 @@
color: var(--color-expand-collapse-toggle);
}
-.CollapsableHeaderTitle {
- flex: 1 1 auto;
+.CollapsableHeaderTitle, .CollapsableHeaderDescription, .CollapsableHeaderSeparator, .CollapsableHeaderFiller {
font-family: var(--font-family-monospace);
font-size: var(--font-size-monospace-normal);
text-align: left;
+ white-space: nowrap;
+}
+.CollapsableHeaderTitle {
+ flex: 0 1 auto;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.CollapsableHeaderSeparator {
+ flex: 0 0 auto;
+ white-space: pre;
+}
+
+.CollapsableHeaderFiller {
+ flex: 1 0 0;
}
.CollapsableContent {
@@ -108,4 +122,4 @@
.TimeBarSpanErrored {
background-color: var(--color-timespan-background-errored);
-}
\ No newline at end of file
+}
diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSuspendedBy.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSuspendedBy.js
index 93f4078a0e..9deddef14b 100644
--- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSuspendedBy.js
+++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSuspendedBy.js
@@ -38,6 +38,37 @@ type RowProps = {
maxTime: number,
};
+function getShortDescription(name: string, description: string): string {
+ const descMaxLength = 30 - name.length;
+ if (descMaxLength > 1) {
+ const l = description.length;
+ if (l > 0 && l <= descMaxLength) {
+ // We can fit the full description
+ return description;
+ } else if (
+ description.startsWith('http://') ||
+ description.startsWith('https://') ||
+ description.startsWith('/')
+ ) {
+ // Looks like a URL. Let's see if we can extract something shorter.
+ // We don't have to do a full parse so let's try something cheaper.
+ let queryIdx = description.indexOf('?');
+ if (queryIdx === -1) {
+ queryIdx = description.length;
+ }
+ if (description.charCodeAt(queryIdx - 1) === 47 /* "/" */) {
+ // Ends with slash. Look before that.
+ queryIdx--;
+ }
+ const slashIdx = description.lastIndexOf('/', queryIdx - 1);
+ // This may now be either the file name or the host.
+ // Include the slash to make it more obvious what we trimmed.
+ return '…' + description.slice(slashIdx, queryIdx);
+ }
+ }
+ return '';
+}
+
function SuspendedByRow({
bridge,
element,
@@ -50,6 +81,9 @@ function SuspendedByRow({
}: RowProps) {
const [isOpen, setIsOpen] = useState(false);
const name = asyncInfo.awaited.name;
+ const description = asyncInfo.awaited.description;
+ const longName = description === '' ? name : name + ' (' + description + ')';
+ const shortDescription = getShortDescription(name, description);
let stack;
let owner;
if (asyncInfo.stack === null || asyncInfo.stack.length === 0) {
@@ -83,12 +117,22 @@ function SuspendedByRow({