1
+/**
2
+ * Copyright (c) Facebook, Inc. and its 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 startTransition;
15
+let useDeferredValue;
16
+let useMemo;
17
+let useState;
18
+
19
+describe('ReactDeferredValue', () => {
20
+ beforeEach(() => {
21
+ jest.resetModules();
22
+
23
+ React = require('react');
24
+ ReactNoop = require('react-noop-renderer');
25
+ Scheduler = require('scheduler');
26
+ act = require('jest-react').act;
27
+ startTransition = React.startTransition;
28
+ useDeferredValue = React.useDeferredValue;
29
+ useMemo = React.useMemo;
30
+ useState = React.useState;
31
+ });
32
+
33
+ function Text({text}) {
34
+ Scheduler.unstable_yieldValue(text);
35
+ return text;
36
+ }
37
+
38
+ it('does not cause an infinite defer loop if the original value isn\t memoized', async () => {
39
+ function App({value}) {
40
+ // The object passed to useDeferredValue is never the same as the previous
41
+ // render. A naive implementation would endlessly spawn deferred renders.
42
+ const {value: deferredValue} = useDeferredValue({value});
43
+
44
+ const child = useMemo(() => <Text text={'Original: ' + value} />, [
45
+ value,
46
+ ]);
47
+
48
+ const deferredChild = useMemo(
49
+ () => <Text text={'Deferred: ' + deferredValue} />,
50
+ [deferredValue],
51
+ );
52
+
53
+ return (
54
+ <div>
55
+ <div>{child}</div>
56
+ <div>{deferredChild}</div>
57
+ </div>
58
+ );
59
+ }
60
+
61
+ const root = ReactNoop.createRoot();
62
+
63
+ // Initial render
64
+ await act(async () => {
65
+ root.render(<App value={1} />);
66
+ });
67
+ expect(Scheduler).toHaveYielded(['Original: 1', 'Deferred: 1']);
68
+
69
+ // If it's an urgent update, the value is deferred
70
+ await act(async () => {
71
+ root.render(<App value={2} />);
72
+
73
+ expect(Scheduler).toFlushUntilNextPaint(['Original: 2']);
74
+ // The deferred value updates in a separate render
75
+ expect(Scheduler).toFlushUntilNextPaint(['Deferred: 2']);
76
+ });
77
+ expect(root).toMatchRenderedOutput(
78
+ <div>
79
+ <div>Original: 2</div>
80
+ <div>Deferred: 2</div>
81
+ </div>,
82
+ );
83
+
84
+ // But if it updates during a transition, it doesn't defer
85
+ await act(async () => {
86
+ startTransition(() => {
87
+ root.render(<App value={3} />);
88
+ });
89
+ // The deferred value updates in the same render as the original
90
+ expect(Scheduler).toFlushUntilNextPaint(['Original: 3', 'Deferred: 3']);
91
+ });
92
+ expect(root).toMatchRenderedOutput(
93
+ <div>
94
+ <div>Original: 3</div>
95
+ <div>Deferred: 3</div>
96
+ </div>,
97
+ );
98
+ });
99
+
100
+ it('does not defer during a transition', async () => {
101
+ function App({value}) {
102
+ const deferredValue = useDeferredValue(value);
103
+
104
+ const child = useMemo(() => <Text text={'Original: ' + value} />, [
105
+ value,
106
+ ]);
107
+
108
+ const deferredChild = useMemo(
109
+ () => <Text text={'Deferred: ' + deferredValue} />,
110
+ [deferredValue],
111
+ );
112
+
113
+ return (
114
+ <div>
115
+ <div>{child}</div>
116
+ <div>{deferredChild}</div>
117
+ </div>
118
+ );
119
+ }
120
+
121
+ const root = ReactNoop.createRoot();
122
+
123
+ // Initial render
124
+ await act(async () => {
125
+ root.render(<App value={1} />);
126
+ });
127
+ expect(Scheduler).toHaveYielded(['Original: 1', 'Deferred: 1']);
128
+
129
+ // If it's an urgent update, the value is deferred
130
+ await act(async () => {
131
+ root.render(<App value={2} />);
132
+
133
+ expect(Scheduler).toFlushUntilNextPaint(['Original: 2']);
134
+ // The deferred value updates in a separate render
135
+ expect(Scheduler).toFlushUntilNextPaint(['Deferred: 2']);
136
+ });
137
+ expect(root).toMatchRenderedOutput(
138
+ <div>
139
+ <div>Original: 2</div>
140
+ <div>Deferred: 2</div>
141
+ </div>,
142
+ );
143
+
144
+ // But if it updates during a transition, it doesn't defer
145
+ await act(async () => {
146
+ startTransition(() => {
147
+ root.render(<App value={3} />);
148
+ });
149
+ // The deferred value updates in the same render as the original
150
+ expect(Scheduler).toFlushUntilNextPaint(['Original: 3', 'Deferred: 3']);
151
+ });
152
+ expect(root).toMatchRenderedOutput(
153
+ <div>
154
+ <div>Original: 3</div>
155
+ <div>Deferred: 3</div>
156
+ </div>,
157
+ );
158
+ });
159
+
160
+ it("works if there's a render phase update", async () => {
161
+ function App({value: propValue}) {
162
+ const [value, setValue] = useState(null);
163
+ if (value !== propValue) {
164
+ setValue(propValue);
165
+ }
166
+
167
+ const deferredValue = useDeferredValue(value);
168
+
169
+ const child = useMemo(() => <Text text={'Original: ' + value} />, [
170
+ value,
171
+ ]);
172
+
173
+ const deferredChild = useMemo(
174
+ () => <Text text={'Deferred: ' + deferredValue} />,
175
+ [deferredValue],
176
+ );
177
+
178
+ return (
179
+ <div>
180
+ <div>{child}</div>
181
+ <div>{deferredChild}</div>
182
+ </div>
183
+ );
184
+ }
185
+
186
+ const root = ReactNoop.createRoot();
187
+
188
+ // Initial render
189
+ await act(async () => {
190
+ root.render(<App value={1} />);
191
+ });
192
+ expect(Scheduler).toHaveYielded(['Original: 1', 'Deferred: 1']);
193
+
194
+ // If it's an urgent update, the value is deferred
195
+ await act(async () => {
196
+ root.render(<App value={2} />);
197
+
198
+ expect(Scheduler).toFlushUntilNextPaint(['Original: 2']);
199
+ // The deferred value updates in a separate render
200
+ expect(Scheduler).toFlushUntilNextPaint(['Deferred: 2']);
201
+ });
202
+ expect(root).toMatchRenderedOutput(
203
+ <div>
204
+ <div>Original: 2</div>
205
+ <div>Deferred: 2</div>
206
+ </div>,
207
+ );
208
+
209
+ // But if it updates during a transition, it doesn't defer
210
+ await act(async () => {
211
+ startTransition(() => {
212
+ root.render(<App value={3} />);
213
+ });
214
+ // The deferred value updates in the same render as the original
215
+ expect(Scheduler).toFlushUntilNextPaint(['Original: 3', 'Deferred: 3']);
216
+ });
217
+ expect(root).toMatchRenderedOutput(
218
+ <div>
219
+ <div>Original: 3</div>
220
+ <div>Deferred: 3</div>
221
+ </div>,
222
+ );
223
+ });
224
+});