diff --git a/packages/react-devtools-shared/src/__tests__/storeComponentFilters-test.js b/packages/react-devtools-shared/src/__tests__/storeComponentFilters-test.js index e2a59177b687f..573ca40cba6b0 100644 --- a/packages/react-devtools-shared/src/__tests__/storeComponentFilters-test.js +++ b/packages/react-devtools-shared/src/__tests__/storeComponentFilters-test.js @@ -10,25 +10,18 @@ import type {FrontendBridge} from 'react-devtools-shared/src/bridge'; import type Store from 'react-devtools-shared/src/devtools/store'; +import { + getLegacyRenderImplementation, + getVersionedRenderImplementation, +} from './utils'; + describe('Store component filters', () => { let React; let Types; let bridge: FrontendBridge; - let legacyRender; let store: Store; let utils; - - const act = async (callback: Function) => { - if (React.act != null) { - await React.act(callback); - } else if (React.unstable_act != null) { - await React.unstable_act(callback); - } else { - callback(); - } - - jest.runAllTimers(); // Flush Bridge operations - }; + let actAsync; beforeEach(() => { bridge = global.bridge; @@ -41,12 +34,14 @@ describe('Store component filters', () => { Types = require('react-devtools-shared/src/frontend/types'); utils = require('./utils'); - legacyRender = utils.legacyRender; + actAsync = utils.actAsync; }); + const {render} = getVersionedRenderImplementation(); + // @reactVersion >= 16.0 it('should throw if filters are updated while profiling', async () => { - await act(async () => store.profilerStore.startProfiling()); + await actAsync(async () => store.profilerStore.startProfiling()); expect(() => (store.componentFilters = [])).toThrow( 'Cannot modify filter preferences while profiling', ); @@ -61,12 +56,11 @@ describe('Store component filters', () => { } const FunctionComponent = () =>
Hi
; - await act(async () => - legacyRender( + await actAsync(async () => + render( , - document.createElement('div'), ), ); expect(store).toMatchInlineSnapshot(` @@ -77,7 +71,7 @@ describe('Store component filters', () => {
`); - await act( + await actAsync( async () => (store.componentFilters = [ utils.createElementTypeFilter(Types.ElementTypeHostComponent), @@ -89,7 +83,7 @@ describe('Store component filters', () => { `); - await act( + await actAsync( async () => (store.componentFilters = [ utils.createElementTypeFilter(Types.ElementTypeClass), @@ -102,7 +96,7 @@ describe('Store component filters', () => {
`); - await act( + await actAsync( async () => (store.componentFilters = [ utils.createElementTypeFilter(Types.ElementTypeClass), @@ -115,7 +109,7 @@ describe('Store component filters', () => {
`); - await act( + await actAsync( async () => (store.componentFilters = [ utils.createElementTypeFilter(Types.ElementTypeClass, false), @@ -130,7 +124,7 @@ describe('Store component filters', () => {
`); - await act(async () => (store.componentFilters = [])); + await actAsync(async () => (store.componentFilters = [])); expect(store).toMatchInlineSnapshot(` [root] ▾ @@ -144,16 +138,14 @@ describe('Store component filters', () => { it('should ignore invalid ElementTypeRoot filter', async () => { const Component = () =>
Hi
; - await act(async () => - legacyRender(, document.createElement('div')), - ); + await actAsync(async () => render()); expect(store).toMatchInlineSnapshot(` [root] ▾
`); - await act( + await actAsync( async () => (store.componentFilters = [ utils.createElementTypeFilter(Types.ElementTypeRoot), @@ -174,14 +166,13 @@ describe('Store component filters', () => { const Bar = () => ; const Baz = () => ; - await act(async () => - legacyRender( + await actAsync(async () => + render( , - document.createElement('div'), ), ); expect(store).toMatchInlineSnapshot(` @@ -194,7 +185,7 @@ describe('Store component filters', () => { `); - await act( + await actAsync( async () => (store.componentFilters = [utils.createDisplayNameFilter('Foo')]), ); @@ -207,7 +198,7 @@ describe('Store component filters', () => { `); - await act( + await actAsync( async () => (store.componentFilters = [utils.createDisplayNameFilter('Ba')]), ); @@ -219,7 +210,7 @@ describe('Store component filters', () => { `); - await act( + await actAsync( async () => (store.componentFilters = [utils.createDisplayNameFilter('B.z')]), ); @@ -237,16 +228,14 @@ describe('Store component filters', () => { it('should filter by path', async () => { const Component = () =>
Hi
; - await act(async () => - legacyRender(, document.createElement('div')), - ); + await actAsync(async () => render()); expect(store).toMatchInlineSnapshot(` [root] ▾
`); - await act( + await actAsync( async () => (store.componentFilters = [ utils.createLocationFilter(__filename.replace(__dirname, '')), @@ -255,7 +244,7 @@ describe('Store component filters', () => { expect(store).toMatchInlineSnapshot(`[root]`); - await act( + await actAsync( async () => (store.componentFilters = [ utils.createLocationFilter('this:is:a:made:up:path'), @@ -277,7 +266,7 @@ describe('Store component filters', () => { const Bar = () => ; Bar.displayName = 'Bar(Foo(Component))'; - await act(async () => legacyRender(, document.createElement('div'))); + await actAsync(async () => render()); expect(store).toMatchInlineSnapshot(` [root] ▾ [Bar][Foo] @@ -286,7 +275,7 @@ describe('Store component filters', () => {
`); - await act( + await actAsync( async () => (store.componentFilters = [utils.createHOCFilter(true)]), ); expect(store).toMatchInlineSnapshot(` @@ -295,7 +284,7 @@ describe('Store component filters', () => {
`); - await act( + await actAsync( async () => (store.componentFilters = [utils.createHOCFilter(false)]), ); expect(store).toMatchInlineSnapshot(` @@ -309,7 +298,7 @@ describe('Store component filters', () => { // @reactVersion >= 16.0 it('should not send a bridge update if the set of enabled filters has not changed', async () => { - await act( + await actAsync( async () => (store.componentFilters = [utils.createHOCFilter(true)]), ); @@ -317,21 +306,21 @@ describe('Store component filters', () => { throw Error('Unexpected component update'); }); - await act( + await actAsync( async () => (store.componentFilters = [ utils.createHOCFilter(false), utils.createHOCFilter(true), ]), ); - await act( + await actAsync( async () => (store.componentFilters = [ utils.createHOCFilter(true), utils.createLocationFilter('abc', false), ]), ); - await act( + await actAsync( async () => (store.componentFilters = [ utils.createHOCFilter(true), @@ -363,10 +352,7 @@ describe('Store component filters', () => { utils.createElementTypeFilter(Types.ElementTypeSuspense), ]; - const container = document.createElement('div'); - await act(async () => - legacyRender(, container), - ); + await actAsync(async () => render()); expect(store).toMatchInlineSnapshot(` [root] ▾ @@ -374,18 +360,14 @@ describe('Store component filters', () => {
`); - await act(async () => - legacyRender(, container), - ); + await actAsync(async () => render()); expect(store).toMatchInlineSnapshot(` [root] ▾ `); - await act(async () => - legacyRender(, container), - ); + await actAsync(async () => render()); expect(store).toMatchInlineSnapshot(` [root] ▾ @@ -395,8 +377,11 @@ describe('Store component filters', () => { }); describe('inline errors and warnings', () => { + const {render: legacyRender} = getLegacyRenderImplementation(); + // @reactVersion >= 17.0 - it('only counts for unfiltered components', async () => { + // @reactVersion <= 18.2 + it('only counts for unfiltered components (legacy render)', async () => { function ComponentWithWarning() { console.warn('test-only: render warning'); return null; @@ -411,15 +396,7 @@ describe('Store component filters', () => { return null; } - // HACK This require() is needed (somewhere in the test) for this case to pass. - // Without it, the legacyRender() call below causes this test to fail - // because it requires "react-dom" for the first time, - // which causes the console error() and warn() methods to be overridden again, - // effectively disconnecting the DevTools override in 'react-devtools-shared/src/backend/console'. - require('react-dom'); - - const container = document.createElement('div'); - await act( + await actAsync( async () => (store.componentFilters = [ utils.createDisplayNameFilter('Warning'), @@ -433,7 +410,6 @@ describe('Store component filters', () => { , - container, ); }); @@ -441,7 +417,99 @@ describe('Store component filters', () => { expect(store.errorCount).toBe(0); expect(store.warningCount).toBe(0); - await act(async () => (store.componentFilters = [])); + await actAsync(async () => (store.componentFilters = [])); + expect(store).toMatchInlineSnapshot(` + ✕ 2, ⚠ 2 + [root] + ✕ + ⚠ + ✕⚠ + `); + + await actAsync( + async () => + (store.componentFilters = [utils.createDisplayNameFilter('Warning')]), + ); + expect(store).toMatchInlineSnapshot(` + ✕ 1, ⚠ 0 + [root] + ✕ + `); + + await actAsync( + async () => + (store.componentFilters = [utils.createDisplayNameFilter('Error')]), + ); + expect(store).toMatchInlineSnapshot(` + ✕ 0, ⚠ 1 + [root] + ⚠ + `); + + await actAsync( + async () => + (store.componentFilters = [ + utils.createDisplayNameFilter('Warning'), + utils.createDisplayNameFilter('Error'), + ]), + ); + expect(store).toMatchInlineSnapshot(`[root]`); + expect(store.errorCount).toBe(0); + expect(store.warningCount).toBe(0); + + await actAsync(async () => (store.componentFilters = [])); + expect(store).toMatchInlineSnapshot(` + ✕ 2, ⚠ 2 + [root] + ✕ + ⚠ + ✕⚠ + `); + }); + + // @reactVersion >= 18 + it('only counts for unfiltered components (createRoot)', async () => { + 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; + } + + await actAsync( + async () => + (store.componentFilters = [ + utils.createDisplayNameFilter('Warning'), + utils.createDisplayNameFilter('Error'), + ]), + ); + + utils.act( + () => + utils.withErrorsOrWarningsIgnored(['test-only:'], () => { + render( + + + + + , + ); + }), + false, + ); + + expect(store).toMatchInlineSnapshot(``); + expect(store.errorCount).toBe(0); + expect(store.warningCount).toBe(0); + + await actAsync(async () => (store.componentFilters = [])); expect(store).toMatchInlineSnapshot(` ✕ 2, ⚠ 2 [root] @@ -450,7 +518,7 @@ describe('Store component filters', () => { ✕⚠ `); - await act( + await actAsync( async () => (store.componentFilters = [utils.createDisplayNameFilter('Warning')]), ); @@ -460,7 +528,7 @@ describe('Store component filters', () => { ✕ `); - await act( + await actAsync( async () => (store.componentFilters = [utils.createDisplayNameFilter('Error')]), ); @@ -470,7 +538,7 @@ describe('Store component filters', () => { ⚠ `); - await act( + await actAsync( async () => (store.componentFilters = [ utils.createDisplayNameFilter('Warning'), @@ -481,7 +549,7 @@ describe('Store component filters', () => { expect(store.errorCount).toBe(0); expect(store.warningCount).toBe(0); - await act(async () => (store.componentFilters = [])); + await actAsync(async () => (store.componentFilters = [])); expect(store).toMatchInlineSnapshot(` ✕ 2, ⚠ 2 [root] diff --git a/packages/react-devtools-shared/src/__tests__/utils.js b/packages/react-devtools-shared/src/__tests__/utils.js index f00e66d98396e..95d2c3c6f860a 100644 --- a/packages/react-devtools-shared/src/__tests__/utils.js +++ b/packages/react-devtools-shared/src/__tests__/utils.js @@ -21,6 +21,12 @@ import {ReactVersion} from '../../../../ReactVersions'; const requestedReactVersion = process.env.REACT_VERSION || ReactVersion; export function getActDOMImplementation(): () => void | Promise { + // This is for React < 17, where act wasn't shipped yet. + if (semver.lt(requestedReactVersion, '17.0.0')) { + require('react-dom/test-utils'); + return cb => cb(); + } + // This is for React < 18, where act was distributed in react-dom/test-utils. if (semver.lt(requestedReactVersion, '18.0.0')) { const ReactDOMTestUtils = require('react-dom/test-utils'); @@ -41,13 +47,30 @@ export function getActDOMImplementation(): () => void | Promise { throw new Error("Couldn't find any available act implementation"); } +export function getActTestRendererImplementation(): () => void | Promise { + // This is for React < 17, where act wasn't shipped yet. + if (semver.lt(requestedReactVersion, '17.0.0')) { + require('react-test-renderer'); + return cb => cb(); + } + + const ReactTestRenderer = require('react-test-renderer'); + if (ReactTestRenderer.act) { + return ReactTestRenderer.act; + } + + throw new Error( + "Couldn't find any available act implementation in react-test-renderer", + ); +} + export function act( callback: Function, recursivelyFlush: boolean = true, ): void { // act from react-test-renderer has some side effects on React DevTools // it injects the renderer for DevTools, see ReactTestRenderer.js - const {act: actTestRenderer} = require('react-test-renderer'); + const actTestRenderer = getActTestRendererImplementation(); const actDOM = getActDOMImplementation(); actDOM(() => { @@ -74,7 +97,7 @@ export async function actAsync( ): Promise { // act from react-test-renderer has some side effects on React DevTools // it injects the renderer for DevTools, see ReactTestRenderer.js - const {act: actTestRenderer} = require('react-test-renderer'); + const actTestRenderer = getActTestRendererImplementation(); const actDOM = getActDOMImplementation(); await actDOM(async () => {