main
js 2,571 lines 78 KB
Raw
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 * @emails react-core
8 * @jest-environment node
9 */
10
11 let React;
12 let ReactNoop;
13 let Scheduler;
14 let act;
15 let waitForAll;
16 let assertLog;
17 let assertConsoleErrorDev;
18
19 let getCacheForType;
20 let useState;
21 let Suspense;
22 let Activity;
23 let startTransition;
24
25 let caches;
26 let seededCache;
27
28 describe('ReactInteractionTracing', () => {
29 function stringifyDeletions(deletions) {
30 return deletions
31 .map(
32 d =>
33 `{${Object.keys(d)
34 .map(key => `${key}: ${d[key]}`)
35 .sort()
36 .join(', ')}}`,
37 )
38 .join(', ');
39 }
40 beforeEach(() => {
41 jest.resetModules();
42
43 React = require('react');
44 ReactNoop = require('react-noop-renderer');
45 Scheduler = require('scheduler');
46
47 const InternalTestUtils = require('internal-test-utils');
48 act = InternalTestUtils.act;
49 waitForAll = InternalTestUtils.waitForAll;
50 assertLog = InternalTestUtils.assertLog;
51 assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
52
53 useState = React.useState;
54 startTransition = React.startTransition;
55 Suspense = React.Suspense;
56 Activity = React.Activity;
57
58 getCacheForType = React.unstable_getCacheForType;
59
60 caches = [];
61 seededCache = null;
62 });
63
64 function createTextCache() {
65 if (seededCache !== null) {
66 const cache = seededCache;
67 seededCache = null;
68 return cache;
69 }
70
71 const data = new Map();
72 const cache = {
73 data,
74 resolve(text) {
75 const record = data.get(text);
76
77 if (record === undefined) {
78 const newRecord = {
79 status: 'resolved',
80 value: text,
81 };
82 data.set(text, newRecord);
83 } else if (record.status === 'pending') {
84 const thenable = record.value;
85 record.status = 'resolved';
86 record.value = text;
87 thenable.pings.forEach(t => t());
88 }
89 },
90 reject(text, error) {
91 const record = data.get(text);
92 if (record === undefined) {
93 const newRecord = {
94 status: 'rejected',
95 value: error,
96 };
97 data.set(text, newRecord);
98 } else if (record.status === 'pending') {
99 const thenable = record.value;
100 record.status = 'rejected';
101 record.value = error;
102 thenable.pings.forEach(t => t());
103 }
104 },
105 };
106 caches.push(cache);
107 return cache;
108 }
109
110 function readText(text) {
111 const textCache = getCacheForType(createTextCache);
112 const record = textCache.data.get(text);
113 if (record !== undefined) {
114 switch (record.status) {
115 case 'pending':
116 Scheduler.log(`Suspend [${text}]`);
117 throw record.value;
118 case 'rejected':
119 Scheduler.log(`Error [${text}]`);
120 throw record.value;
121 case 'resolved':
122 return record.value;
123 }
124 } else {
125 Scheduler.log(`Suspend [${text}]`);
126
127 const thenable = {
128 pings: [],
129 then(resolve) {
130 if (newRecord.status === 'pending') {
131 thenable.pings.push(resolve);
132 } else {
133 Promise.resolve().then(() => resolve(newRecord.value));
134 }
135 },
136 };
137
138 const newRecord = {
139 status: 'pending',
140 value: thenable,
141 };
142 textCache.data.set(text, newRecord);
143
144 throw thenable;
145 }
146 }
147
148 function AsyncText({text}) {
149 const fullText = readText(text);
150 Scheduler.log(fullText);
151 return fullText;
152 }
153
154 function Text({text}) {
155 Scheduler.log(text);
156 return text;
157 }
158
159 function resolveMostRecentTextCache(text) {
160 if (caches.length === 0) {
161 throw Error('Cache does not exist');
162 } else {
163 // Resolve the most recently created cache. An older cache can by
164 // resolved with `caches[index].resolve(text)`.
165 caches[caches.length - 1].resolve(text);
166 }
167 }
168
169 const resolveText = resolveMostRecentTextCache;
170
171 function advanceTimers(ms) {
172 // Note: This advances Jest's virtual time but not React's. Use
173 // ReactNoop.expire for that.
174 if (typeof ms !== 'number') {
175 throw new Error('Must specify ms');
176 }
177 jest.advanceTimersByTime(ms);
178 // Wait until the end of the current tick
179 // We cannot use a timer since we're faking them
180 return Promise.resolve().then(() => {});
181 }
182
183 // @gate enableTransitionTracing
184 it('should not call callbacks when transition is not defined', async () => {
185 const transitionCallbacks = {
186 onTransitionStart: (name, startTime) => {
187 Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
188 },
189 onTransitionProgress: (name, startTime, endTime, pending) => {
190 const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
191 Scheduler.log(
192 `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
193 );
194 },
195 onTransitionComplete: (name, startTime, endTime) => {
196 Scheduler.log(
197 `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
198 );
199 },
200 onMarkerProgress: (
201 transitioName,
202 markerName,
203 startTime,
204 currentTime,
205 pending,
206 ) => {
207 const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
208 Scheduler.log(
209 `onMarkerProgress(${transitioName}, ${markerName}, ${startTime}, ${currentTime}, [${suspenseNames}])`,
210 );
211 },
212 onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
213 Scheduler.log(
214 `onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
215 );
216 },
217 };
218
219 function App({navigate}) {
220 return (
221 <div>
222 {navigate ? (
223 <React.unstable_TracingMarker name="marker">
224 <Text text="Page Two" />
225 </React.unstable_TracingMarker>
226 ) : (
227 <Text text="Page One" />
228 )}
229 </div>
230 );
231 }
232
233 const root = ReactNoop.createRoot({
234 unstable_transitionCallbacks: transitionCallbacks,
235 });
236 await act(async () => {
237 root.render(<App navigate={false} />);
238 ReactNoop.expire(1000);
239 await advanceTimers(1000);
240
241 await waitForAll(['Page One']);
242
243 await act(async () => {
244 startTransition(() => root.render(<App navigate={true} />));
245
246 ReactNoop.expire(1000);
247 await advanceTimers(1000);
248
249 // Doesn't call transition or marker code
250 await waitForAll(['Page Two']);
251
252 startTransition(() => root.render(<App navigate={false} />), {
253 name: 'transition',
254 });
255 await waitForAll([
256 'Page One',
257 'onTransitionStart(transition, 2000)',
258 'onTransitionComplete(transition, 2000, 2000)',
259 ]);
260 });
261 });
262 });
263
264 // @gate enableTransitionTracing
265 it('should correctly trace basic interaction', async () => {
266 const transitionCallbacks = {
267 onTransitionStart: (name, startTime) => {
268 Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
269 },
270 onTransitionProgress: (name, startTime, endTime, pending) => {
271 const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
272 Scheduler.log(
273 `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
274 );
275 },
276 onTransitionComplete: (name, startTime, endTime) => {
277 Scheduler.log(
278 `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
279 );
280 },
281 };
282
283 let navigateToPageTwo;
284 function App() {
285 const [navigate, setNavigate] = useState(false);
286 navigateToPageTwo = () => {
287 setNavigate(true);
288 };
289
290 return (
291 <div>
292 {navigate ? <Text text="Page Two" /> : <Text text="Page One" />}
293 </div>
294 );
295 }
296
297 const root = ReactNoop.createRoot({
298 unstable_transitionCallbacks: transitionCallbacks,
299 });
300 await act(async () => {
301 root.render(<App />);
302 ReactNoop.expire(1000);
303 await advanceTimers(1000);
304
305 await waitForAll(['Page One']);
306
307 await act(async () => {
308 startTransition(() => navigateToPageTwo(), {name: 'page transition'});
309
310 ReactNoop.expire(1000);
311 await advanceTimers(1000);
312
313 await waitForAll([
314 'Page Two',
315 'onTransitionStart(page transition, 1000)',
316 'onTransitionComplete(page transition, 1000, 2000)',
317 ]);
318 });
319 });
320 });
321
322 // @gate enableTransitionTracing
323 it('multiple updates in transition callback should only result in one transitionStart/transitionComplete call', async () => {
324 const transitionCallbacks = {
325 onTransitionStart: (name, startTime) => {
326 Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
327 },
328 onTransitionComplete: (name, startTime, endTime) => {
329 Scheduler.log(
330 `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
331 );
332 },
333 };
334
335 let navigateToPageTwo;
336 let setText;
337 function App() {
338 const [navigate, setNavigate] = useState(false);
339 const [text, _setText] = useState('hide');
340 navigateToPageTwo = () => setNavigate(true);
341 setText = () => _setText('show');
342
343 return (
344 <div>
345 {navigate ? (
346 <Text text={`Page Two: ${text}`} />
347 ) : (
348 <Text text={`Page One: ${text}`} />
349 )}
350 </div>
351 );
352 }
353
354 const root = ReactNoop.createRoot({
355 unstable_transitionCallbacks: transitionCallbacks,
356 });
357 await act(async () => {
358 root.render(<App />);
359 ReactNoop.expire(1000);
360 await advanceTimers(1000);
361
362 await waitForAll(['Page One: hide']);
363
364 await act(async () => {
365 startTransition(
366 () => {
367 navigateToPageTwo();
368 setText();
369 },
370 {name: 'page transition'},
371 );
372
373 ReactNoop.expire(1000);
374 await advanceTimers(1000);
375
376 await waitForAll([
377 'Page Two: show',
378 'onTransitionStart(page transition, 1000)',
379 'onTransitionComplete(page transition, 1000, 2000)',
380 ]);
381 });
382 });
383 });
384
385 // @gate enableTransitionTracing
386 it('should correctly trace interactions for async roots', async () => {
387 const transitionCallbacks = {
388 onTransitionStart: (name, startTime) => {
389 Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
390 },
391 onTransitionProgress: (name, startTime, endTime, pending) => {
392 const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
393 Scheduler.log(
394 `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
395 );
396 },
397 onTransitionComplete: (name, startTime, endTime) => {
398 Scheduler.log(
399 `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
400 );
401 },
402 };
403 let navigateToPageTwo;
404 function App() {
405 const [navigate, setNavigate] = useState(false);
406 navigateToPageTwo = () => {
407 setNavigate(true);
408 };
409
410 return (
411 <div>
412 {navigate ? (
413 <Suspense
414 fallback={<Text text="Loading..." />}
415 name="suspense page">
416 <AsyncText text="Page Two" />
417 </Suspense>
418 ) : (
419 <Text text="Page One" />
420 )}
421 </div>
422 );
423 }
424
425 const root = ReactNoop.createRoot({
426 unstable_transitionCallbacks: transitionCallbacks,
427 });
428 await act(async () => {
429 root.render(<App />);
430 ReactNoop.expire(1000);
431 await advanceTimers(1000);
432
433 await waitForAll(['Page One']);
434 });
435
436 await act(async () => {
437 startTransition(() => navigateToPageTwo(), {name: 'page transition'});
438
439 ReactNoop.expire(1000);
440 await advanceTimers(1000);
441
442 await waitForAll([
443 'Suspend [Page Two]',
444 'Loading...',
445 // pre-warming
446 'Suspend [Page Two]',
447 // end pre-warming
448 'onTransitionStart(page transition, 1000)',
449 'onTransitionProgress(page transition, 1000, 2000, [suspense page])',
450 ]);
451
452 ReactNoop.expire(1000);
453 await advanceTimers(1000);
454 await resolveText('Page Two');
455
456 await waitForAll([
457 'Page Two',
458 'onTransitionProgress(page transition, 1000, 3000, [])',
459 'onTransitionComplete(page transition, 1000, 3000)',
460 ]);
461 });
462 });
463
464 // @gate enableTransitionTracing
465 it('should correctly trace multiple separate root interactions', async () => {
466 const transitionCallbacks = {
467 onTransitionStart: (name, startTime) => {
468 Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
469 },
470 onTransitionProgress: (name, startTime, endTime, pending) => {
471 const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
472 Scheduler.log(
473 `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
474 );
475 },
476 onTransitionComplete: (name, startTime, endTime) => {
477 Scheduler.log(
478 `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
479 );
480 },
481 };
482
483 let navigateToPageTwo;
484 let showTextFn;
485 function App() {
486 const [navigate, setNavigate] = useState(false);
487 const [showText, setShowText] = useState(false);
488
489 navigateToPageTwo = () => {
490 setNavigate(true);
491 };
492
493 showTextFn = () => {
494 setShowText(true);
495 };
496
497 return (
498 <div>
499 {navigate ? (
500 <>
501 {showText ? (
502 <Suspense
503 name="show text"
504 fallback={<Text text="Show Text Loading..." />}>
505 <AsyncText text="Show Text" />
506 </Suspense>
507 ) : null}
508 <Suspense
509 fallback={<Text text="Loading..." />}
510 name="suspense page">
511 <AsyncText text="Page Two" />
512 </Suspense>
513 </>
514 ) : (
515 <Text text="Page One" />
516 )}
517 </div>
518 );
519 }
520
521 const root = ReactNoop.createRoot({
522 unstable_transitionCallbacks: transitionCallbacks,
523 });
524 await act(async () => {
525 root.render(<App />);
526 ReactNoop.expire(1000);
527 await advanceTimers(1000);
528
529 await waitForAll(['Page One']);
530 });
531
532 await act(async () => {
533 startTransition(() => navigateToPageTwo(), {name: 'page transition'});
534
535 await waitForAll([
536 'Suspend [Page Two]',
537 'Loading...',
538 // pre-warming
539 'Suspend [Page Two]',
540 // end pre-warming
541 'onTransitionStart(page transition, 1000)',
542 'onTransitionProgress(page transition, 1000, 1000, [suspense page])',
543 ]);
544
545 await resolveText('Page Two');
546 ReactNoop.expire(1000);
547 await advanceTimers(1000);
548 await waitForAll([
549 'Page Two',
550 'onTransitionProgress(page transition, 1000, 2000, [])',
551 'onTransitionComplete(page transition, 1000, 2000)',
552 ]);
553
554 startTransition(() => showTextFn(), {name: 'text transition'});
555 await waitForAll([
556 'Suspend [Show Text]',
557 'Show Text Loading...',
558 'Page Two',
559 // pre-warming
560 'Suspend [Show Text]',
561 // end pre-warming
562 'onTransitionStart(text transition, 2000)',
563 'onTransitionProgress(text transition, 2000, 2000, [show text])',
564 ]);
565
566 await resolveText('Show Text');
567 ReactNoop.expire(1000);
568 await advanceTimers(1000);
569 await waitForAll([
570 'Show Text',
571 'onTransitionProgress(text transition, 2000, 3000, [])',
572 'onTransitionComplete(text transition, 2000, 3000)',
573 ]);
574 });
575 });
576
577 // @gate enableTransitionTracing
578 it('should correctly trace multiple intertwined root interactions', async () => {
579 const transitionCallbacks = {
580 onTransitionStart: (name, startTime) => {
581 Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
582 },
583 onTransitionProgress: (name, startTime, endTime, pending) => {
584 const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
585 Scheduler.log(
586 `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
587 );
588 },
589 onTransitionComplete: (name, startTime, endTime) => {
590 Scheduler.log(
591 `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
592 );
593 },
594 };
595 let navigateToPageTwo;
596 let showTextFn;
597 function App() {
598 const [navigate, setNavigate] = useState(false);
599 const [showText, setShowText] = useState(false);
600 navigateToPageTwo = () => {
601 setNavigate(true);
602 };
603
604 showTextFn = () => {
605 setShowText(true);
606 };
607
608 return (
609 <div>
610 {navigate ? (
611 <>
612 {showText ? (
613 <Suspense
614 name="show text"
615 fallback={<Text text="Show Text Loading..." />}>
616 <AsyncText text="Show Text" />
617 </Suspense>
618 ) : null}
619 <Suspense
620 fallback={<Text text="Loading..." />}
621 name="suspense page">
622 <AsyncText text="Page Two" />
623 </Suspense>
624 </>
625 ) : (
626 <Text text="Page One" />
627 )}
628 </div>
629 );
630 }
631
632 const root = ReactNoop.createRoot({
633 unstable_transitionCallbacks: transitionCallbacks,
634 });
635 await act(async () => {
636 root.render(<App />);
637 ReactNoop.expire(1000);
638 await advanceTimers(1000);
639
640 await waitForAll(['Page One']);
641 });
642
643 await act(async () => {
644 startTransition(() => navigateToPageTwo(), {name: 'page transition'});
645 ReactNoop.expire(1000);
646 await advanceTimers(1000);
647
648 await waitForAll([
649 'Suspend [Page Two]',
650 'Loading...',
651 // pre-warming
652 'Suspend [Page Two]',
653 // end pre-warming
654 'onTransitionStart(page transition, 1000)',
655 'onTransitionProgress(page transition, 1000, 2000, [suspense page])',
656 ]);
657 });
658
659 await act(async () => {
660 startTransition(() => showTextFn(), {name: 'show text'});
661
662 await waitForAll([
663 'Suspend [Show Text]',
664 'Show Text Loading...',
665 'Suspend [Page Two]',
666 'Loading...',
667 // pre-warming
668 'Suspend [Show Text]',
669 'Suspend [Page Two]',
670 // end pre-warming
671 'onTransitionStart(show text, 2000)',
672 'onTransitionProgress(show text, 2000, 2000, [show text])',
673 ]);
674 });
675
676 await act(async () => {
677 await resolveText('Page Two');
678 ReactNoop.expire(1000);
679 await advanceTimers(1000);
680
681 await waitForAll([
682 'Page Two',
683 'onTransitionProgress(page transition, 1000, 3000, [])',
684 'onTransitionComplete(page transition, 1000, 3000)',
685 ]);
686
687 await resolveText('Show Text');
688 ReactNoop.expire(1000);
689 await advanceTimers(1000);
690
691 await waitForAll([
692 'Show Text',
693 'onTransitionProgress(show text, 2000, 4000, [])',
694 'onTransitionComplete(show text, 2000, 4000)',
695 ]);
696 });
697 });
698
699 // @gate enableTransitionTracing
700 it('trace interaction with nested and sibling suspense boundaries', async () => {
701 const transitionCallbacks = {
702 onTransitionStart: (name, startTime) => {
703 Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
704 },
705 onTransitionProgress: (name, startTime, endTime, pending) => {
706 const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
707 Scheduler.log(
708 `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
709 );
710 },
711 onTransitionComplete: (name, startTime, endTime) => {
712 Scheduler.log(
713 `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
714 );
715 },
716 };
717
718 let navigateToPageTwo;
719 function App() {
720 const [navigate, setNavigate] = useState(false);
721 navigateToPageTwo = () => {
722 setNavigate(true);
723 };
724
725 return (
726 <div>
727 {navigate ? (
728 <>
729 <Suspense
730 fallback={<Text text="Loading..." />}
731 name="suspense page">
732 <AsyncText text="Page Two" />
733 <Suspense
734 name="show text one"
735 fallback={<Text text="Show Text One Loading..." />}>
736 <AsyncText text="Show Text One" />
737 </Suspense>
738 <div>
739 <Suspense
740 name="show text two"
741 fallback={<Text text="Show Text Two Loading..." />}>
742 <AsyncText text="Show Text Two" />
743 </Suspense>
744 </div>
745 </Suspense>
746 </>
747 ) : (
748 <Text text="Page One" />
749 )}
750 </div>
751 );
752 }
753
754 const root = ReactNoop.createRoot({
755 unstable_transitionCallbacks: transitionCallbacks,
756 });
757 await act(async () => {
758 root.render(<App />);
759 ReactNoop.expire(1000);
760 await advanceTimers(1000);
761
762 await waitForAll(['Page One']);
763 });
764
765 await act(async () => {
766 startTransition(() => navigateToPageTwo(), {name: 'page transition'});
767 ReactNoop.expire(1000);
768 await advanceTimers(1000);
769
770 await waitForAll([
771 'Suspend [Page Two]',
772 'Loading...',
773 // pre-warming
774 'Suspend [Page Two]',
775 'Suspend [Show Text One]',
776 'Show Text One Loading...',
777 'Suspend [Show Text Two]',
778 'Show Text Two Loading...',
779 // end pre-warming
780 'onTransitionStart(page transition, 1000)',
781 'onTransitionProgress(page transition, 1000, 2000, [suspense page])',
782 ]);
783
784 resolveText('Page Two');
785 ReactNoop.expire(1000);
786 await advanceTimers(1000);
787
788 await waitForAll([
789 'Page Two',
790 'Suspend [Show Text One]',
791 'Show Text One Loading...',
792 'Suspend [Show Text Two]',
793 'Show Text Two Loading...',
794 // pre-warming
795 'Suspend [Show Text One]',
796 'Suspend [Show Text Two]',
797 // end pre-warming
798 'onTransitionProgress(page transition, 1000, 3000, [show text one, show text two])',
799 ]);
800
801 resolveText('Show Text One');
802 ReactNoop.expire(1000);
803 await advanceTimers(1000);
804
805 await waitForAll([
806 'Show Text One',
807 'onTransitionProgress(page transition, 1000, 4000, [show text two])',
808 ]);
809
810 resolveText('Show Text Two');
811 ReactNoop.expire(1000);
812 await advanceTimers(1000);
813
814 await waitForAll([
815 'Show Text Two',
816 'onTransitionProgress(page transition, 1000, 5000, [])',
817 'onTransitionComplete(page transition, 1000, 5000)',
818 ]);
819 });
820 });
821
822 // @gate enableTransitionTracing
823 it('trace interactions with the same child suspense boundaries', async () => {
824 const transitionCallbacks = {
825 onTransitionStart: (name, startTime) => {
826 Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
827 },
828 onTransitionProgress: (name, startTime, endTime, pending) => {
829 const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
830 Scheduler.log(
831 `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
832 );
833 },
834 onTransitionComplete: (name, startTime, endTime) => {
835 Scheduler.log(
836 `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
837 );
838 },
839 };
840
841 let setNavigate;
842 let setShowTextOne;
843 let setShowTextTwo;
844 function App() {
845 const [navigate, _setNavigate] = useState(false);
846 const [showTextOne, _setShowTextOne] = useState(false);
847 const [showTextTwo, _setShowTextTwo] = useState(false);
848
849 setNavigate = () => _setNavigate(true);
850 setShowTextOne = () => _setShowTextOne(true);
851 setShowTextTwo = () => _setShowTextTwo(true);
852
853 return (
854 <div>
855 {navigate ? (
856 <>
857 <Suspense
858 fallback={<Text text="Loading..." />}
859 name="suspense page">
860 <AsyncText text="Page Two" />
861 {/* showTextOne is entangled with navigate */}
862 {showTextOne ? (
863 <Suspense
864 name="show text one"
865 fallback={<Text text="Show Text One Loading..." />}>
866 <AsyncText text="Show Text One" />
867 </Suspense>
868 ) : null}
869 <Suspense fallback={<Text text="Show Text Loading..." />}>
870 <AsyncText text="Show Text" />
871 </Suspense>
872 {/* showTextTwo's suspense boundaries shouldn't stop navigate's suspense boundaries
873 from completing */}
874 {showTextTwo ? (
875 <Suspense
876 name="show text two"
877 fallback={<Text text="Show Text Two Loading..." />}>
878 <AsyncText text="Show Text Two" />
879 </Suspense>
880 ) : null}
881 </Suspense>
882 </>
883 ) : (
884 <Text text="Page One" />
885 )}
886 </div>
887 );
888 }
889
890 const root = ReactNoop.createRoot({
891 unstable_transitionCallbacks: transitionCallbacks,
892 });
893 await act(async () => {
894 root.render(<App />);
895 ReactNoop.expire(1000);
896 await advanceTimers(1000);
897
898 await waitForAll(['Page One']);
899 });
900
901 await act(async () => {
902 startTransition(() => setNavigate(), {name: 'navigate'});
903 startTransition(() => setShowTextOne(), {name: 'show text one'});
904 ReactNoop.expire(1000);
905 await advanceTimers(1000);
906
907 await waitForAll([
908 'Suspend [Page Two]',
909 'Loading...',
910 // pre-warming
911 'Suspend [Page Two]',
912 'Suspend [Show Text One]',
913 'Show Text One Loading...',
914 'Suspend [Show Text]',
915 'Show Text Loading...',
916 // end pre-warming
917 'onTransitionStart(navigate, 1000)',
918 'onTransitionStart(show text one, 1000)',
919 'onTransitionProgress(navigate, 1000, 2000, [suspense page])',
920 'onTransitionProgress(show text one, 1000, 2000, [suspense page])',
921 ]);
922
923 resolveText('Page Two');
924 ReactNoop.expire(1000);
925 await advanceTimers(1000);
926 await waitForAll([
927 'Page Two',
928 'Suspend [Show Text One]',
929 'Show Text One Loading...',
930 'Suspend [Show Text]',
931 'Show Text Loading...',
932 // pre-warming
933 'Suspend [Show Text One]',
934 'Suspend [Show Text]',
935 // end pre-warming
936 'onTransitionProgress(navigate, 1000, 3000, [show text one, <null>])',
937 'onTransitionProgress(show text one, 1000, 3000, [show text one, <null>])',
938 ]);
939
940 startTransition(() => setShowTextTwo(), {name: 'show text two'});
941 ReactNoop.expire(1000);
942 await advanceTimers(1000);
943
944 await waitForAll([
945 'Page Two',
946 'Suspend [Show Text One]',
947 'Show Text One Loading...',
948 'Suspend [Show Text]',
949 'Show Text Loading...',
950 'Suspend [Show Text Two]',
951 'Show Text Two Loading...',
952 // pre-warming
953 'Suspend [Show Text One]',
954 'Suspend [Show Text]',
955 'Suspend [Show Text Two]',
956 // end pre-warming
957 'onTransitionStart(show text two, 3000)',
958 'onTransitionProgress(show text two, 3000, 4000, [show text two])',
959 ]);
960
961 // This should not cause navigate to finish because it's entangled with
962 // show text one
963 resolveText('Show Text');
964 ReactNoop.expire(1000);
965 await advanceTimers(1000);
966
967 await waitForAll([
968 'Show Text',
969 'onTransitionProgress(navigate, 1000, 5000, [show text one])',
970 'onTransitionProgress(show text one, 1000, 5000, [show text one])',
971 ]);
972
973 // This should not cause show text two to finish but nothing else
974 resolveText('Show Text Two');
975 ReactNoop.expire(1000);
976 await advanceTimers(1000);
977 await waitForAll([
978 'Show Text Two',
979 'onTransitionProgress(show text two, 3000, 6000, [])',
980 'onTransitionComplete(show text two, 3000, 6000)',
981 ]);
982
983 // This should cause everything to finish
984 resolveText('Show Text One');
985 ReactNoop.expire(1000);
986 await advanceTimers(1000);
987
988 await waitForAll([
989 'Show Text One',
990 'onTransitionProgress(navigate, 1000, 7000, [])',
991 'onTransitionProgress(show text one, 1000, 7000, [])',
992 'onTransitionComplete(navigate, 1000, 7000)',
993 'onTransitionComplete(show text one, 1000, 7000)',
994 ]);
995 });
996 });
997
998 // @gate enableTransitionTracing
999 it('should correctly trace basic interaction with tracing markers', async () => {
1000 const transitionCallbacks = {
1001 onTransitionStart: (name, startTime) => {
1002 Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
1003 },
1004 onTransitionProgress: (name, startTime, endTime, pending) => {
1005 const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
1006 Scheduler.log(
1007 `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
1008 );
1009 },
1010 onTransitionComplete: (name, startTime, endTime) => {
1011 Scheduler.log(
1012 `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
1013 );
1014 },
1015 onMarkerProgress: (
1016 transitioName,
1017 markerName,
1018 startTime,
1019 currentTime,
1020 pending,
1021 ) => {
1022 const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
1023 Scheduler.log(
1024 `onMarkerProgress(${transitioName}, ${markerName}, ${startTime}, ${currentTime}, [${suspenseNames}])`,
1025 );
1026 },
1027 onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
1028 Scheduler.log(
1029 `onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
1030 );
1031 },
1032 };
1033
1034 let navigateToPageTwo;
1035 function App() {
1036 const [navigate, setNavigate] = useState(false);
1037 navigateToPageTwo = () => {
1038 setNavigate(true);
1039 };
1040
1041 return (
1042 <div>
1043 {navigate ? (
1044 <React.unstable_TracingMarker name="marker two" key="marker two">
1045 <Text text="Page Two" />
1046 </React.unstable_TracingMarker>
1047 ) : (
1048 <React.unstable_TracingMarker name="marker one">
1049 <Text text="Page One" />
1050 </React.unstable_TracingMarker>
1051 )}
1052 </div>
1053 );
1054 }
1055
1056 const root = ReactNoop.createRoot({
1057 unstable_transitionCallbacks: transitionCallbacks,
1058 });
1059 await act(async () => {
1060 root.render(<App />);
1061 ReactNoop.expire(1000);
1062 await advanceTimers(1000);
1063
1064 await waitForAll(['Page One']);
1065
1066 await act(async () => {
1067 startTransition(() => navigateToPageTwo(), {name: 'page transition'});
1068
1069 ReactNoop.expire(1000);
1070 await advanceTimers(1000);
1071
1072 await waitForAll([
1073 'Page Two',
1074 'onTransitionStart(page transition, 1000)',
1075 'onMarkerComplete(page transition, marker two, 1000, 2000)',
1076 'onTransitionComplete(page transition, 1000, 2000)',
1077 ]);
1078 });
1079 });
1080 });
1081
1082 // @gate enableTransitionTracing
1083 it('should correctly trace interactions for tracing markers', async () => {
1084 const transitionCallbacks = {
1085 onTransitionStart: (name, startTime) => {
1086 Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
1087 },
1088 onTransitionComplete: (name, startTime, endTime) => {
1089 Scheduler.log(
1090 `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
1091 );
1092 },
1093 onMarkerProgress: (
1094 transitioName,
1095 markerName,
1096 startTime,
1097 currentTime,
1098 pending,
1099 ) => {
1100 const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
1101 Scheduler.log(
1102 `onMarkerProgress(${transitioName}, ${markerName}, ${startTime}, ${currentTime}, [${suspenseNames}])`,
1103 );
1104 },
1105 onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
1106 Scheduler.log(
1107 `onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
1108 );
1109 },
1110 };
1111 let navigateToPageTwo;
1112 function App() {
1113 const [navigate, setNavigate] = useState(false);
1114 navigateToPageTwo = () => {
1115 setNavigate(true);
1116 };
1117
1118 return (
1119 <div>
1120 {navigate ? (
1121 <Suspense
1122 fallback={<Text text="Loading..." />}
1123 name="suspense page">
1124 <AsyncText text="Page Two" />
1125 <React.unstable_TracingMarker name="sync marker" />
1126 <React.unstable_TracingMarker name="async marker">
1127 <Suspense
1128 fallback={<Text text="Loading..." />}
1129 name="marker suspense">
1130 <AsyncText text="Marker Text" />
1131 </Suspense>
1132 </React.unstable_TracingMarker>
1133 </Suspense>
1134 ) : (
1135 <Text text="Page One" />
1136 )}
1137 </div>
1138 );
1139 }
1140
1141 const root = ReactNoop.createRoot({
1142 unstable_transitionCallbacks: transitionCallbacks,
1143 });
1144 await act(async () => {
1145 root.render(<App />);
1146 ReactNoop.expire(1000);
1147 await advanceTimers(1000);
1148
1149 await waitForAll(['Page One']);
1150 });
1151
1152 await act(async () => {
1153 startTransition(() => navigateToPageTwo(), {name: 'page transition'});
1154
1155 ReactNoop.expire(1000);
1156 await advanceTimers(1000);
1157
1158 await waitForAll([
1159 'Suspend [Page Two]',
1160 'Loading...',
1161 // pre-warming
1162 'Suspend [Page Two]',
1163 'Suspend [Marker Text]',
1164 'Loading...',
1165 // end pre-warming
1166 'onTransitionStart(page transition, 1000)',
1167 ]);
1168
1169 ReactNoop.expire(1000);
1170 await advanceTimers(1000);
1171 await resolveText('Page Two');
1172
1173 await waitForAll([
1174 'Page Two',
1175 'Suspend [Marker Text]',
1176 'Loading...',
1177 // pre-warming
1178 'Suspend [Marker Text]',
1179 // end pre-warming
1180 'onMarkerProgress(page transition, async marker, 1000, 3000, [marker suspense])',
1181 'onMarkerComplete(page transition, sync marker, 1000, 3000)',
1182 ]);
1183
1184 ReactNoop.expire(1000);
1185 await advanceTimers(1000);
1186 await resolveText('Marker Text');
1187
1188 await waitForAll([
1189 'Marker Text',
1190 'onMarkerProgress(page transition, async marker, 1000, 4000, [])',
1191 'onMarkerComplete(page transition, async marker, 1000, 4000)',
1192 'onTransitionComplete(page transition, 1000, 4000)',
1193 ]);
1194 });
1195 });
1196
1197 // @gate enableTransitionTracing
1198 it('trace interaction with multiple tracing markers', async () => {
1199 const transitionCallbacks = {
1200 onTransitionStart: (name, startTime) => {
1201 Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
1202 },
1203 onTransitionComplete: (name, startTime, endTime) => {
1204 Scheduler.log(
1205 `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
1206 );
1207 },
1208 onMarkerProgress: (
1209 transitioName,
1210 markerName,
1211 startTime,
1212 currentTime,
1213 pending,
1214 ) => {
1215 const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
1216 Scheduler.log(
1217 `onMarkerProgress(${transitioName}, ${markerName}, ${startTime}, ${currentTime}, [${suspenseNames}])`,
1218 );
1219 },
1220 onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
1221 Scheduler.log(
1222 `onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
1223 );
1224 },
1225 };
1226
1227 let navigateToPageTwo;
1228 function App() {
1229 const [navigate, setNavigate] = useState(false);
1230 navigateToPageTwo = () => {
1231 setNavigate(true);
1232 };
1233
1234 return (
1235 <div>
1236 {navigate ? (
1237 <React.unstable_TracingMarker name="outer marker">
1238 <Suspense fallback={<Text text="Outer..." />} name="outer">
1239 <AsyncText text="Outer Text" />
1240 <Suspense
1241 fallback={<Text text="Inner One..." />}
1242 name="inner one">
1243 <React.unstable_TracingMarker name="marker one">
1244 <AsyncText text="Inner Text One" />
1245 </React.unstable_TracingMarker>
1246 </Suspense>
1247 <Suspense
1248 fallback={<Text text="Inner Two..." />}
1249 name="inner two">
1250 <React.unstable_TracingMarker name="marker two">
1251 <AsyncText text="Inner Text Two" />
1252 </React.unstable_TracingMarker>
1253 </Suspense>
1254 </Suspense>
1255 </React.unstable_TracingMarker>
1256 ) : (
1257 <Text text="Page One" />
1258 )}
1259 </div>
1260 );
1261 }
1262
1263 const root = ReactNoop.createRoot({
1264 unstable_transitionCallbacks: transitionCallbacks,
1265 });
1266 await act(async () => {
1267 root.render(<App />);
1268 ReactNoop.expire(1000);
1269 await advanceTimers(1000);
1270
1271 await waitForAll(['Page One']);
1272 });
1273
1274 await act(async () => {
1275 startTransition(() => navigateToPageTwo(), {name: 'page transition'});
1276
1277 ReactNoop.expire(1000);
1278 await advanceTimers(1000);
1279
1280 await waitForAll([
1281 'Suspend [Outer Text]',
1282 'Outer...',
1283 // pre-warming
1284 'Suspend [Outer Text]',
1285 'Suspend [Inner Text One]',
1286 'Inner One...',
1287 'Suspend [Inner Text Two]',
1288 'Inner Two...',
1289 // end pre-warming
1290 'onTransitionStart(page transition, 1000)',
1291 'onMarkerProgress(page transition, outer marker, 1000, 2000, [outer])',
1292 ]);
1293
1294 ReactNoop.expire(1000);
1295 await advanceTimers(1000);
1296 await resolveText('Inner Text Two');
1297 await waitForAll([]);
1298
1299 ReactNoop.expire(1000);
1300 await advanceTimers(1000);
1301 await resolveText('Outer Text');
1302 await waitForAll([
1303 'Outer Text',
1304 'Suspend [Inner Text One]',
1305 'Inner One...',
1306 'Inner Text Two',
1307 // pre-warming
1308 'Suspend [Inner Text One]',
1309 // end pre-warming
1310 'onMarkerProgress(page transition, outer marker, 1000, 4000, [inner one])',
1311 'onMarkerComplete(page transition, marker two, 1000, 4000)',
1312 ]);
1313
1314 ReactNoop.expire(1000);
1315 await advanceTimers(1000);
1316 await resolveText('Inner Text One');
1317 await waitForAll([
1318 'Inner Text One',
1319 'onMarkerProgress(page transition, outer marker, 1000, 5000, [])',
1320 'onMarkerComplete(page transition, marker one, 1000, 5000)',
1321 'onMarkerComplete(page transition, outer marker, 1000, 5000)',
1322 'onTransitionComplete(page transition, 1000, 5000)',
1323 ]);
1324 });
1325 });
1326
1327 // @gate enableTransitionTracing
1328 // eslint-disable-next-line jest/no-disabled-tests
1329 it.skip('warn and calls marker incomplete if name changes before transition completes', async () => {
1330 const transitionCallbacks = {
1331 onTransitionStart: (name, startTime) => {
1332 Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
1333 },
1334 onTransitionProgress: (name, startTime, endTime, pending) => {
1335 const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
1336 Scheduler.log(
1337 `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
1338 );
1339 },
1340 onTransitionComplete: (name, startTime, endTime) => {
1341 Scheduler.log(
1342 `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
1343 );
1344 },
1345 onMarkerProgress: (
1346 transitioName,
1347 markerName,
1348 startTime,
1349 currentTime,
1350 pending,
1351 ) => {
1352 const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
1353 Scheduler.log(
1354 `onMarkerProgress(${transitioName}, ${markerName}, ${startTime}, ${currentTime}, [${suspenseNames}])`,
1355 );
1356 },
1357 onMarkerIncomplete: (
1358 transitionName,
1359 markerName,
1360 startTime,
1361 deletions,
1362 ) => {
1363 Scheduler.log(
1364 `onMarkerIncomplete(${transitionName}, ${markerName}, ${startTime}, [${stringifyDeletions(
1365 deletions,
1366 )}])`,
1367 );
1368 },
1369 onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
1370 Scheduler.log(
1371 `onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
1372 );
1373 },
1374 };
1375
1376 function App({navigate, markerName}) {
1377 return (
1378 <div>
1379 {navigate ? (
1380 <React.unstable_TracingMarker name={markerName}>
1381 <Suspense fallback={<Text text="Loading..." />}>
1382 <AsyncText text="Page Two" />
1383 </Suspense>
1384 </React.unstable_TracingMarker>
1385 ) : (
1386 <Text text="Page One" />
1387 )}
1388 </div>
1389 );
1390 }
1391
1392 const root = ReactNoop.createRoot({
1393 unstable_transitionCallbacks: transitionCallbacks,
1394 });
1395 await act(async () => {
1396 root.render(<App navigate={false} markerName="marker one" />);
1397 ReactNoop.expire(1000);
1398 await advanceTimers(1000);
1399 await waitForAll(['Page One']);
1400
1401 startTransition(
1402 () => root.render(<App navigate={true} markerName="marker one" />),
1403 {
1404 name: 'transition one',
1405 },
1406 );
1407 ReactNoop.expire(1000);
1408 await advanceTimers(1000);
1409
1410 await waitForAll([
1411 'Suspend [Page Two]',
1412 'Loading...',
1413 'onTransitionStart(transition one, 1000)',
1414 'onMarkerProgress(transition one, marker one, 1000, 2000, [<null>])',
1415 'onTransitionProgress(transition one, 1000, 2000, [<null>])',
1416 ]);
1417
1418 root.render(<App navigate={true} markerName="marker two" />);
1419 ReactNoop.expire(1000);
1420 await advanceTimers(1000);
1421
1422 await waitForAll([
1423 'Suspend [Page Two]',
1424 'Loading...',
1425 'onMarkerIncomplete(transition one, marker one, 1000, [{endTime: 3000, name: marker one, newName: marker two, type: marker}])',
1426 ]);
1427
1428 resolveText('Page Two');
1429 ReactNoop.expire(1000);
1430 await advanceTimers(1000);
1431 await waitForAll([
1432 'Page Two',
1433 'onMarkerProgress(transition one, marker one, 1000, 4000, [])',
1434 'onTransitionProgress(transition one, 1000, 4000, [])',
1435 'onTransitionComplete(transition one, 1000, 4000)',
1436 ]);
1437 });
1438 });
1439
1440 // @gate enableTransitionTracing
1441 it('marker incomplete for tree with parent and sibling tracing markers', async () => {
1442 const transitionCallbacks = {
1443 onTransitionStart: (name, startTime) => {
1444 Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
1445 },
1446 onTransitionProgress: (name, startTime, endTime, pending) => {
1447 const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
1448 Scheduler.log(
1449 `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
1450 );
1451 },
1452 onTransitionComplete: (name, startTime, endTime) => {
1453 Scheduler.log(
1454 `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
1455 );
1456 },
1457 onMarkerProgress: (
1458 transitioName,
1459 markerName,
1460 startTime,
1461 currentTime,
1462 pending,
1463 ) => {
1464 const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
1465 Scheduler.log(
1466 `onMarkerProgress(${transitioName}, ${markerName}, ${startTime}, ${currentTime}, [${suspenseNames}])`,
1467 );
1468 },
1469 onMarkerIncomplete: (
1470 transitionName,
1471 markerName,
1472 startTime,
1473 deletions,
1474 ) => {
1475 Scheduler.log(
1476 `onMarkerIncomplete(${transitionName}, ${markerName}, ${startTime}, [${stringifyDeletions(
1477 deletions,
1478 )}])`,
1479 );
1480 },
1481 onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
1482 Scheduler.log(
1483 `onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
1484 );
1485 },
1486 };
1487
1488 function App({navigate, showMarker}) {
1489 return (
1490 <div>
1491 {navigate ? (
1492 <React.unstable_TracingMarker name="parent">
1493 {showMarker ? (
1494 <React.unstable_TracingMarker name="marker one">
1495 <Suspense
1496 name="suspense page"
1497 fallback={<Text text="Loading..." />}>
1498 <AsyncText text="Page Two" />
1499 </Suspense>
1500 </React.unstable_TracingMarker>
1501 ) : (
1502 <Suspense
1503 name="suspense page"
1504 fallback={<Text text="Loading..." />}>
1505 <AsyncText text="Page Two" />
1506 </Suspense>
1507 )}
1508 <React.unstable_TracingMarker name="sibling">
1509 <Suspense
1510 name="suspense sibling"
1511 fallback={<Text text="Sibling Loading..." />}>
1512 <AsyncText text="Sibling Text" />
1513 </Suspense>
1514 </React.unstable_TracingMarker>
1515 </React.unstable_TracingMarker>
1516 ) : (
1517 <Text text="Page One" />
1518 )}
1519 </div>
1520 );
1521 }
1522
1523 const root = ReactNoop.createRoot({
1524 unstable_transitionCallbacks: transitionCallbacks,
1525 });
1526 await act(async () => {
1527 root.render(<App navigate={false} showMarker={true} />);
1528 ReactNoop.expire(1000);
1529 await advanceTimers(1000);
1530 await waitForAll(['Page One']);
1531
1532 startTransition(
1533 () => root.render(<App navigate={true} showMarker={true} />),
1534 {
1535 name: 'transition one',
1536 },
1537 );
1538 ReactNoop.expire(1000);
1539 await advanceTimers(1000);
1540 await waitForAll([
1541 'Suspend [Page Two]',
1542 'Loading...',
1543 'Suspend [Sibling Text]',
1544 'Sibling Loading...',
1545 // pre-warming
1546 'Suspend [Page Two]',
1547 'Suspend [Sibling Text]',
1548 // end pre-warming
1549 'onTransitionStart(transition one, 1000)',
1550 'onMarkerProgress(transition one, parent, 1000, 2000, [suspense page, suspense sibling])',
1551 'onMarkerProgress(transition one, marker one, 1000, 2000, [suspense page])',
1552 'onMarkerProgress(transition one, sibling, 1000, 2000, [suspense sibling])',
1553 'onTransitionProgress(transition one, 1000, 2000, [suspense page, suspense sibling])',
1554 ]);
1555 root.render(<App navigate={true} showMarker={false} />);
1556
1557 ReactNoop.expire(1000);
1558 await advanceTimers(1000);
1559 await waitForAll([
1560 'Suspend [Page Two]',
1561 'Loading...',
1562 'Suspend [Sibling Text]',
1563 'Sibling Loading...',
1564 // pre-warming
1565 'Suspend [Page Two]',
1566 'Suspend [Sibling Text]',
1567 // end pre-warming
1568 'onMarkerProgress(transition one, parent, 1000, 3000, [suspense sibling])',
1569 'onMarkerIncomplete(transition one, marker one, 1000, [{endTime: 3000, name: marker one, type: marker}, {endTime: 3000, name: suspense page, type: suspense}])',
1570 'onMarkerIncomplete(transition one, parent, 1000, [{endTime: 3000, name: marker one, type: marker}, {endTime: 3000, name: suspense page, type: suspense}])',
1571 ]);
1572
1573 root.render(<App navigate={true} showMarker={true} />);
1574 ReactNoop.expire(1000);
1575 await advanceTimers(1000);
1576 await waitForAll([
1577 'Suspend [Page Two]',
1578 'Loading...',
1579 'Suspend [Sibling Text]',
1580 'Sibling Loading...',
1581 // pre-warming
1582 'Suspend [Page Two]',
1583 'Suspend [Sibling Text]',
1584 ]);
1585 });
1586
1587 resolveText('Page Two');
1588 ReactNoop.expire(1000);
1589 await advanceTimers(1000);
1590 await waitForAll(['Page Two']);
1591
1592 resolveText('Sibling Text');
1593 ReactNoop.expire(1000);
1594 await advanceTimers(1000);
1595 await waitForAll([
1596 'Sibling Text',
1597 'onMarkerProgress(transition one, parent, 1000, 6000, [])',
1598 'onMarkerProgress(transition one, sibling, 1000, 6000, [])',
1599 // Calls markerComplete and transitionComplete for all parents
1600 'onMarkerComplete(transition one, sibling, 1000, 6000)',
1601 'onTransitionProgress(transition one, 1000, 6000, [])',
1602 ]);
1603 });
1604
1605 // @gate enableTransitionTracing
1606 it('marker gets deleted', async () => {
1607 const transitionCallbacks = {
1608 onTransitionStart: (name, startTime) => {
1609 Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
1610 },
1611 onTransitionProgress: (name, startTime, endTime, pending) => {
1612 const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
1613 Scheduler.log(
1614 `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
1615 );
1616 },
1617 onTransitionComplete: (name, startTime, endTime) => {
1618 Scheduler.log(
1619 `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
1620 );
1621 },
1622 onMarkerProgress: (
1623 transitioName,
1624 markerName,
1625 startTime,
1626 currentTime,
1627 pending,
1628 ) => {
1629 const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
1630 Scheduler.log(
1631 `onMarkerProgress(${transitioName}, ${markerName}, ${startTime}, ${currentTime}, [${suspenseNames}])`,
1632 );
1633 },
1634 onMarkerIncomplete: (
1635 transitionName,
1636 markerName,
1637 startTime,
1638 deletions,
1639 ) => {
1640 Scheduler.log(
1641 `onMarkerIncomplete(${transitionName}, ${markerName}, ${startTime}, [${stringifyDeletions(
1642 deletions,
1643 )}])`,
1644 );
1645 },
1646 onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
1647 Scheduler.log(
1648 `onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
1649 );
1650 },
1651 };
1652
1653 function App({navigate, deleteOne}) {
1654 return (
1655 <div>
1656 {navigate ? (
1657 <React.unstable_TracingMarker name="parent">
1658 {!deleteOne ? (
1659 <div>
1660 <React.unstable_TracingMarker name="one">
1661 <Suspense
1662 name="suspense one"
1663 fallback={<Text text="Loading One..." />}>
1664 <AsyncText text="Page One" />
1665 </Suspense>
1666 </React.unstable_TracingMarker>
1667 </div>
1668 ) : null}
1669 <React.unstable_TracingMarker name="two">
1670 <Suspense
1671 name="suspense two"
1672 fallback={<Text text="Loading Two..." />}>
1673 <AsyncText text="Page Two" />
1674 </Suspense>
1675 </React.unstable_TracingMarker>
1676 </React.unstable_TracingMarker>
1677 ) : (
1678 <Text text="Page One" />
1679 )}
1680 </div>
1681 );
1682 }
1683 const root = ReactNoop.createRoot({
1684 unstable_transitionCallbacks: transitionCallbacks,
1685 });
1686 await act(async () => {
1687 root.render(<App navigate={false} deleteOne={false} />);
1688 ReactNoop.expire(1000);
1689 await advanceTimers(1000);
1690 await waitForAll(['Page One']);
1691
1692 startTransition(
1693 () => root.render(<App navigate={true} deleteOne={false} />),
1694 {
1695 name: 'transition',
1696 },
1697 );
1698 ReactNoop.expire(1000);
1699 await advanceTimers(1000);
1700 await waitForAll([
1701 'Suspend [Page One]',
1702 'Loading One...',
1703 'Suspend [Page Two]',
1704 'Loading Two...',
1705 // pre-warming
1706 'Suspend [Page One]',
1707 'Suspend [Page Two]',
1708 // end pre-warming
1709 'onTransitionStart(transition, 1000)',
1710 'onMarkerProgress(transition, parent, 1000, 2000, [suspense one, suspense two])',
1711 'onMarkerProgress(transition, one, 1000, 2000, [suspense one])',
1712 'onMarkerProgress(transition, two, 1000, 2000, [suspense two])',
1713 'onTransitionProgress(transition, 1000, 2000, [suspense one, suspense two])',
1714 ]);
1715
1716 root.render(<App navigate={true} deleteOne={true} />);
1717 ReactNoop.expire(1000);
1718 await advanceTimers(1000);
1719 await waitForAll([
1720 'Suspend [Page Two]',
1721 'Loading Two...',
1722 // pre-warming
1723 'Suspend [Page Two]',
1724 // end pre-warming
1725 'onMarkerProgress(transition, parent, 1000, 3000, [suspense two])',
1726 'onMarkerIncomplete(transition, one, 1000, [{endTime: 3000, name: one, type: marker}, {endTime: 3000, name: suspense one, type: suspense}])',
1727 'onMarkerIncomplete(transition, parent, 1000, [{endTime: 3000, name: one, type: marker}, {endTime: 3000, name: suspense one, type: suspense}])',
1728 ]);
1729
1730 await resolveText('Page Two');
1731 ReactNoop.expire(1000);
1732 await advanceTimers(1000);
1733 await waitForAll([
1734 'Page Two',
1735 // Marker progress will still get called after incomplete but not marker complete
1736 'onMarkerProgress(transition, parent, 1000, 4000, [])',
1737 'onMarkerProgress(transition, two, 1000, 4000, [])',
1738 'onMarkerComplete(transition, two, 1000, 4000)',
1739 // Transition progress will still get called after incomplete but not transition complete
1740 'onTransitionProgress(transition, 1000, 4000, [])',
1741 ]);
1742 });
1743 });
1744
1745 // @gate enableTransitionTracing
1746 it('Suspense boundary added by the transition is deleted', async () => {
1747 const transitionCallbacks = {
1748 onTransitionStart: (name, startTime) => {
1749 Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
1750 },
1751 onTransitionProgress: (name, startTime, endTime, pending) => {
1752 const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
1753 Scheduler.log(
1754 `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
1755 );
1756 },
1757 onTransitionComplete: (name, startTime, endTime) => {
1758 Scheduler.log(
1759 `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
1760 );
1761 },
1762 onMarkerProgress: (
1763 transitioName,
1764 markerName,
1765 startTime,
1766 currentTime,
1767 pending,
1768 ) => {
1769 const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
1770 Scheduler.log(
1771 `onMarkerProgress(${transitioName}, ${markerName}, ${startTime}, ${currentTime}, [${suspenseNames}])`,
1772 );
1773 },
1774 onMarkerIncomplete: (
1775 transitionName,
1776 markerName,
1777 startTime,
1778 deletions,
1779 ) => {
1780 Scheduler.log(
1781 `onMarkerIncomplete(${transitionName}, ${markerName}, ${startTime}, [${stringifyDeletions(
1782 deletions,
1783 )}])`,
1784 );
1785 },
1786 onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
1787 Scheduler.log(
1788 `onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
1789 );
1790 },
1791 };
1792
1793 function App({navigate, deleteOne}) {
1794 return (
1795 <div>
1796 {navigate ? (
1797 <React.unstable_TracingMarker name="parent">
1798 <React.unstable_TracingMarker name="one">
1799 {!deleteOne ? (
1800 <Suspense
1801 name="suspense one"
1802 fallback={<Text text="Loading One..." />}>
1803 <AsyncText text="Page One" />
1804 <React.unstable_TracingMarker name="page one" />
1805 <Suspense
1806 name="suspense child"
1807 fallback={<Text text="Loading Child..." />}>
1808 <React.unstable_TracingMarker name="child" />
1809 <AsyncText text="Child" />
1810 </Suspense>
1811 </Suspense>
1812 ) : null}
1813 </React.unstable_TracingMarker>
1814 <React.unstable_TracingMarker name="two">
1815 <Suspense
1816 name="suspense two"
1817 fallback={<Text text="Loading Two..." />}>
1818 <AsyncText text="Page Two" />
1819 </Suspense>
1820 </React.unstable_TracingMarker>
1821 </React.unstable_TracingMarker>
1822 ) : (
1823 <Text text="Page One" />
1824 )}
1825 </div>
1826 );
1827 }
1828 const root = ReactNoop.createRoot({
1829 unstable_transitionCallbacks: transitionCallbacks,
1830 });
1831 await act(async () => {
1832 root.render(<App navigate={false} deleteOne={false} />);
1833
1834 ReactNoop.expire(1000);
1835 await advanceTimers(1000);
1836 await waitForAll(['Page One']);
1837
1838 startTransition(
1839 () => root.render(<App navigate={true} deleteOne={false} />),
1840 {
1841 name: 'transition',
1842 },
1843 );
1844 ReactNoop.expire(1000);
1845 await advanceTimers(1000);
1846 await waitForAll([
1847 'Suspend [Page One]',
1848 'Loading One...',
1849 'Suspend [Page Two]',
1850 'Loading Two...',
1851 // pre-warming
1852 'Suspend [Page One]',
1853 'Suspend [Child]',
1854 'Loading Child...',
1855 'Suspend [Page Two]',
1856 // end pre-warming
1857 'onTransitionStart(transition, 1000)',
1858 'onMarkerProgress(transition, parent, 1000, 2000, [suspense one, suspense two])',
1859 'onMarkerProgress(transition, one, 1000, 2000, [suspense one])',
1860 'onMarkerProgress(transition, two, 1000, 2000, [suspense two])',
1861 'onTransitionProgress(transition, 1000, 2000, [suspense one, suspense two])',
1862 ]);
1863
1864 await resolveText('Page One');
1865 ReactNoop.expire(1000);
1866 await advanceTimers(1000);
1867 await waitForAll([
1868 'Page One',
1869 'Suspend [Child]',
1870 'Loading Child...',
1871 // pre-warming
1872 'Suspend [Child]',
1873 // end pre-warming
1874 'onMarkerProgress(transition, parent, 1000, 3000, [suspense two, suspense child])',
1875 'onMarkerProgress(transition, one, 1000, 3000, [suspense child])',
1876 'onMarkerComplete(transition, page one, 1000, 3000)',
1877 'onTransitionProgress(transition, 1000, 3000, [suspense two, suspense child])',
1878 ]);
1879
1880 root.render(<App navigate={true} deleteOne={true} />);
1881 ReactNoop.expire(1000);
1882 await advanceTimers(1000);
1883 await waitForAll([
1884 'Suspend [Page Two]',
1885 'Loading Two...',
1886 // pre-warming
1887 'Suspend [Page Two]',
1888 // end pre-warming
1889
1890 // "suspense one" has unsuspended so shouldn't be included
1891 // tracing marker "page one" has completed so shouldn't be included
1892 // all children of "suspense child" haven't yet been rendered so shouldn't be included
1893 'onMarkerProgress(transition, one, 1000, 4000, [])',
1894 'onMarkerProgress(transition, parent, 1000, 4000, [suspense two])',
1895 'onMarkerIncomplete(transition, one, 1000, [{endTime: 4000, name: suspense child, type: suspense}])',
1896 'onMarkerIncomplete(transition, parent, 1000, [{endTime: 4000, name: suspense child, type: suspense}])',
1897 ]);
1898
1899 await resolveText('Page Two');
1900 ReactNoop.expire(1000);
1901 await advanceTimers(1000);
1902 await waitForAll([
1903 'Page Two',
1904 'onMarkerProgress(transition, parent, 1000, 5000, [])',
1905 'onMarkerProgress(transition, two, 1000, 5000, [])',
1906 'onMarkerComplete(transition, two, 1000, 5000)',
1907 'onTransitionProgress(transition, 1000, 5000, [])',
1908 ]);
1909 });
1910 });
1911
1912 // @gate enableTransitionTracing
1913 it('Suspense boundary not added by the transition is deleted', async () => {
1914 const transitionCallbacks = {
1915 onTransitionStart: (name, startTime) => {
1916 Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
1917 },
1918 onTransitionProgress: (name, startTime, endTime, pending) => {
1919 const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
1920 Scheduler.log(
1921 `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
1922 );
1923 },
1924 onTransitionComplete: (name, startTime, endTime) => {
1925 Scheduler.log(
1926 `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
1927 );
1928 },
1929 onMarkerProgress: (
1930 transitioName,
1931 markerName,
1932 startTime,
1933 currentTime,
1934 pending,
1935 ) => {
1936 const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
1937 Scheduler.log(
1938 `onMarkerProgress(${transitioName}, ${markerName}, ${startTime}, ${currentTime}, [${suspenseNames}])`,
1939 );
1940 },
1941 onMarkerIncomplete: (
1942 transitionName,
1943 markerName,
1944 startTime,
1945 deletions,
1946 ) => {
1947 Scheduler.log(
1948 `onMarkerIncomplete(${transitionName}, ${markerName}, ${startTime}, [${stringifyDeletions(
1949 deletions,
1950 )}])`,
1951 );
1952 },
1953 onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
1954 Scheduler.log(
1955 `onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
1956 );
1957 },
1958 };
1959
1960 function App({show}) {
1961 return (
1962 <React.unstable_TracingMarker name="parent">
1963 {show ? (
1964 <Suspense name="appended child">
1965 <AsyncText text="Appended child" />
1966 </Suspense>
1967 ) : null}
1968 <Suspense name="child">
1969 <AsyncText text="Child" />
1970 </Suspense>
1971 </React.unstable_TracingMarker>
1972 );
1973 }
1974
1975 const root = ReactNoop.createRoot({
1976 unstable_transitionCallbacks: transitionCallbacks,
1977 });
1978 await act(async () => {
1979 startTransition(() => root.render(<App show={false} />), {
1980 name: 'transition',
1981 });
1982 ReactNoop.expire(1000);
1983 await advanceTimers(1000);
1984
1985 await waitForAll([
1986 'Suspend [Child]',
1987 // pre-warming
1988 'Suspend [Child]',
1989 // end pre-warming
1990 'onTransitionStart(transition, 0)',
1991 'onMarkerProgress(transition, parent, 0, 1000, [child])',
1992 'onTransitionProgress(transition, 0, 1000, [child])',
1993 ]);
1994
1995 root.render(<App show={true} />);
1996 ReactNoop.expire(1000);
1997 await advanceTimers(1000);
1998 // This appended child isn't part of the transition so we
1999 // don't call any callback
2000 await waitForAll([
2001 'Suspend [Appended child]',
2002 'Suspend [Child]',
2003 // pre-warming
2004 'Suspend [Appended child]',
2005 'Suspend [Child]',
2006 ]);
2007
2008 // This deleted child isn't part of the transition so we
2009 // don't call any callbacks
2010 root.render(<App show={false} />);
2011 ReactNoop.expire(1000);
2012 await advanceTimers(1000);
2013 await waitForAll([
2014 'Suspend [Child]',
2015 // pre-warming
2016 'Suspend [Child]',
2017 ]);
2018
2019 await resolveText('Child');
2020 ReactNoop.expire(1000);
2021 await advanceTimers(1000);
2022
2023 await waitForAll([
2024 'Child',
2025 'onMarkerProgress(transition, parent, 0, 4000, [])',
2026 'onMarkerComplete(transition, parent, 0, 4000)',
2027 'onTransitionProgress(transition, 0, 4000, [])',
2028 'onTransitionComplete(transition, 0, 4000)',
2029 ]);
2030 });
2031 });
2032
2033 // @gate enableTransitionTracing
2034 it('marker incomplete gets called properly if child suspense marker is not part of it', async () => {
2035 const transitionCallbacks = {
2036 onTransitionStart: (name, startTime) => {
2037 Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
2038 },
2039 onTransitionProgress: (name, startTime, endTime, pending) => {
2040 const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
2041 Scheduler.log(
2042 `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
2043 );
2044 },
2045 onTransitionComplete: (name, startTime, endTime) => {
2046 Scheduler.log(
2047 `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
2048 );
2049 },
2050 onMarkerProgress: (
2051 transitioName,
2052 markerName,
2053 startTime,
2054 currentTime,
2055 pending,
2056 ) => {
2057 const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
2058 Scheduler.log(
2059 `onMarkerProgress(${transitioName}, ${markerName}, ${startTime}, ${currentTime}, [${suspenseNames}])`,
2060 );
2061 },
2062 onMarkerIncomplete: (
2063 transitionName,
2064 markerName,
2065 startTime,
2066 deletions,
2067 ) => {
2068 Scheduler.log(
2069 `onMarkerIncomplete(${transitionName}, ${markerName}, ${startTime}, [${stringifyDeletions(
2070 deletions,
2071 )}])`,
2072 );
2073 },
2074 onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
2075 Scheduler.log(
2076 `onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
2077 );
2078 },
2079 };
2080
2081 function App({show, showSuspense}) {
2082 return (
2083 <React.unstable_TracingMarker name="parent">
2084 {show ? (
2085 <React.unstable_TracingMarker name="appended child">
2086 {showSuspense ? (
2087 <Suspense name="appended child">
2088 <AsyncText text="Appended child" />
2089 </Suspense>
2090 ) : null}
2091 </React.unstable_TracingMarker>
2092 ) : null}
2093 <Suspense name="child">
2094 <AsyncText text="Child" />
2095 </Suspense>
2096 </React.unstable_TracingMarker>
2097 );
2098 }
2099
2100 const root = ReactNoop.createRoot({
2101 unstable_transitionCallbacks: transitionCallbacks,
2102 });
2103
2104 await act(async () => {
2105 startTransition(
2106 () => root.render(<App show={false} showSuspense={false} />),
2107 {
2108 name: 'transition one',
2109 },
2110 );
2111
2112 ReactNoop.expire(1000);
2113 await advanceTimers(1000);
2114 });
2115
2116 assertLog([
2117 'Suspend [Child]',
2118 // pre-warming
2119 'Suspend [Child]',
2120 // end pre-warming
2121 'onTransitionStart(transition one, 0)',
2122 'onMarkerProgress(transition one, parent, 0, 1000, [child])',
2123 'onTransitionProgress(transition one, 0, 1000, [child])',
2124 ]);
2125
2126 await act(async () => {
2127 startTransition(
2128 () => root.render(<App show={true} showSuspense={true} />),
2129 {
2130 name: 'transition two',
2131 },
2132 );
2133
2134 ReactNoop.expire(1000);
2135 await advanceTimers(1000);
2136 });
2137
2138 assertLog([
2139 'Suspend [Appended child]',
2140 'Suspend [Child]',
2141 // pre-warming
2142 'Suspend [Appended child]',
2143 'Suspend [Child]',
2144 // end pre-warming
2145 'onTransitionStart(transition two, 1000)',
2146 'onMarkerProgress(transition two, appended child, 1000, 2000, [appended child])',
2147 'onTransitionProgress(transition two, 1000, 2000, [appended child])',
2148 ]);
2149
2150 await act(async () => {
2151 root.render(<App show={true} showSuspense={false} />);
2152 ReactNoop.expire(1000);
2153 await advanceTimers(1000);
2154 });
2155
2156 assertLog([
2157 'Suspend [Child]',
2158 // pre-warming
2159 'Suspend [Child]',
2160 // end pre-warming
2161 'onMarkerProgress(transition two, appended child, 1000, 3000, [])',
2162 'onMarkerIncomplete(transition two, appended child, 1000, [{endTime: 3000, name: appended child, type: suspense}])',
2163 ]);
2164
2165 await act(async () => {
2166 resolveText('Child');
2167 ReactNoop.expire(1000);
2168 await advanceTimers(1000);
2169 });
2170
2171 assertLog([
2172 'Child',
2173 'onMarkerProgress(transition one, parent, 0, 4000, [])',
2174 'onMarkerComplete(transition one, parent, 0, 4000)',
2175 'onTransitionProgress(transition one, 0, 4000, [])',
2176 'onTransitionComplete(transition one, 0, 4000)',
2177 ]);
2178 });
2179
2180 // @gate enableTransitionTracing
2181 it('warns when marker name changes', async () => {
2182 const transitionCallbacks = {
2183 onTransitionStart: (name, startTime) => {
2184 Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
2185 },
2186 onTransitionComplete: (name, startTime, endTime) => {
2187 Scheduler.log(
2188 `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
2189 );
2190 },
2191 onMarkerIncomplete: (
2192 transitionName,
2193 markerName,
2194 startTime,
2195 deletions,
2196 ) => {
2197 Scheduler.log(
2198 `onMarkerIncomplete(${transitionName}, ${markerName}, ${startTime}, [${stringifyDeletions(
2199 deletions,
2200 )}])`,
2201 );
2202 },
2203 onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
2204 Scheduler.log(
2205 `onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
2206 );
2207 },
2208 };
2209 function App({markerName, markerKey}) {
2210 return (
2211 <React.unstable_TracingMarker name={markerName} key={markerKey}>
2212 <Text text={markerName} />
2213 </React.unstable_TracingMarker>
2214 );
2215 }
2216
2217 const root = ReactNoop.createRoot({
2218 unstable_transitionCallbacks: transitionCallbacks,
2219 });
2220 await act(async () => {
2221 startTransition(
2222 () => root.render(<App markerName="one" markerKey="key" />),
2223 {
2224 name: 'transition one',
2225 },
2226 );
2227 ReactNoop.expire(1000);
2228 await advanceTimers(1000);
2229 await waitForAll([
2230 'one',
2231 'onTransitionStart(transition one, 0)',
2232 'onMarkerComplete(transition one, one, 0, 1000)',
2233 'onTransitionComplete(transition one, 0, 1000)',
2234 ]);
2235 startTransition(
2236 () => root.render(<App markerName="two" markerKey="key" />),
2237 {
2238 name: 'transition two',
2239 },
2240 );
2241 ReactNoop.expire(1000);
2242 await advanceTimers(1000);
2243 // onMarkerComplete shouldn't be called for transitions with
2244 // new keys
2245 await waitForAll([
2246 'two',
2247 'onTransitionStart(transition two, 1000)',
2248 'onTransitionComplete(transition two, 1000, 2000)',
2249 ]);
2250 assertConsoleErrorDev([
2251 'Changing the name of a tracing marker after mount is not supported. ' +
2252 'To remount the tracing marker, pass it a new key.\n' +
2253 ' in App (at **)',
2254 ]);
2255 startTransition(
2256 () => root.render(<App markerName="three" markerKey="new key" />),
2257 {
2258 name: 'transition three',
2259 },
2260 );
2261 ReactNoop.expire(1000);
2262 await advanceTimers(1000);
2263 // This should not warn and onMarkerComplete should be called
2264 await waitForAll([
2265 'three',
2266 'onTransitionStart(transition three, 2000)',
2267 'onMarkerComplete(transition three, three, 2000, 3000)',
2268 'onTransitionComplete(transition three, 2000, 3000)',
2269 ]);
2270 });
2271 });
2272
2273 // @gate enableTransitionTracing
2274 it('offscreen trees should not stop transition from completing', async () => {
2275 const transitionCallbacks = {
2276 onTransitionStart: (name, startTime) => {
2277 Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
2278 },
2279 onTransitionComplete: (name, startTime, endTime) => {
2280 Scheduler.log(
2281 `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
2282 );
2283 },
2284 onMarkerComplete: (transitioName, markerName, startTime, endTime) => {
2285 Scheduler.log(
2286 `onMarkerComplete(${transitioName}, ${markerName}, ${startTime}, ${endTime})`,
2287 );
2288 },
2289 };
2290
2291 function App() {
2292 return (
2293 <React.unstable_TracingMarker name="marker">
2294 <Suspense fallback={<Text text="Loading..." />}>
2295 <AsyncText text="Text" />
2296 </Suspense>
2297 <Activity mode="hidden">
2298 <Suspense fallback={<Text text="Hidden Loading..." />}>
2299 <AsyncText text="Hidden Text" />
2300 </Suspense>
2301 </Activity>
2302 </React.unstable_TracingMarker>
2303 );
2304 }
2305
2306 const root = ReactNoop.createRoot({
2307 unstable_transitionCallbacks: transitionCallbacks,
2308 });
2309 await act(() => {
2310 startTransition(() => root.render(<App />), {name: 'transition'});
2311 ReactNoop.expire(1000);
2312 advanceTimers(1000);
2313 });
2314 assertLog([
2315 'Suspend [Text]',
2316 'Loading...',
2317 // pre-warming
2318 'Suspend [Text]',
2319 'onTransitionStart(transition, 0)',
2320 'Suspend [Hidden Text]',
2321 'Hidden Loading...',
2322 ]);
2323
2324 await act(() => {
2325 resolveText('Text');
2326 ReactNoop.expire(1000);
2327 advanceTimers(1000);
2328 });
2329 assertLog([
2330 'Text',
2331 'onMarkerComplete(transition, marker, 0, 2000)',
2332 'onTransitionComplete(transition, 0, 2000)',
2333 ]);
2334
2335 await act(() => {
2336 resolveText('Hidden Text');
2337 ReactNoop.expire(1000);
2338 advanceTimers(1000);
2339 });
2340 assertLog(['Hidden Text']);
2341 });
2342
2343 // @gate enableTransitionTracing
2344 it('discrete events', async () => {
2345 const transitionCallbacks = {
2346 onTransitionStart: (name, startTime) => {
2347 Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
2348 },
2349 onTransitionProgress: (name, startTime, endTime, pending) => {
2350 const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
2351 Scheduler.log(
2352 `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
2353 );
2354 },
2355 onTransitionComplete: (name, startTime, endTime) => {
2356 Scheduler.log(
2357 `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
2358 );
2359 },
2360 };
2361
2362 function App() {
2363 return (
2364 <Suspense fallback={<Text text="Loading..." />} name="suspense page">
2365 <AsyncText text="Page Two" />
2366 </Suspense>
2367 );
2368 }
2369
2370 const root = ReactNoop.createRoot({
2371 unstable_transitionCallbacks: transitionCallbacks,
2372 });
2373
2374 await act(async () => {
2375 ReactNoop.discreteUpdates(() =>
2376 startTransition(() => root.render(<App />), {name: 'page transition'}),
2377 );
2378 ReactNoop.expire(1000);
2379 await advanceTimers(1000);
2380 });
2381
2382 assertLog([
2383 'Suspend [Page Two]',
2384 'Loading...',
2385 // pre-warming
2386 'Suspend [Page Two]',
2387 // end pre-warming
2388 'onTransitionStart(page transition, 0)',
2389 'onTransitionProgress(page transition, 0, 1000, [suspense page])',
2390 ]);
2391 await act(async () => {
2392 ReactNoop.discreteUpdates(() => resolveText('Page Two'));
2393 ReactNoop.expire(1000);
2394 await advanceTimers(1000);
2395 });
2396
2397 assertLog([
2398 'Page Two',
2399 'onTransitionProgress(page transition, 0, 2000, [])',
2400 'onTransitionComplete(page transition, 0, 2000)',
2401 ]);
2402 });
2403
2404 // @gate enableTransitionTracing
2405 it('multiple commits happen before a paint', async () => {
2406 const transitionCallbacks = {
2407 onTransitionStart: (name, startTime) => {
2408 Scheduler.log(`onTransitionStart(${name}, ${startTime})`);
2409 },
2410 onTransitionProgress: (name, startTime, endTime, pending) => {
2411 const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
2412 Scheduler.log(
2413 `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}])`,
2414 );
2415 },
2416 onTransitionComplete: (name, startTime, endTime) => {
2417 Scheduler.log(
2418 `onTransitionComplete(${name}, ${startTime}, ${endTime})`,
2419 );
2420 },
2421 };
2422
2423 function App() {
2424 const [, setRerender] = useState(false);
2425 React.useLayoutEffect(() => {
2426 resolveText('Text');
2427 setRerender(true);
2428 });
2429 return (
2430 <>
2431 <Suspense name="one" fallback={<Text text="Loading..." />}>
2432 <AsyncText text="Text" />
2433 </Suspense>
2434 <Suspense name="two" fallback={<Text text="Loading Two..." />}>
2435 <AsyncText text="Text Two" />
2436 </Suspense>
2437 </>
2438 );
2439 }
2440
2441 const root = ReactNoop.createRoot({
2442 unstable_transitionCallbacks: transitionCallbacks,
2443 });
2444
2445 await act(() => {
2446 startTransition(() => root.render(<App />), {name: 'transition'});
2447 ReactNoop.expire(1000);
2448 advanceTimers(1000);
2449 });
2450
2451 assertLog([
2452 'Suspend [Text]',
2453 'Loading...',
2454 'Suspend [Text Two]',
2455 'Loading Two...',
2456 'Text',
2457 'Suspend [Text Two]',
2458 'Loading Two...',
2459 // pre-warming
2460 'Suspend [Text Two]',
2461 // end pre-warming
2462 'onTransitionStart(transition, 0)',
2463 'onTransitionProgress(transition, 0, 1000, [two])',
2464 // pre-warming
2465 'Suspend [Text Two]',
2466 ]);
2467
2468 await act(() => {
2469 resolveText('Text Two');
2470 ReactNoop.expire(1000);
2471 advanceTimers(1000);
2472 });
2473 assertLog([
2474 'Text Two',
2475 'onTransitionProgress(transition, 0, 2000, [])',
2476 'onTransitionComplete(transition, 0, 2000)',
2477 ]);
2478 });
2479
2480 // @gate enableTransitionTracing
2481 it('transition callbacks work for multiple roots', async () => {
2482 const getTransitionCallbacks = transitionName => {
2483 return {
2484 onTransitionStart: (name, startTime) => {
2485 Scheduler.log(
2486 `onTransitionStart(${name}, ${startTime}) /${transitionName}/`,
2487 );
2488 },
2489 onTransitionProgress: (name, startTime, endTime, pending) => {
2490 const suspenseNames = pending.map(p => p.name || '<null>').join(', ');
2491 Scheduler.log(
2492 `onTransitionProgress(${name}, ${startTime}, ${endTime}, [${suspenseNames}]) /${transitionName}/`,
2493 );
2494 },
2495 onTransitionComplete: (name, startTime, endTime) => {
2496 Scheduler.log(
2497 `onTransitionComplete(${name}, ${startTime}, ${endTime}) /${transitionName}/`,
2498 );
2499 },
2500 };
2501 };
2502
2503 function App({name}) {
2504 return (
2505 <>
2506 <Suspense name={name} fallback={<Text text={`Loading ${name}...`} />}>
2507 <AsyncText text={`Text ${name}`} />
2508 </Suspense>
2509 </>
2510 );
2511 }
2512
2513 const rootOne = ReactNoop.createRoot({
2514 unstable_transitionCallbacks: getTransitionCallbacks('root one'),
2515 });
2516
2517 const rootTwo = ReactNoop.createRoot({
2518 unstable_transitionCallbacks: getTransitionCallbacks('root two'),
2519 });
2520
2521 await act(() => {
2522 startTransition(() => rootOne.render(<App name="one" />), {
2523 name: 'transition one',
2524 });
2525 startTransition(() => rootTwo.render(<App name="two" />), {
2526 name: 'transition two',
2527 });
2528 ReactNoop.expire(1000);
2529 advanceTimers(1000);
2530 });
2531
2532 assertLog([
2533 'Suspend [Text one]',
2534 'Loading one...',
2535 'Suspend [Text two]',
2536 'Loading two...',
2537 // pre-warming
2538 'Suspend [Text one]',
2539 'Suspend [Text two]',
2540 // end pre-warming
2541 'onTransitionStart(transition one, 0) /root one/',
2542 'onTransitionProgress(transition one, 0, 1000, [one]) /root one/',
2543 'onTransitionStart(transition two, 0) /root two/',
2544 'onTransitionProgress(transition two, 0, 1000, [two]) /root two/',
2545 ]);
2546
2547 await act(() => {
2548 caches[0].resolve('Text one');
2549 ReactNoop.expire(1000);
2550 advanceTimers(1000);
2551 });
2552
2553 assertLog([
2554 'Text one',
2555 'onTransitionProgress(transition one, 0, 2000, []) /root one/',
2556 'onTransitionComplete(transition one, 0, 2000) /root one/',
2557 ]);
2558
2559 await act(() => {
2560 resolveText('Text two');
2561 ReactNoop.expire(1000);
2562 advanceTimers(1000);
2563 });
2564
2565 assertLog([
2566 'Text two',
2567 'onTransitionProgress(transition two, 0, 3000, []) /root two/',
2568 'onTransitionComplete(transition two, 0, 3000) /root two/',
2569 ]);
2570 });
2571 });