1
+/**
2
+ * Copyright (c) Meta Platforms, Inc. and affiliates.
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+// TODO: Move `internalAct` and other test helpers to this package, too
9
+
10
+import * as SchedulerMock from 'scheduler/unstable_mock';
11
+import {diff} from 'jest-diff';
12
+import {equals} from '@jest/expect-utils';
13
+
14
+function assertYieldsWereCleared(Scheduler) {
15
+ const actualYields = Scheduler.unstable_clearYields();
16
+ if (actualYields.length !== 0) {
17
+ const error = Error(
18
+ 'The event log is not empty. Call assertLog(...) first.',
19
+ );
20
+ Error.captureStackTrace(error, assertYieldsWereCleared);
21
+ throw error;
22
+ }
23
+}
24
+
25
+export async function waitFor(expectedLog) {
26
+ assertYieldsWereCleared(SchedulerMock);
27
+
28
+ // Create the error object before doing any async work, to get a better
29
+ // stack trace.
30
+ const error = new Error();
31
+ Error.captureStackTrace(error, waitFor);
32
+
33
+ const actualLog = [];
34
+ do {
35
+ // Wait until end of current task/microtask.
36
+ await null;
37
+ if (SchedulerMock.unstable_hasPendingWork()) {
38
+ SchedulerMock.unstable_flushNumberOfYields(
39
+ expectedLog.length - actualLog.length,
40
+ );
41
+ actualLog.push(...SchedulerMock.unstable_clearYields());
42
+ if (expectedLog.length > actualLog.length) {
43
+ // Continue flushing until we've logged the expected number of items.
44
+ } else {
45
+ // Once we've reached the expected sequence, wait one more microtask to
46
+ // flush any remaining synchronous work.
47
+ await null;
48
+ actualLog.push(...SchedulerMock.unstable_clearYields());
49
+ break;
50
+ }
51
+ } else {
52
+ // There's no pending work, even after a microtask.
53
+ break;
54
+ }
55
+ } while (true);
56
+
57
+ if (equals(actualLog, expectedLog)) {
58
+ return;
59
+ }
60
+
61
+ error.message = `
62
+Expected sequence of events did not occur.
63
+
64
+${diff(expectedLog, actualLog)}
65
+`;
66
+ throw error;
67
+}
68
+
69
+export async function waitForAll(expectedLog) {
70
+ assertYieldsWereCleared(SchedulerMock);
71
+
72
+ // Create the error object before doing any async work, to get a better
73
+ // stack trace.
74
+ const error = new Error();
75
+ Error.captureStackTrace(error, waitFor);
76
+
77
+ do {
78
+ // Wait until end of current task/microtask.
79
+ await null;
80
+ if (!SchedulerMock.unstable_hasPendingWork()) {
81
+ // There's no pending work, even after a microtask. Stop flushing.
82
+ break;
83
+ }
84
+ SchedulerMock.unstable_flushAllWithoutAsserting();
85
+ } while (true);
86
+
87
+ const actualLog = SchedulerMock.unstable_clearYields();
88
+ if (equals(actualLog, expectedLog)) {
89
+ return;
90
+ }
91
+
92
+ error.message = `
93
+Expected sequence of events did not occur.
94
+
95
+${diff(expectedLog, actualLog)}
96
+`;
97
+ throw error;
98
+}
99
+
100
+export async function waitForThrow(expectedError: mixed) {
101
+ assertYieldsWereCleared(SchedulerMock);
102
+
103
+ // Create the error object before doing any async work, to get a better
104
+ // stack trace.
105
+ const error = new Error();
106
+ Error.captureStackTrace(error, waitFor);
107
+
108
+ do {
109
+ // Wait until end of current task/microtask.
110
+ await null;
111
+ if (!SchedulerMock.unstable_hasPendingWork()) {
112
+ // There's no pending work, even after a microtask. Stop flushing.
113
+ error.message = 'Expected something to throw, but nothing did.';
114
+ throw error;
115
+ }
116
+ try {
117
+ SchedulerMock.unstable_flushAllWithoutAsserting();
118
+ } catch (x) {
119
+ if (equals(x, expectedError)) {
120
+ return;
121
+ }
122
+ if (typeof x === 'object' && x !== null && x.message === expectedError) {
123
+ return;
124
+ }
125
+ error.message = `
126
+Expected error was not thrown.
127
+
128
+${diff(expectedError, x)}
129
+`;
130
+ throw error;
131
+ }
132
+ } while (true);
133
+}
134
+
135
+// TODO: This name is a bit misleading currently because it will stop as soon as
136
+// React yields for any reason, not just for a paint. I've left it this way for
137
+// now because that's how untable_flushUntilNextPaint already worked, but maybe
138
+// we should split these use cases into separate APIs.
139
+export async function waitForPaint(expectedLog) {
140
+ assertYieldsWereCleared(SchedulerMock);
141
+
142
+ // Create the error object before doing any async work, to get a better
143
+ // stack trace.
144
+ const error = new Error();
145
+ Error.captureStackTrace(error, waitFor);
146
+
147
+ // Wait until end of current task/microtask.
148
+ await null;
149
+ if (SchedulerMock.unstable_hasPendingWork()) {
150
+ // Flush until React yields.
151
+ SchedulerMock.unstable_flushUntilNextPaint();
152
+ // Wait one more microtask to flush any remaining synchronous work.
153
+ await null;
154
+ }
155
+
156
+ const actualLog = SchedulerMock.unstable_clearYields();
157
+ if (equals(actualLog, expectedLog)) {
158
+ return;
159
+ }
160
+
161
+ error.message = `
162
+Expected sequence of events did not occur.
163
+
164
+${diff(expectedLog, actualLog)}
165
+`;
166
+ throw error;
167
+}
168
+
169
+export function assertLog(expectedLog) {
170
+ const actualLog = SchedulerMock.unstable_clearYields();
171
+ if (equals(actualLog, expectedLog)) {
172
+ return;
173
+ }
174
+
175
+ const error = new Error(`
176
+Expected sequence of events did not occur.
177
+
178
+${diff(expectedLog, actualLog)}
179
+`);
180
+ Error.captureStackTrace(error, assertLog);
181
+ throw error;
182
+}