@@ -1418,7 +1412,7 @@ describe('ReactOffscreen', () => {
await act(async () => {
root.render(
);
});
- expect(Scheduler).toHaveYielded(['Outer', 'Inner']);
+ assertLog(['Outer', 'Inner']);
// Both the inner and the outer tree should be hidden. Hiding the inner tree
// is arguably redundant, but the advantage of hiding both is that later you
// can reveal the outer tree without having to examine the inner one.
@@ -1435,7 +1429,7 @@ describe('ReactOffscreen', () => {
await act(async () => {
root.render(
);
});
- expect(Scheduler).toHaveYielded(['Mount Outer']);
+ assertLog(['Mount Outer']);
expect(root).toMatchRenderedOutput(
@@ -1535,7 +1529,7 @@ describe('ReactOffscreen', () => {
root.render(
);
});
- expect(Scheduler).toHaveYielded(['HighPriorityComponent 0', 'Child 0']);
+ assertLog(['HighPriorityComponent 0', 'Child 0']);
expect(root).toMatchRenderedOutput(
<>
@@ -1549,10 +1543,7 @@ describe('ReactOffscreen', () => {
await act(async () => {
updateChildState(1);
updateHighPriorityComponentState(1);
- expect(Scheduler).toFlushUntilNextPaint([
- 'HighPriorityComponent 1',
- 'Child 1',
- ]);
+ await waitForPaint(['HighPriorityComponent 1', 'Child 1']);
expect(root).toMatchRenderedOutput(
<>
@@ -1569,7 +1560,7 @@ describe('ReactOffscreen', () => {
await act(async () => {
updateChildState(2);
updateHighPriorityComponentState(2);
- expect(Scheduler).toFlushUntilNextPaint(['HighPriorityComponent 2']);
+ await waitForPaint(['HighPriorityComponent 2']);
expect(root).toMatchRenderedOutput(
<>
@@ -1578,7 +1569,7 @@ describe('ReactOffscreen', () => {
);
});
- expect(Scheduler).toHaveYielded(['Child 2']);
+ assertLog(['Child 2']);
expect(root).toMatchRenderedOutput(
<>
@@ -1594,10 +1585,7 @@ describe('ReactOffscreen', () => {
await act(async () => {
updateChildState(3);
updateHighPriorityComponentState(3);
- expect(Scheduler).toFlushUntilNextPaint([
- 'HighPriorityComponent 3',
- 'Child 3',
- ]);
+ await waitForPaint(['HighPriorityComponent 3', 'Child 3']);
expect(root).toMatchRenderedOutput(
<>
@@ -1666,7 +1654,7 @@ describe('ReactOffscreen', () => {
root.render(
);
});
- expect(Scheduler).toHaveYielded(['HighPriorityComponent 0', 'Child 0']);
+ assertLog(['HighPriorityComponent 0', 'Child 0']);
nextRenderTriggerDetach = true;
@@ -1675,7 +1663,7 @@ describe('ReactOffscreen', () => {
await act(async () => {
updateChildState(1);
updateHighPriorityComponentState(1);
- expect(Scheduler).toFlushUntilNextPaint([
+ await waitForPaint([
'HighPriorityComponent 1',
'Child 1',
'HighPriorityComponent 2',
@@ -1688,7 +1676,7 @@ describe('ReactOffscreen', () => {
);
});
- expect(Scheduler).toHaveYielded(['Child 2']);
+ assertLog(['Child 2']);
expect(root).toMatchRenderedOutput(
<>
@@ -1703,10 +1691,7 @@ describe('ReactOffscreen', () => {
await act(async () => {
updateChildState(3);
updateHighPriorityComponentState(3);
- expect(Scheduler).toFlushUntilNextPaint([
- 'HighPriorityComponent 3',
- 'Child 3',
- ]);
+ await waitForPaint(['HighPriorityComponent 3', 'Child 3']);
expect(root).toMatchRenderedOutput(
<>
@@ -1865,33 +1850,33 @@ describe('ReactOffscreen', () => {
expect(offscreenRef).not.toBeNull();
expect(spanRef.current).not.toBeNull();
- expect(Scheduler).toHaveYielded(['Mount Layout Child', 'Mount Child']);
+ assertLog(['Mount Layout Child', 'Mount Child']);
await act(async () => {
offscreenRef.detach();
});
expect(spanRef.current).toBeNull();
- expect(Scheduler).toHaveYielded(['Unmount Layout Child', 'Unmount Child']);
+ assertLog(['Unmount Layout Child', 'Unmount Child']);
// Calling attach on already attached Offscreen.
await act(async () => {
offscreenRef.detach();
});
- expect(Scheduler).toHaveYielded([]);
+ assertLog([]);
await act(async () => {
offscreenRef.attach();
});
expect(spanRef.current).not.toBeNull();
- expect(Scheduler).toHaveYielded(['Mount Layout Child', 'Mount Child']);
+ assertLog(['Mount Layout Child', 'Mount Child']);
// Calling attach on already attached Offscreen
offscreenRef.attach();
- expect(Scheduler).toHaveYielded([]);
+ assertLog([]);
});
// @gate enableOffscreen
@@ -1919,7 +1904,7 @@ describe('ReactOffscreen', () => {
root.render(
);
});
- expect(Scheduler).toHaveYielded([
+ assertLog([
'outer',
'middle',
'inner',
@@ -1940,7 +1925,7 @@ describe('ReactOffscreen', () => {
expect(innerOffscreen).toBeNull();
- expect(Scheduler).toHaveYielded([
+ assertLog([
'unmount layout middle',
'unmount layout inner',
'unmount middle',
@@ -1951,7 +1936,7 @@ describe('ReactOffscreen', () => {
outerOffscreen.attach();
});
- expect(Scheduler).toHaveYielded([
+ assertLog([
'mount layout inner',
'mount layout middle',
'mount inner',
@@ -1962,27 +1947,27 @@ describe('ReactOffscreen', () => {
innerOffscreen.detach();
});
- expect(Scheduler).toHaveYielded(['unmount layout inner', 'unmount inner']);
+ assertLog(['unmount layout inner', 'unmount inner']);
// Calling detach on already detached Offscreen.
await act(async () => {
innerOffscreen.detach();
});
- expect(Scheduler).toHaveYielded([]);
+ assertLog([]);
await act(async () => {
innerOffscreen.attach();
});
- expect(Scheduler).toHaveYielded(['mount layout inner', 'mount inner']);
+ assertLog(['mount layout inner', 'mount inner']);
await act(async () => {
innerOffscreen.detach();
outerOffscreen.attach();
});
- expect(Scheduler).toHaveYielded(['unmount layout inner', 'unmount inner']);
+ assertLog(['unmount layout inner', 'unmount inner']);
});
// @gate enableOffscreen
@@ -2011,7 +1996,7 @@ describe('ReactOffscreen', () => {
root.render(
);
});
- expect(Scheduler).toHaveYielded(['attach child']);
+ assertLog(['attach child']);
await act(async () => {
const instance = offscreen.current;
@@ -2020,14 +2005,14 @@ describe('ReactOffscreen', () => {
instance.attach();
});
- expect(Scheduler).toHaveYielded([]);
+ assertLog([]);
await act(async () => {
const instance = offscreen.current;
instance.detach();
});
- expect(Scheduler).toHaveYielded(['detach child']);
+ assertLog(['detach child']);
await act(async () => {
const instance = offscreen.current;
@@ -2036,7 +2021,7 @@ describe('ReactOffscreen', () => {
instance.detach();
});
- expect(Scheduler).toHaveYielded([]);
+ assertLog([]);
});
// @gate enableOffscreen
@@ -2070,6 +2055,6 @@ describe('ReactOffscreen', () => {
await act(() => {
root.render(
);
});
- expect(Scheduler).toHaveYielded(['attach child']);
+ assertLog(['attach child']);
});
});
diff --git a/packages/react-reconciler/src/__tests__/ReactOffscreenSuspense-test.js b/packages/react-reconciler/src/__tests__/ReactOffscreenSuspense-test.js
index 272979fab6444..8ad9673f562d3 100644
--- a/packages/react-reconciler/src/__tests__/ReactOffscreenSuspense-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactOffscreenSuspense-test.js
@@ -9,6 +9,8 @@ let useState;
let useEffect;
let startTransition;
let textCache;
+let waitForPaint;
+let assertLog;
describe('ReactOffscreen', () => {
beforeEach(() => {
@@ -25,6 +27,10 @@ describe('ReactOffscreen', () => {
useEffect = React.useEffect;
startTransition = React.startTransition;
+ const InternalTestUtils = require('internal-test-utils');
+ waitForPaint = InternalTestUtils.waitForPaint;
+ assertLog = InternalTestUtils.assertLog;
+
textCache = new Map();
});
@@ -115,7 +121,7 @@ describe('ReactOffscreen', () => {
await act(async () => {
root.render(
);
});
- expect(Scheduler).toHaveYielded(['Visible', 'Suspend! [Hidden]']);
+ assertLog(['Visible', 'Suspend! [Hidden]']);
expect(root).toMatchRenderedOutput(
Visible);
// When the data resolves, we should be able to finish prerendering
@@ -123,7 +129,7 @@ describe('ReactOffscreen', () => {
await act(async () => {
await resolveText('Hidden');
});
- expect(Scheduler).toHaveYielded(['Hidden']);
+ assertLog(['Hidden']);
expect(root).toMatchRenderedOutput(
<>
Visible
@@ -155,11 +161,7 @@ describe('ReactOffscreen', () => {
await act(async () => {
root.render(
);
});
- expect(Scheduler).toHaveYielded([
- 'Visible',
- 'Suspend! [Hidden]',
- 'Loading...',
- ]);
+ assertLog(['Visible', 'Suspend! [Hidden]', 'Loading...']);
// Nearest Suspense boundary switches to a fallback even though the
// suspended content is hidden.
expect(root).toMatchRenderedOutput(
@@ -197,7 +199,7 @@ describe('ReactOffscreen', () => {
,
);
});
- expect(Scheduler).toHaveYielded(['Closed', 'Suspend! [Async]']);
+ assertLog(['Closed', 'Suspend! [Async]']);
expect(root).toMatchRenderedOutput(
Closed);
// But when we switch the boundary from hidden to visible, it should
@@ -211,7 +213,7 @@ describe('ReactOffscreen', () => {
);
});
});
- expect(Scheduler).toHaveYielded(['Open', 'Suspend! [Async]', 'Loading...']);
+ assertLog(['Open', 'Suspend! [Async]', 'Loading...']);
// It should suspend with delay to prevent the already-visible Suspense
// boundary from switching to a fallback
expect(root).toMatchRenderedOutput(
Closed);
@@ -220,7 +222,7 @@ describe('ReactOffscreen', () => {
await act(async () => {
await resolveText('Async');
});
- expect(Scheduler).toHaveYielded(['Open', 'Async']);
+ assertLog(['Open', 'Async']);
expect(root).toMatchRenderedOutput(
<>
Open
@@ -254,7 +256,7 @@ describe('ReactOffscreen', () => {
,
);
});
- expect(Scheduler).toHaveYielded(['Open', '(empty)']);
+ assertLog(['Open', '(empty)']);
expect(root).toMatchRenderedOutput(
<>
Open
@@ -272,7 +274,7 @@ describe('ReactOffscreen', () => {
);
});
});
- expect(Scheduler).toHaveYielded(['Open', 'Suspend! [Async]', 'Loading...']);
+ assertLog(['Open', 'Suspend! [Async]', 'Loading...']);
// It should suspend with delay to prevent the already-visible Suspense
// boundary from switching to a fallback
expect(root).toMatchRenderedOutput(
@@ -294,7 +296,7 @@ describe('ReactOffscreen', () => {
});
// Now the visible part of the tree can commit without being blocked
// by the suspended content, which is hidden.
- expect(Scheduler).toHaveYielded(['Closed', 'Suspend! [Async]']);
+ assertLog(['Closed', 'Suspend! [Async]']);
expect(root).toMatchRenderedOutput(
<>
Closed
@@ -306,7 +308,7 @@ describe('ReactOffscreen', () => {
await act(async () => {
await resolveText('Async');
});
- expect(Scheduler).toHaveYielded(['Async']);
+ assertLog(['Async']);
expect(root).toMatchRenderedOutput(
<>
Closed
@@ -339,7 +341,7 @@ describe('ReactOffscreen', () => {
await act(async () => {
root.render(
);
});
- expect(Scheduler).toHaveYielded(['A']);
+ assertLog(['A']);
await act(async () => {
startTransition(() => {
@@ -377,7 +379,7 @@ describe('ReactOffscreen', () => {
await act(async () => {
root.render(
);
});
- expect(Scheduler).toHaveYielded(['A0']);
+ assertLog(['A0']);
expect(root).toMatchRenderedOutput(
A0);
await act(async () => {
@@ -388,7 +390,7 @@ describe('ReactOffscreen', () => {
setText('B');
});
});
- expect(Scheduler).toHaveYielded([
+ assertLog([
// The high priority render suspends again
'Suspend! [B0]',
// There's still pending work in another lane, so we should attempt
@@ -401,7 +403,7 @@ describe('ReactOffscreen', () => {
await act(async () => {
resolveText('B1');
});
- expect(Scheduler).toHaveYielded(['B1']);
+ assertLog(['B1']);
expect(root).toMatchRenderedOutput(
B1);
});
@@ -465,7 +467,7 @@ describe('ReactOffscreen', () => {
await act(async () => {
root.render(
);
});
- expect(Scheduler).toHaveYielded([
+ assertLog([
'Outer: 0',
'Inner: 0',
'Async: 0',
@@ -487,7 +489,7 @@ describe('ReactOffscreen', () => {
// In the same render, also hide the offscreen tree.
root.render(
);
- expect(Scheduler).toFlushUntilNextPaint([
+ await waitForPaint([
// The outer update will commit, but the inner update is deferred until
// a later render.
'Outer: 1',
@@ -535,7 +537,7 @@ describe('ReactOffscreen', () => {
ReactNoop.flushSync(() => {
root.render(
);
});
- expect(Scheduler).toHaveYielded([
+ assertLog([
'Outer: 1',
// There are two pending updates on Inner, but only the first one
@@ -549,7 +551,7 @@ describe('ReactOffscreen', () => {
'Inner and outer are consistent',
]);
});
- expect(Scheduler).toHaveYielded([
+ assertLog([
'Outer: 2',
'Inner: 2',
'Suspend! [Async: 2]',
diff --git a/packages/react-reconciler/src/__tests__/ReactPersistent-test.js b/packages/react-reconciler/src/__tests__/ReactPersistent-test.js
index f53f45e30d68c..7900ccdadd451 100644
--- a/packages/react-reconciler/src/__tests__/ReactPersistent-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactPersistent-test.js
@@ -12,7 +12,7 @@
let React;
let ReactNoopPersistent;
-let Scheduler;
+let waitForAll;
describe('ReactPersistent', () => {
beforeEach(() => {
@@ -20,7 +20,8 @@ describe('ReactPersistent', () => {
React = require('react');
ReactNoopPersistent = require('react-noop-renderer/persistent');
- Scheduler = require('scheduler');
+ const InternalTestUtils = require('internal-test-utils');
+ waitForAll = InternalTestUtils.waitForAll;
});
// Inlined from shared folder so we can run this test on a bundle.
@@ -57,7 +58,7 @@ describe('ReactPersistent', () => {
return ReactNoopPersistent.dangerouslyGetChildren();
}
- it('can update child nodes of a host instance', () => {
+ it('can update child nodes of a host instance', async () => {
function Bar(props) {
return
{props.text};
}
@@ -72,19 +73,19 @@ describe('ReactPersistent', () => {
}
render(
);
- expect(Scheduler).toFlushWithoutYielding();
+ await waitForAll([]);
const originalChildren = dangerouslyGetChildren();
expect(originalChildren).toEqual([div(span())]);
render(
);
- expect(Scheduler).toFlushWithoutYielding();
+ await waitForAll([]);
const newChildren = dangerouslyGetChildren();
expect(newChildren).toEqual([div(span(), span())]);
expect(originalChildren).toEqual([div(span())]);
});
- it('can reuse child nodes between updates', () => {
+ it('can reuse child nodes between updates', async () => {
function Baz(props) {
return
;
}
@@ -106,12 +107,12 @@ describe('ReactPersistent', () => {
}
render(
);
- expect(Scheduler).toFlushWithoutYielding();
+ await waitForAll([]);
const originalChildren = dangerouslyGetChildren();
expect(originalChildren).toEqual([div(span('Hello'))]);
render(
);
- expect(Scheduler).toFlushWithoutYielding();
+ await waitForAll([]);
const newChildren = dangerouslyGetChildren();
expect(newChildren).toEqual([div(span('Hello'), span('World'))]);
@@ -121,7 +122,7 @@ describe('ReactPersistent', () => {
expect(newChildren[0].children[0]).toBe(originalChildren[0].children[0]);
});
- it('can update child text nodes', () => {
+ it('can update child text nodes', async () => {
function Foo(props) {
return (
@@ -132,19 +133,19 @@ describe('ReactPersistent', () => {
}
render(
);
- expect(Scheduler).toFlushWithoutYielding();
+ await waitForAll([]);
const originalChildren = dangerouslyGetChildren();
expect(originalChildren).toEqual([div('Hello', span())]);
render(
);
- expect(Scheduler).toFlushWithoutYielding();
+ await waitForAll([]);
const newChildren = dangerouslyGetChildren();
expect(newChildren).toEqual([div('World', span())]);
expect(originalChildren).toEqual([div('Hello', span())]);
});
- it('supports portals', () => {
+ it('supports portals', async () => {
function Parent(props) {
return
{props.children}
;
}
@@ -173,7 +174,7 @@ describe('ReactPersistent', () => {
const portalContainer = {rootID: 'persistent-portal-test', children: []};
const emptyPortalChildSet = portalContainer.children;
render(
{createPortal(, portalContainer, null)});
- expect(Scheduler).toFlushWithoutYielding();
+ await waitForAll([]);
expect(emptyPortalChildSet).toEqual([]);
@@ -187,7 +188,7 @@ describe('ReactPersistent', () => {
{createPortal(
Hello {'World'}, portalContainer, null)}
,
);
- expect(Scheduler).toFlushWithoutYielding();
+ await waitForAll([]);
const newChildren = dangerouslyGetChildren();
expect(newChildren).toEqual([div()]);
@@ -204,7 +205,7 @@ describe('ReactPersistent', () => {
// Deleting the Portal, should clear its children
render(
);
- expect(Scheduler).toFlushWithoutYielding();
+ await waitForAll([]);
const clearedPortalChildren = portalContainer.children;
expect(clearedPortalChildren).toEqual([]);
diff --git a/packages/react-reconciler/src/__tests__/ReactSchedulerIntegration-test.js b/packages/react-reconciler/src/__tests__/ReactSchedulerIntegration-test.js
index 8db1a121d8f27..1dffc1be85839 100644
--- a/packages/react-reconciler/src/__tests__/ReactSchedulerIntegration-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactSchedulerIntegration-test.js
@@ -18,6 +18,10 @@ let NormalPriority;
let IdlePriority;
let runWithPriority;
let startTransition;
+let waitForAll;
+let waitForPaint;
+let assertLog;
+let waitFor;
describe('ReactSchedulerIntegration', () => {
beforeEach(() => {
@@ -31,6 +35,12 @@ describe('ReactSchedulerIntegration', () => {
IdlePriority = Scheduler.unstable_IdlePriority;
runWithPriority = Scheduler.unstable_runWithPriority;
startTransition = React.startTransition;
+
+ const InternalTestUtils = require('internal-test-utils');
+ waitForAll = InternalTestUtils.waitForAll;
+ waitForPaint = InternalTestUtils.waitForPaint;
+ assertLog = InternalTestUtils.assertLog;
+ waitFor = InternalTestUtils.waitFor;
});
// Note: This is based on a similar component we use in www. We can delete
@@ -76,11 +86,11 @@ describe('ReactSchedulerIntegration', () => {
await act(async () => {
ReactNoop.render(
);
});
- expect(Scheduler).toHaveYielded([]);
+ assertLog([]);
await act(async () => {
ReactNoop.render(
);
});
- expect(Scheduler).toHaveYielded([
+ assertLog([
'Cleanup Layout Effect',
'Layout Effect',
'Passive Effect',
@@ -90,12 +100,12 @@ describe('ReactSchedulerIntegration', () => {
]);
});
- it('requests a paint after committing', () => {
+ it('requests a paint after committing', async () => {
const scheduleCallback = Scheduler.unstable_scheduleCallback;
const root = ReactNoop.createRoot();
root.render('Initial');
- Scheduler.unstable_flushAll();
+ await waitForAll([]);
scheduleCallback(NormalPriority, () => Scheduler.unstable_yieldValue('A'));
scheduleCallback(NormalPriority, () => Scheduler.unstable_yieldValue('B'));
@@ -115,7 +125,7 @@ describe('ReactSchedulerIntegration', () => {
// Flush everything up to the next paint. Should yield after the
// React commit.
Scheduler.unstable_flushUntilNextPaint();
- expect(Scheduler).toHaveYielded(['A', 'B', 'C']);
+ assertLog(['A', 'B', 'C']);
});
// @gate www
@@ -141,7 +151,7 @@ describe('ReactSchedulerIntegration', () => {
root.render(
);
// Commit the visible content
- expect(Scheduler).toFlushUntilNextPaint(['Visible: A']);
+ await waitForPaint(['Visible: A']);
expect(root).toMatchRenderedOutput(
<>
Visible: A
@@ -156,7 +166,7 @@ describe('ReactSchedulerIntegration', () => {
});
// The next commit should only include the visible content
- expect(Scheduler).toFlushUntilNextPaint(['Visible: B']);
+ await waitForPaint(['Visible: B']);
expect(root).toMatchRenderedOutput(
<>
Visible: B
@@ -166,7 +176,7 @@ describe('ReactSchedulerIntegration', () => {
});
// The hidden content commits later
- expect(Scheduler).toHaveYielded(['Hidden: B']);
+ assertLog(['Hidden: B']);
expect(root).toMatchRenderedOutput(
<>
Visible: B
Hidden: B
@@ -201,6 +211,12 @@ describe(
ReactNoop = require('react-noop-renderer');
Scheduler = require('scheduler');
startTransition = React.startTransition;
+
+ const InternalTestUtils = require('internal-test-utils');
+ waitForAll = InternalTestUtils.waitForAll;
+ waitForPaint = InternalTestUtils.waitForPaint;
+ assertLog = InternalTestUtils.assertLog;
+ waitFor = InternalTestUtils.waitFor;
});
afterEach(() => {
@@ -244,8 +260,8 @@ describe(
await act(async () => {
ReactNoop.render(
);
- expect(Scheduler).toFlushUntilNextPaint([]);
- expect(Scheduler).toFlushUntilNextPaint([]);
+ await waitForPaint([]);
+ await waitForPaint([]);
});
});
@@ -276,13 +292,13 @@ describe(
startTransition(() => {
ReactNoop.render(
);
});
- expect(Scheduler).toFlushAndYieldThrough(['A']);
+ await waitFor(['A']);
// Start logging whenever shouldYield is called
logDuringShouldYield = true;
// Let's call it once to confirm the mock actually works
Scheduler.unstable_shouldYield();
- expect(Scheduler).toHaveYielded(['shouldYield']);
+ assertLog(['shouldYield']);
// Expire the task
Scheduler.unstable_advanceTime(10000);
@@ -299,7 +315,7 @@ describe(
// Because the render expired, React should finish the tree without
// consulting `shouldYield` again
Scheduler.unstable_flushNumberOfYields(1);
- expect(Scheduler).toHaveYielded(['B', 'C']);
+ assertLog(['B', 'C']);
});
});
},
diff --git a/packages/react-reconciler/src/__tests__/ReactSubtreeFlagsWarning-test.js b/packages/react-reconciler/src/__tests__/ReactSubtreeFlagsWarning-test.js
index 8dd183fd4e42f..b5f8f1ad4a6f8 100644
--- a/packages/react-reconciler/src/__tests__/ReactSubtreeFlagsWarning-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactSubtreeFlagsWarning-test.js
@@ -8,6 +8,7 @@ let getCacheForType;
let caches;
let seededCache;
+let assertLog;
describe('ReactSuspenseWithNoopRenderer', () => {
beforeEach(() => {
@@ -22,6 +23,9 @@ describe('ReactSuspenseWithNoopRenderer', () => {
getCacheForType = React.unstable_getCacheForType;
+ const InternalTestUtils = require('internal-test-utils');
+ assertLog = InternalTestUtils.assertLog;
+
caches = [];
seededCache = null;
});
@@ -160,7 +164,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
await act(async () => {
root.render(
);
});
- expect(Scheduler).toHaveYielded(['Suspend! [Async]']);
+ assertLog(['Suspend! [Async]']);
expect(root).toMatchRenderedOutput('Loading...');
// When the promise resolves, a passive static effect flag is added. In the
@@ -169,7 +173,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
await act(async () => {
resolveText('Async');
});
- expect(Scheduler).toHaveYielded(['Async', 'Effect']);
+ assertLog(['Async', 'Effect']);
expect(root).toMatchRenderedOutput('Async');
});
});
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
index 9c4ba7794b1fa..e5045fead43cc 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
@@ -9,6 +9,11 @@ let act;
let TextResource;
let textResourceShouldFail;
+let assertLog;
+let waitForPaint;
+let waitForAll;
+let waitFor;
+
describe('ReactSuspense', () => {
beforeEach(() => {
jest.resetModules();
@@ -23,6 +28,12 @@ describe('ReactSuspense', () => {
Suspense = React.Suspense;
+ const InternalTestUtils = require('internal-test-utils');
+ waitForAll = InternalTestUtils.waitForAll;
+ waitForPaint = InternalTestUtils.waitForPaint;
+ assertLog = InternalTestUtils.assertLog;
+ waitFor = InternalTestUtils.waitFor;
+
TextResource = ReactCache.unstable_createResource(
([text, ms = 0]) => {
let listeners = null;
@@ -94,7 +105,7 @@ describe('ReactSuspense', () => {
}
}
- it('suspends rendering and continues later', () => {
+ it('suspends rendering and continues later', async () => {
function Bar(props) {
Scheduler.unstable_yieldValue('Bar');
return props.children;
@@ -119,7 +130,7 @@ describe('ReactSuspense', () => {
unstable_isConcurrent: true,
});
- expect(Scheduler).toFlushAndYield(['Foo']);
+ await waitForAll(['Foo']);
expect(root).toMatchRenderedOutput(null);
// Navigate the shell to now render the child content.
@@ -128,7 +139,7 @@ describe('ReactSuspense', () => {
root.update(
);
});
- expect(Scheduler).toFlushAndYield([
+ await waitForAll([
'Foo',
'Bar',
// A suspends
@@ -142,18 +153,18 @@ describe('ReactSuspense', () => {
// Flush some of the time
jest.advanceTimersByTime(50);
// Still nothing...
- expect(Scheduler).toFlushWithoutYielding();
+ await waitForAll([]);
expect(root).toMatchRenderedOutput(null);
// Flush the promise completely
jest.advanceTimersByTime(50);
// Renders successfully
- expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
- expect(Scheduler).toFlushAndYield(['Foo', 'Bar', 'A', 'B']);
+ assertLog(['Promise resolved [A]']);
+ await waitForAll(['Foo', 'Bar', 'A', 'B']);
expect(root).toMatchRenderedOutput('AB');
});
- it('suspends siblings and later recovers each independently', () => {
+ it('suspends siblings and later recovers each independently', async () => {
// Render two sibling Suspense components
const root = ReactTestRenderer.create(
<>
@@ -169,7 +180,7 @@ describe('ReactSuspense', () => {
},
);
- expect(Scheduler).toFlushAndYield([
+ await waitForAll([
'Suspend! [A]',
'Loading A...',
'Suspend! [B]',
@@ -182,19 +193,19 @@ describe('ReactSuspense', () => {
// show the placeholder
jest.advanceTimersByTime(5000);
// TODO: Should we throw if you forget to call toHaveYielded?
- expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
- expect(Scheduler).toFlushAndYield(['A']);
+ assertLog(['Promise resolved [A]']);
+ await waitForAll(['A']);
expect(root).toMatchRenderedOutput('ALoading B...');
// Advance time by enough that the second Suspense's promise resolves
// and switches back to the normal view
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
- expect(Scheduler).toFlushAndYield(['B']);
+ assertLog(['Promise resolved [B]']);
+ await waitForAll(['B']);
expect(root).toMatchRenderedOutput('AB');
});
- it('interrupts current render if promise resolves before current render phase', () => {
+ it('interrupts current render if promise resolves before current render phase', async () => {
let didResolve = false;
const listeners = [];
@@ -231,7 +242,7 @@ describe('ReactSuspense', () => {
unstable_isConcurrent: true,
},
);
- expect(Scheduler).toFlushAndYield(['Initial']);
+ await waitForAll(['Initial']);
expect(root).toMatchRenderedOutput('Initial');
// The update will suspend.
@@ -247,19 +258,15 @@ describe('ReactSuspense', () => {
);
});
// Yield past the Suspense boundary but don't complete the last sibling.
- expect(Scheduler).toFlushAndYieldThrough([
- 'Suspend!',
- 'Loading...',
- 'After Suspense',
- ]);
+ await waitFor(['Suspend!', 'Loading...', 'After Suspense']);
// The promise resolves before the current render phase has completed
resolveThenable();
- expect(Scheduler).toHaveYielded([]);
+ assertLog([]);
expect(root).toMatchRenderedOutput('Initial');
// Start over from the root, instead of continuing.
- expect(Scheduler).toFlushAndYield([
+ await waitForAll([
// Async renders again *before* Sibling
'Async',
'After Suspense',
@@ -268,7 +275,7 @@ describe('ReactSuspense', () => {
expect(root).toMatchRenderedOutput('AsyncAfter SuspenseSibling');
});
- it('throttles fallback committing globally', () => {
+ it('throttles fallback committing globally', async () => {
function Foo() {
Scheduler.unstable_yieldValue('Foo');
return (
@@ -290,7 +297,7 @@ describe('ReactSuspense', () => {
unstable_isConcurrent: true,
});
- expect(Scheduler).toFlushAndYield([
+ await waitForAll([
'Foo',
'Suspend! [A]',
'Suspend! [B]',
@@ -302,8 +309,8 @@ describe('ReactSuspense', () => {
// Resolve A.
jest.advanceTimersByTime(200);
Scheduler.unstable_advanceTime(200);
- expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
- expect(Scheduler).toFlushAndYield(['A', 'Suspend! [B]', 'Loading more...']);
+ assertLog(['Promise resolved [A]']);
+ await waitForAll(['A', 'Suspend! [B]', 'Loading more...']);
// By this point, we have enough info to show "A" and "Loading more..."
// However, we've just shown the outer fallback. So we'll delay
@@ -313,17 +320,17 @@ describe('ReactSuspense', () => {
// Resolve B.
jest.advanceTimersByTime(100);
Scheduler.unstable_advanceTime(100);
- expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
+ assertLog(['Promise resolved [B]']);
// By this point, B has resolved.
// We're still showing the outer fallback.
expect(root).toMatchRenderedOutput('Loading...');
- expect(Scheduler).toFlushAndYield(['A', 'B']);
+ await waitForAll(['A', 'B']);
// Then contents of both should pop in together.
expect(root).toMatchRenderedOutput('AB');
});
- it('does not throttle fallback committing for too long', () => {
+ it('does not throttle fallback committing for too long', async () => {
function Foo() {
Scheduler.unstable_yieldValue('Foo');
return (
@@ -345,7 +352,7 @@ describe('ReactSuspense', () => {
unstable_isConcurrent: true,
});
- expect(Scheduler).toFlushAndYield([
+ await waitForAll([
'Foo',
'Suspend! [A]',
'Suspend! [B]',
@@ -357,8 +364,8 @@ describe('ReactSuspense', () => {
// Resolve A.
jest.advanceTimersByTime(200);
Scheduler.unstable_advanceTime(200);
- expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
- expect(Scheduler).toFlushAndYield(['A', 'Suspend! [B]', 'Loading more...']);
+ assertLog(['Promise resolved [A]']);
+ await waitForAll(['A', 'Suspend! [B]', 'Loading more...']);
// By this point, we have enough info to show "A" and "Loading more..."
// However, we've just shown the outer fallback. So we'll delay
@@ -374,8 +381,8 @@ describe('ReactSuspense', () => {
// Resolve B.
jest.advanceTimersByTime(500);
Scheduler.unstable_advanceTime(500);
- expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
- expect(Scheduler).toFlushAndYield(['B']);
+ assertLog(['Promise resolved [B]']);
+ await waitForAll(['B']);
expect(root).toMatchRenderedOutput('AB');
});
@@ -404,16 +411,16 @@ describe('ReactSuspense', () => {
,
);
- expect(Scheduler).toHaveYielded(['Loading...']);
+ assertLog(['Loading...']);
expect(root).toMatchRenderedOutput('Loading...');
await LazyClass;
- expect(Scheduler).toFlushUntilNextPaint(['Hi', 'Did mount: Hi']);
+ await waitForPaint(['Hi', 'Did mount: Hi']);
expect(root).toMatchRenderedOutput('Hi');
});
- it('updates memoized child of suspense component when context updates (simple memo)', () => {
+ it('updates memoized child of suspense component when context updates (simple memo)', async () => {
const {useContext, createContext, useState, memo} = React;
const ValueContext = createContext(null);
@@ -451,23 +458,23 @@ describe('ReactSuspense', () => {
const root = ReactTestRenderer.create(
, {
unstable_isConcurrent: true,
});
- expect(Scheduler).toFlushAndYield(['Suspend! [default]', 'Loading...']);
+ await waitForAll(['Suspend! [default]', 'Loading...']);
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [default]']);
- expect(Scheduler).toFlushAndYield(['default']);
+ assertLog(['Promise resolved [default]']);
+ await waitForAll(['default']);
expect(root).toMatchRenderedOutput('default');
act(() => setValue('new value'));
- expect(Scheduler).toHaveYielded(['Suspend! [new value]', 'Loading...']);
+ assertLog(['Suspend! [new value]', 'Loading...']);
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [new value]']);
- expect(Scheduler).toFlushAndYield(['new value']);
+ assertLog(['Promise resolved [new value]']);
+ await waitForAll(['new value']);
expect(root).toMatchRenderedOutput('new value');
});
- it('updates memoized child of suspense component when context updates (manual memo)', () => {
+ it('updates memoized child of suspense component when context updates (manual memo)', async () => {
const {useContext, createContext, useState, memo} = React;
const ValueContext = createContext(null);
@@ -510,23 +517,23 @@ describe('ReactSuspense', () => {
const root = ReactTestRenderer.create(
, {
unstable_isConcurrent: true,
});
- expect(Scheduler).toFlushAndYield(['Suspend! [default]', 'Loading...']);
+ await waitForAll(['Suspend! [default]', 'Loading...']);
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [default]']);
- expect(Scheduler).toFlushAndYield(['default']);
+ assertLog(['Promise resolved [default]']);
+ await waitForAll(['default']);
expect(root).toMatchRenderedOutput('default');
act(() => setValue('new value'));
- expect(Scheduler).toHaveYielded(['Suspend! [new value]', 'Loading...']);
+ assertLog(['Suspend! [new value]', 'Loading...']);
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [new value]']);
- expect(Scheduler).toFlushAndYield(['new value']);
+ assertLog(['Promise resolved [new value]']);
+ await waitForAll(['new value']);
expect(root).toMatchRenderedOutput('new value');
});
- it('updates memoized child of suspense component when context updates (function)', () => {
+ it('updates memoized child of suspense component when context updates (function)', async () => {
const {useContext, createContext, useState} = React;
const ValueContext = createContext(null);
@@ -567,23 +574,23 @@ describe('ReactSuspense', () => {
unstable_isConcurrent: true,
},
);
- expect(Scheduler).toFlushAndYield(['Suspend! [default]', 'Loading...']);
+ await waitForAll(['Suspend! [default]', 'Loading...']);
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [default]']);
- expect(Scheduler).toFlushAndYield(['default']);
+ assertLog(['Promise resolved [default]']);
+ await waitForAll(['default']);
expect(root).toMatchRenderedOutput('default');
act(() => setValue('new value'));
- expect(Scheduler).toHaveYielded(['Suspend! [new value]', 'Loading...']);
+ assertLog(['Suspend! [new value]', 'Loading...']);
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [new value]']);
- expect(Scheduler).toFlushAndYield(['new value']);
+ assertLog(['Promise resolved [new value]']);
+ await waitForAll(['new value']);
expect(root).toMatchRenderedOutput('new value');
});
- it('updates memoized child of suspense component when context updates (forwardRef)', () => {
+ it('updates memoized child of suspense component when context updates (forwardRef)', async () => {
const {forwardRef, useContext, createContext, useState} = React;
const ValueContext = createContext(null);
@@ -624,23 +631,23 @@ describe('ReactSuspense', () => {
unstable_isConcurrent: true,
},
);
- expect(Scheduler).toFlushAndYield(['Suspend! [default]', 'Loading...']);
+ await waitForAll(['Suspend! [default]', 'Loading...']);
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [default]']);
- expect(Scheduler).toFlushAndYield(['default']);
+ assertLog(['Promise resolved [default]']);
+ await waitForAll(['default']);
expect(root).toMatchRenderedOutput('default');
act(() => setValue('new value'));
- expect(Scheduler).toHaveYielded(['Suspend! [new value]', 'Loading...']);
+ assertLog(['Suspend! [new value]', 'Loading...']);
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [new value]']);
- expect(Scheduler).toFlushAndYield(['new value']);
+ assertLog(['Promise resolved [new value]']);
+ await waitForAll(['new value']);
expect(root).toMatchRenderedOutput('new value');
});
- it('re-fires layout effects when re-showing Suspense', () => {
+ it('re-fires layout effects when re-showing Suspense', async () => {
function TextWithLayout(props) {
Scheduler.unstable_yieldValue(props.text);
React.useLayoutEffect(() => {
@@ -668,28 +675,21 @@ describe('ReactSuspense', () => {
unstable_isConcurrent: true,
});
- expect(Scheduler).toFlushAndYield(['Child 1', 'create layout']);
+ await waitForAll(['Child 1', 'create layout']);
expect(root).toMatchRenderedOutput('Child 1');
act(() => {
_setShow(true);
});
- expect(Scheduler).toHaveYielded([
- 'Child 1',
- 'Suspend! [Child 2]',
- 'Loading...',
- ]);
+ assertLog(['Child 1', 'Suspend! [Child 2]', 'Loading...']);
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded([
- 'destroy layout',
- 'Promise resolved [Child 2]',
- ]);
- expect(Scheduler).toFlushAndYield(['Child 1', 'Child 2', 'create layout']);
+ assertLog(['destroy layout', 'Promise resolved [Child 2]']);
+ await waitForAll(['Child 1', 'Child 2', 'create layout']);
expect(root).toMatchRenderedOutput(['Child 1', 'Child 2'].join(''));
});
describe('outside concurrent mode', () => {
- it('a mounted class component can suspend without losing state', () => {
+ it('a mounted class component can suspend without losing state', async () => {
class TextWithLifecycle extends React.Component {
componentDidMount() {
Scheduler.unstable_yieldValue(`Mount [${this.props.text}]`);
@@ -754,7 +754,7 @@ describe('ReactSuspense', () => {
const root = ReactTestRenderer.create(
);
- expect(Scheduler).toHaveYielded([
+ assertLog([
'A',
'Suspend! [B:1]',
'C',
@@ -770,8 +770,8 @@ describe('ReactSuspense', () => {
jest.advanceTimersByTime(100);
- expect(Scheduler).toHaveYielded(['Promise resolved [B:1]']);
- expect(Scheduler).toFlushUntilNextPaint([
+ assertLog(['Promise resolved [B:1]']);
+ await waitForPaint([
'B:1',
'Unmount [Loading...]',
// Should be a mount, not an update
@@ -780,25 +780,17 @@ describe('ReactSuspense', () => {
expect(root).toMatchRenderedOutput('AB:1C');
instance.setState({step: 2});
- expect(Scheduler).toHaveYielded([
- 'Suspend! [B:2]',
- 'Loading...',
- 'Mount [Loading...]',
- ]);
+ assertLog(['Suspend! [B:2]', 'Loading...', 'Mount [Loading...]']);
expect(root).toMatchRenderedOutput('Loading...');
jest.advanceTimersByTime(100);
- expect(Scheduler).toHaveYielded(['Promise resolved [B:2]']);
- expect(Scheduler).toFlushUntilNextPaint([
- 'B:2',
- 'Unmount [Loading...]',
- 'Update [B:2]',
- ]);
+ assertLog(['Promise resolved [B:2]']);
+ await waitForPaint(['B:2', 'Unmount [Loading...]', 'Update [B:2]']);
expect(root).toMatchRenderedOutput('AB:2C');
});
- it('bails out on timed-out primary children even if they receive an update', () => {
+ it('bails out on timed-out primary children even if they receive an update', async () => {
let instance;
class Stateful extends React.Component {
state = {step: 1};
@@ -819,38 +811,30 @@ describe('ReactSuspense', () => {
const root = ReactTestRenderer.create(
);
- expect(Scheduler).toHaveYielded([
- 'Stateful: 1',
- 'Suspend! [A]',
- 'Loading...',
- ]);
+ assertLog(['Stateful: 1', 'Suspend! [A]', 'Loading...']);
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
- expect(Scheduler).toFlushUntilNextPaint(['A']);
+ assertLog(['Promise resolved [A]']);
+ await waitForPaint(['A']);
expect(root).toMatchRenderedOutput('Stateful: 1A');
root.update(
);
- expect(Scheduler).toHaveYielded([
- 'Stateful: 1',
- 'Suspend! [B]',
- 'Loading...',
- ]);
+ assertLog(['Stateful: 1', 'Suspend! [B]', 'Loading...']);
expect(root).toMatchRenderedOutput('Loading...');
instance.setState({step: 2});
- expect(Scheduler).toHaveYielded(['Stateful: 2', 'Suspend! [B]']);
+ assertLog(['Stateful: 2', 'Suspend! [B]']);
expect(root).toMatchRenderedOutput('Loading...');
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
- expect(Scheduler).toFlushUntilNextPaint(['B']);
+ assertLog(['Promise resolved [B]']);
+ await waitForPaint(['B']);
expect(root).toMatchRenderedOutput('Stateful: 2B');
});
- it('when updating a timed-out tree, always retries the suspended component', () => {
+ it('when updating a timed-out tree, always retries the suspended component', async () => {
let instance;
class Stateful extends React.Component {
state = {step: 1};
@@ -879,28 +863,20 @@ describe('ReactSuspense', () => {
const root = ReactTestRenderer.create(
);
- expect(Scheduler).toHaveYielded([
- 'Stateful: 1',
- 'Suspend! [A]',
- 'Loading...',
- ]);
+ assertLog(['Stateful: 1', 'Suspend! [A]', 'Loading...']);
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
- expect(Scheduler).toFlushUntilNextPaint(['A']);
+ assertLog(['Promise resolved [A]']);
+ await waitForPaint(['A']);
expect(root).toMatchRenderedOutput('Stateful: 1A');
root.update(
);
- expect(Scheduler).toHaveYielded([
- 'Stateful: 1',
- 'Suspend! [B]',
- 'Loading...',
- ]);
+ assertLog(['Stateful: 1', 'Suspend! [B]', 'Loading...']);
expect(root).toMatchRenderedOutput('Loading...');
instance.setState({step: 2});
- expect(Scheduler).toHaveYielded([
+ assertLog([
'Stateful: 2',
// The suspended component should suspend again. If it doesn't, the
@@ -912,8 +888,8 @@ describe('ReactSuspense', () => {
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
- expect(Scheduler).toFlushUntilNextPaint(['B']);
+ assertLog(['Promise resolved [B]']);
+ await waitForPaint(['B']);
expect(root).toMatchRenderedOutput('Stateful: 2B');
});
@@ -949,14 +925,14 @@ describe('ReactSuspense', () => {
}
const root = ReactTestRenderer.create(
);
- expect(Scheduler).toHaveYielded(['Suspend! [A]', 'Loading...']);
+ assertLog(['Suspend! [A]', 'Loading...']);
root.update(
);
// Should not fire componentWillUnmount
- expect(Scheduler).toHaveYielded(['B']);
+ assertLog(['B']);
expect(root).toMatchRenderedOutput('B');
});
- it('suspends in a component that also contains useEffect', () => {
+ it('suspends in a component that also contains useEffect', async () => {
const {useLayoutEffect} = React;
function AsyncTextWithEffect(props) {
@@ -989,14 +965,14 @@ describe('ReactSuspense', () => {
}
ReactTestRenderer.create(
);
- expect(Scheduler).toHaveYielded(['Suspend! [A]', 'Loading...']);
+ assertLog(['Suspend! [A]', 'Loading...']);
jest.advanceTimersByTime(500);
- expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
- expect(Scheduler).toFlushUntilNextPaint(['A', 'Did commit: A']);
+ assertLog(['Promise resolved [A]']);
+ await waitForPaint(['A', 'Did commit: A']);
});
- it('retries when an update is scheduled on a timed out tree', () => {
+ it('retries when an update is scheduled on a timed out tree', async () => {
let instance;
class Stateful extends React.Component {
state = {step: 1};
@@ -1019,33 +995,30 @@ describe('ReactSuspense', () => {
});
// Initial render
- expect(Scheduler).toFlushAndYield(['Suspend! [Step: 1]', 'Loading...']);
+ await waitForAll(['Suspend! [Step: 1]', 'Loading...']);
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [Step: 1]']);
- expect(Scheduler).toFlushAndYield(['Step: 1']);
+ assertLog(['Promise resolved [Step: 1]']);
+ await waitForAll(['Step: 1']);
expect(root).toMatchRenderedOutput('Step: 1');
// Update that suspends
instance.setState({step: 2});
- expect(Scheduler).toFlushAndYield(['Suspend! [Step: 2]', 'Loading...']);
+ await waitForAll(['Suspend! [Step: 2]', 'Loading...']);
jest.advanceTimersByTime(500);
expect(root).toMatchRenderedOutput('Loading...');
// Update while still suspended
instance.setState({step: 3});
- expect(Scheduler).toFlushAndYield(['Suspend! [Step: 3]']);
+ await waitForAll(['Suspend! [Step: 3]']);
expect(root).toMatchRenderedOutput('Loading...');
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded([
- 'Promise resolved [Step: 2]',
- 'Promise resolved [Step: 3]',
- ]);
- expect(Scheduler).toFlushAndYield(['Step: 3']);
+ assertLog(['Promise resolved [Step: 2]', 'Promise resolved [Step: 3]']);
+ await waitForAll(['Step: 3']);
expect(root).toMatchRenderedOutput('Step: 3');
});
- it('does not remount the fallback while suspended children resolve in legacy mode', () => {
+ it('does not remount the fallback while suspended children resolve in legacy mode', async () => {
let mounts = 0;
class ShouldMountOnce extends React.Component {
componentDidMount() {
@@ -1069,18 +1042,18 @@ describe('ReactSuspense', () => {
const root = ReactTestRenderer.create(
);
// Initial render
- expect(Scheduler).toHaveYielded([
+ assertLog([
'Suspend! [Child 1]',
'Suspend! [Child 2]',
'Suspend! [Child 3]',
'Loading...',
]);
- expect(Scheduler).toFlushAndYield([]);
+ await waitForAll([]);
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [Child 1]']);
- expect(Scheduler).toFlushUntilNextPaint([
+ assertLog(['Promise resolved [Child 1]']);
+ await waitForPaint([
'Child 1',
'Suspend! [Child 2]',
'Suspend! [Child 3]',
@@ -1088,23 +1061,20 @@ describe('ReactSuspense', () => {
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [Child 2]']);
- expect(Scheduler).toFlushUntilNextPaint([
- 'Child 2',
- 'Suspend! [Child 3]',
- ]);
+ assertLog(['Promise resolved [Child 2]']);
+ await waitForPaint(['Child 2', 'Suspend! [Child 3]']);
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [Child 3]']);
- expect(Scheduler).toFlushUntilNextPaint(['Child 3']);
+ assertLog(['Promise resolved [Child 3]']);
+ await waitForPaint(['Child 3']);
expect(root).toMatchRenderedOutput(
['Child 1', 'Child 2', 'Child 3'].join(''),
);
expect(mounts).toBe(1);
});
- it('does not get stuck with fallback in concurrent mode for a large delay', () => {
+ it('does not get stuck with fallback in concurrent mode for a large delay', async () => {
function App(props) {
return (
}>
@@ -1118,21 +1088,21 @@ describe('ReactSuspense', () => {
unstable_isConcurrent: true,
});
- expect(Scheduler).toFlushAndYield([
+ await waitForAll([
'Suspend! [Child 1]',
'Suspend! [Child 2]',
'Loading...',
]);
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [Child 1]']);
- expect(Scheduler).toFlushAndYield(['Child 1', 'Suspend! [Child 2]']);
+ assertLog(['Promise resolved [Child 1]']);
+ await waitForAll(['Child 1', 'Suspend! [Child 2]']);
jest.advanceTimersByTime(6000);
- expect(Scheduler).toHaveYielded(['Promise resolved [Child 2]']);
- expect(Scheduler).toFlushAndYield(['Child 1', 'Child 2']);
+ assertLog(['Promise resolved [Child 2]']);
+ await waitForAll(['Child 1', 'Child 2']);
expect(root).toMatchRenderedOutput(['Child 1', 'Child 2'].join(''));
});
- it('reuses effects, including deletions, from the suspended tree', () => {
+ it('reuses effects, including deletions, from the suspended tree', async () => {
const {useState} = React;
let setTab;
@@ -1149,46 +1119,34 @@ describe('ReactSuspense', () => {
}
const root = ReactTestRenderer.create(
);
- expect(Scheduler).toHaveYielded([
- 'Suspend! [Tab: 0]',
- ' + sibling',
- 'Loading...',
- ]);
+ assertLog(['Suspend! [Tab: 0]', ' + sibling', 'Loading...']);
expect(root).toMatchRenderedOutput('Loading...');
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [Tab: 0]']);
- expect(Scheduler).toFlushUntilNextPaint(['Tab: 0']);
+ assertLog(['Promise resolved [Tab: 0]']);
+ await waitForPaint(['Tab: 0']);
expect(root).toMatchRenderedOutput('Tab: 0 + sibling');
act(() => setTab(1));
- expect(Scheduler).toHaveYielded([
- 'Suspend! [Tab: 1]',
- ' + sibling',
- 'Loading...',
- ]);
+ assertLog(['Suspend! [Tab: 1]', ' + sibling', 'Loading...']);
expect(root).toMatchRenderedOutput('Loading...');
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [Tab: 1]']);
- expect(Scheduler).toFlushUntilNextPaint(['Tab: 1']);
+ assertLog(['Promise resolved [Tab: 1]']);
+ await waitForPaint(['Tab: 1']);
expect(root).toMatchRenderedOutput('Tab: 1 + sibling');
act(() => setTab(2));
- expect(Scheduler).toHaveYielded([
- 'Suspend! [Tab: 2]',
- ' + sibling',
- 'Loading...',
- ]);
+ assertLog(['Suspend! [Tab: 2]', ' + sibling', 'Loading...']);
expect(root).toMatchRenderedOutput('Loading...');
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [Tab: 2]']);
- expect(Scheduler).toFlushUntilNextPaint(['Tab: 2']);
+ assertLog(['Promise resolved [Tab: 2]']);
+ await waitForPaint(['Tab: 2']);
expect(root).toMatchRenderedOutput('Tab: 2 + sibling');
});
- it('does not warn if an mounted component is pinged', () => {
+ it('does not warn if an mounted component is pinged', async () => {
const {useState} = React;
const root = ReactTestRenderer.create(null);
@@ -1218,23 +1176,23 @@ describe('ReactSuspense', () => {
,
);
- expect(Scheduler).toHaveYielded(['Suspend! [A:0]', 'Loading...']);
+ assertLog(['Suspend! [A:0]', 'Loading...']);
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [A:0]']);
- expect(Scheduler).toFlushUntilNextPaint(['A:0']);
+ assertLog(['Promise resolved [A:0]']);
+ await waitForPaint(['A:0']);
expect(root).toMatchRenderedOutput('A:0');
act(() => setStep(1));
- expect(Scheduler).toHaveYielded(['Suspend! [A:1]', 'Loading...']);
+ assertLog(['Suspend! [A:1]', 'Loading...']);
expect(root).toMatchRenderedOutput('Loading...');
root.update(null);
- expect(Scheduler).toFlushWithoutYielding();
+ await waitForAll([]);
jest.advanceTimersByTime(1000);
});
- it('memoizes promise listeners per thread ID to prevent redundant renders', () => {
+ it('memoizes promise listeners per thread ID to prevent redundant renders', async () => {
function App() {
return (
}>
@@ -1249,18 +1207,13 @@ describe('ReactSuspense', () => {
root.update(
);
- expect(Scheduler).toHaveYielded([
- 'Suspend! [A]',
- 'Suspend! [B]',
- 'Suspend! [C]',
- 'Loading...',
- ]);
+ assertLog(['Suspend! [A]', 'Suspend! [B]', 'Suspend! [C]', 'Loading...']);
// Resolve A
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
- expect(Scheduler).toFlushUntilNextPaint([
+ assertLog(['Promise resolved [A]']);
+ await waitForPaint([
'A',
// The promises for B and C have now been thrown twice
'Suspend! [B]',
@@ -1270,8 +1223,8 @@ describe('ReactSuspense', () => {
// Resolve B
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
- expect(Scheduler).toFlushUntilNextPaint([
+ assertLog(['Promise resolved [B]']);
+ await waitForPaint([
// Even though the promise for B was thrown twice, we should only
// re-render once.
'B',
@@ -1282,8 +1235,8 @@ describe('ReactSuspense', () => {
// Resolve C
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [C]']);
- expect(Scheduler).toFlushUntilNextPaint([
+ assertLog(['Promise resolved [C]']);
+ await waitForPaint([
// Even though the promise for C was thrown three times, we should only
// re-render once.
'C',
@@ -1328,7 +1281,7 @@ describe('ReactSuspense', () => {
jest.advanceTimersByTime(1000);
});
- it('updates memoized child of suspense component when context updates (simple memo)', () => {
+ it('updates memoized child of suspense component when context updates (simple memo)', async () => {
const {useContext, createContext, useState, memo} = React;
const ValueContext = createContext(null);
@@ -1364,23 +1317,23 @@ describe('ReactSuspense', () => {
}
const root = ReactTestRenderer.create(
);
- expect(Scheduler).toHaveYielded(['Suspend! [default]', 'Loading...']);
+ assertLog(['Suspend! [default]', 'Loading...']);
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [default]']);
- expect(Scheduler).toFlushUntilNextPaint(['default']);
+ assertLog(['Promise resolved [default]']);
+ await waitForPaint(['default']);
expect(root).toMatchRenderedOutput('default');
act(() => setValue('new value'));
- expect(Scheduler).toHaveYielded(['Suspend! [new value]', 'Loading...']);
+ assertLog(['Suspend! [new value]', 'Loading...']);
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [new value]']);
- expect(Scheduler).toFlushUntilNextPaint(['new value']);
+ assertLog(['Promise resolved [new value]']);
+ await waitForPaint(['new value']);
expect(root).toMatchRenderedOutput('new value');
});
- it('updates memoized child of suspense component when context updates (manual memo)', () => {
+ it('updates memoized child of suspense component when context updates (manual memo)', async () => {
const {useContext, createContext, useState, memo} = React;
const ValueContext = createContext(null);
@@ -1421,23 +1374,23 @@ describe('ReactSuspense', () => {
}
const root = ReactTestRenderer.create(
);
- expect(Scheduler).toHaveYielded(['Suspend! [default]', 'Loading...']);
+ assertLog(['Suspend! [default]', 'Loading...']);
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [default]']);
- expect(Scheduler).toFlushUntilNextPaint(['default']);
+ assertLog(['Promise resolved [default]']);
+ await waitForPaint(['default']);
expect(root).toMatchRenderedOutput('default');
act(() => setValue('new value'));
- expect(Scheduler).toHaveYielded(['Suspend! [new value]', 'Loading...']);
+ assertLog(['Suspend! [new value]', 'Loading...']);
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [new value]']);
- expect(Scheduler).toFlushUntilNextPaint(['new value']);
+ assertLog(['Promise resolved [new value]']);
+ await waitForPaint(['new value']);
expect(root).toMatchRenderedOutput('new value');
});
- it('updates memoized child of suspense component when context updates (function)', () => {
+ it('updates memoized child of suspense component when context updates (function)', async () => {
const {useContext, createContext, useState} = React;
const ValueContext = createContext(null);
@@ -1477,23 +1430,23 @@ describe('ReactSuspense', () => {
,
);
- expect(Scheduler).toHaveYielded(['Suspend! [default]', 'Loading...']);
+ assertLog(['Suspend! [default]', 'Loading...']);
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [default]']);
- expect(Scheduler).toFlushUntilNextPaint(['default']);
+ assertLog(['Promise resolved [default]']);
+ await waitForPaint(['default']);
expect(root).toMatchRenderedOutput('default');
act(() => setValue('new value'));
- expect(Scheduler).toHaveYielded(['Suspend! [new value]', 'Loading...']);
+ assertLog(['Suspend! [new value]', 'Loading...']);
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [new value]']);
- expect(Scheduler).toFlushUntilNextPaint(['new value']);
+ assertLog(['Promise resolved [new value]']);
+ await waitForPaint(['new value']);
expect(root).toMatchRenderedOutput('new value');
});
- it('updates memoized child of suspense component when context updates (forwardRef)', () => {
+ it('updates memoized child of suspense component when context updates (forwardRef)', async () => {
const {forwardRef, useContext, createContext, useState} = React;
const ValueContext = createContext(null);
@@ -1529,19 +1482,19 @@ describe('ReactSuspense', () => {
}
const root = ReactTestRenderer.create(
);
- expect(Scheduler).toHaveYielded(['Suspend! [default]', 'Loading...']);
+ assertLog(['Suspend! [default]', 'Loading...']);
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [default]']);
- expect(Scheduler).toFlushUntilNextPaint(['default']);
+ assertLog(['Promise resolved [default]']);
+ await waitForPaint(['default']);
expect(root).toMatchRenderedOutput('default');
act(() => setValue('new value'));
- expect(Scheduler).toHaveYielded(['Suspend! [new value]', 'Loading...']);
+ assertLog(['Suspend! [new value]', 'Loading...']);
jest.advanceTimersByTime(1000);
- expect(Scheduler).toHaveYielded(['Promise resolved [new value]']);
- expect(Scheduler).toFlushUntilNextPaint(['new value']);
+ assertLog(['Promise resolved [new value]']);
+ await waitForPaint(['new value']);
expect(root).toMatchRenderedOutput('new value');
});
@@ -1587,24 +1540,15 @@ describe('ReactSuspense', () => {
}
const root = ReactTestRenderer.create(
);
- expect(Scheduler).toHaveYielded([
- 'Received context value [default]',
- 'default',
- ]);
+ assertLog(['Received context value [default]', 'default']);
expect(root).toMatchRenderedOutput('default');
act(() => setValue('new value'));
- expect(Scheduler).toHaveYielded([
- 'Received context value [new value]',
- 'Loading...',
- ]);
+ assertLog(['Received context value [new value]', 'Loading...']);
expect(root).toMatchRenderedOutput('Loading...');
act(() => setValue('default'));
- expect(Scheduler).toHaveYielded([
- 'Received context value [default]',
- 'default',
- ]);
+ assertLog(['Received context value [default]', 'default']);
expect(root).toMatchRenderedOutput('default');
});
});
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseCallback-test.js b/packages/react-reconciler/src/__tests__/ReactSuspenseCallback-test.js
index b14f7494a572c..011657cd065a8 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspenseCallback-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspenseCallback-test.js
@@ -12,6 +12,7 @@
let React;
let ReactNoop;
let Scheduler;
+let waitForAll;
describe('ReactSuspense', () => {
beforeEach(() => {
@@ -20,6 +21,9 @@ describe('ReactSuspense', () => {
React = require('react');
ReactNoop = require('react-noop-renderer');
Scheduler = require('scheduler');
+
+ const InternalTestUtils = require('internal-test-utils');
+ waitForAll = InternalTestUtils.waitForAll;
});
function createThenable() {
@@ -83,13 +87,13 @@ describe('ReactSuspense', () => {
);
ReactNoop.render(element);
- expect(Scheduler).toFlushWithoutYielding();
+ await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput('Waiting');
expect(ops).toEqual([new Set([promise])]);
ops = [];
await resolve();
- expect(Scheduler).toFlushWithoutYielding();
+ await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput('Done');
expect(ops).toEqual([]);
});
@@ -122,27 +126,27 @@ describe('ReactSuspense', () => {
);
ReactNoop.render(element);
- expect(Scheduler).toFlushWithoutYielding();
+ await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 1');
expect(ops).toEqual([new Set([promise1, promise2])]);
ops = [];
await resolve1();
ReactNoop.render(element);
- expect(Scheduler).toFlushWithoutYielding();
+ await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 1');
expect(ops).toEqual([new Set([promise2])]);
ops = [];
await resolve2();
ReactNoop.render(element);
- expect(Scheduler).toFlushWithoutYielding();
+ await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput('DoneDone');
expect(ops).toEqual([]);
});
// @gate www
- it('nested suspense promises are reported only for their tier', () => {
+ it('nested suspense promises are reported only for their tier', async () => {
const {promise, PromiseComp} = createThenable();
const ops1 = [];
@@ -167,7 +171,7 @@ describe('ReactSuspense', () => {
);
ReactNoop.render(element);
- expect(Scheduler).toFlushWithoutYielding();
+ await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 2');
expect(ops1).toEqual([]);
expect(ops2).toEqual([new Set([promise])]);
@@ -209,7 +213,7 @@ describe('ReactSuspense', () => {
);
ReactNoop.render(element);
- expect(Scheduler).toFlushWithoutYielding();
+ await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 1');
expect(ops1).toEqual([new Set([promise1])]);
expect(ops2).toEqual([]);
@@ -218,7 +222,7 @@ describe('ReactSuspense', () => {
await resolve1();
ReactNoop.render(element);
- expect(Scheduler).toFlushWithoutYielding();
+ await waitForAll([]);
// Force fallback to commit.
// TODO: Should be able to use `act` here.
@@ -232,7 +236,7 @@ describe('ReactSuspense', () => {
await resolve2();
ReactNoop.render(element);
- expect(Scheduler).toFlushWithoutYielding();
+ await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput('DoneDone');
expect(ops1).toEqual([]);
expect(ops2).toEqual([]);