Store Test
Store Test
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');
act(() =>
legacyRender(<Component count={4} />, document.createElement('div')),
);
expect(store).toMatchInlineSnapshot(`
[root]
<Component>
`);
expect(store.roots).toHaveLength(1);
act(() =>
legacyRender(
<>
<React.Suspense fallback="Loading...">
<Owner />
</React.Suspense>
<Parent />
</>,
container,
),
);
expect(store).toMatchInlineSnapshot(`
[root]
<Suspense>
▾ <Parent>
<Child>
`);
});
expect(store.getElementAtIndex(0).isStrictModeNonCompliant).toBe(false);
expect(store.getElementAtIndex(1).isStrictModeNonCompliant).toBe(false);
});
expect(store.getElementAtIndex(0).isStrictModeNonCompliant).toBe(true);
expect(store.getElementAtIndex(1).isStrictModeNonCompliant).toBe(true);
});
describe('collapseNodesByDefault:false', () => {
beforeEach(() => {
store.collapseNodesByDefault = false;
});
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(() =>
legacyRender(<Grandparent count={4} />, document.createElement('div')),
);
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Grandparent>
▾ <Parent>
<Child>
▾ <Parent>
<Child>
`);
});
act(() => {
legacyRender(<Wrapper shouldSuspense={false} />, container);
});
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Wrapper>
<Component key="Outside">
▾ <Suspense>
<Component key="Inside">
`);
});
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>
);
act(() => {
root.render(<Wrapper shouldSuspense={false} />);
});
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Wrapper>
▾ <SuspenseList>
<Component key="A">
▾ <Suspense>
<Component key="B">
<Component key="C">
`);
});
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">
`);
describe('collapseNodesByDefault:true', () => {
beforeEach(() => {
store.collapseNodesByDefault = true;
});
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(() => {
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(() =>
legacyRender(<Grandparent count={4} />, document.createElement('div')),
);
expect(store).toMatchInlineSnapshot(`
[root]
▸ <Grandparent>
`);
act(() => {
legacyRender(<Wrapper shouldSuspense={false} />, container);
});
expect(store).toMatchInlineSnapshot(`
[root]
▾ <Wrapper>
<Component key="Outside">
▾ <Suspense>
<Component key="Inside">
`);
});
act(() =>
legacyRender(<Grandparent count={2} />, document.createElement('div')),
);
expect(store).toMatchInlineSnapshot(`
[root]
▸ <Grandparent>
`);
act(() =>
legacyRender(
<Wrapper forwardedRef={ref} />,
document.createElement('div'),
),
);
expect(store).toMatchInlineSnapshot(`
[root]
▸ <Wrapper>
`);
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(() =>
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;
});
act(() => {
legacyRender(<Grandparent />, document.createElement('div'));
legacyRender(<Grandparent />, document.createElement('div'));
});
act(() =>
legacyRender(
<React.Fragment>
<Grandparent />
<Grandparent />
</React.Fragment>,
document.createElement('div'),
),
);
act(() => {
legacyRender(
<React.Fragment>
<Grandparent />
<Grandparent />
</React.Fragment>,
document.createElement('div'),
);
legacyRender(
<React.Fragment>
<Grandparent />
<Grandparent />
</React.Fragment>,
document.createElement('div'),
);
});
expect(store.rootSupportsBasicProfiling).toBe(false);
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);
await Promise.resolve();
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};
}
let LazyComponent;
beforeEach(() => {
LazyComponent = React.lazy(() => fakeImport(LazyInnerComponent));
});
expect(store).toMatchInlineSnapshot(`
[root]
▾ <App>
<Suspense>
`);
await Promise.resolve();
expect(store).toMatchInlineSnapshot(`
[root]
▾ <App>
▾ <Suspense>
<LazyInnerComponent>
`);
expect(store).toMatchInlineSnapshot(`
[root]
<App>
`);
});
expect(store).toMatchInlineSnapshot(`
[root]
▾ <App>
<Suspense>
`);
await Promise.resolve();
expect(store).toMatchInlineSnapshot(`
[root]
▾ <App>
▾ <Suspense>
<LazyInnerComponent>
`);
expect(store).toMatchInlineSnapshot(`
[root]
<App>
`);
});
expect(store).toMatchInlineSnapshot(`
[root]
▾ <App>
<Suspense>
`);
expect(store).toMatchInlineSnapshot(`
[root]
<App>
`);
});
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>
`);
});
});
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> ✕⚠
`);
});
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> ✕⚠
`);
});
withErrorsOrWarningsIgnored(['test-only:'], () => {
act(() => {
legacyRender(<Example />, container);
}, false);
});
flushPendingBridgeOperations();
expect(store).toMatchInlineSnapshot(`
[root]
<Example>
`);
function Noop() {
return null;
}
withErrorsOrWarningsIgnored(['test-only:'], () => {
act(() => {
legacyRender(
<>
<Example />
</>,
container,
);
}, false);
flushPendingBridgeOperations();
expect(store).toMatchInlineSnapshot(`
[root]
<Example>
`);
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>
`);
});
expect(store).toMatchInlineSnapshot(`
✕ 2, ⚠ 2
[root]
<Example> ✕⚠
<Example> ✕⚠
`);
const {
clearErrorsAndWarnings,
} = require('react-devtools-shared/src/backendAPI');
clearErrorsAndWarnings({bridge, store});
expect(store).toMatchInlineSnapshot(`
[root]
<Example>
<Example>
`);
});
expect(store).toMatchInlineSnapshot(`
✕ 2, ⚠ 2
[root]
<Example> ✕⚠
<Example> ✕⚠
`);
const {
clearWarningsForElement,
} = require('react-devtools-shared/src/backendAPI');
clearWarningsForElement({bridge, id, rendererID});
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 {
clearErrorsForElement,
} = require('react-devtools-shared/src/backendAPI');
clearErrorsForElement({bridge, id, rendererID});
expect(store).toMatchInlineSnapshot(`
✕ 1, ⚠ 2
[root]
<Example> ✕⚠
<Example> ⚠
`);
});
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);
});
function App({renderA}) {
return (
<React.Suspense>
{renderA ? <LazyChildA /> : <LazyChildB />}
</React.Suspense>
);
}
expect(store).toMatchInlineSnapshot(`
[root]
▾ <App>
▾ <Suspense>
<ChildA>
`);
expect(store).toMatchInlineSnapshot(`
[root]
▾ <App>
▾ <Suspense>
<ChildB>
`);
});
});
});