Skip to content

Commit 0ae2b13

Browse files
authored
[Test] Update flushSync tests to use react-dom (#28490)
Updates the `flushSync` tests to use react-dom instead of ReactNoop. flushSync is primarily a react-dom API and asserting the implementation of ReactNoop is not really ideal especially since we are going to be refactoring flushSync soon to not be implemented in the reconciler internals. ReactNoop still have a flushSync api and it can still be used in other tests that are primarily about testing other functionlity and use ReactNoop as the renderer.
1 parent 113ab9a commit 0ae2b13

File tree

2 files changed

+158
-55
lines changed

2 files changed

+158
-55
lines changed

packages/react-reconciler/src/__tests__/ReactFlushSync-test.js

Lines changed: 102 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
let React;
2-
let ReactNoop;
2+
let ReactDOM;
3+
let ReactDOMClient;
34
let Scheduler;
45
let act;
56
let useState;
@@ -15,7 +16,8 @@ describe('ReactFlushSync', () => {
1516
jest.resetModules();
1617

1718
React = require('react');
18-
ReactNoop = require('react-noop-renderer');
19+
ReactDOM = require('react-dom');
20+
ReactDOMClient = require('react-dom/client');
1921
Scheduler = require('scheduler');
2022
act = require('internal-test-utils').act;
2123
useState = React.useState;
@@ -32,7 +34,49 @@ describe('ReactFlushSync', () => {
3234
return text;
3335
}
3436

35-
test('changes priority of updates in useEffect', async () => {
37+
function getVisibleChildren(element: Element): React$Node {
38+
const children = [];
39+
let node: any = element.firstChild;
40+
while (node) {
41+
if (node.nodeType === 1) {
42+
if (
43+
((node.tagName !== 'SCRIPT' && node.tagName !== 'script') ||
44+
node.hasAttribute('data-meaningful')) &&
45+
node.tagName !== 'TEMPLATE' &&
46+
node.tagName !== 'template' &&
47+
!node.hasAttribute('hidden') &&
48+
!node.hasAttribute('aria-hidden')
49+
) {
50+
const props: any = {};
51+
const attributes = node.attributes;
52+
for (let i = 0; i < attributes.length; i++) {
53+
if (
54+
attributes[i].name === 'id' &&
55+
attributes[i].value.includes(':')
56+
) {
57+
// We assume this is a React added ID that's a non-visual implementation detail.
58+
continue;
59+
}
60+
props[attributes[i].name] = attributes[i].value;
61+
}
62+
props.children = getVisibleChildren(node);
63+
children.push(
64+
require('react').createElement(node.tagName.toLowerCase(), props),
65+
);
66+
}
67+
} else if (node.nodeType === 3) {
68+
children.push(node.data);
69+
}
70+
node = node.nextSibling;
71+
}
72+
return children.length === 0
73+
? undefined
74+
: children.length === 1
75+
? children[0]
76+
: children;
77+
}
78+
79+
it('changes priority of updates in useEffect', async () => {
3680
spyOnDev(console, 'error').mockImplementation(() => {});
3781

3882
function App() {
@@ -41,13 +85,14 @@ describe('ReactFlushSync', () => {
4185
useEffect(() => {
4286
if (syncState !== 1) {
4387
setState(1);
44-
ReactNoop.flushSync(() => setSyncState(1));
88+
ReactDOM.flushSync(() => setSyncState(1));
4589
}
4690
}, [syncState, state]);
4791
return <Text text={`${syncState}, ${state}`} />;
4892
}
4993

50-
const root = ReactNoop.createRoot();
94+
const container = document.createElement('div');
95+
const root = ReactDOMClient.createRoot(container);
5196
await act(async () => {
5297
React.startTransition(() => {
5398
root.render(<App />);
@@ -62,7 +107,7 @@ describe('ReactFlushSync', () => {
62107
);
63108

64109
// The remaining update is not sync
65-
ReactNoop.flushSync();
110+
ReactDOM.flushSync();
66111
assertLog([]);
67112

68113
if (gate(flags => flags.enableUnifiedSyncLane)) {
@@ -72,7 +117,7 @@ describe('ReactFlushSync', () => {
72117
await waitForPaint(['1, 1']);
73118
}
74119
});
75-
expect(root).toMatchRenderedOutput('1, 1');
120+
expect(getVisibleChildren(container)).toEqual('1, 1');
76121

77122
if (__DEV__) {
78123
expect(console.error.mock.calls[0][0]).toContain(
@@ -83,7 +128,7 @@ describe('ReactFlushSync', () => {
83128
}
84129
});
85130

86-
test('nested with startTransition', async () => {
131+
it('supports nested flushSync with startTransition', async () => {
87132
let setSyncState;
88133
let setState;
89134
function App() {
@@ -94,20 +139,21 @@ describe('ReactFlushSync', () => {
94139
return <Text text={`${syncState}, ${state}`} />;
95140
}
96141

97-
const root = ReactNoop.createRoot();
142+
const container = document.createElement('div');
143+
const root = ReactDOMClient.createRoot(container);
98144
await act(() => {
99145
root.render(<App />);
100146
});
101147
assertLog(['0, 0']);
102-
expect(root).toMatchRenderedOutput('0, 0');
148+
expect(getVisibleChildren(container)).toEqual('0, 0');
103149

104150
await act(() => {
105-
ReactNoop.flushSync(() => {
151+
ReactDOM.flushSync(() => {
106152
startTransition(() => {
107153
// This should be async even though flushSync is on the stack, because
108154
// startTransition is closer.
109155
setState(1);
110-
ReactNoop.flushSync(() => {
156+
ReactDOM.flushSync(() => {
111157
// This should be async even though startTransition is on the stack,
112158
// because flushSync is closer.
113159
setSyncState(1);
@@ -116,24 +162,25 @@ describe('ReactFlushSync', () => {
116162
});
117163
// Only the sync update should have flushed
118164
assertLog(['1, 0']);
119-
expect(root).toMatchRenderedOutput('1, 0');
165+
expect(getVisibleChildren(container)).toEqual('1, 0');
120166
});
121167
// Now the async update has flushed, too.
122168
assertLog(['1, 1']);
123-
expect(root).toMatchRenderedOutput('1, 1');
169+
expect(getVisibleChildren(container)).toEqual('1, 1');
124170
});
125171

126-
test('flushes passive effects synchronously when they are the result of a sync render', async () => {
172+
it('flushes passive effects synchronously when they are the result of a sync render', async () => {
127173
function App() {
128174
useEffect(() => {
129175
Scheduler.log('Effect');
130176
}, []);
131177
return <Text text="Child" />;
132178
}
133179

134-
const root = ReactNoop.createRoot();
180+
const container = document.createElement('div');
181+
const root = ReactDOMClient.createRoot(container);
135182
await act(() => {
136-
ReactNoop.flushSync(() => {
183+
ReactDOM.flushSync(() => {
137184
root.render(<App />);
138185
});
139186
assertLog([
@@ -142,35 +189,37 @@ describe('ReactFlushSync', () => {
142189
// flushSync should flush it.
143190
'Effect',
144191
]);
145-
expect(root).toMatchRenderedOutput('Child');
192+
expect(getVisibleChildren(container)).toEqual('Child');
146193
});
147194
});
148195

149-
test('do not flush passive effects synchronously after render in legacy mode', async () => {
196+
// @gate !disableLegacyMode
197+
it('does not flush passive effects synchronously after render in legacy mode', async () => {
150198
function App() {
151199
useEffect(() => {
152200
Scheduler.log('Effect');
153201
}, []);
154202
return <Text text="Child" />;
155203
}
156204

157-
const root = ReactNoop.createLegacyRoot();
205+
const container = document.createElement('div');
158206
await act(() => {
159-
ReactNoop.flushSync(() => {
160-
root.render(<App />);
207+
ReactDOM.flushSync(() => {
208+
ReactDOM.render(<App />, container);
161209
});
162210
assertLog([
163211
'Child',
164212
// Because we're in legacy mode, we shouldn't have flushed the passive
165213
// effects yet.
166214
]);
167-
expect(root).toMatchRenderedOutput('Child');
215+
expect(getVisibleChildren(container)).toEqual('Child');
168216
});
169217
// Effect flushes after paint.
170218
assertLog(['Effect']);
171219
});
172220

173-
test('flush pending passive effects before scope is called in legacy mode', async () => {
221+
// @gate !disableLegacyMode
222+
it('flushes pending passive effects before scope is called in legacy mode', async () => {
174223
let currentStep = 0;
175224

176225
function App({step}) {
@@ -181,82 +230,89 @@ describe('ReactFlushSync', () => {
181230
return <Text text={step} />;
182231
}
183232

184-
const root = ReactNoop.createLegacyRoot();
233+
const container = document.createElement('div');
185234
await act(() => {
186-
ReactNoop.flushSync(() => {
187-
root.render(<App step={1} />);
235+
ReactDOM.flushSync(() => {
236+
ReactDOM.render(<App step={1} />, container);
188237
});
189238
assertLog([
190239
1,
191240
// Because we're in legacy mode, we shouldn't have flushed the passive
192241
// effects yet.
193242
]);
194-
expect(root).toMatchRenderedOutput('1');
243+
expect(getVisibleChildren(container)).toEqual('1');
195244

196-
ReactNoop.flushSync(() => {
245+
ReactDOM.flushSync(() => {
197246
// This should render step 2 because the passive effect has already
198247
// fired, before the scope function is called.
199-
root.render(<App step={currentStep + 1} />);
248+
ReactDOM.render(<App step={currentStep + 1} />, container);
200249
});
201250
assertLog(['Effect: 1', 2]);
202-
expect(root).toMatchRenderedOutput('2');
251+
expect(getVisibleChildren(container)).toEqual('2');
203252
});
204253
assertLog(['Effect: 2']);
205254
});
206255

207-
test("do not flush passive effects synchronously when they aren't the result of a sync render", async () => {
256+
it("does not flush passive effects synchronously when they aren't the result of a sync render", async () => {
208257
function App() {
209258
useEffect(() => {
210259
Scheduler.log('Effect');
211260
}, []);
212261
return <Text text="Child" />;
213262
}
214263

215-
const root = ReactNoop.createRoot();
264+
const container = document.createElement('div');
265+
const root = ReactDOMClient.createRoot(container);
216266
await act(async () => {
217267
root.render(<App />);
218268
await waitForPaint([
219269
'Child',
220270
// Because the passive effect was not the result of a sync update, it
221271
// should not flush before paint.
222272
]);
223-
expect(root).toMatchRenderedOutput('Child');
273+
expect(getVisibleChildren(container)).toEqual('Child');
224274
});
225275
// Effect flushes after paint.
226276
assertLog(['Effect']);
227277
});
228278

229-
test('does not flush pending passive effects', async () => {
279+
it('does not flush pending passive effects', async () => {
230280
function App() {
231281
useEffect(() => {
232282
Scheduler.log('Effect');
233283
}, []);
234284
return <Text text="Child" />;
235285
}
236286

237-
const root = ReactNoop.createRoot();
287+
const container = document.createElement('div');
288+
const root = ReactDOMClient.createRoot(container);
238289
await act(async () => {
239290
root.render(<App />);
240291
await waitForPaint(['Child']);
241-
expect(root).toMatchRenderedOutput('Child');
292+
expect(getVisibleChildren(container)).toEqual('Child');
242293

243294
// Passive effects are pending. Calling flushSync should not affect them.
244-
ReactNoop.flushSync();
295+
ReactDOM.flushSync();
245296
// Effects still haven't fired.
246297
assertLog([]);
247298
});
248299
// Now the effects have fired.
249300
assertLog(['Effect']);
250301
});
251302

252-
test('completely exhausts synchronous work queue even if something throws', async () => {
303+
it('completely exhausts synchronous work queue even if something throws', async () => {
253304
function Throws({error}) {
254305
throw error;
255306
}
256307

257-
const root1 = ReactNoop.createRoot();
258-
const root2 = ReactNoop.createRoot();
259-
const root3 = ReactNoop.createRoot();
308+
const container1 = document.createElement('div');
309+
const root1 = ReactDOMClient.createRoot(container1);
310+
311+
const container2 = document.createElement('div');
312+
const root2 = ReactDOMClient.createRoot(container2);
313+
314+
const container3 = document.createElement('div');
315+
const root3 = ReactDOMClient.createRoot(container3);
260316

261317
await act(async () => {
262318
root1.render(<Text text="Hi" />);
@@ -270,7 +326,7 @@ describe('ReactFlushSync', () => {
270326

271327
let error;
272328
try {
273-
ReactNoop.flushSync(() => {
329+
ReactDOM.flushSync(() => {
274330
root1.render(<Throws error={aahh} />);
275331
root2.render(<Throws error={nooo} />);
276332
root3.render(<Text text="aww" />);
@@ -283,9 +339,9 @@ describe('ReactFlushSync', () => {
283339
// earlier updates errored.
284340
assertLog(['aww']);
285341
// Roots 1 and 2 were unmounted.
286-
expect(root1).toMatchRenderedOutput(null);
287-
expect(root2).toMatchRenderedOutput(null);
288-
expect(root3).toMatchRenderedOutput('aww');
342+
expect(getVisibleChildren(container1)).toEqual(undefined);
343+
expect(getVisibleChildren(container2)).toEqual(undefined);
344+
expect(getVisibleChildren(container3)).toEqual('aww');
289345

290346
// Because there were multiple errors, React threw an AggregateError.
291347
// eslint-disable-next-line no-undef

0 commit comments

Comments
 (0)