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

View File

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

View File

@ -37,6 +37,7 @@ let waitForAll;
let waitForThrow; let waitForThrow;
let assertLog; let assertLog;
let Scheduler; let Scheduler;
let clientAct;
function resetJSDOM(markup) { function resetJSDOM(markup) {
// Test Environment // Test Environment
@ -71,6 +72,7 @@ describe('ReactDOMFloat', () => {
waitForAll = InternalTestUtils.waitForAll; waitForAll = InternalTestUtils.waitForAll;
waitForThrow = InternalTestUtils.waitForThrow; waitForThrow = InternalTestUtils.waitForThrow;
assertLog = InternalTestUtils.assertLog; assertLog = InternalTestUtils.assertLog;
clientAct = InternalTestUtils.act;
textCache = new Map(); textCache = new Map();
loadCache = new Set(); 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 // 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 // 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. // to thenables however this slightly increases the fizz runtime code size.
await loadStylesheets(); await clientAct(() => loadStylesheets());
assertLog(['load stylesheet: foo']); assertLog(['load stylesheet: foo']);
expect(getMeaningfulChildren(document)).toEqual( expect(getMeaningfulChildren(document)).toEqual(
<html> <html>

View File

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

View File

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

View File

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

View File

@ -106,12 +106,14 @@ describe('memo', () => {
} }
Counter = memo(Counter); Counter = memo(Counter);
await act(() =>
ReactNoop.render( ReactNoop.render(
<Suspense fallback={<Text text="Loading..." />}> <Suspense fallback={<Text text="Loading..." />}>
<Counter count={0} /> <Counter count={0} />
</Suspense>, </Suspense>,
),
); );
await waitForAll(['Loading...', 0]); assertLog(['Loading...', 0]);
expect(ReactNoop).toMatchRenderedOutput(<span prop={0} />); expect(ReactNoop).toMatchRenderedOutput(<span prop={0} />);
// Should bail out because props have not changed // Should bail out because props have not changed
@ -163,8 +165,8 @@ describe('memo', () => {
} }
const parent = React.createRef(null); const parent = React.createRef(null);
ReactNoop.render(<Parent ref={parent} />); await act(() => ReactNoop.render(<Parent ref={parent} />));
await waitForAll(['Loading...', 'Count: 0']); assertLog(['Loading...', 'Count: 0']);
expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />); expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
// Should bail out because props have not changed // Should bail out because props have not changed
@ -340,12 +342,14 @@ describe('memo', () => {
return oldProps.count === newProps.count; return oldProps.count === newProps.count;
}); });
await act(() =>
ReactNoop.render( ReactNoop.render(
<Suspense fallback={<Text text="Loading..." />}> <Suspense fallback={<Text text="Loading..." />}>
<Counter count={0} /> <Counter count={0} />
</Suspense>, </Suspense>,
),
); );
await waitForAll(['Loading...', 0]); assertLog(['Loading...', 0]);
expect(ReactNoop).toMatchRenderedOutput(<span prop={0} />); expect(ReactNoop).toMatchRenderedOutput(<span prop={0} />);
// Should bail out because props have not changed // Should bail out because props have not changed
@ -376,12 +380,14 @@ describe('memo', () => {
} }
const Counter = memo(CounterInner); const Counter = memo(CounterInner);
await act(() =>
ReactNoop.render( ReactNoop.render(
<Suspense fallback={<Text text="Loading..." />}> <Suspense fallback={<Text text="Loading..." />}>
<Counter count={0} /> <Counter count={0} />
</Suspense>, </Suspense>,
),
); );
await waitForAll(['Loading...', '0!']); assertLog(['Loading...', '0!']);
expect(ReactNoop).toMatchRenderedOutput(<span prop="0!" />); expect(ReactNoop).toMatchRenderedOutput(<span prop="0!" />);
// Should bail out because props have not changed // 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). // The final layer uses memo() from test fixture (which might be lazy).
Counter = memo(Counter); Counter = memo(Counter);
await expect(async () => {
await act(() => {
ReactNoop.render( ReactNoop.render(
<Suspense fallback={<Text text="Loading..." />}> <Suspense fallback={<Text text="Loading..." />}>
<Counter e={5} /> <Counter e={5} />
</Suspense>, </Suspense>,
); );
await expect(async () => { });
await waitForAll(['Loading...', 15]); assertLog(['Loading...', 15]);
}).toErrorDev([ }).toErrorDev([
'Counter: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.', '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 // Resolve first Suspense's promise and switch back to the normal view. The
// second Suspense should still show the placeholder // second Suspense should still show the placeholder
await resolveText('A'); await act(() => resolveText('A'));
await waitForAll(['A']); assertLog(['A']);
expect(root).toMatchRenderedOutput('ALoading B...'); expect(root).toMatchRenderedOutput('ALoading B...');
// Resolve the second Suspense's promise resolves and switche back to the // Resolve the second Suspense's promise resolves and switche back to the
// normal view // normal view
await resolveText('B'); await act(() => resolveText('B'));
await waitForAll(['B']); assertLog(['B']);
expect(root).toMatchRenderedOutput('AB'); expect(root).toMatchRenderedOutput('AB');
}); });
@ -294,13 +294,10 @@ describe('ReactSuspense', () => {
// showing the inner fallback hoping that B will resolve soon enough. // showing the inner fallback hoping that B will resolve soon enough.
expect(root).toMatchRenderedOutput('Loading...'); expect(root).toMatchRenderedOutput('Loading...');
await act(() => resolveText('B'));
// By this point, B has resolved. // By this point, B has resolved.
// We're still showing the outer fallback. // The contents of both should pop in together.
await resolveText('B'); assertLog(['A', 'B']);
expect(root).toMatchRenderedOutput('Loading...');
await waitForAll(['A', 'B']);
// Then contents of both should pop in together.
expect(root).toMatchRenderedOutput('AB'); expect(root).toMatchRenderedOutput('AB');
}); });
@ -337,8 +334,8 @@ describe('ReactSuspense', () => {
jest.advanceTimersByTime(500); jest.advanceTimersByTime(500);
expect(root).toMatchRenderedOutput('ALoading more...'); expect(root).toMatchRenderedOutput('ALoading more...');
await resolveText('B'); await act(() => resolveText('B'));
await waitForAll(['B']); assertLog(['B']);
expect(root).toMatchRenderedOutput('AB'); expect(root).toMatchRenderedOutput('AB');
}); });
@ -475,15 +472,15 @@ describe('ReactSuspense', () => {
}); });
await waitForAll(['Suspend! [default]', 'Loading...']); await waitForAll(['Suspend! [default]', 'Loading...']);
await resolveText('default'); await act(() => resolveText('default'));
await waitForAll(['default']); assertLog(['default']);
expect(root).toMatchRenderedOutput('default'); expect(root).toMatchRenderedOutput('default');
await act(() => setValue('new value')); await act(() => setValue('new value'));
assertLog(['Suspend! [new value]', 'Loading...']); assertLog(['Suspend! [new value]', 'Loading...']);
await resolveText('new value'); await act(() => resolveText('new value'));
await waitForAll(['new value']); assertLog(['new value']);
expect(root).toMatchRenderedOutput('new value'); expect(root).toMatchRenderedOutput('new value');
}); });
@ -521,15 +518,15 @@ describe('ReactSuspense', () => {
}); });
await waitForAll(['Suspend! [default]', 'Loading...']); await waitForAll(['Suspend! [default]', 'Loading...']);
await resolveText('default'); await act(() => resolveText('default'));
await waitForAll(['default']); assertLog(['default']);
expect(root).toMatchRenderedOutput('default'); expect(root).toMatchRenderedOutput('default');
await act(() => setValue('new value')); await act(() => setValue('new value'));
assertLog(['Suspend! [new value]', 'Loading...']); assertLog(['Suspend! [new value]', 'Loading...']);
await resolveText('new value'); await act(() => resolveText('new value'));
await waitForAll(['new value']); assertLog(['new value']);
expect(root).toMatchRenderedOutput('new value'); expect(root).toMatchRenderedOutput('new value');
}); });
@ -565,15 +562,15 @@ describe('ReactSuspense', () => {
); );
await waitForAll(['Suspend! [default]', 'Loading...']); await waitForAll(['Suspend! [default]', 'Loading...']);
await resolveText('default'); await act(() => resolveText('default'));
await waitForAll(['default']); assertLog(['default']);
expect(root).toMatchRenderedOutput('default'); expect(root).toMatchRenderedOutput('default');
await act(() => setValue('new value')); await act(() => setValue('new value'));
assertLog(['Suspend! [new value]', 'Loading...']); assertLog(['Suspend! [new value]', 'Loading...']);
await resolveText('new value'); await act(() => resolveText('new value'));
await waitForAll(['new value']); assertLog(['new value']);
expect(root).toMatchRenderedOutput('new value'); expect(root).toMatchRenderedOutput('new value');
}); });
@ -609,15 +606,15 @@ describe('ReactSuspense', () => {
); );
await waitForAll(['Suspend! [default]', 'Loading...']); await waitForAll(['Suspend! [default]', 'Loading...']);
await resolveText('default'); await act(() => resolveText('default'));
await waitForAll(['default']); assertLog(['default']);
expect(root).toMatchRenderedOutput('default'); expect(root).toMatchRenderedOutput('default');
await act(() => setValue('new value')); await act(() => setValue('new value'));
assertLog(['Suspend! [new value]', 'Loading...']); assertLog(['Suspend! [new value]', 'Loading...']);
await resolveText('new value'); await act(() => resolveText('new value'));
await waitForAll(['new value']); assertLog(['new value']);
expect(root).toMatchRenderedOutput('new value'); expect(root).toMatchRenderedOutput('new value');
}); });
@ -662,8 +659,8 @@ describe('ReactSuspense', () => {
'destroy layout', 'destroy layout',
]); ]);
await resolveText('Child 2'); await act(() => resolveText('Child 2'));
await waitForAll(['Child 1', 'Child 2', 'create layout']); assertLog(['Child 1', 'Child 2', 'create layout']);
expect(root).toMatchRenderedOutput(['Child 1', 'Child 2'].join('')); expect(root).toMatchRenderedOutput(['Child 1', 'Child 2'].join(''));
}); });
@ -920,8 +917,8 @@ describe('ReactSuspense', () => {
// Initial render // Initial render
await waitForAll(['Suspend! [Step: 1]', 'Loading...']); await waitForAll(['Suspend! [Step: 1]', 'Loading...']);
await resolveText('Step: 1'); await act(() => resolveText('Step: 1'));
await waitForAll(['Step: 1']); assertLog(['Step: 1']);
expect(root).toMatchRenderedOutput('Step: 1'); expect(root).toMatchRenderedOutput('Step: 1');
// Update that suspends // Update that suspends
@ -936,9 +933,11 @@ describe('ReactSuspense', () => {
await waitForAll(['Suspend! [Step: 3]']); await waitForAll(['Suspend! [Step: 3]']);
expect(root).toMatchRenderedOutput('Loading...'); expect(root).toMatchRenderedOutput('Loading...');
await resolveText('Step: 2'); await act(() => {
await resolveText('Step: 3'); resolveText('Step: 2');
await waitForAll(['Step: 3']); resolveText('Step: 3');
});
assertLog(['Step: 3']);
expect(root).toMatchRenderedOutput('Step: 3'); expect(root).toMatchRenderedOutput('Step: 3');
}); });
@ -996,8 +995,8 @@ describe('ReactSuspense', () => {
function App(props) { function App(props) {
return ( return (
<Suspense fallback={<Text text="Loading..." />}> <Suspense fallback={<Text text="Loading..." />}>
<AsyncText ms={1000} text="Child 1" /> <AsyncText text="Child 1" />
<AsyncText ms={7000} text="Child 2" /> <AsyncText text="Child 2" />
</Suspense> </Suspense>
); );
} }
@ -1012,8 +1011,8 @@ describe('ReactSuspense', () => {
jest.advanceTimersByTime(6000); jest.advanceTimersByTime(6000);
await resolveText('Child 2'); await act(() => resolveText('Child 2'));
await waitForAll(['Child 1', 'Child 2']); assertLog(['Child 1', 'Child 2']);
expect(root).toMatchRenderedOutput(['Child 1', 'Child 2'].join('')); expect(root).toMatchRenderedOutput(['Child 1', 'Child 2'].join(''));
}); });

View File

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

View File

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

View File

@ -249,9 +249,8 @@ describe('ReactSuspenseList', () => {
</>, </>,
); );
await C.resolve(); await act(() => C.resolve());
assertLog(['C']);
await waitForAll(['C']);
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
<> <>
@ -261,9 +260,8 @@ describe('ReactSuspenseList', () => {
</>, </>,
); );
await B.resolve(); await act(() => B.resolve());
assertLog(['B']);
await waitForAll(['B']);
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
<> <>
@ -384,9 +382,8 @@ describe('ReactSuspenseList', () => {
</>, </>,
); );
await B.resolve(); await act(() => B.resolve());
assertLog(['A', 'B', 'Suspend! [C]']);
await waitForAll(['A', 'B', 'Suspend! [C]']);
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
<> <>
@ -396,9 +393,8 @@ describe('ReactSuspenseList', () => {
</>, </>,
); );
await C.resolve(); await act(() => C.resolve());
assertLog(['A', 'B', 'C']);
await waitForAll(['A', 'B', 'C']);
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
<> <>
@ -462,9 +458,8 @@ describe('ReactSuspenseList', () => {
</>, </>,
); );
await B.resolve(); await act(() => B.resolve());
assertLog(['A', 'B', 'Suspend! [C]']);
await waitForAll(['A', 'B', 'Suspend! [C]']);
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
<> <>
@ -478,9 +473,8 @@ describe('ReactSuspenseList', () => {
</>, </>,
); );
await C.resolve(); await act(() => C.resolve());
assertLog(['A', 'B', 'C']);
await waitForAll(['A', 'B', 'C']);
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
<> <>
@ -544,9 +538,8 @@ describe('ReactSuspenseList', () => {
</>, </>,
); );
await C.resolve(); await act(() => C.resolve());
assertLog(['A', 'B', 'C']);
await waitForAll(['A', 'B', 'C']);
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
<> <>
@ -604,9 +597,8 @@ describe('ReactSuspenseList', () => {
</>, </>,
); );
await C.resolve(); await act(() => C.resolve());
assertLog(['A', 'B', 'C']);
await waitForAll(['A', 'B', 'C']);
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
<> <>
@ -667,8 +659,8 @@ describe('ReactSuspenseList', () => {
<span>Loading B</span> <span>Loading B</span>
</>, </>,
); );
await B.resolve(); await act(() => B.resolve());
await waitForAll(['A', 'B']); assertLog(['A', 'B']);
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
<> <>
<span>A</span> <span>A</span>
@ -692,8 +684,8 @@ describe('ReactSuspenseList', () => {
<span>Loading D</span> <span>Loading D</span>
</>, </>,
); );
await D.resolve(); await act(() => D.resolve());
await waitForAll(['C', 'D']); assertLog(['C', 'D']);
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
<> <>
<span>C</span> <span>C</span>
@ -742,9 +734,8 @@ describe('ReactSuspenseList', () => {
expect(ReactNoop).toMatchRenderedOutput(<span>Loading</span>); expect(ReactNoop).toMatchRenderedOutput(<span>Loading</span>);
await A.resolve(); await act(() => A.resolve());
assertLog(['A']);
await waitForAll(['A']);
expect(ReactNoop).toMatchRenderedOutput(<span>A</span>); expect(ReactNoop).toMatchRenderedOutput(<span>A</span>);
@ -775,9 +766,8 @@ describe('ReactSuspenseList', () => {
</>, </>,
); );
await B.resolve(); await act(() => B.resolve());
assertLog(['B', 'Suspend! [C]']);
await waitForAll(['B', 'Suspend! [C]']);
// Even though we could now show B, we're still waiting on C. // Even though we could now show B, we're still waiting on C.
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
@ -788,9 +778,8 @@ describe('ReactSuspenseList', () => {
</>, </>,
); );
await C.resolve(); await act(() => C.resolve());
assertLog(['B', 'C']);
await waitForAll(['B', 'C']);
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
<> <>
@ -838,9 +827,8 @@ describe('ReactSuspenseList', () => {
expect(ReactNoop).toMatchRenderedOutput(null); expect(ReactNoop).toMatchRenderedOutput(null);
await A.resolve(); await act(() => A.resolve());
assertLog(['A']);
await waitForAll(['A']);
expect(ReactNoop).toMatchRenderedOutput(<span>A</span>); 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. // A is already showing content so it doesn't turn into a fallback.
expect(ReactNoop).toMatchRenderedOutput(<span>A</span>); expect(ReactNoop).toMatchRenderedOutput(<span>A</span>);
await B.resolve(); await act(() => B.resolve());
assertLog(['B', 'Suspend! [C]']);
await waitForAll(['B', 'Suspend! [C]']);
// Even though we could now show B, we're still waiting on C. // Even though we could now show B, we're still waiting on C.
expect(ReactNoop).toMatchRenderedOutput(<span>A</span>); expect(ReactNoop).toMatchRenderedOutput(<span>A</span>);
await C.resolve(); await act(() => C.resolve());
assertLog(['B', 'C']);
await waitForAll(['B', 'C']);
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
<> <>
@ -921,9 +907,8 @@ describe('ReactSuspenseList', () => {
</>, </>,
); );
await A.resolve(); await act(() => A.resolve());
assertLog(['A', 'Suspend! [B]']);
await waitForAll(['A', 'Suspend! [B]']);
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
<> <>
@ -933,9 +918,8 @@ describe('ReactSuspenseList', () => {
</>, </>,
); );
await B.resolve(); await act(() => B.resolve());
assertLog(['B', 'C']);
await waitForAll(['B', 'C']);
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
<> <>
@ -982,9 +966,8 @@ describe('ReactSuspenseList', () => {
</>, </>,
); );
await C.resolve(); await act(() => C.resolve());
assertLog(['C', 'Suspend! [B]']);
await waitForAll(['C', 'Suspend! [B]']);
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
<> <>
@ -994,9 +977,8 @@ describe('ReactSuspenseList', () => {
</>, </>,
); );
await B.resolve(); await act(() => B.resolve());
assertLog(['B', 'A']);
await waitForAll(['B', 'A']);
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
<> <>
@ -1089,9 +1071,8 @@ describe('ReactSuspenseList', () => {
</>, </>,
); );
await A.resolve(); await act(() => A.resolve());
assertLog(['A', 'Suspend! [C]']);
await waitForAll(['A', 'Suspend! [C]']);
// Even though we could show A, it is still in a fallback state because // 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. // C is not yet resolved. We need to resolve everything in the head first.
@ -1106,9 +1087,8 @@ describe('ReactSuspenseList', () => {
</>, </>,
); );
await C.resolve(); await act(() => C.resolve());
assertLog(['A', 'C', 'Suspend! [E]']);
await waitForAll(['A', 'C', 'Suspend! [E]']);
// We can now resolve the full head. // We can now resolve the full head.
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
@ -1122,9 +1102,8 @@ describe('ReactSuspenseList', () => {
</>, </>,
); );
await E.resolve(); await act(() => E.resolve());
assertLog(['E', 'Suspend! [F]']);
await waitForAll(['E', 'Suspend! [F]']);
// In the tail we can resolve one-by-one. // In the tail we can resolve one-by-one.
expect(ReactNoop).toMatchRenderedOutput( 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. // We can also delete some items.
ReactNoop.render( ReactNoop.render(
@ -1296,9 +1286,8 @@ describe('ReactSuspenseList', () => {
</>, </>,
); );
await D.resolve(); await act(() => D.resolve());
assertLog(['D', 'F', 'Suspend! [B]']);
await waitForAll(['D', 'F', 'Suspend! [B]']);
// We can now resolve the full head. // We can now resolve the full head.
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
@ -1314,9 +1303,8 @@ describe('ReactSuspenseList', () => {
</>, </>,
); );
await B.resolve(); await act(() => B.resolve());
assertLog(['B', 'Suspend! [A]']);
await waitForAll(['B', 'Suspend! [A]']);
// In the tail we can resolve one-by-one. // In the tail we can resolve one-by-one.
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
@ -1331,9 +1319,8 @@ describe('ReactSuspenseList', () => {
</>, </>,
); );
await A.resolve(); await act(() => A.resolve());
assertLog(['A']);
await waitForAll(['A']);
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
<> <>
@ -1366,13 +1353,10 @@ describe('ReactSuspenseList', () => {
} }
// This render is only CPU bound. Nothing suspends. // This render is only CPU bound. Nothing suspends.
if (gate(flags => flags.enableSyncDefaultUpdates)) { await act(async () => {
React.startTransition(() => { React.startTransition(() => {
ReactNoop.render(<Foo />); ReactNoop.render(<Foo />);
}); });
} else {
ReactNoop.render(<Foo />);
}
await waitFor(['A']); await waitFor(['A']);
@ -1403,10 +1387,9 @@ describe('ReactSuspenseList', () => {
<span>Loading C</span> <span>Loading C</span>
</>, </>,
); );
});
// Then we do a second pass to commit the last item. // Then we do a second pass to commit the last item.
await waitForAll([]); assertLog([]);
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
<> <>
<span>A</span> <span>A</span>
@ -1473,9 +1456,8 @@ describe('ReactSuspenseList', () => {
</>, </>,
); );
await C.resolve(); await act(() => C.resolve());
await assertLog(['C']);
await waitForAll(['C']);
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
<> <>
@ -1554,13 +1536,10 @@ describe('ReactSuspenseList', () => {
} }
// This render is only CPU bound. Nothing suspends. // This render is only CPU bound. Nothing suspends.
if (gate(flags => flags.enableSyncDefaultUpdates)) { await act(async () => {
React.startTransition(() => { React.startTransition(() => {
ReactNoop.render(<Foo />); ReactNoop.render(<Foo />);
}); });
} else {
ReactNoop.render(<Foo />);
}
await waitFor(['A']); await waitFor(['A']);
@ -1591,10 +1570,9 @@ describe('ReactSuspenseList', () => {
<span>Loading C</span> <span>Loading C</span>
</>, </>,
); );
});
// Then we do a second pass to commit the last two items. // Then we do a second pass to commit the last two items.
await waitForAll(['D']); assertLog(['D']);
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
<> <>
<span>A</span> <span>A</span>
@ -2073,9 +2051,8 @@ describe('ReactSuspenseList', () => {
expect(ReactNoop).toMatchRenderedOutput(<span>A</span>); expect(ReactNoop).toMatchRenderedOutput(<span>A</span>);
await B.resolve(); await act(() => B.resolve());
assertLog(['B', 'Suspend! [C]', 'Loading C']);
await waitForAll(['B', 'Suspend! [C]', 'Loading C']);
// Incremental loading is suspended. // Incremental loading is suspended.
jest.advanceTimersByTime(500); jest.advanceTimersByTime(500);
@ -2087,9 +2064,8 @@ describe('ReactSuspenseList', () => {
</>, </>,
); );
await C.resolve(); await act(() => C.resolve());
assertLog(['C']);
await waitForAll(['C']);
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
<> <>
@ -2149,9 +2125,8 @@ describe('ReactSuspenseList', () => {
</>, </>,
); );
await B.resolve(); await act(() => B.resolve());
assertLog(['A', 'B', 'C', 'D']);
await waitForAll(['A', 'B', 'C', 'D']);
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
<> <>
@ -2191,9 +2166,8 @@ describe('ReactSuspenseList', () => {
expect(ReactNoop).toMatchRenderedOutput(<span>Loading C</span>); expect(ReactNoop).toMatchRenderedOutput(<span>Loading C</span>);
await B.resolve(); await act(() => B.resolve());
assertLog(['A', 'B', 'C']);
await waitForAll(['A', 'B', 'C']);
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
<> <>
@ -2252,9 +2226,8 @@ describe('ReactSuspenseList', () => {
</>, </>,
); );
await B.resolve(); await act(() => B.resolve());
assertLog(['B']);
await waitForAll(['B']);
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
<> <>
@ -2385,9 +2358,8 @@ describe('ReactSuspenseList', () => {
</>, </>,
); );
await AsyncB.resolve(); await act(() => AsyncB.resolve());
assertLog(['B']);
await waitForAll(['B']);
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
<> <>
@ -2474,9 +2446,8 @@ describe('ReactSuspenseList', () => {
</>, </>,
); );
await AsyncB.resolve(); await act(() => AsyncB.resolve());
assertLog(['B']);
await waitForAll(['B']);
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
<> <>
@ -2555,9 +2526,8 @@ describe('ReactSuspenseList', () => {
expect(ReactNoop).toMatchRenderedOutput(<span>Loading A</span>); expect(ReactNoop).toMatchRenderedOutput(<span>Loading A</span>);
}); });
await AsyncA.resolve(); await act(() => AsyncA.resolve());
assertLog(['A', 'B', 'C', 'D']);
await waitForAll(['A', 'B', 'C', 'D']);
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
<> <>
@ -2916,9 +2886,8 @@ describe('ReactSuspenseList', () => {
// treeBaseDuration // treeBaseDuration
expect(onRender.mock.calls[2][3]).toBe(1 + 4 + 3 + 3); expect(onRender.mock.calls[2][3]).toBe(1 + 4 + 3 + 3);
await C.resolve(); await act(() => C.resolve());
assertLog(['C', 'Suspend! [D]']);
await waitForAll(['C', 'Suspend! [D]']);
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
<> <>
<span>A</span> <span>A</span>
@ -3003,10 +2972,8 @@ describe('ReactSuspenseList', () => {
</>, </>,
); );
await A.resolve(); await act(() => A.resolve());
assertLog(['A', 'Suspend! [B]']);
await waitForAll(['A', 'Suspend! [B]']);
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
<> <>
<span>A</span> <span>A</span>
@ -3015,10 +2982,8 @@ describe('ReactSuspenseList', () => {
</>, </>,
); );
await B.resolve(); await act(() => B.resolve());
assertLog(['B', 'C']);
await waitForAll(['B', 'C']);
expect(ReactNoop).toMatchRenderedOutput( expect(ReactNoop).toMatchRenderedOutput(
<> <>
<span>A</span> <span>A</span>

View File

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

View File

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