Improve tests that use discrete events (#20667)
Ricky committed
Jan 26, 2021 at 19:15 UTC
9c32622cf04e70dd40463ece25e83b7cc311b0ed
8 files changed
+114
-99
packages/react-dom/src/__tests__/ReactDOMHooks-test.js
+11
-6
@@ -12,6 +12,7 @@
12
let React;
13
let ReactDOM;
14
let Scheduler;
15
+let act;
16
17
describe('ReactDOMHooks', () => {
18
let container;
@@ -22,6 +23,7 @@ describe('ReactDOMHooks', () => {
23
React = require('react');
24
ReactDOM = require('react-dom');
25
Scheduler = require('scheduler');
26
+ act = require('react-dom/test-utils').unstable_concurrentAct;
27
28
container = document.createElement('div');
29
document.body.appendChild(container);
@@ -106,7 +108,7 @@ describe('ReactDOMHooks', () => {
108
});
109
110
// @gate experimental
109
- it('should not bail out when an update is scheduled from within an event handler in Concurrent Mode', () => {
111
+ it('should not bail out when an update is scheduled from within an event handler in Concurrent Mode', async () => {
112
const {createRef, useCallback, useState} = React;
113
114
const Example = ({inputRef, labelRef}) => {
@@ -132,11 +134,14 @@ describe('ReactDOMHooks', () => {
134
Scheduler.unstable_flushAll();
135
136
inputRef.current.value = 'abc';
135
- inputRef.current.dispatchEvent(
136
- new Event('input', {bubbles: true, cancelable: true}),
137
- );
138
-
139
- Scheduler.unstable_flushAll();
137
+ await act(async () => {
138
+ inputRef.current.dispatchEvent(
139
+ new Event('input', {
140
+ bubbles: true,
141
+ cancelable: true,
142
+ }),
143
+ );
144
+ });
145
146
expect(labelRef.current.innerHTML).toBe('abc');
147
});
packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js
+4
-4
@@ -187,7 +187,7 @@ function runActTests(label, render, unmount, rerender) {
187
expect(Scheduler).toHaveYielded([100]);
188
});
189
190
- it('flushes effects on every call', () => {
190
+ it('flushes effects on every call', async () => {
191
function App() {
192
const [ctr, setCtr] = React.useState(0);
193
React.useEffect(() => {
@@ -209,16 +209,16 @@ function runActTests(label, render, unmount, rerender) {
209
button.dispatchEvent(new MouseEvent('click', {bubbles: true}));
210
}
211
212
- act(() => {
212
+ await act(async () => {
213
click();
214
click();
215
click();
216
});
217
// it consolidates the 3 updates, then fires the effect
218
expect(Scheduler).toHaveYielded([3]);
219
- act(click);
219
+ await act(async () => click());
220
expect(Scheduler).toHaveYielded([4]);
221
- act(click);
221
+ await act(async () => click());
222
expect(Scheduler).toHaveYielded([5]);
223
expect(button.innerHTML).toBe('5');
224
});
packages/react-dom/src/events/plugins/__tests__/ChangeEventPlugin-test.js
+1
-1
@@ -685,7 +685,7 @@ describe('ChangeEventPlugin', () => {
685
});
686
687
// @gate experimental
688
- it('is async for non-input events', () => {
688
+ it('is async for non-input events', async () => {
689
const root = ReactDOM.unstable_createRoot(container);
690
let input;
691
packages/react-dom/src/events/plugins/__tests__/SimpleEventPlugin-test.js
+12
-8
@@ -13,6 +13,7 @@ describe('SimpleEventPlugin', function() {
13
let React;
14
let ReactDOM;
15
let Scheduler;
16
+ let TestUtils;
17
18
let onClick;
19
let container;
@@ -39,6 +40,7 @@ describe('SimpleEventPlugin', function() {
40
React = require('react');
41
ReactDOM = require('react-dom');
42
Scheduler = require('scheduler');
43
+ TestUtils = require('react-dom/test-utils');
44
45
onClick = jest.fn();
46
});
@@ -314,7 +316,7 @@ describe('SimpleEventPlugin', function() {
316
});
317
318
// @gate experimental
317
- it('end result of many interactive updates is deterministic', () => {
319
+ it('end result of many interactive updates is deterministic', async () => {
320
container = document.createElement('div');
321
const root = ReactDOM.unstable_createRoot(container);
322
document.body.appendChild(container);
@@ -361,12 +363,14 @@ describe('SimpleEventPlugin', function() {
363
expect(button.textContent).toEqual('Count: 0');
364
365
// Click the button many more times
364
- click();
365
- click();
366
- click();
367
- click();
368
- click();
369
- click();
366
+ await TestUtils.act(async () => {
367
+ click();
368
+ click();
369
+ click();
370
+ click();
371
+ click();
372
+ click();
373
+ });
374
375
// Flush the remaining work
376
Scheduler.unstable_flushAll();
@@ -375,7 +379,7 @@ describe('SimpleEventPlugin', function() {
379
});
380
381
// @gate experimental
378
- it('flushes discrete updates in order', () => {
382
+ it('flushes discrete updates in order', async () => {
383
container = document.createElement('div');
384
document.body.appendChild(container);
385
packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js
+27
-24
@@ -232,7 +232,8 @@ describe('ReactIncrementalErrorHandling', () => {
232
expect(ReactNoop.getChildren()).toEqual([span('Caught an error: oops!')]);
233
});
234
235
- it("retries at a lower priority if there's additional pending work", () => {
235
+ // @gate experimental
236
+ it("retries at a lower priority if there's additional pending work", async () => {
237
function App(props) {
238
if (props.isBroken) {
239
Scheduler.unstable_yieldValue('error');
@@ -252,14 +253,14 @@ describe('ReactIncrementalErrorHandling', () => {
253
});
254
}
255
255
- ReactNoop.discreteUpdates(() => {
256
- ReactNoop.render(<App isBroken={true} />, onCommit);
257
- });
256
+ ReactNoop.render(<App isBroken={true} />, onCommit);
257
expect(Scheduler).toFlushAndYieldThrough(['error']);
258
interrupt();
259
261
- // This update is in a separate batch
262
- ReactNoop.render(<App isBroken={false} />, onCommit);
260
+ React.unstable_startTransition(() => {
261
+ // This update is in a separate batch
262
+ ReactNoop.render(<App isBroken={false} />, onCommit);
263
+ });
264
265
expect(Scheduler).toFlushAndYieldThrough([
266
// The first render fails. But because there's a lower priority pending
@@ -311,16 +312,16 @@ describe('ReactIncrementalErrorHandling', () => {
312
});
313
}
314
314
- ReactNoop.discreteUpdates(() => {
315
- ReactNoop.render(<App isBroken={true} />, onCommit);
316
- });
315
+ ReactNoop.render(<App isBroken={true} />, onCommit);
316
expect(Scheduler).toFlushAndYieldThrough(['error']);
317
interrupt();
318
319
expect(ReactNoop).toMatchRenderedOutput(null);
320
322
- // This update is in a separate batch
323
- ReactNoop.render(<App isBroken={false} />, onCommit);
321
+ React.unstable_startTransition(() => {
322
+ // This update is in a separate batch
323
+ ReactNoop.render(<App isBroken={false} />, onCommit);
324
+ });
325
326
expect(Scheduler).toFlushAndYieldThrough([
327
// The first render fails. But because there's a lower priority pending
@@ -1786,6 +1787,7 @@ describe('ReactIncrementalErrorHandling', () => {
1787
});
1788
}
1789
1790
+ // @gate experimental
1791
it('uncaught errors should be discarded if the render is aborted', async () => {
1792
const root = ReactNoop.createRoot();
1793
@@ -1795,22 +1797,24 @@ describe('ReactIncrementalErrorHandling', () => {
1797
}
1798
1799
await ReactNoop.act(async () => {
1798
- ReactNoop.discreteUpdates(() => {
1799
- root.render(<Oops />);
1800
- });
1800
+ root.render(<Oops />);
1801
+
1802
// Render past the component that throws, then yield.
1803
expect(Scheduler).toFlushAndYieldThrough(['Oops']);
1804
expect(root).toMatchRenderedOutput(null);
1805
// Interleaved update. When the root completes, instead of throwing the
1806
// error, it should try rendering again. This update will cause it to
1807
// recover gracefully.
1807
- root.render('Everything is fine.');
1808
+ React.unstable_startTransition(() => {
1809
+ root.render('Everything is fine.');
1810
+ });
1811
});
1812
1813
// Should finish without throwing.
1814
expect(root).toMatchRenderedOutput('Everything is fine.');
1815
});
1816
1817
+ // @gate experimental
1818
it('uncaught errors are discarded if the render is aborted, case 2', async () => {
1819
const {useState} = React;
1820
const root = ReactNoop.createRoot();
@@ -1835,21 +1839,20 @@ describe('ReactIncrementalErrorHandling', () => {
1839
});
1840
1841
await ReactNoop.act(async () => {
1838
- // Schedule a high pri and a low pri update on the root.
1839
- ReactNoop.discreteUpdates(() => {
1840
- root.render(<Oops />);
1842
+ // Schedule a default pri and a low pri update on the root.
1843
+ root.render(<Oops />);
1844
+ React.unstable_startTransition(() => {
1845
+ root.render(<AllGood />);
1846
});
1842
- root.render(<AllGood />);
1843
- // Render through just the high pri update. The low pri update remains on
1847
+
1848
+ // Render through just the default pri update. The low pri update remains on
1849
// the queue.
1850
expect(Scheduler).toFlushAndYieldThrough(['Everything is fine.']);
1851
1847
- // Schedule a high pri update on a child that triggers an error.
1852
+ // Schedule a default pri update on a child that triggers an error.
1853
// The root should capture this error. But since there's still a pending
1854
// update on the root, the error should be suppressed.
1850
- ReactNoop.discreteUpdates(() => {
1851
- setShouldThrow(true);
1852
- });
1855
+ setShouldThrow(true);
1856
});
1857
// Should render the final state without throwing the error.
1858
expect(Scheduler).toHaveYielded(['Everything is fine.']);
packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js
+44
-47
@@ -570,14 +570,13 @@ describe('ReactSuspenseWithNoopRenderer', () => {
570
);
571
}
572
573
- // Schedule a high pri update and a low pri update, without rendering in
574
- // between.
575
- ReactNoop.discreteUpdates(() => {
576
- // High pri
577
- ReactNoop.render(<App />);
578
- });
573
+ // Schedule a default pri update and a low pri update, without rendering in between.
574
+ // Default pri
575
+ ReactNoop.render(<App />);
576
// Low pri
580
- ReactNoop.render(<App hide={true} />);
577
+ React.unstable_startTransition(() => {
578
+ ReactNoop.render(<App hide={true} />);
579
+ });
580
581
expect(Scheduler).toFlushAndYield([
582
// The first update suspends
@@ -1879,9 +1878,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1878
ReactNoop.render(<Foo />);
1879
expect(Scheduler).toFlushAndYield(['Foo']);
1880
1882
- ReactNoop.discreteUpdates(() =>
1883
- ReactNoop.render(<Foo renderContent={true} />),
1884
- );
1881
+ ReactNoop.render(<Foo renderContent={true} />);
1882
expect(Scheduler).toFlushAndYieldThrough(['Foo']);
1883
1884
// Advance some time.
@@ -3080,48 +3077,48 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3077
// Schedule an update inside the Suspense boundary that suspends.
3078
setAppText('B');
3079
expect(Scheduler).toFlushAndYield(['Suspend! [B]', 'Loading...']);
3080
+ });
3081
3084
- // Commit the placeholder
3085
- await advanceTimers(250);
3086
- expect(root).toMatchRenderedOutput(
3087
- <>
3088
- <span hidden={true} prop="A" />
3089
- <span prop="Loading..." />
3090
- </>,
3091
- );
3082
+ expect(root).toMatchRenderedOutput(
3083
+ <>
3084
+ <span hidden={true} prop="A" />
3085
+ <span prop="Loading..." />
3086
+ </>,
3087
+ );
3088
3093
- // Schedule a high pri update on the boundary, and a lower pri update
3094
- // on the fallback. We're testing to make sure the fallback can still
3095
- // update even though the primary tree is suspended.{
3096
- ReactNoop.discreteUpdates(() => {
3097
- setAppText('C');
3089
+ // Schedule a default pri update on the boundary, and a lower pri update
3090
+ // on the fallback. We're testing to make sure the fallback can still
3091
+ // update even though the primary tree is suspended.
3092
+ await ReactNoop.act(async () => {
3093
+ setAppText('C');
3094
+ React.unstable_startTransition(() => {
3095
+ setFallbackText('Still loading...');
3096
});
3099
- setFallbackText('Still loading...');
3097
+ });
3098
3101
- expect(Scheduler).toFlushAndYield([
3102
- // First try to render the high pri update. Still suspended.
3103
- 'Suspend! [C]',
3104
- 'Loading...',
3099
+ expect(Scheduler).toHaveYielded([
3100
+ // First try to render the high pri update. Still suspended.
3101
+ 'Suspend! [C]',
3102
+ 'Loading...',
3103
3106
- // In the expiration times model, once the high pri update suspends,
3107
- // we can't be sure if there's additional work at a lower priority
3108
- // that might unblock the tree. We do know that there's a lower
3109
- // priority update *somehwere* in the entire root, though (the update
3110
- // to the fallback). So we try rendering one more time, just in case.
3111
- // TODO: We shouldn't need to do this with lanes, because we always
3112
- // know exactly which lanes have pending work in each tree.
3113
- 'Suspend! [C]',
3114
-
3115
- // Then complete the update to the fallback.
3116
- 'Still loading...',
3117
- ]);
3118
- expect(root).toMatchRenderedOutput(
3119
- <>
3120
- <span hidden={true} prop="A" />
3121
- <span prop="Still loading..." />
3122
- </>,
3123
- );
3124
- });
3104
+ // In the expiration times model, once the high pri update suspends,
3105
+ // we can't be sure if there's additional work at a lower priority
3106
+ // that might unblock the tree. We do know that there's a lower
3107
+ // priority update *somehwere* in the entire root, though (the update
3108
+ // to the fallback). So we try rendering one more time, just in case.
3109
+ // TODO: We shouldn't need to do this with lanes, because we always
3110
+ // know exactly which lanes have pending work in each tree.
3111
+ 'Suspend! [C]',
3112
+
3113
+ // Then complete the update to the fallback.
3114
+ 'Still loading...',
3115
+ ]);
3116
+ expect(root).toMatchRenderedOutput(
3117
+ <>
3118
+ <span hidden={true} prop="A" />
3119
+ <span prop="Still loading..." />
3120
+ </>,
3121
+ );
3122
},
3123
);
3124
packages/react-reconciler/src/__tests__/useMutableSource-test.internal.js
+5
-4
@@ -1397,12 +1397,13 @@ describe('useMutableSource', () => {
1397
// Now mutate A. Both hooks should update.
1398
// This is at high priority so that it doesn't get batched with default
1399
// priority updates that might fire during the passive effect
1400
- ReactNoop.discreteUpdates(() => {
1401
- mutateA('a1');
1400
+ await ReactNoop.act(async () => {
1401
+ ReactNoop.discreteUpdates(() => {
1402
+ mutateA('a1');
1403
+ });
1404
});
1403
- expect(Scheduler).toFlushUntilNextPaint([]);
1405
1405
- expect(root.getChildrenAsJSX()).toEqual('first: a1, second: a1');
1406
+ expect(root).toMatchRenderedOutput('first: a1, second: a1');
1407
});
1408
1409
expect(root.getChildrenAsJSX()).toEqual('first: a1, second: a1');
packages/react-refresh/src/__tests__/ReactFresh-test.js
+10
-5
@@ -2409,7 +2409,7 @@ describe('ReactFresh', () => {
2409
}
2410
});
2411
2412
- it('can hot reload offscreen components', () => {
2412
+ it('can hot reload offscreen components', async () => {
2413
if (__DEV__ && __EXPERIMENTAL__) {
2414
const AppV1 = prepare(() => {
2415
function Hello() {
@@ -2471,10 +2471,15 @@ describe('ReactFresh', () => {
2471
expect(el.firstChild.textContent).toBe('0');
2472
expect(el.firstChild.style.color).toBe('red');
2473
2474
- el.firstChild.dispatchEvent(new MouseEvent('click', {bubbles: true}));
2475
- expect(el.firstChild.textContent).toBe('0');
2476
- expect(el.firstChild.style.color).toBe('red');
2477
- expect(Scheduler).toFlushAndYieldThrough(['Hello#layout']);
2474
+ await act(async () => {
2475
+ el.firstChild.dispatchEvent(
2476
+ new MouseEvent('click', {
2477
+ bubbles: true,
2478
+ }),
2479
+ );
2480
+ });
2481
+
2482
+ expect(Scheduler).toHaveYielded(['Hello#layout']);
2483
expect(el.firstChild.textContent).toBe('1');
2484
expect(el.firstChild.style.color).toBe('red');
2485