2
let ReactNoop;
3
let Scheduler;
4
let LegacyHidden;
5
+let Offscreen;
6
let useState;
7
+let useLayoutEffect;
8
9
describe('ReactOffscreen', () => {
10
beforeEach(() => {
14
ReactNoop = require('react-noop-renderer');
15
Scheduler = require('scheduler');
16
LegacyHidden = React.unstable_LegacyHidden;
17
+ Offscreen = React.unstable_Offscreen;
18
useState = React.useState;
19
+ useLayoutEffect = React.useLayoutEffect;
20
});
21
22
function Text(props) {
173
</>,
174
);
175
});
176
+
177
+ // @gate experimental
178
+ // @gate enableSuspenseLayoutEffectSemantics
179
+ it('mounts without layout effects when hidden', async () => {
180
+ function Child({text}) {
181
+ useLayoutEffect(() => {
182
+ Scheduler.unstable_yieldValue('Mount layout');
183
+ return () => {
184
+ Scheduler.unstable_yieldValue('Unmount layout');
185
+ };
186
+ }, []);
187
+ return <Text text="Child" />;
188
+ }
189
+
190
+ const root = ReactNoop.createRoot();
191
+
192
+ // Mount hidden tree.
193
+ await ReactNoop.act(async () => {
194
+ root.render(
195
+ <Offscreen mode="hidden">
196
+ <Child />
197
+ </Offscreen>,
198
+ );
199
+ });
200
+ // No layout effect.
201
+ expect(Scheduler).toHaveYielded(['Child']);
202
+ // TODO: Offscreen does not yet hide/unhide children correctly. Until we do,
203
+ // it should only be used inside a host component wrapper whose visibility
204
+ // is toggled simultaneously.
205
+ expect(root).toMatchRenderedOutput(<span prop="Child" />);
206
+
207
+ // Unhide the tree. The layout effect is mounted.
208
+ await ReactNoop.act(async () => {
209
+ root.render(
210
+ <Offscreen mode="visible">
211
+ <Child />
212
+ </Offscreen>,
213
+ );
214
+ });
215
+ expect(Scheduler).toHaveYielded(['Child', 'Mount layout']);
216
+ expect(root).toMatchRenderedOutput(<span prop="Child" />);
217
+ });
218
+
219
+ // @gate experimental
220
+ // @gate enableSuspenseLayoutEffectSemantics
221
+ it('mounts/unmounts layout effects when visibility changes (starting visible)', async () => {
222
+ function Child({text}) {
223
+ useLayoutEffect(() => {
224
+ Scheduler.unstable_yieldValue('Mount layout');
225
+ return () => {
226
+ Scheduler.unstable_yieldValue('Unmount layout');
227
+ };
228
+ }, []);
229
+ return <Text text="Child" />;
230
+ }
231
+
232
+ const root = ReactNoop.createRoot();
233
+ await ReactNoop.act(async () => {
234
+ root.render(
235
+ <Offscreen mode="visible">
236
+ <Child />
237
+ </Offscreen>,
238
+ );
239
+ });
240
+ expect(Scheduler).toHaveYielded(['Child', 'Mount layout']);
241
+ expect(root).toMatchRenderedOutput(<span prop="Child" />);
242
+
243
+ // Hide the tree. The layout effect is unmounted.
244
+ await ReactNoop.act(async () => {
245
+ root.render(
246
+ <Offscreen mode="hidden">
247
+ <Child />
248
+ </Offscreen>,
249
+ );
250
+ });
251
+ expect(Scheduler).toHaveYielded(['Unmount layout', 'Child']);
252
+ // TODO: Offscreen does not yet hide/unhide children correctly. Until we do,
253
+ // it should only be used inside a host component wrapper whose visibility
254
+ // is toggled simultaneously.
255
+ expect(root).toMatchRenderedOutput(<span prop="Child" />);
256
+
257
+ // Unhide the tree. The layout effect is re-mounted.
258
+ await ReactNoop.act(async () => {
259
+ root.render(
260
+ <Offscreen mode="visible">
261
+ <Child />
262
+ </Offscreen>,
263
+ );
264
+ });
265
+ expect(Scheduler).toHaveYielded(['Child', 'Mount layout']);
266
+ expect(root).toMatchRenderedOutput(<span prop="Child" />);
267
+ });
268
+
269
+ // @gate experimental
270
+ // @gate enableSuspenseLayoutEffectSemantics
271
+ it('mounts/unmounts layout effects when visibility changes (starting hidden)', async () => {
272
+ function Child({text}) {
273
+ useLayoutEffect(() => {
274
+ Scheduler.unstable_yieldValue('Mount layout');
275
+ return () => {
276
+ Scheduler.unstable_yieldValue('Unmount layout');
277
+ };
278
+ }, []);
279
+ return <Text text="Child" />;
280
+ }
281
+
282
+ const root = ReactNoop.createRoot();
283
+ await ReactNoop.act(async () => {
284
+ // Start the tree hidden. The layout effect is not mounted.
285
+ root.render(
286
+ <Offscreen mode="hidden">
287
+ <Child />
288
+ </Offscreen>,
289
+ );
290
+ });
291
+ expect(Scheduler).toHaveYielded(['Child']);
292
+ // TODO: Offscreen does not yet hide/unhide children correctly. Until we do,
293
+ // it should only be used inside a host component wrapper whose visibility
294
+ // is toggled simultaneously.
295
+ expect(root).toMatchRenderedOutput(<span prop="Child" />);
296
+
297
+ // Show the tree. The layout effect is mounted.
298
+ await ReactNoop.act(async () => {
299
+ root.render(
300
+ <Offscreen mode="visible">
301
+ <Child />
302
+ </Offscreen>,
303
+ );
304
+ });
305
+ expect(Scheduler).toHaveYielded(['Child', 'Mount layout']);
306
+ expect(root).toMatchRenderedOutput(<span prop="Child" />);
307
+
308
+ // Hide the tree again. The layout effect is un-mounted.
309
+ await ReactNoop.act(async () => {
310
+ root.render(
311
+ <Offscreen mode="hidden">
312
+ <Child />
313
+ </Offscreen>,
314
+ );
315
+ });
316
+ expect(Scheduler).toHaveYielded(['Unmount layout', 'Child']);
317
+ // TODO: Offscreen does not yet hide/unhide children correctly. Until we do,
318
+ // it should only be used inside a host component wrapper whose visibility
319
+ // is toggled simultaneously.
320
+ expect(root).toMatchRenderedOutput(<span prop="Child" />);
321
+ });
322
});