chore: use versioned render in storeComponentFilters test (#28241)
Ruslan Lesiutin committed
Feb 6, 2024 at 16:25 UTC
12d56fca3da06328ac61d86976afa728b97b49d6
2 files changed
+164
-73
packages/react-devtools-shared/src/__tests__/storeComponentFilters-test.js
+139
-71
@@ -10,25 +10,18 @@
10
import type {FrontendBridge} from 'react-devtools-shared/src/bridge';
11
import type Store from 'react-devtools-shared/src/devtools/store';
12
13
+import {
14
+ getLegacyRenderImplementation,
15
+ getVersionedRenderImplementation,
16
+} from './utils';
17
+
18
describe('Store component filters', () => {
19
let React;
20
let Types;
21
let bridge: FrontendBridge;
17
- let legacyRender;
22
let store: Store;
23
let utils;
20
-
21
- const act = async (callback: Function) => {
22
- if (React.act != null) {
23
- await React.act(callback);
24
- } else if (React.unstable_act != null) {
25
- await React.unstable_act(callback);
26
- } else {
27
- callback();
28
- }
29
-
30
- jest.runAllTimers(); // Flush Bridge operations
31
- };
24
+ let actAsync;
25
26
beforeEach(() => {
27
bridge = global.bridge;
@@ -41,12 +34,14 @@ describe('Store component filters', () => {
34
Types = require('react-devtools-shared/src/frontend/types');
35
utils = require('./utils');
36
44
- legacyRender = utils.legacyRender;
37
+ actAsync = utils.actAsync;
38
});
39
40
+ const {render} = getVersionedRenderImplementation();
41
+
42
// @reactVersion >= 16.0
43
it('should throw if filters are updated while profiling', async () => {
49
- await act(async () => store.profilerStore.startProfiling());
44
+ await actAsync(async () => store.profilerStore.startProfiling());
45
expect(() => (store.componentFilters = [])).toThrow(
46
'Cannot modify filter preferences while profiling',
47
);
@@ -61,12 +56,11 @@ describe('Store component filters', () => {
56
}
57
const FunctionComponent = () => <div>Hi</div>;
58
64
- await act(async () =>
65
- legacyRender(
59
+ await actAsync(async () =>
60
+ render(
61
<ClassComponent>
62
<FunctionComponent />
63
</ClassComponent>,
69
- document.createElement('div'),
64
),
65
);
66
expect(store).toMatchInlineSnapshot(`
@@ -77,7 +71,7 @@ describe('Store component filters', () => {
71
<div>
72
`);
73
80
- await act(
74
+ await actAsync(
75
async () =>
76
(store.componentFilters = [
77
utils.createElementTypeFilter(Types.ElementTypeHostComponent),
@@ -89,7 +83,7 @@ describe('Store component filters', () => {
83
<FunctionComponent>
84
`);
85
92
- await act(
86
+ await actAsync(
87
async () =>
88
(store.componentFilters = [
89
utils.createElementTypeFilter(Types.ElementTypeClass),
@@ -102,7 +96,7 @@ describe('Store component filters', () => {
96
<div>
97
`);
98
105
- await act(
99
+ await actAsync(
100
async () =>
101
(store.componentFilters = [
102
utils.createElementTypeFilter(Types.ElementTypeClass),
@@ -115,7 +109,7 @@ describe('Store component filters', () => {
109
<div>
110
`);
111
118
- await act(
112
+ await actAsync(
113
async () =>
114
(store.componentFilters = [
115
utils.createElementTypeFilter(Types.ElementTypeClass, false),
@@ -130,7 +124,7 @@ describe('Store component filters', () => {
124
<div>
125
`);
126
133
- await act(async () => (store.componentFilters = []));
127
+ await actAsync(async () => (store.componentFilters = []));
128
expect(store).toMatchInlineSnapshot(`
129
[root]
130
▾ <ClassComponent>
@@ -144,16 +138,14 @@ describe('Store component filters', () => {
138
it('should ignore invalid ElementTypeRoot filter', async () => {
139
const Component = () => <div>Hi</div>;
140
147
- await act(async () =>
148
- legacyRender(<Component />, document.createElement('div')),
149
- );
141
+ await actAsync(async () => render(<Component />));
142
expect(store).toMatchInlineSnapshot(`
143
[root]
144
▾ <Component>
145
<div>
146
`);
147
156
- await act(
148
+ await actAsync(
149
async () =>
150
(store.componentFilters = [
151
utils.createElementTypeFilter(Types.ElementTypeRoot),
@@ -174,14 +166,13 @@ describe('Store component filters', () => {
166
const Bar = () => <Text label="bar" />;
167
const Baz = () => <Text label="baz" />;
168
177
- await act(async () =>
178
- legacyRender(
169
+ await actAsync(async () =>
170
+ render(
171
<React.Fragment>
172
<Foo />
173
<Bar />
174
<Baz />
175
</React.Fragment>,
184
- document.createElement('div'),
176
),
177
);
178
expect(store).toMatchInlineSnapshot(`
@@ -194,7 +185,7 @@ describe('Store component filters', () => {
185
<Text>
186
`);
187
197
- await act(
188
+ await actAsync(
189
async () =>
190
(store.componentFilters = [utils.createDisplayNameFilter('Foo')]),
191
);
@@ -207,7 +198,7 @@ describe('Store component filters', () => {
198
<Text>
199
`);
200
210
- await act(
201
+ await actAsync(
202
async () =>
203
(store.componentFilters = [utils.createDisplayNameFilter('Ba')]),
204
);
@@ -219,7 +210,7 @@ describe('Store component filters', () => {
210
<Text>
211
`);
212
222
- await act(
213
+ await actAsync(
214
async () =>
215
(store.componentFilters = [utils.createDisplayNameFilter('B.z')]),
216
);
@@ -237,16 +228,14 @@ describe('Store component filters', () => {
228
it('should filter by path', async () => {
229
const Component = () => <div>Hi</div>;
230
240
- await act(async () =>
241
- legacyRender(<Component />, document.createElement('div')),
242
- );
231
+ await actAsync(async () => render(<Component />));
232
expect(store).toMatchInlineSnapshot(`
233
[root]
234
▾ <Component>
235
<div>
236
`);
237
249
- await act(
238
+ await actAsync(
239
async () =>
240
(store.componentFilters = [
241
utils.createLocationFilter(__filename.replace(__dirname, '')),
@@ -255,7 +244,7 @@ describe('Store component filters', () => {
244
245
expect(store).toMatchInlineSnapshot(`[root]`);
246
258
- await act(
247
+ await actAsync(
248
async () =>
249
(store.componentFilters = [
250
utils.createLocationFilter('this:is:a:made:up:path'),
@@ -277,7 +266,7 @@ describe('Store component filters', () => {
266
const Bar = () => <Foo />;
267
Bar.displayName = 'Bar(Foo(Component))';
268
280
- await act(async () => legacyRender(<Bar />, document.createElement('div')));
269
+ await actAsync(async () => render(<Bar />));
270
expect(store).toMatchInlineSnapshot(`
271
[root]
272
▾ <Component> [Bar][Foo]
@@ -286,7 +275,7 @@ describe('Store component filters', () => {
275
<div>
276
`);
277
289
- await act(
278
+ await actAsync(
279
async () => (store.componentFilters = [utils.createHOCFilter(true)]),
280
);
281
expect(store).toMatchInlineSnapshot(`
@@ -295,7 +284,7 @@ describe('Store component filters', () => {
284
<div>
285
`);
286
298
- await act(
287
+ await actAsync(
288
async () => (store.componentFilters = [utils.createHOCFilter(false)]),
289
);
290
expect(store).toMatchInlineSnapshot(`
@@ -309,7 +298,7 @@ describe('Store component filters', () => {
298
299
// @reactVersion >= 16.0
300
it('should not send a bridge update if the set of enabled filters has not changed', async () => {
312
- await act(
301
+ await actAsync(
302
async () => (store.componentFilters = [utils.createHOCFilter(true)]),
303
);
304
@@ -317,21 +306,21 @@ describe('Store component filters', () => {
306
throw Error('Unexpected component update');
307
});
308
320
- await act(
309
+ await actAsync(
310
async () =>
311
(store.componentFilters = [
312
utils.createHOCFilter(false),
313
utils.createHOCFilter(true),
314
]),
315
);
327
- await act(
316
+ await actAsync(
317
async () =>
318
(store.componentFilters = [
319
utils.createHOCFilter(true),
320
utils.createLocationFilter('abc', false),
321
]),
322
);
334
- await act(
323
+ await actAsync(
324
async () =>
325
(store.componentFilters = [
326
utils.createHOCFilter(true),
@@ -363,10 +352,7 @@ describe('Store component filters', () => {
352
utils.createElementTypeFilter(Types.ElementTypeSuspense),
353
];
354
366
- const container = document.createElement('div');
367
- await act(async () =>
368
- legacyRender(<Wrapper shouldSuspend={true} />, container),
369
- );
355
+ await actAsync(async () => render(<Wrapper shouldSuspend={true} />));
356
expect(store).toMatchInlineSnapshot(`
357
[root]
358
▾ <Wrapper>
@@ -374,18 +360,14 @@ describe('Store component filters', () => {
360
<div>
361
`);
362
377
- await act(async () =>
378
- legacyRender(<Wrapper shouldSuspend={false} />, container),
379
- );
363
+ await actAsync(async () => render(<Wrapper shouldSuspend={false} />));
364
expect(store).toMatchInlineSnapshot(`
365
[root]
366
▾ <Wrapper>
367
<Component>
368
`);
369
386
- await act(async () =>
387
- legacyRender(<Wrapper shouldSuspend={true} />, container),
388
- );
370
+ await actAsync(async () => render(<Wrapper shouldSuspend={true} />));
371
expect(store).toMatchInlineSnapshot(`
372
[root]
373
▾ <Wrapper>
@@ -395,8 +377,11 @@ describe('Store component filters', () => {
377
});
378
379
describe('inline errors and warnings', () => {
380
+ const {render: legacyRender} = getLegacyRenderImplementation();
381
+
382
// @reactVersion >= 17.0
399
- it('only counts for unfiltered components', async () => {
383
+ // @reactVersion <= 18.2
384
+ it('only counts for unfiltered components (legacy render)', async () => {
385
function ComponentWithWarning() {
386
console.warn('test-only: render warning');
387
return null;
@@ -411,15 +396,7 @@ describe('Store component filters', () => {
396
return null;
397
}
398
414
- // HACK This require() is needed (somewhere in the test) for this case to pass.
415
- // Without it, the legacyRender() call below causes this test to fail
416
- // because it requires "react-dom" for the first time,
417
- // which causes the console error() and warn() methods to be overridden again,
418
- // effectively disconnecting the DevTools override in 'react-devtools-shared/src/backend/console'.
419
- require('react-dom');
420
-
421
- const container = document.createElement('div');
422
- await act(
399
+ await actAsync(
400
async () =>
401
(store.componentFilters = [
402
utils.createDisplayNameFilter('Warning'),
@@ -433,7 +410,6 @@ describe('Store component filters', () => {
410
<ComponentWithWarning />
411
<ComponentWithWarningAndError />
412
</React.Fragment>,
436
- container,
413
);
414
});
415
@@ -441,7 +417,99 @@ describe('Store component filters', () => {
417
expect(store.errorCount).toBe(0);
418
expect(store.warningCount).toBe(0);
419
444
- await act(async () => (store.componentFilters = []));
420
+ await actAsync(async () => (store.componentFilters = []));
421
+ expect(store).toMatchInlineSnapshot(`
422
+ ✕ 2, ⚠ 2
423
+ [root]
424
+ <ComponentWithError> ✕
425
+ <ComponentWithWarning> ⚠
426
+ <ComponentWithWarningAndError> ✕⚠
427
+ `);
428
+
429
+ await actAsync(
430
+ async () =>
431
+ (store.componentFilters = [utils.createDisplayNameFilter('Warning')]),
432
+ );
433
+ expect(store).toMatchInlineSnapshot(`
434
+ ✕ 1, ⚠ 0
435
+ [root]
436
+ <ComponentWithError> ✕
437
+ `);
438
+
439
+ await actAsync(
440
+ async () =>
441
+ (store.componentFilters = [utils.createDisplayNameFilter('Error')]),
442
+ );
443
+ expect(store).toMatchInlineSnapshot(`
444
+ ✕ 0, ⚠ 1
445
+ [root]
446
+ <ComponentWithWarning> ⚠
447
+ `);
448
+
449
+ await actAsync(
450
+ async () =>
451
+ (store.componentFilters = [
452
+ utils.createDisplayNameFilter('Warning'),
453
+ utils.createDisplayNameFilter('Error'),
454
+ ]),
455
+ );
456
+ expect(store).toMatchInlineSnapshot(`[root]`);
457
+ expect(store.errorCount).toBe(0);
458
+ expect(store.warningCount).toBe(0);
459
+
460
+ await actAsync(async () => (store.componentFilters = []));
461
+ expect(store).toMatchInlineSnapshot(`
462
+ ✕ 2, ⚠ 2
463
+ [root]
464
+ <ComponentWithError> ✕
465
+ <ComponentWithWarning> ⚠
466
+ <ComponentWithWarningAndError> ✕⚠
467
+ `);
468
+ });
469
+
470
+ // @reactVersion >= 18
471
+ it('only counts for unfiltered components (createRoot)', async () => {
472
+ function ComponentWithWarning() {
473
+ console.warn('test-only: render warning');
474
+ return null;
475
+ }
476
+ function ComponentWithError() {
477
+ console.error('test-only: render error');
478
+ return null;
479
+ }
480
+ function ComponentWithWarningAndError() {
481
+ console.error('test-only: render error');
482
+ console.warn('test-only: render warning');
483
+ return null;
484
+ }
485
+
486
+ await actAsync(
487
+ async () =>
488
+ (store.componentFilters = [
489
+ utils.createDisplayNameFilter('Warning'),
490
+ utils.createDisplayNameFilter('Error'),
491
+ ]),
492
+ );
493
+
494
+ utils.act(
495
+ () =>
496
+ utils.withErrorsOrWarningsIgnored(['test-only:'], () => {
497
+ render(
498
+ <React.Fragment>
499
+ <ComponentWithError />
500
+ <ComponentWithWarning />
501
+ <ComponentWithWarningAndError />
502
+ </React.Fragment>,
503
+ );
504
+ }),
505
+ false,
506
+ );
507
+
508
+ expect(store).toMatchInlineSnapshot(``);
509
+ expect(store.errorCount).toBe(0);
510
+ expect(store.warningCount).toBe(0);
511
+
512
+ await actAsync(async () => (store.componentFilters = []));
513
expect(store).toMatchInlineSnapshot(`
514
✕ 2, ⚠ 2
515
[root]
@@ -450,7 +518,7 @@ describe('Store component filters', () => {
518
<ComponentWithWarningAndError> ✕⚠
519
`);
520
453
- await act(
521
+ await actAsync(
522
async () =>
523
(store.componentFilters = [utils.createDisplayNameFilter('Warning')]),
524
);
@@ -460,7 +528,7 @@ describe('Store component filters', () => {
528
<ComponentWithError> ✕
529
`);
530
463
- await act(
531
+ await actAsync(
532
async () =>
533
(store.componentFilters = [utils.createDisplayNameFilter('Error')]),
534
);
@@ -470,7 +538,7 @@ describe('Store component filters', () => {
538
<ComponentWithWarning> ⚠
539
`);
540
473
- await act(
541
+ await actAsync(
542
async () =>
543
(store.componentFilters = [
544
utils.createDisplayNameFilter('Warning'),
@@ -481,7 +549,7 @@ describe('Store component filters', () => {
549
expect(store.errorCount).toBe(0);
550
expect(store.warningCount).toBe(0);
551
484
- await act(async () => (store.componentFilters = []));
552
+ await actAsync(async () => (store.componentFilters = []));
553
expect(store).toMatchInlineSnapshot(`
554
✕ 2, ⚠ 2
555
[root]
packages/react-devtools-shared/src/__tests__/utils.js
+25
-2
@@ -21,6 +21,12 @@ import {ReactVersion} from '../../../../ReactVersions';
21
22
const requestedReactVersion = process.env.REACT_VERSION || ReactVersion;
23
export function getActDOMImplementation(): () => void | Promise<void> {
24
+ // This is for React < 17, where act wasn't shipped yet.
25
+ if (semver.lt(requestedReactVersion, '17.0.0')) {
26
+ require('react-dom/test-utils');
27
+ return cb => cb();
28
+ }
29
+
30
// This is for React < 18, where act was distributed in react-dom/test-utils.
31
if (semver.lt(requestedReactVersion, '18.0.0')) {
32
const ReactDOMTestUtils = require('react-dom/test-utils');
@@ -41,13 +47,30 @@ export function getActDOMImplementation(): () => void | Promise<void> {
47
throw new Error("Couldn't find any available act implementation");
48
}
49
50
+export function getActTestRendererImplementation(): () => void | Promise<void> {
51
+ // This is for React < 17, where act wasn't shipped yet.
52
+ if (semver.lt(requestedReactVersion, '17.0.0')) {
53
+ require('react-test-renderer');
54
+ return cb => cb();
55
+ }
56
+
57
+ const RTR = require('react-test-renderer');
58
+ if (RTR.act) {
59
+ return RTR.act;
60
+ }
61
+
62
+ throw new Error(
63
+ "Couldn't find any available act implementation in react-test-renderer",
64
+ );
65
+}
66
+
67
export function act(
68
callback: Function,
69
recursivelyFlush: boolean = true,
70
): void {
71
// act from react-test-renderer has some side effects on React DevTools
72
// it injects the renderer for DevTools, see ReactTestRenderer.js
50
- const {act: actTestRenderer} = require('react-test-renderer');
73
+ const actTestRenderer = getActTestRendererImplementation();
74
const actDOM = getActDOMImplementation();
75
76
actDOM(() => {
@@ -74,7 +97,7 @@ export async function actAsync(
97
): Promise<void> {
98
// act from react-test-renderer has some side effects on React DevTools
99
// it injects the renderer for DevTools, see ReactTestRenderer.js
77
- const {act: actTestRenderer} = require('react-test-renderer');
100
+ const actTestRenderer = getActTestRendererImplementation();
101
const actDOM = getActDOMImplementation();
102
103
await actDOM(async () => {