0% found this document useful (0 votes)
32 views36 pages

Store Test

The document describes tests for the React DevTools store. It tests various features of the store including collapsing nodes, strict mode compliance, mounting order, and displaying Suspense components. The tests aim to ensure the store properly handles updates, multiple roots, and nested components.

Uploaded by

mahoraga
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
32 views36 pages

Store Test

The document describes tests for the React DevTools store. It tests various features of the store including collapsing nodes, strict mode compliance, mounting order, and displaying Suspense components. The tests aim to ensure the store properly handles updates, multiple roots, and nested components.

Uploaded by

mahoraga
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 36

/**

* Copyright (c) Meta Platforms, Inc. and affiliates.


*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

describe('Store', () => {
let React;
let ReactDOM;
let ReactDOMClient;
let agent;
let act;
let actAsync;
let bridge;
let getRendererID;
let legacyRender;
let store;
let withErrorsOrWarningsIgnored;

beforeEach(() => {
agent = global.agent;
bridge = global.bridge;
store = global.store;

React = require('react');
ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');

const utils = require('./utils');


act = utils.act;
actAsync = utils.actAsync;
getRendererID = utils.getRendererID;
legacyRender = utils.legacyRender;
withErrorsOrWarningsIgnored = utils.withErrorsOrWarningsIgnored;
});

// @reactVersion >= 18.0


it('should not allow a root node to be collapsed', () => {
const Component = () => <div>Hi</div>;

act(() =>
legacyRender(<Component count={4} />, document.createElement('div')),
);
expect(store).toMatchInlineSnapshot(`
[root]
<Component>
`);

expect(store.roots).toHaveLength(1);

const rootID = store.roots[0];

expect(() => store.toggleIsCollapsed(rootID, true)).toThrow(


'Root nodes cannot be collapsed',
);
});
// @reactVersion >= 18.0
it('should properly handle a root with no visible nodes', () => {
const Root = ({children}) => children;

const container = document.createElement('div');

act(() => legacyRender(<Root>{null}</Root>, container));


expect(store).toMatchInlineSnapshot(`
[root]
<Root>
`);

act(() => legacyRender(<div />, container));


expect(store).toMatchInlineSnapshot(`[root]`);
});

// This test is not the same cause as what's reported on GitHub,


// but the resulting behavior (owner mounting after descendant) is the same.
// Thec ase below is admittedly contrived and relies on side effects.
// 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', () => {
const promise = new Promise(resolve => {});

let Dynamic = null;


const Owner = () => {
Dynamic = <Child />;
throw promise;
};
const Parent = () => {
return Dynamic;
};
const Child = () => null;

const container = document.createElement('div');

act(() =>
legacyRender(
<>
<React.Suspense fallback="Loading...">
<Owner />
</React.Suspense>
<Parent />
</>,
container,
),
);
expect(store).toMatchInlineSnapshot(`
[root]
<Suspense>
▾ <Parent>
<Child>
`);
});

// @reactVersion >= 18.0


it('should handle multibyte character strings', () => {
const Component = () => null;
Component.displayName = '🟩💜🔵';

const container = document.createElement('div');

act(() => legacyRender(<Component />, container));


expect(store).toMatchInlineSnapshot(`
[root]
<🟩💜🔵>
`);
});

describe('StrictMode compliance', () => {


it('should mark strict root elements as strict', () => {
const App = () => <Component />;
const Component = () => null;

const container = document.createElement('div');


const root = ReactDOMClient.createRoot(container, {
unstable_strictMode: true,
});
act(() => {
root.render(<App />);
});

expect(store.getElementAtIndex(0).isStrictModeNonCompliant).toBe(false);
expect(store.getElementAtIndex(1).isStrictModeNonCompliant).toBe(false);
});

// @reactVersion >= 18.0


it('should mark non strict root elements as not strict', () => {
const App = () => <Component />;
const Component = () => null;

const container = document.createElement('div');


const root = ReactDOMClient.createRoot(container);
act(() => {
root.render(<App />);
});

expect(store.getElementAtIndex(0).isStrictModeNonCompliant).toBe(true);
expect(store.getElementAtIndex(1).isStrictModeNonCompliant).toBe(true);
});

it('should mark StrictMode subtree elements as strict', () => {


const App = () => (
<React.StrictMode>
<Component />
</React.StrictMode>
);
const Component = () => null;

const container = document.createElement('div');


const root = ReactDOMClient.createRoot(container);
act(() => {
root.render(<App />);
});
expect(store.getElementAtIndex(0).isStrictModeNonCompliant).toBe(true);
expect(store.getElementAtIndex(1).isStrictModeNonCompliant).toBe(false);
});
});

describe('collapseNodesByDefault:false', () => {
beforeEach(() => {
store.collapseNodesByDefault = false;
});

// @reactVersion >= 18.0


it('should support mount and update operations', () => {
const Grandparent = ({count}) => (
<React.Fragment>
<Parent count={count} />
<Parent count={count} />
</React.Fragment>
);
const Parent = ({count}) =>
new Array(count).fill(true).map((_, index) => <Child key={index} />);
const Child = () => <div>Hi!</div>;

const container = document.createElement('div');

act(() => legacyRender(<Grandparent count={4} />, container));


expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▾ <Parent>
<Child key="0">
<Child key="1">
<Child key="2">
<Child key="3">
▾ <Parent>
<Child key="0">
<Child key="1">
<Child key="2">
<Child key="3">
`);

act(() => legacyRender(<Grandparent count={2} />, container));


expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▾ <Parent>
<Child key="0">
<Child key="1">
▾ <Parent>
<Child key="0">
<Child key="1">
`);

act(() => ReactDOM.unmountComponentAtNode(container));


expect(store).toMatchInlineSnapshot(``);
});

// @reactVersion >= 18.0


it('should support mount and update operations for multiple roots', () => {
const Parent = ({count}) =>
new Array(count).fill(true).map((_, index) => <Child key={index} />);
const Child = () => <div>Hi!</div>;

const containerA = document.createElement('div');


const containerB = document.createElement('div');

act(() => {
legacyRender(<Parent key="A" count={3} />, containerA);
legacyRender(<Parent key="B" count={2} />, containerB);
});
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Parent key="A">
<Child key="0">
<Child key="1">
<Child key="2">
[root]
▾ <Parent key="B">
<Child key="0">
<Child key="1">
`);

act(() => {
legacyRender(<Parent key="A" count={4} />, containerA);
legacyRender(<Parent key="B" count={1} />, containerB);
});
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Parent key="A">
<Child key="0">
<Child key="1">
<Child key="2">
<Child key="3">
[root]
▾ <Parent key="B">
<Child key="0">
`);

act(() => ReactDOM.unmountComponentAtNode(containerB));


expect(store).toMatchInlineSnapshot(`
[root]
▾ <Parent key="A">
<Child key="0">
<Child key="1">
<Child key="2">
<Child key="3">
`);

act(() => ReactDOM.unmountComponentAtNode(containerA));


expect(store).toMatchInlineSnapshot(``);
});

// @reactVersion >= 18.0


it('should filter DOM nodes from the store tree', () => {
const Grandparent = () => (
<div>
<div>
<Parent />
</div>
<Parent />
</div>
);
const Parent = () => (
<div>
<Child />
</div>
);
const Child = () => <div>Hi!</div>;

act(() =>
legacyRender(<Grandparent count={4} />, document.createElement('div')),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▾ <Parent>
<Child>
▾ <Parent>
<Child>
`);
});

// @reactVersion >= 18.0


it('should display Suspense nodes properly in various states', () => {
const Loading = () => <div>Loading...</div>;
const SuspendingComponent = () => {
throw new Promise(() => {});
};
const Component = () => {
return <div>Hello</div>;
};
const Wrapper = ({shouldSuspense}) => (
<React.Fragment>
<Component key="Outside" />
<React.Suspense fallback={<Loading />}>
{shouldSuspense ? (
<SuspendingComponent />
) : (
<Component key="Inside" />
)}
</React.Suspense>
</React.Fragment>
);

const container = document.createElement('div');


act(() => legacyRender(<Wrapper shouldSuspense={true} />, container));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Wrapper>
<Component key="Outside">
▾ <Suspense>
<Loading>
`);

act(() => {
legacyRender(<Wrapper shouldSuspense={false} />, container);
});
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Wrapper>
<Component key="Outside">
▾ <Suspense>
<Component key="Inside">
`);
});

// @reactVersion >= 18.0


it('should support nested Suspense nodes', () => {
const Component = () => null;
const Loading = () => <div>Loading...</div>;
const Never = () => {
throw new Promise(() => {});
};

const Wrapper = ({
suspendFirst = false,
suspendSecond = false,
suspendParent = false,
}) => (
<React.Fragment>
<Component key="Outside" />
<React.Suspense fallback={<Loading key="Parent Fallback" />}>
<Component key="Unrelated at Start" />
<React.Suspense fallback={<Loading key="Suspense 1 Fallback" />}>
{suspendFirst ? (
<Never />
) : (
<Component key="Suspense 1 Content" />
)}
</React.Suspense>
<React.Suspense fallback={<Loading key="Suspense 2 Fallback" />}>
{suspendSecond ? (
<Never />
) : (
<Component key="Suspense 2 Content" />
)}
</React.Suspense>
<React.Suspense fallback={<Loading key="Suspense 3 Fallback" />}>
<Never />
</React.Suspense>
{suspendParent && <Never />}
<Component key="Unrelated at End" />
</React.Suspense>
</React.Fragment>
);

const container = document.createElement('div');


act(() =>
legacyRender(
<Wrapper
suspendParent={false}
suspendFirst={false}
suspendSecond={false}
/>,
container,
),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Wrapper>
<Component key="Outside">
▾ <Suspense>
<Component key="Unrelated at Start">
▾ <Suspense>
<Component key="Suspense 1 Content">
▾ <Suspense>
<Component key="Suspense 2 Content">
▾ <Suspense>
<Loading key="Suspense 3 Fallback">
<Component key="Unrelated at End">
`);
act(() =>
legacyRender(
<Wrapper
suspendParent={false}
suspendFirst={true}
suspendSecond={false}
/>,
container,
),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Wrapper>
<Component key="Outside">
▾ <Suspense>
<Component key="Unrelated at Start">
▾ <Suspense>
<Loading key="Suspense 1 Fallback">
▾ <Suspense>
<Component key="Suspense 2 Content">
▾ <Suspense>
<Loading key="Suspense 3 Fallback">
<Component key="Unrelated at End">
`);
act(() =>
legacyRender(
<Wrapper
suspendParent={false}
suspendFirst={false}
suspendSecond={true}
/>,
container,
),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Wrapper>
<Component key="Outside">
▾ <Suspense>
<Component key="Unrelated at Start">
▾ <Suspense>
<Component key="Suspense 1 Content">
▾ <Suspense>
<Loading key="Suspense 2 Fallback">
▾ <Suspense>
<Loading key="Suspense 3 Fallback">
<Component key="Unrelated at End">
`);
act(() =>
legacyRender(
<Wrapper
suspendParent={false}
suspendFirst={true}
suspendSecond={false}
/>,
container,
),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Wrapper>
<Component key="Outside">
▾ <Suspense>
<Component key="Unrelated at Start">
▾ <Suspense>
<Loading key="Suspense 1 Fallback">
▾ <Suspense>
<Component key="Suspense 2 Content">
▾ <Suspense>
<Loading key="Suspense 3 Fallback">
<Component key="Unrelated at End">
`);
act(() =>
legacyRender(
<Wrapper
suspendParent={true}
suspendFirst={true}
suspendSecond={false}
/>,
container,
),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Wrapper>
<Component key="Outside">
▾ <Suspense>
<Loading key="Parent Fallback">
`);
act(() =>
legacyRender(
<Wrapper
suspendParent={false}
suspendFirst={true}
suspendSecond={true}
/>,
container,
),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Wrapper>
<Component key="Outside">
▾ <Suspense>
<Component key="Unrelated at Start">
▾ <Suspense>
<Loading key="Suspense 1 Fallback">
▾ <Suspense>
<Loading key="Suspense 2 Fallback">
▾ <Suspense>
<Loading key="Suspense 3 Fallback">
<Component key="Unrelated at End">
`);
act(() =>
legacyRender(
<Wrapper
suspendParent={false}
suspendFirst={false}
suspendSecond={false}
/>,
container,
),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Wrapper>
<Component key="Outside">
▾ <Suspense>
<Component key="Unrelated at Start">
▾ <Suspense>
<Component key="Suspense 1 Content">
▾ <Suspense>
<Component key="Suspense 2 Content">
▾ <Suspense>
<Loading key="Suspense 3 Fallback">
<Component key="Unrelated at End">
`);

const rendererID = getRendererID();


act(() =>
agent.overrideSuspense({
id: store.getElementIDAtIndex(4),
rendererID,
forceFallback: true,
}),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Wrapper>
<Component key="Outside">
▾ <Suspense>
<Component key="Unrelated at Start">
▾ <Suspense>
<Loading key="Suspense 1 Fallback">
▾ <Suspense>
<Component key="Suspense 2 Content">
▾ <Suspense>
<Loading key="Suspense 3 Fallback">
<Component key="Unrelated at End">
`);
act(() =>
agent.overrideSuspense({
id: store.getElementIDAtIndex(2),
rendererID,
forceFallback: true,
}),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Wrapper>
<Component key="Outside">
▾ <Suspense>
<Loading key="Parent Fallback">
`);
act(() =>
legacyRender(
<Wrapper
suspendParent={false}
suspendFirst={true}
suspendSecond={true}
/>,
container,
),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Wrapper>
<Component key="Outside">
▾ <Suspense>
<Loading key="Parent Fallback">
`);
act(() =>
agent.overrideSuspense({
id: store.getElementIDAtIndex(2),
rendererID,
forceFallback: false,
}),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Wrapper>
<Component key="Outside">
▾ <Suspense>
<Component key="Unrelated at Start">
▾ <Suspense>
<Loading key="Suspense 1 Fallback">
▾ <Suspense>
<Loading key="Suspense 2 Fallback">
▾ <Suspense>
<Loading key="Suspense 3 Fallback">
<Component key="Unrelated at End">
`);
act(() =>
agent.overrideSuspense({
id: store.getElementIDAtIndex(4),
rendererID,
forceFallback: false,
}),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Wrapper>
<Component key="Outside">
▾ <Suspense>
<Component key="Unrelated at Start">
▾ <Suspense>
<Loading key="Suspense 1 Fallback">
▾ <Suspense>
<Loading key="Suspense 2 Fallback">
▾ <Suspense>
<Loading key="Suspense 3 Fallback">
<Component key="Unrelated at End">
`);
act(() =>
legacyRender(
<Wrapper
suspendParent={false}
suspendFirst={false}
suspendSecond={false}
/>,
container,
),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Wrapper>
<Component key="Outside">
▾ <Suspense>
<Component key="Unrelated at Start">
▾ <Suspense>
<Component key="Suspense 1 Content">
▾ <Suspense>
<Component key="Suspense 2 Content">
▾ <Suspense>
<Loading key="Suspense 3 Fallback">
<Component key="Unrelated at End">
`);
});

it('should display a partially rendered SuspenseList', () => {


const Loading = () => <div>Loading...</div>;
const SuspendingComponent = () => {
throw new Promise(() => {});
};
const Component = () => {
return <div>Hello</div>;
};
const Wrapper = ({shouldSuspense}) => (
<React.Fragment>
<React.unstable_SuspenseList revealOrder="forwards" tail="collapsed">
<Component key="A" />
<React.Suspense fallback={<Loading />}>
{shouldSuspense ? <SuspendingComponent /> : <Component key="B" />}
</React.Suspense>
<Component key="C" />
</React.unstable_SuspenseList>
</React.Fragment>
);

const container = document.createElement('div');


const root = ReactDOMClient.createRoot(container);
act(() => {
root.render(<Wrapper shouldSuspense={true} />);
});
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Wrapper>
▾ <SuspenseList>
<Component key="A">
▾ <Suspense>
<Loading>
`);

act(() => {
root.render(<Wrapper shouldSuspense={false} />);
});
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Wrapper>
▾ <SuspenseList>
<Component key="A">
▾ <Suspense>
<Component key="B">
<Component key="C">
`);
});

// @reactVersion >= 18.0


it('should support collapsing parts of the tree', () => {
const Grandparent = ({count}) => (
<React.Fragment>
<Parent count={count} />
<Parent count={count} />
</React.Fragment>
);
const Parent = ({count}) =>
new Array(count).fill(true).map((_, index) => <Child key={index} />);
const Child = () => <div>Hi!</div>;

act(() =>
legacyRender(<Grandparent count={2} />, document.createElement('div')),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▾ <Parent>
<Child key="0">
<Child key="1">
▾ <Parent>
<Child key="0">
<Child key="1">
`);

const grandparentID = store.getElementIDAtIndex(0);


const parentOneID = store.getElementIDAtIndex(1);
const parentTwoID = store.getElementIDAtIndex(4);

act(() => store.toggleIsCollapsed(parentOneID, true));


expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▸ <Parent>
▾ <Parent>
<Child key="0">
<Child key="1">
`);

act(() => store.toggleIsCollapsed(parentTwoID, true));


expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▸ <Parent>
▸ <Parent>
`);

act(() => store.toggleIsCollapsed(parentOneID, false));


expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▾ <Parent>
<Child key="0">
<Child key="1">
▸ <Parent>
`);

act(() => store.toggleIsCollapsed(grandparentID, true));


expect(store).toMatchInlineSnapshot(`
[root]
▸ <Grandparent>
`);

act(() => store.toggleIsCollapsed(grandparentID, false));


expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▾ <Parent>
<Child key="0">
<Child key="1">
▸ <Parent>
`);
});

// @reactVersion >= 18.0


it('should support reordering of children', () => {
const Root = ({children}) => children;
const Component = () => null;

const Foo = () => [<Component key="0" />];


const Bar = () => [<Component key="0" />, <Component key="1" />];
const foo = <Foo key="foo" />;
const bar = <Bar key="bar" />;

const container = document.createElement('div');

act(() => legacyRender(<Root>{[foo, bar]}</Root>, container));


expect(store).toMatchInlineSnapshot(`
[root]
▾ <Root>
▾ <Foo key="foo">
<Component key="0">
▾ <Bar key="bar">
<Component key="0">
<Component key="1">
`);

act(() => legacyRender(<Root>{[bar, foo]}</Root>, container));


expect(store).toMatchInlineSnapshot(`
[root]
▾ <Root>
▾ <Bar key="bar">
<Component key="0">
<Component key="1">
▾ <Foo key="foo">
<Component key="0">
`);

act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), true));


expect(store).toMatchInlineSnapshot(`
[root]
▸ <Root>
`);

act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), false));


expect(store).toMatchInlineSnapshot(`
[root]
▾ <Root>
▾ <Bar key="bar">
<Component key="0">
<Component key="1">
▾ <Foo key="foo">
<Component key="0">
`);
});
});

describe('collapseNodesByDefault:true', () => {
beforeEach(() => {
store.collapseNodesByDefault = true;
});

// @reactVersion >= 18.0


it('should support mount and update operations', () => {
const Parent = ({count}) =>
new Array(count).fill(true).map((_, index) => <Child key={index} />);
const Child = () => <div>Hi!</div>;

const container = document.createElement('div');

act(() =>
legacyRender(
<React.Fragment>
<Parent count={1} />
<Parent count={3} />
</React.Fragment>,
container,
),
);
expect(store).toMatchInlineSnapshot(`
[root]
▸ <Parent>
▸ <Parent>
`);

act(() =>
legacyRender(
<React.Fragment>
<Parent count={2} />
<Parent count={1} />
</React.Fragment>,
container,
),
);
expect(store).toMatchInlineSnapshot(`
[root]
▸ <Parent>
▸ <Parent>
`);

act(() => ReactDOM.unmountComponentAtNode(container));


expect(store).toMatchInlineSnapshot(``);
});

// @reactVersion >= 18.0


it('should support mount and update operations for multiple roots', () => {
const Parent = ({count}) =>
new Array(count).fill(true).map((_, index) => <Child key={index} />);
const Child = () => <div>Hi!</div>;

const containerA = document.createElement('div');


const containerB = document.createElement('div');

act(() => {
legacyRender(<Parent key="A" count={3} />, containerA);
legacyRender(<Parent key="B" count={2} />, containerB);
});
expect(store).toMatchInlineSnapshot(`
[root]
▸ <Parent key="A">
[root]
▸ <Parent key="B">
`);

act(() => {
legacyRender(<Parent key="A" count={4} />, containerA);
legacyRender(<Parent key="B" count={1} />, containerB);
});
expect(store).toMatchInlineSnapshot(`
[root]
▸ <Parent key="A">
[root]
▸ <Parent key="B">
`);

act(() => ReactDOM.unmountComponentAtNode(containerB));


expect(store).toMatchInlineSnapshot(`
[root]
▸ <Parent key="A">
`);

act(() => ReactDOM.unmountComponentAtNode(containerA));


expect(store).toMatchInlineSnapshot(``);
});

// @reactVersion >= 18.0


it('should filter DOM nodes from the store tree', () => {
const Grandparent = () => (
<div>
<div>
<Parent />
</div>
<Parent />
</div>
);
const Parent = () => (
<div>
<Child />
</div>
);
const Child = () => <div>Hi!</div>;

act(() =>
legacyRender(<Grandparent count={4} />, document.createElement('div')),
);
expect(store).toMatchInlineSnapshot(`
[root]
▸ <Grandparent>
`);

act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), false));


expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▸ <Parent>
▸ <Parent>
`);

act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(1), false));


expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▾ <Parent>
<Child>
▸ <Parent>
`);
});

// @reactVersion >= 18.0


it('should display Suspense nodes properly in various states', () => {
const Loading = () => <div>Loading...</div>;
const SuspendingComponent = () => {
throw new Promise(() => {});
};
const Component = () => {
return <div>Hello</div>;
};
const Wrapper = ({shouldSuspense}) => (
<React.Fragment>
<Component key="Outside" />
<React.Suspense fallback={<Loading />}>
{shouldSuspense ? (
<SuspendingComponent />
) : (
<Component key="Inside" />
)}
</React.Suspense>
</React.Fragment>
);

const container = document.createElement('div');


act(() => legacyRender(<Wrapper shouldSuspense={true} />, container));
expect(store).toMatchInlineSnapshot(`
[root]
▸ <Wrapper>
`);

// 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));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Wrapper>
<Component key="Outside">
▾ <Suspense>
<Loading>
`);

act(() => {
legacyRender(<Wrapper shouldSuspense={false} />, container);
});
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Wrapper>
<Component key="Outside">
▾ <Suspense>
<Component key="Inside">
`);
});

// @reactVersion >= 18.0


it('should support expanding parts of the tree', () => {
const Grandparent = ({count}) => (
<React.Fragment>
<Parent count={count} />
<Parent count={count} />
</React.Fragment>
);
const Parent = ({count}) =>
new Array(count).fill(true).map((_, index) => <Child key={index} />);
const Child = () => <div>Hi!</div>;

act(() =>
legacyRender(<Grandparent count={2} />, document.createElement('div')),
);
expect(store).toMatchInlineSnapshot(`
[root]
▸ <Grandparent>
`);

const grandparentID = store.getElementIDAtIndex(0);

act(() => store.toggleIsCollapsed(grandparentID, false));


expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▸ <Parent>
▸ <Parent>
`);

const parentOneID = store.getElementIDAtIndex(1);


const parentTwoID = store.getElementIDAtIndex(2);

act(() => store.toggleIsCollapsed(parentOneID, false));


expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▾ <Parent>
<Child key="0">
<Child key="1">
▸ <Parent>
`);

act(() => store.toggleIsCollapsed(parentTwoID, false));


expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▾ <Parent>
<Child key="0">
<Child key="1">
▾ <Parent>
<Child key="0">
<Child key="1">
`);

act(() => store.toggleIsCollapsed(parentOneID, true));


expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▸ <Parent>
▾ <Parent>
<Child key="0">
<Child key="1">
`);

act(() => store.toggleIsCollapsed(parentTwoID, true));


expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▸ <Parent>
▸ <Parent>
`);

act(() => store.toggleIsCollapsed(grandparentID, true));


expect(store).toMatchInlineSnapshot(`
[root]
▸ <Grandparent>
`);
});

// @reactVersion >= 18.0


it('should support expanding deep parts of the tree', () => {
const Wrapper = ({forwardedRef}) => (
<Nested depth={3} forwardedRef={forwardedRef} />
);
const Nested = ({depth, forwardedRef}) =>
depth > 0 ? (
<Nested depth={depth - 1} forwardedRef={forwardedRef} />
) : (
<div ref={forwardedRef} />
);

const ref = React.createRef();

act(() =>
legacyRender(
<Wrapper forwardedRef={ref} />,
document.createElement('div'),
),
);
expect(store).toMatchInlineSnapshot(`
[root]
▸ <Wrapper>
`);

const deepestedNodeID = agent.getIDForNode(ref.current);

act(() => store.toggleIsCollapsed(deepestedNodeID, false));


expect(store).toMatchInlineSnapshot(`
[root]
▾ <Wrapper>
▾ <Nested>
▾ <Nested>
▾ <Nested>
<Nested>
`);

const rootID = store.getElementIDAtIndex(0);

act(() => store.toggleIsCollapsed(rootID, true));


expect(store).toMatchInlineSnapshot(`
[root]
▸ <Wrapper>
`);

act(() => store.toggleIsCollapsed(rootID, false));


expect(store).toMatchInlineSnapshot(`
[root]
▾ <Wrapper>
▾ <Nested>
▾ <Nested>
▾ <Nested>
<Nested>
`);
const id = store.getElementIDAtIndex(1);

act(() => store.toggleIsCollapsed(id, true));


expect(store).toMatchInlineSnapshot(`
[root]
▾ <Wrapper>
▸ <Nested>
`);

act(() => store.toggleIsCollapsed(id, false));


expect(store).toMatchInlineSnapshot(`
[root]
▾ <Wrapper>
▾ <Nested>
▾ <Nested>
▾ <Nested>
<Nested>
`);
});

// @reactVersion >= 18.0


it('should support reordering of children', () => {
const Root = ({children}) => children;
const Component = () => null;

const Foo = () => [<Component key="0" />];


const Bar = () => [<Component key="0" />, <Component key="1" />];
const foo = <Foo key="foo" />;
const bar = <Bar key="bar" />;

const container = document.createElement('div');

act(() => legacyRender(<Root>{[foo, bar]}</Root>, container));


expect(store).toMatchInlineSnapshot(`
[root]
▸ <Root>
`);

act(() => legacyRender(<Root>{[bar, foo]}</Root>, container));


expect(store).toMatchInlineSnapshot(`
[root]
▸ <Root>
`);

act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), false));


expect(store).toMatchInlineSnapshot(`
[root]
▾ <Root>
▸ <Bar key="bar">
▸ <Foo key="foo">
`);

act(() => {
store.toggleIsCollapsed(store.getElementIDAtIndex(2), false);
store.toggleIsCollapsed(store.getElementIDAtIndex(1), false);
});
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Root>
▾ <Bar key="bar">
<Component key="0">
<Component key="1">
▾ <Foo key="foo">
<Component key="0">
`);

act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), true));


expect(store).toMatchInlineSnapshot(`
[root]
▸ <Root>
`);
});

// @reactVersion >= 18.0


it('should not add new nodes when suspense is toggled', () => {
const SuspenseTree = () => {
return (
<React.Suspense fallback={<Fallback>Loading outer</Fallback>}>
<Parent />
</React.Suspense>
);
};

const Fallback = () => null;


const Parent = () => <Child />;
const Child = () => null;

act(() => legacyRender(<SuspenseTree />, document.createElement('div')));


expect(store).toMatchInlineSnapshot(`
[root]
▸ <SuspenseTree>
`);

act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), false));


act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(1), false));
expect(store).toMatchInlineSnapshot(`
[root]
▾ <SuspenseTree>
▾ <Suspense>
▸ <Parent>
`);

const rendererID = getRendererID();


const suspenseID = store.getElementIDAtIndex(1);

act(() =>
agent.overrideSuspense({
id: suspenseID,
rendererID,
forceFallback: true,
}),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾ <SuspenseTree>
▾ <Suspense>
<Fallback>
`);
act(() =>
agent.overrideSuspense({
id: suspenseID,
rendererID,
forceFallback: false,
}),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾ <SuspenseTree>
▾ <Suspense>
▸ <Parent>
`);
});
});

describe('getIndexOfElementID', () => {
beforeEach(() => {
store.collapseNodesByDefault = false;
});

// @reactVersion >= 18.0


it('should support a single root with a single child', () => {
const Grandparent = () => (
<React.Fragment>
<Parent />
<Parent />
</React.Fragment>
);
const Parent = () => <Child />;
const Child = () => null;

act(() => legacyRender(<Grandparent />, document.createElement('div')));

for (let i = 0; i < store.numElements; i++) {


expect(store.getIndexOfElementID(store.getElementIDAtIndex(i))).toBe(i);
}
});

// @reactVersion >= 18.0


it('should support multiple roots with one children each', () => {
const Grandparent = () => <Parent />;
const Parent = () => <Child />;
const Child = () => null;

act(() => {
legacyRender(<Grandparent />, document.createElement('div'));
legacyRender(<Grandparent />, document.createElement('div'));
});

for (let i = 0; i < store.numElements; i++) {


expect(store.getIndexOfElementID(store.getElementIDAtIndex(i))).toBe(i);
}
});

// @reactVersion >= 18.0


it('should support a single root with multiple top level children', () => {
const Grandparent = () => <Parent />;
const Parent = () => <Child />;
const Child = () => null;

act(() =>
legacyRender(
<React.Fragment>
<Grandparent />
<Grandparent />
</React.Fragment>,
document.createElement('div'),
),
);

for (let i = 0; i < store.numElements; i++) {


expect(store.getIndexOfElementID(store.getElementIDAtIndex(i))).toBe(i);
}
});

// @reactVersion >= 18.0


it('should support multiple roots with multiple top level children', () => {
const Grandparent = () => <Parent />;
const Parent = () => <Child />;
const Child = () => null;

act(() => {
legacyRender(
<React.Fragment>
<Grandparent />
<Grandparent />
</React.Fragment>,
document.createElement('div'),
);
legacyRender(
<React.Fragment>
<Grandparent />
<Grandparent />
</React.Fragment>,
document.createElement('div'),
);
});

for (let i = 0; i < store.numElements; i++) {


expect(store.getIndexOfElementID(store.getElementIDAtIndex(i))).toBe(i);
}
});
});

// @reactVersion >= 18.0


it('detects and updates profiling support based on the attached roots', () => {
const Component = () => null;

const containerA = document.createElement('div');


const containerB = document.createElement('div');

expect(store.rootSupportsBasicProfiling).toBe(false);

act(() => legacyRender(<Component />, containerA));


expect(store.rootSupportsBasicProfiling).toBe(true);
act(() => legacyRender(<Component />, containerB));
act(() => ReactDOM.unmountComponentAtNode(containerA));
expect(store.rootSupportsBasicProfiling).toBe(true);

act(() => ReactDOM.unmountComponentAtNode(containerB));


expect(store.rootSupportsBasicProfiling).toBe(false);
});

// @reactVersion >= 18.0


it('should properly serialize non-string key values', () => {
const Child = () => null;

// Bypass React element's automatic stringifying of keys intentionally.


// This is pretty hacky.
const fauxElement = Object.assign({}, <Child />, {key: 123});

act(() => legacyRender([fauxElement], document.createElement('div')));


expect(store).toMatchInlineSnapshot(`
[root]
<Child key="123">
`);
});

it('should show the right display names for special component types', async () =>
{
const MyComponent = (props, ref) => null;
const ForwardRefComponent = React.forwardRef(MyComponent);
const MyComponent2 = (props, ref) => null;
const ForwardRefComponentWithAnonymousFunction = React.forwardRef(() => (
<MyComponent2 />
));
const MyComponent3 = (props, ref) => null;
const ForwardRefComponentWithCustomDisplayName =
React.forwardRef(MyComponent3);
ForwardRefComponentWithCustomDisplayName.displayName = 'Custom';
const MyComponent4 = (props, ref) => null;
const MemoComponent = React.memo(MyComponent4);
const MemoForwardRefComponent = React.memo(ForwardRefComponent);

const FakeHigherOrderComponent = () => null;


FakeHigherOrderComponent.displayName = 'withFoo(withBar(Baz))';

const MemoizedFakeHigherOrderComponent = React.memo(


FakeHigherOrderComponent,
);
const ForwardRefFakeHigherOrderComponent = React.forwardRef(
FakeHigherOrderComponent,
);

const MemoizedFakeHigherOrderComponentWithDisplayNameOverride = React.memo(


FakeHigherOrderComponent,
);
MemoizedFakeHigherOrderComponentWithDisplayNameOverride.displayName =
'memoRefOverride';
const ForwardRefFakeHigherOrderComponentWithDisplayNameOverride =
React.forwardRef(FakeHigherOrderComponent);
ForwardRefFakeHigherOrderComponentWithDisplayNameOverride.displayName =
'forwardRefOverride';
const App = () => (
<React.Fragment>
<MyComponent />
<ForwardRefComponent />
<ForwardRefComponentWithAnonymousFunction />
<ForwardRefComponentWithCustomDisplayName />
<MemoComponent />
<MemoForwardRefComponent />
<FakeHigherOrderComponent />
<MemoizedFakeHigherOrderComponent />
<ForwardRefFakeHigherOrderComponent />
<React.unstable_Cache />
<MemoizedFakeHigherOrderComponentWithDisplayNameOverride />
<ForwardRefFakeHigherOrderComponentWithDisplayNameOverride />
</React.Fragment>
);

const container = document.createElement('div');

// Render once to start fetching the lazy component


act(() => legacyRender(<App />, container));

await Promise.resolve();

// Render again after it resolves


act(() => legacyRender(<App />, container));

expect(store).toMatchInlineSnapshot(`
[root]
▾ <App>
<MyComponent>
<MyComponent> [ForwardRef]
▾ <Anonymous> [ForwardRef]
<MyComponent2>
<Custom>
<MyComponent4> [Memo]
▾ <MyComponent> [Memo]
<MyComponent> [ForwardRef]
<Baz> [withFoo][withBar]
<Baz> [Memo][withFoo][withBar]
<Baz> [ForwardRef][withFoo][withBar]
<Cache>
<memoRefOverride>
<forwardRefOverride>
`);
});

describe('Lazy', () => {
async function fakeImport(result) {
return {default: result};
}

const LazyInnerComponent = () => null;

const App = ({renderChildren}) => {


if (renderChildren) {
return (
<React.Suspense fallback="Loading...">
<LazyComponent />
</React.Suspense>
);
} else {
return null;
}
};

let LazyComponent;
beforeEach(() => {
LazyComponent = React.lazy(() => fakeImport(LazyInnerComponent));
});

// @reactVersion >= 18.0


it('should support Lazy components (legacy render)', async () => {
const container = document.createElement('div');

// Render once to start fetching the lazy component


act(() => legacyRender(<App renderChildren={true} />, container));

expect(store).toMatchInlineSnapshot(`
[root]
▾ <App>
<Suspense>
`);

await Promise.resolve();

// Render again after it resolves


act(() => legacyRender(<App renderChildren={true} />, container));

expect(store).toMatchInlineSnapshot(`
[root]
▾ <App>
▾ <Suspense>
<LazyInnerComponent>
`);

// Render again to unmount it


act(() => legacyRender(<App renderChildren={false} />, container));

expect(store).toMatchInlineSnapshot(`
[root]
<App>
`);
});

// @reactVersion >= 18.0


it('should support Lazy components in (createRoot)', async () => {
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);

// Render once to start fetching the lazy component


act(() => root.render(<App renderChildren={true} />));

expect(store).toMatchInlineSnapshot(`
[root]
▾ <App>
<Suspense>
`);
await Promise.resolve();

// Render again after it resolves


act(() => root.render(<App renderChildren={true} />));

expect(store).toMatchInlineSnapshot(`
[root]
▾ <App>
▾ <Suspense>
<LazyInnerComponent>
`);

// Render again to unmount it


act(() => root.render(<App renderChildren={false} />));

expect(store).toMatchInlineSnapshot(`
[root]
<App>
`);
});

// @reactVersion >= 18.0


it('should support Lazy components that are unmounted before they finish
loading (legacy render)', async () => {
const container = document.createElement('div');

// Render once to start fetching the lazy component


act(() => legacyRender(<App renderChildren={true} />, container));

expect(store).toMatchInlineSnapshot(`
[root]
▾ <App>
<Suspense>
`);

// Render again to unmount it before it finishes loading


act(() => legacyRender(<App renderChildren={false} />, container));

expect(store).toMatchInlineSnapshot(`
[root]
<App>
`);
});

// @reactVersion >= 18.0


it('should support Lazy components that are unmounted before they finish
loading in (createRoot)', async () => {
const container = document.createElement('div');
const root = ReactDOMClient.createRoot(container);

// Render once to start fetching the lazy component


act(() => root.render(<App renderChildren={true} />));

expect(store).toMatchInlineSnapshot(`
[root]
▾ <App>
<Suspense>
`);
// Render again to unmount it before it finishes loading
act(() => root.render(<App renderChildren={false} />));

expect(store).toMatchInlineSnapshot(`
[root]
<App>
`);
});
});

describe('inline errors and warnings', () => {


// @reactVersion >= 18.0
it('during render are counted', () => {
function Example() {
console.error('test-only: render error');
console.warn('test-only: render warning');
return null;
}
const container = document.createElement('div');

withErrorsOrWarningsIgnored(['test-only:'], () => {
act(() => legacyRender(<Example />, container));
});

expect(store).toMatchInlineSnapshot(`
✕ 1, ⚠ 1
[root]
<Example> ✕⚠
`);

withErrorsOrWarningsIgnored(['test-only:'], () => {
act(() => legacyRender(<Example rerender={1} />, container));
});

expect(store).toMatchInlineSnapshot(`
✕ 2, ⚠ 2
[root]
<Example> ✕⚠
`);
});

// @reactVersion >= 18.0


it('during layout get counted', () => {
function Example() {
React.useLayoutEffect(() => {
console.error('test-only: layout error');
console.warn('test-only: layout warning');
});
return null;
}
const container = document.createElement('div');

withErrorsOrWarningsIgnored(['test-only:'], () => {
act(() => legacyRender(<Example />, container));
});

expect(store).toMatchInlineSnapshot(`
✕ 1, ⚠ 1
[root]
<Example> ✕⚠
`);

withErrorsOrWarningsIgnored(['test-only:'], () => {
act(() => legacyRender(<Example rerender={1} />, container));
});

expect(store).toMatchInlineSnapshot(`
✕ 2, ⚠ 2
[root]
<Example> ✕⚠
`);
});

describe('during passive effects', () => {


function flushPendingBridgeOperations() {
jest.runOnlyPendingTimers();
}

// Gross abstraction around pending passive warning/error delay.


function flushPendingPassiveErrorAndWarningCounts() {
jest.advanceTimersByTime(1000);
}

// @reactVersion >= 18.0


it('are counted (after a delay)', () => {
function Example() {
React.useEffect(() => {
console.error('test-only: passive error');
console.warn('test-only: passive warning');
});
return null;
}
const container = document.createElement('div');

withErrorsOrWarningsIgnored(['test-only:'], () => {
act(() => {
legacyRender(<Example />, container);
}, false);
});
flushPendingBridgeOperations();
expect(store).toMatchInlineSnapshot(`
[root]
<Example>
`);

// After a delay, passive effects should be committed as well


act(flushPendingPassiveErrorAndWarningCounts, false);
expect(store).toMatchInlineSnapshot(`
✕ 1, ⚠ 1
[root]
<Example> ✕⚠
`);

act(() => ReactDOM.unmountComponentAtNode(container));


expect(store).toMatchInlineSnapshot(``);
});
// @reactVersion >= 18.0
it('are flushed early when there is a new commit', () => {
function Example() {
React.useEffect(() => {
console.error('test-only: passive error');
console.warn('test-only: passive warning');
});
return null;
}

function Noop() {
return null;
}

const container = document.createElement('div');

withErrorsOrWarningsIgnored(['test-only:'], () => {
act(() => {
legacyRender(
<>
<Example />
</>,
container,
);
}, false);
flushPendingBridgeOperations();
expect(store).toMatchInlineSnapshot(`
[root]
<Example>
`);

// Before warnings and errors have flushed, flush another commit.


act(() => {
legacyRender(
<>
<Example />
<Noop />
</>,
container,
);
}, false);
flushPendingBridgeOperations();
expect(store).toMatchInlineSnapshot(`
✕ 1, ⚠ 1
[root]
<Example> ✕⚠
<Noop>
`);
});

// After a delay, passive effects should be committed as well


act(flushPendingPassiveErrorAndWarningCounts, false);
expect(store).toMatchInlineSnapshot(`
✕ 2, ⚠ 2
[root]
<Example> ✕⚠
<Noop>
`);
act(() => ReactDOM.unmountComponentAtNode(container));
expect(store).toMatchInlineSnapshot(``);
});
});

// @reactVersion >= 18.0


it('from react get counted', () => {
const container = document.createElement('div');
function Example() {
return [<Child />];
}
function Child() {
return null;
}

withErrorsOrWarningsIgnored(
['Warning: Each child in a list should have a unique "key" prop'],
() => {
act(() => legacyRender(<Example />, container));
},
);

expect(store).toMatchInlineSnapshot(`
✕ 1, ⚠ 0
[root]
▾ <Example> ✕
<Child>
`);
});

// @reactVersion >= 18.0


it('can be cleared for the whole app', () => {
function Example() {
console.error('test-only: render error');
console.warn('test-only: render warning');
return null;
}
const container = document.createElement('div');
withErrorsOrWarningsIgnored(['test-only:'], () => {
act(() =>
legacyRender(
<React.Fragment>
<Example />
<Example />
</React.Fragment>,
container,
),
);
});

expect(store).toMatchInlineSnapshot(`
✕ 2, ⚠ 2
[root]
<Example> ✕⚠
<Example> ✕⚠
`);

const {
clearErrorsAndWarnings,
} = require('react-devtools-shared/src/backendAPI');
clearErrorsAndWarnings({bridge, store});

// flush events to the renderer


jest.runAllTimers();

expect(store).toMatchInlineSnapshot(`
[root]
<Example>
<Example>
`);
});

// @reactVersion >= 18.0


it('can be cleared for particular Fiber (only warnings)', () => {
function Example() {
console.error('test-only: render error');
console.warn('test-only: render warning');
return null;
}
const container = document.createElement('div');
withErrorsOrWarningsIgnored(['test-only:'], () => {
act(() =>
legacyRender(
<React.Fragment>
<Example />
<Example />
</React.Fragment>,
container,
),
);
});

expect(store).toMatchInlineSnapshot(`
✕ 2, ⚠ 2
[root]
<Example> ✕⚠
<Example> ✕⚠
`);

const id = ((store.getElementIDAtIndex(1): any): number);


const rendererID = store.getRendererIDForElement(id);

const {
clearWarningsForElement,
} = require('react-devtools-shared/src/backendAPI');
clearWarningsForElement({bridge, id, rendererID});

// Flush events to the renderer.


jest.runAllTimers();

expect(store).toMatchInlineSnapshot(`
✕ 2, ⚠ 1
[root]
<Example> ✕⚠
<Example> ✕
`);
});
// @reactVersion >= 18.0
it('can be cleared for a particular Fiber (only errors)', () => {
function Example() {
console.error('test-only: render error');
console.warn('test-only: render warning');
return null;
}
const container = document.createElement('div');
withErrorsOrWarningsIgnored(['test-only:'], () => {
act(() =>
legacyRender(
<React.Fragment>
<Example />
<Example />
</React.Fragment>,
container,
),
);
});

expect(store).toMatchInlineSnapshot(`
✕ 2, ⚠ 2
[root]
<Example> ✕⚠
<Example> ✕⚠
`);

const id = ((store.getElementIDAtIndex(1): any): number);


const rendererID = store.getRendererIDForElement(id);

const {
clearErrorsForElement,
} = require('react-devtools-shared/src/backendAPI');
clearErrorsForElement({bridge, id, rendererID});

// Flush events to the renderer.


jest.runAllTimers();

expect(store).toMatchInlineSnapshot(`
✕ 1, ⚠ 2
[root]
<Example> ✕⚠
<Example> ⚠
`);
});

// @reactVersion >= 18.0


it('are updated when fibers are removed from the tree', () => {
function ComponentWithWarning() {
console.warn('test-only: render warning');
return null;
}
function ComponentWithError() {
console.error('test-only: render error');
return null;
}
function ComponentWithWarningAndError() {
console.error('test-only: render error');
console.warn('test-only: render warning');
return null;
}
const container = document.createElement('div');
withErrorsOrWarningsIgnored(['test-only:'], () => {
act(() =>
legacyRender(
<React.Fragment>
<ComponentWithError />
<ComponentWithWarning />
<ComponentWithWarningAndError />
</React.Fragment>,
container,
),
);
});
expect(store).toMatchInlineSnapshot(`
✕ 2, ⚠ 2
[root]
<ComponentWithError> ✕
<ComponentWithWarning> ⚠
<ComponentWithWarningAndError> ✕⚠
`);

withErrorsOrWarningsIgnored(['test-only:'], () => {
act(() =>
legacyRender(
<React.Fragment>
<ComponentWithWarning />
<ComponentWithWarningAndError />
</React.Fragment>,
container,
),
);
});
expect(store).toMatchInlineSnapshot(`
✕ 1, ⚠ 2
[root]
<ComponentWithWarning> ⚠
<ComponentWithWarningAndError> ✕⚠
`);

withErrorsOrWarningsIgnored(['test-only:'], () => {
act(() =>
legacyRender(
<React.Fragment>
<ComponentWithWarning />
</React.Fragment>,
container,
),
);
});
expect(store).toMatchInlineSnapshot(`
✕ 0, ⚠ 2
[root]
<ComponentWithWarning> ⚠
`);

withErrorsOrWarningsIgnored(['test-only:'], () => {
act(() => legacyRender(<React.Fragment />, container));
});
expect(store).toMatchInlineSnapshot(`[root]`);
expect(store.errorCount).toBe(0);
expect(store.warningCount).toBe(0);
});

// Regression test for https://github.com/facebook/react/issues/23202


// @reactVersion >= 18.0
it('suspense boundary children should not double unmount and error', async ()
=> {
async function fakeImport(result) {
return {default: result};
}

const ChildA = () => null;


const ChildB = () => null;

const LazyChildA = React.lazy(() => fakeImport(ChildA));


const LazyChildB = React.lazy(() => fakeImport(ChildB));

function App({renderA}) {
return (
<React.Suspense>
{renderA ? <LazyChildA /> : <LazyChildB />}
</React.Suspense>
);
}

const container = document.createElement('div');


const root = ReactDOMClient.createRoot(container);
await actAsync(() => root.render(<App renderA={true} />));

expect(store).toMatchInlineSnapshot(`
[root]
▾ <App>
▾ <Suspense>
<ChildA>
`);

await actAsync(() => root.render(<App renderA={false} />));

expect(store).toMatchInlineSnapshot(`
[root]
▾ <App>
▾ <Suspense>
<ChildB>
`);
});
});
});

You might also like