Codemod act -> await act (4/?) (#26338)
Similar to the rationale for `waitFor` (see #26285), we should always await the result of an `act` call so that microtasks have a chance to fire. This only affects the internal `act` that we use in our repo, for now. In the public `act` API, we don't yet require this; however, we effectively will for any update that triggers suspense once `use` lands. So we likely will start warning in an upcoming minor.
This commit is contained in:
parent
9fb2469a63
commit
702fc984e6
|
@ -984,7 +984,7 @@ describe('ReactHooksInspectionIntegration', () => {
|
|||
children: ['count: ', '1'],
|
||||
});
|
||||
|
||||
act(incrementCount);
|
||||
await act(async () => incrementCount());
|
||||
expect(renderer.toJSON()).toEqual({
|
||||
type: 'div',
|
||||
props: {},
|
||||
|
|
|
@ -1069,8 +1069,8 @@ describe('InspectedElement', () => {
|
|||
});
|
||||
|
||||
async function loadPath(path) {
|
||||
TestUtilsAct(() => {
|
||||
TestRendererAct(() => {
|
||||
await TestUtilsAct(async () => {
|
||||
await TestRendererAct(async () => {
|
||||
inspectElementPath(path);
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
|
@ -1224,8 +1224,8 @@ describe('InspectedElement', () => {
|
|||
});
|
||||
|
||||
async function loadPath(path) {
|
||||
TestUtilsAct(() => {
|
||||
TestRendererAct(() => {
|
||||
await TestUtilsAct(async () => {
|
||||
await TestRendererAct(async () => {
|
||||
inspectElementPath(path);
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
|
@ -1306,8 +1306,8 @@ describe('InspectedElement', () => {
|
|||
});
|
||||
|
||||
async function loadPath(path) {
|
||||
TestUtilsAct(() => {
|
||||
TestRendererAct(() => {
|
||||
await TestUtilsAct(async () => {
|
||||
await TestRendererAct(async () => {
|
||||
inspectElementPath(path);
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
|
@ -1375,8 +1375,8 @@ describe('InspectedElement', () => {
|
|||
}
|
||||
`);
|
||||
|
||||
TestRendererAct(() => {
|
||||
TestUtilsAct(() => {
|
||||
await TestRendererAct(async () => {
|
||||
await TestUtilsAct(async () => {
|
||||
legacyRender(
|
||||
<Example
|
||||
nestedObject={{
|
||||
|
@ -1469,8 +1469,8 @@ describe('InspectedElement', () => {
|
|||
});
|
||||
|
||||
async function loadPath(path) {
|
||||
TestUtilsAct(() => {
|
||||
TestRendererAct(() => {
|
||||
await TestUtilsAct(async () => {
|
||||
await TestRendererAct(async () => {
|
||||
inspectElementPath(path);
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
|
@ -1513,8 +1513,8 @@ describe('InspectedElement', () => {
|
|||
}
|
||||
`);
|
||||
|
||||
TestRendererAct(() => {
|
||||
TestUtilsAct(() => {
|
||||
await TestRendererAct(async () => {
|
||||
await TestUtilsAct(async () => {
|
||||
legacyRender(
|
||||
<Example
|
||||
nestedObject={{
|
||||
|
@ -1596,8 +1596,8 @@ describe('InspectedElement', () => {
|
|||
});
|
||||
|
||||
async function loadPath(path) {
|
||||
TestUtilsAct(() => {
|
||||
TestRendererAct(() => {
|
||||
await TestUtilsAct(async () => {
|
||||
await TestRendererAct(async () => {
|
||||
inspectElementPath(path);
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
|
@ -1618,7 +1618,7 @@ describe('InspectedElement', () => {
|
|||
}
|
||||
`);
|
||||
|
||||
TestUtilsAct(() => {
|
||||
await TestUtilsAct(async () => {
|
||||
legacyRender(
|
||||
<Example
|
||||
nestedObject={{
|
||||
|
@ -1640,11 +1640,9 @@ describe('InspectedElement', () => {
|
|||
expect(inspectedElement.props).toMatchInlineSnapshot(`
|
||||
{
|
||||
"nestedObject": {
|
||||
"a": {
|
||||
"b": {
|
||||
"value": 2,
|
||||
},
|
||||
"value": 2,
|
||||
"a": Dehydrated {
|
||||
"preview_short": {…},
|
||||
"preview_long": {b: {…}, value: 2},
|
||||
},
|
||||
"value": 2,
|
||||
},
|
||||
|
@ -2833,7 +2831,7 @@ describe('InspectedElement', () => {
|
|||
};
|
||||
const toggleError = async forceError => {
|
||||
await withErrorsOrWarningsIgnored(['ErrorBoundary'], async () => {
|
||||
await TestUtilsAct(() => {
|
||||
await TestUtilsAct(async () => {
|
||||
bridge.send('overrideError', {
|
||||
id: targetErrorBoundaryID,
|
||||
rendererID: store.getRendererIDForElement(targetErrorBoundaryID),
|
||||
|
@ -2842,7 +2840,7 @@ describe('InspectedElement', () => {
|
|||
});
|
||||
});
|
||||
|
||||
TestUtilsAct(() => {
|
||||
await TestUtilsAct(async () => {
|
||||
jest.runOnlyPendingTimers();
|
||||
});
|
||||
};
|
||||
|
|
|
@ -19,10 +19,8 @@ describe('Store component filters', () => {
|
|||
let utils;
|
||||
let internalAct;
|
||||
|
||||
const act = (callback: Function) => {
|
||||
internalAct(() => {
|
||||
callback();
|
||||
});
|
||||
const act = async (callback: Function) => {
|
||||
internalAct(callback);
|
||||
jest.runAllTimers(); // Flush Bridge operations
|
||||
};
|
||||
|
||||
|
@ -42,15 +40,15 @@ describe('Store component filters', () => {
|
|||
});
|
||||
|
||||
// @reactVersion >= 16.0
|
||||
it('should throw if filters are updated while profiling', () => {
|
||||
act(() => store.profilerStore.startProfiling());
|
||||
it('should throw if filters are updated while profiling', async () => {
|
||||
await act(async () => store.profilerStore.startProfiling());
|
||||
expect(() => (store.componentFilters = [])).toThrow(
|
||||
'Cannot modify filter preferences while profiling',
|
||||
);
|
||||
});
|
||||
|
||||
// @reactVersion >= 16.0
|
||||
it('should support filtering by element type', () => {
|
||||
it('should support filtering by element type', async () => {
|
||||
class ClassComponent extends React.Component<{children: React$Node}> {
|
||||
render() {
|
||||
return <div>{this.props.children}</div>;
|
||||
|
@ -58,7 +56,7 @@ describe('Store component filters', () => {
|
|||
}
|
||||
const FunctionComponent = () => <div>Hi</div>;
|
||||
|
||||
act(() =>
|
||||
await act(async () =>
|
||||
legacyRender(
|
||||
<ClassComponent>
|
||||
<FunctionComponent />
|
||||
|
@ -74,8 +72,8 @@ describe('Store component filters', () => {
|
|||
<div>
|
||||
`);
|
||||
|
||||
act(
|
||||
() =>
|
||||
await act(
|
||||
async () =>
|
||||
(store.componentFilters = [
|
||||
utils.createElementTypeFilter(Types.ElementTypeHostComponent),
|
||||
]),
|
||||
|
@ -86,8 +84,8 @@ describe('Store component filters', () => {
|
|||
<FunctionComponent>
|
||||
`);
|
||||
|
||||
act(
|
||||
() =>
|
||||
await act(
|
||||
async () =>
|
||||
(store.componentFilters = [
|
||||
utils.createElementTypeFilter(Types.ElementTypeClass),
|
||||
]),
|
||||
|
@ -99,8 +97,8 @@ describe('Store component filters', () => {
|
|||
<div>
|
||||
`);
|
||||
|
||||
act(
|
||||
() =>
|
||||
await act(
|
||||
async () =>
|
||||
(store.componentFilters = [
|
||||
utils.createElementTypeFilter(Types.ElementTypeClass),
|
||||
utils.createElementTypeFilter(Types.ElementTypeFunction),
|
||||
|
@ -112,8 +110,8 @@ describe('Store component filters', () => {
|
|||
<div>
|
||||
`);
|
||||
|
||||
act(
|
||||
() =>
|
||||
await act(
|
||||
async () =>
|
||||
(store.componentFilters = [
|
||||
utils.createElementTypeFilter(Types.ElementTypeClass, false),
|
||||
utils.createElementTypeFilter(Types.ElementTypeFunction, false),
|
||||
|
@ -127,7 +125,7 @@ describe('Store component filters', () => {
|
|||
<div>
|
||||
`);
|
||||
|
||||
act(() => (store.componentFilters = []));
|
||||
await act(async () => (store.componentFilters = []));
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
[root]
|
||||
▾ <ClassComponent>
|
||||
|
@ -138,18 +136,20 @@ describe('Store component filters', () => {
|
|||
});
|
||||
|
||||
// @reactVersion >= 16.0
|
||||
it('should ignore invalid ElementTypeRoot filter', () => {
|
||||
it('should ignore invalid ElementTypeRoot filter', async () => {
|
||||
const Component = () => <div>Hi</div>;
|
||||
|
||||
act(() => legacyRender(<Component />, document.createElement('div')));
|
||||
await act(async () =>
|
||||
legacyRender(<Component />, document.createElement('div')),
|
||||
);
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
[root]
|
||||
▾ <Component>
|
||||
<div>
|
||||
`);
|
||||
|
||||
act(
|
||||
() =>
|
||||
await act(
|
||||
async () =>
|
||||
(store.componentFilters = [
|
||||
utils.createElementTypeFilter(Types.ElementTypeRoot),
|
||||
]),
|
||||
|
@ -163,13 +163,13 @@ describe('Store component filters', () => {
|
|||
});
|
||||
|
||||
// @reactVersion >= 16.2
|
||||
it('should filter by display name', () => {
|
||||
it('should filter by display name', async () => {
|
||||
const Text = ({label}) => label;
|
||||
const Foo = () => <Text label="foo" />;
|
||||
const Bar = () => <Text label="bar" />;
|
||||
const Baz = () => <Text label="baz" />;
|
||||
|
||||
act(() =>
|
||||
await act(async () =>
|
||||
legacyRender(
|
||||
<React.Fragment>
|
||||
<Foo />
|
||||
|
@ -189,8 +189,9 @@ describe('Store component filters', () => {
|
|||
<Text>
|
||||
`);
|
||||
|
||||
act(
|
||||
() => (store.componentFilters = [utils.createDisplayNameFilter('Foo')]),
|
||||
await act(
|
||||
async () =>
|
||||
(store.componentFilters = [utils.createDisplayNameFilter('Foo')]),
|
||||
);
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
[root]
|
||||
|
@ -201,7 +202,10 @@ describe('Store component filters', () => {
|
|||
<Text>
|
||||
`);
|
||||
|
||||
act(() => (store.componentFilters = [utils.createDisplayNameFilter('Ba')]));
|
||||
await act(
|
||||
async () =>
|
||||
(store.componentFilters = [utils.createDisplayNameFilter('Ba')]),
|
||||
);
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
[root]
|
||||
▾ <Foo>
|
||||
|
@ -210,8 +214,9 @@ describe('Store component filters', () => {
|
|||
<Text>
|
||||
`);
|
||||
|
||||
act(
|
||||
() => (store.componentFilters = [utils.createDisplayNameFilter('B.z')]),
|
||||
await act(
|
||||
async () =>
|
||||
(store.componentFilters = [utils.createDisplayNameFilter('B.z')]),
|
||||
);
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
[root]
|
||||
|
@ -224,18 +229,20 @@ describe('Store component filters', () => {
|
|||
});
|
||||
|
||||
// @reactVersion >= 16.0
|
||||
it('should filter by path', () => {
|
||||
it('should filter by path', async () => {
|
||||
const Component = () => <div>Hi</div>;
|
||||
|
||||
act(() => legacyRender(<Component />, document.createElement('div')));
|
||||
await act(async () =>
|
||||
legacyRender(<Component />, document.createElement('div')),
|
||||
);
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
[root]
|
||||
▾ <Component>
|
||||
<div>
|
||||
`);
|
||||
|
||||
act(
|
||||
() =>
|
||||
await act(
|
||||
async () =>
|
||||
(store.componentFilters = [
|
||||
utils.createLocationFilter(__filename.replace(__dirname, '')),
|
||||
]),
|
||||
|
@ -243,8 +250,8 @@ describe('Store component filters', () => {
|
|||
|
||||
expect(store).toMatchInlineSnapshot(`[root]`);
|
||||
|
||||
act(
|
||||
() =>
|
||||
await act(
|
||||
async () =>
|
||||
(store.componentFilters = [
|
||||
utils.createLocationFilter('this:is:a:made:up:path'),
|
||||
]),
|
||||
|
@ -258,14 +265,14 @@ describe('Store component filters', () => {
|
|||
});
|
||||
|
||||
// @reactVersion >= 16.0
|
||||
it('should filter HOCs', () => {
|
||||
it('should filter HOCs', async () => {
|
||||
const Component = () => <div>Hi</div>;
|
||||
const Foo = () => <Component />;
|
||||
Foo.displayName = 'Foo(Component)';
|
||||
const Bar = () => <Foo />;
|
||||
Bar.displayName = 'Bar(Foo(Component))';
|
||||
|
||||
act(() => legacyRender(<Bar />, document.createElement('div')));
|
||||
await act(async () => legacyRender(<Bar />, document.createElement('div')));
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
[root]
|
||||
▾ <Component> [Bar][Foo]
|
||||
|
@ -274,14 +281,18 @@ describe('Store component filters', () => {
|
|||
<div>
|
||||
`);
|
||||
|
||||
act(() => (store.componentFilters = [utils.createHOCFilter(true)]));
|
||||
await act(
|
||||
async () => (store.componentFilters = [utils.createHOCFilter(true)]),
|
||||
);
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
[root]
|
||||
▾ <Component>
|
||||
<div>
|
||||
`);
|
||||
|
||||
act(() => (store.componentFilters = [utils.createHOCFilter(false)]));
|
||||
await act(
|
||||
async () => (store.componentFilters = [utils.createHOCFilter(false)]),
|
||||
);
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
[root]
|
||||
▾ <Component> [Bar][Foo]
|
||||
|
@ -292,29 +303,31 @@ describe('Store component filters', () => {
|
|||
});
|
||||
|
||||
// @reactVersion >= 16.0
|
||||
it('should not send a bridge update if the set of enabled filters has not changed', () => {
|
||||
act(() => (store.componentFilters = [utils.createHOCFilter(true)]));
|
||||
it('should not send a bridge update if the set of enabled filters has not changed', async () => {
|
||||
await act(
|
||||
async () => (store.componentFilters = [utils.createHOCFilter(true)]),
|
||||
);
|
||||
|
||||
bridge.addListener('updateComponentFilters', componentFilters => {
|
||||
throw Error('Unexpected component update');
|
||||
});
|
||||
|
||||
act(
|
||||
() =>
|
||||
await act(
|
||||
async () =>
|
||||
(store.componentFilters = [
|
||||
utils.createHOCFilter(false),
|
||||
utils.createHOCFilter(true),
|
||||
]),
|
||||
);
|
||||
act(
|
||||
() =>
|
||||
await act(
|
||||
async () =>
|
||||
(store.componentFilters = [
|
||||
utils.createHOCFilter(true),
|
||||
utils.createLocationFilter('abc', false),
|
||||
]),
|
||||
);
|
||||
act(
|
||||
() =>
|
||||
await act(
|
||||
async () =>
|
||||
(store.componentFilters = [
|
||||
utils.createHOCFilter(true),
|
||||
utils.createElementTypeFilter(Types.ElementTypeHostComponent, false),
|
||||
|
@ -323,7 +336,7 @@ describe('Store component filters', () => {
|
|||
});
|
||||
|
||||
// @reactVersion >= 18.0
|
||||
it('should not break when Suspense nodes are filtered from the tree', () => {
|
||||
it('should not break when Suspense nodes are filtered from the tree', async () => {
|
||||
const promise = new Promise(() => {});
|
||||
|
||||
const Loading = () => <div>Loading...</div>;
|
||||
|
@ -346,7 +359,9 @@ describe('Store component filters', () => {
|
|||
];
|
||||
|
||||
const container = document.createElement('div');
|
||||
act(() => legacyRender(<Wrapper shouldSuspend={true} />, container));
|
||||
await act(async () =>
|
||||
legacyRender(<Wrapper shouldSuspend={true} />, container),
|
||||
);
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
[root]
|
||||
▾ <Wrapper>
|
||||
|
@ -354,15 +369,21 @@ describe('Store component filters', () => {
|
|||
<div>
|
||||
`);
|
||||
|
||||
act(() => legacyRender(<Wrapper shouldSuspend={false} />, container));
|
||||
await act(async () =>
|
||||
legacyRender(<Wrapper shouldSuspend={false} />, container),
|
||||
);
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
✕ 1, ⚠ 0
|
||||
[root]
|
||||
▾ <Wrapper>
|
||||
<Component>
|
||||
`);
|
||||
|
||||
act(() => legacyRender(<Wrapper shouldSuspend={true} />, container));
|
||||
await act(async () =>
|
||||
legacyRender(<Wrapper shouldSuspend={true} />, container),
|
||||
);
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
✕ 2, ⚠ 0
|
||||
[root]
|
||||
▾ <Wrapper>
|
||||
▾ <Loading>
|
||||
|
@ -372,7 +393,7 @@ describe('Store component filters', () => {
|
|||
|
||||
describe('inline errors and warnings', () => {
|
||||
// @reactVersion >= 17.0
|
||||
it('only counts for unfiltered components', () => {
|
||||
it('only counts for unfiltered components', async () => {
|
||||
function ComponentWithWarning() {
|
||||
console.warn('test-only: render warning');
|
||||
return null;
|
||||
|
@ -395,31 +416,29 @@ describe('Store component filters', () => {
|
|||
require('react-dom');
|
||||
|
||||
const container = document.createElement('div');
|
||||
await act(
|
||||
async () =>
|
||||
(store.componentFilters = [
|
||||
utils.createDisplayNameFilter('Warning'),
|
||||
utils.createDisplayNameFilter('Error'),
|
||||
]),
|
||||
);
|
||||
utils.withErrorsOrWarningsIgnored(['test-only:'], () => {
|
||||
act(
|
||||
() =>
|
||||
(store.componentFilters = [
|
||||
utils.createDisplayNameFilter('Warning'),
|
||||
utils.createDisplayNameFilter('Error'),
|
||||
]),
|
||||
);
|
||||
act(() =>
|
||||
legacyRender(
|
||||
<React.Fragment>
|
||||
<ComponentWithError />
|
||||
<ComponentWithWarning />
|
||||
<ComponentWithWarningAndError />
|
||||
</React.Fragment>,
|
||||
container,
|
||||
),
|
||||
legacyRender(
|
||||
<React.Fragment>
|
||||
<ComponentWithError />
|
||||
<ComponentWithWarning />
|
||||
<ComponentWithWarningAndError />
|
||||
</React.Fragment>,
|
||||
container,
|
||||
);
|
||||
});
|
||||
|
||||
expect(store).toMatchInlineSnapshot(`[root]`);
|
||||
expect(store).toMatchInlineSnapshot(``);
|
||||
expect(store.errorCount).toBe(0);
|
||||
expect(store.warningCount).toBe(0);
|
||||
|
||||
act(() => (store.componentFilters = []));
|
||||
await act(async () => (store.componentFilters = []));
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
✕ 2, ⚠ 2
|
||||
[root]
|
||||
|
@ -428,8 +447,8 @@ describe('Store component filters', () => {
|
|||
<ComponentWithWarningAndError> ✕⚠
|
||||
`);
|
||||
|
||||
act(
|
||||
() =>
|
||||
await act(
|
||||
async () =>
|
||||
(store.componentFilters = [utils.createDisplayNameFilter('Warning')]),
|
||||
);
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
|
@ -438,8 +457,8 @@ describe('Store component filters', () => {
|
|||
<ComponentWithError> ✕
|
||||
`);
|
||||
|
||||
act(
|
||||
() =>
|
||||
await act(
|
||||
async () =>
|
||||
(store.componentFilters = [utils.createDisplayNameFilter('Error')]),
|
||||
);
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
|
@ -448,8 +467,8 @@ describe('Store component filters', () => {
|
|||
<ComponentWithWarning> ⚠
|
||||
`);
|
||||
|
||||
act(
|
||||
() =>
|
||||
await act(
|
||||
async () =>
|
||||
(store.componentFilters = [
|
||||
utils.createDisplayNameFilter('Warning'),
|
||||
utils.createDisplayNameFilter('Error'),
|
||||
|
@ -459,7 +478,7 @@ describe('Store component filters', () => {
|
|||
expect(store.errorCount).toBe(0);
|
||||
expect(store.warningCount).toBe(0);
|
||||
|
||||
act(() => (store.componentFilters = []));
|
||||
await act(async () => (store.componentFilters = []));
|
||||
expect(store).toMatchInlineSnapshot(`
|
||||
✕ 2, ⚠ 2
|
||||
[root]
|
||||
|
|
|
@ -48,21 +48,16 @@ module.exports = function (initModules) {
|
|||
// ====================================
|
||||
|
||||
// promisified version of ReactDOM.render()
|
||||
function asyncReactDOMRender(reactElement, domElement, forceHydrate) {
|
||||
return new Promise(resolve => {
|
||||
if (forceHydrate) {
|
||||
act(() => {
|
||||
ReactDOM.hydrate(reactElement, domElement);
|
||||
});
|
||||
} else {
|
||||
act(() => {
|
||||
ReactDOM.render(reactElement, domElement);
|
||||
});
|
||||
}
|
||||
// We can't use the callback for resolution because that will not catch
|
||||
// errors. They're thrown.
|
||||
resolve();
|
||||
});
|
||||
async function asyncReactDOMRender(reactElement, domElement, forceHydrate) {
|
||||
if (forceHydrate) {
|
||||
await act(async () => {
|
||||
ReactDOM.hydrate(reactElement, domElement);
|
||||
});
|
||||
} else {
|
||||
await act(async () => {
|
||||
ReactDOM.render(reactElement, domElement);
|
||||
});
|
||||
}
|
||||
}
|
||||
// performs fn asynchronously and expects count errors logged to console.error.
|
||||
// will fail the test if the count of errors logged is not equal to count.
|
||||
|
|
|
@ -2520,7 +2520,7 @@ describe('DOMPluginEventSystem', () => {
|
|||
});
|
||||
|
||||
// @gate www
|
||||
it('beforeblur and afterblur are called after a focused element is suspended', () => {
|
||||
it('beforeblur and afterblur are called after a focused element is suspended', async () => {
|
||||
const log = [];
|
||||
// We have to persist here because we want to read relatedTarget later.
|
||||
const onAfterBlur = jest.fn(e => {
|
||||
|
@ -2575,7 +2575,7 @@ describe('DOMPluginEventSystem', () => {
|
|||
|
||||
const root = ReactDOMClient.createRoot(container2);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
root.render(<Component />);
|
||||
});
|
||||
jest.runAllTimers();
|
||||
|
@ -2587,7 +2587,7 @@ describe('DOMPluginEventSystem', () => {
|
|||
expect(onAfterBlur).toHaveBeenCalledTimes(0);
|
||||
|
||||
suspend = true;
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
root.render(<Component />);
|
||||
});
|
||||
jest.runAllTimers();
|
||||
|
@ -2604,7 +2604,7 @@ describe('DOMPluginEventSystem', () => {
|
|||
});
|
||||
|
||||
// @gate www
|
||||
it('beforeblur should skip handlers from a deleted subtree after the focused element is suspended', () => {
|
||||
it('beforeblur should skip handlers from a deleted subtree after the focused element is suspended', async () => {
|
||||
const onBeforeBlur = jest.fn();
|
||||
const innerRef = React.createRef();
|
||||
const innerRef2 = React.createRef();
|
||||
|
@ -2661,7 +2661,7 @@ describe('DOMPluginEventSystem', () => {
|
|||
|
||||
const root = ReactDOMClient.createRoot(container2);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
root.render(<Component />);
|
||||
});
|
||||
jest.runAllTimers();
|
||||
|
@ -2672,7 +2672,7 @@ describe('DOMPluginEventSystem', () => {
|
|||
expect(onBeforeBlur).toHaveBeenCalledTimes(0);
|
||||
|
||||
suspend = true;
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
root.render(<Component />);
|
||||
});
|
||||
jest.runAllTimers();
|
||||
|
@ -2684,17 +2684,17 @@ describe('DOMPluginEventSystem', () => {
|
|||
});
|
||||
|
||||
// @gate www
|
||||
it('regression: does not fire beforeblur/afterblur if target is already hidden', () => {
|
||||
it('regression: does not fire beforeblur/afterblur if target is already hidden', async () => {
|
||||
const Suspense = React.Suspense;
|
||||
let suspend = false;
|
||||
const promise = Promise.resolve();
|
||||
const fakePromise = {then() {}};
|
||||
const setBeforeBlurHandle =
|
||||
ReactDOM.unstable_createEventHandle('beforeblur');
|
||||
const innerRef = React.createRef();
|
||||
|
||||
function Child() {
|
||||
if (suspend) {
|
||||
throw promise;
|
||||
throw fakePromise;
|
||||
}
|
||||
return <input ref={innerRef} />;
|
||||
}
|
||||
|
@ -2726,7 +2726,7 @@ describe('DOMPluginEventSystem', () => {
|
|||
document.body.appendChild(container2);
|
||||
|
||||
const root = ReactDOMClient.createRoot(container2);
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
root.render(<Component />);
|
||||
});
|
||||
|
||||
|
@ -2737,7 +2737,7 @@ describe('DOMPluginEventSystem', () => {
|
|||
|
||||
// Suspend. This hides the input node, causing it to lose focus.
|
||||
suspend = true;
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
root.render(<Component />);
|
||||
});
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ let ReactDOMServer;
|
|||
let act;
|
||||
let assertLog;
|
||||
let waitForAll;
|
||||
let waitForThrow;
|
||||
|
||||
describe('ReactHooks', () => {
|
||||
beforeEach(() => {
|
||||
|
@ -36,6 +37,7 @@ describe('ReactHooks', () => {
|
|||
const InternalTestUtils = require('internal-test-utils');
|
||||
assertLog = InternalTestUtils.assertLog;
|
||||
waitForAll = InternalTestUtils.waitForAll;
|
||||
waitForThrow = InternalTestUtils.waitForThrow;
|
||||
});
|
||||
|
||||
if (__DEV__) {
|
||||
|
@ -90,7 +92,7 @@ describe('ReactHooks', () => {
|
|||
expect(root).toMatchRenderedOutput('0, 0');
|
||||
|
||||
// Normal update
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
setCounter1(1);
|
||||
setCounter2(1);
|
||||
});
|
||||
|
@ -98,12 +100,12 @@ describe('ReactHooks', () => {
|
|||
assertLog(['Parent: 1, 1', 'Child: 1, 1', 'Effect: 1, 1']);
|
||||
|
||||
// Update that bails out.
|
||||
act(() => setCounter1(1));
|
||||
await act(async () => setCounter1(1));
|
||||
assertLog(['Parent: 1, 1']);
|
||||
|
||||
// This time, one of the state updates but the other one doesn't. So we
|
||||
// can't bail out.
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
setCounter1(1);
|
||||
setCounter2(2);
|
||||
});
|
||||
|
@ -111,7 +113,7 @@ describe('ReactHooks', () => {
|
|||
assertLog(['Parent: 1, 2', 'Child: 1, 2', 'Effect: 1, 2']);
|
||||
|
||||
// Lots of updates that eventually resolve to the current values.
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
setCounter1(9);
|
||||
setCounter2(3);
|
||||
setCounter1(4);
|
||||
|
@ -125,7 +127,7 @@ describe('ReactHooks', () => {
|
|||
assertLog(['Parent: 1, 2']);
|
||||
|
||||
// prepare to check SameValue
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
setCounter1(0 / -1);
|
||||
setCounter2(NaN);
|
||||
});
|
||||
|
@ -133,7 +135,7 @@ describe('ReactHooks', () => {
|
|||
assertLog(['Parent: 0, NaN', 'Child: 0, NaN', 'Effect: 0, NaN']);
|
||||
|
||||
// check if re-setting to negative 0 / NaN still bails out
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
setCounter1(0 / -1);
|
||||
setCounter2(NaN);
|
||||
setCounter2(Infinity);
|
||||
|
@ -143,7 +145,7 @@ describe('ReactHooks', () => {
|
|||
assertLog(['Parent: 0, NaN']);
|
||||
|
||||
// check if changing negative 0 to positive 0 does not bail out
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
setCounter1(0);
|
||||
});
|
||||
assertLog(['Parent: 0, NaN', 'Child: 0, NaN', 'Effect: 0, NaN']);
|
||||
|
@ -178,7 +180,7 @@ describe('ReactHooks', () => {
|
|||
expect(root).toMatchRenderedOutput('0, 0 (light)');
|
||||
|
||||
// Normal update
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
setCounter1(1);
|
||||
setCounter2(1);
|
||||
});
|
||||
|
@ -186,12 +188,12 @@ describe('ReactHooks', () => {
|
|||
assertLog(['Parent: 1, 1 (light)', 'Child: 1, 1 (light)']);
|
||||
|
||||
// Update that bails out.
|
||||
act(() => setCounter1(1));
|
||||
await act(async () => setCounter1(1));
|
||||
assertLog(['Parent: 1, 1 (light)']);
|
||||
|
||||
// This time, one of the state updates but the other one doesn't. So we
|
||||
// can't bail out.
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
setCounter1(1);
|
||||
setCounter2(2);
|
||||
});
|
||||
|
@ -200,7 +202,7 @@ describe('ReactHooks', () => {
|
|||
|
||||
// Updates bail out, but component still renders because props
|
||||
// have changed
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
setCounter1(1);
|
||||
setCounter2(2);
|
||||
root.update(<Parent theme="dark" />);
|
||||
|
@ -209,7 +211,7 @@ describe('ReactHooks', () => {
|
|||
assertLog(['Parent: 1, 2 (dark)', 'Child: 1, 2 (dark)']);
|
||||
|
||||
// Both props and state bail out
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
setCounter1(1);
|
||||
setCounter2(2);
|
||||
root.update(<Parent theme="dark" />);
|
||||
|
@ -235,8 +237,8 @@ describe('ReactHooks', () => {
|
|||
await waitForAll(['Count: 0']);
|
||||
expect(root).toMatchRenderedOutput('0');
|
||||
|
||||
expect(() => {
|
||||
act(() =>
|
||||
await expect(async () => {
|
||||
await act(async () =>
|
||||
setCounter(1, () => {
|
||||
throw new Error('Expected to ignore the callback.');
|
||||
}),
|
||||
|
@ -269,8 +271,8 @@ describe('ReactHooks', () => {
|
|||
await waitForAll(['Count: 0']);
|
||||
expect(root).toMatchRenderedOutput('0');
|
||||
|
||||
expect(() => {
|
||||
act(() =>
|
||||
await expect(async () => {
|
||||
await act(async () =>
|
||||
dispatch(1, () => {
|
||||
throw new Error('Expected to ignore the callback.');
|
||||
}),
|
||||
|
@ -321,7 +323,7 @@ describe('ReactHooks', () => {
|
|||
return <Child text={text} />;
|
||||
}
|
||||
const root = ReactTestRenderer.create(null, {unstable_isConcurrent: true});
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
root.update(
|
||||
<ThemeProvider>
|
||||
<Parent />
|
||||
|
@ -344,18 +346,18 @@ describe('ReactHooks', () => {
|
|||
expect(root).toMatchRenderedOutput('0 (light)');
|
||||
|
||||
// Normal update
|
||||
act(() => setCounter(1));
|
||||
await act(async () => setCounter(1));
|
||||
assertLog(['Parent: 1 (light)', 'Child: 1 (light)', 'Effect: 1 (light)']);
|
||||
expect(root).toMatchRenderedOutput('1 (light)');
|
||||
|
||||
// Update that doesn't change state, so it bails out
|
||||
act(() => setCounter(1));
|
||||
await act(async () => setCounter(1));
|
||||
assertLog(['Parent: 1 (light)']);
|
||||
expect(root).toMatchRenderedOutput('1 (light)');
|
||||
|
||||
// Update that doesn't change state, but the context changes, too, so it
|
||||
// can't bail out
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
setCounter(1);
|
||||
setTheme('dark');
|
||||
});
|
||||
|
@ -394,7 +396,7 @@ describe('ReactHooks', () => {
|
|||
expect(root).toMatchRenderedOutput('0');
|
||||
|
||||
// Normal update
|
||||
act(() => setCounter(1));
|
||||
await act(async () => setCounter(1));
|
||||
assertLog(['Parent: 1', 'Child: 1', 'Effect: 1']);
|
||||
expect(root).toMatchRenderedOutput('1');
|
||||
|
||||
|
@ -402,30 +404,30 @@ describe('ReactHooks', () => {
|
|||
// because the alternate fiber has pending update priority, so we have to
|
||||
// enter the render phase before we can bail out. But we bail out before
|
||||
// rendering the child, and we don't fire any effects.
|
||||
act(() => setCounter(1));
|
||||
await act(async () => setCounter(1));
|
||||
assertLog(['Parent: 1']);
|
||||
expect(root).toMatchRenderedOutput('1');
|
||||
|
||||
// Update to the same state again. This times, neither fiber has pending
|
||||
// update priority, so we can bail out before even entering the render phase.
|
||||
act(() => setCounter(1));
|
||||
await act(async () => setCounter(1));
|
||||
await waitForAll([]);
|
||||
expect(root).toMatchRenderedOutput('1');
|
||||
|
||||
// This changes the state to something different so it renders normally.
|
||||
act(() => setCounter(2));
|
||||
await act(async () => setCounter(2));
|
||||
assertLog(['Parent: 2', 'Child: 2', 'Effect: 2']);
|
||||
expect(root).toMatchRenderedOutput('2');
|
||||
|
||||
// prepare to check SameValue
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
setCounter(0);
|
||||
});
|
||||
assertLog(['Parent: 0', 'Child: 0', 'Effect: 0']);
|
||||
expect(root).toMatchRenderedOutput('0');
|
||||
|
||||
// Update to the same state for the first time to flush the queue
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
setCounter(0);
|
||||
});
|
||||
|
||||
|
@ -433,14 +435,14 @@ describe('ReactHooks', () => {
|
|||
expect(root).toMatchRenderedOutput('0');
|
||||
|
||||
// Update again to the same state. Should bail out.
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
setCounter(0);
|
||||
});
|
||||
await waitForAll([]);
|
||||
expect(root).toMatchRenderedOutput('0');
|
||||
|
||||
// Update to a different state (positive 0 to negative 0)
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
setCounter(0 / -1);
|
||||
});
|
||||
assertLog(['Parent: 0', 'Child: 0', 'Effect: 0']);
|
||||
|
@ -615,7 +617,7 @@ describe('ReactHooks', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('warns if deps is not an array', () => {
|
||||
it('warns if deps is not an array', async () => {
|
||||
const {useEffect, useLayoutEffect, useMemo, useCallback} = React;
|
||||
|
||||
function App(props) {
|
||||
|
@ -626,8 +628,8 @@ describe('ReactHooks', () => {
|
|||
return null;
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
act(() => {
|
||||
await expect(async () => {
|
||||
await act(async () => {
|
||||
ReactTestRenderer.create(<App deps={'hello'} />);
|
||||
});
|
||||
}).toErrorDev([
|
||||
|
@ -640,8 +642,8 @@ describe('ReactHooks', () => {
|
|||
'Warning: useCallback received a final argument that is not an array (instead, received `string`). ' +
|
||||
'When specified, the final argument must be an array.',
|
||||
]);
|
||||
expect(() => {
|
||||
act(() => {
|
||||
await expect(async () => {
|
||||
await act(async () => {
|
||||
ReactTestRenderer.create(<App deps={100500} />);
|
||||
});
|
||||
}).toErrorDev([
|
||||
|
@ -654,8 +656,8 @@ describe('ReactHooks', () => {
|
|||
'Warning: useCallback received a final argument that is not an array (instead, received `number`). ' +
|
||||
'When specified, the final argument must be an array.',
|
||||
]);
|
||||
expect(() => {
|
||||
act(() => {
|
||||
await expect(async () => {
|
||||
await act(async () => {
|
||||
ReactTestRenderer.create(<App deps={{}} />);
|
||||
});
|
||||
}).toErrorDev([
|
||||
|
@ -669,7 +671,7 @@ describe('ReactHooks', () => {
|
|||
'When specified, the final argument must be an array.',
|
||||
]);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactTestRenderer.create(<App deps={[]} />);
|
||||
ReactTestRenderer.create(<App deps={null} />);
|
||||
ReactTestRenderer.create(<App deps={undefined} />);
|
||||
|
@ -695,7 +697,7 @@ describe('ReactHooks', () => {
|
|||
ReactTestRenderer.create(<App deps={undefined} />);
|
||||
});
|
||||
|
||||
it('does not forget render phase useState updates inside an effect', () => {
|
||||
it('does not forget render phase useState updates inside an effect', async () => {
|
||||
const {useState, useEffect} = React;
|
||||
|
||||
function Counter() {
|
||||
|
@ -712,13 +714,13 @@ describe('ReactHooks', () => {
|
|||
}
|
||||
|
||||
const root = ReactTestRenderer.create(null);
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
root.update(<Counter />);
|
||||
});
|
||||
expect(root).toMatchRenderedOutput('4');
|
||||
});
|
||||
|
||||
it('does not forget render phase useReducer updates inside an effect with hoisted reducer', () => {
|
||||
it('does not forget render phase useReducer updates inside an effect with hoisted reducer', async () => {
|
||||
const {useReducer, useEffect} = React;
|
||||
|
||||
const reducer = x => x + 1;
|
||||
|
@ -736,13 +738,13 @@ describe('ReactHooks', () => {
|
|||
}
|
||||
|
||||
const root = ReactTestRenderer.create(null);
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
root.update(<Counter />);
|
||||
});
|
||||
expect(root).toMatchRenderedOutput('4');
|
||||
});
|
||||
|
||||
it('does not forget render phase useReducer updates inside an effect with inline reducer', () => {
|
||||
it('does not forget render phase useReducer updates inside an effect with inline reducer', async () => {
|
||||
const {useReducer, useEffect} = React;
|
||||
|
||||
function Counter() {
|
||||
|
@ -759,7 +761,7 @@ describe('ReactHooks', () => {
|
|||
}
|
||||
|
||||
const root = ReactTestRenderer.create(null);
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
root.update(<Counter />);
|
||||
});
|
||||
expect(root).toMatchRenderedOutput('4');
|
||||
|
@ -914,7 +916,7 @@ describe('ReactHooks', () => {
|
|||
});
|
||||
|
||||
// Throws because there's no runtime cost for being strict here.
|
||||
it('throws when reading context inside useEffect', () => {
|
||||
it('throws when reading context inside useEffect', async () => {
|
||||
const {useEffect, createContext} = React;
|
||||
const ReactCurrentDispatcher =
|
||||
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
|
@ -928,14 +930,11 @@ describe('ReactHooks', () => {
|
|||
return null;
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
act(() => {
|
||||
ReactTestRenderer.create(<App />);
|
||||
});
|
||||
}).toThrow(
|
||||
await act(async () => {
|
||||
ReactTestRenderer.create(<App />);
|
||||
// The exact message doesn't matter, just make sure we don't allow this
|
||||
'Context can only be read while React is rendering',
|
||||
);
|
||||
await waitForThrow('Context can only be read while React is rendering');
|
||||
});
|
||||
});
|
||||
|
||||
// Throws because there's no runtime cost for being strict here.
|
||||
|
@ -1070,7 +1069,7 @@ describe('ReactHooks', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('resets warning internal state when interrupted by an error', () => {
|
||||
it('resets warning internal state when interrupted by an error', async () => {
|
||||
const ReactCurrentDispatcher =
|
||||
React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED
|
||||
.ReactCurrentDispatcher;
|
||||
|
@ -1132,7 +1131,7 @@ describe('ReactHooks', () => {
|
|||
}
|
||||
// Verify it doesn't think we're still inside a Hook.
|
||||
// Should have no warnings.
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactTestRenderer.create(<Valid />);
|
||||
});
|
||||
|
||||
|
@ -1473,7 +1472,7 @@ describe('ReactHooks', () => {
|
|||
.replace('use', '')
|
||||
.replace('Helper', '');
|
||||
|
||||
it(`warns on using differently ordered hooks (${hookNameA}, ${hookNameB}) on subsequent renders`, () => {
|
||||
it(`warns on using differently ordered hooks (${hookNameA}, ${hookNameB}) on subsequent renders`, async () => {
|
||||
function App(props) {
|
||||
/* eslint-disable no-unused-vars */
|
||||
if (props.update) {
|
||||
|
@ -1489,12 +1488,12 @@ describe('ReactHooks', () => {
|
|||
/* eslint-enable no-unused-vars */
|
||||
}
|
||||
let root;
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
root = ReactTestRenderer.create(<App update={false} />);
|
||||
});
|
||||
expect(() => {
|
||||
await expect(async () => {
|
||||
try {
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
root.update(<App update={true} />);
|
||||
});
|
||||
} catch (error) {
|
||||
|
@ -1515,7 +1514,7 @@ describe('ReactHooks', () => {
|
|||
|
||||
// further warnings for this component are silenced
|
||||
try {
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
root.update(<App update={false} />);
|
||||
});
|
||||
} catch (error) {
|
||||
|
@ -1525,7 +1524,7 @@ describe('ReactHooks', () => {
|
|||
}
|
||||
});
|
||||
|
||||
it(`warns when more hooks (${hookNameA}, ${hookNameB}) are used during update than mount`, () => {
|
||||
it(`warns when more hooks (${hookNameA}, ${hookNameB}) are used during update than mount`, async () => {
|
||||
function App(props) {
|
||||
/* eslint-disable no-unused-vars */
|
||||
if (props.update) {
|
||||
|
@ -1538,13 +1537,13 @@ describe('ReactHooks', () => {
|
|||
/* eslint-enable no-unused-vars */
|
||||
}
|
||||
let root;
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
root = ReactTestRenderer.create(<App update={false} />);
|
||||
});
|
||||
|
||||
expect(() => {
|
||||
await expect(async () => {
|
||||
try {
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
root.update(<App update={true} />);
|
||||
});
|
||||
} catch (error) {
|
||||
|
@ -1579,7 +1578,7 @@ describe('ReactHooks', () => {
|
|||
.replace('use', '')
|
||||
.replace('Helper', '');
|
||||
|
||||
it(`warns when fewer hooks (${hookNameA}, ${hookNameB}) are used during update than mount`, () => {
|
||||
it(`warns when fewer hooks (${hookNameA}, ${hookNameB}) are used during update than mount`, async () => {
|
||||
function App(props) {
|
||||
/* eslint-disable no-unused-vars */
|
||||
if (props.update) {
|
||||
|
@ -1592,15 +1591,15 @@ describe('ReactHooks', () => {
|
|||
/* eslint-enable no-unused-vars */
|
||||
}
|
||||
let root;
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
root = ReactTestRenderer.create(<App update={false} />);
|
||||
});
|
||||
|
||||
expect(() => {
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
expect(() => {
|
||||
root.update(<App update={true} />);
|
||||
});
|
||||
}).toThrow('Rendered fewer hooks than expected.');
|
||||
}).toThrow('Rendered fewer hooks than expected. ');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1726,7 +1725,7 @@ describe('ReactHooks', () => {
|
|||
});
|
||||
|
||||
// Regression test for https://github.com/facebook/react/issues/15057
|
||||
it('does not fire a false positive warning when previous effect unmounts the component', () => {
|
||||
it('does not fire a false positive warning when previous effect unmounts the component', async () => {
|
||||
const {useState, useEffect} = React;
|
||||
let globalListener;
|
||||
|
||||
|
@ -1762,7 +1761,7 @@ describe('ReactHooks', () => {
|
|||
return null;
|
||||
}
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactTestRenderer.create(<A />);
|
||||
});
|
||||
|
||||
|
@ -1912,7 +1911,7 @@ describe('ReactHooks', () => {
|
|||
}
|
||||
|
||||
let root;
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
root = ReactTestRenderer.create(
|
||||
<ErrorBoundary>
|
||||
<Thrower />
|
||||
|
@ -1921,7 +1920,7 @@ describe('ReactHooks', () => {
|
|||
});
|
||||
|
||||
expect(root).toMatchRenderedOutput('Throw!');
|
||||
act(() => setShouldThrow(true));
|
||||
await act(async () => setShouldThrow(true));
|
||||
expect(root).toMatchRenderedOutput('Error!');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -294,11 +294,11 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
await waitForAll(['Count: 0']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
|
||||
|
||||
act(() => counter.current.updateCount(1));
|
||||
await act(async () => counter.current.updateCount(1));
|
||||
assertLog(['Count: 1']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 1" />);
|
||||
|
||||
act(() => counter.current.updateCount(count => count + 10));
|
||||
await act(async () => counter.current.updateCount(count => count + 10));
|
||||
assertLog(['Count: 11']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 11" />);
|
||||
});
|
||||
|
@ -318,7 +318,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
await waitForAll(['getInitialState', 'Count: 42']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 42" />);
|
||||
|
||||
act(() => counter.current.updateCount(7));
|
||||
await act(async () => counter.current.updateCount(7));
|
||||
assertLog(['Count: 7']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 7" />);
|
||||
});
|
||||
|
@ -336,10 +336,10 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
await waitForAll(['Count: 0']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
|
||||
|
||||
act(() => counter.current.updateCount(7));
|
||||
await act(async () => counter.current.updateCount(7));
|
||||
assertLog(['Count: 7']);
|
||||
|
||||
act(() => counter.current.updateLabel('Total'));
|
||||
await act(async () => counter.current.updateLabel('Total'));
|
||||
assertLog(['Total: 7']);
|
||||
});
|
||||
|
||||
|
@ -356,13 +356,13 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
|
||||
const firstUpdater = updater;
|
||||
|
||||
act(() => firstUpdater(1));
|
||||
await act(async () => firstUpdater(1));
|
||||
assertLog(['Count: 1']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 1" />);
|
||||
|
||||
const secondUpdater = updater;
|
||||
|
||||
act(() => firstUpdater(count => count + 10));
|
||||
await act(async () => firstUpdater(count => count + 10));
|
||||
assertLog(['Count: 11']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 11" />);
|
||||
|
||||
|
@ -381,7 +381,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
await waitForAll([]);
|
||||
ReactNoop.render(null);
|
||||
await waitForAll([]);
|
||||
act(() => _updateCount(1));
|
||||
await act(async () => _updateCount(1));
|
||||
});
|
||||
|
||||
it('works with memo', async () => {
|
||||
|
@ -401,7 +401,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
await waitForAll([]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
|
||||
|
||||
act(() => _updateCount(1));
|
||||
await act(async () => _updateCount(1));
|
||||
assertLog(['Count: 1']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 1" />);
|
||||
});
|
||||
|
@ -637,7 +637,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
|
||||
// Test that it works on update, too. This time the log is a bit different
|
||||
// because we started with reducerB instead of reducerA.
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
counter.current.dispatch('reset');
|
||||
});
|
||||
ReactNoop.render(<Counter ref={counter} />);
|
||||
|
@ -851,10 +851,10 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
await waitForAll(['Count: 0']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
|
||||
|
||||
act(() => counter.current.dispatch(INCREMENT));
|
||||
await act(async () => counter.current.dispatch(INCREMENT));
|
||||
assertLog(['Count: 1']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 1" />);
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
counter.current.dispatch(DECREMENT);
|
||||
counter.current.dispatch(DECREMENT);
|
||||
counter.current.dispatch(DECREMENT);
|
||||
|
@ -893,11 +893,11 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
await waitForAll(['Init', 'Count: 10']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 10" />);
|
||||
|
||||
act(() => counter.current.dispatch(INCREMENT));
|
||||
await act(async () => counter.current.dispatch(INCREMENT));
|
||||
assertLog(['Count: 11']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 11" />);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
counter.current.dispatch(DECREMENT);
|
||||
counter.current.dispatch(DECREMENT);
|
||||
counter.current.dispatch(DECREMENT);
|
||||
|
@ -1427,7 +1427,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
return state;
|
||||
}
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.renderToRootWithID(<Component />, 'root', () =>
|
||||
Scheduler.log('Sync effect'),
|
||||
);
|
||||
|
@ -1437,7 +1437,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
ReactNoop.unmountRootWithID('root');
|
||||
await waitForAll(['passive destroy']);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
updaterFunction(true);
|
||||
});
|
||||
});
|
||||
|
@ -1624,7 +1624,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
|
||||
// A flush sync doesn't cause the passive effects to fire.
|
||||
// So we haven't added the other update yet.
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.flushSync(() => {
|
||||
_updateCount(2);
|
||||
});
|
||||
|
@ -2256,7 +2256,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
|
||||
// @gate skipUnmountedBoundaries
|
||||
it('should use the nearest still-mounted boundary if there are no unmounted boundaries', async () => {
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<LogOnlyErrorBoundary>
|
||||
<BrokenUseEffectCleanup />
|
||||
|
@ -2269,7 +2269,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
'BrokenUseEffectCleanup useEffect',
|
||||
]);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(<LogOnlyErrorBoundary />);
|
||||
});
|
||||
|
||||
|
@ -2294,7 +2294,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
}
|
||||
}
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<LogOnlyErrorBoundary>
|
||||
<Conditional showChildren={true} />
|
||||
|
@ -2308,7 +2308,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
'BrokenUseEffectCleanup useEffect',
|
||||
]);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<LogOnlyErrorBoundary>
|
||||
<Conditional showChildren={false} />
|
||||
|
@ -2333,7 +2333,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
}
|
||||
}
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<ErrorBoundary>
|
||||
<Conditional showChildren={true} />
|
||||
|
@ -2346,7 +2346,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
'BrokenUseEffectCleanup useEffect',
|
||||
]);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<ErrorBoundary>
|
||||
<Conditional showChildren={false} />
|
||||
|
@ -2381,7 +2381,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
}
|
||||
}
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(<Conditional showChildren={true} />);
|
||||
});
|
||||
|
||||
|
@ -2390,11 +2390,10 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
'BrokenUseEffectCleanup useEffect',
|
||||
]);
|
||||
|
||||
expect(() => {
|
||||
act(() => {
|
||||
ReactNoop.render(<Conditional showChildren={false} />);
|
||||
});
|
||||
}).toThrow('Expected error');
|
||||
await act(async () => {
|
||||
ReactNoop.render(<Conditional showChildren={false} />);
|
||||
await waitForThrow('Expected error');
|
||||
});
|
||||
|
||||
assertLog(['BrokenUseEffectCleanup useEffect destroy']);
|
||||
|
||||
|
@ -2425,7 +2424,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
prevProps.prop === nextProps.prop;
|
||||
const MemoizedChild = React.memo(Child, isEqual);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<Wrapper>
|
||||
<MemoizedChild key={1} />
|
||||
|
@ -2435,7 +2434,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
assertLog(['render', 'layout create', 'passive create']);
|
||||
|
||||
// Include at least one no-op (memoized) update to trigger original bug.
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<Wrapper>
|
||||
<MemoizedChild key={1} />
|
||||
|
@ -2444,7 +2443,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
});
|
||||
assertLog([]);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<Wrapper>
|
||||
<MemoizedChild key={2} />
|
||||
|
@ -2459,7 +2458,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
'passive create',
|
||||
]);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(null);
|
||||
});
|
||||
assertLog(['layout destroy', 'passive destroy']);
|
||||
|
@ -2492,7 +2491,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
prevProps.prop === nextProps.prop;
|
||||
const MemoizedChild = React.memo(Child, isEqual);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<Wrapper>
|
||||
<MemoizedChild key={1} />
|
||||
|
@ -2502,7 +2501,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
assertLog(['render', 'layout create', 'passive create']);
|
||||
|
||||
// Include at least one no-op (memoized) update to trigger original bug.
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<Wrapper>
|
||||
<MemoizedChild key={1} />
|
||||
|
@ -2511,7 +2510,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
});
|
||||
assertLog([]);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<Wrapper>
|
||||
<MemoizedChild key={2} />
|
||||
|
@ -2526,12 +2525,16 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
'passive create',
|
||||
]);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(null);
|
||||
});
|
||||
assertLog(['layout destroy', 'passive destroy']);
|
||||
});
|
||||
|
||||
// TODO: This test fails when skipUnmountedBoundaries is disabled. However,
|
||||
// it's also rolled out to open source already and partially to www. So
|
||||
// we should probably just land it.
|
||||
// @gate skipUnmountedBoundaries
|
||||
it('assumes passive effect destroy function is either a function or undefined', async () => {
|
||||
function App(props) {
|
||||
useEffect(() => {
|
||||
|
@ -2541,8 +2544,8 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
}
|
||||
|
||||
const root1 = ReactNoop.createRoot();
|
||||
expect(() => {
|
||||
act(() => {
|
||||
await expect(async () => {
|
||||
await act(async () => {
|
||||
root1.render(<App return={17} />);
|
||||
});
|
||||
}).toErrorDev([
|
||||
|
@ -2551,8 +2554,8 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
]);
|
||||
|
||||
const root2 = ReactNoop.createRoot();
|
||||
expect(() => {
|
||||
act(() => {
|
||||
await expect(async () => {
|
||||
await act(async () => {
|
||||
root2.render(<App return={null} />);
|
||||
});
|
||||
}).toErrorDev([
|
||||
|
@ -2562,8 +2565,8 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
]);
|
||||
|
||||
const root3 = ReactNoop.createRoot();
|
||||
expect(() => {
|
||||
act(() => {
|
||||
await expect(async () => {
|
||||
await act(async () => {
|
||||
root3.render(<App return={Promise.resolve()} />);
|
||||
});
|
||||
}).toErrorDev([
|
||||
|
@ -2573,11 +2576,10 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
]);
|
||||
|
||||
// Error on unmount because React assumes the value is a function
|
||||
expect(() =>
|
||||
act(() => {
|
||||
root3.unmount();
|
||||
}),
|
||||
).toThrow('is not a function');
|
||||
await act(async () => {
|
||||
root3.render(null);
|
||||
await waitForThrow('is not a function');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -2921,8 +2923,8 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
}
|
||||
|
||||
const root1 = ReactNoop.createRoot();
|
||||
expect(() => {
|
||||
act(() => {
|
||||
await expect(async () => {
|
||||
await act(async () => {
|
||||
root1.render(<App return={17} />);
|
||||
});
|
||||
}).toErrorDev([
|
||||
|
@ -2931,8 +2933,8 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
]);
|
||||
|
||||
const root2 = ReactNoop.createRoot();
|
||||
expect(() => {
|
||||
act(() => {
|
||||
await expect(async () => {
|
||||
await act(async () => {
|
||||
root2.render(<App return={null} />);
|
||||
});
|
||||
}).toErrorDev([
|
||||
|
@ -2942,8 +2944,8 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
]);
|
||||
|
||||
const root3 = ReactNoop.createRoot();
|
||||
expect(() => {
|
||||
act(() => {
|
||||
await expect(async () => {
|
||||
await act(async () => {
|
||||
root3.render(<App return={Promise.resolve()} />);
|
||||
});
|
||||
}).toErrorDev([
|
||||
|
@ -2953,11 +2955,10 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
]);
|
||||
|
||||
// Error on unmount because React assumes the value is a function
|
||||
expect(() =>
|
||||
act(() => {
|
||||
root3.unmount();
|
||||
}),
|
||||
).toThrow('is not a function');
|
||||
await act(async () => {
|
||||
root3.render(null);
|
||||
await waitForThrow('is not a function');
|
||||
});
|
||||
});
|
||||
|
||||
it('warns when setState is called from insertion effect setup', async () => {
|
||||
|
@ -2973,17 +2974,16 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
}
|
||||
|
||||
const root = ReactNoop.createRoot();
|
||||
expect(() => {
|
||||
act(() => {
|
||||
await expect(async () => {
|
||||
await act(async () => {
|
||||
root.render(<App />);
|
||||
});
|
||||
}).toErrorDev(['Warning: useInsertionEffect must not schedule updates.']);
|
||||
|
||||
expect(() => {
|
||||
act(() => {
|
||||
root.render(<App throw={true} />);
|
||||
});
|
||||
}).toThrow('No');
|
||||
await act(async () => {
|
||||
root.render(<App throw={true} />);
|
||||
await waitForThrow('No');
|
||||
});
|
||||
|
||||
// Should not warn for regular effects after throw.
|
||||
function NotInsertion() {
|
||||
|
@ -2993,7 +2993,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
}, []);
|
||||
return null;
|
||||
}
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
root.render(<NotInsertion />);
|
||||
});
|
||||
});
|
||||
|
@ -3013,20 +3013,19 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
}
|
||||
|
||||
const root = ReactNoop.createRoot();
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
root.render(<App foo="hello" />);
|
||||
});
|
||||
expect(() => {
|
||||
act(() => {
|
||||
await expect(async () => {
|
||||
await act(async () => {
|
||||
root.render(<App foo="goodbye" />);
|
||||
});
|
||||
}).toErrorDev(['Warning: useInsertionEffect must not schedule updates.']);
|
||||
|
||||
expect(() => {
|
||||
act(() => {
|
||||
root.render(<App throw={true} />);
|
||||
});
|
||||
}).toThrow('No');
|
||||
await act(async () => {
|
||||
root.render(<App throw={true} />);
|
||||
await waitForThrow('No');
|
||||
});
|
||||
|
||||
// Should not warn for regular effects after throw.
|
||||
function NotInsertion() {
|
||||
|
@ -3036,7 +3035,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
}, []);
|
||||
return null;
|
||||
}
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
root.render(<NotInsertion />);
|
||||
});
|
||||
});
|
||||
|
@ -3204,8 +3203,8 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
}
|
||||
|
||||
const root1 = ReactNoop.createRoot();
|
||||
expect(() => {
|
||||
act(() => {
|
||||
await expect(async () => {
|
||||
await act(async () => {
|
||||
root1.render(<App return={17} />);
|
||||
});
|
||||
}).toErrorDev([
|
||||
|
@ -3214,8 +3213,8 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
]);
|
||||
|
||||
const root2 = ReactNoop.createRoot();
|
||||
expect(() => {
|
||||
act(() => {
|
||||
await expect(async () => {
|
||||
await act(async () => {
|
||||
root2.render(<App return={null} />);
|
||||
});
|
||||
}).toErrorDev([
|
||||
|
@ -3225,8 +3224,8 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
]);
|
||||
|
||||
const root3 = ReactNoop.createRoot();
|
||||
expect(() => {
|
||||
act(() => {
|
||||
await expect(async () => {
|
||||
await act(async () => {
|
||||
root3.render(<App return={Promise.resolve()} />);
|
||||
});
|
||||
}).toErrorDev([
|
||||
|
@ -3236,11 +3235,10 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
]);
|
||||
|
||||
// Error on unmount because React assumes the value is a function
|
||||
expect(() =>
|
||||
act(() => {
|
||||
root3.unmount();
|
||||
}),
|
||||
).toThrow('is not a function');
|
||||
await act(async () => {
|
||||
root3.render(null);
|
||||
await waitForThrow('is not a function');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -3279,7 +3277,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
</>,
|
||||
);
|
||||
|
||||
act(button.current.increment);
|
||||
await act(async () => button.current.increment());
|
||||
assertLog([
|
||||
// Button should not re-render, because its props haven't changed
|
||||
// 'Increment',
|
||||
|
@ -3307,7 +3305,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
);
|
||||
|
||||
// Callback should have updated
|
||||
act(button.current.increment);
|
||||
await act(async () => button.current.increment());
|
||||
assertLog(['Count: 11']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
|
@ -3425,7 +3423,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
|
||||
expect(counter.current.count).toBe(0);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
counter.current.dispatch(INCREMENT);
|
||||
});
|
||||
assertLog(['Count: 1']);
|
||||
|
@ -3455,7 +3453,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
|
||||
expect(counter.current.count).toBe(0);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
counter.current.dispatch(INCREMENT);
|
||||
});
|
||||
assertLog(['Count: 1']);
|
||||
|
@ -3492,7 +3490,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
expect(counter.current.count).toBe(0);
|
||||
expect(totalRefUpdates).toBe(1);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
counter.current.dispatch(INCREMENT);
|
||||
});
|
||||
assertLog(['Count: 1']);
|
||||
|
@ -3591,7 +3589,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
);
|
||||
}
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(<App />);
|
||||
});
|
||||
|
||||
|
@ -3689,7 +3687,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
<span prop="A: 0, B: 0, C: [not loaded]" />,
|
||||
);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
updateA(2);
|
||||
updateB(3);
|
||||
});
|
||||
|
@ -3751,7 +3749,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
ReactNoop.render(<App loadC={true} />);
|
||||
await waitForAll(['A: 0, B: 0, C: 0']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="A: 0, B: 0, C: 0" />);
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
updateA(2);
|
||||
updateB(3);
|
||||
updateC(4);
|
||||
|
@ -3857,7 +3855,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
expect(ReactNoop).toMatchRenderedOutput('1');
|
||||
});
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
setCounter(2);
|
||||
});
|
||||
assertLog(['Render: 1', 'Effect: 2', 'Reducer: 2', 'Render: 2']);
|
||||
|
@ -3892,7 +3890,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
await waitForAll(['Render disabled: true', 'Render count: 0']);
|
||||
expect(ReactNoop).toMatchRenderedOutput('0');
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
// These increments should have no effect, since disabled=true
|
||||
increment();
|
||||
increment();
|
||||
|
@ -3901,7 +3899,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
assertLog(['Render disabled: true', 'Render count: 0']);
|
||||
expect(ReactNoop).toMatchRenderedOutput('0');
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
// Enabling the updater should *not* replay the previous increment() actions
|
||||
setDisabled(false);
|
||||
});
|
||||
|
@ -3941,7 +3939,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
await waitForAll(['Render disabled: true', 'Render count: 0']);
|
||||
expect(ReactNoop).toMatchRenderedOutput('0');
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
// These increments should have no effect, since disabled=true
|
||||
increment();
|
||||
increment();
|
||||
|
@ -3950,7 +3948,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
assertLog(['Render count: 0']);
|
||||
expect(ReactNoop).toMatchRenderedOutput('0');
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
// Enabling the updater should *not* replay the previous increment() actions
|
||||
setDisabled(false);
|
||||
});
|
||||
|
@ -3990,7 +3988,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
await waitForAll(['Render disabled: true', 'Render count: 0']);
|
||||
expect(ReactNoop).toMatchRenderedOutput('0');
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
// Although the increment happens first (and would seem to do nothing since disabled=true),
|
||||
// because these calls are in a batch the parent updates first. This should cause the child
|
||||
// to re-render with disabled=false and *then* process the increment action, which now
|
||||
|
@ -4085,7 +4083,7 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput('0');
|
||||
|
||||
act(() => dispatch());
|
||||
await act(async () => dispatch());
|
||||
assertLog(['Step: 5, Shadow: 5']);
|
||||
expect(ReactNoop).toMatchRenderedOutput('5');
|
||||
});
|
||||
|
@ -4110,10 +4108,10 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
return `${a ? 'A' : 'a'}${b ? 'B' : 'b'}${c ? 'C' : 'c'}`;
|
||||
}
|
||||
|
||||
act(() => ReactNoop.render(<App />));
|
||||
await act(async () => ReactNoop.render(<App />));
|
||||
expect(ReactNoop).toMatchRenderedOutput('abc');
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
updateA(true);
|
||||
// This update should not get dropped.
|
||||
updateC(true);
|
||||
|
@ -4282,25 +4280,25 @@ describe('ReactHooksWithNoopRenderer', () => {
|
|||
return <Text text={`Render: ${count}`} />;
|
||||
}
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(<Test />);
|
||||
});
|
||||
|
||||
assertLog(['Render: 0', 'Effect: 0']);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
handleClick();
|
||||
});
|
||||
|
||||
assertLog(['Render: 0']);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
handleClick();
|
||||
});
|
||||
|
||||
assertLog(['Render: 0']);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
handleClick();
|
||||
});
|
||||
|
||||
|
|
|
@ -32,8 +32,8 @@ describe('ReactOffscreenStrictMode', () => {
|
|||
}
|
||||
|
||||
// @gate __DEV__ && enableOffscreen
|
||||
it('should trigger strict effects when offscreen is visible', () => {
|
||||
act(() => {
|
||||
it('should trigger strict effects when offscreen is visible', async () => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<React.StrictMode>
|
||||
<Offscreen mode="visible">
|
||||
|
@ -56,8 +56,8 @@ describe('ReactOffscreenStrictMode', () => {
|
|||
});
|
||||
|
||||
// @gate __DEV__ && enableOffscreen && useModernStrictMode
|
||||
it('should not trigger strict effects when offscreen is hidden', () => {
|
||||
act(() => {
|
||||
it('should not trigger strict effects when offscreen is hidden', async () => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<React.StrictMode>
|
||||
<Offscreen mode="hidden">
|
||||
|
@ -71,7 +71,7 @@ describe('ReactOffscreenStrictMode', () => {
|
|||
|
||||
log = [];
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<React.StrictMode>
|
||||
<Offscreen mode="hidden">
|
||||
|
@ -86,7 +86,7 @@ describe('ReactOffscreenStrictMode', () => {
|
|||
|
||||
log = [];
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<React.StrictMode>
|
||||
<Offscreen mode="visible">
|
||||
|
@ -109,7 +109,7 @@ describe('ReactOffscreenStrictMode', () => {
|
|||
|
||||
log = [];
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<React.StrictMode>
|
||||
<Offscreen mode="hidden">
|
||||
|
@ -127,7 +127,7 @@ describe('ReactOffscreenStrictMode', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('should not cause infinite render loop when StrictMode is used with Suspense and synchronous set states', () => {
|
||||
it('should not cause infinite render loop when StrictMode is used with Suspense and synchronous set states', async () => {
|
||||
// This is a regression test, see https://github.com/facebook/react/pull/25179 for more details.
|
||||
function App() {
|
||||
const [state, setState] = React.useState(false);
|
||||
|
@ -143,7 +143,7 @@ describe('ReactOffscreenStrictMode', () => {
|
|||
return state;
|
||||
}
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<React.StrictMode>
|
||||
<React.Suspense>
|
||||
|
@ -193,7 +193,7 @@ describe('ReactOffscreenStrictMode', () => {
|
|||
return null;
|
||||
}
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<React.StrictMode>
|
||||
<Offscreen mode="visible">
|
||||
|
|
|
@ -2,12 +2,9 @@ let React;
|
|||
let ReactTestRenderer;
|
||||
let ReactFeatureFlags;
|
||||
let Scheduler;
|
||||
let ReactCache;
|
||||
let Suspense;
|
||||
let act;
|
||||
|
||||
let TextResource;
|
||||
let textResourceShouldFail;
|
||||
let textCache;
|
||||
|
||||
let assertLog;
|
||||
let waitForPaint;
|
||||
|
@ -24,7 +21,6 @@ describe('ReactSuspense', () => {
|
|||
ReactTestRenderer = require('react-test-renderer');
|
||||
act = require('jest-react').act;
|
||||
Scheduler = require('scheduler');
|
||||
ReactCache = require('react-cache');
|
||||
|
||||
Suspense = React.Suspense;
|
||||
|
||||
|
@ -34,73 +30,71 @@ describe('ReactSuspense', () => {
|
|||
assertLog = InternalTestUtils.assertLog;
|
||||
waitFor = InternalTestUtils.waitFor;
|
||||
|
||||
TextResource = ReactCache.unstable_createResource(
|
||||
([text, ms = 0]) => {
|
||||
let listeners = null;
|
||||
let status = 'pending';
|
||||
let value = null;
|
||||
return {
|
||||
then(resolve, reject) {
|
||||
switch (status) {
|
||||
case 'pending': {
|
||||
if (listeners === null) {
|
||||
listeners = [{resolve, reject}];
|
||||
setTimeout(() => {
|
||||
if (textResourceShouldFail) {
|
||||
Scheduler.log(`Promise rejected [${text}]`);
|
||||
status = 'rejected';
|
||||
value = new Error('Failed to load: ' + text);
|
||||
listeners.forEach(listener => listener.reject(value));
|
||||
} else {
|
||||
Scheduler.log(`Promise resolved [${text}]`);
|
||||
status = 'resolved';
|
||||
value = text;
|
||||
listeners.forEach(listener => listener.resolve(value));
|
||||
}
|
||||
}, ms);
|
||||
} else {
|
||||
listeners.push({resolve, reject});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'resolved': {
|
||||
resolve(value);
|
||||
break;
|
||||
}
|
||||
case 'rejected': {
|
||||
reject(value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
},
|
||||
([text, ms]) => text,
|
||||
);
|
||||
textResourceShouldFail = false;
|
||||
textCache = new Map();
|
||||
});
|
||||
|
||||
function Text(props) {
|
||||
Scheduler.log(props.text);
|
||||
return props.text;
|
||||
function resolveText(text) {
|
||||
const record = textCache.get(text);
|
||||
if (record === undefined) {
|
||||
const newRecord = {
|
||||
status: 'resolved',
|
||||
value: text,
|
||||
};
|
||||
textCache.set(text, newRecord);
|
||||
} else if (record.status === 'pending') {
|
||||
const thenable = record.value;
|
||||
record.status = 'resolved';
|
||||
record.value = text;
|
||||
thenable.pings.forEach(t => t());
|
||||
}
|
||||
}
|
||||
|
||||
function AsyncText(props) {
|
||||
const text = props.text;
|
||||
try {
|
||||
TextResource.read([props.text, props.ms]);
|
||||
Scheduler.log(text);
|
||||
return text;
|
||||
} catch (promise) {
|
||||
if (typeof promise.then === 'function') {
|
||||
Scheduler.log(`Suspend! [${text}]`);
|
||||
} else {
|
||||
Scheduler.log(`Error! [${text}]`);
|
||||
function readText(text) {
|
||||
const record = textCache.get(text);
|
||||
if (record !== undefined) {
|
||||
switch (record.status) {
|
||||
case 'pending':
|
||||
Scheduler.log(`Suspend! [${text}]`);
|
||||
throw record.value;
|
||||
case 'rejected':
|
||||
throw record.value;
|
||||
case 'resolved':
|
||||
return record.value;
|
||||
}
|
||||
throw promise;
|
||||
} else {
|
||||
Scheduler.log(`Suspend! [${text}]`);
|
||||
const thenable = {
|
||||
pings: [],
|
||||
then(resolve) {
|
||||
if (newRecord.status === 'pending') {
|
||||
thenable.pings.push(resolve);
|
||||
} else {
|
||||
Promise.resolve().then(() => resolve(newRecord.value));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const newRecord = {
|
||||
status: 'pending',
|
||||
value: thenable,
|
||||
};
|
||||
textCache.set(text, newRecord);
|
||||
|
||||
throw thenable;
|
||||
}
|
||||
}
|
||||
|
||||
function Text({text}) {
|
||||
Scheduler.log(text);
|
||||
return text;
|
||||
}
|
||||
|
||||
function AsyncText({text}) {
|
||||
readText(text);
|
||||
Scheduler.log(text);
|
||||
return text;
|
||||
}
|
||||
|
||||
it('suspends rendering and continues later', async () => {
|
||||
function Bar(props) {
|
||||
Scheduler.log('Bar');
|
||||
|
@ -146,16 +140,10 @@ describe('ReactSuspense', () => {
|
|||
]);
|
||||
expect(root).toMatchRenderedOutput(null);
|
||||
|
||||
// Flush some of the time
|
||||
jest.advanceTimersByTime(50);
|
||||
// Still nothing...
|
||||
await waitForAll([]);
|
||||
expect(root).toMatchRenderedOutput(null);
|
||||
|
||||
// Flush the promise completely
|
||||
jest.advanceTimersByTime(50);
|
||||
// Renders successfully
|
||||
assertLog(['Promise resolved [A]']);
|
||||
await resolveText('A');
|
||||
await waitForAll(['Foo', 'Bar', 'A', 'B']);
|
||||
expect(root).toMatchRenderedOutput('AB');
|
||||
});
|
||||
|
@ -184,19 +172,15 @@ describe('ReactSuspense', () => {
|
|||
]);
|
||||
expect(root).toMatchRenderedOutput('Loading A...Loading B...');
|
||||
|
||||
// Advance time by enough that the first Suspense's promise resolves and
|
||||
// switches back to the normal view. The second Suspense should still
|
||||
// show the placeholder
|
||||
jest.advanceTimersByTime(5000);
|
||||
// TODO: Should we throw if you forget to call toHaveYielded?
|
||||
assertLog(['Promise resolved [A]']);
|
||||
// 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']);
|
||||
expect(root).toMatchRenderedOutput('ALoading B...');
|
||||
|
||||
// Advance time by enough that the second Suspense's promise resolves
|
||||
// and switches back to the normal view
|
||||
jest.advanceTimersByTime(1000);
|
||||
assertLog(['Promise resolved [B]']);
|
||||
// Resolve the second Suspense's promise resolves and switche back to the
|
||||
// normal view
|
||||
await resolveText('B');
|
||||
await waitForAll(['B']);
|
||||
expect(root).toMatchRenderedOutput('AB');
|
||||
});
|
||||
|
@ -284,11 +268,6 @@ describe('ReactSuspense', () => {
|
|||
);
|
||||
}
|
||||
|
||||
// Committing fallbacks should be throttled.
|
||||
// First, advance some time to skip the first threshold.
|
||||
jest.advanceTimersByTime(600);
|
||||
Scheduler.unstable_advanceTime(600);
|
||||
|
||||
const root = ReactTestRenderer.create(<Foo />, {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
|
@ -302,10 +281,7 @@ describe('ReactSuspense', () => {
|
|||
]);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
|
||||
// Resolve A.
|
||||
jest.advanceTimersByTime(200);
|
||||
Scheduler.unstable_advanceTime(200);
|
||||
assertLog(['Promise resolved [A]']);
|
||||
await resolveText('A');
|
||||
await waitForAll(['A', 'Suspend! [B]', 'Loading more...']);
|
||||
|
||||
// By this point, we have enough info to show "A" and "Loading more..."
|
||||
|
@ -313,15 +289,12 @@ describe('ReactSuspense', () => {
|
|||
// showing the inner fallback hoping that B will resolve soon enough.
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
|
||||
// Resolve B.
|
||||
jest.advanceTimersByTime(100);
|
||||
Scheduler.unstable_advanceTime(100);
|
||||
assertLog(['Promise resolved [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.
|
||||
expect(root).toMatchRenderedOutput('AB');
|
||||
});
|
||||
|
@ -339,11 +312,6 @@ describe('ReactSuspense', () => {
|
|||
);
|
||||
}
|
||||
|
||||
// Committing fallbacks should be throttled.
|
||||
// First, advance some time to skip the first threshold.
|
||||
jest.advanceTimersByTime(600);
|
||||
Scheduler.unstable_advanceTime(600);
|
||||
|
||||
const root = ReactTestRenderer.create(<Foo />, {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
|
@ -357,27 +325,20 @@ describe('ReactSuspense', () => {
|
|||
]);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
|
||||
// Resolve A.
|
||||
jest.advanceTimersByTime(200);
|
||||
Scheduler.unstable_advanceTime(200);
|
||||
assertLog(['Promise resolved [A]']);
|
||||
await resolveText('A');
|
||||
await waitForAll(['A', 'Suspend! [B]', 'Loading more...']);
|
||||
|
||||
// By this point, we have enough info to show "A" and "Loading more..."
|
||||
// However, we've just shown the outer fallback. So we'll delay
|
||||
// showing the inner fallback hoping that B will resolve soon enough.
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
|
||||
// Wait some more. B is still not resolving.
|
||||
// But if we wait a bit longer, eventually we'll give up and show a
|
||||
// fallback. The exact value here isn't important. It's a JND ("Just
|
||||
// Noticeable Difference").
|
||||
jest.advanceTimersByTime(500);
|
||||
Scheduler.unstable_advanceTime(500);
|
||||
// Give up and render A with a spinner for B.
|
||||
expect(root).toMatchRenderedOutput('ALoading more...');
|
||||
|
||||
// Resolve B.
|
||||
jest.advanceTimersByTime(500);
|
||||
Scheduler.unstable_advanceTime(500);
|
||||
assertLog(['Promise resolved [B]']);
|
||||
await resolveText('B');
|
||||
await waitForAll(['B']);
|
||||
expect(root).toMatchRenderedOutput('AB');
|
||||
});
|
||||
|
@ -423,18 +384,7 @@ describe('ReactSuspense', () => {
|
|||
|
||||
const MemoizedChild = memo(function MemoizedChild() {
|
||||
const text = useContext(ValueContext);
|
||||
try {
|
||||
TextResource.read([text, 1000]);
|
||||
Scheduler.log(text);
|
||||
return text;
|
||||
} catch (promise) {
|
||||
if (typeof promise.then === 'function') {
|
||||
Scheduler.log(`Suspend! [${text}]`);
|
||||
} else {
|
||||
Scheduler.log(`Error! [${text}]`);
|
||||
}
|
||||
throw promise;
|
||||
}
|
||||
return <Text text={readText(text)} />;
|
||||
});
|
||||
|
||||
let setValue;
|
||||
|
@ -455,17 +405,15 @@ describe('ReactSuspense', () => {
|
|||
unstable_isConcurrent: true,
|
||||
});
|
||||
await waitForAll(['Suspend! [default]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
assertLog(['Promise resolved [default]']);
|
||||
await resolveText('default');
|
||||
await waitForAll(['default']);
|
||||
expect(root).toMatchRenderedOutput('default');
|
||||
|
||||
act(() => setValue('new value'));
|
||||
await act(async () => setValue('new value'));
|
||||
assertLog(['Suspend! [new value]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
assertLog(['Promise resolved [new value]']);
|
||||
await resolveText('new value');
|
||||
await waitForAll(['new value']);
|
||||
expect(root).toMatchRenderedOutput('new value');
|
||||
});
|
||||
|
@ -478,18 +426,7 @@ describe('ReactSuspense', () => {
|
|||
const MemoizedChild = memo(
|
||||
function MemoizedChild() {
|
||||
const text = useContext(ValueContext);
|
||||
try {
|
||||
TextResource.read([text, 1000]);
|
||||
Scheduler.log(text);
|
||||
return text;
|
||||
} catch (promise) {
|
||||
if (typeof promise.then === 'function') {
|
||||
Scheduler.log(`Suspend! [${text}]`);
|
||||
} else {
|
||||
Scheduler.log(`Error! [${text}]`);
|
||||
}
|
||||
throw promise;
|
||||
}
|
||||
return <Text text={readText(text)} />;
|
||||
},
|
||||
function areEqual(prevProps, nextProps) {
|
||||
return true;
|
||||
|
@ -514,17 +451,15 @@ describe('ReactSuspense', () => {
|
|||
unstable_isConcurrent: true,
|
||||
});
|
||||
await waitForAll(['Suspend! [default]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
assertLog(['Promise resolved [default]']);
|
||||
await resolveText('default');
|
||||
await waitForAll(['default']);
|
||||
expect(root).toMatchRenderedOutput('default');
|
||||
|
||||
act(() => setValue('new value'));
|
||||
await act(async () => setValue('new value'));
|
||||
assertLog(['Suspend! [new value]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
assertLog(['Promise resolved [new value]']);
|
||||
await resolveText('new value');
|
||||
await waitForAll(['new value']);
|
||||
expect(root).toMatchRenderedOutput('new value');
|
||||
});
|
||||
|
@ -536,18 +471,7 @@ describe('ReactSuspense', () => {
|
|||
|
||||
function MemoizedChild() {
|
||||
const text = useContext(ValueContext);
|
||||
try {
|
||||
TextResource.read([text, 1000]);
|
||||
Scheduler.log(text);
|
||||
return text;
|
||||
} catch (promise) {
|
||||
if (typeof promise.then === 'function') {
|
||||
Scheduler.log(`Suspend! [${text}]`);
|
||||
} else {
|
||||
Scheduler.log(`Error! [${text}]`);
|
||||
}
|
||||
throw promise;
|
||||
}
|
||||
return <Text text={readText(text)} />;
|
||||
}
|
||||
|
||||
let setValue;
|
||||
|
@ -571,17 +495,15 @@ describe('ReactSuspense', () => {
|
|||
},
|
||||
);
|
||||
await waitForAll(['Suspend! [default]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
assertLog(['Promise resolved [default]']);
|
||||
await resolveText('default');
|
||||
await waitForAll(['default']);
|
||||
expect(root).toMatchRenderedOutput('default');
|
||||
|
||||
act(() => setValue('new value'));
|
||||
await act(async () => setValue('new value'));
|
||||
assertLog(['Suspend! [new value]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
assertLog(['Promise resolved [new value]']);
|
||||
await resolveText('new value');
|
||||
await waitForAll(['new value']);
|
||||
expect(root).toMatchRenderedOutput('new value');
|
||||
});
|
||||
|
@ -593,18 +515,7 @@ describe('ReactSuspense', () => {
|
|||
|
||||
const MemoizedChild = forwardRef(() => {
|
||||
const text = useContext(ValueContext);
|
||||
try {
|
||||
TextResource.read([text, 1000]);
|
||||
Scheduler.log(text);
|
||||
return text;
|
||||
} catch (promise) {
|
||||
if (typeof promise.then === 'function') {
|
||||
Scheduler.log(`Suspend! [${text}]`);
|
||||
} else {
|
||||
Scheduler.log(`Error! [${text}]`);
|
||||
}
|
||||
throw promise;
|
||||
}
|
||||
return <Text text={readText(text)} />;
|
||||
});
|
||||
|
||||
let setValue;
|
||||
|
@ -628,17 +539,15 @@ describe('ReactSuspense', () => {
|
|||
},
|
||||
);
|
||||
await waitForAll(['Suspend! [default]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
assertLog(['Promise resolved [default]']);
|
||||
await resolveText('default');
|
||||
await waitForAll(['default']);
|
||||
expect(root).toMatchRenderedOutput('default');
|
||||
|
||||
act(() => setValue('new value'));
|
||||
await act(async () => setValue('new value'));
|
||||
assertLog(['Suspend! [new value]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
assertLog(['Promise resolved [new value]']);
|
||||
await resolveText('new value');
|
||||
await waitForAll(['new value']);
|
||||
expect(root).toMatchRenderedOutput('new value');
|
||||
});
|
||||
|
@ -674,12 +583,17 @@ describe('ReactSuspense', () => {
|
|||
await waitForAll(['Child 1', 'create layout']);
|
||||
expect(root).toMatchRenderedOutput('Child 1');
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
_setShow(true);
|
||||
});
|
||||
assertLog(['Child 1', 'Suspend! [Child 2]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
assertLog(['destroy layout', 'Promise resolved [Child 2]']);
|
||||
assertLog([
|
||||
'Child 1',
|
||||
'Suspend! [Child 2]',
|
||||
'Loading...',
|
||||
'destroy layout',
|
||||
]);
|
||||
|
||||
await resolveText('Child 2');
|
||||
await waitForAll(['Child 1', 'Child 2', 'create layout']);
|
||||
expect(root).toMatchRenderedOutput(['Child 1', 'Child 2'].join(''));
|
||||
});
|
||||
|
@ -715,20 +629,8 @@ describe('ReactSuspense', () => {
|
|||
}
|
||||
render() {
|
||||
instance = this;
|
||||
const text = `${this.props.text}:${this.state.step}`;
|
||||
const ms = this.props.ms;
|
||||
try {
|
||||
TextResource.read([text, ms]);
|
||||
Scheduler.log(text);
|
||||
return text;
|
||||
} catch (promise) {
|
||||
if (typeof promise.then === 'function') {
|
||||
Scheduler.log(`Suspend! [${text}]`);
|
||||
} else {
|
||||
Scheduler.log(`Error! [${text}]`);
|
||||
}
|
||||
throw promise;
|
||||
}
|
||||
const text = readText(`${this.props.text}:${this.state.step}`);
|
||||
return <Text text={text} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -758,9 +660,7 @@ describe('ReactSuspense', () => {
|
|||
]);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
|
||||
jest.advanceTimersByTime(100);
|
||||
|
||||
assertLog(['Promise resolved [B:1]']);
|
||||
await resolveText('B:1');
|
||||
await waitForPaint([
|
||||
'B:1',
|
||||
'Unmount [Loading...]',
|
||||
|
@ -773,9 +673,7 @@ describe('ReactSuspense', () => {
|
|||
assertLog(['Suspend! [B:2]', 'Loading...', 'Mount [Loading...]']);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
|
||||
jest.advanceTimersByTime(100);
|
||||
|
||||
assertLog(['Promise resolved [B:2]']);
|
||||
await resolveText('B:2');
|
||||
await waitForPaint(['B:2', 'Unmount [Loading...]', 'Update [B:2]']);
|
||||
expect(root).toMatchRenderedOutput('AB:2C');
|
||||
});
|
||||
|
@ -803,9 +701,7 @@ describe('ReactSuspense', () => {
|
|||
|
||||
assertLog(['Stateful: 1', 'Suspend! [A]', 'Loading...']);
|
||||
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
assertLog(['Promise resolved [A]']);
|
||||
await resolveText('A');
|
||||
await waitForPaint(['A']);
|
||||
expect(root).toMatchRenderedOutput('Stateful: 1A');
|
||||
|
||||
|
@ -817,9 +713,7 @@ describe('ReactSuspense', () => {
|
|||
assertLog(['Stateful: 2', 'Suspend! [B]']);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
assertLog(['Promise resolved [B]']);
|
||||
await resolveText('B');
|
||||
await waitForPaint(['B']);
|
||||
expect(root).toMatchRenderedOutput('Stateful: 2B');
|
||||
});
|
||||
|
@ -855,9 +749,7 @@ describe('ReactSuspense', () => {
|
|||
|
||||
assertLog(['Stateful: 1', 'Suspend! [A]', 'Loading...']);
|
||||
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
assertLog(['Promise resolved [A]']);
|
||||
await resolveText('A');
|
||||
await waitForPaint(['A']);
|
||||
expect(root).toMatchRenderedOutput('Stateful: 1A');
|
||||
|
||||
|
@ -876,9 +768,7 @@ describe('ReactSuspense', () => {
|
|||
]);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
assertLog(['Promise resolved [B]']);
|
||||
await resolveText('B');
|
||||
await waitForPaint(['B']);
|
||||
expect(root).toMatchRenderedOutput('Stateful: 2B');
|
||||
});
|
||||
|
@ -889,20 +779,7 @@ describe('ReactSuspense', () => {
|
|||
Scheduler.log('will unmount');
|
||||
}
|
||||
render() {
|
||||
const text = this.props.text;
|
||||
const ms = this.props.ms;
|
||||
try {
|
||||
TextResource.read([text, ms]);
|
||||
Scheduler.log(text);
|
||||
return text;
|
||||
} catch (promise) {
|
||||
if (typeof promise.then === 'function') {
|
||||
Scheduler.log(`Suspend! [${text}]`);
|
||||
} else {
|
||||
Scheduler.log(`Error! [${text}]`);
|
||||
}
|
||||
throw promise;
|
||||
}
|
||||
return <Text text={readText(this.props.text)} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -932,18 +809,7 @@ describe('ReactSuspense', () => {
|
|||
Scheduler.log('Did commit: ' + text);
|
||||
}, [text]);
|
||||
|
||||
try {
|
||||
TextResource.read([props.text, props.ms]);
|
||||
Scheduler.log(text);
|
||||
return text;
|
||||
} catch (promise) {
|
||||
if (typeof promise.then === 'function') {
|
||||
Scheduler.log(`Suspend! [${text}]`);
|
||||
} else {
|
||||
Scheduler.log(`Error! [${text}]`);
|
||||
}
|
||||
throw promise;
|
||||
}
|
||||
return <Text text={readText(text)} />;
|
||||
}
|
||||
|
||||
function App({text}) {
|
||||
|
@ -956,9 +822,7 @@ describe('ReactSuspense', () => {
|
|||
|
||||
ReactTestRenderer.create(<App text="A" />);
|
||||
assertLog(['Suspend! [A]', 'Loading...']);
|
||||
jest.advanceTimersByTime(500);
|
||||
|
||||
assertLog(['Promise resolved [A]']);
|
||||
await resolveText('A');
|
||||
await waitForPaint(['A', 'Did commit: A']);
|
||||
});
|
||||
|
||||
|
@ -986,15 +850,16 @@ describe('ReactSuspense', () => {
|
|||
|
||||
// Initial render
|
||||
await waitForAll(['Suspend! [Step: 1]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
assertLog(['Promise resolved [Step: 1]']);
|
||||
|
||||
await resolveText('Step: 1');
|
||||
await waitForAll(['Step: 1']);
|
||||
expect(root).toMatchRenderedOutput('Step: 1');
|
||||
|
||||
// Update that suspends
|
||||
instance.setState({step: 2});
|
||||
await waitForAll(['Suspend! [Step: 2]', 'Loading...']);
|
||||
jest.advanceTimersByTime(500);
|
||||
await act(async () => {
|
||||
instance.setState({step: 2});
|
||||
});
|
||||
assertLog(['Suspend! [Step: 2]', 'Loading...']);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
|
||||
// Update while still suspended
|
||||
|
@ -1002,8 +867,8 @@ describe('ReactSuspense', () => {
|
|||
await waitForAll(['Suspend! [Step: 3]']);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
|
||||
jest.advanceTimersByTime(1000);
|
||||
assertLog(['Promise resolved [Step: 2]', 'Promise resolved [Step: 3]']);
|
||||
await resolveText('Step: 2');
|
||||
await resolveText('Step: 3');
|
||||
await waitForAll(['Step: 3']);
|
||||
expect(root).toMatchRenderedOutput('Step: 3');
|
||||
});
|
||||
|
@ -1040,23 +905,17 @@ describe('ReactSuspense', () => {
|
|||
]);
|
||||
await waitForAll([]);
|
||||
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
assertLog(['Promise resolved [Child 1]']);
|
||||
await resolveText('Child 1');
|
||||
await waitForPaint([
|
||||
'Child 1',
|
||||
'Suspend! [Child 2]',
|
||||
'Suspend! [Child 3]',
|
||||
]);
|
||||
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
assertLog(['Promise resolved [Child 2]']);
|
||||
await resolveText('Child 2');
|
||||
await waitForPaint(['Child 2', 'Suspend! [Child 3]']);
|
||||
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
assertLog(['Promise resolved [Child 3]']);
|
||||
await resolveText('Child 3');
|
||||
await waitForPaint(['Child 3']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
['Child 1', 'Child 2', 'Child 3'].join(''),
|
||||
|
@ -1083,11 +942,12 @@ describe('ReactSuspense', () => {
|
|||
'Suspend! [Child 2]',
|
||||
'Loading...',
|
||||
]);
|
||||
jest.advanceTimersByTime(1000);
|
||||
assertLog(['Promise resolved [Child 1]']);
|
||||
await resolveText('Child 1');
|
||||
await waitForAll(['Child 1', 'Suspend! [Child 2]']);
|
||||
|
||||
jest.advanceTimersByTime(6000);
|
||||
assertLog(['Promise resolved [Child 2]']);
|
||||
|
||||
await resolveText('Child 2');
|
||||
await waitForAll(['Child 1', 'Child 2']);
|
||||
expect(root).toMatchRenderedOutput(['Child 1', 'Child 2'].join(''));
|
||||
});
|
||||
|
@ -1111,32 +971,29 @@ describe('ReactSuspense', () => {
|
|||
const root = ReactTestRenderer.create(<App />);
|
||||
assertLog(['Suspend! [Tab: 0]', ' + sibling', 'Loading...']);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
assertLog(['Promise resolved [Tab: 0]']);
|
||||
await resolveText('Tab: 0');
|
||||
await waitForPaint(['Tab: 0']);
|
||||
expect(root).toMatchRenderedOutput('Tab: 0 + sibling');
|
||||
|
||||
act(() => setTab(1));
|
||||
await act(async () => setTab(1));
|
||||
assertLog(['Suspend! [Tab: 1]', ' + sibling', 'Loading...']);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
assertLog(['Promise resolved [Tab: 1]']);
|
||||
await resolveText('Tab: 1');
|
||||
await waitForPaint(['Tab: 1']);
|
||||
expect(root).toMatchRenderedOutput('Tab: 1 + sibling');
|
||||
|
||||
act(() => setTab(2));
|
||||
await act(async () => setTab(2));
|
||||
assertLog(['Suspend! [Tab: 2]', ' + sibling', 'Loading...']);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
assertLog(['Promise resolved [Tab: 2]']);
|
||||
await resolveText('Tab: 2');
|
||||
await waitForPaint(['Tab: 2']);
|
||||
expect(root).toMatchRenderedOutput('Tab: 2 + sibling');
|
||||
});
|
||||
|
||||
it('does not warn if an mounted component is pinged', async () => {
|
||||
it('does not warn if a mounted component is pinged', async () => {
|
||||
const {useState} = React;
|
||||
|
||||
const root = ReactTestRenderer.create(null);
|
||||
|
@ -1146,18 +1003,7 @@ describe('ReactSuspense', () => {
|
|||
const [step, _setStep] = useState(0);
|
||||
setStep = _setStep;
|
||||
const fullText = `${text}:${step}`;
|
||||
try {
|
||||
TextResource.read([fullText, ms]);
|
||||
Scheduler.log(fullText);
|
||||
return fullText;
|
||||
} catch (promise) {
|
||||
if (typeof promise.then === 'function') {
|
||||
Scheduler.log(`Suspend! [${fullText}]`);
|
||||
} else {
|
||||
Scheduler.log(`Error! [${fullText}]`);
|
||||
}
|
||||
throw promise;
|
||||
}
|
||||
return <Text text={readText(fullText)} />;
|
||||
}
|
||||
|
||||
root.update(
|
||||
|
@ -1167,19 +1013,18 @@ describe('ReactSuspense', () => {
|
|||
);
|
||||
|
||||
assertLog(['Suspend! [A:0]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
assertLog(['Promise resolved [A:0]']);
|
||||
await resolveText('A:0');
|
||||
await waitForPaint(['A:0']);
|
||||
expect(root).toMatchRenderedOutput('A:0');
|
||||
|
||||
act(() => setStep(1));
|
||||
await act(async () => setStep(1));
|
||||
assertLog(['Suspend! [A:1]', 'Loading...']);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
|
||||
root.update(null);
|
||||
await waitForAll([]);
|
||||
jest.advanceTimersByTime(1000);
|
||||
await act(async () => {
|
||||
root.update(null);
|
||||
});
|
||||
});
|
||||
|
||||
it('memoizes promise listeners per thread ID to prevent redundant renders', async () => {
|
||||
|
@ -1199,10 +1044,7 @@ describe('ReactSuspense', () => {
|
|||
|
||||
assertLog(['Suspend! [A]', 'Suspend! [B]', 'Suspend! [C]', 'Loading...']);
|
||||
|
||||
// Resolve A
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
assertLog(['Promise resolved [A]']);
|
||||
await resolveText('A');
|
||||
await waitForPaint([
|
||||
'A',
|
||||
// The promises for B and C have now been thrown twice
|
||||
|
@ -1210,10 +1052,7 @@ describe('ReactSuspense', () => {
|
|||
'Suspend! [C]',
|
||||
]);
|
||||
|
||||
// Resolve B
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
assertLog(['Promise resolved [B]']);
|
||||
await resolveText('B');
|
||||
await waitForPaint([
|
||||
// Even though the promise for B was thrown twice, we should only
|
||||
// re-render once.
|
||||
|
@ -1222,10 +1061,7 @@ describe('ReactSuspense', () => {
|
|||
'Suspend! [C]',
|
||||
]);
|
||||
|
||||
// Resolve C
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
assertLog(['Promise resolved [C]']);
|
||||
await resolveText('C');
|
||||
await waitForPaint([
|
||||
// Even though the promise for C was thrown three times, we should only
|
||||
// re-render once.
|
||||
|
@ -1233,7 +1069,7 @@ describe('ReactSuspense', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('#14162', () => {
|
||||
it('#14162', async () => {
|
||||
const {lazy} = React;
|
||||
|
||||
function Hello() {
|
||||
|
@ -1267,8 +1103,9 @@ describe('ReactSuspense', () => {
|
|||
|
||||
const root = ReactTestRenderer.create(null);
|
||||
|
||||
root.update(<App name="world" />);
|
||||
jest.advanceTimersByTime(1000);
|
||||
await act(async () => {
|
||||
root.update(<App name="world" />);
|
||||
});
|
||||
});
|
||||
|
||||
it('updates memoized child of suspense component when context updates (simple memo)', async () => {
|
||||
|
@ -1278,18 +1115,7 @@ describe('ReactSuspense', () => {
|
|||
|
||||
const MemoizedChild = memo(function MemoizedChild() {
|
||||
const text = useContext(ValueContext);
|
||||
try {
|
||||
TextResource.read([text, 1000]);
|
||||
Scheduler.log(text);
|
||||
return text;
|
||||
} catch (promise) {
|
||||
if (typeof promise.then === 'function') {
|
||||
Scheduler.log(`Suspend! [${text}]`);
|
||||
} else {
|
||||
Scheduler.log(`Error! [${text}]`);
|
||||
}
|
||||
throw promise;
|
||||
}
|
||||
return <Text text={readText(text)} />;
|
||||
});
|
||||
|
||||
let setValue;
|
||||
|
@ -1308,17 +1134,15 @@ describe('ReactSuspense', () => {
|
|||
|
||||
const root = ReactTestRenderer.create(<App />);
|
||||
assertLog(['Suspend! [default]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
assertLog(['Promise resolved [default]']);
|
||||
await resolveText('default');
|
||||
await waitForPaint(['default']);
|
||||
expect(root).toMatchRenderedOutput('default');
|
||||
|
||||
act(() => setValue('new value'));
|
||||
await act(async () => setValue('new value'));
|
||||
assertLog(['Suspend! [new value]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
assertLog(['Promise resolved [new value]']);
|
||||
await resolveText('new value');
|
||||
await waitForPaint(['new value']);
|
||||
expect(root).toMatchRenderedOutput('new value');
|
||||
});
|
||||
|
@ -1331,18 +1155,7 @@ describe('ReactSuspense', () => {
|
|||
const MemoizedChild = memo(
|
||||
function MemoizedChild() {
|
||||
const text = useContext(ValueContext);
|
||||
try {
|
||||
TextResource.read([text, 1000]);
|
||||
Scheduler.log(text);
|
||||
return text;
|
||||
} catch (promise) {
|
||||
if (typeof promise.then === 'function') {
|
||||
Scheduler.log(`Suspend! [${text}]`);
|
||||
} else {
|
||||
Scheduler.log(`Error! [${text}]`);
|
||||
}
|
||||
throw promise;
|
||||
}
|
||||
return <Text text={readText(text)} />;
|
||||
},
|
||||
function areEqual(prevProps, nextProps) {
|
||||
return true;
|
||||
|
@ -1365,17 +1178,15 @@ describe('ReactSuspense', () => {
|
|||
|
||||
const root = ReactTestRenderer.create(<App />);
|
||||
assertLog(['Suspend! [default]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
assertLog(['Promise resolved [default]']);
|
||||
await resolveText('default');
|
||||
await waitForPaint(['default']);
|
||||
expect(root).toMatchRenderedOutput('default');
|
||||
|
||||
act(() => setValue('new value'));
|
||||
await act(async () => setValue('new value'));
|
||||
assertLog(['Suspend! [new value]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
assertLog(['Promise resolved [new value]']);
|
||||
await resolveText('new value');
|
||||
await waitForPaint(['new value']);
|
||||
expect(root).toMatchRenderedOutput('new value');
|
||||
});
|
||||
|
@ -1387,18 +1198,7 @@ describe('ReactSuspense', () => {
|
|||
|
||||
function MemoizedChild() {
|
||||
const text = useContext(ValueContext);
|
||||
try {
|
||||
TextResource.read([text, 1000]);
|
||||
Scheduler.log(text);
|
||||
return text;
|
||||
} catch (promise) {
|
||||
if (typeof promise.then === 'function') {
|
||||
Scheduler.log(`Suspend! [${text}]`);
|
||||
} else {
|
||||
Scheduler.log(`Error! [${text}]`);
|
||||
}
|
||||
throw promise;
|
||||
}
|
||||
return <Text text={readText(text)} />;
|
||||
}
|
||||
|
||||
let setValue;
|
||||
|
@ -1421,17 +1221,15 @@ describe('ReactSuspense', () => {
|
|||
</App>,
|
||||
);
|
||||
assertLog(['Suspend! [default]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
assertLog(['Promise resolved [default]']);
|
||||
await resolveText('default');
|
||||
await waitForPaint(['default']);
|
||||
expect(root).toMatchRenderedOutput('default');
|
||||
|
||||
act(() => setValue('new value'));
|
||||
await act(async () => setValue('new value'));
|
||||
assertLog(['Suspend! [new value]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
assertLog(['Promise resolved [new value]']);
|
||||
await resolveText('new value');
|
||||
await waitForPaint(['new value']);
|
||||
expect(root).toMatchRenderedOutput('new value');
|
||||
});
|
||||
|
@ -1443,18 +1241,7 @@ describe('ReactSuspense', () => {
|
|||
|
||||
const MemoizedChild = forwardRef(function MemoizedChild() {
|
||||
const text = useContext(ValueContext);
|
||||
try {
|
||||
TextResource.read([text, 1000]);
|
||||
Scheduler.log(text);
|
||||
return text;
|
||||
} catch (promise) {
|
||||
if (typeof promise.then === 'function') {
|
||||
Scheduler.log(`Suspend! [${text}]`);
|
||||
} else {
|
||||
Scheduler.log(`Error! [${text}]`);
|
||||
}
|
||||
throw promise;
|
||||
}
|
||||
return <Text text={readText(text)} />;
|
||||
});
|
||||
|
||||
let setValue;
|
||||
|
@ -1473,22 +1260,20 @@ describe('ReactSuspense', () => {
|
|||
|
||||
const root = ReactTestRenderer.create(<App />);
|
||||
assertLog(['Suspend! [default]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
assertLog(['Promise resolved [default]']);
|
||||
await resolveText('default');
|
||||
await waitForPaint(['default']);
|
||||
expect(root).toMatchRenderedOutput('default');
|
||||
|
||||
act(() => setValue('new value'));
|
||||
await act(async () => setValue('new value'));
|
||||
assertLog(['Suspend! [new value]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
assertLog(['Promise resolved [new value]']);
|
||||
await resolveText('new value');
|
||||
await waitForPaint(['new value']);
|
||||
expect(root).toMatchRenderedOutput('new value');
|
||||
});
|
||||
|
||||
it('updates context consumer within child of suspended suspense component when context updates', () => {
|
||||
it('updates context consumer within child of suspended suspense component when context updates', async () => {
|
||||
const {createContext, useState} = React;
|
||||
|
||||
const ValueContext = createContext(null);
|
||||
|
@ -1531,11 +1316,11 @@ describe('ReactSuspense', () => {
|
|||
assertLog(['Received context value [default]', 'default']);
|
||||
expect(root).toMatchRenderedOutput('default');
|
||||
|
||||
act(() => setValue('new value'));
|
||||
await act(async () => setValue('new value'));
|
||||
assertLog(['Received context value [new value]', 'Loading...']);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
|
||||
act(() => setValue('default'));
|
||||
await act(async () => setValue('default'));
|
||||
assertLog(['Received context value [default]', 'default']);
|
||||
expect(root).toMatchRenderedOutput('default');
|
||||
});
|
||||
|
|
|
@ -17,6 +17,7 @@ let caches;
|
|||
let seededCache;
|
||||
let ErrorBoundary;
|
||||
let waitForAll;
|
||||
let waitFor;
|
||||
let assertLog;
|
||||
|
||||
// TODO: These tests don't pass in persistent mode yet. Need to implement.
|
||||
|
@ -35,6 +36,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
waitForAll = InternalTestUtils.waitForAll;
|
||||
waitFor = InternalTestUtils.waitFor;
|
||||
assertLog = InternalTestUtils.assertLog;
|
||||
|
||||
caches = [];
|
||||
|
@ -372,7 +374,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
}
|
||||
|
||||
// Mount and suspend.
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.renderLegacySyncRoot(
|
||||
<App>
|
||||
<AsyncText text="Async" ms={1000} />
|
||||
|
@ -473,7 +475,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
}
|
||||
|
||||
// Mount
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.renderLegacySyncRoot(<App />);
|
||||
});
|
||||
assertLog([
|
||||
|
@ -499,7 +501,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
);
|
||||
|
||||
// Schedule an update that causes React to suspend.
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.renderLegacySyncRoot(
|
||||
<App>
|
||||
<AsyncText text="Async" ms={1000} />
|
||||
|
@ -629,46 +631,46 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
);
|
||||
|
||||
// Schedule an update that causes React to suspend.
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<App>
|
||||
<AsyncText text="Async" ms={1000} />
|
||||
</App>,
|
||||
);
|
||||
await waitFor([
|
||||
'App render',
|
||||
'Text:Inside:Before render',
|
||||
'Suspend:Async',
|
||||
'Text:Inside:After render',
|
||||
'Text:Fallback render',
|
||||
'Text:Outside render',
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Inside:Before" />
|
||||
<span prop="Inside:After" />
|
||||
<span prop="Outside" />
|
||||
</>,
|
||||
);
|
||||
|
||||
await jest.runAllTimers();
|
||||
|
||||
// Timing out should commit the fallback and destroy inner layout effects.
|
||||
assertLog([
|
||||
'Text:Inside:Before destroy layout',
|
||||
'Text:Inside:After destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
]);
|
||||
await waitForAll(['Text:Fallback create passive']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Inside:Before" hidden={true} />
|
||||
<span prop="Inside:After" hidden={true} />
|
||||
<span prop="Fallback" />
|
||||
<span prop="Outside" />
|
||||
</>,
|
||||
);
|
||||
});
|
||||
assertLog([
|
||||
'App render',
|
||||
'Text:Inside:Before render',
|
||||
'Suspend:Async',
|
||||
'Text:Inside:After render',
|
||||
'Text:Fallback render',
|
||||
'Text:Outside render',
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Inside:Before" />
|
||||
<span prop="Inside:After" />
|
||||
<span prop="Outside" />
|
||||
</>,
|
||||
);
|
||||
|
||||
await advanceTimers(1000);
|
||||
|
||||
// Timing out should commit the fallback and destroy inner layout effects.
|
||||
assertLog([
|
||||
'Text:Inside:Before destroy layout',
|
||||
'Text:Inside:After destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
]);
|
||||
await waitForAll(['Text:Fallback create passive']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Inside:Before" hidden={true} />
|
||||
<span prop="Inside:After" hidden={true} />
|
||||
<span prop="Fallback" />
|
||||
<span prop="Outside" />
|
||||
</>,
|
||||
);
|
||||
|
||||
// Resolving the suspended resource should re-create inner layout effects.
|
||||
await act(async () => {
|
||||
|
@ -783,46 +785,47 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
);
|
||||
|
||||
// Schedule an update that causes React to suspend.
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<App>
|
||||
<AsyncText text="Async" ms={1000} />
|
||||
</App>,
|
||||
);
|
||||
|
||||
await waitFor([
|
||||
'App render',
|
||||
'ClassText:Inside:Before render',
|
||||
'Suspend:Async',
|
||||
'ClassText:Inside:After render',
|
||||
'ClassText:Fallback render',
|
||||
'ClassText:Outside render',
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Inside:Before" />
|
||||
<span prop="Inside:After" />
|
||||
<span prop="Outside" />
|
||||
</>,
|
||||
);
|
||||
|
||||
await jest.runAllTimers();
|
||||
|
||||
// Timing out should commit the fallback and destroy inner layout effects.
|
||||
assertLog([
|
||||
'ClassText:Inside:Before componentWillUnmount',
|
||||
'ClassText:Inside:After componentWillUnmount',
|
||||
'ClassText:Fallback componentDidMount',
|
||||
'ClassText:Outside componentDidUpdate',
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Inside:Before" hidden={true} />
|
||||
<span prop="Inside:After" hidden={true} />
|
||||
<span prop="Fallback" />
|
||||
<span prop="Outside" />
|
||||
</>,
|
||||
);
|
||||
});
|
||||
assertLog([
|
||||
'App render',
|
||||
'ClassText:Inside:Before render',
|
||||
'Suspend:Async',
|
||||
'ClassText:Inside:After render',
|
||||
'ClassText:Fallback render',
|
||||
'ClassText:Outside render',
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Inside:Before" />
|
||||
<span prop="Inside:After" />
|
||||
<span prop="Outside" />
|
||||
</>,
|
||||
);
|
||||
|
||||
await advanceTimers(1000);
|
||||
|
||||
// Timing out should commit the fallback and destroy inner layout effects.
|
||||
assertLog([
|
||||
'ClassText:Inside:Before componentWillUnmount',
|
||||
'ClassText:Inside:After componentWillUnmount',
|
||||
'ClassText:Fallback componentDidMount',
|
||||
'ClassText:Outside componentDidUpdate',
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Inside:Before" hidden={true} />
|
||||
<span prop="Inside:After" hidden={true} />
|
||||
<span prop="Fallback" />
|
||||
<span prop="Outside" />
|
||||
</>,
|
||||
);
|
||||
|
||||
// Resolving the suspended resource should re-create inner layout effects.
|
||||
await act(async () => {
|
||||
|
@ -908,43 +911,43 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
);
|
||||
|
||||
// Schedule an update that causes React to suspend.
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<App>
|
||||
<AsyncText text="Async" ms={1000} />
|
||||
</App>,
|
||||
);
|
||||
});
|
||||
assertLog([
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'Text:Outer render',
|
||||
'Text:Inner render',
|
||||
'Text:Fallback render',
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<span prop="Outer">
|
||||
<span prop="Inner" />
|
||||
</span>,
|
||||
);
|
||||
|
||||
await advanceTimers(1000);
|
||||
|
||||
// Timing out should commit the fallback and destroy inner layout effects.
|
||||
assertLog([
|
||||
'Text:Outer destroy layout',
|
||||
'Text:Inner destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
]);
|
||||
await waitForAll(['Text:Fallback create passive']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span hidden={true} prop="Outer">
|
||||
await waitFor([
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'Text:Outer render',
|
||||
'Text:Inner render',
|
||||
'Text:Fallback render',
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<span prop="Outer">
|
||||
<span prop="Inner" />
|
||||
</span>
|
||||
<span prop="Fallback" />
|
||||
</>,
|
||||
);
|
||||
</span>,
|
||||
);
|
||||
|
||||
await jest.runAllTimers();
|
||||
|
||||
// Timing out should commit the fallback and destroy inner layout effects.
|
||||
assertLog([
|
||||
'Text:Outer destroy layout',
|
||||
'Text:Inner destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
]);
|
||||
await waitForAll(['Text:Fallback create passive']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span hidden={true} prop="Outer">
|
||||
<span prop="Inner" />
|
||||
</span>
|
||||
<span prop="Fallback" />
|
||||
</>,
|
||||
);
|
||||
});
|
||||
|
||||
// Resolving the suspended resource should re-create inner layout effects.
|
||||
await act(async () => {
|
||||
|
@ -1035,44 +1038,44 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
);
|
||||
|
||||
// Schedule an update that causes React to suspend.
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<App>
|
||||
<AsyncText text="Async" ms={1000} />
|
||||
</App>,
|
||||
);
|
||||
});
|
||||
assertLog([
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'Text:Outer render',
|
||||
// Text:MemoizedInner is memoized
|
||||
'Text:Fallback render',
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<span prop="Outer">
|
||||
<span prop="MemoizedInner" />
|
||||
</span>,
|
||||
);
|
||||
|
||||
await advanceTimers(1000);
|
||||
|
||||
// Timing out should commit the fallback and destroy inner layout effects.
|
||||
// Even though the innermost layout effects are beneath a hidden HostComponent.
|
||||
assertLog([
|
||||
'Text:Outer destroy layout',
|
||||
'Text:MemoizedInner destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
]);
|
||||
await waitForAll(['Text:Fallback create passive']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span hidden={true} prop="Outer">
|
||||
await waitFor([
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'Text:Outer render',
|
||||
// Text:MemoizedInner is memoized
|
||||
'Text:Fallback render',
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<span prop="Outer">
|
||||
<span prop="MemoizedInner" />
|
||||
</span>
|
||||
<span prop="Fallback" />
|
||||
</>,
|
||||
);
|
||||
</span>,
|
||||
);
|
||||
|
||||
await jest.runAllTimers();
|
||||
|
||||
// Timing out should commit the fallback and destroy inner layout effects.
|
||||
// Even though the innermost layout effects are beneath a hidden HostComponent.
|
||||
assertLog([
|
||||
'Text:Outer destroy layout',
|
||||
'Text:MemoizedInner destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
]);
|
||||
await waitForAll(['Text:Fallback create passive']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span hidden={true} prop="Outer">
|
||||
<span prop="MemoizedInner" />
|
||||
</span>
|
||||
<span prop="Fallback" />
|
||||
</>,
|
||||
);
|
||||
});
|
||||
|
||||
// Resolving the suspended resource should re-create inner layout effects.
|
||||
await act(async () => {
|
||||
|
@ -1147,12 +1150,11 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
);
|
||||
|
||||
// Suspend the inner Suspense subtree (only inner effects should be destroyed)
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<App innerChildren={<AsyncText text="InnerAsync_1" ms={1000} />} />,
|
||||
);
|
||||
});
|
||||
await advanceTimers(1000);
|
||||
assertLog([
|
||||
'Text:Outer render',
|
||||
'Text:Inner render',
|
||||
|
@ -1160,8 +1162,8 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
'Text:InnerFallback render',
|
||||
'Text:Inner destroy layout',
|
||||
'Text:InnerFallback create layout',
|
||||
'Text:InnerFallback create passive',
|
||||
]);
|
||||
await waitForAll(['Text:InnerFallback create passive']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Outer" />
|
||||
|
@ -1172,7 +1174,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
|
||||
// Suspend the outer Suspense subtree (outer effects and inner fallback effects should be destroyed)
|
||||
// (This check also ensures we don't destroy effects for mounted inner fallback.)
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<App
|
||||
outerChildren={<AsyncText text="OuterAsync_1" ms={1000} />}
|
||||
|
@ -1191,8 +1193,8 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
'Text:Outer destroy layout',
|
||||
'Text:InnerFallback destroy layout',
|
||||
'Text:OuterFallback create layout',
|
||||
'Text:OuterFallback create passive',
|
||||
]);
|
||||
await waitForAll(['Text:OuterFallback create passive']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Outer" hidden={true} />
|
||||
|
@ -1222,7 +1224,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
);
|
||||
|
||||
// Suspend the inner Suspense subtree (no effects should be destroyed)
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<App
|
||||
outerChildren={<AsyncText text="OuterAsync_1" ms={1000} />}
|
||||
|
@ -1297,7 +1299,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
);
|
||||
|
||||
// Suspend the outer Suspense subtree (all effects should be destroyed)
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<App
|
||||
outerChildren={<AsyncText text="OuterAsync_2" ms={1000} />}
|
||||
|
@ -1305,7 +1307,6 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
/>,
|
||||
);
|
||||
});
|
||||
await advanceTimers(1000);
|
||||
assertLog([
|
||||
'Text:Outer render',
|
||||
'Suspend:OuterAsync_2',
|
||||
|
@ -1317,6 +1318,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
'Text:Inner destroy layout',
|
||||
'AsyncText:InnerAsync_2 destroy layout',
|
||||
'Text:OuterFallback create layout',
|
||||
'Text:OuterFallback create passive',
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
|
@ -1333,7 +1335,6 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
await resolveText('OuterAsync_2');
|
||||
});
|
||||
assertLog([
|
||||
'Text:OuterFallback create passive',
|
||||
'Text:Outer render',
|
||||
'AsyncText:OuterAsync_2 render',
|
||||
'Text:Inner render',
|
||||
|
@ -1390,12 +1391,11 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
);
|
||||
|
||||
// Suspend the inner Suspense subtree (only inner effects should be destroyed)
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<App innerChildren={<AsyncText text="InnerAsync_1" ms={1000} />} />,
|
||||
);
|
||||
});
|
||||
await advanceTimers(1000);
|
||||
assertLog([
|
||||
'Text:Outer render',
|
||||
'Text:Inner render',
|
||||
|
@ -1403,8 +1403,8 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
'Text:InnerFallback render',
|
||||
'Text:Inner destroy layout',
|
||||
'Text:InnerFallback create layout',
|
||||
'Text:InnerFallback create passive',
|
||||
]);
|
||||
await waitForAll(['Text:InnerFallback create passive']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Outer" />
|
||||
|
@ -1415,7 +1415,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
|
||||
// Suspend the outer Suspense subtree (outer effects and inner fallback effects should be destroyed)
|
||||
// (This check also ensures we don't destroy effects for mounted inner fallback.)
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<App
|
||||
outerChildren={<AsyncText text="OuterAsync_1" ms={1000} />}
|
||||
|
@ -1423,7 +1423,6 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
/>,
|
||||
);
|
||||
});
|
||||
await advanceTimers(1000);
|
||||
assertLog([
|
||||
'Text:Outer render',
|
||||
'Suspend:OuterAsync_1',
|
||||
|
@ -1434,8 +1433,8 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
'Text:Outer destroy layout',
|
||||
'Text:InnerFallback destroy layout',
|
||||
'Text:OuterFallback create layout',
|
||||
'Text:OuterFallback create passive',
|
||||
]);
|
||||
await waitForAll(['Text:OuterFallback create passive']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Outer" hidden={true} />
|
||||
|
@ -1518,88 +1517,88 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
);
|
||||
|
||||
// Suspend the outer shell
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<App outerChildren={<AsyncText text="OutsideAsync" ms={1000} />} />,
|
||||
);
|
||||
});
|
||||
assertLog([
|
||||
'Text:Inside render',
|
||||
'Suspend:OutsideAsync',
|
||||
'Text:Fallback:Inside render',
|
||||
'Text:Fallback:Outside render',
|
||||
'Text:Outside render',
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Inside" />
|
||||
<span prop="Outside" />
|
||||
</>,
|
||||
);
|
||||
await waitFor([
|
||||
'Text:Inside render',
|
||||
'Suspend:OutsideAsync',
|
||||
'Text:Fallback:Inside render',
|
||||
'Text:Fallback:Outside render',
|
||||
'Text:Outside render',
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Inside" />
|
||||
<span prop="Outside" />
|
||||
</>,
|
||||
);
|
||||
|
||||
// Timing out should commit the fallback and destroy inner layout effects.
|
||||
await advanceTimers(1000);
|
||||
assertLog([
|
||||
'Text:Inside destroy layout',
|
||||
'Text:Fallback:Inside create layout',
|
||||
'Text:Fallback:Outside create layout',
|
||||
]);
|
||||
await waitForAll([
|
||||
'Text:Fallback:Inside create passive',
|
||||
'Text:Fallback:Outside create passive',
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Inside" hidden={true} />
|
||||
<span prop="Fallback:Inside" />
|
||||
<span prop="Fallback:Outside" />
|
||||
<span prop="Outside" />
|
||||
</>,
|
||||
);
|
||||
// Timing out should commit the fallback and destroy inner layout effects.
|
||||
await jest.runAllTimers();
|
||||
assertLog([
|
||||
'Text:Inside destroy layout',
|
||||
'Text:Fallback:Inside create layout',
|
||||
'Text:Fallback:Outside create layout',
|
||||
]);
|
||||
await waitForAll([
|
||||
'Text:Fallback:Inside create passive',
|
||||
'Text:Fallback:Outside create passive',
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Inside" hidden={true} />
|
||||
<span prop="Fallback:Inside" />
|
||||
<span prop="Fallback:Outside" />
|
||||
<span prop="Outside" />
|
||||
</>,
|
||||
);
|
||||
});
|
||||
|
||||
// Suspend the fallback and verify that it's effects get cleaned up as well
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<App
|
||||
fallbackChildren={<AsyncText text="FallbackAsync" ms={1000} />}
|
||||
outerChildren={<AsyncText text="OutsideAsync" ms={1000} />}
|
||||
/>,
|
||||
);
|
||||
});
|
||||
assertLog([
|
||||
'Text:Inside render',
|
||||
'Suspend:OutsideAsync',
|
||||
'Text:Fallback:Inside render',
|
||||
'Suspend:FallbackAsync',
|
||||
'Text:Fallback:Fallback render',
|
||||
'Text:Fallback:Outside render',
|
||||
'Text:Outside render',
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Inside" hidden={true} />
|
||||
<span prop="Fallback:Inside" />
|
||||
<span prop="Fallback:Outside" />
|
||||
<span prop="Outside" />
|
||||
</>,
|
||||
);
|
||||
await waitFor([
|
||||
'Text:Inside render',
|
||||
'Suspend:OutsideAsync',
|
||||
'Text:Fallback:Inside render',
|
||||
'Suspend:FallbackAsync',
|
||||
'Text:Fallback:Fallback render',
|
||||
'Text:Fallback:Outside render',
|
||||
'Text:Outside render',
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Inside" hidden={true} />
|
||||
<span prop="Fallback:Inside" />
|
||||
<span prop="Fallback:Outside" />
|
||||
<span prop="Outside" />
|
||||
</>,
|
||||
);
|
||||
|
||||
// Timing out should commit the inner fallback and destroy outer fallback layout effects.
|
||||
await advanceTimers(1000);
|
||||
assertLog([
|
||||
'Text:Fallback:Inside destroy layout',
|
||||
'Text:Fallback:Fallback create layout',
|
||||
]);
|
||||
await waitForAll(['Text:Fallback:Fallback create passive']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Inside" hidden={true} />
|
||||
<span prop="Fallback:Inside" hidden={true} />
|
||||
<span prop="Fallback:Fallback" />
|
||||
<span prop="Fallback:Outside" />
|
||||
<span prop="Outside" />
|
||||
</>,
|
||||
);
|
||||
// Timing out should commit the inner fallback and destroy outer fallback layout effects.
|
||||
await jest.runAllTimers();
|
||||
assertLog([
|
||||
'Text:Fallback:Inside destroy layout',
|
||||
'Text:Fallback:Fallback create layout',
|
||||
]);
|
||||
await waitForAll(['Text:Fallback:Fallback create passive']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Inside" hidden={true} />
|
||||
<span prop="Fallback:Inside" hidden={true} />
|
||||
<span prop="Fallback:Fallback" />
|
||||
<span prop="Fallback:Outside" />
|
||||
<span prop="Outside" />
|
||||
</>,
|
||||
);
|
||||
});
|
||||
|
||||
// Resolving both resources should cleanup fallback effects and recreate main effects
|
||||
await act(async () => {
|
||||
|
@ -1670,7 +1669,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
);
|
||||
|
||||
// Suspend both the outer boundary and the fallback
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<App
|
||||
outerChildren={<AsyncText text="OutsideAsync" ms={1000} />}
|
||||
|
@ -1678,7 +1677,6 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
/>,
|
||||
);
|
||||
});
|
||||
await advanceTimers(1000);
|
||||
assertLog([
|
||||
'Text:Inside render',
|
||||
'Suspend:OutsideAsync',
|
||||
|
@ -1690,8 +1688,6 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
'Text:Inside destroy layout',
|
||||
'Text:Fallback:Fallback create layout',
|
||||
'Text:Fallback:Outside create layout',
|
||||
]);
|
||||
await waitForAll([
|
||||
'Text:Fallback:Fallback create passive',
|
||||
'Text:Fallback:Outside create passive',
|
||||
]);
|
||||
|
@ -1795,32 +1791,35 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
|
||||
// Suspending a component in the middle of the tree
|
||||
// should still properly cleanup effects deeper in the tree
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(<App shouldSuspend={true} />);
|
||||
});
|
||||
assertLog([
|
||||
'Suspend:Suspend',
|
||||
'Text:Fallback render',
|
||||
'Text:Outside render',
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Inside" />
|
||||
<span prop="Outside" />
|
||||
</>,
|
||||
);
|
||||
await waitFor([
|
||||
'Suspend:Suspend',
|
||||
'Text:Fallback render',
|
||||
'Text:Outside render',
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Inside" />
|
||||
<span prop="Outside" />
|
||||
</>,
|
||||
);
|
||||
|
||||
// Timing out should commit the inner fallback and destroy outer fallback layout effects.
|
||||
await advanceTimers(1000);
|
||||
assertLog(['Text:Inside destroy layout', 'Text:Fallback create layout']);
|
||||
await waitForAll(['Text:Fallback create passive']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Inside" hidden={true} />
|
||||
<span prop="Fallback" />
|
||||
<span prop="Outside" />
|
||||
</>,
|
||||
);
|
||||
// Timing out should commit the inner fallback and destroy outer fallback layout effects.
|
||||
await jest.runAllTimers();
|
||||
assertLog([
|
||||
'Text:Inside destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
]);
|
||||
await waitForAll(['Text:Fallback create passive']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Inside" hidden={true} />
|
||||
<span prop="Fallback" />
|
||||
<span prop="Outside" />
|
||||
</>,
|
||||
);
|
||||
});
|
||||
|
||||
// Resolving should cleanup.
|
||||
await act(async () => {
|
||||
|
@ -2390,43 +2389,43 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
);
|
||||
|
||||
// Schedule an update that causes React to suspend.
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<App>
|
||||
<AsyncText text="Async_1" ms={1000} />
|
||||
<AsyncText text="Async_2" ms={2000} />
|
||||
</App>,
|
||||
);
|
||||
await waitFor([
|
||||
'Text:Function render',
|
||||
'Suspend:Async_1',
|
||||
'Suspend:Async_2',
|
||||
'ClassText:Class render',
|
||||
'ClassText:Fallback render',
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Function" />
|
||||
<span prop="Class" />
|
||||
</>,
|
||||
);
|
||||
|
||||
await jest.runAllTimers();
|
||||
|
||||
// Timing out should commit the fallback and destroy inner layout effects.
|
||||
assertLog([
|
||||
'Text:Function destroy layout',
|
||||
'ClassText:Class componentWillUnmount',
|
||||
'ClassText:Fallback componentDidMount',
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Function" hidden={true} />
|
||||
<span prop="Class" hidden={true} />
|
||||
<span prop="Fallback" />
|
||||
</>,
|
||||
);
|
||||
});
|
||||
assertLog([
|
||||
'Text:Function render',
|
||||
'Suspend:Async_1',
|
||||
'Suspend:Async_2',
|
||||
'ClassText:Class render',
|
||||
'ClassText:Fallback render',
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Function" />
|
||||
<span prop="Class" />
|
||||
</>,
|
||||
);
|
||||
|
||||
await advanceTimers(1000);
|
||||
|
||||
// Timing out should commit the fallback and destroy inner layout effects.
|
||||
assertLog([
|
||||
'Text:Function destroy layout',
|
||||
'ClassText:Class componentWillUnmount',
|
||||
'ClassText:Fallback componentDidMount',
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Function" hidden={true} />
|
||||
<span prop="Class" hidden={true} />
|
||||
<span prop="Fallback" />
|
||||
</>,
|
||||
);
|
||||
|
||||
// Resolving the suspended resource should re-create inner layout effects.
|
||||
await act(async () => {
|
||||
|
@ -2549,40 +2548,40 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
|
||||
// Schedule an update that causes React to suspend.
|
||||
textToRead = 'A';
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(<App />);
|
||||
await waitFor([
|
||||
'Text:Function render',
|
||||
'Suspender "A" render',
|
||||
'Suspend:A',
|
||||
'ClassText:Class render',
|
||||
'ClassText:Fallback render',
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Function" />
|
||||
<span prop="Suspender" />
|
||||
<span prop="Class" />
|
||||
</>,
|
||||
);
|
||||
|
||||
await jest.runAllTimers();
|
||||
|
||||
// Timing out should commit the fallback and destroy inner layout effects.
|
||||
assertLog([
|
||||
'Text:Function destroy layout',
|
||||
'ClassText:Class componentWillUnmount',
|
||||
'ClassText:Fallback componentDidMount',
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Function" hidden={true} />
|
||||
<span prop="Suspender" hidden={true} />
|
||||
<span prop="Class" hidden={true} />
|
||||
<span prop="Fallback" />
|
||||
</>,
|
||||
);
|
||||
});
|
||||
assertLog([
|
||||
'Text:Function render',
|
||||
'Suspender "A" render',
|
||||
'Suspend:A',
|
||||
'ClassText:Class render',
|
||||
'ClassText:Fallback render',
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Function" />
|
||||
<span prop="Suspender" />
|
||||
<span prop="Class" />
|
||||
</>,
|
||||
);
|
||||
|
||||
await advanceTimers(1000);
|
||||
|
||||
// Timing out should commit the fallback and destroy inner layout effects.
|
||||
assertLog([
|
||||
'Text:Function destroy layout',
|
||||
'ClassText:Class componentWillUnmount',
|
||||
'ClassText:Fallback componentDidMount',
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Function" hidden={true} />
|
||||
<span prop="Suspender" hidden={true} />
|
||||
<span prop="Class" hidden={true} />
|
||||
<span prop="Fallback" />
|
||||
</>,
|
||||
);
|
||||
|
||||
// Resolving the suspended resource should re-create inner layout effects.
|
||||
textToRead = 'B';
|
||||
|
@ -2712,7 +2711,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
);
|
||||
}
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.renderLegacySyncRoot(<App />);
|
||||
});
|
||||
assertLog([
|
||||
|
@ -2730,7 +2729,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
expect(ReactNoop).toMatchRenderedOutput(null);
|
||||
|
||||
// Suspend the inner Suspense subtree (only inner effects should be destroyed)
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.renderLegacySyncRoot(
|
||||
<App children={<AsyncText text="Async" ms={1000} />} />,
|
||||
);
|
||||
|
@ -2811,7 +2810,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
);
|
||||
|
||||
// Suspend the inner Suspense subtree (only inner effects should be destroyed)
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<App children={<AsyncText text="Async" ms={1000} />} />,
|
||||
);
|
||||
|
@ -2829,6 +2828,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
'RefCheckerOuter refCallback value? false',
|
||||
'RefCheckerInner:refCallback destroy layout ref? false',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
|
@ -2843,7 +2843,6 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
await resolveText('Async');
|
||||
});
|
||||
assertLog([
|
||||
'Text:Fallback create passive',
|
||||
'AsyncText:Async render',
|
||||
'RefCheckerOuter render',
|
||||
'RefCheckerInner:refObject render',
|
||||
|
@ -2917,7 +2916,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
expect(ReactNoop).toMatchRenderedOutput(null);
|
||||
|
||||
// Suspend the inner Suspense subtree (only inner effects should be destroyed)
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<App children={<AsyncText text="Async" ms={1000} />} />,
|
||||
);
|
||||
|
@ -2937,6 +2936,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
'RefCheckerOuter refCallback value? false',
|
||||
'RefCheckerInner:refCallback destroy layout ref? false',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Fallback" />);
|
||||
|
||||
|
@ -2945,7 +2945,6 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
await resolveText('Async');
|
||||
});
|
||||
assertLog([
|
||||
'Text:Fallback create passive',
|
||||
'AsyncText:Async render',
|
||||
'RefCheckerOuter render',
|
||||
'ClassComponent:refObject render',
|
||||
|
@ -3021,7 +3020,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
expect(ReactNoop).toMatchRenderedOutput(null);
|
||||
|
||||
// Suspend the inner Suspense subtree (only inner effects should be destroyed)
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<App children={<AsyncText text="Async" ms={1000} />} />,
|
||||
);
|
||||
|
@ -3041,6 +3040,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
'RefCheckerOuter refCallback value? false',
|
||||
'RefCheckerInner:refCallback destroy layout ref? false',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Fallback" />);
|
||||
|
||||
|
@ -3049,7 +3049,6 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
await resolveText('Async');
|
||||
});
|
||||
assertLog([
|
||||
'Text:Fallback create passive',
|
||||
'AsyncText:Async render',
|
||||
'RefCheckerOuter render',
|
||||
'FunctionComponent render',
|
||||
|
@ -3130,7 +3129,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
expect(ReactNoop).toMatchRenderedOutput(null);
|
||||
|
||||
// Suspend the inner Suspense subtree (only inner effects should be destroyed)
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<App children={<AsyncText text="Async" ms={1000} />} />,
|
||||
);
|
||||
|
@ -3143,6 +3142,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
'Text:Fallback render',
|
||||
'RefChecker destroy layout ref? true',
|
||||
'Text:Fallback create layout',
|
||||
'Text:Fallback create passive',
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Fallback" />);
|
||||
|
||||
|
@ -3151,7 +3151,6 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
|||
await resolveText('Async');
|
||||
});
|
||||
assertLog([
|
||||
'Text:Fallback create passive',
|
||||
'AsyncText:Async render',
|
||||
'RefChecker render',
|
||||
'Text:Fallback destroy layout',
|
||||
|
|
|
@ -17,6 +17,7 @@ let act;
|
|||
let container;
|
||||
let waitForAll;
|
||||
let assertLog;
|
||||
let fakeModuleCache;
|
||||
|
||||
describe('ReactSuspenseEffectsSemanticsDOM', () => {
|
||||
beforeEach(() => {
|
||||
|
@ -34,14 +35,53 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
|
|||
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
|
||||
fakeModuleCache = new Map();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.body.removeChild(container);
|
||||
});
|
||||
|
||||
async function fakeImport(result) {
|
||||
return {default: result};
|
||||
async function fakeImport(Component) {
|
||||
const record = fakeModuleCache.get(Component);
|
||||
if (record === undefined) {
|
||||
const newRecord = {
|
||||
status: 'pending',
|
||||
value: {default: Component},
|
||||
pings: [],
|
||||
then(ping) {
|
||||
switch (newRecord.status) {
|
||||
case 'pending': {
|
||||
newRecord.pings.push(ping);
|
||||
return;
|
||||
}
|
||||
case 'resolved': {
|
||||
ping(newRecord.value);
|
||||
return;
|
||||
}
|
||||
case 'rejected': {
|
||||
throw newRecord.value;
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
fakeModuleCache.set(Component, newRecord);
|
||||
return newRecord;
|
||||
}
|
||||
return record;
|
||||
}
|
||||
|
||||
function resolveFakeImport(moduleName) {
|
||||
const record = fakeModuleCache.get(moduleName);
|
||||
if (record === undefined) {
|
||||
throw new Error('Module not found');
|
||||
}
|
||||
if (record.status !== 'pending') {
|
||||
throw new Error('Module already resolved');
|
||||
}
|
||||
record.status = 'resolved';
|
||||
record.pings.forEach(ping => ping(record.value));
|
||||
}
|
||||
|
||||
function Text(props) {
|
||||
|
@ -49,7 +89,7 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
|
|||
return props.text;
|
||||
}
|
||||
|
||||
it('should not cause a cycle when combined with a render phase update', () => {
|
||||
it('should not cause a cycle when combined with a render phase update', async () => {
|
||||
let scheduleSuspendingUpdate;
|
||||
|
||||
function App() {
|
||||
|
@ -79,22 +119,22 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
|
|||
return <div ref={setRef} />;
|
||||
}
|
||||
|
||||
const promise = Promise.resolve();
|
||||
const neverResolves = {then() {}};
|
||||
|
||||
function ComponentThatSuspendsOnUpdate({shouldSuspend}) {
|
||||
if (shouldSuspend) {
|
||||
// Fake Suspend
|
||||
throw promise;
|
||||
throw neverResolves;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
root.render(<App />);
|
||||
});
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
scheduleSuspendingUpdate();
|
||||
});
|
||||
});
|
||||
|
@ -142,12 +182,12 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
|
|||
}
|
||||
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
root.render(<Parent swap={false} />);
|
||||
});
|
||||
assertLog(['Loading...']);
|
||||
|
||||
await LazyChildA;
|
||||
await resolveFakeImport(ChildA);
|
||||
await waitForAll(['A', 'Ref mount: A']);
|
||||
expect(container.innerHTML).toBe('<span>A</span>');
|
||||
|
||||
|
@ -160,7 +200,7 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
|
|||
'<span style="display: none;">A</span>Loading...',
|
||||
);
|
||||
|
||||
await LazyChildB;
|
||||
await resolveFakeImport(ChildB);
|
||||
await waitForAll(['B', 'Ref mount: B']);
|
||||
expect(container.innerHTML).toBe('<span>B</span>');
|
||||
});
|
||||
|
@ -202,12 +242,12 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
|
|||
}
|
||||
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
root.render(<Parent swap={false} />);
|
||||
});
|
||||
assertLog(['Loading...']);
|
||||
|
||||
await LazyChildA;
|
||||
await resolveFakeImport(ChildA);
|
||||
await waitForAll(['A', 'Did mount: A']);
|
||||
expect(container.innerHTML).toBe('A');
|
||||
|
||||
|
@ -218,7 +258,7 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
|
|||
assertLog(['Loading...', 'Will unmount: A']);
|
||||
expect(container.innerHTML).toBe('Loading...');
|
||||
|
||||
await LazyChildB;
|
||||
await resolveFakeImport(ChildB);
|
||||
await waitForAll(['B', 'Did mount: B']);
|
||||
expect(container.innerHTML).toBe('B');
|
||||
});
|
||||
|
@ -254,12 +294,12 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
|
|||
}
|
||||
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
root.render(<Parent swap={false} />);
|
||||
});
|
||||
assertLog(['Loading...']);
|
||||
|
||||
await LazyChildA;
|
||||
await resolveFakeImport(ChildA);
|
||||
await waitForAll(['A', 'Did mount: A']);
|
||||
expect(container.innerHTML).toBe('A');
|
||||
|
||||
|
@ -321,12 +361,12 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
|
|||
}
|
||||
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
root.render(<Parent swap={false} />);
|
||||
});
|
||||
assertLog(['Loading...']);
|
||||
|
||||
await LazyChildA;
|
||||
await resolveFakeImport(ChildA);
|
||||
await waitForAll(['A', 'Ref mount: A']);
|
||||
expect(container.innerHTML).toBe('<span>A</span>');
|
||||
|
||||
|
@ -384,12 +424,12 @@ describe('ReactSuspenseEffectsSemanticsDOM', () => {
|
|||
}
|
||||
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
root.render(<Parent swap={false} />);
|
||||
});
|
||||
assertLog(['Loading...']);
|
||||
|
||||
await LazyChildA;
|
||||
await resolveFakeImport(ChildA);
|
||||
await waitForAll(['A', 'Did mount: A']);
|
||||
expect(container.innerHTML).toBe('A');
|
||||
|
||||
|
|
|
@ -138,14 +138,14 @@ describe('ReactSuspenseFuzz', () => {
|
|||
return resolvedText;
|
||||
}
|
||||
|
||||
function resolveAllTasks() {
|
||||
async function resolveAllTasks() {
|
||||
Scheduler.unstable_flushAllWithoutAsserting();
|
||||
let elapsedTime = 0;
|
||||
while (pendingTasks && pendingTasks.size > 0) {
|
||||
if ((elapsedTime += 1000) > 1000000) {
|
||||
throw new Error('Something did not resolve properly.');
|
||||
}
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.batchedUpdates(() => {
|
||||
jest.advanceTimersByTime(1000);
|
||||
});
|
||||
|
@ -154,7 +154,7 @@ describe('ReactSuspenseFuzz', () => {
|
|||
}
|
||||
}
|
||||
|
||||
function testResolvedOutput(unwrappedChildren) {
|
||||
async function testResolvedOutput(unwrappedChildren) {
|
||||
const children = (
|
||||
<Suspense fallback="Loading...">{unwrappedChildren}</Suspense>
|
||||
);
|
||||
|
@ -166,17 +166,15 @@ describe('ReactSuspenseFuzz', () => {
|
|||
{children}
|
||||
</ShouldSuspendContext.Provider>,
|
||||
);
|
||||
resolveAllTasks();
|
||||
await resolveAllTasks();
|
||||
const expectedOutput = expectedRoot.getChildrenAsJSX();
|
||||
|
||||
gate(flags => {
|
||||
resetCache();
|
||||
ReactNoop.renderLegacySyncRoot(children);
|
||||
resolveAllTasks();
|
||||
const legacyOutput = ReactNoop.getChildrenAsJSX();
|
||||
expect(legacyOutput).toEqual(expectedOutput);
|
||||
ReactNoop.renderLegacySyncRoot(null);
|
||||
});
|
||||
resetCache();
|
||||
ReactNoop.renderLegacySyncRoot(children);
|
||||
await resolveAllTasks();
|
||||
const legacyOutput = ReactNoop.getChildrenAsJSX();
|
||||
expect(legacyOutput).toEqual(expectedOutput);
|
||||
ReactNoop.renderLegacySyncRoot(null);
|
||||
}
|
||||
|
||||
function pickRandomWeighted(rand, options) {
|
||||
|
@ -298,10 +296,10 @@ describe('ReactSuspenseFuzz', () => {
|
|||
return {Container, Text, testResolvedOutput, generateTestCase};
|
||||
}
|
||||
|
||||
it('basic cases', () => {
|
||||
it('basic cases', async () => {
|
||||
// This demonstrates that the testing primitives work
|
||||
const {Container, Text, testResolvedOutput} = createFuzzer();
|
||||
testResolvedOutput(
|
||||
await testResolvedOutput(
|
||||
<Container updates={[{remountAfter: 150}]}>
|
||||
<Text
|
||||
text="Hi"
|
||||
|
@ -312,7 +310,7 @@ describe('ReactSuspenseFuzz', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it(`generative tests (random seed: ${SEED})`, () => {
|
||||
it(`generative tests (random seed: ${SEED})`, async () => {
|
||||
const {generateTestCase, testResolvedOutput} = createFuzzer();
|
||||
|
||||
const rand = Random.create(SEED);
|
||||
|
@ -323,7 +321,7 @@ describe('ReactSuspenseFuzz', () => {
|
|||
for (let i = 0; i < NUMBER_OF_TEST_CASES; i++) {
|
||||
const randomTestCase = generateTestCase(rand, ELEMENTS_PER_CASE);
|
||||
try {
|
||||
testResolvedOutput(randomTestCase);
|
||||
await testResolvedOutput(randomTestCase);
|
||||
} catch (e) {
|
||||
console.log(`
|
||||
Failed fuzzy test case:
|
||||
|
@ -339,9 +337,9 @@ Random seed is ${SEED}
|
|||
});
|
||||
|
||||
describe('hard-coded cases', () => {
|
||||
it('1', () => {
|
||||
it('1', async () => {
|
||||
const {Text, testResolvedOutput} = createFuzzer();
|
||||
testResolvedOutput(
|
||||
await testResolvedOutput(
|
||||
<>
|
||||
<Text
|
||||
initialDelay={20}
|
||||
|
@ -360,9 +358,9 @@ Random seed is ${SEED}
|
|||
);
|
||||
});
|
||||
|
||||
it('2', () => {
|
||||
it('2', async () => {
|
||||
const {Text, Container, testResolvedOutput} = createFuzzer();
|
||||
testResolvedOutput(
|
||||
await testResolvedOutput(
|
||||
<>
|
||||
<Suspense fallback="Loading...">
|
||||
<Text initialDelay={7200} text="A" />
|
||||
|
@ -378,9 +376,9 @@ Random seed is ${SEED}
|
|||
);
|
||||
});
|
||||
|
||||
it('3', () => {
|
||||
it('3', async () => {
|
||||
const {Text, Container, testResolvedOutput} = createFuzzer();
|
||||
testResolvedOutput(
|
||||
await testResolvedOutput(
|
||||
<>
|
||||
<Suspense fallback="Loading...">
|
||||
<Text
|
||||
|
@ -412,9 +410,9 @@ Random seed is ${SEED}
|
|||
);
|
||||
});
|
||||
|
||||
it('4', () => {
|
||||
it('4', async () => {
|
||||
const {Text, testResolvedOutput} = createFuzzer();
|
||||
testResolvedOutput(
|
||||
await testResolvedOutput(
|
||||
<React.Suspense fallback="Loading...">
|
||||
<React.Suspense>
|
||||
<React.Suspense>
|
||||
|
|
|
@ -2275,7 +2275,7 @@ describe('ReactSuspenseList', () => {
|
|||
);
|
||||
|
||||
// Update the row adjacent to the list
|
||||
act(() => updateAdjacent('C'));
|
||||
await act(async () => updateAdjacent('C'));
|
||||
|
||||
assertLog(['C']);
|
||||
|
||||
|
@ -2332,7 +2332,7 @@ describe('ReactSuspenseList', () => {
|
|||
const previousInst = setAsyncB;
|
||||
|
||||
// During an update we suspend on B.
|
||||
act(() => setAsyncB(true));
|
||||
await act(async () => setAsyncB(true));
|
||||
|
||||
assertLog([
|
||||
'Suspend! [B]',
|
||||
|
@ -2350,7 +2350,7 @@ describe('ReactSuspenseList', () => {
|
|||
|
||||
// Before we resolve we'll rerender the whole list.
|
||||
// This should leave the tree intact.
|
||||
act(() => ReactNoop.render(<Foo updateList={true} />));
|
||||
await act(async () => ReactNoop.render(<Foo updateList={true} />));
|
||||
|
||||
assertLog(['A', 'Suspend! [B]', 'Loading B']);
|
||||
|
||||
|
@ -2421,7 +2421,7 @@ describe('ReactSuspenseList', () => {
|
|||
const previousInst = setAsyncB;
|
||||
|
||||
// During an update we suspend on B.
|
||||
act(() => setAsyncB(true));
|
||||
await act(async () => setAsyncB(true));
|
||||
|
||||
assertLog([
|
||||
'Suspend! [B]',
|
||||
|
@ -2439,7 +2439,7 @@ describe('ReactSuspenseList', () => {
|
|||
|
||||
// Before we resolve we'll rerender the whole list.
|
||||
// This should leave the tree intact.
|
||||
act(() => ReactNoop.render(<Foo updateList={true} />));
|
||||
await act(async () => ReactNoop.render(<Foo updateList={true} />));
|
||||
|
||||
assertLog(['A', 'Suspend! [B]', 'Loading B']);
|
||||
|
||||
|
|
|
@ -2086,7 +2086,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
|||
});
|
||||
|
||||
// TODO: assert toErrorDev() when the warning is implemented again.
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.flushSync(() => _setShow(true));
|
||||
});
|
||||
});
|
||||
|
@ -2113,7 +2113,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
|||
});
|
||||
|
||||
// TODO: assert toErrorDev() when the warning is implemented again.
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.flushSync(() => show());
|
||||
});
|
||||
});
|
||||
|
@ -2142,7 +2142,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
|||
assertLog(['Suspend! [A]']);
|
||||
expect(ReactNoop).toMatchRenderedOutput('Loading...');
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.flushSync(() => showB());
|
||||
});
|
||||
|
||||
|
@ -2173,7 +2173,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
|||
});
|
||||
|
||||
// TODO: assert toErrorDev() when the warning is implemented again.
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.flushSync(() => _setShow(true));
|
||||
});
|
||||
},
|
||||
|
|
|
@ -308,7 +308,7 @@ describe('updaters', () => {
|
|||
expect(allSchedulerTypes).toEqual([[null], [Suspender]]);
|
||||
|
||||
expect(resolver).not.toBeNull();
|
||||
await act(() => {
|
||||
await act(async () => {
|
||||
resolver('abc');
|
||||
return promise;
|
||||
});
|
||||
|
|
|
@ -88,7 +88,7 @@ describe('useEffectEvent', () => {
|
|||
</>,
|
||||
);
|
||||
|
||||
act(button.current.increment);
|
||||
await act(async () => button.current.increment());
|
||||
assertLog(['Increment', 'Count: 1']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
|
@ -97,7 +97,7 @@ describe('useEffectEvent', () => {
|
|||
</>,
|
||||
);
|
||||
|
||||
act(button.current.increment);
|
||||
await act(async () => button.current.increment());
|
||||
assertLog([
|
||||
'Increment',
|
||||
// Event should use the updated callback function closed over the new value.
|
||||
|
@ -121,7 +121,7 @@ describe('useEffectEvent', () => {
|
|||
);
|
||||
|
||||
// Event uses the new prop
|
||||
act(button.current.increment);
|
||||
await act(async () => button.current.increment());
|
||||
assertLog(['Increment', 'Count: 12']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
|
@ -174,7 +174,7 @@ describe('useEffectEvent', () => {
|
|||
</>,
|
||||
);
|
||||
|
||||
act(button.current.increment);
|
||||
await act(async () => button.current.increment());
|
||||
assertLog(['Increment', 'Count: 5']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
|
@ -183,7 +183,7 @@ describe('useEffectEvent', () => {
|
|||
</>,
|
||||
);
|
||||
|
||||
act(button.current.multiply);
|
||||
await act(async () => button.current.multiply());
|
||||
assertLog(['Increment', 'Count: 25']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
|
@ -233,7 +233,7 @@ describe('useEffectEvent', () => {
|
|||
</>,
|
||||
);
|
||||
|
||||
act(button.current.greet);
|
||||
await act(async () => button.current.greet());
|
||||
assertLog(['Say hej', 'Greeting: undefined says hej']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
|
@ -327,7 +327,7 @@ describe('useEffectEvent', () => {
|
|||
</>,
|
||||
);
|
||||
|
||||
act(button.current.increment);
|
||||
await act(async () => button.current.increment());
|
||||
assertLog([
|
||||
'Increment',
|
||||
// Effect should not re-run because the dependency hasn't changed.
|
||||
|
@ -340,7 +340,7 @@ describe('useEffectEvent', () => {
|
|||
</>,
|
||||
);
|
||||
|
||||
act(button.current.increment);
|
||||
await act(async () => button.current.increment());
|
||||
assertLog([
|
||||
'Increment',
|
||||
// Event should use the updated callback function closed over the new value.
|
||||
|
@ -370,7 +370,7 @@ describe('useEffectEvent', () => {
|
|||
);
|
||||
|
||||
// Event uses the new prop
|
||||
act(button.current.increment);
|
||||
await act(async () => button.current.increment());
|
||||
assertLog(['Increment', 'Count: 34']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
|
@ -426,7 +426,7 @@ describe('useEffectEvent', () => {
|
|||
</>,
|
||||
);
|
||||
|
||||
act(button.current.increment);
|
||||
await act(async () => button.current.increment());
|
||||
assertLog([
|
||||
'Increment',
|
||||
// Effect should not re-run because the dependency hasn't changed.
|
||||
|
@ -439,7 +439,7 @@ describe('useEffectEvent', () => {
|
|||
</>,
|
||||
);
|
||||
|
||||
act(button.current.increment);
|
||||
await act(async () => button.current.increment());
|
||||
assertLog([
|
||||
'Increment',
|
||||
// Event should use the updated callback function closed over the new value.
|
||||
|
@ -469,7 +469,7 @@ describe('useEffectEvent', () => {
|
|||
);
|
||||
|
||||
// Event uses the new prop
|
||||
act(button.current.increment);
|
||||
await act(async () => button.current.increment());
|
||||
assertLog(['Increment', 'Count: 34']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
|
@ -531,7 +531,7 @@ describe('useEffectEvent', () => {
|
|||
</>,
|
||||
);
|
||||
|
||||
act(button.current.increment);
|
||||
await act(async () => button.current.increment());
|
||||
assertLog([
|
||||
'Increment',
|
||||
// Effect should not re-run because the dependency hasn't changed.
|
||||
|
@ -544,7 +544,7 @@ describe('useEffectEvent', () => {
|
|||
</>,
|
||||
);
|
||||
|
||||
act(button.current.increment);
|
||||
await act(async () => button.current.increment());
|
||||
assertLog([
|
||||
'Increment',
|
||||
// Event should use the updated callback function closed over the new value.
|
||||
|
@ -574,7 +574,7 @@ describe('useEffectEvent', () => {
|
|||
);
|
||||
|
||||
// Event uses the new prop
|
||||
act(button.current.increment);
|
||||
await act(async () => button.current.increment());
|
||||
assertLog(['Increment', 'Count: 34']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
|
@ -849,7 +849,7 @@ describe('useEffectEvent', () => {
|
|||
),
|
||||
);
|
||||
assertLog(['Add to cart', 'url: /shop/1, numberOfItems: 0']);
|
||||
act(button.current.addToCart);
|
||||
await act(async () => button.current.addToCart());
|
||||
assertLog(['Add to cart']);
|
||||
|
||||
await act(async () =>
|
||||
|
|
|
@ -3783,7 +3783,7 @@ describe('ReactFresh', () => {
|
|||
}
|
||||
|
||||
// This simulates the scenario in https://github.com/facebook/react/issues/17626
|
||||
it('can inject the runtime after the renderer executes', () => {
|
||||
it('can inject the runtime after the renderer executes', async () => {
|
||||
if (__DEV__) {
|
||||
initFauxDevToolsHook();
|
||||
|
||||
|
@ -3792,7 +3792,8 @@ describe('ReactFresh', () => {
|
|||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
Scheduler = require('scheduler');
|
||||
act = require('jest-react').act;
|
||||
act = require('react-dom/test-utils').act;
|
||||
internalAct = require('jest-react').act;
|
||||
|
||||
// Important! Inject into the global hook *after* ReactDOM runs:
|
||||
ReactFreshRuntime = require('react-refresh/runtime');
|
||||
|
|
|
@ -346,7 +346,7 @@ describe(`onRender`, () => {
|
|||
Scheduler.unstable_advanceTime(20); // 30 -> 50
|
||||
|
||||
// Updating a sibling should not report a re-render.
|
||||
act(updateProfilerSibling);
|
||||
await act(async () => updateProfilerSibling());
|
||||
|
||||
expect(callback).not.toHaveBeenCalled();
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue