@samitouri / QOS-React-2 / commits / f0aafa1a7e

Convert a few more tests to waitFor test helpers (#26509)

Continuing my journey to migrate all the Scheduler flush* methods to async versions of the same helpers.

Andrew Clark committed Mar 29, 2023 at 17:02 UTC f0aafa1a7e3338871f60ac3ea8c1c92b8671520c
5 files changed +49 -44
packages/internal-test-utils/ReactInternalTestUtils.js
+10 -10
@@ -12,25 +12,25 @@ import enqueueTask from './enqueueTask';
12
13 export {act} from './internalAct';
14
15 -function assertYieldsWereCleared(Scheduler) {
16 - const actualYields = Scheduler.unstable_clearLog();
15 +function assertYieldsWereCleared(caller) {
16 + const actualYields = SchedulerMock.unstable_clearLog();
17 if (actualYields.length !== 0) {
18 const error = Error(
19 'The event log is not empty. Call assertLog(...) first.',
20 );
21 - Error.captureStackTrace(error, assertYieldsWereCleared);
21 + Error.captureStackTrace(error, caller);
22 throw error;
23 }
24 }
25
26 -async function waitForMicrotasks() {
26 +export async function waitForMicrotasks() {
27 return new Promise(resolve => {
28 enqueueTask(() => resolve());
29 });
30 }
31
32 export async function waitFor(expectedLog, options) {
33 - assertYieldsWereCleared(SchedulerMock);
33 + assertYieldsWereCleared(waitFor);
34
35 // Create the error object before doing any async work, to get a better
36 // stack trace.
@@ -79,7 +79,7 @@ ${diff(expectedLog, actualLog)}
79 }
80
81 export async function waitForAll(expectedLog) {
82 - assertYieldsWereCleared(SchedulerMock);
82 + assertYieldsWereCleared(waitForAll);
83
84 // Create the error object before doing any async work, to get a better
85 // stack trace.
@@ -110,7 +110,7 @@ ${diff(expectedLog, actualLog)}
110 }
111
112 export async function waitForThrow(expectedError: mixed): mixed {
113 - assertYieldsWereCleared(SchedulerMock);
113 + assertYieldsWereCleared(waitForThrow);
114
115 // Create the error object before doing any async work, to get a better
116 // stack trace.
@@ -160,7 +160,7 @@ ${diff(expectedError, x)}
160 // avoid using it in tests. It's really only for testing a particular
161 // implementation detail (update starvation prevention).
162 export async function unstable_waitForExpired(expectedLog): mixed {
163 - assertYieldsWereCleared(SchedulerMock);
163 + assertYieldsWereCleared(unstable_waitForExpired);
164
165 // Create the error object before doing any async work, to get a better
166 // stack trace.
@@ -189,7 +189,7 @@ ${diff(expectedLog, actualLog)}
189 // now because that's how untable_flushUntilNextPaint already worked, but maybe
190 // we should split these use cases into separate APIs.
191 export async function waitForPaint(expectedLog) {
192 - assertYieldsWereCleared(SchedulerMock);
192 + assertYieldsWereCleared(waitForPaint);
193
194 // Create the error object before doing any async work, to get a better
195 // stack trace.
@@ -219,7 +219,7 @@ ${diff(expectedLog, actualLog)}
219 }
220
221 export async function waitForDiscrete(expectedLog) {
222 - assertYieldsWereCleared(SchedulerMock);
222 + assertYieldsWereCleared(waitForDiscrete);
223
224 // Create the error object before doing any async work, to get a better
225 // stack trace.
packages/react-dom/src/__tests__/ReactMultiChildText-test.js
+11 -7
@@ -10,11 +10,12 @@
10 'use strict';
11
12 const React = require('react');
13 -const ReactDOM = require('react-dom');
13 +const ReactDOMClient = require('react-dom/client');
14 const ReactTestUtils = require('react-dom/test-utils');
15 +const act = require('internal-test-utils').act;
16
17 // Helpers
17 -const testAllPermutations = function (testCases) {
18 +const testAllPermutations = async function (testCases) {
19 for (let i = 0; i < testCases.length; i += 2) {
20 const renderWithChildren = testCases[i];
21 const expectedResultAfterRender = testCases[i + 1];
@@ -24,10 +25,11 @@ const testAllPermutations = function (testCases) {
25 const expectedResultAfterUpdate = testCases[j + 1];
26
27 const container = document.createElement('div');
27 - ReactDOM.render(<div>{renderWithChildren}</div>, container);
28 + const root = ReactDOMClient.createRoot(container);
29 + await act(() => root.render(<div>{renderWithChildren}</div>));
30 expectChildren(container, expectedResultAfterRender);
31
30 - ReactDOM.render(<div>{updateWithChildren}</div>, container);
32 + await act(() => root.render(<div>{updateWithChildren}</div>));
33 expectChildren(container, expectedResultAfterUpdate);
34 }
35 }
@@ -75,10 +77,12 @@ const expectChildren = function (container, children) {
77 * faster to render and update.
78 */
79 describe('ReactMultiChildText', () => {
78 - it('should correctly handle all possible children for render and update', () => {
79 - expect(() => {
80 + jest.setTimeout(20000);
81 +
82 + it('should correctly handle all possible children for render and update', async () => {
83 + await expect(async () => {
84 // prettier-ignore
81 - testAllPermutations([
85 + await testAllPermutations([
86 // basic values
87 undefined, [],
88 null, [],
packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js
+8 -11
@@ -1703,17 +1703,14 @@ describe('ReactHooks', () => {
1703 return null;
1704 }
1705
1706 - await act(() => {
1707 - ReactTestRenderer.unstable_batchedUpdates(() => {
1708 - ReactTestRenderer.create(
1709 - <>
1710 - <A />
1711 - <B />
1712 - </>,
1713 - );
1714 - expect(() => Scheduler.unstable_flushAll()).toThrow('Hello');
1715 - });
1716 - });
1706 + expect(() => {
1707 + ReactTestRenderer.create(
1708 + <>
1709 + <A />
1710 + <B />
1711 + </>,
1712 + );
1713 + }).toThrow('Hello');
1714
1715 if (__DEV__) {
1716 expect(console.error).toHaveBeenCalledTimes(2);
packages/react-reconciler/src/__tests__/ReactIsomorphicAct-test.js
+6 -7
@@ -16,6 +16,7 @@ let use;
16 let Suspense;
17 let DiscreteEventPriority;
18 let startTransition;
19 +let waitForMicrotasks;
20
21 describe('isomorphic act()', () => {
22 beforeEach(() => {
@@ -28,6 +29,8 @@ describe('isomorphic act()', () => {
29 use = React.use;
30 Suspense = React.Suspense;
31 startTransition = React.startTransition;
32 +
33 + waitForMicrotasks = require('internal-test-utils').waitForMicrotasks;
34 });
35
36 beforeEach(() => {
@@ -51,7 +54,7 @@ describe('isomorphic act()', () => {
54 // Nothing has rendered yet
55 expect(root).toMatchRenderedOutput(null);
56 // Flush the microtasks by awaiting
54 - await null;
57 + await waitForMicrotasks();
58 expect(root).toMatchRenderedOutput('A');
59
60 // Now do the same thing but wrap the update with `act`. No
@@ -229,9 +232,7 @@ describe('isomorphic act()', () => {
232 //
233 // The exact number of microtasks is an implementation detail; just needs
234 // to happen when the microtask queue is flushed.
232 - await null;
233 - await null;
234 - await null;
235 + await waitForMicrotasks();
236
237 expect(console.error).toHaveBeenCalledTimes(1);
238 expect(console.error.mock.calls[0][0]).toContain(
@@ -282,9 +283,7 @@ describe('isomorphic act()', () => {
283 //
284 // The exact number of microtasks is an implementation detail; just needs
285 // to happen when the microtask queue is flushed.
285 - await null;
286 - await null;
287 - await null;
286 + await waitForMicrotasks();
287
288 expect(console.error).toHaveBeenCalledTimes(0);
289
packages/react-reconciler/src/__tests__/ReactSchedulerIntegration-test.js
+14 -9
@@ -102,6 +102,7 @@ describe('ReactSchedulerIntegration', () => {
102 const root = ReactNoop.createRoot();
103 root.render('Initial');
104 await waitForAll([]);
105 + expect(root).toMatchRenderedOutput('Initial');
106
107 scheduleCallback(NormalPriority, () => Scheduler.log('A'));
108 scheduleCallback(NormalPriority, () => Scheduler.log('B'));
@@ -112,16 +113,22 @@ describe('ReactSchedulerIntegration', () => {
113 root.render('Update');
114 });
115
115 - // Advance time just to be sure the next tasks have lower priority
116 - Scheduler.unstable_advanceTime(2000);
116 + // Perform just a little bit of work. By now, the React task will have
117 + // already been scheduled, behind A, B, and C.
118 + await waitFor(['A']);
119
120 + // Schedule some additional tasks. These won't fire until after the React
121 + // update has finished.
122 scheduleCallback(NormalPriority, () => Scheduler.log('D'));
123 scheduleCallback(NormalPriority, () => Scheduler.log('E'));
124
125 // Flush everything up to the next paint. Should yield after the
126 // React commit.
123 - Scheduler.unstable_flushUntilNextPaint();
124 - assertLog(['A', 'B', 'C']);
127 + await waitForPaint(['B', 'C']);
128 + expect(root).toMatchRenderedOutput('Update');
129 +
130 + // Now flush the rest of the work.
131 + await waitForAll(['D', 'E']);
132 });
133
134 // @gate www
@@ -213,6 +220,7 @@ describe(
220 waitForPaint = InternalTestUtils.waitForPaint;
221 assertLog = InternalTestUtils.assertLog;
222 waitFor = InternalTestUtils.waitFor;
223 + act = InternalTestUtils.act;
224 });
225
226 afterEach(() => {
@@ -293,8 +301,7 @@ describe(
301 // Start logging whenever shouldYield is called
302 logDuringShouldYield = true;
303 // Let's call it once to confirm the mock actually works
296 - Scheduler.unstable_shouldYield();
297 - assertLog(['shouldYield']);
304 + await waitFor(['shouldYield']);
305
306 // Expire the task
307 Scheduler.unstable_advanceTime(10000);
@@ -307,11 +314,9 @@ describe(
314 startTransition(() => {
315 ReactNoop.render(<App />);
316 });
310 -
317 // Because the render expired, React should finish the tree without
318 // consulting `shouldYield` again
313 - Scheduler.unstable_flushNumberOfYields(1);
314 - assertLog(['B', 'C']);
319 + await waitFor(['B', 'C']);
320 });
321 });
322 },