main
js 161 lines 4.06 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
8 'use strict';
9
10 let React;
11 let ReactNoop;
12 let Scheduler;
13 let act;
14 let assertLog;
15 let use;
16 let Suspense;
17 let startTransition;
18
19 describe('conditional use warning', () => {
20 beforeEach(() => {
21 jest.resetModules();
22
23 React = require('react');
24 ReactNoop = require('react-noop-renderer');
25 Scheduler = require('scheduler');
26 act = require('internal-test-utils').act;
27 assertLog = require('internal-test-utils').assertLog;
28 use = React.use;
29 Suspense = React.Suspense;
30 startTransition = React.startTransition;
31 });
32
33 // @gate __DEV__ && enableConditionalUseWarning
34 it('warns if use(promise) is called conditionally based on a cache', async () => {
35 let cachedValue;
36 let resolve;
37 const promise = new Promise(r => {
38 resolve = value => {
39 cachedValue = value;
40 r(value);
41 };
42 });
43
44 function Text({text}) {
45 Scheduler.log(text);
46 return text;
47 }
48
49 function Async() {
50 if (cachedValue !== undefined) {
51 return <Text text={cachedValue} />;
52 }
53 return <Text text={use(promise)} />;
54 }
55
56 const root = ReactNoop.createRoot();
57 await act(() => {
58 root.render(
59 <Suspense fallback={<Text text="Loading..." />}>
60 <Text text="Initial" />
61 </Suspense>,
62 );
63 });
64 assertLog(['Initial']);
65 expect(root).toMatchRenderedOutput('Initial');
66
67 spyOnDev(console, 'error').mockImplementation(() => {});
68 try {
69 await act(() => {
70 startTransition(() => {
71 root.render(
72 <Suspense fallback={<Text text="Loading..." />}>
73 <Async />
74 </Suspense>,
75 );
76 });
77 });
78 assertLog(['Loading...']);
79 expect(root).toMatchRenderedOutput('Initial');
80
81 await act(() => resolve('Async'));
82 assertLog(['Async']);
83 expect(root).toMatchRenderedOutput('Async');
84
85 expect(console.error).toHaveBeenCalledTimes(1);
86 const warning = console.error.mock.calls[0][0];
87 expect(warning).toBeInstanceOf(Error);
88 expect(warning.message).toBe(
89 'This library called use() to suspend in a previous render but ' +
90 'did not call use() when it finished. This indicates an incorrect use of use(). ' +
91 'Learn more: https://react.dev/warnings/conditional-use-of-use',
92 );
93
94 await act(() => {
95 root.render(
96 <Suspense fallback={<Text text="Loading..." />}>
97 <Async />
98 </Suspense>,
99 );
100 });
101 assertLog(['Async']);
102 expect(root).toMatchRenderedOutput('Async');
103 expect(console.error).toHaveBeenCalledTimes(1);
104 } finally {
105 if (__DEV__) {
106 console.error.mockRestore();
107 }
108 }
109 });
110
111 it('does not warn if use(promise) is called unconditionally', async () => {
112 let resolve;
113 const promise = new Promise(r => {
114 resolve = r;
115 });
116
117 function Text({text}) {
118 Scheduler.log(text);
119 return text;
120 }
121
122 function Async() {
123 return <Text text={use(promise)} />;
124 }
125
126 const root = ReactNoop.createRoot();
127 spyOnDev(console, 'error').mockImplementation(() => {});
128 try {
129 await act(() => {
130 root.render(
131 <Suspense fallback={<Text text="Loading..." />}>
132 <Async />
133 </Suspense>,
134 );
135 });
136 assertLog(['Loading...']);
137 expect(root).toMatchRenderedOutput('Loading...');
138
139 await act(() => resolve('Async'));
140 assertLog(['Async']);
141 expect(root).toMatchRenderedOutput('Async');
142
143 await act(() => {
144 root.render(
145 <Suspense fallback={<Text text="Loading..." />}>
146 <Async />
147 </Suspense>,
148 );
149 });
150 assertLog(['Async']);
151 expect(root).toMatchRenderedOutput('Async');
152 if (__DEV__) {
153 expect(console.error).not.toHaveBeenCalled();
154 }
155 } finally {
156 if (__DEV__) {
157 console.error.mockRestore();
158 }
159 }
160 });
161 });