/**
* Copyright (c) Facebook, Inc. and its 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 agent;
let act;
let bridge;
let getRendererID;
let store;
let withErrorsOrWarningsIgnored;
beforeEach(() => {
agent = global.agent;
bridge = global.bridge;
store = global.store;
React = require('react');
ReactDOM = require('react-dom');
const utils = require('./utils');
act = utils.act;
getRendererID = utils.getRendererID;
withErrorsOrWarningsIgnored = utils.withErrorsOrWarningsIgnored;
});
it('should not allow a root node to be collapsed', () => {
const Component = () =>
Hi
;
act(() =>
ReactDOM.render(, document.createElement('div')),
);
expect(store).toMatchSnapshot('1: mount');
expect(store.roots).toHaveLength(1);
const rootID = store.roots[0];
expect(() => store.toggleIsCollapsed(rootID, true)).toThrow(
'Root nodes cannot be collapsed',
);
});
it('should properly handle a root with no visible nodes', () => {
const Root = ({children}) => children;
const container = document.createElement('div');
act(() => ReactDOM.render({null}, container));
expect(store).toMatchSnapshot('1: mount');
act(() => ReactDOM.render(, container));
expect(store).toMatchSnapshot('2: add host nodes');
});
describe('collapseNodesByDefault:false', () => {
beforeEach(() => {
store.collapseNodesByDefault = false;
});
it('should support mount and update operations', () => {
const Grandparent = ({count}) => (
);
const Parent = ({count}) =>
new Array(count).fill(true).map((_, index) => );
const Child = () => Hi!
;
const container = document.createElement('div');
act(() => ReactDOM.render(, container));
expect(store).toMatchSnapshot('1: mount');
act(() => ReactDOM.render(, container));
expect(store).toMatchSnapshot('2: update');
act(() => ReactDOM.unmountComponentAtNode(container));
expect(store).toMatchSnapshot('3: unmount');
});
it('should support mount and update operations for multiple roots', () => {
const Parent = ({count}) =>
new Array(count).fill(true).map((_, index) => );
const Child = () => Hi!
;
const containerA = document.createElement('div');
const containerB = document.createElement('div');
act(() => {
ReactDOM.render(, containerA);
ReactDOM.render(, containerB);
});
expect(store).toMatchSnapshot('1: mount');
act(() => {
ReactDOM.render(, containerA);
ReactDOM.render(, containerB);
});
expect(store).toMatchSnapshot('2: update');
act(() => ReactDOM.unmountComponentAtNode(containerB));
expect(store).toMatchSnapshot('3: unmount B');
act(() => ReactDOM.unmountComponentAtNode(containerA));
expect(store).toMatchSnapshot('4: unmount A');
});
it('should filter DOM nodes from the store tree', () => {
const Grandparent = () => (
);
const Parent = () => (
);
const Child = () => Hi!
;
act(() =>
ReactDOM.render(
,
document.createElement('div'),
),
);
expect(store).toMatchSnapshot('1: mount');
});
it('should display Suspense nodes properly in various states', () => {
const Loading = () => Loading...
;
const SuspendingComponent = () => {
throw new Promise(() => {});
};
const Component = () => {
return Hello
;
};
const Wrapper = ({shouldSuspense}) => (
}>
{shouldSuspense ? (
) : (
)}
);
const container = document.createElement('div');
act(() => ReactDOM.render(, container));
expect(store).toMatchSnapshot('1: loading');
act(() => {
ReactDOM.render(, container);
});
expect(store).toMatchSnapshot('2: resolved');
});
it('should support nested Suspense nodes', () => {
const Component = () => null;
const Loading = () => Loading...
;
const Never = () => {
throw new Promise(() => {});
};
const Wrapper = ({
suspendFirst = false,
suspendSecond = false,
suspendParent = false,
}) => (
}>
}>
{suspendFirst ? (
) : (
)}
}>
{suspendSecond ? (
) : (
)}
}>
{suspendParent && }
);
const container = document.createElement('div');
act(() =>
ReactDOM.render(
,
container,
),
);
expect(store).toMatchSnapshot('1: third child is suspended');
act(() =>
ReactDOM.render(
,
container,
),
);
expect(store).toMatchSnapshot('2: first and third child are suspended');
act(() =>
ReactDOM.render(
,
container,
),
);
expect(store).toMatchSnapshot('3: second and third child are suspended');
act(() =>
ReactDOM.render(
,
container,
),
);
expect(store).toMatchSnapshot('4: first and third child are suspended');
act(() =>
ReactDOM.render(
,
container,
),
);
expect(store).toMatchSnapshot('5: parent is suspended');
act(() =>
ReactDOM.render(
,
container,
),
);
expect(store).toMatchSnapshot('6: all children are suspended');
act(() =>
ReactDOM.render(
,
container,
),
);
expect(store).toMatchSnapshot('7: only third child is suspended');
const rendererID = getRendererID();
act(() =>
agent.overrideSuspense({
id: store.getElementIDAtIndex(4),
rendererID,
forceFallback: true,
}),
);
expect(store).toMatchSnapshot('8: first and third child are suspended');
act(() =>
agent.overrideSuspense({
id: store.getElementIDAtIndex(2),
rendererID,
forceFallback: true,
}),
);
expect(store).toMatchSnapshot('9: parent is suspended');
act(() =>
ReactDOM.render(
,
container,
),
);
expect(store).toMatchSnapshot('10: parent is suspended');
act(() =>
agent.overrideSuspense({
id: store.getElementIDAtIndex(2),
rendererID,
forceFallback: false,
}),
);
expect(store).toMatchSnapshot('11: all children are suspended');
act(() =>
agent.overrideSuspense({
id: store.getElementIDAtIndex(4),
rendererID,
forceFallback: false,
}),
);
expect(store).toMatchSnapshot('12: all children are suspended');
act(() =>
ReactDOM.render(
,
container,
),
);
expect(store).toMatchSnapshot('13: third child is suspended');
});
it('should display a partially rendered SuspenseList', () => {
const Loading = () => Loading...
;
const SuspendingComponent = () => {
throw new Promise(() => {});
};
const Component = () => {
return Hello
;
};
const Wrapper = ({shouldSuspense}) => (
}>
{shouldSuspense ? : }
);
const container = document.createElement('div');
const root = ReactDOM.createRoot(container);
act(() => {
root.render();
});
expect(store).toMatchSnapshot('1: loading');
act(() => {
root.render();
});
expect(store).toMatchSnapshot('2: resolved');
});
it('should support collapsing parts of the tree', () => {
const Grandparent = ({count}) => (
);
const Parent = ({count}) =>
new Array(count).fill(true).map((_, index) => );
const Child = () => Hi!
;
act(() =>
ReactDOM.render(
,
document.createElement('div'),
),
);
expect(store).toMatchSnapshot('1: mount');
const grandparentID = store.getElementIDAtIndex(0);
const parentOneID = store.getElementIDAtIndex(1);
const parentTwoID = store.getElementIDAtIndex(4);
act(() => store.toggleIsCollapsed(parentOneID, true));
expect(store).toMatchSnapshot('2: collapse first Parent');
act(() => store.toggleIsCollapsed(parentTwoID, true));
expect(store).toMatchSnapshot('3: collapse second Parent');
act(() => store.toggleIsCollapsed(parentOneID, false));
expect(store).toMatchSnapshot('4: expand first Parent');
act(() => store.toggleIsCollapsed(grandparentID, true));
expect(store).toMatchSnapshot('5: collapse Grandparent');
act(() => store.toggleIsCollapsed(grandparentID, false));
expect(store).toMatchSnapshot('6: expand Grandparent');
});
it('should support reordering of children', () => {
const Root = ({children}) => children;
const Component = () => null;
const Foo = () => [];
const Bar = () => [, ];
const foo = ;
const bar = ;
const container = document.createElement('div');
act(() => ReactDOM.render({[foo, bar]}, container));
expect(store).toMatchSnapshot('1: mount');
act(() => ReactDOM.render({[bar, foo]}, container));
expect(store).toMatchSnapshot('3: reorder children');
act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), true));
expect(store).toMatchSnapshot('4: collapse root');
act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), false));
expect(store).toMatchSnapshot('5: expand root');
});
});
describe('collapseNodesByDefault:true', () => {
beforeEach(() => {
store.collapseNodesByDefault = true;
});
it('should support mount and update operations', () => {
const Parent = ({count}) =>
new Array(count).fill(true).map((_, index) => );
const Child = () => Hi!
;
const container = document.createElement('div');
act(() =>
ReactDOM.render(
,
container,
),
);
expect(store).toMatchSnapshot('1: mount');
act(() =>
ReactDOM.render(
,
container,
),
);
expect(store).toMatchSnapshot('2: update');
act(() => ReactDOM.unmountComponentAtNode(container));
expect(store).toMatchSnapshot('3: unmount');
});
it('should support mount and update operations for multiple roots', () => {
const Parent = ({count}) =>
new Array(count).fill(true).map((_, index) => );
const Child = () => Hi!
;
const containerA = document.createElement('div');
const containerB = document.createElement('div');
act(() => {
ReactDOM.render(, containerA);
ReactDOM.render(, containerB);
});
expect(store).toMatchSnapshot('1: mount');
act(() => {
ReactDOM.render(, containerA);
ReactDOM.render(, containerB);
});
expect(store).toMatchSnapshot('2: update');
act(() => ReactDOM.unmountComponentAtNode(containerB));
expect(store).toMatchSnapshot('3: unmount B');
act(() => ReactDOM.unmountComponentAtNode(containerA));
expect(store).toMatchSnapshot('4: unmount A');
});
it('should filter DOM nodes from the store tree', () => {
const Grandparent = () => (
);
const Parent = () => (
);
const Child = () => Hi!
;
act(() =>
ReactDOM.render(
,
document.createElement('div'),
),
);
expect(store).toMatchSnapshot('1: mount');
act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), false));
expect(store).toMatchSnapshot('2: expand Grandparent');
act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(1), false));
expect(store).toMatchSnapshot('3: expand Parent');
});
it('should display Suspense nodes properly in various states', () => {
const Loading = () => Loading...
;
const SuspendingComponent = () => {
throw new Promise(() => {});
};
const Component = () => {
return Hello
;
};
const Wrapper = ({shouldSuspense}) => (
}>
{shouldSuspense ? (
) : (
)}
);
const container = document.createElement('div');
act(() => ReactDOM.render(, container));
expect(store).toMatchSnapshot('1: loading');
// 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).toMatchSnapshot('2: expand Wrapper and Suspense');
act(() => {
ReactDOM.render(, container);
});
expect(store).toMatchSnapshot('2: resolved');
});
it('should support expanding parts of the tree', () => {
const Grandparent = ({count}) => (
);
const Parent = ({count}) =>
new Array(count).fill(true).map((_, index) => );
const Child = () => Hi!
;
act(() =>
ReactDOM.render(
,
document.createElement('div'),
),
);
expect(store).toMatchSnapshot('1: mount');
const grandparentID = store.getElementIDAtIndex(0);
act(() => store.toggleIsCollapsed(grandparentID, false));
expect(store).toMatchSnapshot('2: expand Grandparent');
const parentOneID = store.getElementIDAtIndex(1);
const parentTwoID = store.getElementIDAtIndex(2);
act(() => store.toggleIsCollapsed(parentOneID, false));
expect(store).toMatchSnapshot('3: expand first Parent');
act(() => store.toggleIsCollapsed(parentTwoID, false));
expect(store).toMatchSnapshot('4: expand second Parent');
act(() => store.toggleIsCollapsed(parentOneID, true));
expect(store).toMatchSnapshot('5: collapse first Parent');
act(() => store.toggleIsCollapsed(parentTwoID, true));
expect(store).toMatchSnapshot('6: collapse second Parent');
act(() => store.toggleIsCollapsed(grandparentID, true));
expect(store).toMatchSnapshot('7: collapse Grandparent');
});
it('should support expanding deep parts of the tree', () => {
const Wrapper = ({forwardedRef}) => (
);
const Nested = ({depth, forwardedRef}) =>
depth > 0 ? (
) : (
);
const ref = React.createRef();
act(() =>
ReactDOM.render(
,
document.createElement('div'),
),
);
expect(store).toMatchSnapshot('1: mount');
const deepestedNodeID = agent.getIDForNode(ref.current);
act(() => store.toggleIsCollapsed(deepestedNodeID, false));
expect(store).toMatchSnapshot('2: expand deepest node');
const rootID = store.getElementIDAtIndex(0);
act(() => store.toggleIsCollapsed(rootID, true));
expect(store).toMatchSnapshot('3: collapse root');
act(() => store.toggleIsCollapsed(rootID, false));
expect(store).toMatchSnapshot('4: expand root');
const id = store.getElementIDAtIndex(1);
act(() => store.toggleIsCollapsed(id, true));
expect(store).toMatchSnapshot('5: collapse middle node');
act(() => store.toggleIsCollapsed(id, false));
expect(store).toMatchSnapshot('6: expand middle node');
});
it('should support reordering of children', () => {
const Root = ({children}) => children;
const Component = () => null;
const Foo = () => [];
const Bar = () => [, ];
const foo = ;
const bar = ;
const container = document.createElement('div');
act(() => ReactDOM.render({[foo, bar]}, container));
expect(store).toMatchSnapshot('1: mount');
act(() => ReactDOM.render({[bar, foo]}, container));
expect(store).toMatchSnapshot('3: reorder children');
act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), false));
expect(store).toMatchSnapshot('4: expand root');
act(() => {
store.toggleIsCollapsed(store.getElementIDAtIndex(2), false);
store.toggleIsCollapsed(store.getElementIDAtIndex(1), false);
});
expect(store).toMatchSnapshot('5: expand leaves');
act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), true));
expect(store).toMatchSnapshot('6: collapse root');
});
it('should not add new nodes when suspense is toggled', () => {
const SuspenseTree = () => {
return (
Loading outer}>
);
};
const Fallback = () => null;
const Parent = () => ;
const Child = () => null;
act(() =>
ReactDOM.render(, document.createElement('div')),
);
expect(store).toMatchSnapshot('1: mount');
act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(0), false));
act(() => store.toggleIsCollapsed(store.getElementIDAtIndex(1), false));
expect(store).toMatchSnapshot('2: expand tree');
const rendererID = getRendererID();
const suspenseID = store.getElementIDAtIndex(1);
act(() =>
agent.overrideSuspense({
id: suspenseID,
rendererID,
forceFallback: true,
}),
);
expect(store).toMatchSnapshot('3: toggle fallback on');
act(() =>
agent.overrideSuspense({
id: suspenseID,
rendererID,
forceFallback: false,
}),
);
expect(store).toMatchSnapshot('4: toggle fallback on');
});
});
describe('getIndexOfElementID', () => {
beforeEach(() => {
store.collapseNodesByDefault = false;
});
it('should support a single root with a single child', () => {
const Grandparent = () => (
);
const Parent = () => ;
const Child = () => null;
act(() =>
ReactDOM.render(, document.createElement('div')),
);
for (let i = 0; i < store.numElements; i++) {
expect(store.getIndexOfElementID(store.getElementIDAtIndex(i))).toBe(i);
}
});
it('should support multiple roots with one children each', () => {
const Grandparent = () => ;
const Parent = () => ;
const Child = () => null;
act(() => {
ReactDOM.render(, document.createElement('div'));
ReactDOM.render(, document.createElement('div'));
});
for (let i = 0; i < store.numElements; i++) {
expect(store.getIndexOfElementID(store.getElementIDAtIndex(i))).toBe(i);
}
});
it('should support a single root with multiple top level children', () => {
const Grandparent = () => ;
const Parent = () => ;
const Child = () => null;
act(() =>
ReactDOM.render(
,
document.createElement('div'),
),
);
for (let i = 0; i < store.numElements; i++) {
expect(store.getIndexOfElementID(store.getElementIDAtIndex(i))).toBe(i);
}
});
it('should support multiple roots with multiple top level children', () => {
const Grandparent = () => ;
const Parent = () => ;
const Child = () => null;
act(() => {
ReactDOM.render(
,
document.createElement('div'),
);
ReactDOM.render(
,
document.createElement('div'),
);
});
for (let i = 0; i < store.numElements; i++) {
expect(store.getIndexOfElementID(store.getElementIDAtIndex(i))).toBe(i);
}
});
});
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.supportsProfiling).toBe(false);
act(() => ReactDOM.render(, containerA));
expect(store.supportsProfiling).toBe(true);
act(() => ReactDOM.render(, containerB));
act(() => ReactDOM.unmountComponentAtNode(containerA));
expect(store.supportsProfiling).toBe(true);
act(() => ReactDOM.unmountComponentAtNode(containerB));
expect(store.supportsProfiling).toBe(false);
});
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({}, , {key: 123});
act(() => ReactDOM.render([fauxElement], document.createElement('div')));
expect(store).toMatchSnapshot('1: mount');
});
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(() => (
));
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 = () => (
);
const container = document.createElement('div');
// Render once to start fetching the lazy component
act(() => ReactDOM.render(, container));
await Promise.resolve();
// Render again after it resolves
act(() => ReactDOM.render(, container));
expect(store).toMatchInlineSnapshot(`
[root]
▾
[ForwardRef]
▾ [ForwardRef]
[ForwardRef]
[Memo]
▾ [Memo]
[ForwardRef]
[withFoo][withBar]
[Memo][withFoo][withBar]
[ForwardRef][withFoo][withBar]
[Memo]
[ForwardRef]
`);
});
describe('Lazy', () => {
async function fakeImport(result) {
return {default: result};
}
const LazyInnerComponent = () => null;
const App = ({renderChildren}) => {
if (renderChildren) {
return (
);
} else {
return null;
}
};
let LazyComponent;
beforeEach(() => {
LazyComponent = React.lazy(() => fakeImport(LazyInnerComponent));
});
it('should support Lazy components (legacy render)', async () => {
const container = document.createElement('div');
// Render once to start fetching the lazy component
act(() => ReactDOM.render(, container));
expect(store).toMatchSnapshot('1: mounted + loading');
await Promise.resolve();
// Render again after it resolves
act(() => ReactDOM.render(, container));
expect(store).toMatchSnapshot('2: mounted + loaded');
// Render again to unmount it
act(() => ReactDOM.render(, container));
expect(store).toMatchSnapshot('3: unmounted');
});
it('should support Lazy components in (createRoot)', async () => {
const container = document.createElement('div');
const root = ReactDOM.createRoot(container);
// Render once to start fetching the lazy component
act(() => root.render());
expect(store).toMatchSnapshot('1: mounted + loading');
await Promise.resolve();
// Render again after it resolves
act(() => root.render());
expect(store).toMatchSnapshot('2: mounted + loaded');
// Render again to unmount it
act(() => root.render());
expect(store).toMatchSnapshot('3: unmounted');
});
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(() => ReactDOM.render(, container));
expect(store).toMatchSnapshot('1: mounted + loading');
// Render again to unmount it before it finishes loading
act(() => ReactDOM.render(, container));
expect(store).toMatchSnapshot('2: unmounted');
});
it('should support Lazy components that are unmounted before they finish loading in (createRoot)', async () => {
const container = document.createElement('div');
const root = ReactDOM.createRoot(container);
// Render once to start fetching the lazy component
act(() => root.render());
expect(store).toMatchSnapshot('1: mounted + loading');
// Render again to unmount it before it finishes loading
act(() => root.render());
expect(store).toMatchSnapshot('2: unmounted');
});
});
describe('inline errors and warnings', () => {
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(() => ReactDOM.render(, container));
});
expect(store).toMatchInlineSnapshot(`
✕ 1, ⚠ 1
[root]
✕⚠
`);
withErrorsOrWarningsIgnored(['test-only:'], () => {
act(() => ReactDOM.render(, container));
});
expect(store).toMatchInlineSnapshot(`
✕ 2, ⚠ 2
[root]
✕⚠
`);
});
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(() => ReactDOM.render(, container));
});
expect(store).toMatchInlineSnapshot(`
✕ 1, ⚠ 1
[root]
✕⚠
`);
withErrorsOrWarningsIgnored(['test-only:'], () => {
act(() => ReactDOM.render(, container));
});
expect(store).toMatchInlineSnapshot(`
✕ 2, ⚠ 2
[root]
✕⚠
`);
});
describe('during passive effects', () => {
function flushPendingBridgeOperations() {
jest.runOnlyPendingTimers();
}
// Gross abstraction around pending passive warning/error delay.
function flushPendingPassiveErrorAndWarningCounts() {
jest.advanceTimersByTime(1000);
}
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(() => {
ReactDOM.render(, container);
}, false);
});
flushPendingBridgeOperations();
expect(store).toMatchInlineSnapshot(`
[root]
`);
// After a delay, passive effects should be committed as well
act(flushPendingPassiveErrorAndWarningCounts, false);
expect(store).toMatchInlineSnapshot(`
✕ 1, ⚠ 1
[root]
✕⚠
`);
act(() => ReactDOM.unmountComponentAtNode(container));
expect(store).toMatchInlineSnapshot(``);
});
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(() => {
ReactDOM.render(
<>
>,
container,
);
}, false);
flushPendingBridgeOperations();
expect(store).toMatchInlineSnapshot(`
[root]
`);
// Before warnings and errors have flushed, flush another commit.
act(() => {
ReactDOM.render(
<>
>,
container,
);
}, false);
flushPendingBridgeOperations();
expect(store).toMatchInlineSnapshot(`
✕ 1, ⚠ 1
[root]
✕⚠
`);
});
// After a delay, passive effects should be committed as well
act(flushPendingPassiveErrorAndWarningCounts, false);
expect(store).toMatchInlineSnapshot(`
✕ 2, ⚠ 2
[root]
✕⚠
`);
act(() => ReactDOM.unmountComponentAtNode(container));
expect(store).toMatchInlineSnapshot(``);
});
});
it('from react get counted', () => {
const container = document.createElement('div');
function Example() {
return [];
}
function Child() {
return null;
}
withErrorsOrWarningsIgnored(
['Warning: Each child in a list should have a unique "key" prop'],
() => {
act(() => ReactDOM.render(, container));
},
);
expect(store).toMatchInlineSnapshot(`
✕ 1, ⚠ 0
[root]
▾ ✕
`);
});
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(() =>
ReactDOM.render(
,
container,
),
);
});
expect(store).toMatchInlineSnapshot(`
✕ 2, ⚠ 2
[root]
✕⚠
✕⚠
`);
const {
clearErrorsAndWarnings,
} = require('react-devtools-shared/src/backendAPI');
clearErrorsAndWarnings({bridge, store});
// flush events to the renderer
jest.runAllTimers();
expect(store).toMatchInlineSnapshot(`
[root]
`);
});
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(() =>
ReactDOM.render(
,
container,
),
);
});
expect(store).toMatchInlineSnapshot(`
✕ 2, ⚠ 2
[root]
✕⚠
✕⚠
`);
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]
✕⚠
✕
`);
});
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(() =>
ReactDOM.render(
,
container,
),
);
});
expect(store).toMatchInlineSnapshot(`
✕ 2, ⚠ 2
[root]
✕⚠
✕⚠
`);
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]
✕⚠
⚠
`);
});
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(() =>
ReactDOM.render(
,
container,
),
);
});
expect(store).toMatchInlineSnapshot(`
✕ 2, ⚠ 2
[root]
✕
⚠
✕⚠
`);
withErrorsOrWarningsIgnored(['test-only:'], () => {
act(() =>
ReactDOM.render(
,
container,
),
);
});
expect(store).toMatchInlineSnapshot(`
✕ 1, ⚠ 2
[root]
⚠
✕⚠
`);
withErrorsOrWarningsIgnored(['test-only:'], () => {
act(() =>
ReactDOM.render(
,
container,
),
);
});
expect(store).toMatchInlineSnapshot(`
✕ 0, ⚠ 2
[root]
⚠
`);
withErrorsOrWarningsIgnored(['test-only:'], () => {
act(() => ReactDOM.render(, container));
});
expect(store).toMatchInlineSnapshot(`[root]`);
expect(store.errorCount).toBe(0);
expect(store.warningCount).toBe(0);
});
});
});