@samitouri / QOS-React / commits / a155860018

Fix: Don't flush discrete at end of batchedUpdates (#21229)

The outermost `batchedUpdates` call flushes pending sync updates at the end. This was intended for legacy sync mode, but it also happens to flush discrete updates in concurrent mode. Instead, we should only flush sync updates at the end of `batchedUpdates` for legacy roots. Discrete sync updates can wait to flush in the microtask. `discreteUpdates` has the same issue, which is how I originally noticed this, but I'll change that one in a separate commit since it requires updating a few (no longer relevant) internal tests.

Andrew Clark committed Apr 21, 2021 at 11:32 UTC a1558600183b4321d4cdc117925980f1865641fa
8 files changed +198 -71
packages/react-devtools-shared/src/__tests__/storeStressTestConcurrent-test.js
+12 -8
@@ -11,6 +11,7 @@ describe('StoreStressConcurrent', () => {
11 let React;
12 let ReactDOM;
13 let act;
14 + let actAsync;
15 let bridge;
16 let store;
17 let print;
@@ -23,6 +24,9 @@ describe('StoreStressConcurrent', () => {
24 React = require('react');
25 ReactDOM = require('react-dom');
26 act = require('./utils').act;
27 + // TODO: Figure out recommendation for concurrent mode tests, then replace
28 + // this helper with the real thing.
29 + actAsync = require('./utils').actAsync;
30
31 print = require('./storeSerializer').print;
32 });
@@ -758,7 +762,7 @@ describe('StoreStressConcurrent', () => {
762
763 // Force fallback.
764 expect(print(store)).toEqual(snapshots[i]);
761 - act(() => {
765 + await actAsync(async () => {
766 bridge.send('overrideSuspense', {
767 id: suspenseID,
768 rendererID: store.getRendererIDForElement(suspenseID),
@@ -768,7 +772,7 @@ describe('StoreStressConcurrent', () => {
772 expect(print(store)).toEqual(snapshots[j]);
773
774 // Stop forcing fallback.
771 - act(() => {
775 + await actAsync(async () => {
776 bridge.send('overrideSuspense', {
777 id: suspenseID,
778 rendererID: store.getRendererIDForElement(suspenseID),
@@ -818,7 +822,7 @@ describe('StoreStressConcurrent', () => {
822 expect(print(store)).toEqual(snapshots[j]);
823
824 // Stop forcing fallback. This reverts to primary content.
821 - act(() => {
825 + await actAsync(async () => {
826 bridge.send('overrideSuspense', {
827 id: suspenseID,
828 rendererID: store.getRendererIDForElement(suspenseID),
@@ -829,13 +833,13 @@ describe('StoreStressConcurrent', () => {
833 expect(print(store)).toEqual(snapshots[i]);
834
835 // Clean up after every iteration.
832 - act(() => root.unmount());
836 + await actAsync(async () => root.unmount());
837 expect(print(store)).toBe('');
838 }
839 }
840 });
841
838 - it('should handle a stress test for Suspense without type change (Concurrent Mode)', () => {
842 + it('should handle a stress test for Suspense without type change (Concurrent Mode)', async () => {
843 const A = () => 'a';
844 const B = () => 'b';
845 const C = () => 'c';
@@ -1294,7 +1298,7 @@ describe('StoreStressConcurrent', () => {
1298
1299 // Force fallback.
1300 expect(print(store)).toEqual(snapshots[i]);
1297 - act(() => {
1301 + await actAsync(async () => {
1302 bridge.send('overrideSuspense', {
1303 id: suspenseID,
1304 rendererID: store.getRendererIDForElement(suspenseID),
@@ -1304,7 +1308,7 @@ describe('StoreStressConcurrent', () => {
1308 expect(print(store)).toEqual(fallbackSnapshots[j]);
1309
1310 // Stop forcing fallback.
1307 - act(() => {
1311 + await actAsync(async () => {
1312 bridge.send('overrideSuspense', {
1313 id: suspenseID,
1314 rendererID: store.getRendererIDForElement(suspenseID),
@@ -1354,7 +1358,7 @@ describe('StoreStressConcurrent', () => {
1358 expect(print(store)).toEqual(fallbackSnapshots[j]);
1359
1360 // Stop forcing fallback. This reverts to primary content.
1357 - act(() => {
1361 + await actAsync(async () => {
1362 bridge.send('overrideSuspense', {
1363 id: suspenseID,
1364 rendererID: store.getRendererIDForElement(suspenseID),
packages/react-dom/src/__tests__/ReactDOMNativeEventHeuristic-test.js
+50
@@ -371,4 +371,54 @@ describe('ReactDOMNativeEventHeuristic-test', () => {
371 );
372 }
373 });
374 +
375 + // @gate experimental
376 + it('should not flush discrete events at the end of outermost batchedUpdates', async () => {
377 + const root = ReactDOM.unstable_createRoot(container);
378 +
379 + let target;
380 + function Foo() {
381 + const [count, setCount] = React.useState(0);
382 + return (
383 + <div
384 + ref={el => {
385 + target = el;
386 + if (target !== null) {
387 + el.onclick = () => {
388 + ReactDOM.unstable_batchedUpdates(() => {
389 + setCount(count + 1);
390 + });
391 + Scheduler.unstable_yieldValue(
392 + container.textContent + ' [after batchedUpdates]',
393 + );
394 + };
395 + }
396 + }}>
397 + Count: {count}
398 + </div>
399 + );
400 + }
401 +
402 + await act(async () => {
403 + root.render(<Foo />);
404 + });
405 + expect(container.textContent).toEqual('Count: 0');
406 +
407 + const pressEvent = document.createEvent('Event');
408 + pressEvent.initEvent('click', true, true);
409 + dispatchAndSetCurrentEvent(target, pressEvent);
410 +
411 + expect(Scheduler).toHaveYielded(['Count: 0 [after batchedUpdates]']);
412 + // TODO: There's a `flushDiscreteUpdates` call at the end of the event
413 + // delegation listener that gets called even if no React event handlers are
414 + // fired. Once that is removed, this will be 0, not 1.
415 + // expect(container.textContent).toEqual('Count: 0');
416 + expect(container.textContent).toEqual('Count: 1');
417 +
418 + // Intentionally not using `act` so we can observe in between the click
419 + // event and the microtask, without batching.
420 + await null;
421 +
422 + expect(container.textContent).toEqual('Count: 1');
423 + });
424 });
packages/react-reconciler/src/ReactFiberSyncTaskQueue.new.js
+20 -2
@@ -17,6 +17,7 @@ import {
17 import {ImmediatePriority, scheduleCallback} from './Scheduler';
18
19 let syncQueue: Array<SchedulerCallback> | null = null;
20 +let includesLegacySyncCallbacks: boolean = false;
21 let isFlushingSyncQueue: boolean = false;
22
23 export function scheduleSyncCallback(callback: SchedulerCallback) {
@@ -31,7 +32,23 @@ export function scheduleSyncCallback(callback: SchedulerCallback) {
32 }
33 }
34
34 -export function flushSyncCallbackQueue() {
35 +export function scheduleLegacySyncCallback(callback: SchedulerCallback) {
36 + includesLegacySyncCallbacks = true;
37 + scheduleSyncCallback(callback);
38 +}
39 +
40 +export function flushSyncCallbacksOnlyInLegacyMode() {
41 + // Only flushes the queue if there's a legacy sync callback scheduled.
42 + // TODO: There's only a single type of callback: performSyncOnWorkOnRoot. So
43 + // it might make more sense for the queue to be a list of roots instead of a
44 + // list of generic callbacks. Then we can have two: one for legacy roots, one
45 + // for concurrent roots. And this method would only flush the legacy ones.
46 + if (includesLegacySyncCallbacks) {
47 + flushSyncCallbacks();
48 + }
49 +}
50 +
51 +export function flushSyncCallbacks() {
52 if (!isFlushingSyncQueue && syncQueue !== null) {
53 // Prevent re-entrancy.
54 isFlushingSyncQueue = true;
@@ -50,13 +67,14 @@ export function flushSyncCallbackQueue() {
67 } while (callback !== null);
68 }
69 syncQueue = null;
70 + includesLegacySyncCallbacks = false;
71 } catch (error) {
72 // If something throws, leave the remaining callbacks on the queue.
73 if (syncQueue !== null) {
74 syncQueue = syncQueue.slice(i + 1);
75 }
76 // Resume flushing in the next tick
59 - scheduleCallback(ImmediatePriority, flushSyncCallbackQueue);
77 + scheduleCallback(ImmediatePriority, flushSyncCallbacks);
78 throw error;
79 } finally {
80 setCurrentUpdatePriority(previousUpdatePriority);
packages/react-reconciler/src/ReactFiberSyncTaskQueue.old.js
+20 -2
@@ -17,6 +17,7 @@ import {
17 import {ImmediatePriority, scheduleCallback} from './Scheduler';
18
19 let syncQueue: Array<SchedulerCallback> | null = null;
20 +let includesLegacySyncCallbacks: boolean = false;
21 let isFlushingSyncQueue: boolean = false;
22
23 export function scheduleSyncCallback(callback: SchedulerCallback) {
@@ -31,7 +32,23 @@ export function scheduleSyncCallback(callback: SchedulerCallback) {
32 }
33 }
34
34 -export function flushSyncCallbackQueue() {
35 +export function scheduleLegacySyncCallback(callback: SchedulerCallback) {
36 + includesLegacySyncCallbacks = true;
37 + scheduleSyncCallback(callback);
38 +}
39 +
40 +export function flushSyncCallbacksOnlyInLegacyMode() {
41 + // Only flushes the queue if there's a legacy sync callback scheduled.
42 + // TODO: There's only a single type of callback: performSyncOnWorkOnRoot. So
43 + // it might make more sense for the queue to be a list of roots instead of a
44 + // list of generic callbacks. Then we can have two: one for legacy roots, one
45 + // for concurrent roots. And this method would only flush the legacy ones.
46 + if (includesLegacySyncCallbacks) {
47 + flushSyncCallbacks();
48 + }
49 +}
50 +
51 +export function flushSyncCallbacks() {
52 if (!isFlushingSyncQueue && syncQueue !== null) {
53 // Prevent re-entrancy.
54 isFlushingSyncQueue = true;
@@ -50,13 +67,14 @@ export function flushSyncCallbackQueue() {
67 } while (callback !== null);
68 }
69 syncQueue = null;
70 + includesLegacySyncCallbacks = false;
71 } catch (error) {
72 // If something throws, leave the remaining callbacks on the queue.
73 if (syncQueue !== null) {
74 syncQueue = syncQueue.slice(i + 1);
75 }
76 // Resume flushing in the next tick
59 - scheduleCallback(ImmediatePriority, flushSyncCallbackQueue);
77 + scheduleCallback(ImmediatePriority, flushSyncCallbacks);
78 throw error;
79 } finally {
80 setCurrentUpdatePriority(previousUpdatePriority);
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+31 -19
@@ -49,8 +49,10 @@ import {
49 IdlePriority as IdleSchedulerPriority,
50 } from './Scheduler';
51 import {
52 - flushSyncCallbackQueue,
52 + flushSyncCallbacks,
53 + flushSyncCallbacksOnlyInLegacyMode,
54 scheduleSyncCallback,
55 + scheduleLegacySyncCallback,
56 } from './ReactFiberSyncTaskQueue.new';
57 import {
58 NoFlags as NoHookEffect,
@@ -561,7 +563,7 @@ export function scheduleUpdateOnFiber(
563 // without immediately flushing it. We only do this for user-initiated
564 // updates, to preserve historical behavior of legacy mode.
565 resetRenderTimer();
564 - flushSyncCallbackQueue();
566 + flushSyncCallbacksOnlyInLegacyMode();
567 }
568 }
569 } else {
@@ -698,13 +700,17 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
700 if (newCallbackPriority === SyncLane) {
701 // Special case: Sync React callbacks are scheduled on a special
702 // internal queue
701 - scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
703 + if (root.tag === LegacyRoot) {
704 + scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root));
705 + } else {
706 + scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
707 + }
708 if (supportsMicrotasks) {
709 // Flush the queue in a microtask.
704 - scheduleMicrotask(flushSyncCallbackQueue);
710 + scheduleMicrotask(flushSyncCallbacks);
711 } else {
712 // Flush the queue in an Immediate task.
707 - scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbackQueue);
713 + scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbacks);
714 }
715 newCallbackNode = null;
716 } else {
@@ -1054,7 +1060,7 @@ export function flushRoot(root: FiberRoot, lanes: Lanes) {
1060 ensureRootIsScheduled(root, now());
1061 if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
1062 resetRenderTimer();
1057 - flushSyncCallbackQueue();
1063 + flushSyncCallbacks();
1064 }
1065 }
1066 }
@@ -1085,7 +1091,7 @@ export function flushDiscreteUpdates() {
1091 // like `el.focus()`. Exit.
1092 return;
1093 }
1088 - flushSyncCallbackQueue();
1094 + flushSyncCallbacks();
1095 // If the discrete updates scheduled passive effects, flush them now so that
1096 // they fire before the next serial event.
1097 flushPassiveEffects();
@@ -1111,10 +1117,11 @@ export function batchedUpdates<A, R>(fn: A => R, a: A): R {
1117 return fn(a);
1118 } finally {
1119 executionContext = prevExecutionContext;
1120 + // If there were legacy sync updates, flush them at the end of the outer
1121 + // most batchedUpdates-like method.
1122 if (executionContext === NoContext) {
1115 - // Flush the immediate callbacks that were scheduled during this batch
1123 resetRenderTimer();
1117 - flushSyncCallbackQueue();
1124 + flushSyncCallbacksOnlyInLegacyMode();
1125 }
1126 }
1127 }
@@ -1126,10 +1133,11 @@ export function batchedEventUpdates<A, R>(fn: A => R, a: A): R {
1133 return fn(a);
1134 } finally {
1135 executionContext = prevExecutionContext;
1136 + // If there were legacy sync updates, flush them at the end of the outer
1137 + // most batchedUpdates-like method.
1138 if (executionContext === NoContext) {
1130 - // Flush the immediate callbacks that were scheduled during this batch
1139 resetRenderTimer();
1132 - flushSyncCallbackQueue();
1140 + flushSyncCallbacksOnlyInLegacyMode();
1141 }
1142 }
1143 }
@@ -1151,9 +1159,10 @@ export function discreteUpdates<A, B, C, D, R>(
1159 setCurrentUpdatePriority(previousPriority);
1160 ReactCurrentBatchConfig.transition = prevTransition;
1161 if (executionContext === NoContext) {
1154 - // Flush the immediate callbacks that were scheduled during this batch
1162 resetRenderTimer();
1156 - flushSyncCallbackQueue();
1163 + // TODO: This should only flush legacy sync updates. Not discrete updates
1164 + // in Concurrent Mode. Discrete updates will flush in a microtask.
1165 + flushSyncCallbacks();
1166 }
1167 }
1168 }
@@ -1166,10 +1175,13 @@ export function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
1175 return fn(a);
1176 } finally {
1177 executionContext = prevExecutionContext;
1178 + // If there were legacy sync updates, flush them at the end of the outer
1179 + // most batchedUpdates-like method.
1180 if (executionContext === NoContext) {
1170 - // Flush the immediate callbacks that were scheduled during this batch
1181 resetRenderTimer();
1172 - flushSyncCallbackQueue();
1182 + // TODO: I think this call is redundant, because we flush inside
1183 + // scheduleUpdateOnFiber when LegacyUnbatchedContext is set.
1184 + flushSyncCallbacksOnlyInLegacyMode();
1185 }
1186 }
1187 }
@@ -1196,7 +1208,7 @@ export function flushSync<A, R>(fn: A => R, a: A): R {
1208 // Note that this will happen even if batchedUpdates is higher up
1209 // the stack.
1210 if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
1199 - flushSyncCallbackQueue();
1211 + flushSyncCallbacks();
1212 } else {
1213 if (__DEV__) {
1214 console.error(
@@ -1226,7 +1238,7 @@ export function flushControlled(fn: () => mixed): void {
1238 if (executionContext === NoContext) {
1239 // Flush the immediate callbacks that were scheduled during this batch
1240 resetRenderTimer();
1229 - flushSyncCallbackQueue();
1241 + flushSyncCallbacks();
1242 }
1243 }
1244 }
@@ -2098,7 +2110,7 @@ function commitRootImpl(root, renderPriorityLevel) {
2110 }
2111
2112 // If layout work was scheduled, flush it now.
2101 - flushSyncCallbackQueue();
2113 + flushSyncCallbacks();
2114
2115 if (__DEV__) {
2116 if (enableDebugTracing) {
@@ -2224,7 +2236,7 @@ function flushPassiveEffectsImpl() {
2236
2237 executionContext = prevExecutionContext;
2238
2227 - flushSyncCallbackQueue();
2239 + flushSyncCallbacks();
2240
2241 // If additional passive effects were scheduled, increment a counter. If this
2242 // exceeds the limit, we'll fire a warning.
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+31 -19
@@ -49,8 +49,10 @@ import {
49 IdlePriority as IdleSchedulerPriority,
50 } from './Scheduler';
51 import {
52 - flushSyncCallbackQueue,
52 + flushSyncCallbacks,
53 + flushSyncCallbacksOnlyInLegacyMode,
54 scheduleSyncCallback,
55 + scheduleLegacySyncCallback,
56 } from './ReactFiberSyncTaskQueue.old';
57 import {
58 NoFlags as NoHookEffect,
@@ -561,7 +563,7 @@ export function scheduleUpdateOnFiber(
563 // without immediately flushing it. We only do this for user-initiated
564 // updates, to preserve historical behavior of legacy mode.
565 resetRenderTimer();
564 - flushSyncCallbackQueue();
566 + flushSyncCallbacksOnlyInLegacyMode();
567 }
568 }
569 } else {
@@ -698,13 +700,17 @@ function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
700 if (newCallbackPriority === SyncLane) {
701 // Special case: Sync React callbacks are scheduled on a special
702 // internal queue
701 - scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
703 + if (root.tag === LegacyRoot) {
704 + scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root));
705 + } else {
706 + scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
707 + }
708 if (supportsMicrotasks) {
709 // Flush the queue in a microtask.
704 - scheduleMicrotask(flushSyncCallbackQueue);
710 + scheduleMicrotask(flushSyncCallbacks);
711 } else {
712 // Flush the queue in an Immediate task.
707 - scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbackQueue);
713 + scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbacks);
714 }
715 newCallbackNode = null;
716 } else {
@@ -1054,7 +1060,7 @@ export function flushRoot(root: FiberRoot, lanes: Lanes) {
1060 ensureRootIsScheduled(root, now());
1061 if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
1062 resetRenderTimer();
1057 - flushSyncCallbackQueue();
1063 + flushSyncCallbacks();
1064 }
1065 }
1066 }
@@ -1085,7 +1091,7 @@ export function flushDiscreteUpdates() {
1091 // like `el.focus()`. Exit.
1092 return;
1093 }
1088 - flushSyncCallbackQueue();
1094 + flushSyncCallbacks();
1095 // If the discrete updates scheduled passive effects, flush them now so that
1096 // they fire before the next serial event.
1097 flushPassiveEffects();
@@ -1111,10 +1117,11 @@ export function batchedUpdates<A, R>(fn: A => R, a: A): R {
1117 return fn(a);
1118 } finally {
1119 executionContext = prevExecutionContext;
1120 + // If there were legacy sync updates, flush them at the end of the outer
1121 + // most batchedUpdates-like method.
1122 if (executionContext === NoContext) {
1115 - // Flush the immediate callbacks that were scheduled during this batch
1123 resetRenderTimer();
1117 - flushSyncCallbackQueue();
1124 + flushSyncCallbacksOnlyInLegacyMode();
1125 }
1126 }
1127 }
@@ -1126,10 +1133,11 @@ export function batchedEventUpdates<A, R>(fn: A => R, a: A): R {
1133 return fn(a);
1134 } finally {
1135 executionContext = prevExecutionContext;
1136 + // If there were legacy sync updates, flush them at the end of the outer
1137 + // most batchedUpdates-like method.
1138 if (executionContext === NoContext) {
1130 - // Flush the immediate callbacks that were scheduled during this batch
1139 resetRenderTimer();
1132 - flushSyncCallbackQueue();
1140 + flushSyncCallbacksOnlyInLegacyMode();
1141 }
1142 }
1143 }
@@ -1151,9 +1159,10 @@ export function discreteUpdates<A, B, C, D, R>(
1159 setCurrentUpdatePriority(previousPriority);
1160 ReactCurrentBatchConfig.transition = prevTransition;
1161 if (executionContext === NoContext) {
1154 - // Flush the immediate callbacks that were scheduled during this batch
1162 resetRenderTimer();
1156 - flushSyncCallbackQueue();
1163 + // TODO: This should only flush legacy sync updates. Not discrete updates
1164 + // in Concurrent Mode. Discrete updates will flush in a microtask.
1165 + flushSyncCallbacks();
1166 }
1167 }
1168 }
@@ -1166,10 +1175,13 @@ export function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
1175 return fn(a);
1176 } finally {
1177 executionContext = prevExecutionContext;
1178 + // If there were legacy sync updates, flush them at the end of the outer
1179 + // most batchedUpdates-like method.
1180 if (executionContext === NoContext) {
1170 - // Flush the immediate callbacks that were scheduled during this batch
1181 resetRenderTimer();
1172 - flushSyncCallbackQueue();
1182 + // TODO: I think this call is redundant, because we flush inside
1183 + // scheduleUpdateOnFiber when LegacyUnbatchedContext is set.
1184 + flushSyncCallbacksOnlyInLegacyMode();
1185 }
1186 }
1187 }
@@ -1196,7 +1208,7 @@ export function flushSync<A, R>(fn: A => R, a: A): R {
1208 // Note that this will happen even if batchedUpdates is higher up
1209 // the stack.
1210 if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
1199 - flushSyncCallbackQueue();
1211 + flushSyncCallbacks();
1212 } else {
1213 if (__DEV__) {
1214 console.error(
@@ -1226,7 +1238,7 @@ export function flushControlled(fn: () => mixed): void {
1238 if (executionContext === NoContext) {
1239 // Flush the immediate callbacks that were scheduled during this batch
1240 resetRenderTimer();
1229 - flushSyncCallbackQueue();
1241 + flushSyncCallbacks();
1242 }
1243 }
1244 }
@@ -2098,7 +2110,7 @@ function commitRootImpl(root, renderPriorityLevel) {
2110 }
2111
2112 // If layout work was scheduled, flush it now.
2101 - flushSyncCallbackQueue();
2113 + flushSyncCallbacks();
2114
2115 if (__DEV__) {
2116 if (enableDebugTracing) {
@@ -2224,7 +2236,7 @@ function flushPassiveEffectsImpl() {
2236
2237 executionContext = prevExecutionContext;
2238
2227 - flushSyncCallbackQueue();
2239 + flushSyncCallbacks();
2240
2241 // If additional passive effects were scheduled, increment a counter. If this
2242 // exceeds the limit, we'll fire a warning.
packages/react-reconciler/src/__tests__/ReactFiberHostContext-test.internal.js
+32 -18
@@ -13,16 +13,20 @@
13 let React;
14 let ReactFiberReconciler;
15 let ConcurrentRoot;
16 +let DefaultEventPriority;
17
18 describe('ReactFiberHostContext', () => {
19 beforeEach(() => {
20 jest.resetModules();
21 React = require('react');
22 ReactFiberReconciler = require('react-reconciler');
22 - ConcurrentRoot = require('react-reconciler/src/ReactRootTags');
23 + ConcurrentRoot = require('react-reconciler/src/ReactRootTags')
24 + .ConcurrentRoot;
25 + DefaultEventPriority = require('react-reconciler/src/ReactEventPriorities')
26 + .DefaultEventPriority;
27 });
28
25 - it('works with null host context', () => {
29 + it('works with null host context', async () => {
30 let creates = 0;
31 const Renderer = ReactFiberReconciler({
32 prepareForCommit: function() {
@@ -54,6 +58,9 @@ describe('ReactFiberHostContext', () => {
58 return null;
59 },
60 clearContainer: function() {},
61 + getCurrentEventPriority: function() {
62 + return DefaultEventPriority;
63 + },
64 supportsMutation: true,
65 });
66
@@ -63,14 +70,16 @@ describe('ReactFiberHostContext', () => {
70 false,
71 null,
72 );
66 - Renderer.updateContainer(
67 - <a>
68 - <b />
69 - </a>,
70 - container,
71 - /* parentComponent: */ null,
72 - /* callback: */ null,
73 - );
73 + Renderer.act(() => {
74 + Renderer.updateContainer(
75 + <a>
76 + <b />
77 + </a>,
78 + container,
79 + /* parentComponent: */ null,
80 + /* callback: */ null,
81 + );
82 + });
83 expect(creates).toBe(2);
84 });
85
@@ -109,6 +118,9 @@ describe('ReactFiberHostContext', () => {
118 return null;
119 },
120 clearContainer: function() {},
121 + getCurrentEventPriority: function() {
122 + return DefaultEventPriority;
123 + },
124 supportsMutation: true,
125 });
126
@@ -118,13 +130,15 @@ describe('ReactFiberHostContext', () => {
130 false,
131 null,
132 );
121 - Renderer.updateContainer(
122 - <a>
123 - <b />
124 - </a>,
125 - container,
126 - /* parentComponent: */ null,
127 - /* callback: */ null,
128 - );
133 + Renderer.act(() => {
134 + Renderer.updateContainer(
135 + <a>
136 + <b />
137 + </a>,
138 + container,
139 + /* parentComponent: */ null,
140 + /* callback: */ null,
141 + );
142 + });
143 });
144 });
packages/react-reconciler/src/__tests__/ReactIncrementalTriangle-test.js
+2 -3
@@ -432,9 +432,8 @@ describe('ReactIncrementalTriangle', () => {
432 assertConsistentTree(activeLeafIndices);
433 }
434 // Flush remaining work
435 - ReactNoop.act(() => {
436 - Scheduler.unstable_flushAllWithoutAsserting();
437 - });
435 + Scheduler.unstable_flushAllWithoutAsserting();
436 + ReactNoop.flushSync();
437 assertConsistentTree(activeLeafIndices, expectedCounterAtEnd);
438 }
439