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