main
js 136 lines 3.51 KB
Raw
1 let React;
2 let ReactNoop;
3 let Scheduler;
4 let startTransition;
5 let useState;
6 let useEffect;
7 let act;
8 let assertLog;
9 let waitFor;
10 let waitForPaint;
11
12 describe('ReactInterleavedUpdates', () => {
13 beforeEach(() => {
14 jest.resetModules();
15
16 React = require('react');
17 ReactNoop = require('react-noop-renderer');
18 Scheduler = require('scheduler');
19 act = require('internal-test-utils').act;
20 startTransition = React.startTransition;
21 useState = React.useState;
22 useEffect = React.useEffect;
23
24 const InternalTestUtils = require('internal-test-utils');
25 assertLog = InternalTestUtils.assertLog;
26 waitFor = InternalTestUtils.waitFor;
27 waitForPaint = InternalTestUtils.waitForPaint;
28 });
29
30 function Text({text}) {
31 Scheduler.log(text);
32 return text;
33 }
34
35 it('update during an interleaved event is not processed during the current render', async () => {
36 const updaters = [];
37
38 function Child() {
39 const [state, setState] = useState(0);
40 useEffect(() => {
41 updaters.push(setState);
42 }, []);
43 return <Text text={state} />;
44 }
45
46 function updateChildren(value) {
47 for (let i = 0; i < updaters.length; i++) {
48 const setState = updaters[i];
49 setState(value);
50 }
51 }
52
53 const root = ReactNoop.createRoot();
54
55 await act(() => {
56 root.render(
57 <>
58 <Child />
59 <Child />
60 <Child />
61 </>,
62 );
63 });
64 assertLog([0, 0, 0]);
65 expect(root).toMatchRenderedOutput('000');
66
67 await act(async () => {
68 React.startTransition(() => {
69 updateChildren(1);
70 });
71 // Partially render the children. Only the first one.
72 await waitFor([1]);
73
74 // In an interleaved event, schedule an update on each of the children.
75 // Including the two that haven't rendered yet.
76 React.startTransition(() => {
77 updateChildren(2);
78 });
79
80 // We should continue rendering without including the interleaved updates.
81 await waitForPaint([1, 1]);
82 expect(root).toMatchRenderedOutput('111');
83 });
84 // The interleaved updates flush in a separate render.
85 assertLog([2, 2, 2]);
86 expect(root).toMatchRenderedOutput('222');
87 });
88
89 it('regression for #24350: does not add to main update queue until interleaved update queue has been cleared', async () => {
90 let setStep;
91 function App() {
92 const [step, _setState] = useState(0);
93 setStep = _setState;
94 return (
95 <>
96 <Text text={'A' + step} />
97 <Text text={'B' + step} />
98 <Text text={'C' + step} />
99 </>
100 );
101 }
102
103 const root = ReactNoop.createRoot();
104 await act(() => {
105 root.render(<App />);
106 });
107 assertLog(['A0', 'B0', 'C0']);
108 expect(root).toMatchRenderedOutput('A0B0C0');
109
110 await act(async () => {
111 // Start the render phase.
112 startTransition(() => {
113 setStep(1);
114 });
115 await waitFor(['A1', 'B1']);
116
117 // Schedule an interleaved update. This gets placed on a special queue.
118 startTransition(() => {
119 setStep(2);
120 });
121
122 // Finish rendering the first update.
123 await waitForPaint(['C1']);
124
125 // Schedule another update. (In the regression case, this was treated
126 // as a normal, non-interleaved update and it was inserted into the queue
127 // before the interleaved one was processed.)
128 startTransition(() => {
129 setStep(3);
130 });
131 });
132 // The last update should win.
133 assertLog(['A3', 'B3', 'C3']);
134 expect(root).toMatchRenderedOutput('A3B3C3');
135 });
136 });