Skip to content

Commit 1c43d0a

Browse files
authored
Unify serverAct helpers (facebook#33327)
This uses the richer `serverAct` helper that we already use in other tests. This avoids using the `Scheduler`. We don't use that package on the server so it doesn't make sense to simulate going through it. Additionally, we really should be getting rid of it on the client too to favor `postTask` polyfills.
1 parent 1835b3f commit 1c43d0a

18 files changed

+94
-255
lines changed

packages/internal-test-utils/internalAct.js

Lines changed: 45 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ export async function act<T>(scope: () => Thenable<T>): Thenable<T> {
138138
// those will also fire now, too, which is not ideal. (The public
139139
// version of `act` doesn't do this.) For this reason, we should try
140140
// to avoid using timers in our internal tests.
141+
j.runAllTicks();
141142
j.runOnlyPendingTimers();
142143
// If a committing a fallback triggers another update, it might not
143144
// get scheduled until a microtask. So wait one more time.
@@ -194,6 +195,39 @@ export async function act<T>(scope: () => Thenable<T>): Thenable<T> {
194195
}
195196
}
196197

198+
async function waitForTasksAndTimers(error: Error) {
199+
do {
200+
// Wait until end of current task/microtask.
201+
await waitForMicrotasks();
202+
203+
// $FlowFixMe[cannot-resolve-name]: Flow doesn't know about global Jest object
204+
if (jest.isEnvironmentTornDown()) {
205+
error.message =
206+
'The Jest environment was torn down before `act` completed. This ' +
207+
'probably means you forgot to `await` an `act` call.';
208+
throw error;
209+
}
210+
211+
// $FlowFixMe[cannot-resolve-name]: Flow doesn't know about global Jest object
212+
const j = jest;
213+
if (j.getTimerCount() > 0) {
214+
// There's a pending timer. Flush it now. We only do this in order to
215+
// force Suspense fallbacks to display; the fact that it's a timer
216+
// is an implementation detail. If there are other timers scheduled,
217+
// those will also fire now, too, which is not ideal. (The public
218+
// version of `act` doesn't do this.) For this reason, we should try
219+
// to avoid using timers in our internal tests.
220+
j.runAllTicks();
221+
j.runOnlyPendingTimers();
222+
// If a committing a fallback triggers another update, it might not
223+
// get scheduled until a microtask. So wait one more time.
224+
await waitForMicrotasks();
225+
} else {
226+
break;
227+
}
228+
} while (true);
229+
}
230+
197231
export async function serverAct<T>(scope: () => Thenable<T>): Thenable<T> {
198232
// We require every `act` call to assert console logs
199233
// with one of the assertion helpers. Fails if not empty.
@@ -233,37 +267,17 @@ export async function serverAct<T>(scope: () => Thenable<T>): Thenable<T> {
233267
}
234268

235269
try {
236-
const result = await scope();
237-
238-
do {
239-
// Wait until end of current task/microtask.
240-
await waitForMicrotasks();
241-
242-
// $FlowFixMe[cannot-resolve-name]: Flow doesn't know about global Jest object
243-
if (jest.isEnvironmentTornDown()) {
244-
error.message =
245-
'The Jest environment was torn down before `act` completed. This ' +
246-
'probably means you forgot to `await` an `act` call.';
247-
throw error;
248-
}
249-
250-
// $FlowFixMe[cannot-resolve-name]: Flow doesn't know about global Jest object
251-
const j = jest;
252-
if (j.getTimerCount() > 0) {
253-
// There's a pending timer. Flush it now. We only do this in order to
254-
// force Suspense fallbacks to display; the fact that it's a timer
255-
// is an implementation detail. If there are other timers scheduled,
256-
// those will also fire now, too, which is not ideal. (The public
257-
// version of `act` doesn't do this.) For this reason, we should try
258-
// to avoid using timers in our internal tests.
259-
j.runOnlyPendingTimers();
260-
// If a committing a fallback triggers another update, it might not
261-
// get scheduled until a microtask. So wait one more time.
262-
await waitForMicrotasks();
263-
} else {
264-
break;
265-
}
266-
} while (true);
270+
const promise = scope();
271+
// $FlowFixMe[prop-missing]
272+
if (promise && typeof promise.catch === 'function') {
273+
// $FlowFixMe[incompatible-use]
274+
promise.catch(() => {}); // Handle below
275+
}
276+
// See if we need to do some work to unblock the promise first.
277+
await waitForTasksAndTimers(error);
278+
const result = await promise;
279+
// Then wait to flush the result.
280+
await waitForTasksAndTimers(error);
267281

268282
if (thrownErrors.length > 0) {
269283
// Rethrow any errors logged by the global error handling.

packages/react-dom/src/__tests__/ReactClassComponentPropResolutionFizz-test.js

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,18 +22,18 @@ let ReactDOMServer;
2222
let Scheduler;
2323
let assertLog;
2424
let container;
25-
let act;
25+
let serverAct;
2626

2727
describe('ReactClassComponentPropResolutionFizz', () => {
2828
beforeEach(() => {
2929
jest.resetModules();
3030
Scheduler = require('scheduler');
31-
patchMessageChannel(Scheduler);
32-
act = require('internal-test-utils').act;
31+
patchMessageChannel();
3332

3433
React = require('react');
3534
ReactDOMServer = require('react-dom/server.browser');
3635
assertLog = require('internal-test-utils').assertLog;
36+
serverAct = require('internal-test-utils').serverAct;
3737
container = document.createElement('div');
3838
document.body.appendChild(container);
3939
});
@@ -42,17 +42,6 @@ describe('ReactClassComponentPropResolutionFizz', () => {
4242
document.body.removeChild(container);
4343
});
4444

45-
async function serverAct(callback) {
46-
let maybePromise;
47-
await act(() => {
48-
maybePromise = callback();
49-
if (maybePromise && typeof maybePromise.catch === 'function') {
50-
maybePromise.catch(() => {});
51-
}
52-
});
53-
return maybePromise;
54-
}
55-
5645
async function readIntoContainer(stream) {
5746
const reader = stream.getReader();
5847
let result = '';

packages/react-dom/src/__tests__/ReactDOMFizzDeferredValue-test.js

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ global.ReadableStream =
2121
global.TextEncoder = require('util').TextEncoder;
2222

2323
let act;
24+
let serverAct;
2425
let assertLog;
2526
let waitForPaint;
2627
let container;
@@ -35,8 +36,9 @@ describe('ReactDOMFizzForm', () => {
3536
beforeEach(() => {
3637
jest.resetModules();
3738
Scheduler = require('scheduler');
38-
patchMessageChannel(Scheduler);
39+
patchMessageChannel();
3940
act = require('internal-test-utils').act;
41+
serverAct = require('internal-test-utils').serverAct;
4042
React = require('react');
4143
ReactDOMServer = require('react-dom/server.browser');
4244
ReactDOMClient = require('react-dom/client');
@@ -52,17 +54,6 @@ describe('ReactDOMFizzForm', () => {
5254
document.body.removeChild(container);
5355
});
5456

55-
async function serverAct(callback) {
56-
let maybePromise;
57-
await act(() => {
58-
maybePromise = callback();
59-
if (maybePromise && typeof maybePromise.catch === 'function') {
60-
maybePromise.catch(() => {});
61-
}
62-
});
63-
return maybePromise;
64-
}
65-
6657
async function readIntoContainer(stream) {
6758
const reader = stream.getReader();
6859
let result = '';

packages/react-dom/src/__tests__/ReactDOMFizzForm-test.js

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,27 +18,27 @@ global.ReadableStream =
1818
global.TextEncoder = require('util').TextEncoder;
1919

2020
let act;
21+
let serverAct;
2122
let container;
2223
let React;
2324
let ReactDOMServer;
2425
let ReactDOMClient;
2526
let useFormStatus;
2627
let useOptimistic;
2728
let useActionState;
28-
let Scheduler;
2929
let assertConsoleErrorDev;
3030

3131
describe('ReactDOMFizzForm', () => {
3232
beforeEach(() => {
3333
jest.resetModules();
34-
Scheduler = require('scheduler');
35-
patchMessageChannel(Scheduler);
34+
patchMessageChannel();
3635
React = require('react');
3736
ReactDOMServer = require('react-dom/server.browser');
3837
ReactDOMClient = require('react-dom/client');
3938
useFormStatus = require('react-dom').useFormStatus;
4039
useOptimistic = require('react').useOptimistic;
4140
act = require('internal-test-utils').act;
41+
serverAct = require('internal-test-utils').serverAct;
4242
assertConsoleErrorDev =
4343
require('internal-test-utils').assertConsoleErrorDev;
4444
container = document.createElement('div');
@@ -55,14 +55,6 @@ describe('ReactDOMFizzForm', () => {
5555
document.body.removeChild(container);
5656
});
5757

58-
async function serverAct(callback) {
59-
let maybePromise;
60-
await act(() => {
61-
maybePromise = callback();
62-
});
63-
return maybePromise;
64-
}
65-
6658
function submit(submitter) {
6759
const form = submitter.form || submitter;
6860
if (!submitter.form) {

packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,33 +19,20 @@ global.TextEncoder = require('util').TextEncoder;
1919
let React;
2020
let ReactDOMFizzServer;
2121
let Suspense;
22-
let Scheduler;
23-
let act;
22+
let serverAct;
2423

2524
describe('ReactDOMFizzServerBrowser', () => {
2625
beforeEach(() => {
2726
jest.resetModules();
2827

29-
Scheduler = require('scheduler');
30-
patchMessageChannel(Scheduler);
31-
act = require('internal-test-utils').act;
28+
patchMessageChannel();
29+
serverAct = require('internal-test-utils').serverAct;
3230

3331
React = require('react');
3432
ReactDOMFizzServer = require('react-dom/server.browser');
3533
Suspense = React.Suspense;
3634
});
3735

38-
async function serverAct(callback) {
39-
let maybePromise;
40-
await act(() => {
41-
maybePromise = callback();
42-
if (maybePromise && typeof maybePromise.catch === 'function') {
43-
maybePromise.catch(() => {});
44-
}
45-
});
46-
return maybePromise;
47-
}
48-
4936
const theError = new Error('This is an error');
5037
function Throw() {
5138
throw theError;

packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,7 @@ let ReactDOMFizzStatic;
3131
let Suspense;
3232
let SuspenseList;
3333
let container;
34-
let Scheduler;
35-
let act;
34+
let serverAct;
3635

3736
describe('ReactDOMFizzStaticBrowser', () => {
3837
beforeEach(() => {
@@ -42,9 +41,8 @@ describe('ReactDOMFizzStaticBrowser', () => {
4241
// We need the mocked version of setTimeout inside the document.
4342
window.setTimeout = setTimeout;
4443

45-
Scheduler = require('scheduler');
46-
patchMessageChannel(Scheduler);
47-
act = require('internal-test-utils').act;
44+
patchMessageChannel();
45+
serverAct = require('internal-test-utils').serverAct;
4846

4947
React = require('react');
5048
ReactDOM = require('react-dom');
@@ -63,17 +61,6 @@ describe('ReactDOMFizzStaticBrowser', () => {
6361
document.body.removeChild(container);
6462
});
6563

66-
async function serverAct(callback) {
67-
let maybePromise;
68-
await act(() => {
69-
maybePromise = callback();
70-
if (maybePromise && typeof maybePromise.catch === 'function') {
71-
maybePromise.catch(() => {});
72-
}
73-
});
74-
return maybePromise;
75-
}
76-
7764
const theError = new Error('This is an error');
7865
function Throw() {
7966
throw theError;

packages/react-dom/src/__tests__/ReactDOMFizzStaticFloat-test.js

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,15 @@ let ReactDOMFizzServer;
2727
let ReactDOMFizzStatic;
2828
let Suspense;
2929
let container;
30-
let Scheduler;
3130
let act;
31+
let serverAct;
3232

3333
describe('ReactDOMFizzStaticFloat', () => {
3434
beforeEach(() => {
3535
jest.resetModules();
36-
Scheduler = require('scheduler');
37-
patchMessageChannel(Scheduler);
36+
patchMessageChannel();
3837
act = require('internal-test-utils').act;
38+
serverAct = require('internal-test-utils').serverAct;
3939

4040
React = require('react');
4141
ReactDOM = require('react-dom');
@@ -52,17 +52,6 @@ describe('ReactDOMFizzStaticFloat', () => {
5252
document.body.removeChild(container);
5353
});
5454

55-
async function serverAct(callback) {
56-
let maybePromise;
57-
await act(() => {
58-
maybePromise = callback();
59-
if (maybePromise && typeof maybePromise.catch === 'function') {
60-
maybePromise.catch(() => {});
61-
}
62-
});
63-
return maybePromise;
64-
}
65-
6655
async function readIntoContainer(stream) {
6756
const reader = stream.getReader();
6857
let result = '';

packages/react-dom/src/test-utils/FizzTestUtils.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,10 @@ function getVisibleChildren(element: Element): React$Node {
167167
}
168168
props[attributes[i].name] = attributes[i].value;
169169
}
170-
props.children = getVisibleChildren(node);
170+
const nestedChildren = getVisibleChildren(node);
171+
if (nestedChildren !== undefined) {
172+
props.children = nestedChildren;
173+
}
171174
children.push(
172175
require('react').createElement(node.tagName.toLowerCase(), props),
173176
);

packages/react-server-dom-turbopack/src/__tests__/ReactFlightTurbopackDOM-test.js

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ global.TextEncoder = require('util').TextEncoder;
1818
global.TextDecoder = require('util').TextDecoder;
1919

2020
let act;
21+
let serverAct;
2122
let use;
2223
let clientExports;
2324
let clientExportsESM;
@@ -28,8 +29,6 @@ let ReactDOMClient;
2829
let ReactServerDOMServer;
2930
let ReactServerDOMClient;
3031
let Suspense;
31-
let ReactServerScheduler;
32-
let reactServerAct;
3332
let ErrorBoundary;
3433

3534
describe('ReactFlightTurbopackDOM', () => {
@@ -39,9 +38,8 @@ describe('ReactFlightTurbopackDOM', () => {
3938
// condition
4039
jest.resetModules();
4140

42-
ReactServerScheduler = require('scheduler');
43-
patchSetImmediate(ReactServerScheduler);
44-
reactServerAct = require('internal-test-utils').act;
41+
patchSetImmediate();
42+
serverAct = require('internal-test-utils').serverAct;
4543

4644
// Simulate the condition resolution
4745
jest.mock('react-server-dom-turbopack/server', () =>
@@ -84,17 +82,6 @@ describe('ReactFlightTurbopackDOM', () => {
8482
};
8583
});
8684

87-
async function serverAct(callback) {
88-
let maybePromise;
89-
await reactServerAct(() => {
90-
maybePromise = callback();
91-
if (maybePromise && typeof maybePromise.catch === 'function') {
92-
maybePromise.catch(() => {});
93-
}
94-
});
95-
return maybePromise;
96-
}
97-
9885
function getTestStream() {
9986
const writable = new Stream.PassThrough();
10087
const readable = new ReadableStream({

0 commit comments

Comments
 (0)