mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Codemod tests to waitFor pattern (6/?) (#26305)
This converts some of our test suite to use the `waitFor` test pattern, instead of the `expect(Scheduler).toFlushAndYield` pattern. Most of these changes are automated with jscodeshift, with some slight manual cleanup in certain cases. See #26285 for full context.
This commit is contained in:
+44
-48
@@ -18,6 +18,8 @@ let ReactNoop;
|
||||
let Suspense;
|
||||
let Scheduler;
|
||||
let act;
|
||||
let waitForAll;
|
||||
let assertLog;
|
||||
|
||||
describe('memo', () => {
|
||||
beforeEach(() => {
|
||||
@@ -29,6 +31,10 @@ describe('memo', () => {
|
||||
Scheduler = require('scheduler');
|
||||
act = require('jest-react').act;
|
||||
({Suspense} = React);
|
||||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
waitForAll = InternalTestUtils.waitForAll;
|
||||
assertLog = InternalTestUtils.assertLog;
|
||||
});
|
||||
|
||||
function Text(props) {
|
||||
@@ -105,9 +111,7 @@ describe('memo', () => {
|
||||
<Counter count={0} />
|
||||
</Suspense>,
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield(['Loading...']);
|
||||
await Promise.resolve();
|
||||
expect(Scheduler).toFlushAndYield([0]);
|
||||
await waitForAll(['Loading...', 0]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop={0} />);
|
||||
|
||||
// Should bail out because props have not changed
|
||||
@@ -116,7 +120,7 @@ describe('memo', () => {
|
||||
<Counter count={0} />
|
||||
</Suspense>,
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield([]);
|
||||
await waitForAll([]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop={0} />);
|
||||
|
||||
// Should update because count prop changed
|
||||
@@ -125,7 +129,7 @@ describe('memo', () => {
|
||||
<Counter count={1} />
|
||||
</Suspense>,
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield([1]);
|
||||
await waitForAll([1]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop={1} />);
|
||||
});
|
||||
|
||||
@@ -160,19 +164,17 @@ describe('memo', () => {
|
||||
|
||||
const parent = React.createRef(null);
|
||||
ReactNoop.render(<Parent ref={parent} />);
|
||||
expect(Scheduler).toFlushAndYield(['Loading...']);
|
||||
await Promise.resolve();
|
||||
expect(Scheduler).toFlushAndYield(['Count: 0']);
|
||||
await waitForAll(['Loading...', 'Count: 0']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
|
||||
|
||||
// Should bail out because props have not changed
|
||||
ReactNoop.render(<Parent ref={parent} />);
|
||||
expect(Scheduler).toFlushAndYield([]);
|
||||
await waitForAll([]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
|
||||
|
||||
// Should update because there was a context change
|
||||
parent.current.setState({count: 1});
|
||||
expect(Scheduler).toFlushAndYield(['Count: 1']);
|
||||
await waitForAll(['Count: 1']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 1" />);
|
||||
});
|
||||
|
||||
@@ -273,7 +275,7 @@ describe('memo', () => {
|
||||
await act(async () => {
|
||||
root.render(<App prop="A" />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'SimpleMemo [A0]',
|
||||
'ComplexMemo [A0]',
|
||||
'MemoWithIndirection [A0]',
|
||||
@@ -283,7 +285,7 @@ describe('memo', () => {
|
||||
await act(async () => {
|
||||
root.render(<App prop="B" />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'SimpleMemo [B0]',
|
||||
'ComplexMemo [B0]',
|
||||
'MemoWithIndirection [B0]',
|
||||
@@ -298,7 +300,7 @@ describe('memo', () => {
|
||||
root.render(<App prop="B" />);
|
||||
});
|
||||
// Nothing re-renders
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
assertLog([]);
|
||||
|
||||
// Demonstrate what happens when the prop object changes, it bails out
|
||||
// because all the props are the same, but we still render the
|
||||
@@ -309,7 +311,7 @@ describe('memo', () => {
|
||||
});
|
||||
// The components should re-render with the new local state, but none
|
||||
// of the props objects should have changed
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'SimpleMemo [B1]',
|
||||
'ComplexMemo [B1]',
|
||||
'MemoWithIndirection [B1]',
|
||||
@@ -322,7 +324,7 @@ describe('memo', () => {
|
||||
});
|
||||
// The components should re-render with the new local state, but none
|
||||
// of the props objects should have changed
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'SimpleMemo [B2]',
|
||||
'ComplexMemo [B2]',
|
||||
'MemoWithIndirection [B2]',
|
||||
@@ -345,9 +347,7 @@ describe('memo', () => {
|
||||
<Counter count={0} />
|
||||
</Suspense>,
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield(['Loading...']);
|
||||
await Promise.resolve();
|
||||
expect(Scheduler).toFlushAndYield([0]);
|
||||
await waitForAll(['Loading...', 0]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop={0} />);
|
||||
|
||||
// Should bail out because props have not changed
|
||||
@@ -356,7 +356,7 @@ describe('memo', () => {
|
||||
<Counter count={0} />
|
||||
</Suspense>,
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield(['Old count: 0, New count: 0']);
|
||||
await waitForAll(['Old count: 0, New count: 0']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop={0} />);
|
||||
|
||||
// Should update because count prop changed
|
||||
@@ -365,7 +365,7 @@ describe('memo', () => {
|
||||
<Counter count={1} />
|
||||
</Suspense>,
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield(['Old count: 0, New count: 1', 1]);
|
||||
await waitForAll(['Old count: 0, New count: 1', 1]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop={1} />);
|
||||
});
|
||||
|
||||
@@ -383,9 +383,7 @@ describe('memo', () => {
|
||||
<Counter count={0} />
|
||||
</Suspense>,
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield(['Loading...']);
|
||||
await Promise.resolve();
|
||||
expect(Scheduler).toFlushAndYield(['0!']);
|
||||
await waitForAll(['Loading...', '0!']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="0!" />);
|
||||
|
||||
// Should bail out because props have not changed
|
||||
@@ -394,7 +392,7 @@ describe('memo', () => {
|
||||
<Counter count={0} />
|
||||
</Suspense>,
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield([]);
|
||||
await waitForAll([]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="0!" />);
|
||||
|
||||
// Should update because count prop changed
|
||||
@@ -403,7 +401,7 @@ describe('memo', () => {
|
||||
<Counter count={1} />
|
||||
</Suspense>,
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield(['1!']);
|
||||
await waitForAll(['1!']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="1!" />);
|
||||
});
|
||||
|
||||
@@ -436,10 +434,8 @@ describe('memo', () => {
|
||||
<Counter e={5} />
|
||||
</Suspense>,
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield(['Loading...']);
|
||||
await Promise.resolve();
|
||||
expect(() => {
|
||||
expect(Scheduler).toFlushAndYield([15]);
|
||||
await expect(async () => {
|
||||
await waitForAll(['Loading...', 15]);
|
||||
}).toErrorDev([
|
||||
'Counter: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
|
||||
]);
|
||||
@@ -451,7 +447,7 @@ describe('memo', () => {
|
||||
<Counter e={5} />
|
||||
</Suspense>,
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield([]);
|
||||
await waitForAll([]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop={15} />);
|
||||
|
||||
// Should update because count prop changed
|
||||
@@ -460,7 +456,7 @@ describe('memo', () => {
|
||||
<Counter e={10} />
|
||||
</Suspense>,
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield([20]);
|
||||
await waitForAll([20]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop={20} />);
|
||||
});
|
||||
|
||||
@@ -480,7 +476,7 @@ describe('memo', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('validates propTypes declared on the inner component', () => {
|
||||
it('validates propTypes declared on the inner component', async () => {
|
||||
function FnInner(props) {
|
||||
return props.inner;
|
||||
}
|
||||
@@ -488,23 +484,23 @@ describe('memo', () => {
|
||||
const Fn = React.memo(FnInner);
|
||||
|
||||
// Mount
|
||||
expect(() => {
|
||||
await expect(async () => {
|
||||
ReactNoop.render(<Fn inner="2" />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
}).toErrorDev(
|
||||
'Invalid prop `inner` of type `string` supplied to `FnInner`, expected `number`.',
|
||||
);
|
||||
|
||||
// Update
|
||||
expect(() => {
|
||||
await expect(async () => {
|
||||
ReactNoop.render(<Fn inner={false} />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
}).toErrorDev(
|
||||
'Invalid prop `inner` of type `boolean` supplied to `FnInner`, expected `number`.',
|
||||
);
|
||||
});
|
||||
|
||||
it('validates propTypes declared on the outer component', () => {
|
||||
it('validates propTypes declared on the outer component', async () => {
|
||||
function FnInner(props) {
|
||||
return props.outer;
|
||||
}
|
||||
@@ -512,25 +508,25 @@ describe('memo', () => {
|
||||
Fn.propTypes = {outer: PropTypes.number.isRequired};
|
||||
|
||||
// Mount
|
||||
expect(() => {
|
||||
await expect(async () => {
|
||||
ReactNoop.render(<Fn outer="3" />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
}).toErrorDev(
|
||||
// Outer props are checked in createElement
|
||||
'Invalid prop `outer` of type `string` supplied to `FnInner`, expected `number`.',
|
||||
);
|
||||
|
||||
// Update
|
||||
expect(() => {
|
||||
await expect(async () => {
|
||||
ReactNoop.render(<Fn outer={false} />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
}).toErrorDev(
|
||||
// Outer props are checked in createElement
|
||||
'Invalid prop `outer` of type `boolean` supplied to `FnInner`, expected `number`.',
|
||||
);
|
||||
});
|
||||
|
||||
it('validates nested propTypes declarations', () => {
|
||||
it('validates nested propTypes declarations', async () => {
|
||||
function Inner(props) {
|
||||
return props.inner + props.middle + props.outer;
|
||||
}
|
||||
@@ -549,20 +545,20 @@ describe('memo', () => {
|
||||
<Outer />
|
||||
</div>,
|
||||
);
|
||||
expect(() => {
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await expect(async () => {
|
||||
await waitForAll([]);
|
||||
}).toErrorDev([
|
||||
'Inner: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
|
||||
]);
|
||||
|
||||
// Mount
|
||||
expect(() => {
|
||||
await expect(async () => {
|
||||
ReactNoop.render(
|
||||
<div>
|
||||
<Outer inner="2" middle="3" outer="4" />
|
||||
</div>,
|
||||
);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
}).toErrorDev([
|
||||
'Invalid prop `outer` of type `string` supplied to `Inner`, expected `number`.',
|
||||
'Invalid prop `middle` of type `string` supplied to `Inner`, expected `number`.',
|
||||
@@ -570,13 +566,13 @@ describe('memo', () => {
|
||||
]);
|
||||
|
||||
// Update
|
||||
expect(() => {
|
||||
await expect(async () => {
|
||||
ReactNoop.render(
|
||||
<div>
|
||||
<Outer inner={false} middle={false} outer={false} />
|
||||
</div>,
|
||||
);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
}).toErrorDev([
|
||||
'Invalid prop `outer` of type `boolean` supplied to `Inner`, expected `number`.',
|
||||
'Invalid prop `middle` of type `boolean` supplied to `Inner`, expected `number`.',
|
||||
|
||||
+98
-90
@@ -14,6 +14,8 @@ let useContext;
|
||||
let ReactNoop;
|
||||
let Scheduler;
|
||||
let gen;
|
||||
let waitForAll;
|
||||
let waitFor;
|
||||
|
||||
describe('ReactNewContext', () => {
|
||||
beforeEach(() => {
|
||||
@@ -24,6 +26,10 @@ describe('ReactNewContext', () => {
|
||||
ReactNoop = require('react-noop-renderer');
|
||||
Scheduler = require('scheduler');
|
||||
gen = require('random-seed');
|
||||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
waitForAll = InternalTestUtils.waitForAll;
|
||||
waitFor = InternalTestUtils.waitFor;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -110,7 +116,7 @@ describe('ReactNewContext', () => {
|
||||
|
||||
function sharedContextTests(label, getConsumer) {
|
||||
describe(`reading context with ${label}`, () => {
|
||||
it('simple mount and update', () => {
|
||||
it('simple mount and update', async () => {
|
||||
const Context = React.createContext(1);
|
||||
const Consumer = getConsumer(Context);
|
||||
|
||||
@@ -131,16 +137,16 @@ describe('ReactNewContext', () => {
|
||||
}
|
||||
|
||||
ReactNoop.render(<App value={2} />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Result: 2" />);
|
||||
|
||||
// Update
|
||||
ReactNoop.render(<App value={3} />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Result: 3" />);
|
||||
});
|
||||
|
||||
it('propagates through shouldComponentUpdate false', () => {
|
||||
it('propagates through shouldComponentUpdate false', async () => {
|
||||
const Context = React.createContext(1);
|
||||
const ContextConsumer = getConsumer(Context);
|
||||
|
||||
@@ -189,7 +195,7 @@ describe('ReactNewContext', () => {
|
||||
}
|
||||
|
||||
ReactNoop.render(<App value={2} />);
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
await waitForAll([
|
||||
'App',
|
||||
'Provider',
|
||||
'Indirection',
|
||||
@@ -201,15 +207,11 @@ describe('ReactNewContext', () => {
|
||||
|
||||
// Update
|
||||
ReactNoop.render(<App value={3} />);
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
'App',
|
||||
'Provider',
|
||||
'Consumer render prop',
|
||||
]);
|
||||
await waitForAll(['App', 'Provider', 'Consumer render prop']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Result: 3" />);
|
||||
});
|
||||
|
||||
it('consumers bail out if context value is the same', () => {
|
||||
it('consumers bail out if context value is the same', async () => {
|
||||
const Context = React.createContext(1);
|
||||
const ContextConsumer = getConsumer(Context);
|
||||
|
||||
@@ -258,7 +260,7 @@ describe('ReactNewContext', () => {
|
||||
}
|
||||
|
||||
ReactNoop.render(<App value={2} />);
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
await waitForAll([
|
||||
'App',
|
||||
'Provider',
|
||||
'Indirection',
|
||||
@@ -270,7 +272,7 @@ describe('ReactNewContext', () => {
|
||||
|
||||
// Update with the same context value
|
||||
ReactNoop.render(<App value={2} />);
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
await waitForAll([
|
||||
'App',
|
||||
'Provider',
|
||||
// Don't call render prop again
|
||||
@@ -278,7 +280,7 @@ describe('ReactNewContext', () => {
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Result: 2" />);
|
||||
});
|
||||
|
||||
it('nested providers', () => {
|
||||
it('nested providers', async () => {
|
||||
const Context = React.createContext(1);
|
||||
const Consumer = getConsumer(Context);
|
||||
|
||||
@@ -325,16 +327,16 @@ describe('ReactNewContext', () => {
|
||||
}
|
||||
|
||||
ReactNoop.render(<App value={2} />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Result: 8" />);
|
||||
|
||||
// Update
|
||||
ReactNoop.render(<App value={3} />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Result: 12" />);
|
||||
});
|
||||
|
||||
it('should provide the correct (default) values to consumers outside of a provider', () => {
|
||||
it('should provide the correct (default) values to consumers outside of a provider', async () => {
|
||||
const FooContext = React.createContext({value: 'foo-initial'});
|
||||
const BarContext = React.createContext({value: 'bar-initial'});
|
||||
const FooConsumer = getConsumer(FooContext);
|
||||
@@ -369,10 +371,10 @@ describe('ReactNewContext', () => {
|
||||
</BarConsumer>
|
||||
</>,
|
||||
);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
});
|
||||
|
||||
it('multiple consumers in different branches', () => {
|
||||
it('multiple consumers in different branches', async () => {
|
||||
const Context = React.createContext(1);
|
||||
const Consumer = getConsumer(Context);
|
||||
|
||||
@@ -420,7 +422,7 @@ describe('ReactNewContext', () => {
|
||||
}
|
||||
|
||||
ReactNoop.render(<App value={2} />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Result: 4" />
|
||||
@@ -430,7 +432,7 @@ describe('ReactNewContext', () => {
|
||||
|
||||
// Update
|
||||
ReactNoop.render(<App value={3} />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Result: 6" />
|
||||
@@ -440,7 +442,7 @@ describe('ReactNewContext', () => {
|
||||
|
||||
// Another update
|
||||
ReactNoop.render(<App value={4} />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Result: 8" />
|
||||
@@ -449,7 +451,7 @@ describe('ReactNewContext', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('compares context values with Object.is semantics', () => {
|
||||
it('compares context values with Object.is semantics', async () => {
|
||||
const Context = React.createContext(1);
|
||||
const ContextConsumer = getConsumer(Context);
|
||||
|
||||
@@ -498,7 +500,7 @@ describe('ReactNewContext', () => {
|
||||
}
|
||||
|
||||
ReactNoop.render(<App value={NaN} />);
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
await waitForAll([
|
||||
'App',
|
||||
'Provider',
|
||||
'Indirection',
|
||||
@@ -510,7 +512,7 @@ describe('ReactNewContext', () => {
|
||||
|
||||
// Update
|
||||
ReactNoop.render(<App value={NaN} />);
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
await waitForAll([
|
||||
'App',
|
||||
'Provider',
|
||||
// Consumer should not re-render again
|
||||
@@ -519,7 +521,7 @@ describe('ReactNewContext', () => {
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Result: NaN" />);
|
||||
});
|
||||
|
||||
it('context unwinds when interrupted', () => {
|
||||
it('context unwinds when interrupted', async () => {
|
||||
const Context = React.createContext('Default');
|
||||
const ContextConsumer = getConsumer(Context);
|
||||
|
||||
@@ -564,14 +566,14 @@ describe('ReactNewContext', () => {
|
||||
}
|
||||
|
||||
ReactNoop.render(<App value="A" />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
// The second provider should use the default value.
|
||||
<span prop="Result: Does not unwind" />,
|
||||
);
|
||||
});
|
||||
|
||||
it("does not re-render if there's an update in a child", () => {
|
||||
it("does not re-render if there's an update in a child", async () => {
|
||||
const Context = React.createContext(0);
|
||||
const Consumer = getConsumer(Context);
|
||||
|
||||
@@ -603,19 +605,19 @@ describe('ReactNewContext', () => {
|
||||
|
||||
// Initial mount
|
||||
ReactNoop.render(<App value={1} />);
|
||||
expect(Scheduler).toFlushAndYield(['Consumer render prop', 'Child']);
|
||||
await waitForAll(['Consumer render prop', 'Child']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<span prop="Context: 1, Step: 0" />,
|
||||
);
|
||||
|
||||
child.setState({step: 1});
|
||||
expect(Scheduler).toFlushAndYield(['Child']);
|
||||
await waitForAll(['Child']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<span prop="Context: 1, Step: 1" />,
|
||||
);
|
||||
});
|
||||
|
||||
it('consumer bails out if value is unchanged and something above bailed out', () => {
|
||||
it('consumer bails out if value is unchanged and something above bailed out', async () => {
|
||||
const Context = React.createContext(0);
|
||||
const Consumer = getConsumer(Context);
|
||||
|
||||
@@ -660,7 +662,7 @@ describe('ReactNewContext', () => {
|
||||
|
||||
// Initial mount
|
||||
ReactNoop.render(<App value={1} />);
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
await waitForAll([
|
||||
'App',
|
||||
'PureIndirection',
|
||||
'ChildWithInlineRenderCallback',
|
||||
@@ -677,7 +679,7 @@ describe('ReactNewContext', () => {
|
||||
|
||||
// Update (bailout)
|
||||
ReactNoop.render(<App value={1} />);
|
||||
expect(Scheduler).toFlushAndYield(['App']);
|
||||
await waitForAll(['App']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop={1} />
|
||||
@@ -687,7 +689,7 @@ describe('ReactNewContext', () => {
|
||||
|
||||
// Update (no bailout)
|
||||
ReactNoop.render(<App value={2} />);
|
||||
expect(Scheduler).toFlushAndYield(['App', 'Consumer', 'Consumer']);
|
||||
await waitForAll(['App', 'Consumer', 'Consumer']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop={2} />
|
||||
@@ -697,7 +699,7 @@ describe('ReactNewContext', () => {
|
||||
});
|
||||
|
||||
// @gate www
|
||||
it("context consumer doesn't bail out inside hidden subtree", () => {
|
||||
it("context consumer doesn't bail out inside hidden subtree", async () => {
|
||||
const Context = React.createContext('dark');
|
||||
const Consumer = getConsumer(Context);
|
||||
|
||||
@@ -712,7 +714,7 @@ describe('ReactNewContext', () => {
|
||||
}
|
||||
|
||||
ReactNoop.render(<App theme="dark" />);
|
||||
expect(Scheduler).toFlushAndYield(['dark']);
|
||||
await waitForAll(['dark']);
|
||||
expect(ReactNoop.getChildrenAsJSX()).toEqual(
|
||||
<div hidden={true}>
|
||||
<span prop="dark" />
|
||||
@@ -720,7 +722,7 @@ describe('ReactNewContext', () => {
|
||||
);
|
||||
|
||||
ReactNoop.render(<App theme="light" />);
|
||||
expect(Scheduler).toFlushAndYield(['light']);
|
||||
await waitForAll(['light']);
|
||||
expect(ReactNoop.getChildrenAsJSX()).toEqual(
|
||||
<div hidden={true}>
|
||||
<span prop="light" />
|
||||
@@ -729,7 +731,7 @@ describe('ReactNewContext', () => {
|
||||
});
|
||||
|
||||
// This is a regression case for https://github.com/facebook/react/issues/12389.
|
||||
it('does not run into an infinite loop', () => {
|
||||
it('does not run into an infinite loop', async () => {
|
||||
const Context = React.createContext(null);
|
||||
const Consumer = getConsumer(Context);
|
||||
|
||||
@@ -759,15 +761,15 @@ describe('ReactNewContext', () => {
|
||||
}
|
||||
|
||||
ReactNoop.render(<App reverse={false} />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
ReactNoop.render(<App reverse={true} />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
ReactNoop.render(<App reverse={false} />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
});
|
||||
|
||||
// This is a regression case for https://github.com/facebook/react/issues/12686
|
||||
it('does not skip some siblings', () => {
|
||||
it('does not skip some siblings', async () => {
|
||||
const Context = React.createContext(0);
|
||||
const ContextConsumer = getConsumer(Context);
|
||||
|
||||
@@ -816,7 +818,7 @@ describe('ReactNewContext', () => {
|
||||
// Initial mount
|
||||
let inst;
|
||||
ReactNoop.render(<App ref={ref => (inst = ref)} />);
|
||||
expect(Scheduler).toFlushAndYield(['App']);
|
||||
await waitForAll(['App']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="static 1" />
|
||||
@@ -825,7 +827,7 @@ describe('ReactNewContext', () => {
|
||||
);
|
||||
// Update the first time
|
||||
inst.setState({step: 1});
|
||||
expect(Scheduler).toFlushAndYield(['App', 'Consumer']);
|
||||
await waitForAll(['App', 'Consumer']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="static 1" />
|
||||
@@ -835,7 +837,7 @@ describe('ReactNewContext', () => {
|
||||
);
|
||||
// Update the second time
|
||||
inst.setState({step: 2});
|
||||
expect(Scheduler).toFlushAndYield(['App', 'Consumer']);
|
||||
await waitForAll(['App', 'Consumer']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="static 1" />
|
||||
@@ -863,7 +865,7 @@ describe('ReactNewContext', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('warns if multiple renderers concurrently render the same context', () => {
|
||||
it('warns if multiple renderers concurrently render the same context', async () => {
|
||||
spyOnDev(console, 'error').mockImplementation(() => {});
|
||||
const Context = React.createContext(0);
|
||||
|
||||
@@ -885,17 +887,20 @@ describe('ReactNewContext', () => {
|
||||
ReactNoop.render(<App value={1} />);
|
||||
});
|
||||
// Render past the Provider, but don't commit yet
|
||||
expect(Scheduler).toFlushAndYieldThrough(['Foo']);
|
||||
await waitFor(['Foo']);
|
||||
|
||||
// Get a new copy of ReactNoop
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactNoop = require('react-noop-renderer');
|
||||
Scheduler = require('scheduler');
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
waitForAll = InternalTestUtils.waitForAll;
|
||||
waitFor = InternalTestUtils.waitFor;
|
||||
|
||||
// Render the provider again using a different renderer
|
||||
ReactNoop.render(<App value={1} />);
|
||||
expect(Scheduler).toFlushAndYield(['Foo', 'Foo']);
|
||||
await waitForAll(['Foo', 'Foo']);
|
||||
|
||||
if (__DEV__) {
|
||||
expect(console.error.mock.calls[0][0]).toContain(
|
||||
@@ -905,7 +910,7 @@ describe('ReactNewContext', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('does not warn if multiple renderers use the same context sequentially', () => {
|
||||
it('does not warn if multiple renderers use the same context sequentially', async () => {
|
||||
spyOnDev(console, 'error');
|
||||
const Context = React.createContext(0);
|
||||
|
||||
@@ -926,24 +931,27 @@ describe('ReactNewContext', () => {
|
||||
React.startTransition(() => {
|
||||
ReactNoop.render(<App value={1} />);
|
||||
});
|
||||
expect(Scheduler).toFlushAndYield(['Foo', 'Foo']);
|
||||
await waitForAll(['Foo', 'Foo']);
|
||||
|
||||
// Get a new copy of ReactNoop
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactNoop = require('react-noop-renderer');
|
||||
Scheduler = require('scheduler');
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
waitForAll = InternalTestUtils.waitForAll;
|
||||
waitFor = InternalTestUtils.waitFor;
|
||||
|
||||
// Render the provider again using a different renderer
|
||||
ReactNoop.render(<App value={1} />);
|
||||
expect(Scheduler).toFlushAndYield(['Foo', 'Foo']);
|
||||
await waitForAll(['Foo', 'Foo']);
|
||||
|
||||
if (__DEV__) {
|
||||
expect(console.error).not.toHaveBeenCalled();
|
||||
}
|
||||
});
|
||||
|
||||
it('provider bails out if children and value are unchanged (like sCU)', () => {
|
||||
it('provider bails out if children and value are unchanged (like sCU)', async () => {
|
||||
const Context = React.createContext(0);
|
||||
|
||||
function Child() {
|
||||
@@ -962,19 +970,19 @@ describe('ReactNewContext', () => {
|
||||
|
||||
// Initial mount
|
||||
ReactNoop.render(<App value={1} />);
|
||||
expect(Scheduler).toFlushAndYield(['App', 'Child']);
|
||||
await waitForAll(['App', 'Child']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Child" />);
|
||||
|
||||
// Update
|
||||
ReactNoop.render(<App value={1} />);
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
await waitForAll([
|
||||
'App',
|
||||
// Child does not re-render
|
||||
]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Child" />);
|
||||
});
|
||||
|
||||
it('provider does not bail out if legacy context changed above', () => {
|
||||
it('provider does not bail out if legacy context changed above', async () => {
|
||||
const Context = React.createContext(0);
|
||||
|
||||
function Child() {
|
||||
@@ -1021,22 +1029,22 @@ describe('ReactNewContext', () => {
|
||||
</App>
|
||||
</LegacyProvider>,
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield(['LegacyProvider', 'App', 'Child']);
|
||||
await waitForAll(['LegacyProvider', 'App', 'Child']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Child" />);
|
||||
|
||||
// Update App with same value (should bail out)
|
||||
appRef.current.setState({value: 1});
|
||||
expect(Scheduler).toFlushAndYield(['App']);
|
||||
await waitForAll(['App']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Child" />);
|
||||
|
||||
// Update LegacyProvider (should not bail out)
|
||||
legacyProviderRef.current.setState({value: 1});
|
||||
expect(Scheduler).toFlushAndYield(['LegacyProvider', 'App', 'Child']);
|
||||
await waitForAll(['LegacyProvider', 'App', 'Child']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Child" />);
|
||||
|
||||
// Update App with same value (should bail out)
|
||||
appRef.current.setState({value: 1});
|
||||
expect(Scheduler).toFlushAndYield(['App']);
|
||||
await waitForAll(['App']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Child" />);
|
||||
});
|
||||
});
|
||||
@@ -1055,7 +1063,7 @@ describe('ReactNewContext', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('can read other contexts inside consumer render prop', () => {
|
||||
it('can read other contexts inside consumer render prop', async () => {
|
||||
const FooContext = React.createContext(0);
|
||||
const BarContext = React.createContext(0);
|
||||
|
||||
@@ -1092,17 +1100,17 @@ describe('ReactNewContext', () => {
|
||||
}
|
||||
|
||||
ReactNoop.render(<App foo={1} bar={1} />);
|
||||
expect(Scheduler).toFlushAndYield(['Foo: 1, Bar: 1']);
|
||||
await waitForAll(['Foo: 1, Bar: 1']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Foo: 1, Bar: 1" />);
|
||||
|
||||
// Update foo
|
||||
ReactNoop.render(<App foo={2} bar={1} />);
|
||||
expect(Scheduler).toFlushAndYield(['Foo: 2, Bar: 1']);
|
||||
await waitForAll(['Foo: 2, Bar: 1']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Foo: 2, Bar: 1" />);
|
||||
|
||||
// Update bar
|
||||
ReactNoop.render(<App foo={2} bar={2} />);
|
||||
expect(Scheduler).toFlushAndYield(['Foo: 2, Bar: 2']);
|
||||
await waitForAll(['Foo: 2, Bar: 2']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="Foo: 2, Bar: 2" />);
|
||||
});
|
||||
|
||||
@@ -1111,7 +1119,7 @@ describe('ReactNewContext', () => {
|
||||
// If we bailed out on referential equality, it would be confusing that you
|
||||
// can call this.setState(), but an autobound render callback "blocked" the update.
|
||||
// https://github.com/facebook/react/pull/12470#issuecomment-376917711
|
||||
it('consumer does not bail out if there were no bailouts above it', () => {
|
||||
it('consumer does not bail out if there were no bailouts above it', async () => {
|
||||
const Context = React.createContext(0);
|
||||
const Consumer = Context.Consumer;
|
||||
|
||||
@@ -1138,12 +1146,12 @@ describe('ReactNewContext', () => {
|
||||
// Initial mount
|
||||
let inst;
|
||||
ReactNoop.render(<App value={1} ref={ref => (inst = ref)} />);
|
||||
expect(Scheduler).toFlushAndYield(['App', 'App#renderConsumer']);
|
||||
await waitForAll(['App', 'App#renderConsumer']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="hello" />);
|
||||
|
||||
// Update
|
||||
inst.setState({text: 'goodbye'});
|
||||
expect(Scheduler).toFlushAndYield(['App', 'App#renderConsumer']);
|
||||
await waitForAll(['App', 'App#renderConsumer']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="goodbye" />);
|
||||
});
|
||||
});
|
||||
@@ -1152,7 +1160,7 @@ describe('ReactNewContext', () => {
|
||||
// Unstable changedBits API was removed. Port this test to context selectors
|
||||
// once that exists.
|
||||
// @gate FIXME
|
||||
it('can read the same context multiple times in the same function', () => {
|
||||
it('can read the same context multiple times in the same function', async () => {
|
||||
const Context = React.createContext({foo: 0, bar: 0, baz: 0}, (a, b) => {
|
||||
let result = 0;
|
||||
if (a.foo !== b.foo) {
|
||||
@@ -1212,7 +1220,7 @@ describe('ReactNewContext', () => {
|
||||
}
|
||||
|
||||
ReactNoop.render(<App foo={1} bar={1} baz={1} />);
|
||||
expect(Scheduler).toFlushAndYield(['Foo: 1, Bar: 1', 'Baz: 1']);
|
||||
await waitForAll(['Foo: 1, Bar: 1', 'Baz: 1']);
|
||||
expect(ReactNoop).toMatchRenderedOutput([
|
||||
<span prop="Foo: 1, Bar: 1" />,
|
||||
<span prop="Baz: 1" />,
|
||||
@@ -1220,7 +1228,7 @@ describe('ReactNewContext', () => {
|
||||
|
||||
// Update only foo
|
||||
ReactNoop.render(<App foo={2} bar={1} baz={1} />);
|
||||
expect(Scheduler).toFlushAndYield(['Foo: 2, Bar: 1']);
|
||||
await waitForAll(['Foo: 2, Bar: 1']);
|
||||
expect(ReactNoop).toMatchRenderedOutput([
|
||||
<span prop="Foo: 2, Bar: 1" />,
|
||||
<span prop="Baz: 1" />,
|
||||
@@ -1228,7 +1236,7 @@ describe('ReactNewContext', () => {
|
||||
|
||||
// Update only bar
|
||||
ReactNoop.render(<App foo={2} bar={2} baz={1} />);
|
||||
expect(Scheduler).toFlushAndYield(['Foo: 2, Bar: 2']);
|
||||
await waitForAll(['Foo: 2, Bar: 2']);
|
||||
expect(ReactNoop).toMatchRenderedOutput([
|
||||
<span prop="Foo: 2, Bar: 2" />,
|
||||
<span prop="Baz: 1" />,
|
||||
@@ -1236,7 +1244,7 @@ describe('ReactNewContext', () => {
|
||||
|
||||
// Update only baz
|
||||
ReactNoop.render(<App foo={2} bar={2} baz={2} />);
|
||||
expect(Scheduler).toFlushAndYield(['Baz: 2']);
|
||||
await waitForAll(['Baz: 2']);
|
||||
expect(ReactNoop).toMatchRenderedOutput([
|
||||
<span prop="Foo: 2, Bar: 2" />,
|
||||
<span prop="Baz: 2" />,
|
||||
@@ -1248,7 +1256,7 @@ describe('ReactNewContext', () => {
|
||||
// If we bailed out on referential equality, it would be confusing that you
|
||||
// can call this.setState(), but an autobound render callback "blocked" the update.
|
||||
// https://github.com/facebook/react/pull/12470#issuecomment-376917711
|
||||
it('does not bail out if there were no bailouts above it', () => {
|
||||
it('does not bail out if there were no bailouts above it', async () => {
|
||||
const Context = React.createContext(0);
|
||||
|
||||
class Consumer extends React.Component {
|
||||
@@ -1281,12 +1289,12 @@ describe('ReactNewContext', () => {
|
||||
// Initial mount
|
||||
let inst;
|
||||
ReactNoop.render(<App value={1} ref={ref => (inst = ref)} />);
|
||||
expect(Scheduler).toFlushAndYield(['App', 'App#renderConsumer']);
|
||||
await waitForAll(['App', 'App#renderConsumer']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="hello" />);
|
||||
|
||||
// Update
|
||||
inst.setState({text: 'goodbye'});
|
||||
expect(Scheduler).toFlushAndYield(['App', 'App#renderConsumer']);
|
||||
await waitForAll(['App', 'App#renderConsumer']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="goodbye" />);
|
||||
});
|
||||
|
||||
@@ -1361,7 +1369,7 @@ describe('ReactNewContext', () => {
|
||||
// If we bailed out on referential equality, it would be confusing that you
|
||||
// can call this.setState(), but an autobound render callback "blocked" the update.
|
||||
// https://github.com/facebook/react/pull/12470#issuecomment-376917711
|
||||
it('does not bail out if there were no bailouts above it', () => {
|
||||
it('does not bail out if there were no bailouts above it', async () => {
|
||||
const Context = React.createContext(0);
|
||||
|
||||
function Consumer({children}) {
|
||||
@@ -1392,17 +1400,17 @@ describe('ReactNewContext', () => {
|
||||
// Initial mount
|
||||
let inst;
|
||||
ReactNoop.render(<App value={1} ref={ref => (inst = ref)} />);
|
||||
expect(Scheduler).toFlushAndYield(['App', 'App#renderConsumer']);
|
||||
await waitForAll(['App', 'App#renderConsumer']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="hello" />);
|
||||
|
||||
// Update
|
||||
inst.setState({text: 'goodbye'});
|
||||
expect(Scheduler).toFlushAndYield(['App', 'App#renderConsumer']);
|
||||
await waitForAll(['App', 'App#renderConsumer']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop="goodbye" />);
|
||||
});
|
||||
});
|
||||
|
||||
it('unwinds after errors in complete phase', () => {
|
||||
it('unwinds after errors in complete phase', async () => {
|
||||
const Context = React.createContext(0);
|
||||
|
||||
// This is a regression test for stack misalignment
|
||||
@@ -1419,7 +1427,7 @@ describe('ReactNewContext', () => {
|
||||
<Context.Consumer>{value => <span prop={value} />}</Context.Consumer>
|
||||
</Context.Provider>,
|
||||
);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(<span prop={10} />);
|
||||
});
|
||||
|
||||
@@ -1631,7 +1639,7 @@ Context fuzz tester error! Copy and paste the following line into the test suite
|
||||
});
|
||||
});
|
||||
|
||||
it('should warn with an error message when using context as a consumer in DEV', () => {
|
||||
it('should warn with an error message when using context as a consumer in DEV', async () => {
|
||||
const BarContext = React.createContext({value: 'bar-initial'});
|
||||
const BarConsumer = BarContext;
|
||||
|
||||
@@ -1647,9 +1655,9 @@ Context fuzz tester error! Copy and paste the following line into the test suite
|
||||
);
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
await expect(async () => {
|
||||
ReactNoop.render(<Component />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
}).toErrorDev(
|
||||
'Rendering <Context> directly is not supported and will be removed in ' +
|
||||
'a future major release. Did you mean to render <Context.Consumer> instead?',
|
||||
@@ -1657,7 +1665,7 @@ Context fuzz tester error! Copy and paste the following line into the test suite
|
||||
});
|
||||
|
||||
// False positive regression test.
|
||||
it('should not warn when using Consumer from React < 16.6 with newer renderer', () => {
|
||||
it('should not warn when using Consumer from React < 16.6 with newer renderer', async () => {
|
||||
const BarContext = React.createContext({value: 'bar-initial'});
|
||||
// React 16.5 and earlier didn't have a separate object.
|
||||
BarContext.Consumer = BarContext;
|
||||
@@ -1675,10 +1683,10 @@ Context fuzz tester error! Copy and paste the following line into the test suite
|
||||
}
|
||||
|
||||
ReactNoop.render(<Component />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
});
|
||||
|
||||
it('should warn with an error message when using nested context consumers in DEV', () => {
|
||||
it('should warn with an error message when using nested context consumers in DEV', async () => {
|
||||
const BarContext = React.createContext({value: 'bar-initial'});
|
||||
const BarConsumer = BarContext;
|
||||
|
||||
@@ -1694,16 +1702,16 @@ Context fuzz tester error! Copy and paste the following line into the test suite
|
||||
);
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
await expect(async () => {
|
||||
ReactNoop.render(<Component />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
}).toErrorDev(
|
||||
'Rendering <Context.Consumer.Consumer> is not supported and will be removed in ' +
|
||||
'a future major release. Did you mean to render <Context.Consumer> instead?',
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn with an error message when using Context.Consumer.Provider DEV', () => {
|
||||
it('should warn with an error message when using Context.Consumer.Provider DEV', async () => {
|
||||
const BarContext = React.createContext({value: 'bar-initial'});
|
||||
|
||||
function Component() {
|
||||
@@ -1718,9 +1726,9 @@ Context fuzz tester error! Copy and paste the following line into the test suite
|
||||
);
|
||||
}
|
||||
|
||||
expect(() => {
|
||||
await expect(async () => {
|
||||
ReactNoop.render(<Component />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
}).toErrorDev(
|
||||
'Rendering <Context.Consumer.Provider> is not supported and will be removed in ' +
|
||||
'a future major release. Did you mean to render <Context.Provider> instead?',
|
||||
|
||||
@@ -13,6 +13,7 @@ const React = require('react');
|
||||
const ReactNoop = require('react-noop-renderer');
|
||||
const Scheduler = require('scheduler');
|
||||
const act = require('jest-react').act;
|
||||
const {assertLog, waitForAll} = require('internal-test-utils');
|
||||
|
||||
// TODO: These tests are no longer specific to the noop renderer
|
||||
// implementation. They test the internal implementation we use in the React
|
||||
@@ -34,7 +35,7 @@ describe('internal act()', () => {
|
||||
/>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
expect(calledLog).toEqual([0]);
|
||||
});
|
||||
|
||||
@@ -56,8 +57,8 @@ describe('internal act()', () => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(<App />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['stage 1', 'stage 2']);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
assertLog(['stage 1', 'stage 2']);
|
||||
await waitForAll([]);
|
||||
expect(ReactNoop).toMatchRenderedOutput('1');
|
||||
});
|
||||
});
|
||||
|
||||
+100
-115
@@ -10,6 +10,9 @@ let useEffect;
|
||||
let useMemo;
|
||||
let useRef;
|
||||
let startTransition;
|
||||
let waitForPaint;
|
||||
let waitFor;
|
||||
let assertLog;
|
||||
|
||||
describe('ReactOffscreen', () => {
|
||||
beforeEach(() => {
|
||||
@@ -27,6 +30,11 @@ describe('ReactOffscreen', () => {
|
||||
useMemo = React.useMemo;
|
||||
useRef = React.useRef;
|
||||
startTransition = React.startTransition;
|
||||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
waitForPaint = InternalTestUtils.waitForPaint;
|
||||
waitFor = InternalTestUtils.waitFor;
|
||||
assertLog = InternalTestUtils.assertLog;
|
||||
});
|
||||
|
||||
function Text(props) {
|
||||
@@ -68,10 +76,10 @@ describe('ReactOffscreen', () => {
|
||||
const root = ReactNoop.createRoot();
|
||||
await act(async () => {
|
||||
root.render(<App mode="unstable-defer-without-hiding" />);
|
||||
expect(Scheduler).toFlushUntilNextPaint(['Normal']);
|
||||
await waitForPaint(['Normal']);
|
||||
expect(root).toMatchRenderedOutput(<span prop="Normal" />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Deferred']);
|
||||
assertLog(['Deferred']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Normal" />
|
||||
@@ -83,7 +91,7 @@ describe('ReactOffscreen', () => {
|
||||
await act(async () => {
|
||||
root.render(<App mode="visible" />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Normal', 'Deferred']);
|
||||
assertLog(['Normal', 'Deferred']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Normal" />
|
||||
@@ -93,7 +101,7 @@ describe('ReactOffscreen', () => {
|
||||
|
||||
await act(async () => {
|
||||
root.render(<App mode="unstable-defer-without-hiding" />);
|
||||
expect(Scheduler).toFlushUntilNextPaint(['Normal']);
|
||||
await waitForPaint(['Normal']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Normal" />
|
||||
@@ -101,7 +109,7 @@ describe('ReactOffscreen', () => {
|
||||
</>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Deferred']);
|
||||
assertLog(['Deferred']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Normal" />
|
||||
@@ -133,7 +141,7 @@ describe('ReactOffscreen', () => {
|
||||
ReactNoop.flushSync();
|
||||
|
||||
// Should not defer the hidden tree
|
||||
expect(Scheduler).toHaveYielded(['A', 'Outside']);
|
||||
assertLog(['A', 'Outside']);
|
||||
});
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
@@ -146,7 +154,7 @@ describe('ReactOffscreen', () => {
|
||||
await act(async () => {
|
||||
setState('B');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['B']);
|
||||
assertLog(['B']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="B" />
|
||||
@@ -175,11 +183,11 @@ describe('ReactOffscreen', () => {
|
||||
</>,
|
||||
);
|
||||
// Should defer the hidden tree.
|
||||
expect(Scheduler).toFlushUntilNextPaint(['Outside']);
|
||||
await waitForPaint(['Outside']);
|
||||
});
|
||||
|
||||
// The hidden tree was rendered at lower priority.
|
||||
expect(Scheduler).toHaveYielded(['A']);
|
||||
assertLog(['A']);
|
||||
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
@@ -192,7 +200,7 @@ describe('ReactOffscreen', () => {
|
||||
await act(async () => {
|
||||
setState('B');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['B']);
|
||||
assertLog(['B']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="B" />
|
||||
@@ -224,7 +232,7 @@ describe('ReactOffscreen', () => {
|
||||
);
|
||||
});
|
||||
// No layout effect.
|
||||
expect(Scheduler).toHaveYielded(['Child']);
|
||||
assertLog(['Child']);
|
||||
expect(root).toMatchRenderedOutput(<span hidden={true} prop="Child" />);
|
||||
|
||||
// Unhide the tree. The layout effect is mounted.
|
||||
@@ -235,7 +243,7 @@ describe('ReactOffscreen', () => {
|
||||
</Offscreen>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Child', 'Mount layout']);
|
||||
assertLog(['Child', 'Mount layout']);
|
||||
expect(root).toMatchRenderedOutput(<span prop="Child" />);
|
||||
});
|
||||
|
||||
@@ -259,7 +267,7 @@ describe('ReactOffscreen', () => {
|
||||
</Offscreen>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Child', 'Mount layout']);
|
||||
assertLog(['Child', 'Mount layout']);
|
||||
expect(root).toMatchRenderedOutput(<span prop="Child" />);
|
||||
|
||||
// Hide the tree. The layout effect is unmounted.
|
||||
@@ -270,7 +278,7 @@ describe('ReactOffscreen', () => {
|
||||
</Offscreen>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Unmount layout', 'Child']);
|
||||
assertLog(['Unmount layout', 'Child']);
|
||||
expect(root).toMatchRenderedOutput(<span hidden={true} prop="Child" />);
|
||||
|
||||
// Unhide the tree. The layout effect is re-mounted.
|
||||
@@ -281,7 +289,7 @@ describe('ReactOffscreen', () => {
|
||||
</Offscreen>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Child', 'Mount layout']);
|
||||
assertLog(['Child', 'Mount layout']);
|
||||
expect(root).toMatchRenderedOutput(<span prop="Child" />);
|
||||
});
|
||||
|
||||
@@ -316,7 +324,7 @@ describe('ReactOffscreen', () => {
|
||||
);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded(['child']);
|
||||
assertLog(['child']);
|
||||
expect(root).toMatchRenderedOutput(<span hidden={true} prop="child" />);
|
||||
|
||||
await act(async () => {
|
||||
@@ -330,7 +338,7 @@ describe('ReactOffscreen', () => {
|
||||
);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded(['child']);
|
||||
assertLog(['child']);
|
||||
expect(root).toMatchRenderedOutput(<span hidden={true} prop="child" />);
|
||||
|
||||
await act(async () => {
|
||||
@@ -344,7 +352,7 @@ describe('ReactOffscreen', () => {
|
||||
);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded(['child']);
|
||||
assertLog(['child']);
|
||||
expect(root).toMatchRenderedOutput(<span hidden={true} prop="child" />);
|
||||
|
||||
await act(async () => {
|
||||
@@ -372,7 +380,7 @@ describe('ReactOffscreen', () => {
|
||||
);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded(['child']);
|
||||
assertLog(['child']);
|
||||
|
||||
await act(async () => {
|
||||
// Outer offscreen is hidden.
|
||||
@@ -386,7 +394,7 @@ describe('ReactOffscreen', () => {
|
||||
);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded(['child']);
|
||||
assertLog(['child']);
|
||||
});
|
||||
|
||||
// @gate enableOffscreen
|
||||
@@ -410,7 +418,7 @@ describe('ReactOffscreen', () => {
|
||||
</Offscreen>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Child']);
|
||||
assertLog(['Child']);
|
||||
expect(root).toMatchRenderedOutput(<span hidden={true} prop="Child" />);
|
||||
|
||||
// Show the tree. The layout effect is mounted.
|
||||
@@ -421,7 +429,7 @@ describe('ReactOffscreen', () => {
|
||||
</Offscreen>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Child', 'Mount layout']);
|
||||
assertLog(['Child', 'Mount layout']);
|
||||
expect(root).toMatchRenderedOutput(<span prop="Child" />);
|
||||
|
||||
// Hide the tree again. The layout effect is un-mounted.
|
||||
@@ -432,7 +440,7 @@ describe('ReactOffscreen', () => {
|
||||
</Offscreen>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Unmount layout', 'Child']);
|
||||
assertLog(['Unmount layout', 'Child']);
|
||||
expect(root).toMatchRenderedOutput(<span hidden={true} prop="Child" />);
|
||||
});
|
||||
|
||||
@@ -458,7 +466,7 @@ describe('ReactOffscreen', () => {
|
||||
</Offscreen>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Child', 'Mount layout']);
|
||||
assertLog(['Child', 'Mount layout']);
|
||||
expect(root).toMatchRenderedOutput(<span prop="Child" />);
|
||||
|
||||
// Hide the tree. The layout effect is unmounted.
|
||||
@@ -469,7 +477,7 @@ describe('ReactOffscreen', () => {
|
||||
</Offscreen>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Unmount layout', 'Child']);
|
||||
assertLog(['Unmount layout', 'Child']);
|
||||
|
||||
// After the layout effect is unmounted, the child is hidden.
|
||||
expect(root).toMatchRenderedOutput(<span hidden={true} prop="Child" />);
|
||||
@@ -497,7 +505,7 @@ describe('ReactOffscreen', () => {
|
||||
</LegacyHidden>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Child', 'Mount layout']);
|
||||
assertLog(['Child', 'Mount layout']);
|
||||
|
||||
await act(async () => {
|
||||
root.render(
|
||||
@@ -506,7 +514,7 @@ describe('ReactOffscreen', () => {
|
||||
</LegacyHidden>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Child']);
|
||||
assertLog(['Child']);
|
||||
|
||||
await act(async () => {
|
||||
root.render(
|
||||
@@ -515,12 +523,12 @@ describe('ReactOffscreen', () => {
|
||||
</LegacyHidden>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Child']);
|
||||
assertLog(['Child']);
|
||||
|
||||
await act(async () => {
|
||||
root.render(null);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Unmount layout']);
|
||||
assertLog(['Unmount layout']);
|
||||
});
|
||||
|
||||
// @gate enableOffscreen
|
||||
@@ -665,7 +673,7 @@ describe('ReactOffscreen', () => {
|
||||
await act(async () => {
|
||||
root.render(<App show={false} />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Outer: 0', 'Inner: 0']);
|
||||
assertLog(['Outer: 0', 'Inner: 0']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Outer: 0" />
|
||||
@@ -681,7 +689,7 @@ describe('ReactOffscreen', () => {
|
||||
setInner(1);
|
||||
// Only the outer updates finishes because the inner update is inside a
|
||||
// hidden tree. The outer update is deferred to a later render.
|
||||
expect(Scheduler).toFlushUntilNextPaint(['Outer: 1']);
|
||||
await waitForPaint(['Outer: 1']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Outer: 1" />
|
||||
@@ -706,7 +714,7 @@ describe('ReactOffscreen', () => {
|
||||
root.render(<App show={true} />);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Outer: 1',
|
||||
|
||||
// There are two pending updates on Inner, but only the first one
|
||||
@@ -723,7 +731,7 @@ describe('ReactOffscreen', () => {
|
||||
);
|
||||
expect(areOuterAndInnerConsistent()).toBe(true);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Outer: 2', 'Inner: 2']);
|
||||
assertLog(['Outer: 2', 'Inner: 2']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Outer: 2" />
|
||||
@@ -746,7 +754,7 @@ describe('ReactOffscreen', () => {
|
||||
await act(async () => {
|
||||
root.render(<Offscreen hidden={false} />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
assertLog([]);
|
||||
expect(root).toMatchRenderedOutput(null);
|
||||
|
||||
await act(async () => {
|
||||
@@ -759,7 +767,7 @@ describe('ReactOffscreen', () => {
|
||||
</Offscreen>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toFlushAndYieldThrough(['Initial']);
|
||||
await waitFor(['Initial']);
|
||||
|
||||
// Before it finishes rendering, the whole tree gets deleted
|
||||
ReactNoop.flushSync(() => {
|
||||
@@ -802,7 +810,7 @@ describe('ReactOffscreen', () => {
|
||||
</Offscreen>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['A']);
|
||||
assertLog(['A']);
|
||||
expect(root).toMatchRenderedOutput(<span hidden={true} prop="A" />);
|
||||
|
||||
// Schedule an update to a hidden class component. The update will finish
|
||||
@@ -813,7 +821,7 @@ describe('ReactOffscreen', () => {
|
||||
Scheduler.unstable_yieldValue('B update finished');
|
||||
});
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['B']);
|
||||
assertLog(['B']);
|
||||
expect(root).toMatchRenderedOutput(<span hidden={true} prop="B" />);
|
||||
|
||||
// Now reveal the hidden component. Simultaneously, schedule another
|
||||
@@ -829,11 +837,7 @@ describe('ReactOffscreen', () => {
|
||||
Scheduler.unstable_yieldValue('C update finished');
|
||||
});
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'C',
|
||||
'B update finished',
|
||||
'C update finished',
|
||||
]);
|
||||
assertLog(['C', 'B update finished', 'C update finished']);
|
||||
expect(root).toMatchRenderedOutput(<span prop="C" />);
|
||||
});
|
||||
|
||||
@@ -863,7 +867,7 @@ describe('ReactOffscreen', () => {
|
||||
</Offscreen>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['componentDidMount']);
|
||||
assertLog(['componentDidMount']);
|
||||
|
||||
// Hide the class component
|
||||
await act(async () => {
|
||||
@@ -873,7 +877,7 @@ describe('ReactOffscreen', () => {
|
||||
</Offscreen>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['componentWillUnmount']);
|
||||
assertLog(['componentWillUnmount']);
|
||||
|
||||
// Reappear the class component. componentDidMount should fire, not
|
||||
// componentDidUpdate.
|
||||
@@ -884,7 +888,7 @@ describe('ReactOffscreen', () => {
|
||||
</Offscreen>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['componentDidMount']);
|
||||
assertLog(['componentDidMount']);
|
||||
});
|
||||
|
||||
// @gate enableOffscreen
|
||||
@@ -911,7 +915,7 @@ describe('ReactOffscreen', () => {
|
||||
</Offscreen>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Mount B']);
|
||||
assertLog(['Mount B']);
|
||||
|
||||
// Hide the component
|
||||
await act(async () => {
|
||||
@@ -921,7 +925,7 @@ describe('ReactOffscreen', () => {
|
||||
</Offscreen>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Unmount B']);
|
||||
assertLog(['Unmount B']);
|
||||
|
||||
// Reappear the component and also add some new siblings.
|
||||
await act(async () => {
|
||||
@@ -936,7 +940,7 @@ describe('ReactOffscreen', () => {
|
||||
// B's effect should fire in between A and C even though it's been reused
|
||||
// from a previous render. In other words, it's the same order as if all
|
||||
// three siblings were brand new.
|
||||
expect(Scheduler).toHaveYielded(['Mount A', 'Mount B', 'Mount C']);
|
||||
assertLog(['Mount A', 'Mount B', 'Mount C']);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -967,7 +971,7 @@ describe('ReactOffscreen', () => {
|
||||
</Offscreen>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Mount B']);
|
||||
assertLog(['Mount B']);
|
||||
|
||||
// We're going to schedule an update on a hidden component, so stash a
|
||||
// reference to its setState before the ref gets detached
|
||||
@@ -981,7 +985,7 @@ describe('ReactOffscreen', () => {
|
||||
</Offscreen>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Unmount B']);
|
||||
assertLog(['Unmount B']);
|
||||
|
||||
// Reappear the component and also add some new siblings.
|
||||
await act(async () => {
|
||||
@@ -999,12 +1003,7 @@ describe('ReactOffscreen', () => {
|
||||
// B's effect should fire in between A and C even though it's been reused
|
||||
// from a previous render. In other words, it's the same order as if all
|
||||
// three siblings were brand new.
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Mount A',
|
||||
'Mount B',
|
||||
'setState callback B',
|
||||
'Mount C',
|
||||
]);
|
||||
assertLog(['Mount A', 'Mount B', 'setState callback B', 'Mount C']);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1037,7 +1036,7 @@ describe('ReactOffscreen', () => {
|
||||
await act(async () => {
|
||||
root.render(<App showMore={false} />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
// First mount the outer visible shell
|
||||
'Shell',
|
||||
'Mount Shell',
|
||||
@@ -1060,7 +1059,7 @@ describe('ReactOffscreen', () => {
|
||||
await act(async () => {
|
||||
root.render(<App showMore={true} />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Shell',
|
||||
'More',
|
||||
|
||||
@@ -1100,7 +1099,7 @@ describe('ReactOffscreen', () => {
|
||||
await act(async () => {
|
||||
root.render(<App showMore={false} />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
// First mount the outer visible shell
|
||||
'Shell',
|
||||
'Mount Shell',
|
||||
@@ -1123,7 +1122,7 @@ describe('ReactOffscreen', () => {
|
||||
await act(async () => {
|
||||
root.render(<App showMore={true} />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Shell', 'More']);
|
||||
assertLog(['Shell', 'More']);
|
||||
});
|
||||
|
||||
// @gate enableOffscreen
|
||||
@@ -1155,14 +1154,14 @@ describe('ReactOffscreen', () => {
|
||||
await act(async () => {
|
||||
root.render(<App show={true} step={1} />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([1, 'Commit mount [1]']);
|
||||
assertLog([1, 'Commit mount [1]']);
|
||||
expect(root).toMatchRenderedOutput(<span prop={1} />);
|
||||
|
||||
// Hide the tree. This will unmount the effect.
|
||||
await act(async () => {
|
||||
root.render(<App show={false} step={1} />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Commit unmount [1]']);
|
||||
assertLog(['Commit unmount [1]']);
|
||||
expect(root).toMatchRenderedOutput(<span hidden={true} prop={1} />);
|
||||
|
||||
// Update.
|
||||
@@ -1170,7 +1169,7 @@ describe('ReactOffscreen', () => {
|
||||
root.render(<App show={false} step={2} />);
|
||||
});
|
||||
// The update is prerendered but no effects are fired
|
||||
expect(Scheduler).toHaveYielded([2]);
|
||||
assertLog([2]);
|
||||
expect(root).toMatchRenderedOutput(<span hidden={true} prop={2} />);
|
||||
|
||||
// Reveal the tree.
|
||||
@@ -1179,7 +1178,7 @@ describe('ReactOffscreen', () => {
|
||||
});
|
||||
// The update doesn't render because it was already prerendered, but we do
|
||||
// fire the effect.
|
||||
expect(Scheduler).toHaveYielded(['Commit mount [2]']);
|
||||
assertLog(['Commit mount [2]']);
|
||||
expect(root).toMatchRenderedOutput(<span prop={2} />);
|
||||
});
|
||||
|
||||
@@ -1216,24 +1215,24 @@ describe('ReactOffscreen', () => {
|
||||
await act(async () => {
|
||||
root.render(<App show={true} />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Mount Child', 'Mount Parent']);
|
||||
assertLog(['Mount Child', 'Mount Parent']);
|
||||
|
||||
// First demonstrate what happens during a normal deletion
|
||||
await act(async () => {
|
||||
root.render(null);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Unmount Parent', 'Unmount Child']);
|
||||
assertLog(['Unmount Parent', 'Unmount Child']);
|
||||
|
||||
// Now redo the same thing but hide instead of deleting
|
||||
await act(async () => {
|
||||
root.render(<App show={true} />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Mount Child', 'Mount Parent']);
|
||||
assertLog(['Mount Child', 'Mount Parent']);
|
||||
await act(async () => {
|
||||
root.render(<App show={false} />);
|
||||
});
|
||||
// The order is the same as during a deletion: parent before child
|
||||
expect(Scheduler).toHaveYielded(['Unmount Parent', 'Unmount Child']);
|
||||
assertLog(['Unmount Parent', 'Unmount Child']);
|
||||
});
|
||||
|
||||
// TODO: As of now, there's no way to hide a tree without also unmounting its
|
||||
@@ -1270,12 +1269,7 @@ describe('ReactOffscreen', () => {
|
||||
await act(async () => {
|
||||
root.render(<App showMore={true} step={1} />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Shell 1',
|
||||
'More 1',
|
||||
'Mount Shell 1',
|
||||
'Mount More 1',
|
||||
]);
|
||||
assertLog(['Shell 1', 'More 1', 'Mount Shell 1', 'Mount More 1']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Shell 1" />
|
||||
@@ -1287,7 +1281,7 @@ describe('ReactOffscreen', () => {
|
||||
await act(async () => {
|
||||
root.render(<App showMore={false} step={2} />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
// First update the outer visible shell
|
||||
'Shell 2',
|
||||
'Unmount Shell 1',
|
||||
@@ -1342,7 +1336,7 @@ describe('ReactOffscreen', () => {
|
||||
await act(async () => {
|
||||
root.render(<App showOuter={false} showInner={false} />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Outer']);
|
||||
assertLog(['Outer']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<div hidden={true}>
|
||||
<span prop="Outer" />
|
||||
@@ -1353,7 +1347,7 @@ describe('ReactOffscreen', () => {
|
||||
await act(async () => {
|
||||
root.render(<App showOuter={false} showInner={true} />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Outer', 'Inner']);
|
||||
assertLog(['Outer', 'Inner']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<div hidden={true}>
|
||||
<span prop="Outer" />
|
||||
@@ -1369,7 +1363,7 @@ describe('ReactOffscreen', () => {
|
||||
});
|
||||
// The effects fire, but the tree is not re-rendered because it already
|
||||
// prerendered.
|
||||
expect(Scheduler).toHaveYielded(['Mount Outer', 'Mount Inner']);
|
||||
assertLog(['Mount Outer', 'Mount Inner']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<div>
|
||||
<span prop="Outer" />
|
||||
@@ -1418,7 +1412,7 @@ describe('ReactOffscreen', () => {
|
||||
await act(async () => {
|
||||
root.render(<App showOuter={false} showInner={false} />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Outer', 'Inner']);
|
||||
assertLog(['Outer', 'Inner']);
|
||||
// Both the inner and the outer tree should be hidden. Hiding the inner tree
|
||||
// is arguably redundant, but the advantage of hiding both is that later you
|
||||
// can reveal the outer tree without having to examine the inner one.
|
||||
@@ -1435,7 +1429,7 @@ describe('ReactOffscreen', () => {
|
||||
await act(async () => {
|
||||
root.render(<App showOuter={true} showInner={false} />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Mount Outer']);
|
||||
assertLog(['Mount Outer']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<div>
|
||||
<span prop="Outer" />
|
||||
@@ -1535,7 +1529,7 @@ describe('ReactOffscreen', () => {
|
||||
root.render(<App />);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded(['HighPriorityComponent 0', 'Child 0']);
|
||||
assertLog(['HighPriorityComponent 0', 'Child 0']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="HighPriorityComponent 0" />
|
||||
@@ -1549,10 +1543,7 @@ describe('ReactOffscreen', () => {
|
||||
await act(async () => {
|
||||
updateChildState(1);
|
||||
updateHighPriorityComponentState(1);
|
||||
expect(Scheduler).toFlushUntilNextPaint([
|
||||
'HighPriorityComponent 1',
|
||||
'Child 1',
|
||||
]);
|
||||
await waitForPaint(['HighPriorityComponent 1', 'Child 1']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="HighPriorityComponent 1" />
|
||||
@@ -1569,7 +1560,7 @@ describe('ReactOffscreen', () => {
|
||||
await act(async () => {
|
||||
updateChildState(2);
|
||||
updateHighPriorityComponentState(2);
|
||||
expect(Scheduler).toFlushUntilNextPaint(['HighPriorityComponent 2']);
|
||||
await waitForPaint(['HighPriorityComponent 2']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="HighPriorityComponent 2" />
|
||||
@@ -1578,7 +1569,7 @@ describe('ReactOffscreen', () => {
|
||||
);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Child 2']);
|
||||
assertLog(['Child 2']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="HighPriorityComponent 2" />
|
||||
@@ -1594,10 +1585,7 @@ describe('ReactOffscreen', () => {
|
||||
await act(async () => {
|
||||
updateChildState(3);
|
||||
updateHighPriorityComponentState(3);
|
||||
expect(Scheduler).toFlushUntilNextPaint([
|
||||
'HighPriorityComponent 3',
|
||||
'Child 3',
|
||||
]);
|
||||
await waitForPaint(['HighPriorityComponent 3', 'Child 3']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="HighPriorityComponent 3" />
|
||||
@@ -1666,7 +1654,7 @@ describe('ReactOffscreen', () => {
|
||||
root.render(<App />);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded(['HighPriorityComponent 0', 'Child 0']);
|
||||
assertLog(['HighPriorityComponent 0', 'Child 0']);
|
||||
|
||||
nextRenderTriggerDetach = true;
|
||||
|
||||
@@ -1675,7 +1663,7 @@ describe('ReactOffscreen', () => {
|
||||
await act(async () => {
|
||||
updateChildState(1);
|
||||
updateHighPriorityComponentState(1);
|
||||
expect(Scheduler).toFlushUntilNextPaint([
|
||||
await waitForPaint([
|
||||
'HighPriorityComponent 1',
|
||||
'Child 1',
|
||||
'HighPriorityComponent 2',
|
||||
@@ -1688,7 +1676,7 @@ describe('ReactOffscreen', () => {
|
||||
);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Child 2']);
|
||||
assertLog(['Child 2']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="HighPriorityComponent 2" />
|
||||
@@ -1703,10 +1691,7 @@ describe('ReactOffscreen', () => {
|
||||
await act(async () => {
|
||||
updateChildState(3);
|
||||
updateHighPriorityComponentState(3);
|
||||
expect(Scheduler).toFlushUntilNextPaint([
|
||||
'HighPriorityComponent 3',
|
||||
'Child 3',
|
||||
]);
|
||||
await waitForPaint(['HighPriorityComponent 3', 'Child 3']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="HighPriorityComponent 3" />
|
||||
@@ -1865,33 +1850,33 @@ describe('ReactOffscreen', () => {
|
||||
|
||||
expect(offscreenRef).not.toBeNull();
|
||||
expect(spanRef.current).not.toBeNull();
|
||||
expect(Scheduler).toHaveYielded(['Mount Layout Child', 'Mount Child']);
|
||||
assertLog(['Mount Layout Child', 'Mount Child']);
|
||||
|
||||
await act(async () => {
|
||||
offscreenRef.detach();
|
||||
});
|
||||
|
||||
expect(spanRef.current).toBeNull();
|
||||
expect(Scheduler).toHaveYielded(['Unmount Layout Child', 'Unmount Child']);
|
||||
assertLog(['Unmount Layout Child', 'Unmount Child']);
|
||||
|
||||
// Calling attach on already attached Offscreen.
|
||||
await act(async () => {
|
||||
offscreenRef.detach();
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
assertLog([]);
|
||||
|
||||
await act(async () => {
|
||||
offscreenRef.attach();
|
||||
});
|
||||
|
||||
expect(spanRef.current).not.toBeNull();
|
||||
expect(Scheduler).toHaveYielded(['Mount Layout Child', 'Mount Child']);
|
||||
assertLog(['Mount Layout Child', 'Mount Child']);
|
||||
|
||||
// Calling attach on already attached Offscreen
|
||||
offscreenRef.attach();
|
||||
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
assertLog([]);
|
||||
});
|
||||
|
||||
// @gate enableOffscreen
|
||||
@@ -1919,7 +1904,7 @@ describe('ReactOffscreen', () => {
|
||||
root.render(<App />);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'outer',
|
||||
'middle',
|
||||
'inner',
|
||||
@@ -1940,7 +1925,7 @@ describe('ReactOffscreen', () => {
|
||||
|
||||
expect(innerOffscreen).toBeNull();
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'unmount layout middle',
|
||||
'unmount layout inner',
|
||||
'unmount middle',
|
||||
@@ -1951,7 +1936,7 @@ describe('ReactOffscreen', () => {
|
||||
outerOffscreen.attach();
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'mount layout inner',
|
||||
'mount layout middle',
|
||||
'mount inner',
|
||||
@@ -1962,27 +1947,27 @@ describe('ReactOffscreen', () => {
|
||||
innerOffscreen.detach();
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded(['unmount layout inner', 'unmount inner']);
|
||||
assertLog(['unmount layout inner', 'unmount inner']);
|
||||
|
||||
// Calling detach on already detached Offscreen.
|
||||
await act(async () => {
|
||||
innerOffscreen.detach();
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
assertLog([]);
|
||||
|
||||
await act(async () => {
|
||||
innerOffscreen.attach();
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded(['mount layout inner', 'mount inner']);
|
||||
assertLog(['mount layout inner', 'mount inner']);
|
||||
|
||||
await act(async () => {
|
||||
innerOffscreen.detach();
|
||||
outerOffscreen.attach();
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded(['unmount layout inner', 'unmount inner']);
|
||||
assertLog(['unmount layout inner', 'unmount inner']);
|
||||
});
|
||||
|
||||
// @gate enableOffscreen
|
||||
@@ -2011,7 +1996,7 @@ describe('ReactOffscreen', () => {
|
||||
root.render(<App />);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded(['attach child']);
|
||||
assertLog(['attach child']);
|
||||
|
||||
await act(async () => {
|
||||
const instance = offscreen.current;
|
||||
@@ -2020,14 +2005,14 @@ describe('ReactOffscreen', () => {
|
||||
instance.attach();
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
assertLog([]);
|
||||
|
||||
await act(async () => {
|
||||
const instance = offscreen.current;
|
||||
instance.detach();
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded(['detach child']);
|
||||
assertLog(['detach child']);
|
||||
|
||||
await act(async () => {
|
||||
const instance = offscreen.current;
|
||||
@@ -2036,7 +2021,7 @@ describe('ReactOffscreen', () => {
|
||||
instance.detach();
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
assertLog([]);
|
||||
});
|
||||
|
||||
// @gate enableOffscreen
|
||||
@@ -2070,6 +2055,6 @@ describe('ReactOffscreen', () => {
|
||||
await act(() => {
|
||||
root.render(<App />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['attach child']);
|
||||
assertLog(['attach child']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,6 +9,8 @@ let useState;
|
||||
let useEffect;
|
||||
let startTransition;
|
||||
let textCache;
|
||||
let waitForPaint;
|
||||
let assertLog;
|
||||
|
||||
describe('ReactOffscreen', () => {
|
||||
beforeEach(() => {
|
||||
@@ -25,6 +27,10 @@ describe('ReactOffscreen', () => {
|
||||
useEffect = React.useEffect;
|
||||
startTransition = React.startTransition;
|
||||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
waitForPaint = InternalTestUtils.waitForPaint;
|
||||
assertLog = InternalTestUtils.assertLog;
|
||||
|
||||
textCache = new Map();
|
||||
});
|
||||
|
||||
@@ -115,7 +121,7 @@ describe('ReactOffscreen', () => {
|
||||
await act(async () => {
|
||||
root.render(<App />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Visible', 'Suspend! [Hidden]']);
|
||||
assertLog(['Visible', 'Suspend! [Hidden]']);
|
||||
expect(root).toMatchRenderedOutput(<span>Visible</span>);
|
||||
|
||||
// When the data resolves, we should be able to finish prerendering
|
||||
@@ -123,7 +129,7 @@ describe('ReactOffscreen', () => {
|
||||
await act(async () => {
|
||||
await resolveText('Hidden');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Hidden']);
|
||||
assertLog(['Hidden']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span>Visible</span>
|
||||
@@ -155,11 +161,7 @@ describe('ReactOffscreen', () => {
|
||||
await act(async () => {
|
||||
root.render(<App />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Visible',
|
||||
'Suspend! [Hidden]',
|
||||
'Loading...',
|
||||
]);
|
||||
assertLog(['Visible', 'Suspend! [Hidden]', 'Loading...']);
|
||||
// Nearest Suspense boundary switches to a fallback even though the
|
||||
// suspended content is hidden.
|
||||
expect(root).toMatchRenderedOutput(
|
||||
@@ -197,7 +199,7 @@ describe('ReactOffscreen', () => {
|
||||
</Details>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Closed', 'Suspend! [Async]']);
|
||||
assertLog(['Closed', 'Suspend! [Async]']);
|
||||
expect(root).toMatchRenderedOutput(<span>Closed</span>);
|
||||
|
||||
// But when we switch the boundary from hidden to visible, it should
|
||||
@@ -211,7 +213,7 @@ describe('ReactOffscreen', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Open', 'Suspend! [Async]', 'Loading...']);
|
||||
assertLog(['Open', 'Suspend! [Async]', 'Loading...']);
|
||||
// It should suspend with delay to prevent the already-visible Suspense
|
||||
// boundary from switching to a fallback
|
||||
expect(root).toMatchRenderedOutput(<span>Closed</span>);
|
||||
@@ -220,7 +222,7 @@ describe('ReactOffscreen', () => {
|
||||
await act(async () => {
|
||||
await resolveText('Async');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Open', 'Async']);
|
||||
assertLog(['Open', 'Async']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span>Open</span>
|
||||
@@ -254,7 +256,7 @@ describe('ReactOffscreen', () => {
|
||||
</Details>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Open', '(empty)']);
|
||||
assertLog(['Open', '(empty)']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span>Open</span>
|
||||
@@ -272,7 +274,7 @@ describe('ReactOffscreen', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Open', 'Suspend! [Async]', 'Loading...']);
|
||||
assertLog(['Open', 'Suspend! [Async]', 'Loading...']);
|
||||
// It should suspend with delay to prevent the already-visible Suspense
|
||||
// boundary from switching to a fallback
|
||||
expect(root).toMatchRenderedOutput(
|
||||
@@ -294,7 +296,7 @@ describe('ReactOffscreen', () => {
|
||||
});
|
||||
// Now the visible part of the tree can commit without being blocked
|
||||
// by the suspended content, which is hidden.
|
||||
expect(Scheduler).toHaveYielded(['Closed', 'Suspend! [Async]']);
|
||||
assertLog(['Closed', 'Suspend! [Async]']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span>Closed</span>
|
||||
@@ -306,7 +308,7 @@ describe('ReactOffscreen', () => {
|
||||
await act(async () => {
|
||||
await resolveText('Async');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Async']);
|
||||
assertLog(['Async']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
<span>Closed</span>
|
||||
@@ -339,7 +341,7 @@ describe('ReactOffscreen', () => {
|
||||
await act(async () => {
|
||||
root.render(<App show={false} />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['A']);
|
||||
assertLog(['A']);
|
||||
|
||||
await act(async () => {
|
||||
startTransition(() => {
|
||||
@@ -377,7 +379,7 @@ describe('ReactOffscreen', () => {
|
||||
await act(async () => {
|
||||
root.render(<App show={false} />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['A0']);
|
||||
assertLog(['A0']);
|
||||
expect(root).toMatchRenderedOutput(<span hidden={true}>A0</span>);
|
||||
|
||||
await act(async () => {
|
||||
@@ -388,7 +390,7 @@ describe('ReactOffscreen', () => {
|
||||
setText('B');
|
||||
});
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
// The high priority render suspends again
|
||||
'Suspend! [B0]',
|
||||
// There's still pending work in another lane, so we should attempt
|
||||
@@ -401,7 +403,7 @@ describe('ReactOffscreen', () => {
|
||||
await act(async () => {
|
||||
resolveText('B1');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['B1']);
|
||||
assertLog(['B1']);
|
||||
expect(root).toMatchRenderedOutput(<span hidden={true}>B1</span>);
|
||||
});
|
||||
|
||||
@@ -465,7 +467,7 @@ describe('ReactOffscreen', () => {
|
||||
await act(async () => {
|
||||
root.render(<App show={true} />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Outer: 0',
|
||||
'Inner: 0',
|
||||
'Async: 0',
|
||||
@@ -487,7 +489,7 @@ describe('ReactOffscreen', () => {
|
||||
// In the same render, also hide the offscreen tree.
|
||||
root.render(<App show={false} />);
|
||||
|
||||
expect(Scheduler).toFlushUntilNextPaint([
|
||||
await waitForPaint([
|
||||
// The outer update will commit, but the inner update is deferred until
|
||||
// a later render.
|
||||
'Outer: 1',
|
||||
@@ -535,7 +537,7 @@ describe('ReactOffscreen', () => {
|
||||
ReactNoop.flushSync(() => {
|
||||
root.render(<App show={true} />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Outer: 1',
|
||||
|
||||
// There are two pending updates on Inner, but only the first one
|
||||
@@ -549,7 +551,7 @@ describe('ReactOffscreen', () => {
|
||||
'Inner and outer are consistent',
|
||||
]);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Outer: 2',
|
||||
'Inner: 2',
|
||||
'Suspend! [Async: 2]',
|
||||
|
||||
+16
-15
@@ -12,7 +12,7 @@
|
||||
|
||||
let React;
|
||||
let ReactNoopPersistent;
|
||||
let Scheduler;
|
||||
let waitForAll;
|
||||
|
||||
describe('ReactPersistent', () => {
|
||||
beforeEach(() => {
|
||||
@@ -20,7 +20,8 @@ describe('ReactPersistent', () => {
|
||||
|
||||
React = require('react');
|
||||
ReactNoopPersistent = require('react-noop-renderer/persistent');
|
||||
Scheduler = require('scheduler');
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
waitForAll = InternalTestUtils.waitForAll;
|
||||
});
|
||||
|
||||
// Inlined from shared folder so we can run this test on a bundle.
|
||||
@@ -57,7 +58,7 @@ describe('ReactPersistent', () => {
|
||||
return ReactNoopPersistent.dangerouslyGetChildren();
|
||||
}
|
||||
|
||||
it('can update child nodes of a host instance', () => {
|
||||
it('can update child nodes of a host instance', async () => {
|
||||
function Bar(props) {
|
||||
return <span>{props.text}</span>;
|
||||
}
|
||||
@@ -72,19 +73,19 @@ describe('ReactPersistent', () => {
|
||||
}
|
||||
|
||||
render(<Foo text="Hello" />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
const originalChildren = dangerouslyGetChildren();
|
||||
expect(originalChildren).toEqual([div(span())]);
|
||||
|
||||
render(<Foo text="World" />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
const newChildren = dangerouslyGetChildren();
|
||||
expect(newChildren).toEqual([div(span(), span())]);
|
||||
|
||||
expect(originalChildren).toEqual([div(span())]);
|
||||
});
|
||||
|
||||
it('can reuse child nodes between updates', () => {
|
||||
it('can reuse child nodes between updates', async () => {
|
||||
function Baz(props) {
|
||||
return <span prop={props.text} />;
|
||||
}
|
||||
@@ -106,12 +107,12 @@ describe('ReactPersistent', () => {
|
||||
}
|
||||
|
||||
render(<Foo text="Hello" />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
const originalChildren = dangerouslyGetChildren();
|
||||
expect(originalChildren).toEqual([div(span('Hello'))]);
|
||||
|
||||
render(<Foo text="World" />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
const newChildren = dangerouslyGetChildren();
|
||||
expect(newChildren).toEqual([div(span('Hello'), span('World'))]);
|
||||
|
||||
@@ -121,7 +122,7 @@ describe('ReactPersistent', () => {
|
||||
expect(newChildren[0].children[0]).toBe(originalChildren[0].children[0]);
|
||||
});
|
||||
|
||||
it('can update child text nodes', () => {
|
||||
it('can update child text nodes', async () => {
|
||||
function Foo(props) {
|
||||
return (
|
||||
<div>
|
||||
@@ -132,19 +133,19 @@ describe('ReactPersistent', () => {
|
||||
}
|
||||
|
||||
render(<Foo text="Hello" />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
const originalChildren = dangerouslyGetChildren();
|
||||
expect(originalChildren).toEqual([div('Hello', span())]);
|
||||
|
||||
render(<Foo text="World" />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
const newChildren = dangerouslyGetChildren();
|
||||
expect(newChildren).toEqual([div('World', span())]);
|
||||
|
||||
expect(originalChildren).toEqual([div('Hello', span())]);
|
||||
});
|
||||
|
||||
it('supports portals', () => {
|
||||
it('supports portals', async () => {
|
||||
function Parent(props) {
|
||||
return <div>{props.children}</div>;
|
||||
}
|
||||
@@ -173,7 +174,7 @@ describe('ReactPersistent', () => {
|
||||
const portalContainer = {rootID: 'persistent-portal-test', children: []};
|
||||
const emptyPortalChildSet = portalContainer.children;
|
||||
render(<Parent>{createPortal(<Child />, portalContainer, null)}</Parent>);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
|
||||
expect(emptyPortalChildSet).toEqual([]);
|
||||
|
||||
@@ -187,7 +188,7 @@ describe('ReactPersistent', () => {
|
||||
{createPortal(<Child>Hello {'World'}</Child>, portalContainer, null)}
|
||||
</Parent>,
|
||||
);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
|
||||
const newChildren = dangerouslyGetChildren();
|
||||
expect(newChildren).toEqual([div()]);
|
||||
@@ -204,7 +205,7 @@ describe('ReactPersistent', () => {
|
||||
|
||||
// Deleting the Portal, should clear its children
|
||||
render(<Parent />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
|
||||
const clearedPortalChildren = portalContainer.children;
|
||||
expect(clearedPortalChildren).toEqual([]);
|
||||
|
||||
+29
-13
@@ -18,6 +18,10 @@ let NormalPriority;
|
||||
let IdlePriority;
|
||||
let runWithPriority;
|
||||
let startTransition;
|
||||
let waitForAll;
|
||||
let waitForPaint;
|
||||
let assertLog;
|
||||
let waitFor;
|
||||
|
||||
describe('ReactSchedulerIntegration', () => {
|
||||
beforeEach(() => {
|
||||
@@ -31,6 +35,12 @@ describe('ReactSchedulerIntegration', () => {
|
||||
IdlePriority = Scheduler.unstable_IdlePriority;
|
||||
runWithPriority = Scheduler.unstable_runWithPriority;
|
||||
startTransition = React.startTransition;
|
||||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
waitForAll = InternalTestUtils.waitForAll;
|
||||
waitForPaint = InternalTestUtils.waitForPaint;
|
||||
assertLog = InternalTestUtils.assertLog;
|
||||
waitFor = InternalTestUtils.waitFor;
|
||||
});
|
||||
|
||||
// Note: This is based on a similar component we use in www. We can delete
|
||||
@@ -76,11 +86,11 @@ describe('ReactSchedulerIntegration', () => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(<CleanupEffect />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
assertLog([]);
|
||||
await act(async () => {
|
||||
ReactNoop.render(<Effects />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Cleanup Layout Effect',
|
||||
'Layout Effect',
|
||||
'Passive Effect',
|
||||
@@ -90,12 +100,12 @@ describe('ReactSchedulerIntegration', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('requests a paint after committing', () => {
|
||||
it('requests a paint after committing', async () => {
|
||||
const scheduleCallback = Scheduler.unstable_scheduleCallback;
|
||||
|
||||
const root = ReactNoop.createRoot();
|
||||
root.render('Initial');
|
||||
Scheduler.unstable_flushAll();
|
||||
await waitForAll([]);
|
||||
|
||||
scheduleCallback(NormalPriority, () => Scheduler.unstable_yieldValue('A'));
|
||||
scheduleCallback(NormalPriority, () => Scheduler.unstable_yieldValue('B'));
|
||||
@@ -115,7 +125,7 @@ describe('ReactSchedulerIntegration', () => {
|
||||
// Flush everything up to the next paint. Should yield after the
|
||||
// React commit.
|
||||
Scheduler.unstable_flushUntilNextPaint();
|
||||
expect(Scheduler).toHaveYielded(['A', 'B', 'C']);
|
||||
assertLog(['A', 'B', 'C']);
|
||||
});
|
||||
|
||||
// @gate www
|
||||
@@ -141,7 +151,7 @@ describe('ReactSchedulerIntegration', () => {
|
||||
root.render(<App label="A" />);
|
||||
|
||||
// Commit the visible content
|
||||
expect(Scheduler).toFlushUntilNextPaint(['Visible: A']);
|
||||
await waitForPaint(['Visible: A']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
Visible: A
|
||||
@@ -156,7 +166,7 @@ describe('ReactSchedulerIntegration', () => {
|
||||
});
|
||||
|
||||
// The next commit should only include the visible content
|
||||
expect(Scheduler).toFlushUntilNextPaint(['Visible: B']);
|
||||
await waitForPaint(['Visible: B']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
Visible: B
|
||||
@@ -166,7 +176,7 @@ describe('ReactSchedulerIntegration', () => {
|
||||
});
|
||||
|
||||
// The hidden content commits later
|
||||
expect(Scheduler).toHaveYielded(['Hidden: B']);
|
||||
assertLog(['Hidden: B']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
<>
|
||||
Visible: B<div hidden={true}>Hidden: B</div>
|
||||
@@ -201,6 +211,12 @@ describe(
|
||||
ReactNoop = require('react-noop-renderer');
|
||||
Scheduler = require('scheduler');
|
||||
startTransition = React.startTransition;
|
||||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
waitForAll = InternalTestUtils.waitForAll;
|
||||
waitForPaint = InternalTestUtils.waitForPaint;
|
||||
assertLog = InternalTestUtils.assertLog;
|
||||
waitFor = InternalTestUtils.waitFor;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -244,8 +260,8 @@ describe(
|
||||
|
||||
await act(async () => {
|
||||
ReactNoop.render(<App />);
|
||||
expect(Scheduler).toFlushUntilNextPaint([]);
|
||||
expect(Scheduler).toFlushUntilNextPaint([]);
|
||||
await waitForPaint([]);
|
||||
await waitForPaint([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -276,13 +292,13 @@ describe(
|
||||
startTransition(() => {
|
||||
ReactNoop.render(<App />);
|
||||
});
|
||||
expect(Scheduler).toFlushAndYieldThrough(['A']);
|
||||
await waitFor(['A']);
|
||||
|
||||
// Start logging whenever shouldYield is called
|
||||
logDuringShouldYield = true;
|
||||
// Let's call it once to confirm the mock actually works
|
||||
Scheduler.unstable_shouldYield();
|
||||
expect(Scheduler).toHaveYielded(['shouldYield']);
|
||||
assertLog(['shouldYield']);
|
||||
|
||||
// Expire the task
|
||||
Scheduler.unstable_advanceTime(10000);
|
||||
@@ -299,7 +315,7 @@ describe(
|
||||
// Because the render expired, React should finish the tree without
|
||||
// consulting `shouldYield` again
|
||||
Scheduler.unstable_flushNumberOfYields(1);
|
||||
expect(Scheduler).toHaveYielded(['B', 'C']);
|
||||
assertLog(['B', 'C']);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
@@ -8,6 +8,7 @@ let getCacheForType;
|
||||
|
||||
let caches;
|
||||
let seededCache;
|
||||
let assertLog;
|
||||
|
||||
describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
beforeEach(() => {
|
||||
@@ -22,6 +23,9 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
|
||||
getCacheForType = React.unstable_getCacheForType;
|
||||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
assertLog = InternalTestUtils.assertLog;
|
||||
|
||||
caches = [];
|
||||
seededCache = null;
|
||||
});
|
||||
@@ -160,7 +164,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
await act(async () => {
|
||||
root.render(<App />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Suspend! [Async]']);
|
||||
assertLog(['Suspend! [Async]']);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
|
||||
// When the promise resolves, a passive static effect flag is added. In the
|
||||
@@ -169,7 +173,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
|
||||
await act(async () => {
|
||||
resolveText('Async');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Async', 'Effect']);
|
||||
assertLog(['Async', 'Effect']);
|
||||
expect(root).toMatchRenderedOutput('Async');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -9,6 +9,11 @@ let act;
|
||||
let TextResource;
|
||||
let textResourceShouldFail;
|
||||
|
||||
let assertLog;
|
||||
let waitForPaint;
|
||||
let waitForAll;
|
||||
let waitFor;
|
||||
|
||||
describe('ReactSuspense', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
@@ -23,6 +28,12 @@ describe('ReactSuspense', () => {
|
||||
|
||||
Suspense = React.Suspense;
|
||||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
waitForAll = InternalTestUtils.waitForAll;
|
||||
waitForPaint = InternalTestUtils.waitForPaint;
|
||||
assertLog = InternalTestUtils.assertLog;
|
||||
waitFor = InternalTestUtils.waitFor;
|
||||
|
||||
TextResource = ReactCache.unstable_createResource(
|
||||
([text, ms = 0]) => {
|
||||
let listeners = null;
|
||||
@@ -94,7 +105,7 @@ describe('ReactSuspense', () => {
|
||||
}
|
||||
}
|
||||
|
||||
it('suspends rendering and continues later', () => {
|
||||
it('suspends rendering and continues later', async () => {
|
||||
function Bar(props) {
|
||||
Scheduler.unstable_yieldValue('Bar');
|
||||
return props.children;
|
||||
@@ -119,7 +130,7 @@ describe('ReactSuspense', () => {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
|
||||
expect(Scheduler).toFlushAndYield(['Foo']);
|
||||
await waitForAll(['Foo']);
|
||||
expect(root).toMatchRenderedOutput(null);
|
||||
|
||||
// Navigate the shell to now render the child content.
|
||||
@@ -128,7 +139,7 @@ describe('ReactSuspense', () => {
|
||||
root.update(<Foo renderBar={true} />);
|
||||
});
|
||||
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
await waitForAll([
|
||||
'Foo',
|
||||
'Bar',
|
||||
// A suspends
|
||||
@@ -142,18 +153,18 @@ describe('ReactSuspense', () => {
|
||||
// Flush some of the time
|
||||
jest.advanceTimersByTime(50);
|
||||
// Still nothing...
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
expect(root).toMatchRenderedOutput(null);
|
||||
|
||||
// Flush the promise completely
|
||||
jest.advanceTimersByTime(50);
|
||||
// Renders successfully
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
|
||||
expect(Scheduler).toFlushAndYield(['Foo', 'Bar', 'A', 'B']);
|
||||
assertLog(['Promise resolved [A]']);
|
||||
await waitForAll(['Foo', 'Bar', 'A', 'B']);
|
||||
expect(root).toMatchRenderedOutput('AB');
|
||||
});
|
||||
|
||||
it('suspends siblings and later recovers each independently', () => {
|
||||
it('suspends siblings and later recovers each independently', async () => {
|
||||
// Render two sibling Suspense components
|
||||
const root = ReactTestRenderer.create(
|
||||
<>
|
||||
@@ -169,7 +180,7 @@ describe('ReactSuspense', () => {
|
||||
},
|
||||
);
|
||||
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
await waitForAll([
|
||||
'Suspend! [A]',
|
||||
'Loading A...',
|
||||
'Suspend! [B]',
|
||||
@@ -182,19 +193,19 @@ describe('ReactSuspense', () => {
|
||||
// show the placeholder
|
||||
jest.advanceTimersByTime(5000);
|
||||
// TODO: Should we throw if you forget to call toHaveYielded?
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
|
||||
expect(Scheduler).toFlushAndYield(['A']);
|
||||
assertLog(['Promise resolved [A]']);
|
||||
await waitForAll(['A']);
|
||||
expect(root).toMatchRenderedOutput('ALoading B...');
|
||||
|
||||
// Advance time by enough that the second Suspense's promise resolves
|
||||
// and switches back to the normal view
|
||||
jest.advanceTimersByTime(1000);
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
|
||||
expect(Scheduler).toFlushAndYield(['B']);
|
||||
assertLog(['Promise resolved [B]']);
|
||||
await waitForAll(['B']);
|
||||
expect(root).toMatchRenderedOutput('AB');
|
||||
});
|
||||
|
||||
it('interrupts current render if promise resolves before current render phase', () => {
|
||||
it('interrupts current render if promise resolves before current render phase', async () => {
|
||||
let didResolve = false;
|
||||
const listeners = [];
|
||||
|
||||
@@ -231,7 +242,7 @@ describe('ReactSuspense', () => {
|
||||
unstable_isConcurrent: true,
|
||||
},
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield(['Initial']);
|
||||
await waitForAll(['Initial']);
|
||||
expect(root).toMatchRenderedOutput('Initial');
|
||||
|
||||
// The update will suspend.
|
||||
@@ -247,19 +258,15 @@ describe('ReactSuspense', () => {
|
||||
);
|
||||
});
|
||||
// Yield past the Suspense boundary but don't complete the last sibling.
|
||||
expect(Scheduler).toFlushAndYieldThrough([
|
||||
'Suspend!',
|
||||
'Loading...',
|
||||
'After Suspense',
|
||||
]);
|
||||
await waitFor(['Suspend!', 'Loading...', 'After Suspense']);
|
||||
|
||||
// The promise resolves before the current render phase has completed
|
||||
resolveThenable();
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
assertLog([]);
|
||||
expect(root).toMatchRenderedOutput('Initial');
|
||||
|
||||
// Start over from the root, instead of continuing.
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
await waitForAll([
|
||||
// Async renders again *before* Sibling
|
||||
'Async',
|
||||
'After Suspense',
|
||||
@@ -268,7 +275,7 @@ describe('ReactSuspense', () => {
|
||||
expect(root).toMatchRenderedOutput('AsyncAfter SuspenseSibling');
|
||||
});
|
||||
|
||||
it('throttles fallback committing globally', () => {
|
||||
it('throttles fallback committing globally', async () => {
|
||||
function Foo() {
|
||||
Scheduler.unstable_yieldValue('Foo');
|
||||
return (
|
||||
@@ -290,7 +297,7 @@ describe('ReactSuspense', () => {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
await waitForAll([
|
||||
'Foo',
|
||||
'Suspend! [A]',
|
||||
'Suspend! [B]',
|
||||
@@ -302,8 +309,8 @@ describe('ReactSuspense', () => {
|
||||
// Resolve A.
|
||||
jest.advanceTimersByTime(200);
|
||||
Scheduler.unstable_advanceTime(200);
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
|
||||
expect(Scheduler).toFlushAndYield(['A', 'Suspend! [B]', 'Loading more...']);
|
||||
assertLog(['Promise resolved [A]']);
|
||||
await waitForAll(['A', 'Suspend! [B]', 'Loading more...']);
|
||||
|
||||
// By this point, we have enough info to show "A" and "Loading more..."
|
||||
// However, we've just shown the outer fallback. So we'll delay
|
||||
@@ -313,17 +320,17 @@ describe('ReactSuspense', () => {
|
||||
// Resolve B.
|
||||
jest.advanceTimersByTime(100);
|
||||
Scheduler.unstable_advanceTime(100);
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
|
||||
assertLog(['Promise resolved [B]']);
|
||||
|
||||
// By this point, B has resolved.
|
||||
// We're still showing the outer fallback.
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
expect(Scheduler).toFlushAndYield(['A', 'B']);
|
||||
await waitForAll(['A', 'B']);
|
||||
// Then contents of both should pop in together.
|
||||
expect(root).toMatchRenderedOutput('AB');
|
||||
});
|
||||
|
||||
it('does not throttle fallback committing for too long', () => {
|
||||
it('does not throttle fallback committing for too long', async () => {
|
||||
function Foo() {
|
||||
Scheduler.unstable_yieldValue('Foo');
|
||||
return (
|
||||
@@ -345,7 +352,7 @@ describe('ReactSuspense', () => {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
await waitForAll([
|
||||
'Foo',
|
||||
'Suspend! [A]',
|
||||
'Suspend! [B]',
|
||||
@@ -357,8 +364,8 @@ describe('ReactSuspense', () => {
|
||||
// Resolve A.
|
||||
jest.advanceTimersByTime(200);
|
||||
Scheduler.unstable_advanceTime(200);
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
|
||||
expect(Scheduler).toFlushAndYield(['A', 'Suspend! [B]', 'Loading more...']);
|
||||
assertLog(['Promise resolved [A]']);
|
||||
await waitForAll(['A', 'Suspend! [B]', 'Loading more...']);
|
||||
|
||||
// By this point, we have enough info to show "A" and "Loading more..."
|
||||
// However, we've just shown the outer fallback. So we'll delay
|
||||
@@ -374,8 +381,8 @@ describe('ReactSuspense', () => {
|
||||
// Resolve B.
|
||||
jest.advanceTimersByTime(500);
|
||||
Scheduler.unstable_advanceTime(500);
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
|
||||
expect(Scheduler).toFlushAndYield(['B']);
|
||||
assertLog(['Promise resolved [B]']);
|
||||
await waitForAll(['B']);
|
||||
expect(root).toMatchRenderedOutput('AB');
|
||||
});
|
||||
|
||||
@@ -404,16 +411,16 @@ describe('ReactSuspense', () => {
|
||||
</Suspense>,
|
||||
);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Loading...']);
|
||||
assertLog(['Loading...']);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
|
||||
await LazyClass;
|
||||
|
||||
expect(Scheduler).toFlushUntilNextPaint(['Hi', 'Did mount: Hi']);
|
||||
await waitForPaint(['Hi', 'Did mount: Hi']);
|
||||
expect(root).toMatchRenderedOutput('Hi');
|
||||
});
|
||||
|
||||
it('updates memoized child of suspense component when context updates (simple memo)', () => {
|
||||
it('updates memoized child of suspense component when context updates (simple memo)', async () => {
|
||||
const {useContext, createContext, useState, memo} = React;
|
||||
|
||||
const ValueContext = createContext(null);
|
||||
@@ -451,23 +458,23 @@ describe('ReactSuspense', () => {
|
||||
const root = ReactTestRenderer.create(<App />, {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
expect(Scheduler).toFlushAndYield(['Suspend! [default]', 'Loading...']);
|
||||
await waitForAll(['Suspend! [default]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [default]']);
|
||||
expect(Scheduler).toFlushAndYield(['default']);
|
||||
assertLog(['Promise resolved [default]']);
|
||||
await waitForAll(['default']);
|
||||
expect(root).toMatchRenderedOutput('default');
|
||||
|
||||
act(() => setValue('new value'));
|
||||
expect(Scheduler).toHaveYielded(['Suspend! [new value]', 'Loading...']);
|
||||
assertLog(['Suspend! [new value]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [new value]']);
|
||||
expect(Scheduler).toFlushAndYield(['new value']);
|
||||
assertLog(['Promise resolved [new value]']);
|
||||
await waitForAll(['new value']);
|
||||
expect(root).toMatchRenderedOutput('new value');
|
||||
});
|
||||
|
||||
it('updates memoized child of suspense component when context updates (manual memo)', () => {
|
||||
it('updates memoized child of suspense component when context updates (manual memo)', async () => {
|
||||
const {useContext, createContext, useState, memo} = React;
|
||||
|
||||
const ValueContext = createContext(null);
|
||||
@@ -510,23 +517,23 @@ describe('ReactSuspense', () => {
|
||||
const root = ReactTestRenderer.create(<App />, {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
expect(Scheduler).toFlushAndYield(['Suspend! [default]', 'Loading...']);
|
||||
await waitForAll(['Suspend! [default]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [default]']);
|
||||
expect(Scheduler).toFlushAndYield(['default']);
|
||||
assertLog(['Promise resolved [default]']);
|
||||
await waitForAll(['default']);
|
||||
expect(root).toMatchRenderedOutput('default');
|
||||
|
||||
act(() => setValue('new value'));
|
||||
expect(Scheduler).toHaveYielded(['Suspend! [new value]', 'Loading...']);
|
||||
assertLog(['Suspend! [new value]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [new value]']);
|
||||
expect(Scheduler).toFlushAndYield(['new value']);
|
||||
assertLog(['Promise resolved [new value]']);
|
||||
await waitForAll(['new value']);
|
||||
expect(root).toMatchRenderedOutput('new value');
|
||||
});
|
||||
|
||||
it('updates memoized child of suspense component when context updates (function)', () => {
|
||||
it('updates memoized child of suspense component when context updates (function)', async () => {
|
||||
const {useContext, createContext, useState} = React;
|
||||
|
||||
const ValueContext = createContext(null);
|
||||
@@ -567,23 +574,23 @@ describe('ReactSuspense', () => {
|
||||
unstable_isConcurrent: true,
|
||||
},
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield(['Suspend! [default]', 'Loading...']);
|
||||
await waitForAll(['Suspend! [default]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [default]']);
|
||||
expect(Scheduler).toFlushAndYield(['default']);
|
||||
assertLog(['Promise resolved [default]']);
|
||||
await waitForAll(['default']);
|
||||
expect(root).toMatchRenderedOutput('default');
|
||||
|
||||
act(() => setValue('new value'));
|
||||
expect(Scheduler).toHaveYielded(['Suspend! [new value]', 'Loading...']);
|
||||
assertLog(['Suspend! [new value]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [new value]']);
|
||||
expect(Scheduler).toFlushAndYield(['new value']);
|
||||
assertLog(['Promise resolved [new value]']);
|
||||
await waitForAll(['new value']);
|
||||
expect(root).toMatchRenderedOutput('new value');
|
||||
});
|
||||
|
||||
it('updates memoized child of suspense component when context updates (forwardRef)', () => {
|
||||
it('updates memoized child of suspense component when context updates (forwardRef)', async () => {
|
||||
const {forwardRef, useContext, createContext, useState} = React;
|
||||
|
||||
const ValueContext = createContext(null);
|
||||
@@ -624,23 +631,23 @@ describe('ReactSuspense', () => {
|
||||
unstable_isConcurrent: true,
|
||||
},
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield(['Suspend! [default]', 'Loading...']);
|
||||
await waitForAll(['Suspend! [default]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [default]']);
|
||||
expect(Scheduler).toFlushAndYield(['default']);
|
||||
assertLog(['Promise resolved [default]']);
|
||||
await waitForAll(['default']);
|
||||
expect(root).toMatchRenderedOutput('default');
|
||||
|
||||
act(() => setValue('new value'));
|
||||
expect(Scheduler).toHaveYielded(['Suspend! [new value]', 'Loading...']);
|
||||
assertLog(['Suspend! [new value]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [new value]']);
|
||||
expect(Scheduler).toFlushAndYield(['new value']);
|
||||
assertLog(['Promise resolved [new value]']);
|
||||
await waitForAll(['new value']);
|
||||
expect(root).toMatchRenderedOutput('new value');
|
||||
});
|
||||
|
||||
it('re-fires layout effects when re-showing Suspense', () => {
|
||||
it('re-fires layout effects when re-showing Suspense', async () => {
|
||||
function TextWithLayout(props) {
|
||||
Scheduler.unstable_yieldValue(props.text);
|
||||
React.useLayoutEffect(() => {
|
||||
@@ -668,28 +675,21 @@ describe('ReactSuspense', () => {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
|
||||
expect(Scheduler).toFlushAndYield(['Child 1', 'create layout']);
|
||||
await waitForAll(['Child 1', 'create layout']);
|
||||
expect(root).toMatchRenderedOutput('Child 1');
|
||||
|
||||
act(() => {
|
||||
_setShow(true);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Child 1',
|
||||
'Suspend! [Child 2]',
|
||||
'Loading...',
|
||||
]);
|
||||
assertLog(['Child 1', 'Suspend! [Child 2]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'destroy layout',
|
||||
'Promise resolved [Child 2]',
|
||||
]);
|
||||
expect(Scheduler).toFlushAndYield(['Child 1', 'Child 2', 'create layout']);
|
||||
assertLog(['destroy layout', 'Promise resolved [Child 2]']);
|
||||
await waitForAll(['Child 1', 'Child 2', 'create layout']);
|
||||
expect(root).toMatchRenderedOutput(['Child 1', 'Child 2'].join(''));
|
||||
});
|
||||
|
||||
describe('outside concurrent mode', () => {
|
||||
it('a mounted class component can suspend without losing state', () => {
|
||||
it('a mounted class component can suspend without losing state', async () => {
|
||||
class TextWithLifecycle extends React.Component {
|
||||
componentDidMount() {
|
||||
Scheduler.unstable_yieldValue(`Mount [${this.props.text}]`);
|
||||
@@ -754,7 +754,7 @@ describe('ReactSuspense', () => {
|
||||
|
||||
const root = ReactTestRenderer.create(<App />);
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'A',
|
||||
'Suspend! [B:1]',
|
||||
'C',
|
||||
@@ -770,8 +770,8 @@ describe('ReactSuspense', () => {
|
||||
|
||||
jest.advanceTimersByTime(100);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [B:1]']);
|
||||
expect(Scheduler).toFlushUntilNextPaint([
|
||||
assertLog(['Promise resolved [B:1]']);
|
||||
await waitForPaint([
|
||||
'B:1',
|
||||
'Unmount [Loading...]',
|
||||
// Should be a mount, not an update
|
||||
@@ -780,25 +780,17 @@ describe('ReactSuspense', () => {
|
||||
expect(root).toMatchRenderedOutput('AB:1C');
|
||||
|
||||
instance.setState({step: 2});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Suspend! [B:2]',
|
||||
'Loading...',
|
||||
'Mount [Loading...]',
|
||||
]);
|
||||
assertLog(['Suspend! [B:2]', 'Loading...', 'Mount [Loading...]']);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
|
||||
jest.advanceTimersByTime(100);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [B:2]']);
|
||||
expect(Scheduler).toFlushUntilNextPaint([
|
||||
'B:2',
|
||||
'Unmount [Loading...]',
|
||||
'Update [B:2]',
|
||||
]);
|
||||
assertLog(['Promise resolved [B:2]']);
|
||||
await waitForPaint(['B:2', 'Unmount [Loading...]', 'Update [B:2]']);
|
||||
expect(root).toMatchRenderedOutput('AB:2C');
|
||||
});
|
||||
|
||||
it('bails out on timed-out primary children even if they receive an update', () => {
|
||||
it('bails out on timed-out primary children even if they receive an update', async () => {
|
||||
let instance;
|
||||
class Stateful extends React.Component {
|
||||
state = {step: 1};
|
||||
@@ -819,38 +811,30 @@ describe('ReactSuspense', () => {
|
||||
|
||||
const root = ReactTestRenderer.create(<App text="A" />);
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Stateful: 1',
|
||||
'Suspend! [A]',
|
||||
'Loading...',
|
||||
]);
|
||||
assertLog(['Stateful: 1', 'Suspend! [A]', 'Loading...']);
|
||||
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
|
||||
expect(Scheduler).toFlushUntilNextPaint(['A']);
|
||||
assertLog(['Promise resolved [A]']);
|
||||
await waitForPaint(['A']);
|
||||
expect(root).toMatchRenderedOutput('Stateful: 1A');
|
||||
|
||||
root.update(<App text="B" />);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Stateful: 1',
|
||||
'Suspend! [B]',
|
||||
'Loading...',
|
||||
]);
|
||||
assertLog(['Stateful: 1', 'Suspend! [B]', 'Loading...']);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
|
||||
instance.setState({step: 2});
|
||||
expect(Scheduler).toHaveYielded(['Stateful: 2', 'Suspend! [B]']);
|
||||
assertLog(['Stateful: 2', 'Suspend! [B]']);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
|
||||
expect(Scheduler).toFlushUntilNextPaint(['B']);
|
||||
assertLog(['Promise resolved [B]']);
|
||||
await waitForPaint(['B']);
|
||||
expect(root).toMatchRenderedOutput('Stateful: 2B');
|
||||
});
|
||||
|
||||
it('when updating a timed-out tree, always retries the suspended component', () => {
|
||||
it('when updating a timed-out tree, always retries the suspended component', async () => {
|
||||
let instance;
|
||||
class Stateful extends React.Component {
|
||||
state = {step: 1};
|
||||
@@ -879,28 +863,20 @@ describe('ReactSuspense', () => {
|
||||
|
||||
const root = ReactTestRenderer.create(<App text="A" />);
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Stateful: 1',
|
||||
'Suspend! [A]',
|
||||
'Loading...',
|
||||
]);
|
||||
assertLog(['Stateful: 1', 'Suspend! [A]', 'Loading...']);
|
||||
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
|
||||
expect(Scheduler).toFlushUntilNextPaint(['A']);
|
||||
assertLog(['Promise resolved [A]']);
|
||||
await waitForPaint(['A']);
|
||||
expect(root).toMatchRenderedOutput('Stateful: 1A');
|
||||
|
||||
root.update(<App text="B" />);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Stateful: 1',
|
||||
'Suspend! [B]',
|
||||
'Loading...',
|
||||
]);
|
||||
assertLog(['Stateful: 1', 'Suspend! [B]', 'Loading...']);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
|
||||
instance.setState({step: 2});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Stateful: 2',
|
||||
|
||||
// The suspended component should suspend again. If it doesn't, the
|
||||
@@ -912,8 +888,8 @@ describe('ReactSuspense', () => {
|
||||
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
|
||||
expect(Scheduler).toFlushUntilNextPaint(['B']);
|
||||
assertLog(['Promise resolved [B]']);
|
||||
await waitForPaint(['B']);
|
||||
expect(root).toMatchRenderedOutput('Stateful: 2B');
|
||||
});
|
||||
|
||||
@@ -949,14 +925,14 @@ describe('ReactSuspense', () => {
|
||||
}
|
||||
|
||||
const root = ReactTestRenderer.create(<App text="A" />);
|
||||
expect(Scheduler).toHaveYielded(['Suspend! [A]', 'Loading...']);
|
||||
assertLog(['Suspend! [A]', 'Loading...']);
|
||||
root.update(<Text text="B" />);
|
||||
// Should not fire componentWillUnmount
|
||||
expect(Scheduler).toHaveYielded(['B']);
|
||||
assertLog(['B']);
|
||||
expect(root).toMatchRenderedOutput('B');
|
||||
});
|
||||
|
||||
it('suspends in a component that also contains useEffect', () => {
|
||||
it('suspends in a component that also contains useEffect', async () => {
|
||||
const {useLayoutEffect} = React;
|
||||
|
||||
function AsyncTextWithEffect(props) {
|
||||
@@ -989,14 +965,14 @@ describe('ReactSuspense', () => {
|
||||
}
|
||||
|
||||
ReactTestRenderer.create(<App text="A" />);
|
||||
expect(Scheduler).toHaveYielded(['Suspend! [A]', 'Loading...']);
|
||||
assertLog(['Suspend! [A]', 'Loading...']);
|
||||
jest.advanceTimersByTime(500);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
|
||||
expect(Scheduler).toFlushUntilNextPaint(['A', 'Did commit: A']);
|
||||
assertLog(['Promise resolved [A]']);
|
||||
await waitForPaint(['A', 'Did commit: A']);
|
||||
});
|
||||
|
||||
it('retries when an update is scheduled on a timed out tree', () => {
|
||||
it('retries when an update is scheduled on a timed out tree', async () => {
|
||||
let instance;
|
||||
class Stateful extends React.Component {
|
||||
state = {step: 1};
|
||||
@@ -1019,33 +995,30 @@ describe('ReactSuspense', () => {
|
||||
});
|
||||
|
||||
// Initial render
|
||||
expect(Scheduler).toFlushAndYield(['Suspend! [Step: 1]', 'Loading...']);
|
||||
await waitForAll(['Suspend! [Step: 1]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Step: 1]']);
|
||||
expect(Scheduler).toFlushAndYield(['Step: 1']);
|
||||
assertLog(['Promise resolved [Step: 1]']);
|
||||
await waitForAll(['Step: 1']);
|
||||
expect(root).toMatchRenderedOutput('Step: 1');
|
||||
|
||||
// Update that suspends
|
||||
instance.setState({step: 2});
|
||||
expect(Scheduler).toFlushAndYield(['Suspend! [Step: 2]', 'Loading...']);
|
||||
await waitForAll(['Suspend! [Step: 2]', 'Loading...']);
|
||||
jest.advanceTimersByTime(500);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
|
||||
// Update while still suspended
|
||||
instance.setState({step: 3});
|
||||
expect(Scheduler).toFlushAndYield(['Suspend! [Step: 3]']);
|
||||
await waitForAll(['Suspend! [Step: 3]']);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
|
||||
jest.advanceTimersByTime(1000);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Promise resolved [Step: 2]',
|
||||
'Promise resolved [Step: 3]',
|
||||
]);
|
||||
expect(Scheduler).toFlushAndYield(['Step: 3']);
|
||||
assertLog(['Promise resolved [Step: 2]', 'Promise resolved [Step: 3]']);
|
||||
await waitForAll(['Step: 3']);
|
||||
expect(root).toMatchRenderedOutput('Step: 3');
|
||||
});
|
||||
|
||||
it('does not remount the fallback while suspended children resolve in legacy mode', () => {
|
||||
it('does not remount the fallback while suspended children resolve in legacy mode', async () => {
|
||||
let mounts = 0;
|
||||
class ShouldMountOnce extends React.Component {
|
||||
componentDidMount() {
|
||||
@@ -1069,18 +1042,18 @@ describe('ReactSuspense', () => {
|
||||
const root = ReactTestRenderer.create(<App />);
|
||||
|
||||
// Initial render
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Suspend! [Child 1]',
|
||||
'Suspend! [Child 2]',
|
||||
'Suspend! [Child 3]',
|
||||
'Loading...',
|
||||
]);
|
||||
expect(Scheduler).toFlushAndYield([]);
|
||||
await waitForAll([]);
|
||||
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Child 1]']);
|
||||
expect(Scheduler).toFlushUntilNextPaint([
|
||||
assertLog(['Promise resolved [Child 1]']);
|
||||
await waitForPaint([
|
||||
'Child 1',
|
||||
'Suspend! [Child 2]',
|
||||
'Suspend! [Child 3]',
|
||||
@@ -1088,23 +1061,20 @@ describe('ReactSuspense', () => {
|
||||
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Child 2]']);
|
||||
expect(Scheduler).toFlushUntilNextPaint([
|
||||
'Child 2',
|
||||
'Suspend! [Child 3]',
|
||||
]);
|
||||
assertLog(['Promise resolved [Child 2]']);
|
||||
await waitForPaint(['Child 2', 'Suspend! [Child 3]']);
|
||||
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Child 3]']);
|
||||
expect(Scheduler).toFlushUntilNextPaint(['Child 3']);
|
||||
assertLog(['Promise resolved [Child 3]']);
|
||||
await waitForPaint(['Child 3']);
|
||||
expect(root).toMatchRenderedOutput(
|
||||
['Child 1', 'Child 2', 'Child 3'].join(''),
|
||||
);
|
||||
expect(mounts).toBe(1);
|
||||
});
|
||||
|
||||
it('does not get stuck with fallback in concurrent mode for a large delay', () => {
|
||||
it('does not get stuck with fallback in concurrent mode for a large delay', async () => {
|
||||
function App(props) {
|
||||
return (
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
@@ -1118,21 +1088,21 @@ describe('ReactSuspense', () => {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
await waitForAll([
|
||||
'Suspend! [Child 1]',
|
||||
'Suspend! [Child 2]',
|
||||
'Loading...',
|
||||
]);
|
||||
jest.advanceTimersByTime(1000);
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Child 1]']);
|
||||
expect(Scheduler).toFlushAndYield(['Child 1', 'Suspend! [Child 2]']);
|
||||
assertLog(['Promise resolved [Child 1]']);
|
||||
await waitForAll(['Child 1', 'Suspend! [Child 2]']);
|
||||
jest.advanceTimersByTime(6000);
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Child 2]']);
|
||||
expect(Scheduler).toFlushAndYield(['Child 1', 'Child 2']);
|
||||
assertLog(['Promise resolved [Child 2]']);
|
||||
await waitForAll(['Child 1', 'Child 2']);
|
||||
expect(root).toMatchRenderedOutput(['Child 1', 'Child 2'].join(''));
|
||||
});
|
||||
|
||||
it('reuses effects, including deletions, from the suspended tree', () => {
|
||||
it('reuses effects, including deletions, from the suspended tree', async () => {
|
||||
const {useState} = React;
|
||||
|
||||
let setTab;
|
||||
@@ -1149,46 +1119,34 @@ describe('ReactSuspense', () => {
|
||||
}
|
||||
|
||||
const root = ReactTestRenderer.create(<App />);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Suspend! [Tab: 0]',
|
||||
' + sibling',
|
||||
'Loading...',
|
||||
]);
|
||||
assertLog(['Suspend! [Tab: 0]', ' + sibling', 'Loading...']);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Tab: 0]']);
|
||||
expect(Scheduler).toFlushUntilNextPaint(['Tab: 0']);
|
||||
assertLog(['Promise resolved [Tab: 0]']);
|
||||
await waitForPaint(['Tab: 0']);
|
||||
expect(root).toMatchRenderedOutput('Tab: 0 + sibling');
|
||||
|
||||
act(() => setTab(1));
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Suspend! [Tab: 1]',
|
||||
' + sibling',
|
||||
'Loading...',
|
||||
]);
|
||||
assertLog(['Suspend! [Tab: 1]', ' + sibling', 'Loading...']);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Tab: 1]']);
|
||||
expect(Scheduler).toFlushUntilNextPaint(['Tab: 1']);
|
||||
assertLog(['Promise resolved [Tab: 1]']);
|
||||
await waitForPaint(['Tab: 1']);
|
||||
expect(root).toMatchRenderedOutput('Tab: 1 + sibling');
|
||||
|
||||
act(() => setTab(2));
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Suspend! [Tab: 2]',
|
||||
' + sibling',
|
||||
'Loading...',
|
||||
]);
|
||||
assertLog(['Suspend! [Tab: 2]', ' + sibling', 'Loading...']);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [Tab: 2]']);
|
||||
expect(Scheduler).toFlushUntilNextPaint(['Tab: 2']);
|
||||
assertLog(['Promise resolved [Tab: 2]']);
|
||||
await waitForPaint(['Tab: 2']);
|
||||
expect(root).toMatchRenderedOutput('Tab: 2 + sibling');
|
||||
});
|
||||
|
||||
it('does not warn if an mounted component is pinged', () => {
|
||||
it('does not warn if an mounted component is pinged', async () => {
|
||||
const {useState} = React;
|
||||
|
||||
const root = ReactTestRenderer.create(null);
|
||||
@@ -1218,23 +1176,23 @@ describe('ReactSuspense', () => {
|
||||
</Suspense>,
|
||||
);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Suspend! [A:0]', 'Loading...']);
|
||||
assertLog(['Suspend! [A:0]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [A:0]']);
|
||||
expect(Scheduler).toFlushUntilNextPaint(['A:0']);
|
||||
assertLog(['Promise resolved [A:0]']);
|
||||
await waitForPaint(['A:0']);
|
||||
expect(root).toMatchRenderedOutput('A:0');
|
||||
|
||||
act(() => setStep(1));
|
||||
expect(Scheduler).toHaveYielded(['Suspend! [A:1]', 'Loading...']);
|
||||
assertLog(['Suspend! [A:1]', 'Loading...']);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
|
||||
root.update(null);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
jest.advanceTimersByTime(1000);
|
||||
});
|
||||
|
||||
it('memoizes promise listeners per thread ID to prevent redundant renders', () => {
|
||||
it('memoizes promise listeners per thread ID to prevent redundant renders', async () => {
|
||||
function App() {
|
||||
return (
|
||||
<Suspense fallback={<Text text="Loading..." />}>
|
||||
@@ -1249,18 +1207,13 @@ describe('ReactSuspense', () => {
|
||||
|
||||
root.update(<App />);
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Suspend! [A]',
|
||||
'Suspend! [B]',
|
||||
'Suspend! [C]',
|
||||
'Loading...',
|
||||
]);
|
||||
assertLog(['Suspend! [A]', 'Suspend! [B]', 'Suspend! [C]', 'Loading...']);
|
||||
|
||||
// Resolve A
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
|
||||
expect(Scheduler).toFlushUntilNextPaint([
|
||||
assertLog(['Promise resolved [A]']);
|
||||
await waitForPaint([
|
||||
'A',
|
||||
// The promises for B and C have now been thrown twice
|
||||
'Suspend! [B]',
|
||||
@@ -1270,8 +1223,8 @@ describe('ReactSuspense', () => {
|
||||
// Resolve B
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
|
||||
expect(Scheduler).toFlushUntilNextPaint([
|
||||
assertLog(['Promise resolved [B]']);
|
||||
await waitForPaint([
|
||||
// Even though the promise for B was thrown twice, we should only
|
||||
// re-render once.
|
||||
'B',
|
||||
@@ -1282,8 +1235,8 @@ describe('ReactSuspense', () => {
|
||||
// Resolve C
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [C]']);
|
||||
expect(Scheduler).toFlushUntilNextPaint([
|
||||
assertLog(['Promise resolved [C]']);
|
||||
await waitForPaint([
|
||||
// Even though the promise for C was thrown three times, we should only
|
||||
// re-render once.
|
||||
'C',
|
||||
@@ -1328,7 +1281,7 @@ describe('ReactSuspense', () => {
|
||||
jest.advanceTimersByTime(1000);
|
||||
});
|
||||
|
||||
it('updates memoized child of suspense component when context updates (simple memo)', () => {
|
||||
it('updates memoized child of suspense component when context updates (simple memo)', async () => {
|
||||
const {useContext, createContext, useState, memo} = React;
|
||||
|
||||
const ValueContext = createContext(null);
|
||||
@@ -1364,23 +1317,23 @@ describe('ReactSuspense', () => {
|
||||
}
|
||||
|
||||
const root = ReactTestRenderer.create(<App />);
|
||||
expect(Scheduler).toHaveYielded(['Suspend! [default]', 'Loading...']);
|
||||
assertLog(['Suspend! [default]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [default]']);
|
||||
expect(Scheduler).toFlushUntilNextPaint(['default']);
|
||||
assertLog(['Promise resolved [default]']);
|
||||
await waitForPaint(['default']);
|
||||
expect(root).toMatchRenderedOutput('default');
|
||||
|
||||
act(() => setValue('new value'));
|
||||
expect(Scheduler).toHaveYielded(['Suspend! [new value]', 'Loading...']);
|
||||
assertLog(['Suspend! [new value]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [new value]']);
|
||||
expect(Scheduler).toFlushUntilNextPaint(['new value']);
|
||||
assertLog(['Promise resolved [new value]']);
|
||||
await waitForPaint(['new value']);
|
||||
expect(root).toMatchRenderedOutput('new value');
|
||||
});
|
||||
|
||||
it('updates memoized child of suspense component when context updates (manual memo)', () => {
|
||||
it('updates memoized child of suspense component when context updates (manual memo)', async () => {
|
||||
const {useContext, createContext, useState, memo} = React;
|
||||
|
||||
const ValueContext = createContext(null);
|
||||
@@ -1421,23 +1374,23 @@ describe('ReactSuspense', () => {
|
||||
}
|
||||
|
||||
const root = ReactTestRenderer.create(<App />);
|
||||
expect(Scheduler).toHaveYielded(['Suspend! [default]', 'Loading...']);
|
||||
assertLog(['Suspend! [default]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [default]']);
|
||||
expect(Scheduler).toFlushUntilNextPaint(['default']);
|
||||
assertLog(['Promise resolved [default]']);
|
||||
await waitForPaint(['default']);
|
||||
expect(root).toMatchRenderedOutput('default');
|
||||
|
||||
act(() => setValue('new value'));
|
||||
expect(Scheduler).toHaveYielded(['Suspend! [new value]', 'Loading...']);
|
||||
assertLog(['Suspend! [new value]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [new value]']);
|
||||
expect(Scheduler).toFlushUntilNextPaint(['new value']);
|
||||
assertLog(['Promise resolved [new value]']);
|
||||
await waitForPaint(['new value']);
|
||||
expect(root).toMatchRenderedOutput('new value');
|
||||
});
|
||||
|
||||
it('updates memoized child of suspense component when context updates (function)', () => {
|
||||
it('updates memoized child of suspense component when context updates (function)', async () => {
|
||||
const {useContext, createContext, useState} = React;
|
||||
|
||||
const ValueContext = createContext(null);
|
||||
@@ -1477,23 +1430,23 @@ describe('ReactSuspense', () => {
|
||||
</Suspense>
|
||||
</App>,
|
||||
);
|
||||
expect(Scheduler).toHaveYielded(['Suspend! [default]', 'Loading...']);
|
||||
assertLog(['Suspend! [default]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [default]']);
|
||||
expect(Scheduler).toFlushUntilNextPaint(['default']);
|
||||
assertLog(['Promise resolved [default]']);
|
||||
await waitForPaint(['default']);
|
||||
expect(root).toMatchRenderedOutput('default');
|
||||
|
||||
act(() => setValue('new value'));
|
||||
expect(Scheduler).toHaveYielded(['Suspend! [new value]', 'Loading...']);
|
||||
assertLog(['Suspend! [new value]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [new value]']);
|
||||
expect(Scheduler).toFlushUntilNextPaint(['new value']);
|
||||
assertLog(['Promise resolved [new value]']);
|
||||
await waitForPaint(['new value']);
|
||||
expect(root).toMatchRenderedOutput('new value');
|
||||
});
|
||||
|
||||
it('updates memoized child of suspense component when context updates (forwardRef)', () => {
|
||||
it('updates memoized child of suspense component when context updates (forwardRef)', async () => {
|
||||
const {forwardRef, useContext, createContext, useState} = React;
|
||||
|
||||
const ValueContext = createContext(null);
|
||||
@@ -1529,19 +1482,19 @@ describe('ReactSuspense', () => {
|
||||
}
|
||||
|
||||
const root = ReactTestRenderer.create(<App />);
|
||||
expect(Scheduler).toHaveYielded(['Suspend! [default]', 'Loading...']);
|
||||
assertLog(['Suspend! [default]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [default]']);
|
||||
expect(Scheduler).toFlushUntilNextPaint(['default']);
|
||||
assertLog(['Promise resolved [default]']);
|
||||
await waitForPaint(['default']);
|
||||
expect(root).toMatchRenderedOutput('default');
|
||||
|
||||
act(() => setValue('new value'));
|
||||
expect(Scheduler).toHaveYielded(['Suspend! [new value]', 'Loading...']);
|
||||
assertLog(['Suspend! [new value]', 'Loading...']);
|
||||
jest.advanceTimersByTime(1000);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Promise resolved [new value]']);
|
||||
expect(Scheduler).toFlushUntilNextPaint(['new value']);
|
||||
assertLog(['Promise resolved [new value]']);
|
||||
await waitForPaint(['new value']);
|
||||
expect(root).toMatchRenderedOutput('new value');
|
||||
});
|
||||
|
||||
@@ -1587,24 +1540,15 @@ describe('ReactSuspense', () => {
|
||||
}
|
||||
|
||||
const root = ReactTestRenderer.create(<App />);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Received context value [default]',
|
||||
'default',
|
||||
]);
|
||||
assertLog(['Received context value [default]', 'default']);
|
||||
expect(root).toMatchRenderedOutput('default');
|
||||
|
||||
act(() => setValue('new value'));
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Received context value [new value]',
|
||||
'Loading...',
|
||||
]);
|
||||
assertLog(['Received context value [new value]', 'Loading...']);
|
||||
expect(root).toMatchRenderedOutput('Loading...');
|
||||
|
||||
act(() => setValue('default'));
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Received context value [default]',
|
||||
'default',
|
||||
]);
|
||||
assertLog(['Received context value [default]', 'default']);
|
||||
expect(root).toMatchRenderedOutput('default');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
let React;
|
||||
let ReactNoop;
|
||||
let Scheduler;
|
||||
let waitForAll;
|
||||
|
||||
describe('ReactSuspense', () => {
|
||||
beforeEach(() => {
|
||||
@@ -20,6 +21,9 @@ describe('ReactSuspense', () => {
|
||||
React = require('react');
|
||||
ReactNoop = require('react-noop-renderer');
|
||||
Scheduler = require('scheduler');
|
||||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
waitForAll = InternalTestUtils.waitForAll;
|
||||
});
|
||||
|
||||
function createThenable() {
|
||||
@@ -83,13 +87,13 @@ describe('ReactSuspense', () => {
|
||||
);
|
||||
|
||||
ReactNoop.render(element);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
expect(ReactNoop).toMatchRenderedOutput('Waiting');
|
||||
expect(ops).toEqual([new Set([promise])]);
|
||||
ops = [];
|
||||
|
||||
await resolve();
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
expect(ReactNoop).toMatchRenderedOutput('Done');
|
||||
expect(ops).toEqual([]);
|
||||
});
|
||||
@@ -122,27 +126,27 @@ describe('ReactSuspense', () => {
|
||||
);
|
||||
|
||||
ReactNoop.render(element);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 1');
|
||||
expect(ops).toEqual([new Set([promise1, promise2])]);
|
||||
ops = [];
|
||||
|
||||
await resolve1();
|
||||
ReactNoop.render(element);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 1');
|
||||
expect(ops).toEqual([new Set([promise2])]);
|
||||
ops = [];
|
||||
|
||||
await resolve2();
|
||||
ReactNoop.render(element);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
expect(ReactNoop).toMatchRenderedOutput('DoneDone');
|
||||
expect(ops).toEqual([]);
|
||||
});
|
||||
|
||||
// @gate www
|
||||
it('nested suspense promises are reported only for their tier', () => {
|
||||
it('nested suspense promises are reported only for their tier', async () => {
|
||||
const {promise, PromiseComp} = createThenable();
|
||||
|
||||
const ops1 = [];
|
||||
@@ -167,7 +171,7 @@ describe('ReactSuspense', () => {
|
||||
);
|
||||
|
||||
ReactNoop.render(element);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 2');
|
||||
expect(ops1).toEqual([]);
|
||||
expect(ops2).toEqual([new Set([promise])]);
|
||||
@@ -209,7 +213,7 @@ describe('ReactSuspense', () => {
|
||||
);
|
||||
|
||||
ReactNoop.render(element);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
expect(ReactNoop).toMatchRenderedOutput('Waiting Tier 1');
|
||||
expect(ops1).toEqual([new Set([promise1])]);
|
||||
expect(ops2).toEqual([]);
|
||||
@@ -218,7 +222,7 @@ describe('ReactSuspense', () => {
|
||||
|
||||
await resolve1();
|
||||
ReactNoop.render(element);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
|
||||
// Force fallback to commit.
|
||||
// TODO: Should be able to use `act` here.
|
||||
@@ -232,7 +236,7 @@ describe('ReactSuspense', () => {
|
||||
|
||||
await resolve2();
|
||||
ReactNoop.render(element);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
expect(ReactNoop).toMatchRenderedOutput('DoneDone');
|
||||
expect(ops1).toEqual([]);
|
||||
expect(ops2).toEqual([]);
|
||||
|
||||
Reference in New Issue
Block a user