let React;
let ReactNoop;
let Scheduler;
let act;
let useState;
let useEffect;
let startTransition;
describe('ReactFlushSync', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactNoop = require('react-noop-renderer');
Scheduler = require('scheduler');
act = require('jest-react').act;
useState = React.useState;
useEffect = React.useEffect;
startTransition = React.startTransition;
});
function Text({text}) {
Scheduler.unstable_yieldValue(text);
return text;
}
test('changes priority of updates in useEffect', async () => {
function App() {
const [syncState, setSyncState] = useState(0);
const [state, setState] = useState(0);
useEffect(() => {
if (syncState !== 1) {
setState(1);
ReactNoop.flushSync(() => setSyncState(1));
}
}, [syncState, state]);
return <Text text={`${syncState}, ${state}`} />;
}
const root = ReactNoop.createRoot();
await act(async () => {
if (gate(flags => flags.enableSyncDefaultUpdates)) {
React.startTransition(() => {
root.render(<App />);
});
} else {
root.render(<App />);
}
expect(Scheduler).toFlushUntilNextPaint(['0, 0']);
expect(() => {
expect(Scheduler).toFlushUntilNextPaint(
gate(flags => flags.enableUnifiedSyncLane) ? ['1, 1'] : ['1, 0'],
);
}).toErrorDev('flushSync was called from inside a lifecycle method');
ReactNoop.flushSync();
expect(Scheduler).toHaveYielded([]);
if (gate(flags => flags.enableUnifiedSyncLane)) {
expect(Scheduler).toFlushUntilNextPaint([]);
} else {
expect(Scheduler).toFlushUntilNextPaint(['1, 1']);
}
});
expect(root).toMatchRenderedOutput('1, 1');
});
test('nested with startTransition', async () => {
let setSyncState;
let setState;
function App() {
const [syncState, _setSyncState] = useState(0);
const [state, _setState] = useState(0);
setSyncState = _setSyncState;
setState = _setState;
return <Text text={`${syncState}, ${state}`} />;
}
const root = ReactNoop.createRoot();
await act(async () => {
root.render(<App />);
});
expect(Scheduler).toHaveYielded(['0, 0']);
expect(root).toMatchRenderedOutput('0, 0');
await act(async () => {
ReactNoop.flushSync(() => {
startTransition(() => {
setState(1);
ReactNoop.flushSync(() => {
setSyncState(1);
});
});
});
expect(Scheduler).toHaveYielded(['1, 0']);
expect(root).toMatchRenderedOutput('1, 0');
});
expect(Scheduler).toHaveYielded(['1, 1']);
expect(root).toMatchRenderedOutput('1, 1');
});
test('flushes passive effects synchronously when they are the result of a sync render', async () => {
function App() {
useEffect(() => {
Scheduler.unstable_yieldValue('Effect');
}, []);
return <Text text="Child" />;
}
const root = ReactNoop.createRoot();
await act(async () => {
ReactNoop.flushSync(() => {
root.render(<App />);
});
expect(Scheduler).toHaveYielded([
'Child',
'Effect',
]);
expect(root).toMatchRenderedOutput('Child');
});
});
test('do not flush passive effects synchronously after render in legacy mode', async () => {
function App() {
useEffect(() => {
Scheduler.unstable_yieldValue('Effect');
}, []);
return <Text text="Child" />;
}
const root = ReactNoop.createLegacyRoot();
await act(async () => {
ReactNoop.flushSync(() => {
root.render(<App />);
});
expect(Scheduler).toHaveYielded([
'Child',
]);
expect(root).toMatchRenderedOutput('Child');
});
expect(Scheduler).toHaveYielded(['Effect']);
});
test('flush pending passive effects before scope is called in legacy mode', async () => {
let currentStep = 0;
function App({step}) {
useEffect(() => {
currentStep = step;
Scheduler.unstable_yieldValue('Effect: ' + step);
}, [step]);
return <Text text={step} />;
}
const root = ReactNoop.createLegacyRoot();
await act(async () => {
ReactNoop.flushSync(() => {
root.render(<App step={1} />);
});
expect(Scheduler).toHaveYielded([
1,
]);
expect(root).toMatchRenderedOutput('1');
ReactNoop.flushSync(() => {
root.render(<App step={currentStep + 1} />);
});
expect(Scheduler).toHaveYielded(['Effect: 1', 2]);
expect(root).toMatchRenderedOutput('2');
});
expect(Scheduler).toHaveYielded(['Effect: 2']);
});
test("do not flush passive effects synchronously when they aren't the result of a sync render", async () => {
function App() {
useEffect(() => {
Scheduler.unstable_yieldValue('Effect');
}, []);
return <Text text="Child" />;
}
const root = ReactNoop.createRoot();
await act(async () => {
root.render(<App />);
expect(Scheduler).toFlushUntilNextPaint([
'Child',
]);
expect(root).toMatchRenderedOutput('Child');
});
expect(Scheduler).toHaveYielded(['Effect']);
});
test('does not flush pending passive effects', async () => {
function App() {
useEffect(() => {
Scheduler.unstable_yieldValue('Effect');
}, []);
return <Text text="Child" />;
}
const root = ReactNoop.createRoot();
await act(async () => {
root.render(<App />);
expect(Scheduler).toFlushUntilNextPaint(['Child']);
expect(root).toMatchRenderedOutput('Child');
ReactNoop.flushSync();
expect(Scheduler).toHaveYielded([]);
});
expect(Scheduler).toHaveYielded(['Effect']);
});
});