main
js 173 lines 4.28 KB
Raw
1 let React;
2 let ReactNoop;
3 let Scheduler;
4 let waitForAll;
5 let assertLog;
6 let ReactCache;
7 let Suspense;
8 let TextResource;
9 let act;
10
11 describe('ReactBlockingMode', () => {
12 beforeEach(() => {
13 jest.resetModules();
14 React = require('react');
15 ReactNoop = require('react-noop-renderer');
16 Scheduler = require('scheduler');
17 ReactCache = require('react-cache');
18 Suspense = React.Suspense;
19
20 const InternalTestUtils = require('internal-test-utils');
21 waitForAll = InternalTestUtils.waitForAll;
22 assertLog = InternalTestUtils.assertLog;
23 act = InternalTestUtils.act;
24
25 TextResource = ReactCache.unstable_createResource(
26 ([text, ms = 0]) => {
27 return new Promise((resolve, reject) =>
28 setTimeout(() => {
29 Scheduler.log(`Promise resolved [${text}]`);
30 resolve(text);
31 }, ms),
32 );
33 },
34 ([text, ms]) => text,
35 );
36 });
37
38 function Text(props) {
39 Scheduler.log(props.text);
40 return props.text;
41 }
42
43 function AsyncText(props) {
44 const text = props.text;
45 try {
46 TextResource.read([props.text, props.ms]);
47 Scheduler.log(text);
48 return props.text;
49 } catch (promise) {
50 if (typeof promise.then === 'function') {
51 Scheduler.log(`Suspend! [${text}]`);
52 } else {
53 Scheduler.log(`Error! [${text}]`);
54 }
55 throw promise;
56 }
57 }
58
59 it('updates flush without yielding in the next event', async () => {
60 const root = ReactNoop.createRoot();
61
62 root.render(
63 <>
64 <Text text="A" />
65 <Text text="B" />
66 <Text text="C" />
67 </>,
68 );
69
70 // Nothing should have rendered yet
71 expect(root).toMatchRenderedOutput(null);
72
73 await waitForAll(['A', 'B', 'C']);
74 expect(root).toMatchRenderedOutput('ABC');
75 });
76
77 it('layout updates flush synchronously in same event', async () => {
78 const {useLayoutEffect} = React;
79
80 function App() {
81 useLayoutEffect(() => {
82 Scheduler.log('Layout effect');
83 });
84 return <Text text="Hi" />;
85 }
86
87 const root = ReactNoop.createRoot();
88 root.render(<App />);
89 expect(root).toMatchRenderedOutput(null);
90 assertLog([]);
91
92 await waitForAll(['Hi', 'Layout effect']);
93 expect(root).toMatchRenderedOutput('Hi');
94 });
95
96 it('uses proper Suspense semantics, not legacy ones', async () => {
97 const root = ReactNoop.createRoot();
98 root.render(
99 <Suspense fallback={<Text text="Loading..." />}>
100 <span>
101 <Text text="A" />
102 </span>
103 <span>
104 <AsyncText text="B" />
105 </span>
106 <span>
107 <Text text="C" />
108 </span>
109 </Suspense>,
110 );
111
112 await waitForAll([
113 'A',
114 'Suspend! [B]',
115 'Loading...',
116 // pre-warming
117 'A',
118 'Suspend! [B]',
119 'C',
120 ]);
121 // In Legacy Mode, A and B would mount in a hidden primary tree. In
122 // Concurrent Mode, nothing in the primary tree should mount. But the
123 // fallback should mount immediately.
124 expect(root).toMatchRenderedOutput('Loading...');
125
126 await act(() => jest.advanceTimersByTime(1000));
127 assertLog(['Promise resolved [B]', 'A', 'B', 'C']);
128 expect(root).toMatchRenderedOutput(
129 <>
130 <span>A</span>
131 <span>B</span>
132 <span>C</span>
133 </>,
134 );
135 });
136
137 it('flushSync does not flush batched work', async () => {
138 const {useState, forwardRef, useImperativeHandle} = React;
139 const root = ReactNoop.createRoot();
140
141 const Foo = forwardRef(({label}, ref) => {
142 const [step, setStep] = useState(0);
143 useImperativeHandle(ref, () => ({setStep}));
144 return <Text text={label + step} />;
145 });
146
147 const foo1 = React.createRef(null);
148 const foo2 = React.createRef(null);
149 root.render(
150 <>
151 <Foo label="A" ref={foo1} />
152 <Foo label="B" ref={foo2} />
153 </>,
154 );
155
156 await waitForAll(['A0', 'B0']);
157 expect(root).toMatchRenderedOutput('A0B0');
158
159 // Schedule a batched update to the first sibling
160 ReactNoop.batchedUpdates(() => foo1.current.setStep(1));
161
162 // Before it flushes, update the second sibling inside flushSync
163 ReactNoop.batchedUpdates(() =>
164 ReactNoop.flushSync(() => {
165 foo2.current.setStep(1);
166 }),
167 );
168
169 // Now flush the first update
170 assertLog(['A1', 'B1']);
171 expect(root).toMatchRenderedOutput('A1B1');
172 });
173 });