Convert more Suspense tests to use act (2/n) (#26610)

Many of our Suspense-related tests were written before the `act` API was
introduced, and use the lower level `waitFor` helpers instead. So they
are less resilient to changes in implementation details than they could
be.

This converts some of our test suite to use `act` in more places. I
found these while working on a PR to expand our fallback throttling
mechanism to include all renders that result from a promise resolving,
even if there are no more fallbacks in the tree.

I think this covers all the remaining tests that are affected.
This commit is contained in:
Andrew Clark 2023-04-12 13:36:13 -04:00 committed by GitHub
parent 21021fb0f0
commit 72c890e312
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 422 additions and 429 deletions

View File

@ -21,6 +21,7 @@ let textResourceShouldFail;
let waitForAll;
let assertLog;
let waitForThrow;
let act;
describe('ReactCache', () => {
beforeEach(() => {
@ -40,6 +41,7 @@ describe('ReactCache', () => {
waitForAll = InternalTestUtils.waitForAll;
assertLog = InternalTestUtils.assertLog;
waitForThrow = InternalTestUtils.waitForThrow;
act = InternalTestUtils.act;
TextResource = createResource(
([text, ms = 0]) => {
@ -145,11 +147,14 @@ describe('ReactCache', () => {
await waitForAll(['Suspend! [Hi]', 'Loading...']);
textResourceShouldFail = true;
jest.advanceTimersByTime(100);
assertLog(['Promise rejected [Hi]']);
await waitForThrow('Failed to load: Hi');
assertLog(['Error! [Hi]', 'Error! [Hi]']);
let error;
try {
await act(() => jest.advanceTimersByTime(100));
} catch (e) {
error = e;
}
expect(error.message).toMatch('Failed to load: Hi');
assertLog(['Promise rejected [Hi]', 'Error! [Hi]', 'Error! [Hi]']);
// Should throw again on a subsequent read
root.update(<App />);
@ -217,9 +222,8 @@ describe('ReactCache', () => {
assertLog(['Promise resolved [2]']);
await waitForAll([1, 2, 'Suspend! [3]']);
jest.advanceTimersByTime(100);
assertLog(['Promise resolved [3]']);
await waitForAll([1, 2, 3]);
await act(() => jest.advanceTimersByTime(100));
assertLog(['Promise resolved [3]', 1, 2, 3]);
expect(root).toMatchRenderedOutput('123');
@ -234,13 +238,17 @@ describe('ReactCache', () => {
await waitForAll([1, 'Suspend! [4]', 'Loading...']);
jest.advanceTimersByTime(100);
assertLog(['Promise resolved [4]']);
await waitForAll([1, 4, 'Suspend! [5]']);
jest.advanceTimersByTime(100);
assertLog(['Promise resolved [5]']);
await waitForAll([1, 4, 5]);
await act(() => jest.advanceTimersByTime(100));
assertLog([
'Promise resolved [4]',
1,
4,
'Suspend! [5]',
'Promise resolved [5]',
1,
4,
5,
]);
expect(root).toMatchRenderedOutput('145');
@ -262,13 +270,18 @@ describe('ReactCache', () => {
'Suspend! [2]',
'Loading...',
]);
jest.advanceTimersByTime(100);
assertLog(['Promise resolved [2]']);
await waitForAll([1, 2, 'Suspend! [3]']);
jest.advanceTimersByTime(100);
assertLog(['Promise resolved [3]']);
await waitForAll([1, 2, 3]);
await act(() => jest.advanceTimersByTime(100));
assertLog([
'Promise resolved [2]',
1,
2,
'Suspend! [3]',
'Promise resolved [3]',
1,
2,
3,
]);
expect(root).toMatchRenderedOutput('123');
});
@ -291,9 +304,8 @@ describe('ReactCache', () => {
await waitForAll(['Loading...']);
jest.advanceTimersByTime(1000);
assertLog(['Promise resolved [B]', 'Promise resolved [A]']);
await waitForAll(['Result']);
await act(() => jest.advanceTimersByTime(1000));
assertLog(['Promise resolved [B]', 'Promise resolved [A]', 'Result']);
expect(root).toMatchRenderedOutput('Result');
});

View File

@ -42,6 +42,7 @@ let waitFor;
let waitForAll;
let assertLog;
let waitForPaint;
let clientAct;
function resetJSDOM(markup) {
// Test Environment
@ -74,6 +75,7 @@ describe('ReactDOMFizzServer', () => {
waitFor = InternalTestUtils.waitFor;
waitForPaint = InternalTestUtils.waitForPaint;
assertLog = InternalTestUtils.assertLog;
clientAct = InternalTestUtils.act;
if (gate(flags => flags.source)) {
// The `with-selector` module composes the main `use-sync-external-store`
@ -1191,8 +1193,8 @@ describe('ReactDOMFizzServer', () => {
expect(getVisibleChildren(container)).toEqual(<div>Loading...</div>);
// We now resolve it on the client.
resolveText('Hello');
await waitForAll([]);
await clientAct(() => resolveText('Hello'));
assertLog([]);
// The client rendered HTML is now in place.
expect(getVisibleChildren(container)).toEqual(
@ -2884,10 +2886,10 @@ describe('ReactDOMFizzServer', () => {
</div>,
);
await act(() => {
await clientAct(() => {
resolveText('Yay!');
});
await waitForAll(['Yay!']);
assertLog(['Yay!']);
expect(getVisibleChildren(container)).toEqual(
<div>
<span />
@ -4310,10 +4312,10 @@ describe('ReactDOMFizzServer', () => {
<h1>Loading...</h1>
</div>,
);
await unsuspend();
await clientAct(() => unsuspend());
// Since our client components only throw on the very first render there are no
// new throws in this pass
await waitForAll([]);
assertLog([]);
expect(mockError.mock.calls).toEqual([]);
expect(getVisibleChildren(container)).toEqual(

View File

@ -37,6 +37,7 @@ let waitForAll;
let waitForThrow;
let assertLog;
let Scheduler;
let clientAct;
function resetJSDOM(markup) {
// Test Environment
@ -71,6 +72,7 @@ describe('ReactDOMFloat', () => {
waitForAll = InternalTestUtils.waitForAll;
waitForThrow = InternalTestUtils.waitForThrow;
assertLog = InternalTestUtils.assertLog;
clientAct = InternalTestUtils.act;
textCache = new Map();
loadCache = new Set();
@ -1186,7 +1188,7 @@ body {
// events have already fired. This requires the load to be awaited for the commit to have a chance to flush
// We could change this by tracking the loadingState's fulfilled status directly on the loadingState similar
// to thenables however this slightly increases the fizz runtime code size.
await loadStylesheets();
await clientAct(() => loadStylesheets());
assertLog(['load stylesheet: foo']);
expect(getMeaningfulChildren(document)).toEqual(
<html>

View File

@ -7,6 +7,7 @@ let assertLog;
let ReactCache;
let Suspense;
let TextResource;
let act;
describe('ReactBlockingMode', () => {
beforeEach(() => {
@ -23,6 +24,7 @@ describe('ReactBlockingMode', () => {
const InternalTestUtils = require('internal-test-utils');
waitForAll = InternalTestUtils.waitForAll;
assertLog = InternalTestUtils.assertLog;
act = InternalTestUtils.act;
TextResource = ReactCache.unstable_createResource(
([text, ms = 0]) => {
@ -117,9 +119,8 @@ describe('ReactBlockingMode', () => {
// fallback should mount immediately.
expect(root).toMatchRenderedOutput('Loading...');
await jest.advanceTimersByTime(1000);
assertLog(['Promise resolved [B]']);
await waitForAll(['A', 'B', 'C']);
await act(() => jest.advanceTimersByTime(1000));
assertLog(['Promise resolved [B]', 'A', 'B', 'C']);
expect(root).toMatchRenderedOutput(
<>
<span>A</span>

View File

@ -3623,9 +3623,8 @@ describe('ReactHooksWithNoopRenderer', () => {
</>,
);
await resolveText('A');
assertLog(['Promise resolved [A]']);
await waitForAll(['A']);
await act(() => resolveText('A'));
assertLog(['Promise resolved [A]', 'A']);
expect(ReactNoop).toMatchRenderedOutput(
<>
<span prop="A" />

View File

@ -9,6 +9,7 @@ let waitFor;
let waitForAll;
let waitForThrow;
let assertLog;
let act;
let fakeModuleCache;
@ -39,6 +40,7 @@ describe('ReactLazy', () => {
waitForAll = InternalTestUtils.waitForAll;
waitForThrow = InternalTestUtils.waitForThrow;
assertLog = InternalTestUtils.assertLog;
act = InternalTestUtils.act;
fakeModuleCache = new Map();
});
@ -104,9 +106,8 @@ describe('ReactLazy', () => {
await waitForAll(['Loading...']);
expect(root).not.toMatchRenderedOutput('Hi');
await resolveFakeImport(Text);
await waitForAll(['Hi']);
await act(() => resolveFakeImport(Text));
assertLog(['Hi']);
expect(root).toMatchRenderedOutput('Hi');
// Should not suspend on update
@ -196,9 +197,8 @@ describe('ReactLazy', () => {
await waitForAll(['Foo']);
expect(root).not.toMatchRenderedOutput('FooBar');
await resolveFakeImport(Bar);
await waitForAll(['Foo', 'Bar']);
await act(() => resolveFakeImport(Bar));
assertLog(['Foo', 'Bar']);
expect(root).toMatchRenderedOutput('FooBar');
});
@ -207,15 +207,24 @@ describe('ReactLazy', () => {
const LazyText = lazy(async () => Text);
const root = ReactTestRenderer.create(
<Suspense fallback={<Text text="Loading..." />}>
<LazyText text="Hi" />
</Suspense>,
{
unstable_isConcurrent: true,
},
);
await waitForThrow('Element type is invalid');
const root = ReactTestRenderer.create(null, {
unstable_isConcurrent: true,
});
let error;
try {
await act(() => {
root.update(
<Suspense fallback={<Text text="Loading..." />}>
<LazyText text="Hi" />
</Suspense>,
);
});
} catch (e) {
error = e;
}
expect(error.message).toMatch('Element type is invalid');
assertLog(['Loading...']);
expect(root).not.toMatchRenderedOutput('Hi');
if (__DEV__) {
@ -227,20 +236,29 @@ describe('ReactLazy', () => {
});
it('throws if promise rejects', async () => {
const networkError = new Error('Bad network');
const LazyText = lazy(async () => {
throw new Error('Bad network');
throw networkError;
});
const root = ReactTestRenderer.create(
<Suspense fallback={<Text text="Loading..." />}>
<LazyText text="Hi" />
</Suspense>,
{
unstable_isConcurrent: true,
},
);
const root = ReactTestRenderer.create(null, {
unstable_isConcurrent: true,
});
await waitForThrow('Bad network');
let error;
try {
await act(() => {
root.update(
<Suspense fallback={<Text text="Loading..." />}>
<LazyText text="Hi" />
</Suspense>,
);
});
} catch (e) {
error = e;
}
expect(error).toBe(networkError);
assertLog(['Loading...']);
expect(root).not.toMatchRenderedOutput('Hi');
});
@ -290,14 +308,15 @@ describe('ReactLazy', () => {
await waitForAll(['Suspend! [LazyChildA]', 'Loading...']);
expect(root).not.toMatchRenderedOutput('AB');
await resolveFakeImport(Child);
await act(async () => {
await resolveFakeImport(Child);
// B suspends even though it happens to share the same import as A.
// TODO: React.lazy should implement the `status` and `value` fields, so
// we can unwrap the result synchronously if it already loaded. Like `use`.
await waitFor(['A', 'Suspend! [LazyChildB]']);
await waitForAll(['A', 'B', 'Did mount: A', 'Did mount: B']);
// B suspends even though it happens to share the same import as A.
// TODO: React.lazy should implement the `status` and `value` fields, so
// we can unwrap the result synchronously if it already loaded. Like `use`.
await waitFor(['A', 'Suspend! [LazyChildB]']);
});
assertLog(['A', 'B', 'Did mount: A', 'Did mount: B']);
expect(root).toMatchRenderedOutput('AB');
// Swap the position of A and B
@ -325,9 +344,10 @@ describe('ReactLazy', () => {
await waitForAll(['Loading...']);
expect(root).not.toMatchRenderedOutput('Hi');
await resolveFakeImport(T);
await expect(async () => await waitForAll(['Hi'])).toErrorDev(
await expect(async () => {
await act(() => resolveFakeImport(T));
assertLog(['Hi']);
}).toErrorDev(
'Warning: T: Support for defaultProps ' +
'will be removed from function components in a future major ' +
'release. Use JavaScript default parameters instead.',
@ -380,11 +400,10 @@ describe('ReactLazy', () => {
await waitForAll(['Loading...']);
expect(root).not.toMatchRenderedOutput('SiblingA');
await resolveFakeImport(LazyImpl);
await expect(
async () => await waitForAll(['Lazy', 'Sibling', 'A']),
).toErrorDev(
await expect(async () => {
await act(() => resolveFakeImport(LazyImpl));
assertLog(['Lazy', 'Sibling', 'A']);
}).toErrorDev(
'Warning: LazyImpl: Support for defaultProps ' +
'will be removed from function components in a future major ' +
'release. Use JavaScript default parameters instead.',
@ -427,9 +446,8 @@ describe('ReactLazy', () => {
await waitForAll(['Not lazy: 0', 'Loading...']);
expect(root).not.toMatchRenderedOutput('Not lazy: 0Lazy: 0');
await resolveFakeImport(LazyImpl);
await waitForAll(['Lazy: 0']);
await act(() => resolveFakeImport(LazyImpl));
assertLog(['Lazy: 0']);
expect(root).toMatchRenderedOutput('Not lazy: 0Lazy: 0');
// Should bailout due to unchanged props and state
@ -473,9 +491,8 @@ describe('ReactLazy', () => {
await waitForAll(['Not lazy: 0', 'Loading...']);
expect(root).not.toMatchRenderedOutput('Not lazy: 0Lazy: 0');
await resolveFakeImport(LazyImpl);
await waitForAll(['Lazy: 0']);
await act(() => resolveFakeImport(LazyImpl));
assertLog(['Lazy: 0']);
expect(root).toMatchRenderedOutput('Not lazy: 0Lazy: 0');
// Should bailout due to shallow equal props and state
@ -551,9 +568,8 @@ describe('ReactLazy', () => {
await waitForAll(['Loading...']);
expect(root).not.toMatchRenderedOutput('A1');
await resolveFakeImport(C);
await waitForAll([
await act(() => resolveFakeImport(C));
assertLog([
'constructor: A',
'getDerivedStateFromProps: A',
'A1',
@ -682,8 +698,10 @@ describe('ReactLazy', () => {
await waitForAll(['Loading...']);
expect(root).not.toMatchRenderedOutput('Hi Bye');
await resolveFakeImport(T);
await expect(async () => await waitForAll(['Hi Bye'])).toErrorDev(
await expect(async () => {
await act(() => resolveFakeImport(T));
assertLog(['Hi Bye']);
}).toErrorDev(
'Warning: T: Support for defaultProps ' +
'will be removed from function components in a future major ' +
'release. Use JavaScript default parameters instead.',
@ -806,9 +824,8 @@ describe('ReactLazy', () => {
expect(root).not.toMatchRenderedOutput('22');
// Mount
await resolveFakeImport(Add);
await expect(async () => {
await waitForAll([]);
await act(() => resolveFakeImport(Add));
}).toErrorDev(
shouldWarnAboutFunctionDefaultProps
? [
@ -1003,9 +1020,9 @@ describe('ReactLazy', () => {
expect(root).not.toMatchRenderedOutput('Inner default text');
// Mount
await resolveFakeImport(T);
await expect(async () => {
await waitForAll(['Inner default text']);
await act(() => resolveFakeImport(T));
assertLog(['Inner default text']);
}).toErrorDev([
'T: Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.',
'The prop `text` is marked as required in `T`, but its value is `undefined`',
@ -1045,10 +1062,9 @@ describe('ReactLazy', () => {
await waitForAll(['Started loading', 'Loading...']);
expect(root).not.toMatchRenderedOutput(<div>AB</div>);
await resolveFakeImport(Foo);
await expect(async () => {
await waitForAll(['A', 'B']);
await act(() => resolveFakeImport(Foo));
assertLog(['A', 'B']);
}).toErrorDev(' in Text (at **)\n' + ' in Foo (at **)');
expect(root).toMatchRenderedOutput(<div>AB</div>);
});
@ -1092,12 +1108,11 @@ describe('ReactLazy', () => {
expect(root).not.toMatchRenderedOutput('FooBar');
expect(ref.current).toBe(null);
await resolveFakeImport(Foo);
await waitForAll(['Foo']);
await act(() => resolveFakeImport(Foo));
assertLog(['Foo']);
await resolveFakeImport(ForwardRefBar);
await waitForAll(['Foo', 'forwardRef', 'Bar']);
await act(() => resolveFakeImport(ForwardRefBar));
assertLog(['Foo', 'forwardRef', 'Bar']);
expect(root).toMatchRenderedOutput('FooBar');
expect(ref.current).not.toBe(null);
});
@ -1123,9 +1138,8 @@ describe('ReactLazy', () => {
expect(root).not.toMatchRenderedOutput('4');
// Mount
await resolveFakeImport(Add);
await expect(async () => {
await waitForAll([]);
await act(() => resolveFakeImport(Add));
}).toErrorDev(
'Unknown: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
);
@ -1211,9 +1225,8 @@ describe('ReactLazy', () => {
expect(root).not.toMatchRenderedOutput('4');
// Mount
await resolveFakeImport(Add);
await expect(async () => {
await waitForAll([]);
await act(() => resolveFakeImport(Add));
}).toErrorDev([
'Memo: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
'Unknown: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
@ -1296,8 +1309,8 @@ describe('ReactLazy', () => {
await waitForAll(['Loading...']);
await resolveFakeImport(ResolvedText);
await waitForAll([]);
await act(() => resolveFakeImport(ResolvedText));
assertLog([]);
expect(componentStackMessage).toContain('in ResolvedText');
});
@ -1405,11 +1418,11 @@ describe('ReactLazy', () => {
await waitForAll(['Init A', 'Loading...']);
expect(root).not.toMatchRenderedOutput('AB');
await resolveFakeImport(ChildA);
await waitForAll(['A', 'Init B']);
await act(() => resolveFakeImport(ChildA));
assertLog(['A', 'Init B']);
await resolveFakeImport(ChildB);
await waitForAll(['A', 'B', 'Did mount: A', 'Did mount: B']);
await act(() => resolveFakeImport(ChildB));
assertLog(['A', 'B', 'Did mount: A', 'Did mount: B']);
expect(root).toMatchRenderedOutput('AB');
// Swap the position of A and B
@ -1423,10 +1436,10 @@ describe('ReactLazy', () => {
// The suspense boundary should've triggered now.
expect(root).toMatchRenderedOutput('Loading...');
await resolveB2({default: ChildB});
await act(() => resolveB2({default: ChildB}));
// We need to flush to trigger the second one to load.
await waitForAll(['Init A2', 'b', 'a', 'Did mount: b', 'Did mount: a']);
assertLog(['Init A2', 'b', 'a', 'Did mount: b', 'Did mount: a']);
expect(root).toMatchRenderedOutput('ba');
});
@ -1552,12 +1565,11 @@ describe('ReactLazy', () => {
await waitForAll(['Init A', 'Loading...']);
expect(root).not.toMatchRenderedOutput('AB');
await resolveFakeImport(ChildA);
await act(() => resolveFakeImport(ChildA));
// We need to flush to trigger the B to load.
await waitForAll(['Init B']);
await resolveFakeImport(ChildB);
await waitForAll(['A', 'B', 'Did mount: A', 'Did mount: B']);
await assertLog(['Init B']);
await act(() => resolveFakeImport(ChildB));
assertLog(['A', 'B', 'Did mount: A', 'Did mount: B']);
expect(root).toMatchRenderedOutput('AB');
// Swap the position of A and B
@ -1565,12 +1577,11 @@ describe('ReactLazy', () => {
root.update(<Parent swap={true} />);
});
await waitForAll(['Init B2', 'Loading...']);
await resolveFakeImport(ChildB2);
await act(() => resolveFakeImport(ChildB2));
// We need to flush to trigger the second one to load.
await waitForAll(['Init A2', 'Loading...']);
await resolveFakeImport(ChildA2);
await waitForAll(['b', 'a', 'Did update: b', 'Did update: a']);
assertLog(['Init A2', 'Loading...']);
await act(() => resolveFakeImport(ChildA2));
assertLog(['b', 'a', 'Did update: b', 'Did update: a']);
expect(root).toMatchRenderedOutput('ba');
});

View File

@ -106,12 +106,14 @@ describe('memo', () => {
}
Counter = memo(Counter);
ReactNoop.render(
<Suspense fallback={<Text text="Loading..." />}>
<Counter count={0} />
</Suspense>,
await act(() =>
ReactNoop.render(
<Suspense fallback={<Text text="Loading..." />}>
<Counter count={0} />
</Suspense>,
),
);
await waitForAll(['Loading...', 0]);
assertLog(['Loading...', 0]);
expect(ReactNoop).toMatchRenderedOutput(<span prop={0} />);
// Should bail out because props have not changed
@ -163,8 +165,8 @@ describe('memo', () => {
}
const parent = React.createRef(null);
ReactNoop.render(<Parent ref={parent} />);
await waitForAll(['Loading...', 'Count: 0']);
await act(() => ReactNoop.render(<Parent ref={parent} />));
assertLog(['Loading...', 'Count: 0']);
expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
// Should bail out because props have not changed
@ -340,12 +342,14 @@ describe('memo', () => {
return oldProps.count === newProps.count;
});
ReactNoop.render(
<Suspense fallback={<Text text="Loading..." />}>
<Counter count={0} />
</Suspense>,
await act(() =>
ReactNoop.render(
<Suspense fallback={<Text text="Loading..." />}>
<Counter count={0} />
</Suspense>,
),
);
await waitForAll(['Loading...', 0]);
assertLog(['Loading...', 0]);
expect(ReactNoop).toMatchRenderedOutput(<span prop={0} />);
// Should bail out because props have not changed
@ -376,12 +380,14 @@ describe('memo', () => {
}
const Counter = memo(CounterInner);
ReactNoop.render(
<Suspense fallback={<Text text="Loading..." />}>
<Counter count={0} />
</Suspense>,
await act(() =>
ReactNoop.render(
<Suspense fallback={<Text text="Loading..." />}>
<Counter count={0} />
</Suspense>,
),
);
await waitForAll(['Loading...', '0!']);
assertLog(['Loading...', '0!']);
expect(ReactNoop).toMatchRenderedOutput(<span prop="0!" />);
// Should bail out because props have not changed
@ -427,13 +433,16 @@ describe('memo', () => {
};
// The final layer uses memo() from test fixture (which might be lazy).
Counter = memo(Counter);
ReactNoop.render(
<Suspense fallback={<Text text="Loading..." />}>
<Counter e={5} />
</Suspense>,
);
await expect(async () => {
await waitForAll(['Loading...', 15]);
await act(() => {
ReactNoop.render(
<Suspense fallback={<Text text="Loading..." />}>
<Counter e={5} />
</Suspense>,
);
});
assertLog(['Loading...', 15]);
}).toErrorDev([
'Counter: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
]);

View File

@ -172,14 +172,14 @@ describe('ReactSuspense', () => {
// Resolve first Suspense's promise and switch back to the normal view. The
// second Suspense should still show the placeholder
await resolveText('A');
await waitForAll(['A']);
await act(() => resolveText('A'));
assertLog(['A']);
expect(root).toMatchRenderedOutput('ALoading B...');
// Resolve the second Suspense's promise resolves and switche back to the
// normal view
await resolveText('B');
await waitForAll(['B']);
await act(() => resolveText('B'));
assertLog(['B']);
expect(root).toMatchRenderedOutput('AB');
});
@ -294,13 +294,10 @@ describe('ReactSuspense', () => {
// showing the inner fallback hoping that B will resolve soon enough.
expect(root).toMatchRenderedOutput('Loading...');
await act(() => resolveText('B'));
// By this point, B has resolved.
// We're still showing the outer fallback.
await resolveText('B');
expect(root).toMatchRenderedOutput('Loading...');
await waitForAll(['A', 'B']);
// Then contents of both should pop in together.
// The contents of both should pop in together.
assertLog(['A', 'B']);
expect(root).toMatchRenderedOutput('AB');
});
@ -337,8 +334,8 @@ describe('ReactSuspense', () => {
jest.advanceTimersByTime(500);
expect(root).toMatchRenderedOutput('ALoading more...');
await resolveText('B');
await waitForAll(['B']);
await act(() => resolveText('B'));
assertLog(['B']);
expect(root).toMatchRenderedOutput('AB');
});
@ -475,15 +472,15 @@ describe('ReactSuspense', () => {
});
await waitForAll(['Suspend! [default]', 'Loading...']);
await resolveText('default');
await waitForAll(['default']);
await act(() => resolveText('default'));
assertLog(['default']);
expect(root).toMatchRenderedOutput('default');
await act(() => setValue('new value'));
assertLog(['Suspend! [new value]', 'Loading...']);
await resolveText('new value');
await waitForAll(['new value']);
await act(() => resolveText('new value'));
assertLog(['new value']);
expect(root).toMatchRenderedOutput('new value');
});
@ -521,15 +518,15 @@ describe('ReactSuspense', () => {
});
await waitForAll(['Suspend! [default]', 'Loading...']);
await resolveText('default');
await waitForAll(['default']);
await act(() => resolveText('default'));
assertLog(['default']);
expect(root).toMatchRenderedOutput('default');
await act(() => setValue('new value'));
assertLog(['Suspend! [new value]', 'Loading...']);
await resolveText('new value');
await waitForAll(['new value']);
await act(() => resolveText('new value'));
assertLog(['new value']);
expect(root).toMatchRenderedOutput('new value');
});
@ -565,15 +562,15 @@ describe('ReactSuspense', () => {
);
await waitForAll(['Suspend! [default]', 'Loading...']);
await resolveText('default');
await waitForAll(['default']);
await act(() => resolveText('default'));
assertLog(['default']);
expect(root).toMatchRenderedOutput('default');
await act(() => setValue('new value'));
assertLog(['Suspend! [new value]', 'Loading...']);
await resolveText('new value');
await waitForAll(['new value']);
await act(() => resolveText('new value'));
assertLog(['new value']);
expect(root).toMatchRenderedOutput('new value');
});
@ -609,15 +606,15 @@ describe('ReactSuspense', () => {
);
await waitForAll(['Suspend! [default]', 'Loading...']);
await resolveText('default');
await waitForAll(['default']);
await act(() => resolveText('default'));
assertLog(['default']);
expect(root).toMatchRenderedOutput('default');
await act(() => setValue('new value'));
assertLog(['Suspend! [new value]', 'Loading...']);
await resolveText('new value');
await waitForAll(['new value']);
await act(() => resolveText('new value'));
assertLog(['new value']);
expect(root).toMatchRenderedOutput('new value');
});
@ -662,8 +659,8 @@ describe('ReactSuspense', () => {
'destroy layout',
]);
await resolveText('Child 2');
await waitForAll(['Child 1', 'Child 2', 'create layout']);
await act(() => resolveText('Child 2'));
assertLog(['Child 1', 'Child 2', 'create layout']);
expect(root).toMatchRenderedOutput(['Child 1', 'Child 2'].join(''));
});
@ -920,8 +917,8 @@ describe('ReactSuspense', () => {
// Initial render
await waitForAll(['Suspend! [Step: 1]', 'Loading...']);
await resolveText('Step: 1');
await waitForAll(['Step: 1']);
await act(() => resolveText('Step: 1'));
assertLog(['Step: 1']);
expect(root).toMatchRenderedOutput('Step: 1');
// Update that suspends
@ -936,9 +933,11 @@ describe('ReactSuspense', () => {
await waitForAll(['Suspend! [Step: 3]']);
expect(root).toMatchRenderedOutput('Loading...');
await resolveText('Step: 2');
await resolveText('Step: 3');
await waitForAll(['Step: 3']);
await act(() => {
resolveText('Step: 2');
resolveText('Step: 3');
});
assertLog(['Step: 3']);
expect(root).toMatchRenderedOutput('Step: 3');
});
@ -996,8 +995,8 @@ describe('ReactSuspense', () => {
function App(props) {
return (
<Suspense fallback={<Text text="Loading..." />}>
<AsyncText ms={1000} text="Child 1" />
<AsyncText ms={7000} text="Child 2" />
<AsyncText text="Child 1" />
<AsyncText text="Child 2" />
</Suspense>
);
}
@ -1012,8 +1011,8 @@ describe('ReactSuspense', () => {
jest.advanceTimersByTime(6000);
await resolveText('Child 2');
await waitForAll(['Child 1', 'Child 2']);
await act(() => resolveText('Child 2'));
assertLog(['Child 1', 'Child 2']);
expect(root).toMatchRenderedOutput(['Child 1', 'Child 2'].join(''));
});

View File

@ -12,6 +12,7 @@
let React;
let ReactNoop;
let waitForAll;
let act;
describe('ReactSuspense', () => {
beforeEach(() => {
@ -22,6 +23,7 @@ describe('ReactSuspense', () => {
const InternalTestUtils = require('internal-test-utils');
waitForAll = InternalTestUtils.waitForAll;
act = InternalTestUtils.act;
});
function createThenable() {
@ -90,7 +92,7 @@ describe('ReactSuspense', () => {
expect(ops).toEqual([new Set([promise])]);
ops = [];
await resolve();
await act(() => resolve());
await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput('Done');
expect(ops).toEqual([]);
@ -129,14 +131,14 @@ describe('ReactSuspense', () => {
expect(ops).toEqual([new Set([promise1])]);
ops = [];
await resolve1();
await act(() => resolve1());
ReactNoop.render(element);
await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 1');
expect(ops).toEqual([new Set([promise2])]);
ops = [];
await resolve2();
await act(() => resolve2());
ReactNoop.render(element);
await waitForAll([]);
expect(ReactNoop).toMatchRenderedOutput('DoneDone');
@ -218,23 +220,14 @@ describe('ReactSuspense', () => {
ops1 = [];
ops2 = [];
await resolve1();
ReactNoop.render(element);
await waitForAll([]);
// Force fallback to commit.
// TODO: Should be able to use `act` here.
jest.runAllTimers();
await act(() => resolve1());
expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 2Done');
expect(ops1).toEqual([]);
expect(ops2).toEqual([new Set([promise2])]);
ops1 = [];
ops2 = [];
await resolve2();
ReactNoop.render(element);
await waitForAll([]);
await act(() => resolve2());
expect(ReactNoop).toMatchRenderedOutput('DoneDone');
expect(ops1).toEqual([]);
expect(ops2).toEqual([]);

View File

@ -187,8 +187,8 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
});
assertLog(['Loading...']);
await resolveFakeImport(ChildA);
await waitForAll(['A', 'Ref mount: A']);
await act(() => resolveFakeImport(ChildA));
assertLog(['A', 'Ref mount: A']);
expect(container.innerHTML).toBe('<span>A</span>');
// Swap the position of A and B
@ -200,8 +200,8 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
'<span style="display: none;">A</span>Loading...',
);
await resolveFakeImport(ChildB);
await waitForAll(['B', 'Ref mount: B']);
await act(() => resolveFakeImport(ChildB));
assertLog(['B', 'Ref mount: B']);
expect(container.innerHTML).toBe('<span>B</span>');
});
@ -247,8 +247,8 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
});
assertLog(['Loading...']);
await resolveFakeImport(ChildA);
await waitForAll(['A', 'Did mount: A']);
await act(() => resolveFakeImport(ChildA));
assertLog(['A', 'Did mount: A']);
expect(container.innerHTML).toBe('A');
// Swap the position of A and B
@ -258,8 +258,8 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
assertLog(['Loading...', 'Will unmount: A']);
expect(container.innerHTML).toBe('Loading...');
await resolveFakeImport(ChildB);
await waitForAll(['B', 'Did mount: B']);
await act(() => resolveFakeImport(ChildB));
assertLog(['B', 'Did mount: B']);
expect(container.innerHTML).toBe('B');
});
@ -299,8 +299,8 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
});
assertLog(['Loading...']);
await resolveFakeImport(ChildA);
await waitForAll(['A', 'Did mount: A']);
await act(() => resolveFakeImport(ChildA));
assertLog(['A', 'Did mount: A']);
expect(container.innerHTML).toBe('A');
// Swap the position of A and B
@ -366,8 +366,8 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
});
assertLog(['Loading...']);
await resolveFakeImport(ChildA);
await waitForAll(['A', 'Ref mount: A']);
await act(() => resolveFakeImport(ChildA));
assertLog(['A', 'Ref mount: A']);
expect(container.innerHTML).toBe('<span>A</span>');
// Swap the position of A and B
@ -429,8 +429,8 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
});
assertLog(['Loading...']);
await resolveFakeImport(ChildA);
await waitForAll(['A', 'Did mount: A']);
await act(() => resolveFakeImport(ChildA));
assertLog(['A', 'Did mount: A']);
expect(container.innerHTML).toBe('A');
// Swap the position of A and B

View File

@ -249,9 +249,8 @@ describe('ReactSuspenseList', () => {
</>,
);
await C.resolve();
await waitForAll(['C']);
await act(() => C.resolve());
assertLog(['C']);
expect(ReactNoop).toMatchRenderedOutput(
<>
@ -261,9 +260,8 @@ describe('ReactSuspenseList', () => {
</>,
);
await B.resolve();
await waitForAll(['B']);
await act(() => B.resolve());
assertLog(['B']);
expect(ReactNoop).toMatchRenderedOutput(
<>
@ -384,9 +382,8 @@ describe('ReactSuspenseList', () => {
</>,
);
await B.resolve();
await waitForAll(['A', 'B', 'Suspend! [C]']);
await act(() => B.resolve());
assertLog(['A', 'B', 'Suspend! [C]']);
expect(ReactNoop).toMatchRenderedOutput(
<>
@ -396,9 +393,8 @@ describe('ReactSuspenseList', () => {
</>,
);
await C.resolve();
await waitForAll(['A', 'B', 'C']);
await act(() => C.resolve());
assertLog(['A', 'B', 'C']);
expect(ReactNoop).toMatchRenderedOutput(
<>
@ -462,9 +458,8 @@ describe('ReactSuspenseList', () => {
</>,
);
await B.resolve();
await waitForAll(['A', 'B', 'Suspend! [C]']);
await act(() => B.resolve());
assertLog(['A', 'B', 'Suspend! [C]']);
expect(ReactNoop).toMatchRenderedOutput(
<>
@ -478,9 +473,8 @@ describe('ReactSuspenseList', () => {
</>,
);
await C.resolve();
await waitForAll(['A', 'B', 'C']);
await act(() => C.resolve());
assertLog(['A', 'B', 'C']);
expect(ReactNoop).toMatchRenderedOutput(
<>
@ -544,9 +538,8 @@ describe('ReactSuspenseList', () => {
</>,
);
await C.resolve();
await waitForAll(['A', 'B', 'C']);
await act(() => C.resolve());
assertLog(['A', 'B', 'C']);
expect(ReactNoop).toMatchRenderedOutput(
<>
@ -604,9 +597,8 @@ describe('ReactSuspenseList', () => {
</>,
);
await C.resolve();
await waitForAll(['A', 'B', 'C']);
await act(() => C.resolve());
assertLog(['A', 'B', 'C']);
expect(ReactNoop).toMatchRenderedOutput(
<>
@ -667,8 +659,8 @@ describe('ReactSuspenseList', () => {
<span>Loading B</span>
</>,
);
await B.resolve();
await waitForAll(['A', 'B']);
await act(() => B.resolve());
assertLog(['A', 'B']);
expect(ReactNoop).toMatchRenderedOutput(
<>
<span>A</span>
@ -692,8 +684,8 @@ describe('ReactSuspenseList', () => {
<span>Loading D</span>
</>,
);
await D.resolve();
await waitForAll(['C', 'D']);
await act(() => D.resolve());
assertLog(['C', 'D']);
expect(ReactNoop).toMatchRenderedOutput(
<>
<span>C</span>
@ -742,9 +734,8 @@ describe('ReactSuspenseList', () => {
expect(ReactNoop).toMatchRenderedOutput(<span>Loading</span>);
await A.resolve();
await waitForAll(['A']);
await act(() => A.resolve());
assertLog(['A']);
expect(ReactNoop).toMatchRenderedOutput(<span>A</span>);
@ -775,9 +766,8 @@ describe('ReactSuspenseList', () => {
</>,
);
await B.resolve();
await waitForAll(['B', 'Suspend! [C]']);
await act(() => B.resolve());
assertLog(['B', 'Suspend! [C]']);
// Even though we could now show B, we're still waiting on C.
expect(ReactNoop).toMatchRenderedOutput(
@ -788,9 +778,8 @@ describe('ReactSuspenseList', () => {
</>,
);
await C.resolve();
await waitForAll(['B', 'C']);
await act(() => C.resolve());
assertLog(['B', 'C']);
expect(ReactNoop).toMatchRenderedOutput(
<>
@ -838,9 +827,8 @@ describe('ReactSuspenseList', () => {
expect(ReactNoop).toMatchRenderedOutput(null);
await A.resolve();
await waitForAll(['A']);
await act(() => A.resolve());
assertLog(['A']);
expect(ReactNoop).toMatchRenderedOutput(<span>A</span>);
@ -865,16 +853,14 @@ describe('ReactSuspenseList', () => {
// A is already showing content so it doesn't turn into a fallback.
expect(ReactNoop).toMatchRenderedOutput(<span>A</span>);
await B.resolve();
await waitForAll(['B', 'Suspend! [C]']);
await act(() => B.resolve());
assertLog(['B', 'Suspend! [C]']);
// Even though we could now show B, we're still waiting on C.
expect(ReactNoop).toMatchRenderedOutput(<span>A</span>);
await C.resolve();
await waitForAll(['B', 'C']);
await act(() => C.resolve());
assertLog(['B', 'C']);
expect(ReactNoop).toMatchRenderedOutput(
<>
@ -921,9 +907,8 @@ describe('ReactSuspenseList', () => {
</>,
);
await A.resolve();
await waitForAll(['A', 'Suspend! [B]']);
await act(() => A.resolve());
assertLog(['A', 'Suspend! [B]']);
expect(ReactNoop).toMatchRenderedOutput(
<>
@ -933,9 +918,8 @@ describe('ReactSuspenseList', () => {
</>,
);
await B.resolve();
await waitForAll(['B', 'C']);
await act(() => B.resolve());
assertLog(['B', 'C']);
expect(ReactNoop).toMatchRenderedOutput(
<>
@ -982,9 +966,8 @@ describe('ReactSuspenseList', () => {
</>,
);
await C.resolve();
await waitForAll(['C', 'Suspend! [B]']);
await act(() => C.resolve());
assertLog(['C', 'Suspend! [B]']);
expect(ReactNoop).toMatchRenderedOutput(
<>
@ -994,9 +977,8 @@ describe('ReactSuspenseList', () => {
</>,
);
await B.resolve();
await waitForAll(['B', 'A']);
await act(() => B.resolve());
assertLog(['B', 'A']);
expect(ReactNoop).toMatchRenderedOutput(
<>
@ -1089,9 +1071,8 @@ describe('ReactSuspenseList', () => {
</>,
);
await A.resolve();
await waitForAll(['A', 'Suspend! [C]']);
await act(() => A.resolve());
assertLog(['A', 'Suspend! [C]']);
// Even though we could show A, it is still in a fallback state because
// C is not yet resolved. We need to resolve everything in the head first.
@ -1106,9 +1087,8 @@ describe('ReactSuspenseList', () => {
</>,
);
await C.resolve();
await waitForAll(['A', 'C', 'Suspend! [E]']);
await act(() => C.resolve());
assertLog(['A', 'C', 'Suspend! [E]']);
// We can now resolve the full head.
expect(ReactNoop).toMatchRenderedOutput(
@ -1122,9 +1102,8 @@ describe('ReactSuspenseList', () => {
</>,
);
await E.resolve();
await waitForAll(['E', 'Suspend! [F]']);
await act(() => E.resolve());
assertLog(['E', 'Suspend! [F]']);
// In the tail we can resolve one-by-one.
expect(ReactNoop).toMatchRenderedOutput(
@ -1138,7 +1117,18 @@ describe('ReactSuspenseList', () => {
</>,
);
await F.resolve();
await act(() => F.resolve());
assertLog(['F']);
expect(ReactNoop).toMatchRenderedOutput(
<>
<span>A</span>
<span>B</span>
<span>C</span>
<span>D</span>
<span>E</span>
<span>F</span>
</>,
);
// We can also delete some items.
ReactNoop.render(
@ -1296,9 +1286,8 @@ describe('ReactSuspenseList', () => {
</>,
);
await D.resolve();
await waitForAll(['D', 'F', 'Suspend! [B]']);
await act(() => D.resolve());
assertLog(['D', 'F', 'Suspend! [B]']);
// We can now resolve the full head.
expect(ReactNoop).toMatchRenderedOutput(
@ -1314,9 +1303,8 @@ describe('ReactSuspenseList', () => {
</>,
);
await B.resolve();
await waitForAll(['B', 'Suspend! [A]']);
await act(() => B.resolve());
assertLog(['B', 'Suspend! [A]']);
// In the tail we can resolve one-by-one.
expect(ReactNoop).toMatchRenderedOutput(
@ -1331,9 +1319,8 @@ describe('ReactSuspenseList', () => {
</>,
);
await A.resolve();
await waitForAll(['A']);
await act(() => A.resolve());
assertLog(['A']);
expect(ReactNoop).toMatchRenderedOutput(
<>
@ -1366,47 +1353,43 @@ describe('ReactSuspenseList', () => {
}
// This render is only CPU bound. Nothing suspends.
if (gate(flags => flags.enableSyncDefaultUpdates)) {
await act(async () => {
React.startTransition(() => {
ReactNoop.render(<Foo />);
});
} else {
ReactNoop.render(<Foo />);
}
await waitFor(['A']);
await waitFor(['A']);
Scheduler.unstable_advanceTime(200);
jest.advanceTimersByTime(200);
Scheduler.unstable_advanceTime(200);
jest.advanceTimersByTime(200);
await waitFor(['B']);
await waitFor(['B']);
Scheduler.unstable_advanceTime(300);
jest.advanceTimersByTime(300);
Scheduler.unstable_advanceTime(300);
jest.advanceTimersByTime(300);
// We've still not been able to show anything on the screen even though
// we have two items ready.
expect(ReactNoop).toMatchRenderedOutput(null);
// We've still not been able to show anything on the screen even though
// we have two items ready.
expect(ReactNoop).toMatchRenderedOutput(null);
// Time has now elapsed for so long that we're just going to give up
// rendering the rest of the content. So that we can at least show
// something.
await waitFor([
'Loading C',
'C', // I'll flush through into the next render so that the first commits.
]);
expect(ReactNoop).toMatchRenderedOutput(
<>
<span>A</span>
<span>B</span>
<span>Loading C</span>
</>,
);
// Time has now elapsed for so long that we're just going to give up
// rendering the rest of the content. So that we can at least show
// something.
await waitFor([
'Loading C',
'C', // I'll flush through into the next render so that the first commits.
]);
expect(ReactNoop).toMatchRenderedOutput(
<>
<span>A</span>
<span>B</span>
<span>Loading C</span>
</>,
);
});
// Then we do a second pass to commit the last item.
await waitForAll([]);
assertLog([]);
expect(ReactNoop).toMatchRenderedOutput(
<>
<span>A</span>
@ -1473,9 +1456,8 @@ describe('ReactSuspenseList', () => {
</>,
);
await C.resolve();
await waitForAll(['C']);
await act(() => C.resolve());
await assertLog(['C']);
expect(ReactNoop).toMatchRenderedOutput(
<>
@ -1554,47 +1536,43 @@ describe('ReactSuspenseList', () => {
}
// This render is only CPU bound. Nothing suspends.
if (gate(flags => flags.enableSyncDefaultUpdates)) {
await act(async () => {
React.startTransition(() => {
ReactNoop.render(<Foo />);
});
} else {
ReactNoop.render(<Foo />);
}
await waitFor(['A']);
await waitFor(['A']);
Scheduler.unstable_advanceTime(200);
jest.advanceTimersByTime(200);
Scheduler.unstable_advanceTime(200);
jest.advanceTimersByTime(200);
await waitFor(['B']);
await waitFor(['B']);
Scheduler.unstable_advanceTime(300);
jest.advanceTimersByTime(300);
Scheduler.unstable_advanceTime(300);
jest.advanceTimersByTime(300);
// We've still not been able to show anything on the screen even though
// we have two items ready.
expect(ReactNoop).toMatchRenderedOutput(null);
// We've still not been able to show anything on the screen even though
// we have two items ready.
expect(ReactNoop).toMatchRenderedOutput(null);
// Time has now elapsed for so long that we're just going to give up
// rendering the rest of the content. So that we can at least show
// something.
await waitFor([
'Loading C',
'C', // I'll flush through into the next render so that the first commits.
]);
expect(ReactNoop).toMatchRenderedOutput(
<>
<span>A</span>
<span>B</span>
<span>Loading C</span>
</>,
);
// Time has now elapsed for so long that we're just going to give up
// rendering the rest of the content. So that we can at least show
// something.
await waitFor([
'Loading C',
'C', // I'll flush through into the next render so that the first commits.
]);
expect(ReactNoop).toMatchRenderedOutput(
<>
<span>A</span>
<span>B</span>
<span>Loading C</span>
</>,
);
});
// Then we do a second pass to commit the last two items.
await waitForAll(['D']);
assertLog(['D']);
expect(ReactNoop).toMatchRenderedOutput(
<>
<span>A</span>
@ -2073,9 +2051,8 @@ describe('ReactSuspenseList', () => {
expect(ReactNoop).toMatchRenderedOutput(<span>A</span>);
await B.resolve();
await waitForAll(['B', 'Suspend! [C]', 'Loading C']);
await act(() => B.resolve());
assertLog(['B', 'Suspend! [C]', 'Loading C']);
// Incremental loading is suspended.
jest.advanceTimersByTime(500);
@ -2087,9 +2064,8 @@ describe('ReactSuspenseList', () => {
</>,
);
await C.resolve();
await waitForAll(['C']);
await act(() => C.resolve());
assertLog(['C']);
expect(ReactNoop).toMatchRenderedOutput(
<>
@ -2149,9 +2125,8 @@ describe('ReactSuspenseList', () => {
</>,
);
await B.resolve();
await waitForAll(['A', 'B', 'C', 'D']);
await act(() => B.resolve());
assertLog(['A', 'B', 'C', 'D']);
expect(ReactNoop).toMatchRenderedOutput(
<>
@ -2191,9 +2166,8 @@ describe('ReactSuspenseList', () => {
expect(ReactNoop).toMatchRenderedOutput(<span>Loading C</span>);
await B.resolve();
await waitForAll(['A', 'B', 'C']);
await act(() => B.resolve());
assertLog(['A', 'B', 'C']);
expect(ReactNoop).toMatchRenderedOutput(
<>
@ -2252,9 +2226,8 @@ describe('ReactSuspenseList', () => {
</>,
);
await B.resolve();
await waitForAll(['B']);
await act(() => B.resolve());
assertLog(['B']);
expect(ReactNoop).toMatchRenderedOutput(
<>
@ -2385,9 +2358,8 @@ describe('ReactSuspenseList', () => {
</>,
);
await AsyncB.resolve();
await waitForAll(['B']);
await act(() => AsyncB.resolve());
assertLog(['B']);
expect(ReactNoop).toMatchRenderedOutput(
<>
@ -2474,9 +2446,8 @@ describe('ReactSuspenseList', () => {
</>,
);
await AsyncB.resolve();
await waitForAll(['B']);
await act(() => AsyncB.resolve());
assertLog(['B']);
expect(ReactNoop).toMatchRenderedOutput(
<>
@ -2555,9 +2526,8 @@ describe('ReactSuspenseList', () => {
expect(ReactNoop).toMatchRenderedOutput(<span>Loading A</span>);
});
await AsyncA.resolve();
await waitForAll(['A', 'B', 'C', 'D']);
await act(() => AsyncA.resolve());
assertLog(['A', 'B', 'C', 'D']);
expect(ReactNoop).toMatchRenderedOutput(
<>
@ -2916,9 +2886,8 @@ describe('ReactSuspenseList', () => {
// treeBaseDuration
expect(onRender.mock.calls[2][3]).toBe(1 + 4 + 3 + 3);
await C.resolve();
await waitForAll(['C', 'Suspend! [D]']);
await act(() => C.resolve());
assertLog(['C', 'Suspend! [D]']);
expect(ReactNoop).toMatchRenderedOutput(
<>
<span>A</span>
@ -3003,10 +2972,8 @@ describe('ReactSuspenseList', () => {
</>,
);
await A.resolve();
await waitForAll(['A', 'Suspend! [B]']);
await act(() => A.resolve());
assertLog(['A', 'Suspend! [B]']);
expect(ReactNoop).toMatchRenderedOutput(
<>
<span>A</span>
@ -3015,10 +2982,8 @@ describe('ReactSuspenseList', () => {
</>,
);
await B.resolve();
await waitForAll(['B', 'C']);
await act(() => B.resolve());
assertLog(['B', 'C']);
expect(ReactNoop).toMatchRenderedOutput(
<>
<span>A</span>

View File

@ -19,6 +19,7 @@ let TextResource;
let textResourceShouldFail;
let waitForAll;
let assertLog;
let act;
describe('ReactSuspensePlaceholder', () => {
beforeEach(() => {
@ -39,6 +40,7 @@ describe('ReactSuspensePlaceholder', () => {
const InternalTestUtils = require('internal-test-utils');
waitForAll = InternalTestUtils.waitForAll;
assertLog = InternalTestUtils.assertLog;
act = InternalTestUtils.act;
TextResource = ReactCache.unstable_createResource(
([text, ms = 0]) => {
@ -137,10 +139,8 @@ describe('ReactSuspensePlaceholder', () => {
await waitForAll(['A', 'Suspend! [B]', 'Loading...']);
expect(ReactNoop).toMatchRenderedOutput('Loading...');
jest.advanceTimersByTime(1000);
assertLog(['Promise resolved [B]']);
await waitForAll(['A', 'B', 'C']);
await act(() => jest.advanceTimersByTime(1000));
assertLog(['Promise resolved [B]', 'A', 'B', 'C']);
expect(ReactNoop).toMatchRenderedOutput(
<>
@ -167,9 +167,8 @@ describe('ReactSuspensePlaceholder', () => {
);
// Resolve the promise
jest.advanceTimersByTime(1000);
assertLog(['Promise resolved [B2]']);
await waitForAll(['B2', 'C']);
await act(() => jest.advanceTimersByTime(1000));
assertLog(['Promise resolved [B2]', 'B2', 'C']);
// Render the final update. A should still be hidden, because it was
// given a `hidden` prop.
@ -200,9 +199,8 @@ describe('ReactSuspensePlaceholder', () => {
expect(ReactNoop).not.toMatchRenderedOutput('ABC');
jest.advanceTimersByTime(1000);
assertLog(['Promise resolved [B]']);
await waitForAll(['A', 'B', 'C']);
await act(() => jest.advanceTimersByTime(1000));
assertLog(['Promise resolved [B]', 'A', 'B', 'C']);
expect(ReactNoop).toMatchRenderedOutput('ABC');
// Update
@ -214,9 +212,8 @@ describe('ReactSuspensePlaceholder', () => {
expect(ReactNoop).toMatchRenderedOutput('Loading...');
// Resolve the promise
jest.advanceTimersByTime(1000);
assertLog(['Promise resolved [B2]']);
await waitForAll(['A', 'B2', 'C']);
await act(() => jest.advanceTimersByTime(1000));
assertLog(['Promise resolved [B2]', 'A', 'B2', 'C']);
// Render the final update. A should still be hidden, because it was
// given a `hidden` prop.
@ -245,9 +242,8 @@ describe('ReactSuspensePlaceholder', () => {
expect(ReactNoop).toMatchRenderedOutput(<uppercase>LOADING...</uppercase>);
jest.advanceTimersByTime(1000);
assertLog(['Promise resolved [b]']);
await waitForAll(['a', 'b', 'c']);
await act(() => jest.advanceTimersByTime(1000));
assertLog(['Promise resolved [b]', 'a', 'b', 'c']);
expect(ReactNoop).toMatchRenderedOutput(<uppercase>ABC</uppercase>);
// Update
@ -259,9 +255,8 @@ describe('ReactSuspensePlaceholder', () => {
expect(ReactNoop).toMatchRenderedOutput(<uppercase>LOADING...</uppercase>);
// Resolve the promise
jest.advanceTimersByTime(1000);
assertLog(['Promise resolved [b2]']);
await waitForAll(['a', 'b2', 'c']);
await act(() => jest.advanceTimersByTime(1000));
assertLog(['Promise resolved [b2]', 'a', 'b2', 'c']);
// Render the final update. A should still be hidden, because it was
// given a `hidden` prop.
@ -358,9 +353,13 @@ describe('ReactSuspensePlaceholder', () => {
expect(onRender.mock.calls[0][3]).toBe(10);
// Resolve the pending promise.
jest.advanceTimersByTime(1000);
assertLog(['Promise resolved [Loaded]']);
await waitForAll(['Suspending', 'Loaded', 'Text']);
await act(() => jest.advanceTimersByTime(1000));
assertLog([
'Promise resolved [Loaded]',
'Suspending',
'Loaded',
'Text',
]);
expect(ReactNoop).toMatchRenderedOutput('LoadedText');
expect(onRender).toHaveBeenCalledTimes(2);
@ -531,9 +530,14 @@ describe('ReactSuspensePlaceholder', () => {
expect(onRender).toHaveBeenCalledTimes(3);
// Resolve the pending promise.
jest.advanceTimersByTime(100);
assertLog(['Promise resolved [Loaded]', 'Promise resolved [Sibling]']);
await waitForAll(['Suspending', 'Loaded', 'New', 'Sibling']);
await act(async () => {
jest.advanceTimersByTime(100);
assertLog([
'Promise resolved [Loaded]',
'Promise resolved [Sibling]',
]);
await waitForAll(['Suspending', 'Loaded', 'New', 'Sibling']);
});
expect(onRender).toHaveBeenCalledTimes(4);
// When the suspending data is resolved and our final UI is rendered,

View File

@ -481,7 +481,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
return (
<Suspense fallback={<Text text="Loading..." />}>
<ErrorBoundary ref={errorBoundary}>
<AsyncText text="Result" ms={3000} />
<AsyncText text="Result" />
</ErrorBoundary>
</Suspense>
);
@ -491,9 +491,8 @@ describe('ReactSuspenseWithNoopRenderer', () => {
await waitForAll(['Suspend! [Result]', 'Loading...']);
expect(ReactNoop).toMatchRenderedOutput(<span prop="Loading..." />);
await rejectText('Result', new Error('Failed to load: Result'));
await waitForAll([
await act(() => rejectText('Result', new Error('Failed to load: Result')));
assertLog([
'Error! [Result]',
// React retries one more time
@ -3565,20 +3564,17 @@ describe('ReactSuspenseWithNoopRenderer', () => {
);
await resolveText('A1');
await waitFor([
'A1',
'Suspend! [A2]',
'Loading...',
'Suspend! [B2]',
'Loading...',
]);
expect(root).toMatchRenderedOutput(
<>
<span prop="A1" />
<span prop="B" />
</>,
);
await waitFor(['A1']);
});
assertLog(['Suspend! [A2]', 'Loading...', 'Suspend! [B2]', 'Loading...']);
expect(root).toMatchRenderedOutput(
<>
<span prop="A1" />
<span prop="B" />
</>,
);
await act(async () => {
await resolveText('A2');
await resolveText('B2');
});