/** * 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); }); }); });