mirror of
https://github.com/zebrajr/react.git
synced 2026-01-15 12:15:22 +00:00
Codemod tests to waitFor pattern (8/?) (#26308)
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:
@@ -17,6 +17,8 @@ describe('Timeline profiler', () => {
|
||||
let ReactDOMClient;
|
||||
let Scheduler;
|
||||
let utils;
|
||||
let assertLog;
|
||||
let waitFor;
|
||||
|
||||
describe('User Timing API', () => {
|
||||
let clearedMarks;
|
||||
@@ -82,6 +84,10 @@ describe('Timeline profiler', () => {
|
||||
ReactDOMClient = require('react-dom/client');
|
||||
Scheduler = require('scheduler');
|
||||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
assertLog = InternalTestUtils.assertLog;
|
||||
waitFor = InternalTestUtils.waitFor;
|
||||
|
||||
setPerformanceMock =
|
||||
require('react-devtools-shared/src/backend/profilingHooks').setPerformanceMock_ONLY_FOR_TESTING;
|
||||
setPerformanceMock(createUserTimingPolyfill());
|
||||
@@ -1372,28 +1378,29 @@ describe('Timeline profiler', () => {
|
||||
<Yield id="B" value={1} />
|
||||
</>,
|
||||
);
|
||||
expect(Scheduler).toFlushAndYieldThrough(['A:1']);
|
||||
|
||||
testMarks.push(...createUserTimingData(clearedMarks));
|
||||
clearPendingMarks();
|
||||
|
||||
// Advance the clock some more to make the pending React update seem long.
|
||||
startTime += 20000;
|
||||
|
||||
// Fake a long "click" event in the middle
|
||||
// and schedule a sync update that will also flush the previous work.
|
||||
testMarks.push(createNativeEventEntry('click', 25000));
|
||||
ReactDOM.flushSync(() => {
|
||||
root.render(
|
||||
<>
|
||||
<Yield id="A" value={2} />
|
||||
<Yield id="B" value={2} />
|
||||
</>,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded(['A:2', 'B:2']);
|
||||
await waitFor(['A:1']);
|
||||
|
||||
testMarks.push(...createUserTimingData(clearedMarks));
|
||||
clearPendingMarks();
|
||||
|
||||
// Advance the clock some more to make the pending React update seem long.
|
||||
startTime += 20000;
|
||||
|
||||
// Fake a long "click" event in the middle
|
||||
// and schedule a sync update that will also flush the previous work.
|
||||
testMarks.push(createNativeEventEntry('click', 25000));
|
||||
ReactDOM.flushSync(() => {
|
||||
root.render(
|
||||
<>
|
||||
<Yield id="A" value={2} />
|
||||
<Yield id="B" value={2} />
|
||||
</>,
|
||||
);
|
||||
});
|
||||
|
||||
assertLog(['A:2', 'B:2']);
|
||||
|
||||
testMarks.push(...createUserTimingData(clearedMarks));
|
||||
|
||||
@@ -1424,10 +1431,7 @@ describe('Timeline profiler', () => {
|
||||
root.render(<Component />);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Component mount',
|
||||
'Component update',
|
||||
]);
|
||||
assertLog(['Component mount', 'Component update']);
|
||||
|
||||
const data = await preprocessData([
|
||||
...createBoilerplateEntries(),
|
||||
@@ -1463,10 +1467,7 @@ describe('Timeline profiler', () => {
|
||||
root.render(<Component />);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Component mount',
|
||||
'Component update',
|
||||
]);
|
||||
assertLog(['Component mount', 'Component update']);
|
||||
|
||||
const data = await preprocessData([
|
||||
...createBoilerplateEntries(),
|
||||
@@ -1504,10 +1505,7 @@ describe('Timeline profiler', () => {
|
||||
root.render(<Component />);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Component mount',
|
||||
'Component update',
|
||||
]);
|
||||
assertLog(['Component mount', 'Component update']);
|
||||
|
||||
const testMarks = [];
|
||||
clearedMarks.forEach(markName => {
|
||||
@@ -1567,10 +1565,7 @@ describe('Timeline profiler', () => {
|
||||
root.render(<Component />);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Component mount',
|
||||
'Component update',
|
||||
]);
|
||||
assertLog(['Component mount', 'Component update']);
|
||||
|
||||
const testMarks = [];
|
||||
clearedMarks.forEach(markName => {
|
||||
@@ -1639,7 +1634,7 @@ describe('Timeline profiler', () => {
|
||||
root.render(<Component />);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Component rendered with value 0',
|
||||
'Component rendered with value 0',
|
||||
'Component rendered with value 1',
|
||||
@@ -1708,7 +1703,7 @@ describe('Timeline profiler', () => {
|
||||
root.render(<Component />);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Component rendered with value 0 and deferredValue 0',
|
||||
'Component rendered with value 1 and deferredValue 0',
|
||||
'Component rendered with value 1 and deferredValue 1',
|
||||
|
||||
@@ -23,6 +23,7 @@ let buffer = '';
|
||||
let hasErrored = false;
|
||||
let fatalError = undefined;
|
||||
let textCache;
|
||||
let assertLog;
|
||||
|
||||
describe('ReactDOMFizzShellHydration', () => {
|
||||
beforeEach(() => {
|
||||
@@ -35,6 +36,9 @@ describe('ReactDOMFizzShellHydration', () => {
|
||||
ReactDOMFizzServer = require('react-dom/server');
|
||||
Stream = require('stream');
|
||||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
assertLog = InternalTestUtils.assertLog;
|
||||
|
||||
startTransition = React.startTransition;
|
||||
|
||||
textCache = new Map();
|
||||
@@ -180,7 +184,7 @@ describe('ReactDOMFizzShellHydration', () => {
|
||||
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
|
||||
pipe(writable);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Shell']);
|
||||
assertLog(['Shell']);
|
||||
const dehydratedDiv = container.getElementsByTagName('div')[0];
|
||||
|
||||
// Clear the cache and start rendering on the client
|
||||
@@ -190,7 +194,7 @@ describe('ReactDOMFizzShellHydration', () => {
|
||||
await clientAct(async () => {
|
||||
ReactDOMClient.hydrateRoot(container, <App />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Suspend! [Shell]']);
|
||||
assertLog(['Suspend! [Shell]']);
|
||||
expect(div.current).toBe(null);
|
||||
expect(container.textContent).toBe('Shell');
|
||||
|
||||
@@ -198,7 +202,7 @@ describe('ReactDOMFizzShellHydration', () => {
|
||||
await clientAct(async () => {
|
||||
await resolveText('Shell');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Shell']);
|
||||
assertLog(['Shell']);
|
||||
expect(div.current).toBe(dehydratedDiv);
|
||||
expect(container.textContent).toBe('Shell');
|
||||
});
|
||||
@@ -213,12 +217,12 @@ describe('ReactDOMFizzShellHydration', () => {
|
||||
await clientAct(async () => {
|
||||
root.render(<App />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Suspend! [Shell]']);
|
||||
assertLog(['Suspend! [Shell]']);
|
||||
|
||||
await clientAct(async () => {
|
||||
await resolveText('Shell');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Shell']);
|
||||
assertLog(['Shell']);
|
||||
expect(container.textContent).toBe('Shell');
|
||||
});
|
||||
|
||||
@@ -236,7 +240,7 @@ describe('ReactDOMFizzShellHydration', () => {
|
||||
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
|
||||
pipe(writable);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Initial']);
|
||||
assertLog(['Initial']);
|
||||
|
||||
await clientAct(async () => {
|
||||
const root = ReactDOMClient.hydrateRoot(container, <App />);
|
||||
@@ -246,7 +250,7 @@ describe('ReactDOMFizzShellHydration', () => {
|
||||
root.render(<Text text="Updated" />);
|
||||
});
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Initial', 'Updated']);
|
||||
assertLog(['Initial', 'Updated']);
|
||||
expect(container.textContent).toBe('Updated');
|
||||
},
|
||||
);
|
||||
@@ -262,7 +266,7 @@ describe('ReactDOMFizzShellHydration', () => {
|
||||
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(<App />);
|
||||
pipe(writable);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Shell']);
|
||||
assertLog(['Shell']);
|
||||
|
||||
// Clear the cache and start rendering on the client
|
||||
resetTextCache();
|
||||
@@ -275,13 +279,13 @@ describe('ReactDOMFizzShellHydration', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Suspend! [Shell]']);
|
||||
assertLog(['Suspend! [Shell]']);
|
||||
expect(container.textContent).toBe('Shell');
|
||||
|
||||
await clientAct(async () => {
|
||||
root.render(<Text text="New screen" />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'New screen',
|
||||
'This root received an early update, before anything was able ' +
|
||||
'hydrate. Switched the entire root to client rendering.',
|
||||
|
||||
@@ -16,6 +16,8 @@ let getCacheForType;
|
||||
let caches;
|
||||
let seededCache;
|
||||
let ErrorBoundary;
|
||||
let waitForAll;
|
||||
let assertLog;
|
||||
|
||||
// TODO: These tests don't pass in persistent mode yet. Need to implement.
|
||||
|
||||
@@ -31,6 +33,10 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
|
||||
getCacheForType = React.unstable_getCacheForType;
|
||||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
waitForAll = InternalTestUtils.waitForAll;
|
||||
assertLog = InternalTestUtils.assertLog;
|
||||
|
||||
caches = [];
|
||||
seededCache = null;
|
||||
|
||||
@@ -256,7 +262,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
</App>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'App render',
|
||||
'Text:Inside:Before render',
|
||||
'Suspend:Async',
|
||||
@@ -281,7 +287,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
await resolveText('Async');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Inside:Before render',
|
||||
'AsyncText:Async render',
|
||||
'ClassText:Inside:After render',
|
||||
@@ -305,7 +311,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(null);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'App destroy layout',
|
||||
'Text:Inside:Before destroy layout',
|
||||
'AsyncText:Async destroy layout',
|
||||
@@ -377,7 +383,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
</App>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'App render',
|
||||
'Text:Inside:Before render',
|
||||
'Suspend:Async',
|
||||
@@ -407,7 +413,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
await resolveText('Async');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'AsyncText:Async render',
|
||||
'Text:Fallback destroy layout',
|
||||
'AsyncText:Async create layout',
|
||||
@@ -426,7 +432,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
ReactNoop.renderLegacySyncRoot(null);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'App destroy layout',
|
||||
'Text:Inside:Before destroy layout',
|
||||
'AsyncText:Async destroy layout',
|
||||
@@ -474,7 +480,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
act(() => {
|
||||
ReactNoop.renderLegacySyncRoot(<App />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'App render',
|
||||
'Text:Inside:Before render',
|
||||
'Text:Inside:After render',
|
||||
@@ -504,7 +510,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
</App>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'App render',
|
||||
'Text:Inside:Before render',
|
||||
'Suspend:Async',
|
||||
@@ -526,7 +532,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await advanceTimers(1000);
|
||||
|
||||
// Noop since sync root has already committed
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
assertLog([]);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Inside:Before" hidden={true} />
|
||||
@@ -540,7 +546,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
await resolveText('Async');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'AsyncText:Async render',
|
||||
'Text:Fallback destroy layout',
|
||||
'AsyncText:Async create layout',
|
||||
@@ -559,7 +565,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
ReactNoop.renderLegacySyncRoot(null);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'App destroy layout',
|
||||
'Text:Inside:Before destroy layout',
|
||||
'AsyncText:Async destroy layout',
|
||||
@@ -604,7 +610,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(<App />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'App render',
|
||||
'Text:Inside:Before render',
|
||||
'Text:Inside:After render',
|
||||
@@ -634,7 +640,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
</App>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'App render',
|
||||
'Text:Inside:Before render',
|
||||
'Suspend:Async',
|
||||
@@ -653,12 +659,12 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await advanceTimers(1000);
|
||||
|
||||
// Timing out should commit the fallback and destroy inner layout effects.
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Inside:Before destroy layout',
|
||||
'Text:Inside:After destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
]);
|
||||
expect(Scheduler).toFlushAndYield(['Text:Fallback create passive']);
|
||||
await waitForAll(['Text:Fallback create passive']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Inside:Before" hidden={true} />
|
||||
@@ -672,7 +678,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
await resolveText('Async');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Inside:Before render',
|
||||
'AsyncText:Async render',
|
||||
'Text:Inside:After render',
|
||||
@@ -695,7 +701,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(null);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'App destroy layout',
|
||||
'Text:Inside:Before destroy layout',
|
||||
'AsyncText:Async destroy layout',
|
||||
@@ -763,7 +769,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(<App />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'App render',
|
||||
'ClassText:Inside:Before render',
|
||||
'ClassText:Inside:After render',
|
||||
@@ -790,7 +796,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
</App>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'App render',
|
||||
'ClassText:Inside:Before render',
|
||||
'Suspend:Async',
|
||||
@@ -809,7 +815,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await advanceTimers(1000);
|
||||
|
||||
// Timing out should commit the fallback and destroy inner layout effects.
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'ClassText:Inside:Before componentWillUnmount',
|
||||
'ClassText:Inside:After componentWillUnmount',
|
||||
'ClassText:Fallback componentDidMount',
|
||||
@@ -828,7 +834,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
await resolveText('Async');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'ClassText:Inside:Before render',
|
||||
'AsyncText:Async render',
|
||||
'ClassText:Inside:After render',
|
||||
@@ -849,7 +855,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(null);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'App destroy layout',
|
||||
'ClassText:Inside:Before componentWillUnmount',
|
||||
'AsyncText:Async destroy layout',
|
||||
@@ -890,7 +896,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(<App />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'App render',
|
||||
'Text:Outer render',
|
||||
'Text:Inner render',
|
||||
@@ -915,7 +921,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
</App>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'Text:Outer render',
|
||||
@@ -931,12 +937,12 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await advanceTimers(1000);
|
||||
|
||||
// Timing out should commit the fallback and destroy inner layout effects.
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Outer destroy layout',
|
||||
'Text:Inner destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
]);
|
||||
expect(Scheduler).toFlushAndYield(['Text:Fallback create passive']);
|
||||
await waitForAll(['Text:Fallback create passive']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span hidden={true} prop="Outer">
|
||||
@@ -950,7 +956,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
await resolveText('Async');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'AsyncText:Async render',
|
||||
'Text:Outer render',
|
||||
'Text:Inner render',
|
||||
@@ -973,7 +979,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(null);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'App destroy layout',
|
||||
'AsyncText:Async destroy layout',
|
||||
'Text:Outer destroy layout',
|
||||
@@ -1017,7 +1023,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(<App />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'App render',
|
||||
'Text:Outer render',
|
||||
'Text:MemoizedInner render',
|
||||
@@ -1042,7 +1048,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
</App>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'Text:Outer render',
|
||||
@@ -1059,12 +1065,12 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
|
||||
// Timing out should commit the fallback and destroy inner layout effects.
|
||||
// Even though the innermost layout effects are beneath a hidden HostComponent.
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Outer destroy layout',
|
||||
'Text:MemoizedInner destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
]);
|
||||
expect(Scheduler).toFlushAndYield(['Text:Fallback create passive']);
|
||||
await waitForAll(['Text:Fallback create passive']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span hidden={true} prop="Outer">
|
||||
@@ -1078,7 +1084,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
await resolveText('Async');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'AsyncText:Async render',
|
||||
'Text:Outer render',
|
||||
'Text:Fallback destroy layout',
|
||||
@@ -1100,7 +1106,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(null);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'App destroy layout',
|
||||
'AsyncText:Async destroy layout',
|
||||
'Text:Outer destroy layout',
|
||||
@@ -1131,7 +1137,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(<App />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Outer render',
|
||||
'Text:Inner render',
|
||||
'Text:Outer create layout',
|
||||
@@ -1153,7 +1159,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
);
|
||||
});
|
||||
await advanceTimers(1000);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Outer render',
|
||||
'Text:Inner render',
|
||||
'Suspend:InnerAsync_1',
|
||||
@@ -1161,7 +1167,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
'Text:Inner destroy layout',
|
||||
'Text:InnerFallback create layout',
|
||||
]);
|
||||
expect(Scheduler).toFlushAndYield(['Text:InnerFallback create passive']);
|
||||
await waitForAll(['Text:InnerFallback create passive']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Outer" />
|
||||
@@ -1181,7 +1187,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
);
|
||||
});
|
||||
await advanceTimers(1000);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Outer render',
|
||||
'Suspend:OuterAsync_1',
|
||||
'Text:Inner render',
|
||||
@@ -1192,7 +1198,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
'Text:InnerFallback destroy layout',
|
||||
'Text:OuterFallback create layout',
|
||||
]);
|
||||
expect(Scheduler).toFlushAndYield(['Text:OuterFallback create passive']);
|
||||
await waitForAll(['Text:OuterFallback create passive']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Outer" hidden={true} />
|
||||
@@ -1206,7 +1212,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
await resolveText('InnerAsync_1');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Outer render',
|
||||
'Suspend:OuterAsync_1',
|
||||
'Text:Inner render',
|
||||
@@ -1231,7 +1237,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
);
|
||||
});
|
||||
await advanceTimers(1000);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Outer render',
|
||||
'Suspend:OuterAsync_1',
|
||||
'Text:Inner render',
|
||||
@@ -1252,7 +1258,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
await resolveText('OuterAsync_1');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Outer render',
|
||||
'AsyncText:OuterAsync_1 render',
|
||||
'Text:Inner render',
|
||||
@@ -1278,7 +1284,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
await resolveText('InnerAsync_2');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Inner render',
|
||||
'AsyncText:InnerAsync_2 render',
|
||||
'Text:InnerFallback destroy layout',
|
||||
@@ -1306,7 +1312,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
);
|
||||
});
|
||||
await advanceTimers(1000);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Outer render',
|
||||
'Suspend:OuterAsync_2',
|
||||
'Text:Inner render',
|
||||
@@ -1332,7 +1338,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
await resolveText('OuterAsync_2');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:OuterFallback create passive',
|
||||
'Text:Outer render',
|
||||
'AsyncText:OuterAsync_2 render',
|
||||
@@ -1374,7 +1380,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(<App />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Outer render',
|
||||
'Text:Inner render',
|
||||
'Text:Outer create layout',
|
||||
@@ -1396,7 +1402,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
);
|
||||
});
|
||||
await advanceTimers(1000);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Outer render',
|
||||
'Text:Inner render',
|
||||
'Suspend:InnerAsync_1',
|
||||
@@ -1404,7 +1410,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
'Text:Inner destroy layout',
|
||||
'Text:InnerFallback create layout',
|
||||
]);
|
||||
expect(Scheduler).toFlushAndYield(['Text:InnerFallback create passive']);
|
||||
await waitForAll(['Text:InnerFallback create passive']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Outer" />
|
||||
@@ -1424,7 +1430,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
);
|
||||
});
|
||||
await advanceTimers(1000);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Outer render',
|
||||
'Suspend:OuterAsync_1',
|
||||
'Text:Inner render',
|
||||
@@ -1435,7 +1441,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
'Text:InnerFallback destroy layout',
|
||||
'Text:OuterFallback create layout',
|
||||
]);
|
||||
expect(Scheduler).toFlushAndYield(['Text:OuterFallback create passive']);
|
||||
await waitForAll(['Text:OuterFallback create passive']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Outer" hidden={true} />
|
||||
@@ -1450,7 +1456,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await resolveText('OuterAsync_1');
|
||||
await resolveText('InnerAsync_1');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Outer render',
|
||||
'AsyncText:OuterAsync_1 render',
|
||||
'Text:Inner render',
|
||||
@@ -1502,7 +1508,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(<App />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Inside render',
|
||||
'Text:Outside render',
|
||||
'Text:Inside create layout',
|
||||
@@ -1523,7 +1529,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
<App outerChildren={<AsyncText text="OutsideAsync" ms={1000} />} />,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Inside render',
|
||||
'Suspend:OutsideAsync',
|
||||
'Text:Fallback:Inside render',
|
||||
@@ -1539,12 +1545,12 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
|
||||
// Timing out should commit the fallback and destroy inner layout effects.
|
||||
await advanceTimers(1000);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Inside destroy layout',
|
||||
'Text:Fallback:Inside create layout',
|
||||
'Text:Fallback:Outside create layout',
|
||||
]);
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
await waitForAll([
|
||||
'Text:Fallback:Inside create passive',
|
||||
'Text:Fallback:Outside create passive',
|
||||
]);
|
||||
@@ -1566,7 +1572,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
/>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Inside render',
|
||||
'Suspend:OutsideAsync',
|
||||
'Text:Fallback:Inside render',
|
||||
@@ -1586,13 +1592,11 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
|
||||
// Timing out should commit the inner fallback and destroy outer fallback layout effects.
|
||||
await advanceTimers(1000);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Fallback:Inside destroy layout',
|
||||
'Text:Fallback:Fallback create layout',
|
||||
]);
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
'Text:Fallback:Fallback create passive',
|
||||
]);
|
||||
await waitForAll(['Text:Fallback:Fallback create passive']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Inside" hidden={true} />
|
||||
@@ -1608,7 +1612,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await resolveText('FallbackAsync');
|
||||
await resolveText('OutsideAsync');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Inside render',
|
||||
'AsyncText:OutsideAsync render',
|
||||
'Text:Fallback:Fallback destroy layout',
|
||||
@@ -1656,7 +1660,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(<App />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Inside render',
|
||||
'Text:Outside render',
|
||||
'Text:Inside create layout',
|
||||
@@ -1681,7 +1685,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
);
|
||||
});
|
||||
await advanceTimers(1000);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Inside render',
|
||||
'Suspend:OutsideAsync',
|
||||
'Text:Fallback:Inside render',
|
||||
@@ -1693,7 +1697,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
'Text:Fallback:Fallback create layout',
|
||||
'Text:Fallback:Outside create layout',
|
||||
]);
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
await waitForAll([
|
||||
'Text:Fallback:Fallback create passive',
|
||||
'Text:Fallback:Outside create passive',
|
||||
]);
|
||||
@@ -1710,7 +1714,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
await resolveText('FallbackAsync');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Fallback:Inside render',
|
||||
'AsyncText:FallbackAsync render',
|
||||
'Text:Fallback:Fallback destroy layout',
|
||||
@@ -1734,7 +1738,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
await resolveText('OutsideAsync');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Inside render',
|
||||
'AsyncText:OutsideAsync render',
|
||||
'Text:Fallback:Inside destroy layout',
|
||||
@@ -1780,7 +1784,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(<App shouldSuspend={false} />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Inside render',
|
||||
'Text:Outside render',
|
||||
'Text:Inside create layout',
|
||||
@@ -1800,7 +1804,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
act(() => {
|
||||
ReactNoop.render(<App shouldSuspend={true} />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Suspend:Suspend',
|
||||
'Text:Fallback render',
|
||||
'Text:Outside render',
|
||||
@@ -1814,11 +1818,8 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
|
||||
// Timing out should commit the inner fallback and destroy outer fallback layout effects.
|
||||
await advanceTimers(1000);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Text:Inside destroy layout',
|
||||
'Text:Fallback create layout',
|
||||
]);
|
||||
expect(Scheduler).toFlushAndYield(['Text:Fallback create passive']);
|
||||
assertLog(['Text:Inside destroy layout', 'Text:Fallback create layout']);
|
||||
await waitForAll(['Text:Fallback create passive']);
|
||||
expect(ReactNoop).toMatchRenderedOutput(
|
||||
<>
|
||||
<span prop="Inside" hidden={true} />
|
||||
@@ -1831,7 +1832,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
await resolveText('Suspend');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Inside render',
|
||||
'Text:Fallback destroy layout',
|
||||
'Text:Inside create layout',
|
||||
@@ -1895,7 +1896,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
</ErrorBoundary>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'ErrorBoundary render: try',
|
||||
'App render',
|
||||
'ThrowsInDidMount render',
|
||||
@@ -1926,7 +1927,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
</ErrorBoundary>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'ErrorBoundary render: try',
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
@@ -1953,7 +1954,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
await resolveText('Async');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'AsyncText:Async render',
|
||||
'ThrowsInDidMount render',
|
||||
'Text:Inside render',
|
||||
@@ -2035,7 +2036,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
</ErrorBoundary>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'ErrorBoundary render: try',
|
||||
'App render',
|
||||
'ThrowsInWillUnmount render',
|
||||
@@ -2066,7 +2067,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
</ErrorBoundary>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'ErrorBoundary render: try',
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
@@ -2151,7 +2152,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
</ErrorBoundary>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'ErrorBoundary render: try',
|
||||
'App render',
|
||||
'ThrowsInLayoutEffect render',
|
||||
@@ -2182,7 +2183,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
</ErrorBoundary>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'ErrorBoundary render: try',
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
@@ -2209,7 +2210,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
await resolveText('Async');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'AsyncText:Async render',
|
||||
'ThrowsInLayoutEffect render',
|
||||
'Text:Inside render',
|
||||
@@ -2290,7 +2291,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
</ErrorBoundary>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'ErrorBoundary render: try',
|
||||
'App render',
|
||||
'ThrowsInLayoutEffectDestroy render',
|
||||
@@ -2321,7 +2322,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
</ErrorBoundary>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'ErrorBoundary render: try',
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
@@ -2394,7 +2395,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(<App />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Function render',
|
||||
'ClassText:Class render',
|
||||
'Text:Function create layout',
|
||||
@@ -2417,7 +2418,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
</App>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Function render',
|
||||
'Suspend:Async_1',
|
||||
'Suspend:Async_2',
|
||||
@@ -2434,7 +2435,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await advanceTimers(1000);
|
||||
|
||||
// Timing out should commit the fallback and destroy inner layout effects.
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Function destroy layout',
|
||||
'ClassText:Class componentWillUnmount',
|
||||
'ClassText:Fallback componentDidMount',
|
||||
@@ -2451,7 +2452,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
await resolveText('Async_1');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Function render',
|
||||
'AsyncText:Async_1 render',
|
||||
'Suspend:Async_2',
|
||||
@@ -2469,7 +2470,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
await resolveText('Async_2');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Function render',
|
||||
'AsyncText:Async_1 render',
|
||||
'AsyncText:Async_2 render',
|
||||
@@ -2494,7 +2495,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(null);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Function destroy layout',
|
||||
'AsyncText:Async_1 destroy layout',
|
||||
'AsyncText:Async_2 destroy layout',
|
||||
@@ -2552,7 +2553,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(<App />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Function render',
|
||||
'Suspender "null" render',
|
||||
'ClassText:Class render',
|
||||
@@ -2573,7 +2574,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
act(() => {
|
||||
ReactNoop.render(<App />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Function render',
|
||||
'Suspender "A" render',
|
||||
'Suspend:A',
|
||||
@@ -2591,7 +2592,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await advanceTimers(1000);
|
||||
|
||||
// Timing out should commit the fallback and destroy inner layout effects.
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Function destroy layout',
|
||||
'ClassText:Class componentWillUnmount',
|
||||
'ClassText:Fallback componentDidMount',
|
||||
@@ -2610,7 +2611,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
await resolveText('A');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Function render',
|
||||
'Suspender "B" render',
|
||||
'Suspend:B',
|
||||
@@ -2629,7 +2630,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
await resolveText('B');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Function render',
|
||||
'Suspender "B" render',
|
||||
'ClassText:Class render',
|
||||
@@ -2648,7 +2649,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(null);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Function destroy layout',
|
||||
'ClassText:Class componentWillUnmount',
|
||||
'Text:Function destroy passive',
|
||||
@@ -2740,7 +2741,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
act(() => {
|
||||
ReactNoop.renderLegacySyncRoot(<App />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'App render',
|
||||
'RefCheckerOuter render',
|
||||
'ClassComponent:refObject render',
|
||||
@@ -2761,7 +2762,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
);
|
||||
});
|
||||
await advanceTimers(1000);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'RefCheckerOuter render',
|
||||
@@ -2779,7 +2780,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
await resolveText('Async');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'AsyncText:Async render',
|
||||
'Text:Fallback destroy layout',
|
||||
'AsyncText:Async create layout',
|
||||
@@ -2791,7 +2792,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
ReactNoop.renderLegacySyncRoot(null);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'AsyncText:Async destroy layout',
|
||||
'RefCheckerOuter destroy layout refObject? true refCallback? true',
|
||||
'RefCheckerInner:refObject destroy layout ref? false',
|
||||
@@ -2818,7 +2819,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(<App />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'App render',
|
||||
'RefCheckerOuter render',
|
||||
'RefCheckerInner:refObject render',
|
||||
@@ -2842,7 +2843,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
);
|
||||
});
|
||||
await advanceTimers(1000);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'RefCheckerOuter render',
|
||||
@@ -2867,7 +2868,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
await resolveText('Async');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Fallback create passive',
|
||||
'AsyncText:Async render',
|
||||
'RefCheckerOuter render',
|
||||
@@ -2893,7 +2894,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(null);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'AsyncText:Async destroy layout',
|
||||
'RefCheckerOuter destroy layout refObject? true refCallback? true',
|
||||
'RefCheckerInner:refObject destroy layout ref? false',
|
||||
@@ -2929,7 +2930,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(<App />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'App render',
|
||||
'RefCheckerOuter render',
|
||||
'ClassComponent:refObject render',
|
||||
@@ -2950,7 +2951,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
);
|
||||
});
|
||||
await advanceTimers(1000);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'RefCheckerOuter render',
|
||||
@@ -2971,7 +2972,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
await resolveText('Async');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Fallback create passive',
|
||||
'AsyncText:Async render',
|
||||
'RefCheckerOuter render',
|
||||
@@ -2993,7 +2994,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(null);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'AsyncText:Async destroy layout',
|
||||
'RefCheckerOuter destroy layout refObject? true refCallback? true',
|
||||
'RefCheckerInner:refObject destroy layout ref? false',
|
||||
@@ -3033,7 +3034,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(<App />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'App render',
|
||||
'RefCheckerOuter render',
|
||||
'FunctionComponent render',
|
||||
@@ -3054,7 +3055,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
);
|
||||
});
|
||||
await advanceTimers(1000);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'RefCheckerOuter render',
|
||||
@@ -3075,7 +3076,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
await resolveText('Async');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Fallback create passive',
|
||||
'AsyncText:Async render',
|
||||
'RefCheckerOuter render',
|
||||
@@ -3097,7 +3098,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(null);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'AsyncText:Async destroy layout',
|
||||
'RefCheckerOuter destroy layout refObject? true refCallback? true',
|
||||
'RefCheckerInner:refObject destroy layout ref? false',
|
||||
@@ -3152,7 +3153,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(<App />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'App render',
|
||||
'RefChecker render',
|
||||
'RefChecker create layout ref? true',
|
||||
@@ -3167,7 +3168,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
);
|
||||
});
|
||||
await advanceTimers(1000);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
'RefChecker render',
|
||||
@@ -3181,7 +3182,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
await resolveText('Async');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Text:Fallback create passive',
|
||||
'AsyncText:Async render',
|
||||
'RefChecker render',
|
||||
@@ -3196,7 +3197,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(null);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'App destroy layout ref? true',
|
||||
'AsyncText:Async destroy layout',
|
||||
'RefChecker destroy layout ref? true',
|
||||
@@ -3251,7 +3252,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
</ErrorBoundary>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'ErrorBoundary render: try',
|
||||
'App render',
|
||||
'ThrowsInRefCallback render',
|
||||
@@ -3282,7 +3283,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
</ErrorBoundary>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'ErrorBoundary render: try',
|
||||
'App render',
|
||||
'Suspend:Async',
|
||||
@@ -3309,7 +3310,7 @@ describe('ReactSuspenseEffectsSemantics', () => {
|
||||
await act(async () => {
|
||||
await resolveText('Async');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'AsyncText:Async render',
|
||||
'ThrowsInRefCallback render',
|
||||
'Text:Inside render',
|
||||
|
||||
@@ -19,6 +19,10 @@ let Scheduler;
|
||||
let act;
|
||||
let createMutableSource;
|
||||
let useMutableSource;
|
||||
let waitFor;
|
||||
let waitForAll;
|
||||
let assertLog;
|
||||
let waitForPaint;
|
||||
|
||||
function loadModules() {
|
||||
jest.resetModules();
|
||||
@@ -32,6 +36,12 @@ function loadModules() {
|
||||
Scheduler = require('scheduler');
|
||||
act = require('jest-react').act;
|
||||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
waitFor = InternalTestUtils.waitFor;
|
||||
waitForAll = InternalTestUtils.waitForAll;
|
||||
waitForPaint = InternalTestUtils.waitForPaint;
|
||||
assertLog = InternalTestUtils.assertLog;
|
||||
|
||||
// Stable entrypoints export with "unstable_" prefix.
|
||||
createMutableSource =
|
||||
React.createMutableSource || React.unstable_createMutableSource;
|
||||
@@ -142,11 +152,11 @@ describe('useMutableSource', () => {
|
||||
beforeEach(loadModules);
|
||||
|
||||
// @gate enableUseMutableSource
|
||||
it('should subscribe to a source and schedule updates when it changes', () => {
|
||||
it('should subscribe to a source and schedule updates when it changes', async () => {
|
||||
const source = createSource('one');
|
||||
const mutableSource = createMutableSource(source, param => param.version);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.renderToRootWithID(
|
||||
<>
|
||||
<Component
|
||||
@@ -165,11 +175,7 @@ describe('useMutableSource', () => {
|
||||
'root',
|
||||
() => Scheduler.unstable_yieldValue('Sync effect'),
|
||||
);
|
||||
expect(Scheduler).toFlushAndYieldThrough([
|
||||
'a:one',
|
||||
'b:one',
|
||||
'Sync effect',
|
||||
]);
|
||||
await waitFor(['a:one', 'b:one', 'Sync effect']);
|
||||
|
||||
// Subscriptions should be passive
|
||||
expect(source.listenerCount).toBe(0);
|
||||
@@ -178,7 +184,7 @@ describe('useMutableSource', () => {
|
||||
|
||||
// Changing values should schedule an update with React
|
||||
source.value = 'two';
|
||||
expect(Scheduler).toFlushAndYieldThrough(['a:two', 'b:two']);
|
||||
await waitFor(['a:two', 'b:two']);
|
||||
|
||||
// Unmounting a component should remove its subscription.
|
||||
ReactNoop.renderToRootWithID(
|
||||
@@ -193,28 +199,28 @@ describe('useMutableSource', () => {
|
||||
'root',
|
||||
() => Scheduler.unstable_yieldValue('Sync effect'),
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield(['a:two', 'Sync effect']);
|
||||
await waitForAll(['a:two', 'Sync effect']);
|
||||
ReactNoop.flushPassiveEffects();
|
||||
expect(source.listenerCount).toBe(1);
|
||||
|
||||
// Unmounting a root should remove the remaining event listeners
|
||||
ReactNoop.unmountRootWithID('root');
|
||||
expect(Scheduler).toFlushAndYield([]);
|
||||
await waitForAll([]);
|
||||
ReactNoop.flushPassiveEffects();
|
||||
expect(source.listenerCount).toBe(0);
|
||||
|
||||
// Changes to source should not trigger an updates or warnings.
|
||||
source.value = 'three';
|
||||
expect(Scheduler).toFlushAndYield([]);
|
||||
await waitForAll([]);
|
||||
});
|
||||
});
|
||||
|
||||
// @gate enableUseMutableSource
|
||||
it('should restart work if a new source is mutated during render', () => {
|
||||
it('should restart work if a new source is mutated during render', async () => {
|
||||
const source = createSource('one');
|
||||
const mutableSource = createMutableSource(source, param => param.version);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
React.startTransition(() => {
|
||||
ReactNoop.render(
|
||||
<>
|
||||
@@ -235,22 +241,22 @@ describe('useMutableSource', () => {
|
||||
);
|
||||
});
|
||||
// Do enough work to read from one component
|
||||
expect(Scheduler).toFlushAndYieldThrough(['a:one']);
|
||||
await waitFor(['a:one']);
|
||||
|
||||
// Mutate source before continuing work
|
||||
source.value = 'two';
|
||||
|
||||
// Render work should restart and the updated value should be used
|
||||
expect(Scheduler).toFlushAndYield(['a:two', 'b:two', 'Sync effect']);
|
||||
await waitForAll(['a:two', 'b:two', 'Sync effect']);
|
||||
});
|
||||
});
|
||||
|
||||
// @gate enableUseMutableSource
|
||||
it('should schedule an update if a new source is mutated between render and commit (subscription)', () => {
|
||||
it('should schedule an update if a new source is mutated between render and commit (subscription)', async () => {
|
||||
const source = createSource('one');
|
||||
const mutableSource = createMutableSource(source, param => param.version);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<>
|
||||
<Component
|
||||
@@ -270,23 +276,19 @@ describe('useMutableSource', () => {
|
||||
);
|
||||
|
||||
// Finish rendering
|
||||
expect(Scheduler).toFlushAndYieldThrough([
|
||||
'a:one',
|
||||
'b:one',
|
||||
'Sync effect',
|
||||
]);
|
||||
await waitFor(['a:one', 'b:one', 'Sync effect']);
|
||||
|
||||
// Mutate source before subscriptions are attached
|
||||
expect(source.listenerCount).toBe(0);
|
||||
source.value = 'two';
|
||||
|
||||
// Mutation should be detected, and a new render should be scheduled
|
||||
expect(Scheduler).toFlushAndYield(['a:two', 'b:two']);
|
||||
await waitForAll(['a:two', 'b:two']);
|
||||
});
|
||||
});
|
||||
|
||||
// @gate enableUseMutableSource
|
||||
it('should unsubscribe and resubscribe if a new source is used', () => {
|
||||
it('should unsubscribe and resubscribe if a new source is used', async () => {
|
||||
const sourceA = createSource('a-one');
|
||||
const mutableSourceA = createMutableSource(
|
||||
sourceA,
|
||||
@@ -299,7 +301,7 @@ describe('useMutableSource', () => {
|
||||
param => param.versionB,
|
||||
);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<Component
|
||||
label="only"
|
||||
@@ -309,13 +311,13 @@ describe('useMutableSource', () => {
|
||||
/>,
|
||||
() => Scheduler.unstable_yieldValue('Sync effect'),
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield(['only:a-one', 'Sync effect']);
|
||||
await waitForAll(['only:a-one', 'Sync effect']);
|
||||
ReactNoop.flushPassiveEffects();
|
||||
expect(sourceA.listenerCount).toBe(1);
|
||||
|
||||
// Changing values should schedule an update with React
|
||||
sourceA.value = 'a-two';
|
||||
expect(Scheduler).toFlushAndYield(['only:a-two']);
|
||||
await waitForAll(['only:a-two']);
|
||||
|
||||
// If we re-render with a new source, the old one should be unsubscribed.
|
||||
ReactNoop.render(
|
||||
@@ -327,23 +329,23 @@ describe('useMutableSource', () => {
|
||||
/>,
|
||||
() => Scheduler.unstable_yieldValue('Sync effect'),
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield(['only:b-one', 'Sync effect']);
|
||||
await waitForAll(['only:b-one', 'Sync effect']);
|
||||
ReactNoop.flushPassiveEffects();
|
||||
expect(sourceA.listenerCount).toBe(0);
|
||||
expect(sourceB.listenerCount).toBe(1);
|
||||
|
||||
// Changing to original source should not schedule updates with React
|
||||
sourceA.value = 'a-three';
|
||||
expect(Scheduler).toFlushAndYield([]);
|
||||
await waitForAll([]);
|
||||
|
||||
// Changing new source value should schedule an update with React
|
||||
sourceB.value = 'b-two';
|
||||
expect(Scheduler).toFlushAndYield(['only:b-two']);
|
||||
await waitForAll(['only:b-two']);
|
||||
});
|
||||
});
|
||||
|
||||
// @gate enableUseMutableSource
|
||||
it('should unsubscribe and resubscribe if a new subscribe function is provided', () => {
|
||||
it('should unsubscribe and resubscribe if a new subscribe function is provided', async () => {
|
||||
const source = createSource('a-one');
|
||||
const mutableSource = createMutableSource(source, param => param.version);
|
||||
|
||||
@@ -364,7 +366,7 @@ describe('useMutableSource', () => {
|
||||
};
|
||||
});
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.renderToRootWithID(
|
||||
<Component
|
||||
label="only"
|
||||
@@ -375,7 +377,7 @@ describe('useMutableSource', () => {
|
||||
'root',
|
||||
() => Scheduler.unstable_yieldValue('Sync effect'),
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield(['only:a-one', 'Sync effect']);
|
||||
await waitForAll(['only:a-one', 'Sync effect']);
|
||||
ReactNoop.flushPassiveEffects();
|
||||
expect(source.listenerCount).toBe(1);
|
||||
expect(subscribeA).toHaveBeenCalledTimes(1);
|
||||
@@ -392,7 +394,7 @@ describe('useMutableSource', () => {
|
||||
'root',
|
||||
() => Scheduler.unstable_yieldValue('Sync effect'),
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield(['only:a-one', 'Sync effect']);
|
||||
await waitForAll(['only:a-one', 'Sync effect']);
|
||||
ReactNoop.flushPassiveEffects();
|
||||
expect(source.listenerCount).toBe(1);
|
||||
expect(unsubscribeA).toHaveBeenCalledTimes(1);
|
||||
@@ -400,7 +402,7 @@ describe('useMutableSource', () => {
|
||||
|
||||
// Unmounting should call the newer unsubscribe.
|
||||
ReactNoop.unmountRootWithID('root');
|
||||
expect(Scheduler).toFlushAndYield([]);
|
||||
await waitForAll([]);
|
||||
ReactNoop.flushPassiveEffects();
|
||||
expect(source.listenerCount).toBe(0);
|
||||
expect(unsubscribeB).toHaveBeenCalledTimes(1);
|
||||
@@ -408,11 +410,11 @@ describe('useMutableSource', () => {
|
||||
});
|
||||
|
||||
// @gate enableUseMutableSource
|
||||
it('should re-use previously read snapshot value when reading is unsafe', () => {
|
||||
it('should re-use previously read snapshot value when reading is unsafe', async () => {
|
||||
const source = createSource('one');
|
||||
const mutableSource = createMutableSource(source, param => param.version);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<>
|
||||
<Component
|
||||
@@ -430,14 +432,14 @@ describe('useMutableSource', () => {
|
||||
</>,
|
||||
() => Scheduler.unstable_yieldValue('Sync effect'),
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield(['a:one', 'b:one', 'Sync effect']);
|
||||
await waitForAll(['a:one', 'b:one', 'Sync effect']);
|
||||
|
||||
// Changing values should schedule an update with React.
|
||||
// Start working on this update but don't finish it.
|
||||
React.startTransition(() => {
|
||||
source.value = 'two';
|
||||
});
|
||||
expect(Scheduler).toFlushAndYieldThrough(['a:two']);
|
||||
await waitFor(['a:two']);
|
||||
|
||||
// Re-renders that occur before the update is processed
|
||||
// should reuse snapshot so long as the config has not changed
|
||||
@@ -460,18 +462,18 @@ describe('useMutableSource', () => {
|
||||
() => Scheduler.unstable_yieldValue('Sync effect'),
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['a:one', 'b:one', 'Sync effect']);
|
||||
assertLog(['a:one', 'b:one', 'Sync effect']);
|
||||
|
||||
expect(Scheduler).toFlushAndYield(['a:two', 'b:two']);
|
||||
await waitForAll(['a:two', 'b:two']);
|
||||
});
|
||||
});
|
||||
|
||||
// @gate enableUseMutableSource
|
||||
it('should read from source on newly mounted subtree if no pending updates are scheduled for source', () => {
|
||||
it('should read from source on newly mounted subtree if no pending updates are scheduled for source', async () => {
|
||||
const source = createSource('one');
|
||||
const mutableSource = createMutableSource(source, param => param.version);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<>
|
||||
<Component
|
||||
@@ -483,7 +485,7 @@ describe('useMutableSource', () => {
|
||||
</>,
|
||||
() => Scheduler.unstable_yieldValue('Sync effect'),
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield(['a:one', 'Sync effect']);
|
||||
await waitForAll(['a:one', 'Sync effect']);
|
||||
|
||||
ReactNoop.render(
|
||||
<>
|
||||
@@ -502,16 +504,16 @@ describe('useMutableSource', () => {
|
||||
</>,
|
||||
() => Scheduler.unstable_yieldValue('Sync effect'),
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield(['a:one', 'b:one', 'Sync effect']);
|
||||
await waitForAll(['a:one', 'b:one', 'Sync effect']);
|
||||
});
|
||||
});
|
||||
|
||||
// @gate enableUseMutableSource
|
||||
it('should throw and restart render if source and snapshot are unavailable during an update', () => {
|
||||
it('should throw and restart render if source and snapshot are unavailable during an update', async () => {
|
||||
const source = createSource('one');
|
||||
const mutableSource = createMutableSource(source, param => param.version);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<>
|
||||
<Component
|
||||
@@ -529,16 +531,17 @@ describe('useMutableSource', () => {
|
||||
</>,
|
||||
() => Scheduler.unstable_yieldValue('Sync effect'),
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield(['a:one', 'b:one', 'Sync effect']);
|
||||
await waitForAll(['a:one', 'b:one', 'Sync effect']);
|
||||
ReactNoop.flushPassiveEffects();
|
||||
|
||||
// Changing values should schedule an update with React.
|
||||
// Start working on this update but don't finish it.
|
||||
ReactNoop.idleUpdates(() => {
|
||||
source.value = 'two';
|
||||
expect(Scheduler).toFlushAndYieldThrough(['a:two']);
|
||||
});
|
||||
|
||||
// Start working on this update but don't finish it.
|
||||
await waitFor(['a:two']);
|
||||
|
||||
const newGetSnapshot = s => 'new:' + defaultGetSnapshot(s);
|
||||
|
||||
// Force a higher priority render with a new config.
|
||||
@@ -562,20 +565,16 @@ describe('useMutableSource', () => {
|
||||
() => Scheduler.unstable_yieldValue('Sync effect'),
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'a:new:two',
|
||||
'b:new:two',
|
||||
'Sync effect',
|
||||
]);
|
||||
assertLog(['a:new:two', 'b:new:two', 'Sync effect']);
|
||||
});
|
||||
});
|
||||
|
||||
// @gate enableUseMutableSource
|
||||
it('should throw and restart render if source and snapshot are unavailable during a sync update', () => {
|
||||
it('should throw and restart render if source and snapshot are unavailable during a sync update', async () => {
|
||||
const source = createSource('one');
|
||||
const mutableSource = createMutableSource(source, param => param.version);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<>
|
||||
<Component
|
||||
@@ -593,16 +592,17 @@ describe('useMutableSource', () => {
|
||||
</>,
|
||||
() => Scheduler.unstable_yieldValue('Sync effect'),
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield(['a:one', 'b:one', 'Sync effect']);
|
||||
await waitForAll(['a:one', 'b:one', 'Sync effect']);
|
||||
ReactNoop.flushPassiveEffects();
|
||||
|
||||
// Changing values should schedule an update with React.
|
||||
// Start working on this update but don't finish it.
|
||||
ReactNoop.idleUpdates(() => {
|
||||
source.value = 'two';
|
||||
expect(Scheduler).toFlushAndYieldThrough(['a:two']);
|
||||
});
|
||||
|
||||
// Start working on this update but don't finish it.
|
||||
await waitFor(['a:two']);
|
||||
|
||||
const newGetSnapshot = s => 'new:' + defaultGetSnapshot(s);
|
||||
|
||||
// Force a higher priority render with a new config.
|
||||
@@ -626,16 +626,12 @@ describe('useMutableSource', () => {
|
||||
() => Scheduler.unstable_yieldValue('Sync effect'),
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'a:new:two',
|
||||
'b:new:two',
|
||||
'Sync effect',
|
||||
]);
|
||||
assertLog(['a:new:two', 'b:new:two', 'Sync effect']);
|
||||
});
|
||||
});
|
||||
|
||||
// @gate enableUseMutableSource
|
||||
it('should only update components whose subscriptions fire', () => {
|
||||
it('should only update components whose subscriptions fire', async () => {
|
||||
const source = createComplexSource('a:one', 'b:one');
|
||||
const mutableSource = createMutableSource(source, param => param.version);
|
||||
|
||||
@@ -645,7 +641,7 @@ describe('useMutableSource', () => {
|
||||
const getSnapshotB = s => s.valueB;
|
||||
const subscribeB = (s, callback) => s.subscribeB(callback);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<>
|
||||
<Component
|
||||
@@ -663,18 +659,18 @@ describe('useMutableSource', () => {
|
||||
</>,
|
||||
() => Scheduler.unstable_yieldValue('Sync effect'),
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield(['a:a:one', 'b:b:one', 'Sync effect']);
|
||||
await waitForAll(['a:a:one', 'b:b:one', 'Sync effect']);
|
||||
|
||||
// Changes to part of the store (e.g. A) should not render other parts.
|
||||
source.valueA = 'a:two';
|
||||
expect(Scheduler).toFlushAndYield(['a:a:two']);
|
||||
await waitForAll(['a:a:two']);
|
||||
source.valueB = 'b:two';
|
||||
expect(Scheduler).toFlushAndYield(['b:b:two']);
|
||||
await waitForAll(['b:b:two']);
|
||||
});
|
||||
});
|
||||
|
||||
// @gate enableUseMutableSource
|
||||
it('should detect tearing in part of the store not yet subscribed to', () => {
|
||||
it('should detect tearing in part of the store not yet subscribed to', async () => {
|
||||
const source = createComplexSource('a:one', 'b:one');
|
||||
const mutableSource = createMutableSource(source, param => param.version);
|
||||
|
||||
@@ -684,7 +680,7 @@ describe('useMutableSource', () => {
|
||||
const getSnapshotB = s => s.valueB;
|
||||
const subscribeB = (s, callback) => s.subscribeB(callback);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<>
|
||||
<Component
|
||||
@@ -696,7 +692,7 @@ describe('useMutableSource', () => {
|
||||
</>,
|
||||
() => Scheduler.unstable_yieldValue('Sync effect'),
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield(['a:a:one', 'Sync effect']);
|
||||
await waitForAll(['a:a:one', 'Sync effect']);
|
||||
|
||||
// Because the store has not changed yet, there are no pending updates,
|
||||
// so it is considered safe to read from when we start this render.
|
||||
@@ -725,29 +721,24 @@ describe('useMutableSource', () => {
|
||||
() => Scheduler.unstable_yieldValue('Sync effect'),
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toFlushAndYieldThrough(['a:a:one', 'b:b:one']);
|
||||
await waitFor(['a:a:one', 'b:b:one']);
|
||||
|
||||
// Mutating the source should trigger a tear detection on the next read,
|
||||
// which should throw and re-render the entire tree.
|
||||
source.valueB = 'b:two';
|
||||
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
'a:a:one',
|
||||
'b:b:two',
|
||||
'c:b:two',
|
||||
'Sync effect',
|
||||
]);
|
||||
await waitForAll(['a:a:one', 'b:b:two', 'c:b:two', 'Sync effect']);
|
||||
});
|
||||
});
|
||||
|
||||
// @gate enableUseMutableSource
|
||||
it('does not schedule an update for subscriptions that fire with an unchanged snapshot', () => {
|
||||
it('does not schedule an update for subscriptions that fire with an unchanged snapshot', async () => {
|
||||
const MockComponent = jest.fn(Component);
|
||||
|
||||
const source = createSource('one');
|
||||
const mutableSource = createMutableSource(source, param => param.version);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<MockComponent
|
||||
label="only"
|
||||
@@ -757,18 +748,18 @@ describe('useMutableSource', () => {
|
||||
/>,
|
||||
() => Scheduler.unstable_yieldValue('Sync effect'),
|
||||
);
|
||||
expect(Scheduler).toFlushAndYieldThrough(['only:one', 'Sync effect']);
|
||||
await waitFor(['only:one', 'Sync effect']);
|
||||
ReactNoop.flushPassiveEffects();
|
||||
expect(source.listenerCount).toBe(1);
|
||||
|
||||
// Notify subscribe function but don't change the value
|
||||
source.value = 'one';
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
});
|
||||
});
|
||||
|
||||
// @gate enableUseMutableSource
|
||||
it('should throw and restart if getSnapshot changes between scheduled update and re-render', () => {
|
||||
it('should throw and restart if getSnapshot changes between scheduled update and re-render', async () => {
|
||||
const source = createSource('one');
|
||||
const mutableSource = createMutableSource(source, param => param.version);
|
||||
|
||||
@@ -789,11 +780,11 @@ describe('useMutableSource', () => {
|
||||
);
|
||||
}
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(<WrapperWithState />, () =>
|
||||
Scheduler.unstable_yieldValue('Sync effect'),
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield(['only:one', 'Sync effect']);
|
||||
await waitForAll(['only:one', 'Sync effect']);
|
||||
ReactNoop.flushPassiveEffects();
|
||||
|
||||
// Change the source (and schedule an update).
|
||||
@@ -804,16 +795,16 @@ describe('useMutableSource', () => {
|
||||
updateGetSnapshot(() => newGetSnapshot);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded(['only:new:two']);
|
||||
assertLog(['only:new:two']);
|
||||
});
|
||||
});
|
||||
|
||||
// @gate enableUseMutableSource
|
||||
it('should recover from a mutation during yield when other work is scheduled', () => {
|
||||
it('should recover from a mutation during yield when other work is scheduled', async () => {
|
||||
const source = createSource('one');
|
||||
const mutableSource = createMutableSource(source, param => param.version);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
// Start a render that uses the mutable source.
|
||||
React.startTransition(() => {
|
||||
ReactNoop.render(
|
||||
@@ -833,19 +824,19 @@ describe('useMutableSource', () => {
|
||||
</>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toFlushAndYieldThrough(['a:one']);
|
||||
await waitFor(['a:one']);
|
||||
|
||||
// Mutate source
|
||||
source.value = 'two';
|
||||
|
||||
// Now render something different.
|
||||
ReactNoop.render(<div />);
|
||||
expect(Scheduler).toFlushAndYield([]);
|
||||
await waitForAll([]);
|
||||
});
|
||||
});
|
||||
|
||||
// @gate enableUseMutableSource
|
||||
it('should not throw if the new getSnapshot returns the same snapshot value', () => {
|
||||
it('should not throw if the new getSnapshot returns the same snapshot value', async () => {
|
||||
const source = createSource('one');
|
||||
const mutableSource = createMutableSource(source, param => param.version);
|
||||
|
||||
@@ -867,7 +858,7 @@ describe('useMutableSource', () => {
|
||||
);
|
||||
}
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<>
|
||||
<React.Profiler id="a" onRender={onRenderA}>
|
||||
@@ -884,7 +875,7 @@ describe('useMutableSource', () => {
|
||||
</>,
|
||||
() => Scheduler.unstable_yieldValue('Sync effect'),
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield(['a:one', 'b:one', 'Sync effect']);
|
||||
await waitForAll(['a:one', 'b:one', 'Sync effect']);
|
||||
ReactNoop.flushPassiveEffects();
|
||||
expect(onRenderA).toHaveBeenCalledTimes(1);
|
||||
expect(onRenderB).toHaveBeenCalledTimes(1);
|
||||
@@ -892,7 +883,7 @@ describe('useMutableSource', () => {
|
||||
// If B's getSnapshot function updates, but the snapshot it returns is the same,
|
||||
// only B should re-render (to update its state).
|
||||
updateGetSnapshot(() => s => defaultGetSnapshot(s));
|
||||
expect(Scheduler).toFlushAndYield(['b:one']);
|
||||
await waitForAll(['b:one']);
|
||||
ReactNoop.flushPassiveEffects();
|
||||
expect(onRenderA).toHaveBeenCalledTimes(1);
|
||||
expect(onRenderB).toHaveBeenCalledTimes(2);
|
||||
@@ -900,7 +891,7 @@ describe('useMutableSource', () => {
|
||||
});
|
||||
|
||||
// @gate enableUseMutableSource
|
||||
it('should not throw if getSnapshot changes but the source can be safely read from anyway', () => {
|
||||
it('should not throw if getSnapshot changes but the source can be safely read from anyway', async () => {
|
||||
const source = createSource('one');
|
||||
const mutableSource = createMutableSource(source, param => param.version);
|
||||
|
||||
@@ -921,11 +912,11 @@ describe('useMutableSource', () => {
|
||||
);
|
||||
}
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(<WrapperWithState />, () =>
|
||||
Scheduler.unstable_yieldValue('Sync effect'),
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield(['only:one', 'Sync effect']);
|
||||
await waitForAll(['only:one', 'Sync effect']);
|
||||
ReactNoop.flushPassiveEffects();
|
||||
|
||||
// Change the source (and schedule an update)
|
||||
@@ -935,12 +926,12 @@ describe('useMutableSource', () => {
|
||||
updateGetSnapshot(() => newGetSnapshot);
|
||||
});
|
||||
|
||||
expect(Scheduler).toFlushAndYield(['only:new:two']);
|
||||
await waitForAll(['only:new:two']);
|
||||
});
|
||||
});
|
||||
|
||||
// @gate enableUseMutableSource
|
||||
it('should still schedule an update if an eager selector throws after a mutation', () => {
|
||||
it('should still schedule an update if an eager selector throws after a mutation', async () => {
|
||||
const source = createSource({
|
||||
friends: [
|
||||
{id: 1, name: 'Foo'},
|
||||
@@ -986,11 +977,11 @@ describe('useMutableSource', () => {
|
||||
return <li>{name}</li>;
|
||||
}
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(<FriendsList />, () =>
|
||||
Scheduler.unstable_yieldValue('Sync effect'),
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield(['1:Foo', '2:Bar', 'Sync effect']);
|
||||
await waitForAll(['1:Foo', '2:Bar', 'Sync effect']);
|
||||
|
||||
// This mutation will cause the "Bar" component to throw,
|
||||
// since its value will no longer be a part of the store.
|
||||
@@ -1002,12 +993,12 @@ describe('useMutableSource', () => {
|
||||
{id: 3, name: 'Baz'},
|
||||
],
|
||||
};
|
||||
expect(Scheduler).toFlushAndYield(['1:Foo', '3:Baz']);
|
||||
await waitForAll(['1:Foo', '3:Baz']);
|
||||
});
|
||||
});
|
||||
|
||||
// @gate enableUseMutableSource
|
||||
it('should not warn about updates that fire between unmount and passive unsubscribe', () => {
|
||||
it('should not warn about updates that fire between unmount and passive unsubscribe', async () => {
|
||||
const source = createSource('one');
|
||||
const mutableSource = createMutableSource(source, param => param.version);
|
||||
|
||||
@@ -1025,21 +1016,21 @@ describe('useMutableSource', () => {
|
||||
);
|
||||
}
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.renderToRootWithID(<Wrapper />, 'root', () =>
|
||||
Scheduler.unstable_yieldValue('Sync effect'),
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield(['only:one', 'Sync effect']);
|
||||
await waitForAll(['only:one', 'Sync effect']);
|
||||
ReactNoop.flushPassiveEffects();
|
||||
|
||||
// Unmounting a root should remove the remaining event listeners in a passive effect
|
||||
ReactNoop.unmountRootWithID('root');
|
||||
expect(Scheduler).toFlushAndYieldThrough(['layout unmount']);
|
||||
await waitFor(['layout unmount']);
|
||||
|
||||
// Changes to source should not cause a warning,
|
||||
// even though the unsubscribe hasn't run yet (since it's a pending passive effect).
|
||||
source.value = 'two';
|
||||
expect(Scheduler).toFlushAndYield([]);
|
||||
await waitForAll([]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1247,7 +1238,7 @@ describe('useMutableSource', () => {
|
||||
);
|
||||
});
|
||||
// x and y start out reading from different parts of the store.
|
||||
expect(Scheduler).toHaveYielded(['x: foo, y: bar']);
|
||||
assertLog(['x: foo, y: bar']);
|
||||
|
||||
await act(async () => {
|
||||
ReactNoop.discreteUpdates(() => {
|
||||
@@ -1277,7 +1268,7 @@ describe('useMutableSource', () => {
|
||||
// The actual sequence of work will be:
|
||||
// 1. React renders the high-pri update, sees a new getSnapshot, detects the source has been further mutated, and throws
|
||||
// 2. React re-renders with all pending updates, including the second mutation, and renders "bar" and "bar".
|
||||
expect(Scheduler).toHaveYielded(['x: bar, y: bar']);
|
||||
assertLog(['x: bar, y: bar']);
|
||||
});
|
||||
|
||||
// @gate enableUseMutableSource
|
||||
@@ -1317,18 +1308,18 @@ describe('useMutableSource', () => {
|
||||
await act(async () => {
|
||||
root.render(<App getSnapshot={getSnapshotA} />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Render: foo', 'Commit: foo']);
|
||||
assertLog(['Render: foo', 'Commit: foo']);
|
||||
|
||||
await act(async () => {
|
||||
// Switch getSnapshot to read from B instead
|
||||
root.render(<App getSnapshot={getSnapshotB} />);
|
||||
// Render and finish the tree, but yield right after paint, before
|
||||
// the passive effects have fired.
|
||||
expect(Scheduler).toFlushUntilNextPaint(['Render: bar']);
|
||||
await waitForPaint(['Render: bar']);
|
||||
// Then mutate B.
|
||||
mutateB('baz');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
// Fires the effect from the previous render
|
||||
'Commit: bar',
|
||||
// During that effect, it should detect that the snapshot has changed
|
||||
@@ -1393,7 +1384,7 @@ describe('useMutableSource', () => {
|
||||
);
|
||||
// Render and finish the tree, but yield right after paint, before
|
||||
// the passive effects have fired.
|
||||
expect(Scheduler).toFlushUntilNextPaint([]);
|
||||
await waitForPaint([]);
|
||||
|
||||
// Now mutate A. Both hooks should update.
|
||||
// This is at high priority so that it doesn't get batched with default
|
||||
@@ -1462,7 +1453,7 @@ describe('useMutableSource', () => {
|
||||
</>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['a0']);
|
||||
assertLog(['a0']);
|
||||
expect(root).toMatchRenderedOutput('a0');
|
||||
|
||||
await act(async () => {
|
||||
@@ -1476,7 +1467,7 @@ describe('useMutableSource', () => {
|
||||
);
|
||||
});
|
||||
|
||||
expect(Scheduler).toFlushAndYieldThrough(['a0', 'b0']);
|
||||
await waitFor(['a0', 'b0']);
|
||||
// Mutate in an event. This schedules a subscription update on a, which
|
||||
// already mounted, but not b, which hasn't subscribed yet.
|
||||
if (gate(flags => flags.enableUnifiedSyncLane)) {
|
||||
@@ -1499,14 +1490,14 @@ describe('useMutableSource', () => {
|
||||
mutateB('b0');
|
||||
});
|
||||
// Finish the current render
|
||||
expect(Scheduler).toFlushUntilNextPaint(['c']);
|
||||
await waitForPaint(['c']);
|
||||
// a0 will re-render because of the mutation update. But it should show
|
||||
// the latest value, not the intermediate one, to avoid tearing with b.
|
||||
expect(Scheduler).toFlushUntilNextPaint(['a0']);
|
||||
await waitForPaint(['a0']);
|
||||
|
||||
expect(root).toMatchRenderedOutput('a0b0c');
|
||||
// We should be done.
|
||||
expect(Scheduler).toFlushAndYield([]);
|
||||
await waitForAll([]);
|
||||
expect(root).toMatchRenderedOutput('a0b0c');
|
||||
});
|
||||
},
|
||||
@@ -1606,7 +1597,7 @@ describe('useMutableSource', () => {
|
||||
await act(async () => {
|
||||
root.render(<App parentConfig={configA} childConfig={configB} />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Parent: 1', 'Child: 2', 'Commit: 1, 2']);
|
||||
assertLog(['Parent: 1', 'Child: 2', 'Commit: 1, 2']);
|
||||
|
||||
await act(async () => {
|
||||
// Switch the parent and the child to read using the same config
|
||||
@@ -1614,7 +1605,7 @@ describe('useMutableSource', () => {
|
||||
root.render(<App parentConfig={configB} childConfig={configB} />);
|
||||
});
|
||||
// Start rendering the parent, but yield before rendering the child
|
||||
expect(Scheduler).toFlushAndYieldThrough(['Parent: 2']);
|
||||
await waitFor(['Parent: 2']);
|
||||
|
||||
// Mutate the config. This is at lower priority so that 1) to make sure
|
||||
// it doesn't happen to get batched with the in-progress render, and 2)
|
||||
@@ -1624,7 +1615,7 @@ describe('useMutableSource', () => {
|
||||
});
|
||||
|
||||
// In default sync mode, all of the updates flush sync.
|
||||
expect(Scheduler).toFlushAndYieldThrough([
|
||||
await waitFor([
|
||||
// The partial render completes
|
||||
'Child: 2',
|
||||
'Commit: 2, 2',
|
||||
@@ -1632,7 +1623,7 @@ describe('useMutableSource', () => {
|
||||
'Child: 3',
|
||||
]);
|
||||
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
await waitForAll([
|
||||
// Now finish the rest of the update
|
||||
'Commit: 3, 3',
|
||||
]);
|
||||
@@ -1687,11 +1678,11 @@ describe('useMutableSource', () => {
|
||||
() => Scheduler.unstable_yieldValue('Sync effect'),
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['a:one', 'Sync effect']);
|
||||
assertLog(['a:one', 'Sync effect']);
|
||||
expect(source.listenerCount).toBe(1);
|
||||
|
||||
// Mount ComponentB with version 1 (but don't commit it)
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
ReactNoop.render(
|
||||
<React.Profiler id="root" onRender={onRender}>
|
||||
<ComponentA />
|
||||
@@ -1699,11 +1690,7 @@ describe('useMutableSource', () => {
|
||||
</React.Profiler>,
|
||||
() => Scheduler.unstable_yieldValue('Sync effect'),
|
||||
);
|
||||
expect(Scheduler).toFlushAndYieldThrough([
|
||||
'a:one',
|
||||
'b:one',
|
||||
'Sync effect',
|
||||
]);
|
||||
await waitFor(['a:one', 'b:one', 'Sync effect']);
|
||||
expect(source.listenerCount).toBe(1);
|
||||
|
||||
// Mutate -> schedule update for ComponentA
|
||||
@@ -1712,7 +1699,7 @@ describe('useMutableSource', () => {
|
||||
});
|
||||
|
||||
// Commit ComponentB -> notice the change and schedule an update for ComponentB
|
||||
expect(Scheduler).toFlushAndYield(['a:two', 'b:two']);
|
||||
await waitForAll(['a:two', 'b:two']);
|
||||
expect(source.listenerCount).toBe(2);
|
||||
});
|
||||
});
|
||||
@@ -1746,14 +1733,14 @@ describe('useMutableSource', () => {
|
||||
});
|
||||
|
||||
// @gate enableUseMutableSource
|
||||
it('should error if multiple renderers of the same type use a mutable source at the same time', () => {
|
||||
it('should error if multiple renderers of the same type use a mutable source at the same time', async () => {
|
||||
const source = createSource('one');
|
||||
const mutableSource = createMutableSource(
|
||||
source,
|
||||
param => param.version,
|
||||
);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
// Start a render that uses the mutable source.
|
||||
React.startTransition(() => {
|
||||
ReactNoop.render(
|
||||
@@ -1773,7 +1760,7 @@ describe('useMutableSource', () => {
|
||||
</>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toFlushAndYieldThrough(['a:one']);
|
||||
await waitFor(['a:one']);
|
||||
|
||||
const PrevScheduler = Scheduler;
|
||||
|
||||
@@ -1791,7 +1778,7 @@ describe('useMutableSource', () => {
|
||||
subscribe={defaultSubscribe}
|
||||
/>,
|
||||
);
|
||||
expect(Scheduler).toFlushAndYieldThrough(['c:one']);
|
||||
await waitFor(['c:one']);
|
||||
|
||||
expect(console.error.mock.calls[0][0]).toContain(
|
||||
'Detected multiple renderers concurrently rendering the ' +
|
||||
@@ -1808,14 +1795,14 @@ describe('useMutableSource', () => {
|
||||
});
|
||||
|
||||
// @gate enableUseMutableSource
|
||||
it('should error if multiple renderers of the same type use a mutable source at the same time with mutation between', () => {
|
||||
it('should error if multiple renderers of the same type use a mutable source at the same time with mutation between', async () => {
|
||||
const source = createSource('one');
|
||||
const mutableSource = createMutableSource(
|
||||
source,
|
||||
param => param.version,
|
||||
);
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
// Start a render that uses the mutable source.
|
||||
React.startTransition(() => {
|
||||
ReactNoop.render(
|
||||
@@ -1835,7 +1822,7 @@ describe('useMutableSource', () => {
|
||||
</>,
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toFlushAndYieldThrough(['a:one']);
|
||||
await waitFor(['a:one']);
|
||||
|
||||
const PrevScheduler = Scheduler;
|
||||
|
||||
@@ -1856,7 +1843,7 @@ describe('useMutableSource', () => {
|
||||
subscribe={defaultSubscribe}
|
||||
/>,
|
||||
);
|
||||
expect(Scheduler).toFlushAndYieldThrough(['c:two']);
|
||||
await waitFor(['c:two']);
|
||||
|
||||
expect(console.error.mock.calls[0][0]).toContain(
|
||||
'Detected multiple renderers concurrently rendering the ' +
|
||||
|
||||
@@ -16,6 +16,8 @@ let Scheduler;
|
||||
let act;
|
||||
let createMutableSource;
|
||||
let useMutableSource;
|
||||
let waitFor;
|
||||
let assertLog;
|
||||
|
||||
describe('useMutableSourceHydration', () => {
|
||||
beforeEach(() => {
|
||||
@@ -33,6 +35,10 @@ describe('useMutableSourceHydration', () => {
|
||||
React.createMutableSource || React.unstable_createMutableSource;
|
||||
useMutableSource =
|
||||
React.useMutableSource || React.unstable_useMutableSource;
|
||||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
waitFor = InternalTestUtils.waitFor;
|
||||
assertLog = InternalTestUtils.assertLog;
|
||||
});
|
||||
|
||||
const defaultGetSnapshot = source => source.value;
|
||||
@@ -156,7 +162,7 @@ describe('useMutableSourceHydration', () => {
|
||||
|
||||
const htmlString = ReactDOMServer.renderToString(<TestComponent />);
|
||||
container.innerHTML = htmlString;
|
||||
expect(Scheduler).toHaveYielded(['only:one']);
|
||||
assertLog(['only:one']);
|
||||
expect(source.listenerCount).toBe(0);
|
||||
|
||||
act(() => {
|
||||
@@ -164,7 +170,7 @@ describe('useMutableSourceHydration', () => {
|
||||
mutableSources: [mutableSource],
|
||||
});
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['only:one']);
|
||||
assertLog(['only:one']);
|
||||
expect(source.listenerCount).toBe(1);
|
||||
});
|
||||
|
||||
@@ -190,7 +196,7 @@ describe('useMutableSourceHydration', () => {
|
||||
|
||||
const htmlString = ReactDOMServer.renderToString(<TestComponent />);
|
||||
container.innerHTML = htmlString;
|
||||
expect(Scheduler).toHaveYielded(['only:one']);
|
||||
assertLog(['only:one']);
|
||||
expect(source.listenerCount).toBe(0);
|
||||
|
||||
expect(() => {
|
||||
@@ -211,7 +217,7 @@ describe('useMutableSourceHydration', () => {
|
||||
],
|
||||
{withoutStack: 1},
|
||||
);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'only:two',
|
||||
'only:two',
|
||||
'Log error: Text content does not match server-rendered HTML.',
|
||||
@@ -221,7 +227,7 @@ describe('useMutableSourceHydration', () => {
|
||||
});
|
||||
|
||||
// @gate enableUseMutableSource
|
||||
it('should detect a tear between hydrating components', () => {
|
||||
it('should detect a tear between hydrating components', async () => {
|
||||
const source = createSource('one');
|
||||
const mutableSource = createMutableSource(source, param => param.version);
|
||||
|
||||
@@ -249,11 +255,11 @@ describe('useMutableSourceHydration', () => {
|
||||
|
||||
const htmlString = ReactDOMServer.renderToString(<TestComponent />);
|
||||
container.innerHTML = htmlString;
|
||||
expect(Scheduler).toHaveYielded(['a:one', 'b:one']);
|
||||
assertLog(['a:one', 'b:one']);
|
||||
expect(source.listenerCount).toBe(0);
|
||||
|
||||
expect(() => {
|
||||
act(() => {
|
||||
await expect(async () => {
|
||||
await act(async () => {
|
||||
React.startTransition(() => {
|
||||
ReactDOMClient.hydrateRoot(container, <TestComponent />, {
|
||||
mutableSources: [mutableSource],
|
||||
@@ -262,7 +268,7 @@ describe('useMutableSourceHydration', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
expect(Scheduler).toFlushAndYieldThrough(['a:one']);
|
||||
await waitFor(['a:one']);
|
||||
source.value = 'two';
|
||||
});
|
||||
}).toErrorDev(
|
||||
@@ -270,7 +276,7 @@ describe('useMutableSourceHydration', () => {
|
||||
'The server HTML was replaced with client content in <div>.',
|
||||
{withoutStack: true},
|
||||
);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'a:two',
|
||||
'b:two',
|
||||
// TODO: Before onRecoverableError, this error was never surfaced to the
|
||||
@@ -288,7 +294,7 @@ describe('useMutableSourceHydration', () => {
|
||||
});
|
||||
|
||||
// @gate enableUseMutableSource
|
||||
it('should detect a tear between hydrating components reading from different parts of a source', () => {
|
||||
it('should detect a tear between hydrating components reading from different parts of a source', async () => {
|
||||
const source = createComplexSource('a:one', 'b:one');
|
||||
const mutableSource = createMutableSource(source, param => param.version);
|
||||
|
||||
@@ -318,10 +324,10 @@ describe('useMutableSourceHydration', () => {
|
||||
</>,
|
||||
);
|
||||
container.innerHTML = htmlString;
|
||||
expect(Scheduler).toHaveYielded(['0:a:one', '1:b:one']);
|
||||
assertLog(['0:a:one', '1:b:one']);
|
||||
|
||||
expect(() => {
|
||||
act(() => {
|
||||
await expect(async () => {
|
||||
await act(async () => {
|
||||
const fragment = (
|
||||
<>
|
||||
<Component
|
||||
@@ -346,7 +352,7 @@ describe('useMutableSourceHydration', () => {
|
||||
},
|
||||
});
|
||||
});
|
||||
expect(Scheduler).toFlushAndYieldThrough(['0:a:one']);
|
||||
await waitFor(['0:a:one']);
|
||||
source.valueB = 'b:two';
|
||||
});
|
||||
}).toErrorDev(
|
||||
@@ -354,7 +360,7 @@ describe('useMutableSourceHydration', () => {
|
||||
'The server HTML was replaced with client content in <div>.',
|
||||
{withoutStack: true},
|
||||
);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'0:a:one',
|
||||
'1:b:two',
|
||||
// TODO: Before onRecoverableError, this error was never surfaced to the
|
||||
|
||||
@@ -22,6 +22,8 @@ describe('useRef', () => {
|
||||
let useLayoutEffect;
|
||||
let useRef;
|
||||
let useState;
|
||||
let waitForAll;
|
||||
let assertLog;
|
||||
|
||||
beforeEach(() => {
|
||||
React = require('react');
|
||||
@@ -37,6 +39,10 @@ describe('useRef', () => {
|
||||
useLayoutEffect = React.useLayoutEffect;
|
||||
useRef = React.useRef;
|
||||
useState = React.useState;
|
||||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
waitForAll = InternalTestUtils.waitForAll;
|
||||
assertLog = InternalTestUtils.assertLog;
|
||||
});
|
||||
|
||||
function Text(props) {
|
||||
@@ -79,17 +85,17 @@ describe('useRef', () => {
|
||||
act(() => {
|
||||
ReactNoop.render(<App />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
assertLog([]);
|
||||
|
||||
ping(1);
|
||||
ping(2);
|
||||
ping(3);
|
||||
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
assertLog([]);
|
||||
|
||||
jest.advanceTimersByTime(100);
|
||||
|
||||
expect(Scheduler).toHaveYielded(['ping: 3']);
|
||||
assertLog(['ping: 3']);
|
||||
|
||||
ping(4);
|
||||
jest.advanceTimersByTime(20);
|
||||
@@ -97,13 +103,13 @@ describe('useRef', () => {
|
||||
ping(6);
|
||||
jest.advanceTimersByTime(80);
|
||||
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
assertLog([]);
|
||||
|
||||
jest.advanceTimersByTime(20);
|
||||
expect(Scheduler).toHaveYielded(['ping: 6']);
|
||||
assertLog(['ping: 6']);
|
||||
});
|
||||
|
||||
it('should return the same ref during re-renders', () => {
|
||||
it('should return the same ref during re-renders', async () => {
|
||||
function Counter() {
|
||||
const ref = useRef('val');
|
||||
const [count, setCount] = useState(0);
|
||||
@@ -121,10 +127,10 @@ describe('useRef', () => {
|
||||
}
|
||||
|
||||
ReactNoop.render(<Counter />);
|
||||
expect(Scheduler).toFlushAndYield([3]);
|
||||
await waitForAll([3]);
|
||||
|
||||
ReactNoop.render(<Counter />);
|
||||
expect(Scheduler).toFlushAndYield([3]);
|
||||
await waitForAll([3]);
|
||||
});
|
||||
|
||||
if (__DEV__) {
|
||||
|
||||
@@ -20,6 +20,9 @@ let useImperativeHandle;
|
||||
let useRef;
|
||||
let useState;
|
||||
let startTransition;
|
||||
let waitFor;
|
||||
let waitForAll;
|
||||
let assertLog;
|
||||
|
||||
// This tests the native useSyncExternalStore implementation, not the shim.
|
||||
// Tests that apply to both the native implementation and the shim should go
|
||||
@@ -41,6 +44,11 @@ describe('useSyncExternalStore', () => {
|
||||
useSyncExternalStore = React.useSyncExternalStore;
|
||||
startTransition = React.startTransition;
|
||||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
waitFor = InternalTestUtils.waitFor;
|
||||
waitForAll = InternalTestUtils.waitForAll;
|
||||
assertLog = InternalTestUtils.assertLog;
|
||||
|
||||
act = require('jest-react').act;
|
||||
});
|
||||
|
||||
@@ -122,13 +130,13 @@ describe('useSyncExternalStore', () => {
|
||||
root.render(<App store={store1} />);
|
||||
});
|
||||
|
||||
expect(Scheduler).toFlushAndYieldThrough(['A0', 'B0']);
|
||||
await waitFor(['A0', 'B0']);
|
||||
|
||||
// During an interleaved event, the store is mutated.
|
||||
store1.set(1);
|
||||
|
||||
// Then we continue rendering.
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
await waitForAll([
|
||||
// C reads a newer value from the store than A or B, which means they
|
||||
// are inconsistent.
|
||||
'C1',
|
||||
@@ -152,13 +160,13 @@ describe('useSyncExternalStore', () => {
|
||||
});
|
||||
|
||||
// Start a concurrent render that reads from the store, then yield.
|
||||
expect(Scheduler).toFlushAndYieldThrough(['A0', 'B0']);
|
||||
await waitFor(['A0', 'B0']);
|
||||
|
||||
// During an interleaved event, the store is mutated.
|
||||
store2.set(1);
|
||||
|
||||
// Then we continue rendering.
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
await waitForAll([
|
||||
// C reads a newer value from the store than A or B, which means they
|
||||
// are inconsistent.
|
||||
'C1',
|
||||
@@ -191,17 +199,17 @@ describe('useSyncExternalStore', () => {
|
||||
// Start a render that reads from the store and yields value
|
||||
root.render(<App />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['value:initial']);
|
||||
assertLog(['value:initial']);
|
||||
|
||||
await act(() => {
|
||||
store.set('value:changed');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['value:changed']);
|
||||
assertLog(['value:changed']);
|
||||
|
||||
// If cached value was updated, we expect a re-render
|
||||
await act(() => {
|
||||
store.set('value:initial');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['value:initial']);
|
||||
assertLog(['value:initial']);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,6 +18,8 @@ let ReactFreshRuntime;
|
||||
let Scheduler;
|
||||
let act;
|
||||
let createReactClass;
|
||||
let waitFor;
|
||||
let assertLog;
|
||||
|
||||
describe('ReactFresh', () => {
|
||||
let container;
|
||||
@@ -32,6 +34,11 @@ describe('ReactFresh', () => {
|
||||
ReactDOMClient = require('react-dom/client');
|
||||
Scheduler = require('scheduler');
|
||||
act = require('jest-react').act;
|
||||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
waitFor = InternalTestUtils.waitFor;
|
||||
assertLog = InternalTestUtils.assertLog;
|
||||
|
||||
createReactClass = require('create-react-class/factory')(
|
||||
React.Component,
|
||||
React.isValidElement,
|
||||
@@ -2441,7 +2448,7 @@ describe('ReactFresh', () => {
|
||||
|
||||
const root = ReactDOMClient.createRoot(container);
|
||||
root.render(<AppV1 offscreen={true} />);
|
||||
expect(Scheduler).toFlushAndYieldThrough(['App#layout']);
|
||||
await waitFor(['App#layout']);
|
||||
const el = container.firstChild;
|
||||
expect(el.hidden).toBe(true);
|
||||
expect(el.firstChild).toBe(null); // Offscreen content not flushed yet.
|
||||
@@ -2468,7 +2475,7 @@ describe('ReactFresh', () => {
|
||||
expect(el.firstChild).toBe(null);
|
||||
|
||||
// Process the offscreen updates.
|
||||
expect(Scheduler).toFlushAndYieldThrough(['Hello#layout']);
|
||||
await waitFor(['Hello#layout']);
|
||||
expect(container.firstChild).toBe(el);
|
||||
expect(el.firstChild.textContent).toBe('0');
|
||||
expect(el.firstChild.style.color).toBe('red');
|
||||
@@ -2481,7 +2488,7 @@ describe('ReactFresh', () => {
|
||||
);
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Hello#layout']);
|
||||
assertLog(['Hello#layout']);
|
||||
expect(el.firstChild.textContent).toBe('1');
|
||||
expect(el.firstChild.style.color).toBe('red');
|
||||
|
||||
@@ -2507,7 +2514,7 @@ describe('ReactFresh', () => {
|
||||
expect(el.firstChild.style.color).toBe('red');
|
||||
|
||||
// Process the offscreen updates.
|
||||
expect(Scheduler).toFlushAndYieldThrough(['Hello#layout']);
|
||||
await waitFor(['Hello#layout']);
|
||||
expect(container.firstChild).toBe(el);
|
||||
expect(el.firstChild.textContent).toBe('1');
|
||||
expect(el.firstChild.style.color).toBe('orange');
|
||||
|
||||
@@ -19,7 +19,9 @@ const {format: prettyFormat} = require('pretty-format');
|
||||
// Isolate noop renderer
|
||||
jest.resetModules();
|
||||
const ReactNoop = require('react-noop-renderer');
|
||||
const Scheduler = require('scheduler');
|
||||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
const waitForAll = InternalTestUtils.waitForAll;
|
||||
|
||||
// Kind of hacky, but we nullify all the instances to test the tree structure
|
||||
// with jasmine's deep equality function, and test the instances separate. We
|
||||
@@ -1015,7 +1017,7 @@ describe('ReactTestRenderer', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('can concurrently render context with a "primary" renderer', () => {
|
||||
it('can concurrently render context with a "primary" renderer', async () => {
|
||||
const Context = React.createContext(null);
|
||||
const Indirection = React.Fragment;
|
||||
const App = () => (
|
||||
@@ -1026,7 +1028,7 @@ describe('ReactTestRenderer', () => {
|
||||
</Context.Provider>
|
||||
);
|
||||
ReactNoop.render(<App />);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
ReactTestRenderer.create(<App />);
|
||||
});
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ let React;
|
||||
let ReactTestRenderer;
|
||||
let Scheduler;
|
||||
let act;
|
||||
let assertLog;
|
||||
|
||||
describe('ReactTestRenderer.act()', () => {
|
||||
beforeEach(() => {
|
||||
@@ -12,6 +13,9 @@ describe('ReactTestRenderer.act()', () => {
|
||||
ReactTestRenderer = require('react-test-renderer');
|
||||
Scheduler = require('scheduler');
|
||||
act = ReactTestRenderer.act;
|
||||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
assertLog = InternalTestUtils.assertLog;
|
||||
});
|
||||
|
||||
// @gate __DEV__
|
||||
@@ -91,7 +95,7 @@ describe('ReactTestRenderer.act()', () => {
|
||||
await act(async () => {
|
||||
root.update(<App />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
// Should not flush effects without also flushing microtasks
|
||||
// First render:
|
||||
'Effect',
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
let React;
|
||||
let ReactTestRenderer;
|
||||
let Scheduler;
|
||||
let waitForAll;
|
||||
let waitFor;
|
||||
let assertLog;
|
||||
|
||||
describe('ReactTestRendererAsync', () => {
|
||||
beforeEach(() => {
|
||||
@@ -21,9 +24,14 @@ describe('ReactTestRendererAsync', () => {
|
||||
React = require('react');
|
||||
ReactTestRenderer = require('react-test-renderer');
|
||||
Scheduler = require('scheduler');
|
||||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
waitForAll = InternalTestUtils.waitForAll;
|
||||
waitFor = InternalTestUtils.waitFor;
|
||||
assertLog = InternalTestUtils.assertLog;
|
||||
});
|
||||
|
||||
it('flushAll flushes all work', () => {
|
||||
it('flushAll flushes all work', async () => {
|
||||
function Foo(props) {
|
||||
return props.children;
|
||||
}
|
||||
@@ -35,7 +43,7 @@ describe('ReactTestRendererAsync', () => {
|
||||
expect(renderer.toJSON()).toEqual(null);
|
||||
|
||||
// Flush initial mount.
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
expect(renderer.toJSON()).toEqual('Hi');
|
||||
|
||||
// Update
|
||||
@@ -43,11 +51,11 @@ describe('ReactTestRendererAsync', () => {
|
||||
// Not yet updated.
|
||||
expect(renderer.toJSON()).toEqual('Hi');
|
||||
// Flush update.
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
expect(renderer.toJSON()).toEqual('Bye');
|
||||
});
|
||||
|
||||
it('flushAll returns array of yielded values', () => {
|
||||
it('flushAll returns array of yielded values', async () => {
|
||||
function Child(props) {
|
||||
Scheduler.unstable_yieldValue(props.children);
|
||||
return props.children;
|
||||
@@ -65,15 +73,15 @@ describe('ReactTestRendererAsync', () => {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
|
||||
expect(Scheduler).toFlushAndYield(['A:1', 'B:1', 'C:1']);
|
||||
await waitForAll(['A:1', 'B:1', 'C:1']);
|
||||
expect(renderer.toJSON()).toEqual(['A:1', 'B:1', 'C:1']);
|
||||
|
||||
renderer.update(<Parent step={2} />);
|
||||
expect(Scheduler).toFlushAndYield(['A:2', 'B:2', 'C:2']);
|
||||
await waitForAll(['A:2', 'B:2', 'C:2']);
|
||||
expect(renderer.toJSON()).toEqual(['A:2', 'B:2', 'C:2']);
|
||||
});
|
||||
|
||||
it('flushThrough flushes until the expected values is yielded', () => {
|
||||
it('flushThrough flushes until the expected values is yielded', async () => {
|
||||
function Child(props) {
|
||||
Scheduler.unstable_yieldValue(props.children);
|
||||
return props.children;
|
||||
@@ -96,16 +104,16 @@ describe('ReactTestRendererAsync', () => {
|
||||
});
|
||||
|
||||
// Flush the first two siblings
|
||||
expect(Scheduler).toFlushAndYieldThrough(['A:1', 'B:1']);
|
||||
await waitFor(['A:1', 'B:1']);
|
||||
// Did not commit yet.
|
||||
expect(renderer.toJSON()).toEqual(null);
|
||||
|
||||
// Flush the remaining work
|
||||
expect(Scheduler).toFlushAndYield(['C:1']);
|
||||
await waitForAll(['C:1']);
|
||||
expect(renderer.toJSON()).toEqual(['A:1', 'B:1', 'C:1']);
|
||||
});
|
||||
|
||||
it('supports high priority interruptions', () => {
|
||||
it('supports high priority interruptions', async () => {
|
||||
function Child(props) {
|
||||
Scheduler.unstable_yieldValue(props.children);
|
||||
return props.children;
|
||||
@@ -136,7 +144,7 @@ describe('ReactTestRendererAsync', () => {
|
||||
});
|
||||
|
||||
// Flush the some of the changes, but don't commit
|
||||
expect(Scheduler).toFlushAndYieldThrough(['A:1']);
|
||||
await waitFor(['A:1']);
|
||||
expect(renderer.toJSON()).toEqual(null);
|
||||
|
||||
// Interrupt with higher priority properties
|
||||
@@ -232,12 +240,12 @@ describe('ReactTestRendererAsync', () => {
|
||||
});
|
||||
|
||||
expect(Scheduler).toFlushAndThrow('Oh no!');
|
||||
expect(Scheduler).toHaveYielded(['A', 'B', 'C', 'D', 'A', 'B', 'C', 'D']);
|
||||
assertLog(['A', 'B', 'C', 'D', 'A', 'B', 'C', 'D']);
|
||||
|
||||
renderer.update(<App />);
|
||||
|
||||
expect(Scheduler).toFlushAndThrow('Oh no!');
|
||||
expect(Scheduler).toHaveYielded(['A', 'B', 'C', 'D', 'A', 'B', 'C', 'D']);
|
||||
assertLog(['A', 'B', 'C', 'D', 'A', 'B', 'C', 'D']);
|
||||
|
||||
renderer.update(<App />);
|
||||
expect(Scheduler).toFlushAndThrow('Oh no!');
|
||||
|
||||
@@ -21,6 +21,10 @@ let cancelCallback;
|
||||
let wrapCallback;
|
||||
let getCurrentPriorityLevel;
|
||||
let shouldYield;
|
||||
let waitForAll;
|
||||
let assertLog;
|
||||
let waitFor;
|
||||
let waitForPaint;
|
||||
|
||||
describe('Scheduler', () => {
|
||||
beforeEach(() => {
|
||||
@@ -40,20 +44,26 @@ describe('Scheduler', () => {
|
||||
wrapCallback = Scheduler.unstable_wrapCallback;
|
||||
getCurrentPriorityLevel = Scheduler.unstable_getCurrentPriorityLevel;
|
||||
shouldYield = Scheduler.unstable_shouldYield;
|
||||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
waitForAll = InternalTestUtils.waitForAll;
|
||||
assertLog = InternalTestUtils.assertLog;
|
||||
waitFor = InternalTestUtils.waitFor;
|
||||
waitForPaint = InternalTestUtils.waitForPaint;
|
||||
});
|
||||
|
||||
it('flushes work incrementally', () => {
|
||||
it('flushes work incrementally', async () => {
|
||||
scheduleCallback(NormalPriority, () => Scheduler.unstable_yieldValue('A'));
|
||||
scheduleCallback(NormalPriority, () => Scheduler.unstable_yieldValue('B'));
|
||||
scheduleCallback(NormalPriority, () => Scheduler.unstable_yieldValue('C'));
|
||||
scheduleCallback(NormalPriority, () => Scheduler.unstable_yieldValue('D'));
|
||||
|
||||
expect(Scheduler).toFlushAndYieldThrough(['A', 'B']);
|
||||
expect(Scheduler).toFlushAndYieldThrough(['C']);
|
||||
expect(Scheduler).toFlushAndYield(['D']);
|
||||
await waitFor(['A', 'B']);
|
||||
await waitFor(['C']);
|
||||
await waitForAll(['D']);
|
||||
});
|
||||
|
||||
it('cancels work', () => {
|
||||
it('cancels work', async () => {
|
||||
scheduleCallback(NormalPriority, () => Scheduler.unstable_yieldValue('A'));
|
||||
const callbackHandleB = scheduleCallback(NormalPriority, () =>
|
||||
Scheduler.unstable_yieldValue('B'),
|
||||
@@ -62,19 +72,19 @@ describe('Scheduler', () => {
|
||||
|
||||
cancelCallback(callbackHandleB);
|
||||
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
await waitForAll([
|
||||
'A',
|
||||
// B should have been cancelled
|
||||
'C',
|
||||
]);
|
||||
});
|
||||
|
||||
it('executes the highest priority callbacks first', () => {
|
||||
it('executes the highest priority callbacks first', async () => {
|
||||
scheduleCallback(NormalPriority, () => Scheduler.unstable_yieldValue('A'));
|
||||
scheduleCallback(NormalPriority, () => Scheduler.unstable_yieldValue('B'));
|
||||
|
||||
// Yield before B is flushed
|
||||
expect(Scheduler).toFlushAndYieldThrough(['A']);
|
||||
await waitFor(['A']);
|
||||
|
||||
scheduleCallback(UserBlockingPriority, () =>
|
||||
Scheduler.unstable_yieldValue('C'),
|
||||
@@ -84,10 +94,10 @@ describe('Scheduler', () => {
|
||||
);
|
||||
|
||||
// C and D should come first, because they are higher priority
|
||||
expect(Scheduler).toFlushAndYield(['C', 'D', 'B']);
|
||||
await waitForAll(['C', 'D', 'B']);
|
||||
});
|
||||
|
||||
it('expires work', () => {
|
||||
it('expires work', async () => {
|
||||
scheduleCallback(NormalPriority, didTimeout => {
|
||||
Scheduler.unstable_advanceTime(100);
|
||||
Scheduler.unstable_yieldValue(`A (did timeout: ${didTimeout})`);
|
||||
@@ -103,7 +113,7 @@ describe('Scheduler', () => {
|
||||
|
||||
// Advance time, but not by enough to expire any work
|
||||
Scheduler.unstable_advanceTime(249);
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
assertLog([]);
|
||||
|
||||
// Schedule a few more callbacks
|
||||
scheduleCallback(NormalPriority, didTimeout => {
|
||||
@@ -117,33 +127,27 @@ describe('Scheduler', () => {
|
||||
|
||||
// Advance by just a bit more to expire the user blocking callbacks
|
||||
Scheduler.unstable_advanceTime(1);
|
||||
expect(Scheduler).toFlushAndYieldThrough([
|
||||
'B (did timeout: true)',
|
||||
'C (did timeout: true)',
|
||||
]);
|
||||
await waitFor(['B (did timeout: true)', 'C (did timeout: true)']);
|
||||
|
||||
// Expire A
|
||||
Scheduler.unstable_advanceTime(4600);
|
||||
expect(Scheduler).toFlushAndYieldThrough(['A (did timeout: true)']);
|
||||
await waitFor(['A (did timeout: true)']);
|
||||
|
||||
// Flush the rest without expiring
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
'D (did timeout: false)',
|
||||
'E (did timeout: true)',
|
||||
]);
|
||||
await waitForAll(['D (did timeout: false)', 'E (did timeout: true)']);
|
||||
});
|
||||
|
||||
it('has a default expiration of ~5 seconds', () => {
|
||||
scheduleCallback(NormalPriority, () => Scheduler.unstable_yieldValue('A'));
|
||||
|
||||
Scheduler.unstable_advanceTime(4999);
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
assertLog([]);
|
||||
|
||||
Scheduler.unstable_advanceTime(1);
|
||||
expect(Scheduler).toFlushExpired(['A']);
|
||||
});
|
||||
|
||||
it('continues working on same task after yielding', () => {
|
||||
it('continues working on same task after yielding', async () => {
|
||||
scheduleCallback(NormalPriority, () => {
|
||||
Scheduler.unstable_advanceTime(100);
|
||||
Scheduler.unstable_yieldValue('A');
|
||||
@@ -184,14 +188,14 @@ describe('Scheduler', () => {
|
||||
|
||||
// Flush, then yield while in the middle of C.
|
||||
expect(didYield).toBe(false);
|
||||
expect(Scheduler).toFlushAndYieldThrough(['A', 'B', 'C1']);
|
||||
await waitFor(['A', 'B', 'C1']);
|
||||
expect(didYield).toBe(true);
|
||||
|
||||
// When we resume, we should continue working on C.
|
||||
expect(Scheduler).toFlushAndYield(['C2', 'C3', 'D', 'E']);
|
||||
await waitForAll(['C2', 'C3', 'D', 'E']);
|
||||
});
|
||||
|
||||
it('continuation callbacks inherit the expiration of the previous callback', () => {
|
||||
it('continuation callbacks inherit the expiration of the previous callback', async () => {
|
||||
const tasks = [
|
||||
['A', 125],
|
||||
['B', 124],
|
||||
@@ -213,14 +217,14 @@ describe('Scheduler', () => {
|
||||
scheduleCallback(UserBlockingPriority, work);
|
||||
|
||||
// Flush until just before the expiration time
|
||||
expect(Scheduler).toFlushAndYieldThrough(['A', 'B']);
|
||||
await waitFor(['A', 'B']);
|
||||
|
||||
// Advance time by just a bit more. This should expire all the remaining work.
|
||||
Scheduler.unstable_advanceTime(1);
|
||||
expect(Scheduler).toFlushExpired(['C', 'D']);
|
||||
});
|
||||
|
||||
it('continuations are interrupted by higher priority work', () => {
|
||||
it('continuations are interrupted by higher priority work', async () => {
|
||||
const tasks = [
|
||||
['A', 100],
|
||||
['B', 100],
|
||||
@@ -238,20 +242,20 @@ describe('Scheduler', () => {
|
||||
}
|
||||
};
|
||||
scheduleCallback(NormalPriority, work);
|
||||
expect(Scheduler).toFlushAndYieldThrough(['A']);
|
||||
await waitFor(['A']);
|
||||
|
||||
scheduleCallback(UserBlockingPriority, () => {
|
||||
Scheduler.unstable_advanceTime(100);
|
||||
Scheduler.unstable_yieldValue('High pri');
|
||||
});
|
||||
|
||||
expect(Scheduler).toFlushAndYield(['High pri', 'B', 'C', 'D']);
|
||||
await waitForAll(['High pri', 'B', 'C', 'D']);
|
||||
});
|
||||
|
||||
it(
|
||||
'continuations do not block higher priority work scheduled ' +
|
||||
'inside an executing callback',
|
||||
() => {
|
||||
async () => {
|
||||
const tasks = [
|
||||
['A', 100],
|
||||
['B', 100],
|
||||
@@ -279,7 +283,7 @@ describe('Scheduler', () => {
|
||||
}
|
||||
};
|
||||
scheduleCallback(NormalPriority, work);
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
await waitForAll([
|
||||
'A',
|
||||
'B',
|
||||
'Schedule high pri',
|
||||
@@ -293,7 +297,7 @@ describe('Scheduler', () => {
|
||||
},
|
||||
);
|
||||
|
||||
it('cancelling a continuation', () => {
|
||||
it('cancelling a continuation', async () => {
|
||||
const task = scheduleCallback(NormalPriority, () => {
|
||||
Scheduler.unstable_yieldValue('Yield');
|
||||
return () => {
|
||||
@@ -301,9 +305,9 @@ describe('Scheduler', () => {
|
||||
};
|
||||
});
|
||||
|
||||
expect(Scheduler).toFlushAndYieldThrough(['Yield']);
|
||||
await waitFor(['Yield']);
|
||||
cancelCallback(task);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
});
|
||||
|
||||
it('top-level immediate callbacks fire in a subsequent task', () => {
|
||||
@@ -320,7 +324,7 @@ describe('Scheduler', () => {
|
||||
Scheduler.unstable_yieldValue('D'),
|
||||
);
|
||||
// Immediate callback hasn't fired, yet.
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
assertLog([]);
|
||||
// They all flush immediately within the subsequent task.
|
||||
expect(Scheduler).toFlushExpired(['A', 'B', 'C', 'D']);
|
||||
});
|
||||
@@ -339,7 +343,7 @@ describe('Scheduler', () => {
|
||||
scheduleCallback(ImmediatePriority, () =>
|
||||
Scheduler.unstable_yieldValue('D'),
|
||||
);
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
assertLog([]);
|
||||
// C should flush at the end
|
||||
expect(Scheduler).toFlushExpired(['A', 'B', 'D', 'C']);
|
||||
});
|
||||
@@ -365,10 +369,10 @@ describe('Scheduler', () => {
|
||||
);
|
||||
|
||||
wrappedCallback();
|
||||
expect(Scheduler).toHaveYielded([NormalPriority]);
|
||||
assertLog([NormalPriority]);
|
||||
|
||||
wrappedUserBlockingCallback();
|
||||
expect(Scheduler).toHaveYielded([UserBlockingPriority]);
|
||||
assertLog([UserBlockingPriority]);
|
||||
});
|
||||
|
||||
it('wrapped callbacks inherit the current priority even when nested', () => {
|
||||
@@ -387,10 +391,10 @@ describe('Scheduler', () => {
|
||||
});
|
||||
|
||||
wrappedCallback();
|
||||
expect(Scheduler).toHaveYielded([NormalPriority]);
|
||||
assertLog([NormalPriority]);
|
||||
|
||||
wrappedUserBlockingCallback();
|
||||
expect(Scheduler).toHaveYielded([UserBlockingPriority]);
|
||||
assertLog([UserBlockingPriority]);
|
||||
});
|
||||
|
||||
it("immediate callbacks fire even if there's an error", () => {
|
||||
@@ -407,12 +411,12 @@ describe('Scheduler', () => {
|
||||
});
|
||||
|
||||
expect(() => expect(Scheduler).toFlushExpired()).toThrow('Oops A');
|
||||
expect(Scheduler).toHaveYielded(['A']);
|
||||
assertLog(['A']);
|
||||
|
||||
// B and C flush in a subsequent event. That way, the second error is not
|
||||
// swallowed.
|
||||
expect(() => expect(Scheduler).toFlushExpired()).toThrow('Oops C');
|
||||
expect(Scheduler).toHaveYielded(['B', 'C']);
|
||||
assertLog(['B', 'C']);
|
||||
});
|
||||
|
||||
it('multiple immediate callbacks can throw and there will be an error for each one', () => {
|
||||
@@ -440,7 +444,7 @@ describe('Scheduler', () => {
|
||||
Scheduler.unstable_yieldValue(getCurrentPriorityLevel());
|
||||
});
|
||||
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
NormalPriority,
|
||||
ImmediatePriority,
|
||||
NormalPriority,
|
||||
@@ -454,7 +458,7 @@ describe('Scheduler', () => {
|
||||
// priority if you have sourcemaps.
|
||||
// TODO: Feature temporarily disabled while we investigate a bug in one of
|
||||
// our minifiers.
|
||||
it.skip('adds extra function to the JS stack whose name includes the priority level', () => {
|
||||
it.skip('adds extra function to the JS stack whose name includes the priority level', async () => {
|
||||
function inferPriorityFromCallstack() {
|
||||
try {
|
||||
throw Error();
|
||||
@@ -508,7 +512,7 @@ describe('Scheduler', () => {
|
||||
Scheduler.unstable_yieldValue('Idle: ' + inferPriorityFromCallstack()),
|
||||
);
|
||||
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
await waitForAll([
|
||||
'Immediate: ' + ImmediatePriority,
|
||||
'UserBlocking: ' + UserBlockingPriority,
|
||||
'Normal: ' + NormalPriority,
|
||||
@@ -519,7 +523,7 @@ describe('Scheduler', () => {
|
||||
}
|
||||
|
||||
describe('delayed tasks', () => {
|
||||
it('schedules a delayed task', () => {
|
||||
it('schedules a delayed task', async () => {
|
||||
scheduleCallback(
|
||||
NormalPriority,
|
||||
() => Scheduler.unstable_yieldValue('A'),
|
||||
@@ -529,21 +533,21 @@ describe('Scheduler', () => {
|
||||
);
|
||||
|
||||
// Should flush nothing, because delay hasn't elapsed
|
||||
expect(Scheduler).toFlushAndYield([]);
|
||||
await waitForAll([]);
|
||||
|
||||
// Advance time until right before the threshold
|
||||
Scheduler.unstable_advanceTime(999);
|
||||
// Still nothing
|
||||
expect(Scheduler).toFlushAndYield([]);
|
||||
await waitForAll([]);
|
||||
|
||||
// Advance time past the threshold
|
||||
Scheduler.unstable_advanceTime(1);
|
||||
|
||||
// Now it should flush like normal
|
||||
expect(Scheduler).toFlushAndYield(['A']);
|
||||
await waitForAll(['A']);
|
||||
});
|
||||
|
||||
it('schedules multiple delayed tasks', () => {
|
||||
it('schedules multiple delayed tasks', async () => {
|
||||
scheduleCallback(
|
||||
NormalPriority,
|
||||
() => Scheduler.unstable_yieldValue('C'),
|
||||
@@ -577,20 +581,20 @@ describe('Scheduler', () => {
|
||||
);
|
||||
|
||||
// Should flush nothing, because delay hasn't elapsed
|
||||
expect(Scheduler).toFlushAndYield([]);
|
||||
await waitForAll([]);
|
||||
|
||||
// Advance some time.
|
||||
Scheduler.unstable_advanceTime(200);
|
||||
// Both A and B are no longer delayed. They can now flush incrementally.
|
||||
expect(Scheduler).toFlushAndYieldThrough(['A']);
|
||||
expect(Scheduler).toFlushAndYield(['B']);
|
||||
await waitFor(['A']);
|
||||
await waitForAll(['B']);
|
||||
|
||||
// Advance the rest
|
||||
Scheduler.unstable_advanceTime(200);
|
||||
expect(Scheduler).toFlushAndYield(['C', 'D']);
|
||||
await waitForAll(['C', 'D']);
|
||||
});
|
||||
|
||||
it('interleaves normal tasks and delayed tasks', () => {
|
||||
it('interleaves normal tasks and delayed tasks', async () => {
|
||||
// Schedule some high priority callbacks with a delay. When their delay
|
||||
// elapses, they will be the most important callback in the queue.
|
||||
scheduleCallback(
|
||||
@@ -624,17 +628,10 @@ describe('Scheduler', () => {
|
||||
|
||||
// Flush all the work. The timers should be interleaved with the
|
||||
// other tasks.
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
'A',
|
||||
'Timer 1',
|
||||
'B',
|
||||
'C',
|
||||
'Timer 2',
|
||||
'D',
|
||||
]);
|
||||
await waitForAll(['A', 'Timer 1', 'B', 'C', 'Timer 2', 'D']);
|
||||
});
|
||||
|
||||
it('interleaves delayed tasks with time-sliced tasks', () => {
|
||||
it('interleaves delayed tasks with time-sliced tasks', async () => {
|
||||
// Schedule some high priority callbacks with a delay. When their delay
|
||||
// elapses, they will be the most important callback in the queue.
|
||||
scheduleCallback(
|
||||
@@ -670,17 +667,10 @@ describe('Scheduler', () => {
|
||||
|
||||
// Flush all the work. The timers should be interleaved with the
|
||||
// other tasks.
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
'A',
|
||||
'Timer 1',
|
||||
'B',
|
||||
'C',
|
||||
'Timer 2',
|
||||
'D',
|
||||
]);
|
||||
await waitForAll(['A', 'Timer 1', 'B', 'C', 'Timer 2', 'D']);
|
||||
});
|
||||
|
||||
it('cancels a delayed task', () => {
|
||||
it('cancels a delayed task', async () => {
|
||||
// Schedule several tasks with the same delay
|
||||
const options = {delay: 100};
|
||||
|
||||
@@ -701,7 +691,7 @@ describe('Scheduler', () => {
|
||||
);
|
||||
|
||||
// Cancel B before its delay has elapsed
|
||||
expect(Scheduler).toFlushAndYield([]);
|
||||
await waitForAll([]);
|
||||
cancelCallback(taskB);
|
||||
|
||||
// Cancel C after its delay has elapsed
|
||||
@@ -709,24 +699,24 @@ describe('Scheduler', () => {
|
||||
cancelCallback(taskC);
|
||||
|
||||
// Only A should flush
|
||||
expect(Scheduler).toFlushAndYield(['A']);
|
||||
await waitForAll(['A']);
|
||||
});
|
||||
|
||||
it('gracefully handles scheduled tasks that are not a function', () => {
|
||||
it('gracefully handles scheduled tasks that are not a function', async () => {
|
||||
scheduleCallback(ImmediatePriority, null);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
|
||||
scheduleCallback(ImmediatePriority, undefined);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
|
||||
scheduleCallback(ImmediatePriority, {});
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
|
||||
scheduleCallback(ImmediatePriority, 42);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
});
|
||||
|
||||
it('toFlushUntilNextPaint stops if a continuation is returned', () => {
|
||||
it('toFlushUntilNextPaint stops if a continuation is returned', async () => {
|
||||
scheduleCallback(NormalPriority, () => {
|
||||
Scheduler.unstable_yieldValue('Original Task');
|
||||
Scheduler.unstable_yieldValue('shouldYield: ' + shouldYield());
|
||||
@@ -736,7 +726,7 @@ describe('Scheduler', () => {
|
||||
};
|
||||
});
|
||||
|
||||
expect(Scheduler).toFlushUntilNextPaint([
|
||||
await waitForPaint([
|
||||
'Original Task',
|
||||
// Immediately before returning a continuation, `shouldYield` returns
|
||||
// false, which means there must be time remaining in the frame.
|
||||
@@ -750,10 +740,10 @@ describe('Scheduler', () => {
|
||||
expect(Scheduler.unstable_now()).toBe(0);
|
||||
|
||||
// Continue the task
|
||||
expect(Scheduler).toFlushAndYield(['Continuation Task']);
|
||||
await waitForAll(['Continuation Task']);
|
||||
});
|
||||
|
||||
it("toFlushAndYield keeps flushing even if there's a continuation", () => {
|
||||
it("toFlushAndYield keeps flushing even if there's a continuation", async () => {
|
||||
scheduleCallback(NormalPriority, () => {
|
||||
Scheduler.unstable_yieldValue('Original Task');
|
||||
Scheduler.unstable_yieldValue('shouldYield: ' + shouldYield());
|
||||
@@ -763,7 +753,7 @@ describe('Scheduler', () => {
|
||||
};
|
||||
});
|
||||
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
await waitForAll([
|
||||
'Original Task',
|
||||
// Immediately before returning a continuation, `shouldYield` returns
|
||||
// false, which means there must be time remaining in the frame.
|
||||
|
||||
@@ -24,6 +24,8 @@ let cancelCallback;
|
||||
// let wrapCallback;
|
||||
// let getCurrentPriorityLevel;
|
||||
// let shouldYield;
|
||||
let waitForAll;
|
||||
let waitFor;
|
||||
|
||||
function priorityLevelToString(priorityLevel) {
|
||||
switch (priorityLevel) {
|
||||
@@ -69,6 +71,10 @@ describe('Scheduler', () => {
|
||||
// wrapCallback = Scheduler.unstable_wrapCallback;
|
||||
// getCurrentPriorityLevel = Scheduler.unstable_getCurrentPriorityLevel;
|
||||
// shouldYield = Scheduler.unstable_shouldYield;
|
||||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
waitForAll = InternalTestUtils.waitForAll;
|
||||
waitFor = InternalTestUtils.waitFor;
|
||||
});
|
||||
|
||||
const TaskStartEvent = 1;
|
||||
@@ -254,7 +260,7 @@ describe('Scheduler', () => {
|
||||
return '\n' + result;
|
||||
}
|
||||
|
||||
it('creates a basic flamegraph', () => {
|
||||
it('creates a basic flamegraph', async () => {
|
||||
Scheduler.unstable_Profiling.startLoggingProfilingEvents();
|
||||
|
||||
Scheduler.unstable_advanceTime(100);
|
||||
@@ -280,9 +286,9 @@ describe('Scheduler', () => {
|
||||
},
|
||||
{label: 'Foo'},
|
||||
);
|
||||
expect(Scheduler).toFlushAndYieldThrough(['Yield 1', 'Yield 3']);
|
||||
await waitFor(['Yield 1', 'Yield 3']);
|
||||
Scheduler.unstable_advanceTime(100);
|
||||
expect(Scheduler).toFlushAndYield(['Yield 2', 'Yield 4']);
|
||||
await waitForAll(['Yield 2', 'Yield 4']);
|
||||
|
||||
expect(stopProfilingAndPrintFlamegraph()).toEqual(
|
||||
`
|
||||
@@ -293,7 +299,7 @@ Task 1 [Normal] │ ████████░░░░░░░
|
||||
);
|
||||
});
|
||||
|
||||
it('marks when a task is canceled', () => {
|
||||
it('marks when a task is canceled', async () => {
|
||||
Scheduler.unstable_Profiling.startLoggingProfilingEvents();
|
||||
|
||||
const task = scheduleCallback(NormalPriority, () => {
|
||||
@@ -306,13 +312,13 @@ Task 1 [Normal] │ ████████░░░░░░░
|
||||
};
|
||||
});
|
||||
|
||||
expect(Scheduler).toFlushAndYieldThrough(['Yield 1', 'Yield 2']);
|
||||
await waitFor(['Yield 1', 'Yield 2']);
|
||||
Scheduler.unstable_advanceTime(100);
|
||||
|
||||
cancelCallback(task);
|
||||
|
||||
Scheduler.unstable_advanceTime(1000);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
expect(stopProfilingAndPrintFlamegraph()).toEqual(
|
||||
`
|
||||
!!! Main thread │░░░░░░██████████████████████
|
||||
@@ -321,7 +327,7 @@ Task 1 [Normal] │██████░░🡐 canceled
|
||||
);
|
||||
});
|
||||
|
||||
it('marks when a task errors', () => {
|
||||
it('marks when a task errors', async () => {
|
||||
Scheduler.unstable_Profiling.startLoggingProfilingEvents();
|
||||
|
||||
scheduleCallback(NormalPriority, () => {
|
||||
@@ -333,7 +339,7 @@ Task 1 [Normal] │██████░░🡐 canceled
|
||||
Scheduler.unstable_advanceTime(100);
|
||||
|
||||
Scheduler.unstable_advanceTime(1000);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
expect(stopProfilingAndPrintFlamegraph()).toEqual(
|
||||
`
|
||||
!!! Main thread │░░░░░░██████████████████████
|
||||
@@ -342,7 +348,7 @@ Task 1 [Normal] │██████🡐 errored
|
||||
);
|
||||
});
|
||||
|
||||
it('marks when multiple tasks are canceled', () => {
|
||||
it('marks when multiple tasks are canceled', async () => {
|
||||
Scheduler.unstable_Profiling.startLoggingProfilingEvents();
|
||||
|
||||
const task1 = scheduleCallback(NormalPriority, () => {
|
||||
@@ -364,7 +370,7 @@ Task 1 [Normal] │██████🡐 errored
|
||||
};
|
||||
});
|
||||
|
||||
expect(Scheduler).toFlushAndYieldThrough(['Yield 1', 'Yield 2']);
|
||||
await waitFor(['Yield 1', 'Yield 2']);
|
||||
Scheduler.unstable_advanceTime(100);
|
||||
|
||||
cancelCallback(task1);
|
||||
@@ -373,7 +379,7 @@ Task 1 [Normal] │██████🡐 errored
|
||||
// Advance more time. This should not affect the size of the main
|
||||
// thread row, since the Scheduler queue is empty.
|
||||
Scheduler.unstable_advanceTime(1000);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
|
||||
// The main thread row should end when the callback is cancelled.
|
||||
expect(stopProfilingAndPrintFlamegraph()).toEqual(
|
||||
@@ -385,14 +391,14 @@ Task 2 [Normal] │░░░░░░░░🡐 canceled
|
||||
);
|
||||
});
|
||||
|
||||
it('handles cancelling a task that already finished', () => {
|
||||
it('handles cancelling a task that already finished', async () => {
|
||||
Scheduler.unstable_Profiling.startLoggingProfilingEvents();
|
||||
|
||||
const task = scheduleCallback(NormalPriority, () => {
|
||||
Scheduler.unstable_yieldValue('A');
|
||||
Scheduler.unstable_advanceTime(1000);
|
||||
});
|
||||
expect(Scheduler).toFlushAndYield(['A']);
|
||||
await waitForAll(['A']);
|
||||
cancelCallback(task);
|
||||
expect(stopProfilingAndPrintFlamegraph()).toEqual(
|
||||
`
|
||||
@@ -402,7 +408,7 @@ Task 1 [Normal] │████████████████
|
||||
);
|
||||
});
|
||||
|
||||
it('handles cancelling a task multiple times', () => {
|
||||
it('handles cancelling a task multiple times', async () => {
|
||||
Scheduler.unstable_Profiling.startLoggingProfilingEvents();
|
||||
|
||||
scheduleCallback(
|
||||
@@ -426,7 +432,7 @@ Task 1 [Normal] │████████████████
|
||||
cancelCallback(task);
|
||||
cancelCallback(task);
|
||||
cancelCallback(task);
|
||||
expect(Scheduler).toFlushAndYield(['A']);
|
||||
await waitForAll(['A']);
|
||||
expect(stopProfilingAndPrintFlamegraph()).toEqual(
|
||||
`
|
||||
!!! Main thread │████████████░░░░░░░░░░░░░░░░░░░░
|
||||
@@ -436,7 +442,7 @@ Task 2 [Normal] │ ░░░░░░░░🡐 canceled
|
||||
);
|
||||
});
|
||||
|
||||
it('handles delayed tasks', () => {
|
||||
it('handles delayed tasks', async () => {
|
||||
Scheduler.unstable_Profiling.startLoggingProfilingEvents();
|
||||
scheduleCallback(
|
||||
NormalPriority,
|
||||
@@ -448,11 +454,11 @@ Task 2 [Normal] │ ░░░░░░░░🡐 canceled
|
||||
delay: 1000,
|
||||
},
|
||||
);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
|
||||
Scheduler.unstable_advanceTime(1000);
|
||||
|
||||
expect(Scheduler).toFlushAndYield(['A']);
|
||||
await waitForAll(['A']);
|
||||
|
||||
expect(stopProfilingAndPrintFlamegraph()).toEqual(
|
||||
`
|
||||
@@ -462,7 +468,7 @@ Task 1 [Normal] │ █████████
|
||||
);
|
||||
});
|
||||
|
||||
it('handles cancelling a delayed task', () => {
|
||||
it('handles cancelling a delayed task', async () => {
|
||||
Scheduler.unstable_Profiling.startLoggingProfilingEvents();
|
||||
const task = scheduleCallback(
|
||||
NormalPriority,
|
||||
@@ -470,7 +476,7 @@ Task 1 [Normal] │ █████████
|
||||
{delay: 1000},
|
||||
);
|
||||
cancelCallback(task);
|
||||
expect(Scheduler).toFlushWithoutYielding();
|
||||
await waitForAll([]);
|
||||
expect(stopProfilingAndPrintFlamegraph()).toEqual(
|
||||
`
|
||||
!!! Main thread │
|
||||
@@ -492,7 +498,7 @@ Task 1 [Normal] │ █████████
|
||||
taskId++;
|
||||
const task = scheduleCallback(NormalPriority, () => {});
|
||||
cancelCallback(task);
|
||||
expect(Scheduler).toFlushAndYield([]);
|
||||
await waitForAll([]);
|
||||
}
|
||||
|
||||
expect(console.error).toHaveBeenCalledTimes(1);
|
||||
@@ -509,7 +515,7 @@ Task 1 [Normal] │ █████████
|
||||
scheduleCallback(NormalPriority, () => {
|
||||
Scheduler.unstable_advanceTime(1000);
|
||||
});
|
||||
expect(Scheduler).toFlushAndYield([]);
|
||||
await waitForAll([]);
|
||||
|
||||
// Note: The exact task id is not super important. That just how many tasks
|
||||
// it happens to take before the array is resized.
|
||||
|
||||
@@ -16,6 +16,9 @@ let React;
|
||||
let ReactTestRenderer;
|
||||
let Scheduler;
|
||||
let ReplaySubject;
|
||||
let assertLog;
|
||||
let waitForAll;
|
||||
let waitFor;
|
||||
|
||||
describe('useSubscription', () => {
|
||||
beforeEach(() => {
|
||||
@@ -31,6 +34,11 @@ describe('useSubscription', () => {
|
||||
|
||||
BehaviorSubject = require('rxjs').BehaviorSubject;
|
||||
ReplaySubject = require('rxjs').ReplaySubject;
|
||||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
waitForAll = InternalTestUtils.waitForAll;
|
||||
assertLog = InternalTestUtils.assertLog;
|
||||
waitFor = InternalTestUtils.waitFor;
|
||||
});
|
||||
|
||||
function createBehaviorSubject(initialValue) {
|
||||
@@ -49,7 +57,7 @@ describe('useSubscription', () => {
|
||||
return replaySubject;
|
||||
}
|
||||
|
||||
it('supports basic subscription pattern', () => {
|
||||
it('supports basic subscription pattern', async () => {
|
||||
function Child({value = 'default'}) {
|
||||
Scheduler.unstable_yieldValue(value);
|
||||
return null;
|
||||
@@ -79,21 +87,21 @@ describe('useSubscription', () => {
|
||||
{unstable_isConcurrent: true},
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['default']);
|
||||
assertLog(['default']);
|
||||
|
||||
// Updates while subscribed should re-render the child component
|
||||
act(() => observable.next(123));
|
||||
expect(Scheduler).toHaveYielded([123]);
|
||||
assertLog([123]);
|
||||
act(() => observable.next('abc'));
|
||||
expect(Scheduler).toHaveYielded(['abc']);
|
||||
assertLog(['abc']);
|
||||
|
||||
// Unmounting the subscriber should remove listeners
|
||||
act(() => renderer.update(<div />));
|
||||
act(() => observable.next(456));
|
||||
expect(Scheduler).toFlushAndYield([]);
|
||||
await waitForAll([]);
|
||||
});
|
||||
|
||||
it('should support observable types like RxJS ReplaySubject', () => {
|
||||
it('should support observable types like RxJS ReplaySubject', async () => {
|
||||
function Child({value = 'default'}) {
|
||||
Scheduler.unstable_yieldValue(value);
|
||||
return null;
|
||||
@@ -131,19 +139,19 @@ describe('useSubscription', () => {
|
||||
{unstable_isConcurrent: true},
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['initial']);
|
||||
assertLog(['initial']);
|
||||
act(() => observable.next('updated'));
|
||||
expect(Scheduler).toHaveYielded(['updated']);
|
||||
assertLog(['updated']);
|
||||
|
||||
Scheduler.unstable_flushAll();
|
||||
await waitForAll([]);
|
||||
|
||||
// Unsetting the subscriber prop should reset subscribed values
|
||||
observable = createReplaySubject(undefined);
|
||||
act(() => renderer.update(<Subscription source={observable} />));
|
||||
expect(Scheduler).toHaveYielded(['default']);
|
||||
assertLog(['default']);
|
||||
});
|
||||
|
||||
it('should unsubscribe from old sources and subscribe to new sources when memoized props change', () => {
|
||||
it('should unsubscribe from old sources and subscribe to new sources when memoized props change', async () => {
|
||||
function Child({value = 'default'}) {
|
||||
Scheduler.unstable_yieldValue(value);
|
||||
return null;
|
||||
@@ -182,29 +190,29 @@ describe('useSubscription', () => {
|
||||
});
|
||||
|
||||
// Updates while subscribed should re-render the child component
|
||||
expect(Scheduler).toHaveYielded(['a-0']);
|
||||
assertLog(['a-0']);
|
||||
expect(subscriptions).toHaveLength(1);
|
||||
expect(subscriptions[0]).toBe(observableA);
|
||||
|
||||
// Unsetting the subscriber prop should reset subscribed values
|
||||
act(() => renderer.update(<Subscription source={observableB} />));
|
||||
|
||||
expect(Scheduler).toHaveYielded(['b-0']);
|
||||
assertLog(['b-0']);
|
||||
expect(subscriptions).toHaveLength(2);
|
||||
expect(subscriptions[1]).toBe(observableB);
|
||||
|
||||
// Updates to the old subscribable should not re-render the child component
|
||||
act(() => observableA.next('a-1'));
|
||||
expect(Scheduler).toFlushAndYield([]);
|
||||
await waitForAll([]);
|
||||
|
||||
// Updates to the bew subscribable should re-render the child component
|
||||
act(() => observableB.next('b-1'));
|
||||
expect(Scheduler).toHaveYielded(['b-1']);
|
||||
assertLog(['b-1']);
|
||||
|
||||
expect(subscriptions).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should unsubscribe from old sources and subscribe to new sources when useCallback functions change', () => {
|
||||
it('should unsubscribe from old sources and subscribe to new sources when useCallback functions change', async () => {
|
||||
function Child({value = 'default'}) {
|
||||
Scheduler.unstable_yieldValue(value);
|
||||
return null;
|
||||
@@ -241,28 +249,28 @@ describe('useSubscription', () => {
|
||||
});
|
||||
|
||||
// Updates while subscribed should re-render the child component
|
||||
expect(Scheduler).toHaveYielded(['a-0']);
|
||||
assertLog(['a-0']);
|
||||
expect(subscriptions).toHaveLength(1);
|
||||
expect(subscriptions[0]).toBe(observableA);
|
||||
|
||||
// Unsetting the subscriber prop should reset subscribed values
|
||||
act(() => renderer.update(<Subscription source={observableB} />));
|
||||
expect(Scheduler).toHaveYielded(['b-0']);
|
||||
assertLog(['b-0']);
|
||||
expect(subscriptions).toHaveLength(2);
|
||||
expect(subscriptions[1]).toBe(observableB);
|
||||
|
||||
// Updates to the old subscribable should not re-render the child component
|
||||
act(() => observableA.next('a-1'));
|
||||
expect(Scheduler).toFlushAndYield([]);
|
||||
await waitForAll([]);
|
||||
|
||||
// Updates to the bew subscribable should re-render the child component
|
||||
act(() => observableB.next('b-1'));
|
||||
expect(Scheduler).toHaveYielded(['b-1']);
|
||||
assertLog(['b-1']);
|
||||
|
||||
expect(subscriptions).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('should ignore values emitted by a new subscribable until the commit phase', () => {
|
||||
it('should ignore values emitted by a new subscribable until the commit phase', async () => {
|
||||
const log = [];
|
||||
|
||||
function Grandchild({value}) {
|
||||
@@ -326,16 +334,16 @@ describe('useSubscription', () => {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Child: a-0', 'Grandchild: a-0']);
|
||||
assertLog(['Child: a-0', 'Grandchild: a-0']);
|
||||
expect(log).toEqual(['Parent.componentDidMount']);
|
||||
|
||||
// Start React update, but don't finish
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
React.startTransition(() => {
|
||||
renderer.update(<Parent observed={observableB} />);
|
||||
});
|
||||
|
||||
expect(Scheduler).toFlushAndYieldThrough(['Child: b-0']);
|
||||
await waitFor(['Child: b-0']);
|
||||
expect(log).toEqual(['Parent.componentDidMount']);
|
||||
|
||||
// Emit some updates from the uncommitted subscribable
|
||||
@@ -351,7 +359,7 @@ describe('useSubscription', () => {
|
||||
// We expect the last emitted update to be rendered (because of the commit phase value check)
|
||||
// But the intermediate ones should be ignored,
|
||||
// And the final rendered output should be the higher-priority observable.
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Grandchild: b-0',
|
||||
'Child: b-3',
|
||||
'Grandchild: b-3',
|
||||
@@ -365,7 +373,7 @@ describe('useSubscription', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not drop values emitted between updates', () => {
|
||||
it('should not drop values emitted between updates', async () => {
|
||||
const log = [];
|
||||
|
||||
function Grandchild({value}) {
|
||||
@@ -429,16 +437,16 @@ describe('useSubscription', () => {
|
||||
unstable_isConcurrent: true,
|
||||
});
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Child: a-0', 'Grandchild: a-0']);
|
||||
assertLog(['Child: a-0', 'Grandchild: a-0']);
|
||||
expect(log).toEqual(['Parent.componentDidMount:a-0']);
|
||||
log.splice(0);
|
||||
|
||||
// Start React update, but don't finish
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
React.startTransition(() => {
|
||||
renderer.update(<Parent observed={observableB} />);
|
||||
});
|
||||
expect(Scheduler).toFlushAndYieldThrough(['Child: b-0']);
|
||||
await waitFor(['Child: b-0']);
|
||||
expect(log).toEqual([]);
|
||||
|
||||
// Emit some updates from the old subscribable
|
||||
@@ -455,7 +463,7 @@ describe('useSubscription', () => {
|
||||
}
|
||||
|
||||
// Flush everything and ensure that the correct subscribable is used
|
||||
expect(Scheduler).toFlushAndYield([
|
||||
await waitForAll([
|
||||
'Child: a-2',
|
||||
'Grandchild: a-2',
|
||||
'Child: a-2',
|
||||
@@ -467,7 +475,7 @@ describe('useSubscription', () => {
|
||||
// Updates from the new subscribable should be ignored.
|
||||
log.splice(0);
|
||||
act(() => observableB.next('b-1'));
|
||||
expect(Scheduler).toFlushAndYield([]);
|
||||
await waitForAll([]);
|
||||
expect(log).toEqual([]);
|
||||
});
|
||||
|
||||
@@ -514,10 +522,9 @@ describe('useSubscription', () => {
|
||||
},
|
||||
};
|
||||
|
||||
eventHandler.subscribe(value => {
|
||||
eventHandler.subscribe(async value => {
|
||||
if (value === false) {
|
||||
renderer.unmount();
|
||||
expect(Scheduler).toFlushAndYield([]);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -528,13 +535,13 @@ describe('useSubscription', () => {
|
||||
{unstable_isConcurrent: true},
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([true]);
|
||||
assertLog([true]);
|
||||
|
||||
// This event should unmount
|
||||
eventHandler.change(false);
|
||||
});
|
||||
|
||||
it('does not return a value from the previous subscription if the source is updated', () => {
|
||||
it('does not return a value from the previous subscription if the source is updated', async () => {
|
||||
const subscription1 = {
|
||||
getCurrentValue: () => 'one',
|
||||
subscribe: () => () => {},
|
||||
@@ -562,13 +569,13 @@ describe('useSubscription', () => {
|
||||
{unstable_isConcurrent: true},
|
||||
);
|
||||
});
|
||||
Scheduler.unstable_flushAll();
|
||||
await waitForAll([]);
|
||||
|
||||
act(() => renderer.update(<Subscription subscription={subscription2} />));
|
||||
Scheduler.unstable_flushAll();
|
||||
await waitForAll([]);
|
||||
});
|
||||
|
||||
it('should not tear if a mutation occurs during a concurrent update', () => {
|
||||
it('should not tear if a mutation occurs during a concurrent update', async () => {
|
||||
const input = document.createElement('input');
|
||||
|
||||
const mutate = value => {
|
||||
@@ -590,7 +597,7 @@ describe('useSubscription', () => {
|
||||
return value;
|
||||
};
|
||||
|
||||
act(() => {
|
||||
await act(async () => {
|
||||
// Initial render of "A"
|
||||
mutate('A');
|
||||
ReactTestRenderer.create(
|
||||
@@ -600,13 +607,13 @@ describe('useSubscription', () => {
|
||||
</React.Fragment>,
|
||||
{unstable_isConcurrent: true},
|
||||
);
|
||||
expect(Scheduler).toFlushAndYield(['render:first:A', 'render:second:A']);
|
||||
await waitForAll(['render:first:A', 'render:second:A']);
|
||||
|
||||
// Update state "A" -> "B"
|
||||
// This update will be eagerly evaluated,
|
||||
// so the tearing case this test is guarding against would not happen.
|
||||
mutate('B');
|
||||
expect(Scheduler).toFlushAndYield(['render:first:B', 'render:second:B']);
|
||||
await waitForAll(['render:first:B', 'render:second:B']);
|
||||
|
||||
// No more pending updates
|
||||
jest.runAllTimers();
|
||||
@@ -618,14 +625,11 @@ describe('useSubscription', () => {
|
||||
React.startTransition(() => {
|
||||
mutate('C');
|
||||
});
|
||||
expect(Scheduler).toFlushAndYieldThrough([
|
||||
'render:first:C',
|
||||
'render:second:C',
|
||||
]);
|
||||
await waitFor(['render:first:C', 'render:second:C']);
|
||||
React.startTransition(() => {
|
||||
mutate('D');
|
||||
});
|
||||
expect(Scheduler).toFlushAndYield(['render:first:D', 'render:second:D']);
|
||||
await waitForAll(['render:first:D', 'render:second:D']);
|
||||
|
||||
// No more pending updates
|
||||
jest.runAllTimers();
|
||||
|
||||
@@ -17,6 +17,7 @@ let Scheduler;
|
||||
let useSyncExternalStore;
|
||||
let useSyncExternalStoreWithSelector;
|
||||
let act;
|
||||
let assertLog;
|
||||
|
||||
// This tests the userspace shim of `useSyncExternalStore` in a server-rendering
|
||||
// (Node) environment
|
||||
@@ -50,6 +51,9 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => {
|
||||
Scheduler = require('scheduler');
|
||||
act = require('jest-react').act;
|
||||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
assertLog = InternalTestUtils.assertLog;
|
||||
|
||||
if (gate(flags => flags.source)) {
|
||||
// The `shim/with-selector` module composes the main
|
||||
// `use-sync-external-store` entrypoint. In the compiled artifacts, this
|
||||
@@ -116,7 +120,7 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => {
|
||||
await act(() => {
|
||||
root.render(<App />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['client']);
|
||||
assertLog(['client']);
|
||||
expect(root).toMatchRenderedOutput('client');
|
||||
});
|
||||
|
||||
@@ -159,7 +163,7 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => {
|
||||
const root = ReactNoop.createRoot();
|
||||
act(() => root.render(<App />));
|
||||
|
||||
expect(Scheduler).toHaveYielded(['A0', 'B0']);
|
||||
assertLog(['A0', 'B0']);
|
||||
expect(root).toMatchRenderedOutput('A0B0');
|
||||
|
||||
// Update b but not a
|
||||
@@ -167,7 +171,7 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => {
|
||||
store.set({a: 0, b: 1});
|
||||
});
|
||||
// Only b re-renders
|
||||
expect(Scheduler).toHaveYielded(['B1']);
|
||||
assertLog(['B1']);
|
||||
expect(root).toMatchRenderedOutput('A0B1');
|
||||
|
||||
// Update a but not b
|
||||
@@ -175,7 +179,7 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => {
|
||||
store.set({a: 1, b: 1});
|
||||
});
|
||||
// Only a re-renders
|
||||
expect(Scheduler).toHaveYielded(['A1']);
|
||||
assertLog(['A1']);
|
||||
expect(root).toMatchRenderedOutput('A1B1');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -20,6 +20,7 @@ let act;
|
||||
let useState;
|
||||
let useEffect;
|
||||
let useLayoutEffect;
|
||||
let assertLog;
|
||||
|
||||
// This tests shared behavior between the built-in and shim implementations of
|
||||
// of useSyncExternalStore.
|
||||
@@ -55,6 +56,9 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
|
||||
useEffect = React.useEffect;
|
||||
useLayoutEffect = React.useLayoutEffect;
|
||||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
assertLog = InternalTestUtils.assertLog;
|
||||
|
||||
const internalAct = require('jest-react').act;
|
||||
|
||||
// The internal act implementation doesn't batch updates by default, since
|
||||
@@ -140,13 +144,13 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
|
||||
const root = createRoot(container);
|
||||
await act(() => root.render(<App />));
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Initial']);
|
||||
assertLog(['Initial']);
|
||||
expect(container.textContent).toEqual('Initial');
|
||||
|
||||
await act(() => {
|
||||
store.set('Updated');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['Updated']);
|
||||
assertLog(['Updated']);
|
||||
expect(container.textContent).toEqual('Updated');
|
||||
});
|
||||
|
||||
@@ -162,7 +166,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
|
||||
const root = createRoot(container);
|
||||
act(() => root.render(<App />));
|
||||
|
||||
expect(Scheduler).toHaveYielded(['Initial']);
|
||||
assertLog(['Initial']);
|
||||
expect(container.textContent).toEqual('Initial');
|
||||
|
||||
// Update to the same value
|
||||
@@ -170,7 +174,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
|
||||
store.set('Initial');
|
||||
});
|
||||
// Should not re-render
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
assertLog([]);
|
||||
expect(container.textContent).toEqual('Initial');
|
||||
});
|
||||
|
||||
@@ -190,13 +194,13 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
|
||||
const root = createRoot(container);
|
||||
await act(() => root.render(<App />));
|
||||
|
||||
expect(Scheduler).toHaveYielded([0]);
|
||||
assertLog([0]);
|
||||
expect(container.textContent).toEqual('0');
|
||||
|
||||
await act(() => {
|
||||
storeA.set(1);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([1]);
|
||||
assertLog([1]);
|
||||
expect(container.textContent).toEqual('1');
|
||||
|
||||
// Switch stores and update in the same batch
|
||||
@@ -208,7 +212,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
|
||||
});
|
||||
});
|
||||
// Now reading from B instead of A
|
||||
expect(Scheduler).toHaveYielded([0]);
|
||||
assertLog([0]);
|
||||
expect(container.textContent).toEqual('0');
|
||||
|
||||
// Update A
|
||||
@@ -216,14 +220,14 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
|
||||
storeA.set(3);
|
||||
});
|
||||
// Nothing happened, because we're no longer subscribed to A
|
||||
expect(Scheduler).toHaveYielded([]);
|
||||
assertLog([]);
|
||||
expect(container.textContent).toEqual('0');
|
||||
|
||||
// Update B
|
||||
await act(() => {
|
||||
storeB.set(1);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([1]);
|
||||
assertLog([1]);
|
||||
expect(container.textContent).toEqual('1');
|
||||
});
|
||||
|
||||
@@ -252,7 +256,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
|
||||
const root = createRoot(container);
|
||||
act(() => root.render(<App />));
|
||||
|
||||
expect(Scheduler).toHaveYielded(['A0', 'B0']);
|
||||
assertLog(['A0', 'B0']);
|
||||
expect(container.textContent).toEqual('A0B0');
|
||||
|
||||
// Update b but not a
|
||||
@@ -260,7 +264,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
|
||||
store.set({a: 0, b: 1});
|
||||
});
|
||||
// Only b re-renders
|
||||
expect(Scheduler).toHaveYielded(['B1']);
|
||||
assertLog(['B1']);
|
||||
expect(container.textContent).toEqual('A0B1');
|
||||
|
||||
// Update a but not b
|
||||
@@ -268,7 +272,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
|
||||
store.set({a: 1, b: 1});
|
||||
});
|
||||
// Only a re-renders
|
||||
expect(Scheduler).toHaveYielded(['A1']);
|
||||
assertLog(['A1']);
|
||||
expect(container.textContent).toEqual('A1B1');
|
||||
});
|
||||
|
||||
@@ -292,13 +296,13 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
|
||||
const container = document.createElement('div');
|
||||
const root = createRoot(container);
|
||||
act(() => root.render(<App />));
|
||||
expect(Scheduler).toHaveYielded([0, 'Passive effect: 0']);
|
||||
assertLog([0, 'Passive effect: 0']);
|
||||
|
||||
// Schedule an update. We'll intentionally not use `act` so that we can
|
||||
// insert a mutation before React subscribes to the store in a
|
||||
// passive effect.
|
||||
store.set(1);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
1,
|
||||
// Passive effect hasn't fired yet
|
||||
]);
|
||||
@@ -306,7 +310,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
|
||||
|
||||
// Flip the store state back to the previous value.
|
||||
store.set(0);
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Passive effect: 1',
|
||||
// Re-render. If the current state were tracked by updating a ref in a
|
||||
// passive effect, then this would break because the previous render's
|
||||
@@ -362,14 +366,14 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
|
||||
const container = document.createElement('div');
|
||||
const root = createRoot(container);
|
||||
act(() => root.render(<App />));
|
||||
expect(Scheduler).toHaveYielded(['A1']);
|
||||
assertLog(['A1']);
|
||||
expect(container.textContent).toEqual('A1');
|
||||
|
||||
act(() => {
|
||||
// Change getSnapshot and update the store in the same batch
|
||||
setStep(1);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'B1',
|
||||
'Update B in commit phase',
|
||||
// If Child2 had used the old getSnapshot to bail out, then it would have
|
||||
@@ -420,7 +424,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
|
||||
const container = document.createElement('div');
|
||||
const root = createRoot(container);
|
||||
act(() => root.render(<App />));
|
||||
expect(Scheduler).toHaveYielded(['A1']);
|
||||
assertLog(['A1']);
|
||||
expect(container.textContent).toEqual('A1');
|
||||
|
||||
// This will cause a layout effect, and in the layout effect we'll update
|
||||
@@ -428,7 +432,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
|
||||
act(() => {
|
||||
setStep(1);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'A1',
|
||||
// This updates B, but since Child2 doesn't subscribe to B, it doesn't
|
||||
// need to re-render.
|
||||
@@ -467,13 +471,13 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
|
||||
</>,
|
||||
),
|
||||
);
|
||||
expect(Scheduler).toHaveYielded([0, 0]);
|
||||
assertLog([0, 0]);
|
||||
expect(container.textContent).toEqual('00');
|
||||
|
||||
await act(() => {
|
||||
store.set(1);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([1, 1, 'Reset back to 0', 0, 0]);
|
||||
assertLog([1, 1, 'Reset back to 0', 0, 0]);
|
||||
expect(container.textContent).toEqual('00');
|
||||
});
|
||||
|
||||
@@ -494,7 +498,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
|
||||
const container = document.createElement('div');
|
||||
const root = createRoot(container);
|
||||
act(() => root.render(<App />));
|
||||
expect(Scheduler).toHaveYielded([0]);
|
||||
assertLog([0]);
|
||||
|
||||
// Update the store and getSnapshot at the same time
|
||||
act(() => {
|
||||
@@ -504,7 +508,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
|
||||
});
|
||||
});
|
||||
// It should read from B instead of A
|
||||
expect(Scheduler).toHaveYielded([2]);
|
||||
assertLog([2]);
|
||||
expect(container.textContent).toEqual('2');
|
||||
});
|
||||
|
||||
@@ -549,7 +553,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
|
||||
</ErrorBoundary>,
|
||||
),
|
||||
);
|
||||
expect(Scheduler).toHaveYielded([0]);
|
||||
assertLog([0]);
|
||||
expect(container.textContent).toEqual('0');
|
||||
|
||||
// Update that throws in a getSnapshot. We can catch it with an error boundary.
|
||||
@@ -557,14 +561,14 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
|
||||
store.set({value: 1, throwInGetSnapshot: true, throwInIsEqual: false});
|
||||
});
|
||||
if (gate(flags => !flags.enableUseSyncExternalStoreShim)) {
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
'Error in getSnapshot',
|
||||
// In a concurrent root, React renders a second time to attempt to
|
||||
// recover from the error.
|
||||
'Error in getSnapshot',
|
||||
]);
|
||||
} else {
|
||||
expect(Scheduler).toHaveYielded(['Error in getSnapshot']);
|
||||
assertLog(['Error in getSnapshot']);
|
||||
}
|
||||
expect(container.textContent).toEqual('Error in getSnapshot');
|
||||
});
|
||||
@@ -644,14 +648,14 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
|
||||
const root = createRoot(container);
|
||||
act(() => root.render(<App />));
|
||||
|
||||
expect(Scheduler).toHaveYielded(['App', 'Selector', 'A0']);
|
||||
assertLog(['App', 'Selector', 'A0']);
|
||||
expect(container.textContent).toEqual('A0');
|
||||
|
||||
// Update the store
|
||||
await act(() => {
|
||||
store.set({a: 1, b: 0});
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
// The selector runs before React starts rendering
|
||||
'Selector',
|
||||
'App',
|
||||
@@ -703,7 +707,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
|
||||
const root = createRoot(container);
|
||||
act(() => root.render(<App />));
|
||||
|
||||
expect(Scheduler).toHaveYielded(['A0', 'B0']);
|
||||
assertLog(['A0', 'B0']);
|
||||
expect(container.textContent).toEqual('A0B0');
|
||||
|
||||
// Update b but not a
|
||||
@@ -711,7 +715,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
|
||||
store.set({a: 0, b: 1});
|
||||
});
|
||||
// Only b re-renders
|
||||
expect(Scheduler).toHaveYielded(['B1']);
|
||||
assertLog(['B1']);
|
||||
expect(container.textContent).toEqual('A0B1');
|
||||
|
||||
// Update a but not b
|
||||
@@ -719,7 +723,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
|
||||
store.set({a: 1, b: 1});
|
||||
});
|
||||
// Only a re-renders
|
||||
expect(Scheduler).toHaveYielded(['A1']);
|
||||
assertLog(['A1']);
|
||||
expect(container.textContent).toEqual('A1B1');
|
||||
});
|
||||
|
||||
@@ -751,7 +755,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
|
||||
act(() => {
|
||||
ReactDOMClient.hydrateRoot(container, <App />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
// First it hydrates the server rendered HTML
|
||||
'server',
|
||||
'Passive effect: server',
|
||||
@@ -769,7 +773,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
|
||||
'Text content did not match',
|
||||
);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['client', 'Passive effect: client']);
|
||||
assertLog(['client', 'Passive effect: client']);
|
||||
}
|
||||
expect(container.textContent).toEqual('client');
|
||||
expect(ref.current).toEqual(serverRenderedDiv);
|
||||
@@ -793,13 +797,13 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
|
||||
const root = createRoot(container);
|
||||
await act(() => root.render(<App />));
|
||||
|
||||
expect(Scheduler).toHaveYielded(['INITIAL']);
|
||||
assertLog(['INITIAL']);
|
||||
expect(container.textContent).toEqual('INITIAL');
|
||||
|
||||
await act(() => {
|
||||
store.set('Updated');
|
||||
});
|
||||
expect(Scheduler).toHaveYielded(['UPDATED']);
|
||||
assertLog(['UPDATED']);
|
||||
expect(container.textContent).toEqual('UPDATED');
|
||||
});
|
||||
|
||||
@@ -857,18 +861,12 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
|
||||
await act(() => {
|
||||
root.render(<App step={0} />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
'Inline selector',
|
||||
'A',
|
||||
'B',
|
||||
'C',
|
||||
'Sibling: 0',
|
||||
]);
|
||||
assertLog(['Inline selector', 'A', 'B', 'C', 'Sibling: 0']);
|
||||
|
||||
await act(() => {
|
||||
root.render(<App step={1} />);
|
||||
});
|
||||
expect(Scheduler).toHaveYielded([
|
||||
assertLog([
|
||||
// We had to call the selector again because it's not memoized
|
||||
'Inline selector',
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ let React;
|
||||
let ReactDOM;
|
||||
let ReactDOMServer;
|
||||
let Scheduler;
|
||||
let assertLog;
|
||||
|
||||
// This tests the userspace shim of `useSyncExternalStore` in a server-rendering
|
||||
// (Node) environment
|
||||
@@ -45,6 +46,9 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => {
|
||||
ReactDOMServer = require('react-dom/server');
|
||||
Scheduler = require('scheduler');
|
||||
|
||||
const InternalTestUtils = require('internal-test-utils');
|
||||
assertLog = InternalTestUtils.assertLog;
|
||||
|
||||
useSyncExternalStore =
|
||||
require('use-sync-external-store/shim').useSyncExternalStore;
|
||||
});
|
||||
@@ -92,7 +96,7 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => {
|
||||
const html = ReactDOMServer.renderToString(<App />);
|
||||
|
||||
// We don't call getServerSnapshot in the shim
|
||||
expect(Scheduler).toHaveYielded(['client']);
|
||||
assertLog(['client']);
|
||||
expect(html).toEqual('client');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user