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:
parent
21021fb0f0
commit
72c890e312
|
@ -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');
|
||||
});
|
||||
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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(
|
||||
const root = ReactTestRenderer.create(null, {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
|
||||
let error;
|
||||
try {
|
||||
await act(() => {
|
||||
root.update(
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
<LazyText text="Hi" />
|
||||
</Suspense>,
|
||||
{
|
||||
unstable_isConcurrent: true,
|
||||
},
|
||||
);
|
||||
await waitForThrow('Element type is invalid');
|
||||
});
|
||||
} 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(
|
||||
const root = ReactTestRenderer.create(null, {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
|
||||
let error;
|
||||
try {
|
||||
await act(() => {
|
||||
root.update(
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
<LazyText text="Hi" />
|
||||
</Suspense>,
|
||||
{
|
||||
unstable_isConcurrent: true,
|
||||
},
|
||||
);
|
||||
});
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
|
||||
await waitForThrow('Bad network');
|
||||
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 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']);
|
||||
});
|
||||
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');
|
||||
});
|
||||
|
||||
|
|
|
@ -106,12 +106,14 @@ describe('memo', () => {
|
|||
}
|
||||
Counter = memo(Counter);
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
ReactNoop.render(
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
<Counter e={5} />
|
||||
</Suspense>,
|
||||
);
|
||||
await expect(async () => {
|
||||
await waitForAll(['Loading...', 15]);
|
||||
});
|
||||
assertLog(['Loading...', 15]);
|
||||
}).toErrorDev([
|
||||
'Counter: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
|
||||
]);
|
||||
|
|
|
@ -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(''));
|
||||
});
|
||||
|
||||
|
|
|
@ -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([]);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,13 +1353,10 @@ 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']);
|
||||
|
||||
|
@ -1403,10 +1387,9 @@ describe('ReactSuspenseList', () => {
|
|||
<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,13 +1536,10 @@ 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']);
|
||||
|
||||
|
@ -1591,10 +1570,9 @@ describe('ReactSuspenseList', () => {
|
|||
<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>
|
||||
|
|
|
@ -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.
|
||||
await act(async () => {
|
||||
jest.advanceTimersByTime(100);
|
||||
assertLog(['Promise resolved [Loaded]', 'Promise resolved [Sibling]']);
|
||||
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,
|
||||
|
|
|
@ -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,13 +3564,9 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
|||
);
|
||||
|
||||
await resolveText('A1');
|
||||
await waitFor([
|
||||
'A1',
|
||||
'Suspend! [A2]',
|
||||
'Loading...',
|
||||
'Suspend! [B2]',
|
||||
'Loading...',
|
||||
]);
|
||||
await waitFor(['A1']);
|
||||
});
|
||||
assertLog(['Suspend! [A2]', 'Loading...', 'Suspend! [B2]', 'Loading...']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="A1" />
|
||||
|
@ -3579,6 +3574,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
|||
</>,
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await resolveText('A2');
|
||||
await resolveText('B2');
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue