@samitouri / QOS-React-2 / commits / ba708fa79b

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 `;