Remove ReactNoop.flushDeferredPri and flushUnitsOfWork (#14934)
* Remove ReactNoop.flushDeferredPri and flushUnitsOfWork Some of our older tests worked by counting how many times React checked whether it should yield to the main thread, instead of something publicly observable like how many times a component is rendered. Our newer tests have converged on a style where we push into a log and make assertions on the log. This pattern is less coupled to the implementation while still being sufficient to test performance optimizations, like resuming (whenever we add that back). This commit removes flushDeferredPri and flushUnitsOfWork and upgrades the affected tests. * Remove shouldYieldToRenderer indirection This wrapper is no longer necessary.
Andrew Clark committed
Feb 22, 2019 at 17:27 UTC
ba708fa79b3db6481b7886f9fdb6bb776d0c2fb9
13 files changed
+309
-386
packages/react-noop-renderer/src/createReactNoop.js
+18
-21
@@ -747,36 +747,27 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
747
return NoopRenderer.findHostInstance(component);
748
},
749
750
- flushDeferredPri(timeout: number = Infinity): Array<mixed> {
751
- // The legacy version of this function decremented the timeout before
752
- // returning the new time.
753
- // TODO: Convert tests to use flushUnitsOfWork or flushAndYield instead.
754
- const n = timeout / 5 - 1;
755
-
756
- let values = [];
750
+ flush(): Array<mixed> {
751
+ let values = yieldedValues || [];
752
+ yieldedValues = null;
753
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
758
- for (const value of flushUnitsOfWork(n)) {
754
+ for (const value of flushUnitsOfWork(Infinity)) {
755
values.push(...value);
756
}
757
return values;
758
},
759
764
- flush(): Array<mixed> {
765
- return ReactNoop.flushUnitsOfWork(Infinity);
766
- },
767
-
768
- flushAndYield(
769
- unitsOfWork: number = Infinity,
770
- ): Generator<Array<mixed>, void, void> {
771
- return flushUnitsOfWork(unitsOfWork);
772
- },
773
-
774
- flushUnitsOfWork(n: number): Array<mixed> {
760
+ // TODO: Should only be used via a Jest plugin (like we do with the
761
+ // test renderer).
762
+ unstable_flushNumberOfYields(n: number): Array<mixed> {
763
let values = yieldedValues || [];
764
yieldedValues = null;
765
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
778
- for (const value of flushUnitsOfWork(n)) {
766
+ for (const value of flushUnitsOfWork(Infinity)) {
767
values.push(...value);
768
+ if (values.length >= n) {
769
+ break;
770
+ }
771
}
772
return values;
773
},
@@ -847,7 +838,13 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
838
},
839
840
flushExpired(): Array<mixed> {
850
- return ReactNoop.flushUnitsOfWork(0);
841
+ let values = yieldedValues || [];
842
+ yieldedValues = null;
843
+ // eslint-disable-next-line no-for-of-loops/no-for-of-loops
844
+ for (const value of flushUnitsOfWork(0)) {
845
+ values.push(...value);
846
+ }
847
+ return values;
848
},
849
850
yield(value: mixed) {
packages/react-reconciler/src/ReactFiberScheduler.js
+19
-38
@@ -1214,7 +1214,7 @@ function workLoop(isYieldy) {
1214
}
1215
} else {
1216
// Flush asynchronous work until there's a higher priority event
1217
- while (nextUnitOfWork !== null && !shouldYieldToRenderer()) {
1217
+ while (nextUnitOfWork !== null && !shouldYield()) {
1218
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
1219
}
1220
}
@@ -2007,7 +2007,7 @@ function onSuspend(
2007
msUntilTimeout: number,
2008
): void {
2009
root.expirationTime = rootExpirationTime;
2010
- if (msUntilTimeout === 0 && !shouldYieldToRenderer()) {
2010
+ if (msUntilTimeout === 0 && !shouldYield()) {
2011
// Don't wait an additional tick. Commit the tree immediately.
2012
root.pendingCommitExpirationTime = suspendedExpirationTime;
2013
root.finishedWork = finishedWork;
@@ -2204,43 +2204,24 @@ function findHighestPriorityRoot() {
2204
nextFlushedExpirationTime = highestPriorityWork;
2205
}
2206
2207
-// TODO: This wrapper exists because many of the older tests (the ones that use
2208
-// flushDeferredPri) rely on the number of times `shouldYield` is called. We
2209
-// should get rid of it.
2210
-let didYield: boolean = false;
2211
-function shouldYieldToRenderer() {
2212
- if (didYield) {
2213
- return true;
2214
- }
2215
- if (shouldYield()) {
2216
- didYield = true;
2217
- return true;
2218
- }
2219
- return false;
2220
-}
2221
-
2207
function performAsyncWork(didTimeout) {
2223
- try {
2224
- if (didTimeout) {
2225
- // The callback timed out. That means at least one update has expired.
2226
- // Iterate through the root schedule. If they contain expired work, set
2227
- // the next render expiration time to the current time. This has the effect
2228
- // of flushing all expired work in a single batch, instead of flushing each
2229
- // level one at a time.
2230
- if (firstScheduledRoot !== null) {
2231
- recomputeCurrentRendererTime();
2232
- let root: FiberRoot = firstScheduledRoot;
2233
- do {
2234
- didExpireAtExpirationTime(root, currentRendererTime);
2235
- // The root schedule is circular, so this is never null.
2236
- root = (root.nextScheduledRoot: any);
2237
- } while (root !== firstScheduledRoot);
2238
- }
2208
+ if (didTimeout) {
2209
+ // The callback timed out. That means at least one update has expired.
2210
+ // Iterate through the root schedule. If they contain expired work, set
2211
+ // the next render expiration time to the current time. This has the effect
2212
+ // of flushing all expired work in a single batch, instead of flushing each
2213
+ // level one at a time.
2214
+ if (firstScheduledRoot !== null) {
2215
+ recomputeCurrentRendererTime();
2216
+ let root: FiberRoot = firstScheduledRoot;
2217
+ do {
2218
+ didExpireAtExpirationTime(root, currentRendererTime);
2219
+ // The root schedule is circular, so this is never null.
2220
+ root = (root.nextScheduledRoot: any);
2221
+ } while (root !== firstScheduledRoot);
2222
}
2240
- performWork(NoWork, true);
2241
- } finally {
2242
- didYield = false;
2223
}
2224
+ performWork(NoWork, true);
2225
}
2226
2227
function performSyncWork() {
@@ -2266,7 +2247,7 @@ function performWork(minExpirationTime: ExpirationTime, isYieldy: boolean) {
2247
nextFlushedRoot !== null &&
2248
nextFlushedExpirationTime !== NoWork &&
2249
minExpirationTime <= nextFlushedExpirationTime &&
2269
- !(didYield && currentRendererTime > nextFlushedExpirationTime)
2250
+ !(shouldYield() && currentRendererTime > nextFlushedExpirationTime)
2251
) {
2252
performWorkOnRoot(
2253
nextFlushedRoot,
@@ -2414,7 +2395,7 @@ function performWorkOnRoot(
2395
if (finishedWork !== null) {
2396
// We've completed the root. Check the if we should yield one more time
2397
// before committing.
2417
- if (!shouldYieldToRenderer()) {
2398
+ if (!shouldYield()) {
2399
// Still time left. Commit the root.
2400
completeRoot(root, finishedWork, expirationTime);
2401
} else {
packages/react-reconciler/src/__tests__/ReactIncremental-test.internal.js
+72
-119
@@ -39,10 +39,8 @@ describe('ReactIncremental', () => {
39
});
40
41
it('should render a simple component, in steps if needed', () => {
42
- let renderCallbackCalled = false;
43
- let barCalled = false;
42
function Bar() {
45
- barCalled = true;
43
+ ReactNoop.yield('Bar');
44
return (
45
<span>
46
<div>Hello World</div>
@@ -50,26 +48,17 @@ describe('ReactIncremental', () => {
48
);
49
}
50
53
- let fooCalled = false;
51
function Foo() {
55
- fooCalled = true;
52
+ ReactNoop.yield('Foo');
53
return [<Bar key="a" isBar={true} />, <Bar key="b" isBar={true} />];
54
}
55
59
- ReactNoop.render(<Foo />, () => (renderCallbackCalled = true));
60
- expect(fooCalled).toBe(false);
61
- expect(barCalled).toBe(false);
62
- expect(renderCallbackCalled).toBe(false);
56
+ ReactNoop.render(<Foo />, () => ReactNoop.yield('callback'));
57
// Do one step of work.
64
- ReactNoop.flushDeferredPri(7 + 5);
65
- expect(fooCalled).toBe(true);
66
- expect(barCalled).toBe(false);
67
- expect(renderCallbackCalled).toBe(false);
58
+ expect(ReactNoop.flushNextYield()).toEqual(['Foo']);
59
+
60
// Do the rest of the work.
69
- ReactNoop.flushDeferredPri(50);
70
- expect(fooCalled).toBe(true);
71
- expect(barCalled).toBe(true);
72
- expect(renderCallbackCalled).toBe(true);
61
+ expect(ReactNoop.flush()).toEqual(['Bar', 'Bar', 'callback']);
62
});
63
64
it('updates a previous render', () => {
@@ -140,15 +129,13 @@ describe('ReactIncremental', () => {
129
});
130
131
it('can cancel partially rendered work and restart', () => {
143
- let ops = [];
144
-
132
function Bar(props) {
146
- ops.push('Bar');
133
+ ReactNoop.yield('Bar');
134
return <div>{props.children}</div>;
135
}
136
137
function Foo(props) {
151
- ops.push('Foo');
138
+ ReactNoop.yield('Foo');
139
return (
140
<div>
141
<Bar>{props.text}</Bar>
@@ -161,34 +148,22 @@ describe('ReactIncremental', () => {
148
ReactNoop.render(<Foo text="foo" />);
149
ReactNoop.flush();
150
164
- ops = [];
165
-
151
ReactNoop.render(<Foo text="bar" />);
152
// Flush part of the work
168
- ReactNoop.flushDeferredPri(20 + 5);
169
-
170
- expect(ops).toEqual(['Foo', 'Bar']);
171
-
172
- ops = [];
153
+ ReactNoop.flushThrough(['Foo', 'Bar']);
154
155
// This will abort the previous work and restart
156
ReactNoop.flushSync(() => ReactNoop.render(null));
157
ReactNoop.render(<Foo text="baz" />);
177
- ReactNoop.clearYields();
158
159
// Flush part of the new work
180
- ReactNoop.flushDeferredPri(20 + 5);
181
-
182
- expect(ops).toEqual(['Foo', 'Bar']);
160
+ ReactNoop.flushThrough(['Foo', 'Bar']);
161
162
// Flush the rest of the work which now includes the low priority
185
- ReactNoop.flush(20);
186
-
187
- expect(ops).toEqual(['Foo', 'Bar', 'Bar']);
163
+ expect(ReactNoop.flush()).toEqual(['Bar']);
164
});
165
166
it('should call callbacks even if updates are aborted', () => {
191
- const ops = [];
167
let inst;
168
169
class Foo extends React.Component {
@@ -215,32 +190,27 @@ describe('ReactIncremental', () => {
190
191
inst.setState(
192
() => {
218
- ops.push('setState1');
193
+ ReactNoop.yield('setState1');
194
return {text: 'bar'};
195
},
221
- () => ops.push('callback1'),
196
+ () => ReactNoop.yield('callback1'),
197
);
198
199
// Flush part of the work
225
- ReactNoop.flushDeferredPri(20 + 5);
226
-
227
- expect(ops).toEqual(['setState1']);
200
+ ReactNoop.flushThrough(['setState1']);
201
202
// This will abort the previous work and restart
203
ReactNoop.flushSync(() => ReactNoop.render(<Foo />));
204
inst.setState(
205
() => {
233
- ops.push('setState2');
206
+ ReactNoop.yield('setState2');
207
return {text2: 'baz'};
208
},
236
- () => ops.push('callback2'),
209
+ () => ReactNoop.yield('callback2'),
210
);
211
212
// Flush the rest of the work which now includes the low priority
240
- ReactNoop.flush();
241
-
242
- expect(ops).toEqual([
243
- 'setState1',
213
+ expect(ReactNoop.flush()).toEqual([
214
'setState1',
215
'setState2',
216
'callback1',
@@ -250,20 +220,18 @@ describe('ReactIncremental', () => {
220
});
221
222
it('can deprioritize unfinished work and resume it later', () => {
253
- let ops = [];
254
-
223
function Bar(props) {
256
- ops.push('Bar');
224
+ ReactNoop.yield('Bar');
225
return <div>{props.children}</div>;
226
}
227
228
function Middle(props) {
261
- ops.push('Middle');
229
+ ReactNoop.yield('Middle');
230
return <span>{props.children}</span>;
231
}
232
233
function Foo(props) {
266
- ops.push('Foo');
234
+ ReactNoop.yield('Foo');
235
return (
236
<div>
237
<Bar>{props.text}</Bar>
@@ -280,25 +248,20 @@ describe('ReactIncremental', () => {
248
249
// Init
250
ReactNoop.render(<Foo text="foo" />);
283
- ReactNoop.flush();
284
-
285
- expect(ops).toEqual(['Foo', 'Bar', 'Bar', 'Middle', 'Middle']);
286
-
287
- ops = [];
251
+ expect(ReactNoop.flush()).toEqual([
252
+ 'Foo',
253
+ 'Bar',
254
+ 'Bar',
255
+ 'Middle',
256
+ 'Middle',
257
+ ]);
258
259
// Render part of the work. This should be enough to flush everything except
260
// the middle which has lower priority.
261
ReactNoop.render(<Foo text="bar" />);
292
- ReactNoop.flushDeferredPri(40);
293
-
294
- expect(ops).toEqual(['Foo', 'Bar', 'Bar']);
295
-
296
- ops = [];
297
-
262
+ ReactNoop.flushThrough(['Foo', 'Bar', 'Bar']);
263
// Flush only the remaining work
299
- ReactNoop.flush();
300
-
301
- expect(ops).toEqual(['Middle', 'Middle']);
264
+ expect(ReactNoop.flush()).toEqual(['Middle', 'Middle']);
265
});
266
267
it('can deprioritize a tree from without dropping work', () => {
@@ -1844,8 +1807,6 @@ describe('ReactIncremental', () => {
1807
});
1808
1809
it('merges and masks context', () => {
1847
- const ops = [];
1848
-
1810
class Intl extends React.Component {
1811
static childContextTypes = {
1812
locale: PropTypes.string,
@@ -1856,7 +1817,7 @@ describe('ReactIncremental', () => {
1817
};
1818
}
1819
render() {
1859
- ops.push('Intl ' + JSON.stringify(this.context));
1820
+ ReactNoop.yield('Intl ' + JSON.stringify(this.context));
1821
return this.props.children;
1822
}
1823
}
@@ -1871,7 +1832,7 @@ describe('ReactIncremental', () => {
1832
};
1833
}
1834
render() {
1874
- ops.push('Router ' + JSON.stringify(this.context));
1835
+ ReactNoop.yield('Router ' + JSON.stringify(this.context));
1836
return this.props.children;
1837
}
1838
}
@@ -1881,7 +1842,7 @@ describe('ReactIncremental', () => {
1842
locale: PropTypes.string,
1843
};
1844
render() {
1884
- ops.push('ShowLocale ' + JSON.stringify(this.context));
1845
+ ReactNoop.yield('ShowLocale ' + JSON.stringify(this.context));
1846
return this.context.locale;
1847
}
1848
}
@@ -1891,13 +1852,13 @@ describe('ReactIncremental', () => {
1852
route: PropTypes.string,
1853
};
1854
render() {
1894
- ops.push('ShowRoute ' + JSON.stringify(this.context));
1855
+ ReactNoop.yield('ShowRoute ' + JSON.stringify(this.context));
1856
return this.context.route;
1857
}
1858
}
1859
1860
function ShowBoth(props, context) {
1900
- ops.push('ShowBoth ' + JSON.stringify(context));
1861
+ ReactNoop.yield('ShowBoth ' + JSON.stringify(context));
1862
return `${context.route} in ${context.locale}`;
1863
}
1864
ShowBoth.contextTypes = {
@@ -1907,14 +1868,14 @@ describe('ReactIncremental', () => {
1868
1869
class ShowNeither extends React.Component {
1870
render() {
1910
- ops.push('ShowNeither ' + JSON.stringify(this.context));
1871
+ ReactNoop.yield('ShowNeither ' + JSON.stringify(this.context));
1872
return null;
1873
}
1874
}
1875
1876
class Indirection extends React.Component {
1877
render() {
1917
- ops.push('Indirection ' + JSON.stringify(this.context));
1878
+ ReactNoop.yield('Indirection ' + JSON.stringify(this.context));
1879
return [
1880
<ShowLocale key="a" />,
1881
<ShowRoute key="b" />,
@@ -1927,7 +1888,6 @@ describe('ReactIncremental', () => {
1888
}
1889
}
1890
1930
- ops.length = 0;
1891
ReactNoop.render(
1892
<Intl locale="fr">
1893
<ShowLocale />
@@ -1936,18 +1896,18 @@ describe('ReactIncremental', () => {
1896
</div>
1897
</Intl>,
1898
);
1939
- expect(ReactNoop.flush).toWarnDev(
1899
+ expect(() =>
1900
+ expect(ReactNoop.flush()).toEqual([
1901
+ 'Intl {}',
1902
+ 'ShowLocale {"locale":"fr"}',
1903
+ 'ShowBoth {"locale":"fr"}',
1904
+ ]),
1905
+ ).toWarnDev(
1906
'Legacy context API has been detected within a strict-mode tree: \n\n' +
1907
'Please update the following components: Intl, ShowBoth, ShowLocale',
1908
{withoutStack: true},
1909
);
1944
- expect(ops).toEqual([
1945
- 'Intl {}',
1946
- 'ShowLocale {"locale":"fr"}',
1947
- 'ShowBoth {"locale":"fr"}',
1948
- ]);
1910
1950
- ops.length = 0;
1911
ReactNoop.render(
1912
<Intl locale="de">
1913
<ShowLocale />
@@ -1956,14 +1916,12 @@ describe('ReactIncremental', () => {
1916
</div>
1917
</Intl>,
1918
);
1959
- ReactNoop.flush();
1960
- expect(ops).toEqual([
1919
+ expect(ReactNoop.flush()).toEqual([
1920
'Intl {}',
1921
'ShowLocale {"locale":"de"}',
1922
'ShowBoth {"locale":"de"}',
1923
]);
1924
1966
- ops.length = 0;
1925
ReactNoop.render(
1926
<Intl locale="sv">
1927
<ShowLocale />
@@ -1972,10 +1930,8 @@ describe('ReactIncremental', () => {
1930
</div>
1931
</Intl>,
1932
);
1975
- ReactNoop.flushDeferredPri(15);
1976
- expect(ops).toEqual(['Intl {}']);
1933
+ ReactNoop.flushThrough(['Intl {}']);
1934
1978
- ops.length = 0;
1935
ReactNoop.render(
1936
<Intl locale="en">
1937
<ShowLocale />
@@ -1985,26 +1941,27 @@ describe('ReactIncremental', () => {
1941
<ShowBoth />
1942
</Intl>,
1943
);
1988
- expect(ReactNoop.flush).toWarnDev(
1944
+ expect(() =>
1945
+ expect(ReactNoop.flush()).toEqual([
1946
+ 'ShowLocale {"locale":"sv"}',
1947
+ 'ShowBoth {"locale":"sv"}',
1948
+ 'Intl {}',
1949
+ 'ShowLocale {"locale":"en"}',
1950
+ 'Router {}',
1951
+ 'Indirection {}',
1952
+ 'ShowLocale {"locale":"en"}',
1953
+ 'ShowRoute {"route":"/about"}',
1954
+ 'ShowNeither {}',
1955
+ 'Intl {}',
1956
+ 'ShowBoth {"locale":"ru","route":"/about"}',
1957
+ 'ShowBoth {"locale":"en","route":"/about"}',
1958
+ 'ShowBoth {"locale":"en"}',
1959
+ ]),
1960
+ ).toWarnDev(
1961
'Legacy context API has been detected within a strict-mode tree: \n\n' +
1962
'Please update the following components: Router, ShowRoute',
1963
{withoutStack: true},
1964
);
1993
- expect(ops).toEqual([
1994
- 'ShowLocale {"locale":"sv"}',
1995
- 'ShowBoth {"locale":"sv"}',
1996
- 'Intl {}',
1997
- 'ShowLocale {"locale":"en"}',
1998
- 'Router {}',
1999
- 'Indirection {}',
2000
- 'ShowLocale {"locale":"en"}',
2001
- 'ShowRoute {"route":"/about"}',
2002
- 'ShowNeither {}',
2003
- 'Intl {}',
2004
- 'ShowBoth {"locale":"ru","route":"/about"}',
2005
- 'ShowBoth {"locale":"en","route":"/about"}',
2006
- 'ShowBoth {"locale":"en"}',
2007
- ]);
1965
});
1966
1967
it('does not leak own context into context provider', () => {
@@ -2080,8 +2037,6 @@ describe('ReactIncremental', () => {
2037
});
2038
2039
it('provides context when reusing work', () => {
2083
- const ops = [];
2084
-
2040
class Intl extends React.Component {
2041
static childContextTypes = {
2042
locale: PropTypes.string,
@@ -2092,7 +2047,7 @@ describe('ReactIncremental', () => {
2047
};
2048
}
2049
render() {
2095
- ops.push('Intl ' + JSON.stringify(this.context));
2050
+ ReactNoop.yield('Intl ' + JSON.stringify(this.context));
2051
return this.props.children;
2052
}
2053
}
@@ -2102,12 +2057,11 @@ describe('ReactIncremental', () => {
2057
locale: PropTypes.string,
2058
};
2059
render() {
2105
- ops.push('ShowLocale ' + JSON.stringify(this.context));
2060
+ ReactNoop.yield('ShowLocale ' + JSON.stringify(this.context));
2061
return this.context.locale;
2062
}
2063
}
2064
2110
- ops.length = 0;
2065
ReactNoop.render(
2066
<Intl locale="fr">
2067
<ShowLocale />
@@ -2120,24 +2074,23 @@ describe('ReactIncremental', () => {
2074
<ShowLocale />
2075
</Intl>,
2076
);
2123
- ReactNoop.flushDeferredPri(40);
2124
- expect(ops).toEqual([
2077
+ ReactNoop.flushThrough([
2078
'Intl {}',
2079
'ShowLocale {"locale":"fr"}',
2080
'ShowLocale {"locale":"fr"}',
2081
]);
2082
2130
- ops.length = 0;
2131
- expect(ReactNoop.flush).toWarnDev(
2083
+ expect(() =>
2084
+ expect(ReactNoop.flush()).toEqual([
2085
+ 'ShowLocale {"locale":"fr"}',
2086
+ 'Intl {}',
2087
+ 'ShowLocale {"locale":"ru"}',
2088
+ ]),
2089
+ ).toWarnDev(
2090
'Legacy context API has been detected within a strict-mode tree: \n\n' +
2091
'Please update the following components: Intl, ShowLocale',
2092
{withoutStack: true},
2093
);
2136
- expect(ops).toEqual([
2137
- 'ShowLocale {"locale":"fr"}',
2138
- 'Intl {}',
2139
- 'ShowLocale {"locale":"ru"}',
2140
- ]);
2094
});
2095
2096
it('reads context when setState is below the provider', () => {
packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js
+11
-17
@@ -318,7 +318,7 @@ describe('ReactIncrementalErrorHandling', () => {
318
);
319
}
320
321
- ReactNoop.render(<Parent />);
321
+ ReactNoop.render(<Parent />, () => ReactNoop.yield('commit'));
322
323
// Render the bad component asynchronously
324
ReactNoop.flushThrough(['Parent', 'BadRender']);
@@ -326,10 +326,9 @@ describe('ReactIncrementalErrorHandling', () => {
326
// Finish the rest of the async work
327
ReactNoop.flushThrough(['Sibling']);
328
329
- // Rendering two more units of work should be enough to trigger the retry
330
- // and synchronously throw an error.
329
+ // React retries once, synchronously, before throwing.
330
ops = [];
332
- expect(() => ReactNoop.flushUnitsOfWork(2)).toThrow('oops');
331
+ expect(() => ReactNoop.flushNextYield()).toThrow('oops');
332
expect(ops).toEqual(['Parent', 'BadRender', 'Sibling']);
333
});
334
@@ -409,7 +408,7 @@ describe('ReactIncrementalErrorHandling', () => {
408
<BrokenRender />
409
</ErrorBoundary>,
410
);
412
- ReactNoop.flushDeferredPri();
411
+ ReactNoop.flush();
412
expect(ReactNoop.getChildren()).toEqual([span('Caught an error: Hello.')]);
413
});
414
@@ -588,20 +587,19 @@ describe('ReactIncrementalErrorHandling', () => {
587
});
588
589
it('propagates an error from a noop error boundary during partial deferred mounting', () => {
591
- const ops = [];
590
class RethrowErrorBoundary extends React.Component {
591
componentDidCatch(error) {
594
- ops.push('RethrowErrorBoundary componentDidCatch');
592
+ ReactNoop.yield('RethrowErrorBoundary componentDidCatch');
593
throw error;
594
}
595
render() {
598
- ops.push('RethrowErrorBoundary render');
596
+ ReactNoop.yield('RethrowErrorBoundary render');
597
return this.props.children;
598
}
599
}
600
601
function BrokenRender() {
604
- ops.push('BrokenRender');
602
+ ReactNoop.yield('BrokenRender');
603
throw new Error('Hello');
604
}
605
@@ -611,16 +609,12 @@ describe('ReactIncrementalErrorHandling', () => {
609
</RethrowErrorBoundary>,
610
);
611
614
- ReactNoop.flushDeferredPri(15);
615
- expect(ops).toEqual(['RethrowErrorBoundary render']);
612
+ ReactNoop.flushThrough(['RethrowErrorBoundary render']);
613
617
- ops.length = 0;
614
expect(() => {
615
ReactNoop.flush();
616
}).toThrow('Hello');
621
- expect(ops).toEqual([
622
- 'BrokenRender',
623
-
617
+ expect(ReactNoop.clearYields()).toEqual([
618
// React retries one more time
619
'RethrowErrorBoundary render',
620
'BrokenRender',
@@ -1526,7 +1520,7 @@ describe('ReactIncrementalErrorHandling', () => {
1520
<BrokenRender />
1521
</ErrorBoundary>,
1522
);
1529
- ReactNoop.flushDeferredPri();
1523
+ ReactNoop.flush();
1524
expect(ReactNoop.getChildren()).toEqual([
1525
span(
1526
'Caught an error:\n' +
@@ -1562,7 +1556,7 @@ describe('ReactIncrementalErrorHandling', () => {
1556
<BrokenRender />
1557
</ErrorBoundary>,
1558
);
1565
- ReactNoop.flushDeferredPri();
1559
+ ReactNoop.flush();
1560
expect(ReactNoop.getChildren()).toEqual([span('Caught an error: Hello')]);
1561
});
1562
packages/react-reconciler/src/__tests__/ReactIncrementalErrorLogging-test.js
+3
-3
@@ -50,7 +50,7 @@ describe('ReactIncrementalErrorLogging', () => {
50
</span>
51
</div>,
52
);
53
- expect(ReactNoop.flushDeferredPri).toThrowError('constructor error');
53
+ expect(() => ReactNoop.flush()).toThrowError('constructor error');
54
expect(console.error).toHaveBeenCalledTimes(1);
55
expect(console.error).toHaveBeenCalledWith(
56
__DEV__
@@ -86,7 +86,7 @@ describe('ReactIncrementalErrorLogging', () => {
86
</span>
87
</div>,
88
);
89
- expect(ReactNoop.flushDeferredPri).toThrowError('componentDidMount error');
89
+ expect(() => ReactNoop.flush()).toThrowError('componentDidMount error');
90
expect(console.error).toHaveBeenCalledTimes(1);
91
expect(console.error).toHaveBeenCalledWith(
92
__DEV__
@@ -125,7 +125,7 @@ describe('ReactIncrementalErrorLogging', () => {
125
</span>
126
</div>,
127
);
128
- expect(ReactNoop.flushDeferredPri).toThrow('render error');
128
+ expect(() => ReactNoop.flush()).toThrow('render error');
129
expect(logCapturedErrorCalls.length).toBe(1);
130
expect(logCapturedErrorCalls[0]).toEqual(
131
__DEV__
packages/react-reconciler/src/__tests__/ReactIncrementalPerf-test.internal.js
+19
-7
@@ -117,6 +117,7 @@ describe('ReactDebugFiberPerf', () => {
117
require('shared/ReactFeatureFlags').enableUserTimingAPI = true;
118
require('shared/ReactFeatureFlags').enableProfilerTimer = false;
119
require('shared/ReactFeatureFlags').replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
120
+ require('shared/ReactFeatureFlags').debugRenderPhaseSideEffectsForStrictMode = false;
121
122
// Import after the polyfill is set up:
123
React = require('react');
@@ -397,12 +398,21 @@ describe('ReactDebugFiberPerf', () => {
398
it('measures deferred work in chunks', () => {
399
class A extends React.Component {
400
render() {
401
+ ReactNoop.yield('A');
402
return <div>{this.props.children}</div>;
403
}
404
}
405
406
class B extends React.Component {
407
render() {
408
+ ReactNoop.yield('B');
409
+ return <div>{this.props.children}</div>;
410
+ }
411
+ }
412
+
413
+ class C extends React.Component {
414
+ render() {
415
+ ReactNoop.yield('C');
416
return <div>{this.props.children}</div>;
417
}
418
}
@@ -415,14 +425,15 @@ describe('ReactDebugFiberPerf', () => {
425
<B>
426
<Child />
427
</B>
428
+ <C>
429
+ <Child />
430
+ </C>
431
</Parent>,
432
);
420
- addComment('Start mounting Parent and A');
421
- ReactNoop.flushDeferredPri(40);
422
- addComment('Mount B just a little (but not enough to memoize)');
423
- ReactNoop.flushDeferredPri(10);
424
- addComment('Complete B and Parent');
425
- ReactNoop.flushDeferredPri();
433
+ addComment('Start rendering through B');
434
+ ReactNoop.flushThrough(['A', 'B']);
435
+ addComment('Complete the rest');
436
+ ReactNoop.flush();
437
expect(getFlameChart()).toMatchSnapshot();
438
});
439
@@ -632,11 +643,12 @@ describe('ReactDebugFiberPerf', () => {
643
644
it('warns if an in-progress update is interrupted', () => {
645
function Foo() {
646
+ ReactNoop.yield('Foo');
647
return <span />;
648
}
649
650
ReactNoop.render(<Foo />);
639
- ReactNoop.flushUnitsOfWork(2);
651
+ ReactNoop.flushNextYield();
652
ReactNoop.flushSync(() => {
653
ReactNoop.render(<Foo />);
654
});
packages/react-reconciler/src/__tests__/ReactIncrementalReflection-test.internal.js
+38
-85
@@ -24,8 +24,6 @@ describe('ReactIncrementalReflection', () => {
24
});
25
26
it('handles isMounted even when the initial render is deferred', () => {
27
- let ops = [];
28
-
27
const instances = [];
28
29
class Component extends React.Component {
@@ -36,10 +34,10 @@ describe('ReactIncrementalReflection', () => {
34
}
35
UNSAFE_componentWillMount() {
36
instances.push(this);
39
- ops.push('componentWillMount', this._isMounted());
37
+ ReactNoop.yield('componentWillMount: ' + this._isMounted());
38
}
39
componentDidMount() {
42
- ops.push('componentDidMount', this._isMounted());
40
+ ReactNoop.yield('componentDidMount: ' + this._isMounted());
41
}
42
render() {
43
return <span />;
@@ -53,29 +51,23 @@ describe('ReactIncrementalReflection', () => {
51
ReactNoop.render(<Foo />);
52
53
// Render part way through but don't yet commit the updates.
56
- ReactNoop.flushDeferredPri(20);
57
-
58
- expect(ops).toEqual(['componentWillMount', false]);
54
+ ReactNoop.flushThrough(['componentWillMount: false']);
55
56
expect(instances[0]._isMounted()).toBe(false);
57
62
- ops = [];
63
-
58
// Render the rest and commit the updates.
65
- expect(ReactNoop.flush).toWarnDev(
59
+ expect(() =>
60
+ expect(ReactNoop.flush()).toEqual(['componentDidMount: true']),
61
+ ).toWarnDev(
62
'componentWillMount: Please update the following components ' +
63
'to use componentDidMount instead: Component',
64
{withoutStack: true},
65
);
66
71
- expect(ops).toEqual(['componentDidMount', true]);
72
-
67
expect(instances[0]._isMounted()).toBe(true);
68
});
69
70
it('handles isMounted when an unmount is deferred', () => {
77
- let ops = [];
78
-
71
const instances = [];
72
73
class Component extends React.Component {
@@ -86,16 +78,16 @@ describe('ReactIncrementalReflection', () => {
78
instances.push(this);
79
}
80
componentWillUnmount() {
89
- ops.push('componentWillUnmount', this._isMounted());
81
+ ReactNoop.yield('componentWillUnmount: ' + this._isMounted());
82
}
83
render() {
92
- ops.push('Component');
84
+ ReactNoop.yield('Component');
85
return <span />;
86
}
87
}
88
89
function Other() {
98
- ops.push('Other');
90
+ ReactNoop.yield('Other');
91
return <span />;
92
}
93
@@ -104,38 +96,28 @@ describe('ReactIncrementalReflection', () => {
96
}
97
98
ReactNoop.render(<Foo mount={true} />);
107
- expect(ReactNoop.flush).toWarnDev(
99
+ expect(() => expect(ReactNoop.flush()).toEqual(['Component'])).toWarnDev(
100
'componentWillMount: Please update the following components ' +
101
'to use componentDidMount instead: Component',
102
{withoutStack: true},
103
);
104
113
- expect(ops).toEqual(['Component']);
114
- ops = [];
115
-
105
expect(instances[0]._isMounted()).toBe(true);
106
107
ReactNoop.render(<Foo mount={false} />);
108
// Render part way through but don't yet commit the updates so it is not
109
// fully unmounted yet.
121
- ReactNoop.flushDeferredPri(20);
122
-
123
- expect(ops).toEqual(['Other']);
124
- ops = [];
110
+ ReactNoop.flushThrough(['Other']);
111
112
expect(instances[0]._isMounted()).toBe(true);
113
114
// Finish flushing the unmount.
129
- ReactNoop.flush();
130
-
131
- expect(ops).toEqual(['componentWillUnmount', true]);
115
+ expect(ReactNoop.flush()).toEqual(['componentWillUnmount: true']);
116
117
expect(instances[0]._isMounted()).toBe(false);
118
});
119
120
it('finds no node before insertion and correct node before deletion', () => {
137
- let ops = [];
138
-
121
let classInstance = null;
122
123
function findInstance(inst) {
@@ -154,22 +136,22 @@ describe('ReactIncrementalReflection', () => {
136
class Component extends React.Component {
137
UNSAFE_componentWillMount() {
138
classInstance = this;
157
- ops.push('componentWillMount', findInstance(this));
139
+ ReactNoop.yield(['componentWillMount', findInstance(this)]);
140
}
141
componentDidMount() {
160
- ops.push('componentDidMount', findInstance(this));
142
+ ReactNoop.yield(['componentDidMount', findInstance(this)]);
143
}
144
UNSAFE_componentWillUpdate() {
163
- ops.push('componentWillUpdate', findInstance(this));
145
+ ReactNoop.yield(['componentWillUpdate', findInstance(this)]);
146
}
147
componentDidUpdate() {
166
- ops.push('componentDidUpdate', findInstance(this));
148
+ ReactNoop.yield(['componentDidUpdate', findInstance(this)]);
149
}
150
componentWillUnmount() {
169
- ops.push('componentWillUnmount', findInstance(this));
151
+ ReactNoop.yield(['componentWillUnmount', findInstance(this)]);
152
}
153
render() {
172
- ops.push('render');
154
+ ReactNoop.yield('render');
155
return this.props.step < 2 ? (
156
<span ref={ref => (this.span = ref)} />
157
) : this.props.step === 2 ? (
@@ -182,7 +164,7 @@ describe('ReactIncrementalReflection', () => {
164
165
function Sibling() {
166
// Sibling is used to assert that we've rendered past the first component.
185
- ops.push('render sibling');
167
+ ReactNoop.yield('render sibling');
168
return <span />;
169
}
170
@@ -192,23 +174,22 @@ describe('ReactIncrementalReflection', () => {
174
175
ReactNoop.render(<Foo step={0} />);
176
// Flush past Component but don't complete rendering everything yet.
195
- ReactNoop.flushDeferredPri(30);
196
-
197
- expect(ops).toEqual([
198
- 'componentWillMount',
199
- null,
177
+ ReactNoop.flushThrough([
178
+ ['componentWillMount', null],
179
'render',
180
'render sibling',
181
]);
182
204
- ops = [];
205
-
183
expect(classInstance).toBeDefined();
184
// The instance has been complete but is still not committed so it should
185
// not find any host nodes in it.
186
expect(findInstance(classInstance)).toBe(null);
187
211
- expect(ReactNoop.flush).toWarnDev(
188
+ expect(() =>
189
+ expect(ReactNoop.flush()).toEqual([
190
+ ['componentDidMount', classInstance.span],
191
+ ]),
192
+ ).toWarnDev(
193
'componentWillMount: Please update the following components ' +
194
'to use componentDidMount instead: Component' +
195
'\n\ncomponentWillUpdate: Please update the following components ' +
@@ -221,79 +202,54 @@ describe('ReactIncrementalReflection', () => {
202
203
expect(findInstance(classInstance)).toBe(hostSpan);
204
224
- expect(ops).toEqual(['componentDidMount', hostSpan]);
225
-
226
- ops = [];
227
-
205
// Flush next step which will cause an update but not yet render a new host
206
// node.
207
ReactNoop.render(<Foo step={1} />);
231
- ReactNoop.flush();
232
-
233
- expect(ops).toEqual([
234
- 'componentWillUpdate',
235
- hostSpan,
208
+ expect(ReactNoop.flush()).toEqual([
209
+ ['componentWillUpdate', hostSpan],
210
'render',
211
'render sibling',
238
- 'componentDidUpdate',
239
- hostSpan,
212
+ ['componentDidUpdate', hostSpan],
213
]);
214
215
expect(ReactNoop.findInstance(classInstance)).toBe(hostSpan);
216
244
- ops = [];
245
-
217
// The next step will render a new host node but won't get committed yet.
218
// We expect this to mutate the original Fiber.
219
ReactNoop.render(<Foo step={2} />);
249
- ReactNoop.flushDeferredPri(30);
250
-
251
- expect(ops).toEqual([
252
- 'componentWillUpdate',
253
- hostSpan,
220
+ ReactNoop.flushThrough([
221
+ ['componentWillUpdate', hostSpan],
222
'render',
223
'render sibling',
224
]);
225
258
- ops = [];
259
-
226
// This should still be the host span.
227
expect(ReactNoop.findInstance(classInstance)).toBe(hostSpan);
228
229
// When we finally flush the tree it will get committed.
264
- ReactNoop.flush();
230
+ expect(ReactNoop.flush()).toEqual([
231
+ ['componentDidUpdate', classInstance.div],
232
+ ]);
233
234
const hostDiv = classInstance.div;
267
-
235
expect(hostDiv).toBeDefined();
236
expect(hostSpan).not.toBe(hostDiv);
237
271
- expect(ops).toEqual(['componentDidUpdate', hostDiv]);
272
-
273
- ops = [];
274
-
238
// We should now find the new host node.
239
expect(ReactNoop.findInstance(classInstance)).toBe(hostDiv);
240
241
// Render to null but don't commit it yet.
242
ReactNoop.render(<Foo step={3} />);
280
- ReactNoop.flushDeferredPri(25);
281
-
282
- expect(ops).toEqual([
283
- 'componentWillUpdate',
284
- hostDiv,
243
+ ReactNoop.flushThrough([
244
+ ['componentWillUpdate', hostDiv],
245
'render',
246
'render sibling',
247
]);
248
289
- ops = [];
290
-
249
// This should still be the host div since the deletion is not committed.
250
expect(ReactNoop.findInstance(classInstance)).toBe(hostDiv);
251
294
- ReactNoop.flush();
295
-
296
- expect(ops).toEqual(['componentDidUpdate', null]);
252
+ expect(ReactNoop.flush()).toEqual([['componentDidUpdate', null]]);
253
254
// This should still be the host div since the deletion is not committed.
255
expect(ReactNoop.findInstance(classInstance)).toBe(null);
@@ -302,11 +258,8 @@ describe('ReactIncrementalReflection', () => {
258
ReactNoop.render(<Foo step={4} />);
259
ReactNoop.flush();
260
305
- ops = [];
306
-
261
// Unmount the component.
262
ReactNoop.render([]);
309
- ReactNoop.flush();
310
- expect(ops).toEqual(['componentWillUnmount', hostDiv]);
263
+ expect(ReactNoop.flush()).toEqual([['componentWillUnmount', hostDiv]]);
264
});
265
});
packages/react-reconciler/src/__tests__/ReactIncrementalScheduling-test.internal.js
+65
-39
@@ -31,7 +31,7 @@ describe('ReactIncrementalScheduling', () => {
31
ReactNoop.render(<span prop="1" />);
32
expect(ReactNoop.getChildren()).toEqual([]);
33
34
- ReactNoop.flushDeferredPri();
34
+ ReactNoop.flush();
35
expect(ReactNoop.getChildren()).toEqual([span('1')]);
36
});
37
@@ -86,71 +86,99 @@ describe('ReactIncrementalScheduling', () => {
86
});
87
88
it('works on deferred roots in the order they were scheduled', () => {
89
- ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
90
- ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
91
- ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
89
+ const {useEffect} = React;
90
+ function Text({text}) {
91
+ useEffect(
92
+ () => {
93
+ ReactNoop.yield(text);
94
+ },
95
+ [text],
96
+ );
97
+ return text;
98
+ }
99
+
100
+ ReactNoop.act(() => {
101
+ ReactNoop.renderToRootWithID(<Text text="a:1" />, 'a');
102
+ ReactNoop.renderToRootWithID(<Text text="b:1" />, 'b');
103
+ ReactNoop.renderToRootWithID(<Text text="c:1" />, 'c');
104
+ });
105
ReactNoop.flush();
93
- expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
94
- expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
95
- expect(ReactNoop.getChildren('c')).toEqual([span('c:1')]);
106
+
107
+ expect(ReactNoop.getChildrenAsJSX('a')).toEqual('a:1');
108
+ expect(ReactNoop.getChildrenAsJSX('b')).toEqual('b:1');
109
+ expect(ReactNoop.getChildrenAsJSX('c')).toEqual('c:1');
110
111
// Schedule deferred work in the reverse order
98
- ReactNoop.renderToRootWithID(<span prop="c:2" />, 'c');
99
- ReactNoop.renderToRootWithID(<span prop="b:2" />, 'b');
112
+ ReactNoop.act(() => {
113
+ ReactNoop.renderToRootWithID(<Text text="c:2" />, 'c');
114
+ ReactNoop.renderToRootWithID(<Text text="b:2" />, 'b');
115
+ });
116
// Ensure it starts in the order it was scheduled
101
- ReactNoop.flushDeferredPri(15 + 5);
102
- expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
103
- expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
104
- expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
117
+ ReactNoop.flushThrough(['c:2']);
118
+
119
+ expect(ReactNoop.getChildrenAsJSX('a')).toEqual('a:1');
120
+ expect(ReactNoop.getChildrenAsJSX('b')).toEqual('b:1');
121
+ expect(ReactNoop.getChildrenAsJSX('c')).toEqual('c:2');
122
// Schedule last bit of work, it will get processed the last
106
- ReactNoop.renderToRootWithID(<span prop="a:2" />, 'a');
123
+ ReactNoop.act(() => {
124
+ ReactNoop.renderToRootWithID(<Text text="a:2" />, 'a');
125
+ });
126
// Keep performing work in the order it was scheduled
108
- ReactNoop.flushDeferredPri(15 + 5);
109
- expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
110
- expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
111
- expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
112
- ReactNoop.flushDeferredPri(15 + 5);
113
- expect(ReactNoop.getChildren('a')).toEqual([span('a:2')]);
114
- expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
115
- expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
127
+ ReactNoop.flushThrough(['b:2']);
128
+ expect(ReactNoop.getChildrenAsJSX('a')).toEqual('a:1');
129
+ expect(ReactNoop.getChildrenAsJSX('b')).toEqual('b:2');
130
+ expect(ReactNoop.getChildrenAsJSX('c')).toEqual('c:2');
131
+
132
+ ReactNoop.flushThrough(['a:2']);
133
+ expect(ReactNoop.getChildrenAsJSX('a')).toEqual('a:2');
134
+ expect(ReactNoop.getChildrenAsJSX('b')).toEqual('b:2');
135
+ expect(ReactNoop.getChildrenAsJSX('c')).toEqual('c:2');
136
});
137
138
it('schedules sync updates when inside componentDidMount/Update', () => {
139
let instance;
120
- let ops = [];
140
141
class Foo extends React.Component {
142
state = {tick: 0};
143
144
componentDidMount() {
126
- ops.push('componentDidMount (before setState): ' + this.state.tick);
145
+ ReactNoop.yield(
146
+ 'componentDidMount (before setState): ' + this.state.tick,
147
+ );
148
this.setState({tick: 1});
149
// We're in a batch. Update hasn't flushed yet.
129
- ops.push('componentDidMount (after setState): ' + this.state.tick);
150
+ ReactNoop.yield(
151
+ 'componentDidMount (after setState): ' + this.state.tick,
152
+ );
153
}
154
155
componentDidUpdate() {
133
- ops.push('componentDidUpdate: ' + this.state.tick);
156
+ ReactNoop.yield('componentDidUpdate: ' + this.state.tick);
157
if (this.state.tick === 2) {
135
- ops.push('componentDidUpdate (before setState): ' + this.state.tick);
158
+ ReactNoop.yield(
159
+ 'componentDidUpdate (before setState): ' + this.state.tick,
160
+ );
161
this.setState({tick: 3});
137
- ops.push('componentDidUpdate (after setState): ' + this.state.tick);
162
+ ReactNoop.yield(
163
+ 'componentDidUpdate (after setState): ' + this.state.tick,
164
+ );
165
// We're in a batch. Update hasn't flushed yet.
166
}
167
}
168
169
render() {
143
- ops.push('render: ' + this.state.tick);
170
+ ReactNoop.yield('render: ' + this.state.tick);
171
instance = this;
172
return <span prop={this.state.tick} />;
173
}
174
}
175
176
ReactNoop.render(<Foo />);
177
+ // Render without committing
178
+ ReactNoop.flushThrough(['render: 0']);
179
151
- ReactNoop.flushDeferredPri(20 + 5);
152
- expect(ops).toEqual([
153
- 'render: 0',
180
+ // Do one more unit of work to commit
181
+ expect(ReactNoop.flushNextYield()).toEqual([
182
'componentDidMount (before setState): 0',
183
'componentDidMount (after setState): 0',
184
// If the setState inside componentDidMount were deferred, there would be
@@ -159,12 +187,9 @@ describe('ReactIncrementalScheduling', () => {
187
'componentDidUpdate: 1',
188
]);
189
162
- ops = [];
190
instance.setState({tick: 2});
164
- ReactNoop.flushDeferredPri(20 + 5);
165
-
166
- expect(ops).toEqual([
167
- 'render: 2',
191
+ ReactNoop.flushThrough(['render: 2']);
192
+ expect(ReactNoop.flushNextYield()).toEqual([
193
'componentDidUpdate: 2',
194
'componentDidUpdate (before setState): 2',
195
'componentDidUpdate (after setState): 2',
@@ -254,17 +279,18 @@ describe('ReactIncrementalScheduling', () => {
279
});
280
}
281
render() {
282
+ ReactNoop.yield('Foo');
283
return <span prop={this.state.step} />;
284
}
285
}
286
ReactNoop.render(<Foo />);
287
// This should be just enough to complete all the work, but not enough to
288
// commit it.
263
- ReactNoop.flushDeferredPri(20);
289
+ ReactNoop.flushThrough(['Foo']);
290
expect(ReactNoop.getChildren()).toEqual([]);
291
292
// Do one more unit of work.
267
- ReactNoop.flushDeferredPri(10);
293
+ ReactNoop.flushNextYield();
294
// The updates should all be flushed with Task priority
295
expect(ReactNoop.getChildren()).toEqual([span(5)]);
296
});
packages/react-reconciler/src/__tests__/ReactIncrementalSideEffects-test.internal.js
+41
-44
@@ -373,10 +373,12 @@ describe('ReactIncrementalSideEffects', () => {
373
374
it('does not update child nodes if a flush is aborted', () => {
375
function Bar(props) {
376
+ ReactNoop.yield('Bar');
377
return <span prop={props.text} />;
378
}
379
380
function Foo(props) {
381
+ ReactNoop.yield('Foo');
382
return (
383
<div>
384
<div>
@@ -395,7 +397,9 @@ describe('ReactIncrementalSideEffects', () => {
397
]);
398
399
ReactNoop.render(<Foo text="World" />);
398
- ReactNoop.flushDeferredPri(35);
400
+
401
+ // Flush some of the work without committing
402
+ ReactNoop.flushThrough(['Foo', 'Bar']);
403
expect(ReactNoop.getChildren()).toEqual([
404
div(div(span('Hello'), span('Hello')), span('Yo')),
405
]);
@@ -403,10 +407,12 @@ describe('ReactIncrementalSideEffects', () => {
407
408
it('preserves a previously rendered node when deprioritized', () => {
409
function Middle(props) {
410
+ ReactNoop.yield('Middle');
411
return <span prop={props.children} />;
412
}
413
414
function Foo(props) {
415
+ ReactNoop.yield('Foo');
416
return (
417
<div>
418
<div hidden={true}>
@@ -427,9 +433,8 @@ describe('ReactIncrementalSideEffects', () => {
433
</div>,
434
);
435
430
- ReactNoop.render(<Foo text="bar" />);
431
- ReactNoop.flushDeferredPri(20);
432
-
436
+ ReactNoop.render(<Foo text="bar" />, () => ReactNoop.yield('commit'));
437
+ ReactNoop.flushThrough(['Foo', 'commit']);
438
expect(ReactNoop.getChildrenAsJSX()).toEqual(
439
<div>
440
<div hidden={true}>
@@ -437,8 +442,8 @@ describe('ReactIncrementalSideEffects', () => {
442
</div>
443
</div>,
444
);
440
- ReactNoop.flush();
445
446
+ ReactNoop.flush();
447
expect(ReactNoop.getChildrenAsJSX()).toEqual(
448
<div>
449
<div hidden={true}>
@@ -450,6 +455,7 @@ describe('ReactIncrementalSideEffects', () => {
455
456
it('can reuse side-effects after being preempted', () => {
457
function Bar(props) {
458
+ ReactNoop.yield('Bar');
459
return <span prop={props.children} />;
460
}
461
@@ -461,6 +467,7 @@ describe('ReactIncrementalSideEffects', () => {
467
);
468
469
function Foo(props) {
470
+ ReactNoop.yield('Foo');
471
return (
472
<div hidden={true}>
473
{props.step === 0 ? (
@@ -490,8 +497,10 @@ describe('ReactIncrementalSideEffects', () => {
497
498
// Make a quick update which will schedule low priority work to
499
// update the middle content.
493
- ReactNoop.render(<Foo text="bar" step={1} />);
494
- ReactNoop.flushDeferredPri(30);
500
+ ReactNoop.render(<Foo text="bar" step={1} />, () =>
501
+ ReactNoop.yield('commit'),
502
+ );
503
+ ReactNoop.flushThrough(['Foo', 'commit', 'Bar']);
504
505
// The tree remains unchanged.
506
expect(ReactNoop.getChildrenAsJSX()).toEqual(
@@ -529,6 +538,7 @@ describe('ReactIncrementalSideEffects', () => {
538
return this.props.children !== nextProps.children;
539
}
540
render() {
541
+ ReactNoop.yield('Bar');
542
return <span prop={this.props.children} />;
543
}
544
}
@@ -538,6 +548,7 @@ describe('ReactIncrementalSideEffects', () => {
548
return this.props.step !== nextProps.step;
549
}
550
render() {
551
+ ReactNoop.yield('Content');
552
return (
553
<div>
554
<Bar>{this.props.step === 0 ? 'Hi' : 'Hello'}</Bar>
@@ -548,6 +559,7 @@ describe('ReactIncrementalSideEffects', () => {
559
}
560
561
function Foo(props) {
562
+ ReactNoop.yield('Foo');
563
return (
564
<div hidden={true}>
565
<Content step={props.step} text={props.text} />
@@ -571,7 +583,7 @@ describe('ReactIncrementalSideEffects', () => {
583
// Make a quick update which will schedule low priority work to
584
// update the middle content.
585
ReactNoop.render(<Foo text="bar" step={1} />);
574
- ReactNoop.flushDeferredPri(35);
586
+ ReactNoop.flushThrough(['Foo', 'Content', 'Bar']);
587
588
// The tree remains unchanged.
589
expect(ReactNoop.getChildrenAsJSX()).toEqual(
@@ -587,7 +599,7 @@ describe('ReactIncrementalSideEffects', () => {
599
// render some higher priority work. The middle content will bailout so
600
// it remains untouched which means that it should reuse it next time.
601
ReactNoop.render(<Foo text="foo" step={1} />);
590
- ReactNoop.flush(30);
602
+ ReactNoop.flush();
603
604
// Since we did nothing to the middle subtree during the interruption,
605
// we should be able to reuse the reconciliation work that we already did
@@ -605,34 +617,34 @@ describe('ReactIncrementalSideEffects', () => {
617
618
it('can update a completed tree before it has a chance to commit', () => {
619
function Foo(props) {
620
+ ReactNoop.yield('Foo');
621
return <span prop={props.step} />;
622
}
623
ReactNoop.render(<Foo step={1} />);
624
// This should be just enough to complete the tree without committing it
612
- ReactNoop.flushDeferredPri(20);
613
- expect(ReactNoop.getChildren()).toEqual([]);
614
- // To confirm, perform one more unit of work. The tree should now be flushed.
615
- // (ReactNoop decrements the time remaining by 5 *before* returning it from
616
- // the deadline, so to perform n units of work, you need to give it 5n + 5.
617
- // TODO: This is confusing. Decrement it after.)
618
- ReactNoop.flushDeferredPri(10);
619
- expect(ReactNoop.getChildren()).toEqual([span(1)]);
625
+ ReactNoop.flushThrough(['Foo']);
626
+ expect(ReactNoop.getChildrenAsJSX()).toEqual(null);
627
+ // To confirm, perform one more unit of work. The tree should now
628
+ // be flushed.
629
+ ReactNoop.flushNextYield();
630
+ expect(ReactNoop.getChildrenAsJSX()).toEqual(<span prop={1} />);
631
632
ReactNoop.render(<Foo step={2} />);
633
// This should be just enough to complete the tree without committing it
623
- ReactNoop.flushDeferredPri(20);
624
- expect(ReactNoop.getChildren()).toEqual([span(1)]);
634
+ ReactNoop.flushThrough(['Foo']);
635
+ expect(ReactNoop.getChildrenAsJSX()).toEqual(<span prop={1} />);
636
// This time, before we commit the tree, we update the root component with
637
// new props
638
ReactNoop.render(<Foo step={3} />);
639
+ expect(ReactNoop.getChildrenAsJSX()).toEqual(<span prop={1} />);
640
// Now let's commit. We already had a commit that was pending, which will
641
// render 2.
630
- ReactNoop.flushDeferredPri(10);
631
- expect(ReactNoop.getChildren()).toEqual([span(2)]);
642
+ ReactNoop.flushNextYield();
643
+ expect(ReactNoop.getChildrenAsJSX()).toEqual(<span prop={2} />);
644
// If we flush the rest of the work, we should get another commit that
645
// renders 3. If it renders 2 again, that means an update was dropped.
646
ReactNoop.flush();
635
- expect(ReactNoop.getChildren()).toEqual([span(3)]);
647
+ expect(ReactNoop.getChildrenAsJSX()).toEqual(<span prop={3} />);
648
});
649
650
it('updates a child even though the old props is empty', () => {
@@ -875,8 +887,6 @@ describe('ReactIncrementalSideEffects', () => {
887
});
888
889
it('deprioritizes setStates that happens within a deprioritized tree', () => {
878
- let ops = [];
879
-
890
const barInstances = [];
891
892
class Bar extends React.Component {
@@ -889,12 +899,12 @@ describe('ReactIncrementalSideEffects', () => {
899
this.setState({active: true});
900
}
901
render() {
892
- ops.push('Bar');
902
+ ReactNoop.yield('Bar');
903
return <span prop={this.state.active ? 'X' : this.props.idx} />;
904
}
905
}
906
function Foo(props) {
897
- ops.push('Foo');
907
+ ReactNoop.yield('Foo');
908
return (
909
<div>
910
<span prop={props.tick} />
@@ -907,7 +917,7 @@ describe('ReactIncrementalSideEffects', () => {
917
);
918
}
919
ReactNoop.render(<Foo tick={0} idx={0} />);
910
- ReactNoop.flush();
920
+ expect(ReactNoop.flush()).toEqual(['Foo', 'Bar', 'Bar', 'Bar']);
921
expect(ReactNoop.getChildrenAsJSX()).toEqual(
922
<div>
923
<span prop={0} />
@@ -919,12 +929,8 @@ describe('ReactIncrementalSideEffects', () => {
929
</div>,
930
);
931
922
- expect(ops).toEqual(['Foo', 'Bar', 'Bar', 'Bar']);
923
-
924
- ops = [];
925
-
932
ReactNoop.render(<Foo tick={1} idx={1} />);
927
- ReactNoop.flushDeferredPri(70 + 5);
933
+ ReactNoop.flushThrough(['Foo', 'Bar', 'Bar']);
934
expect(ReactNoop.getChildrenAsJSX()).toEqual(
935
<div>
936
{/* Updated */}
@@ -937,16 +943,12 @@ describe('ReactIncrementalSideEffects', () => {
943
</div>,
944
);
945
940
- expect(ops).toEqual(['Foo', 'Bar', 'Bar']);
941
- ops = [];
942
-
946
barInstances[0].activate();
947
948
// This should not be enough time to render the content of all the hidden
949
// items. Including the set state since that is deprioritized.
947
- // TODO: The cycles it takes to do this could be lowered with further
948
- // optimizations.
949
- ReactNoop.flushDeferredPri(35);
950
+ // ReactNoop.flushDeferredPri(35);
951
+ ReactNoop.flushThrough(['Bar']);
952
expect(ReactNoop.getChildrenAsJSX()).toEqual(
953
<div>
954
{/* Updated */}
@@ -960,12 +962,9 @@ describe('ReactIncrementalSideEffects', () => {
962
</div>,
963
);
964
963
- expect(ops).toEqual(['Bar']);
964
- ops = [];
965
-
965
// However, once we render fully, we will have enough time to finish it all
966
// at once.
968
- ReactNoop.flush();
967
+ expect(ReactNoop.flush()).toEqual(['Bar', 'Bar', 'Bar']);
968
expect(ReactNoop.getChildrenAsJSX()).toEqual(
969
<div>
970
<span prop={1} />
@@ -977,8 +976,6 @@ describe('ReactIncrementalSideEffects', () => {
976
</div>
977
</div>,
978
);
980
-
981
- expect(ops).toEqual(['Bar', 'Bar', 'Bar']);
979
});
980
// TODO: Test that side-effects are not cut off when a work in progress node
981
// moves to "current" without flushing due to having lower priority. Does this
packages/react-reconciler/src/__tests__/ReactIncrementalTriangle-test.internal.js
+1
-1
@@ -385,7 +385,7 @@ describe('ReactIncrementalTriangle', () => {
385
ReactNoop.flushSync(() => {
386
switch (action.type) {
387
case FLUSH:
388
- ReactNoop.flushUnitsOfWork(action.unitsOfWork);
388
+ ReactNoop.unstable_flushNumberOfYields(action.unitsOfWork);
389
break;
390
case FLUSH_ALL:
391
ReactNoop.flush();
packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.internal.js
+2
-1
@@ -32,6 +32,7 @@ describe('ReactIncrementalUpdates', () => {
32
class Foo extends React.Component {
33
state = {};
34
componentDidMount() {
35
+ ReactNoop.yield('commit');
36
ReactNoop.deferredUpdates(() => {
37
// Has low priority
38
this.setState({b: 'b'});
@@ -47,7 +48,7 @@ describe('ReactIncrementalUpdates', () => {
48
}
49
50
ReactNoop.render(<Foo />);
50
- ReactNoop.flushDeferredPri(25);
51
+ ReactNoop.flushThrough(['commit']);
52
expect(state).toEqual({a: 'a'});
53
ReactNoop.flush();
54
expect(state).toEqual({a: 'a', b: 'b', c: 'c'});
packages/react-reconciler/src/__tests__/ReactNewContext-test.internal.js
+2
-1
@@ -1691,6 +1691,7 @@ describe('ReactNewContext', () => {
1691
return false;
1692
}
1693
render() {
1694
+ ReactNoop.yield();
1695
if (this.props.depth >= this.props.maxDepth) {
1696
return null;
1697
}
@@ -1771,7 +1772,7 @@ describe('ReactNewContext', () => {
1772
ReactNoop.flush();
1773
break;
1774
case FLUSH:
1774
- ReactNoop.flushUnitsOfWork(action.unitsOfWork);
1775
+ ReactNoop.unstable_flushNumberOfYields(action.unitsOfWork);
1776
break;
1777
case UPDATE:
1778
finalExpectedValues = {
packages/react-reconciler/src/__tests__/__snapshots__/ReactIncrementalPerf-test.internal.js.snap
+18
-10
@@ -211,25 +211,33 @@ exports[`ReactDebugFiberPerf measures a simple reconciliation 1`] = `
211
exports[`ReactDebugFiberPerf measures deferred work in chunks 1`] = `
212
"⚛ (Waiting for async callback... will force flush in 5250 ms)
213
214
-// Start mounting Parent and A
214
+// Start rendering through B
215
+⚛ (React Tree Reconciliation: Yielded)
216
+ ⚛ Parent [mount]
217
+ ⚛ A [mount]
218
+
219
+⚛ (Waiting for async callback... will force flush in 5250 ms)
220
+
221
⚛ (React Tree Reconciliation: Yielded)
222
⚛ Parent [mount]
223
⚛ A [mount]
224
⚛ Child [mount]
225
+ ⚛ B [mount]
226
227
⚛ (Waiting for async callback... will force flush in 5250 ms)
228
222
-// Mount B just a little (but not enough to memoize)
229
+// Complete the rest
230
⚛ (React Tree Reconciliation: Yielded)
231
⚛ Parent [mount]
232
⚛ B [mount]
233
+ ⚛ Child [mount]
234
+ ⚛ C [mount]
235
236
⚛ (Waiting for async callback... will force flush in 5250 ms)
237
229
-// Complete B and Parent
238
⚛ (React Tree Reconciliation: Completed Root)
239
⚛ Parent [mount]
232
- ⚛ B [mount]
240
+ ⚛ C [mount]
241
⚛ Child [mount]
242
243
⚛ (Committing Changes)
@@ -396,30 +404,30 @@ exports[`ReactDebugFiberPerf supports Suspense and lazy 2`] = `
404
"
405
`;
406
399
-exports[`ReactDebugFiberPerf supports portals 1`] = `
407
+exports[`ReactDebugFiberPerf supports memo 1`] = `
408
"⚛ (Waiting for async callback... will force flush in 5250 ms)
409
410
⚛ (React Tree Reconciliation: Completed Root)
411
⚛ Parent [mount]
404
- ⚛ Child [mount]
412
+ ⚛ Foo [mount]
413
414
⚛ (Committing Changes)
415
⚛ (Committing Snapshot Effects: 0 Total)
408
- ⚛ (Committing Host Effects: 2 Total)
416
+ ⚛ (Committing Host Effects: 1 Total)
417
⚛ (Calling Lifecycle Methods: 0 Total)
418
"
419
`;
420
413
-exports[`ReactDebugFiberPerf supports memo 1`] = `
421
+exports[`ReactDebugFiberPerf supports portals 1`] = `
422
"⚛ (Waiting for async callback... will force flush in 5250 ms)
423
424
⚛ (React Tree Reconciliation: Completed Root)
425
⚛ Parent [mount]
418
- ⚛ Foo [mount]
426
+ ⚛ Child [mount]
427
428
⚛ (Committing Changes)
429
⚛ (Committing Snapshot Effects: 0 Total)
422
- ⚛ (Committing Host Effects: 1 Total)
430
+ ⚛ (Committing Host Effects: 2 Total)
431
⚛ (Calling Lifecycle Methods: 0 Total)
432
"
433
`;