@samitouri / QOS-React-2 / commits / 6dc2734b41

Codemod tests to `it.experimental` (#17243)

`it.experimental` marks that a test only works in Experimental builds. It also asserts that a test does *not* work in the stable builds. The main benefit is that we're less likely to accidentally expose an experimental API before we intend. It also forces us to un- mark an experimental test once it become stable.

Andrew Clark committed Nov 1, 2019 at 10:20 UTC 6dc2734b41aef944e457eaa23ae218952fce0a54
10 files changed +1005 -1008
packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.internal.js
+295 -284
@@ -47,255 +47,254 @@ describe('ReactDOMFiberAsync', () => {
47 expect(ops).toEqual(['Hi', 'Bye']);
48 });
49
50 - if (__EXPERIMENTAL__) {
51 - describe('concurrent mode', () => {
52 - beforeEach(() => {
53 - jest.resetModules();
54 - ReactFeatureFlags = require('shared/ReactFeatureFlags');
55 - ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
56 - ReactDOM = require('react-dom');
57 - Scheduler = require('scheduler');
58 - });
59 -
60 - it('does not perform deferred updates synchronously', () => {
61 - let inputRef = React.createRef();
62 - let asyncValueRef = React.createRef();
63 - let syncValueRef = React.createRef();
64 -
65 - class Counter extends React.Component {
66 - state = {asyncValue: '', syncValue: ''};
67 -
68 - handleChange = e => {
69 - const nextValue = e.target.value;
70 - requestIdleCallback(() => {
71 - this.setState({
72 - asyncValue: nextValue,
73 - });
74 - // It should not be flushed yet.
75 - expect(asyncValueRef.current.textContent).toBe('');
76 - });
77 - this.setState({
78 - syncValue: nextValue,
79 - });
80 - };
81 -
82 - render() {
83 - return (
84 - <div>
85 - <input
86 - ref={inputRef}
87 - onChange={this.handleChange}
88 - defaultValue=""
89 - />
90 - <p ref={asyncValueRef}>{this.state.asyncValue}</p>
91 - <p ref={syncValueRef}>{this.state.syncValue}</p>
92 - </div>
93 - );
94 - }
95 - }
96 - const root = ReactDOM.createRoot(container);
97 - root.render(<Counter />);
98 - Scheduler.unstable_flushAll();
99 - expect(asyncValueRef.current.textContent).toBe('');
100 - expect(syncValueRef.current.textContent).toBe('');
101 -
102 - setUntrackedInputValue.call(inputRef.current, 'hello');
103 - inputRef.current.dispatchEvent(
104 - new MouseEvent('input', {bubbles: true}),
105 - );
106 - // Should only flush non-deferred update.
107 - expect(asyncValueRef.current.textContent).toBe('');
108 - expect(syncValueRef.current.textContent).toBe('hello');
109 -
110 - // Should flush both updates now.
111 - jest.runAllTimers();
112 - Scheduler.unstable_flushAll();
113 - expect(asyncValueRef.current.textContent).toBe('hello');
114 - expect(syncValueRef.current.textContent).toBe('hello');
115 - });
116 -
117 - it('top-level updates are concurrent', () => {
118 - const root = ReactDOM.createRoot(container);
119 - root.render(<div>Hi</div>);
120 - expect(container.textContent).toEqual('');
121 - Scheduler.unstable_flushAll();
122 - expect(container.textContent).toEqual('Hi');
123 -
124 - root.render(<div>Bye</div>);
125 - expect(container.textContent).toEqual('Hi');
126 - Scheduler.unstable_flushAll();
127 - expect(container.textContent).toEqual('Bye');
128 - });
129 -
130 - it('deep updates (setState) are concurrent', () => {
131 - let instance;
132 - class Component extends React.Component {
133 - state = {step: 0};
134 - render() {
135 - instance = this;
136 - return <div>{this.state.step}</div>;
137 - }
138 - }
139 -
140 - const root = ReactDOM.createRoot(container);
141 - root.render(<Component />);
142 - expect(container.textContent).toEqual('');
143 - Scheduler.unstable_flushAll();
144 - expect(container.textContent).toEqual('0');
145 -
146 - instance.setState({step: 1});
147 - expect(container.textContent).toEqual('0');
148 - Scheduler.unstable_flushAll();
149 - expect(container.textContent).toEqual('1');
150 - });
151 -
152 - it('flushSync batches sync updates and flushes them at the end of the batch', () => {
153 - let ops = [];
154 - let instance;
155 -
156 - class Component extends React.Component {
157 - state = {text: ''};
158 - push(val) {
159 - this.setState(state => ({text: state.text + val}));
160 - }
161 - componentDidUpdate() {
162 - ops.push(this.state.text);
163 - }
164 - render() {
165 - instance = this;
166 - return <span>{this.state.text}</span>;
167 - }
168 - }
169 -
170 - ReactDOM.render(<Component />, container);
171 -
172 - instance.push('A');
173 - expect(ops).toEqual(['A']);
174 - expect(container.textContent).toEqual('A');
50 + it('flushSync batches sync updates and flushes them at the end of the batch', () => {
51 + let ops = [];
52 + let instance;
53 +
54 + class Component extends React.Component {
55 + state = {text: ''};
56 + push(val) {
57 + this.setState(state => ({text: state.text + val}));
58 + }
59 + componentDidUpdate() {
60 + ops.push(this.state.text);
61 + }
62 + render() {
63 + instance = this;
64 + return <span>{this.state.text}</span>;
65 + }
66 + }
67 +
68 + ReactDOM.render(<Component />, container);
69 +
70 + instance.push('A');
71 + expect(ops).toEqual(['A']);
72 + expect(container.textContent).toEqual('A');
73 +
74 + ReactDOM.flushSync(() => {
75 + instance.push('B');
76 + instance.push('C');
77 + // Not flushed yet
78 + expect(container.textContent).toEqual('A');
79 + expect(ops).toEqual(['A']);
80 + });
81 + expect(container.textContent).toEqual('ABC');
82 + expect(ops).toEqual(['A', 'ABC']);
83 + instance.push('D');
84 + expect(container.textContent).toEqual('ABCD');
85 + expect(ops).toEqual(['A', 'ABC', 'ABCD']);
86 + });
87
176 - ReactDOM.flushSync(() => {
177 - instance.push('B');
178 - instance.push('C');
179 - // Not flushed yet
180 - expect(container.textContent).toEqual('A');
181 - expect(ops).toEqual(['A']);
182 - });
183 - expect(container.textContent).toEqual('ABC');
184 - expect(ops).toEqual(['A', 'ABC']);
88 + it('flushSync flushes updates even if nested inside another flushSync', () => {
89 + let ops = [];
90 + let instance;
91 +
92 + class Component extends React.Component {
93 + state = {text: ''};
94 + push(val) {
95 + this.setState(state => ({text: state.text + val}));
96 + }
97 + componentDidUpdate() {
98 + ops.push(this.state.text);
99 + }
100 + render() {
101 + instance = this;
102 + return <span>{this.state.text}</span>;
103 + }
104 + }
105 +
106 + ReactDOM.render(<Component />, container);
107 +
108 + instance.push('A');
109 + expect(ops).toEqual(['A']);
110 + expect(container.textContent).toEqual('A');
111 +
112 + ReactDOM.flushSync(() => {
113 + instance.push('B');
114 + instance.push('C');
115 + // Not flushed yet
116 + expect(container.textContent).toEqual('A');
117 + expect(ops).toEqual(['A']);
118 +
119 + ReactDOM.flushSync(() => {
120 instance.push('D');
186 - expect(container.textContent).toEqual('ABCD');
187 - expect(ops).toEqual(['A', 'ABC', 'ABCD']);
121 });
122 + // The nested flushSync caused everything to flush.
123 + expect(container.textContent).toEqual('ABCD');
124 + expect(ops).toEqual(['A', 'ABCD']);
125 + });
126 + expect(container.textContent).toEqual('ABCD');
127 + expect(ops).toEqual(['A', 'ABCD']);
128 + });
129
190 - it('flushSync flushes updates even if nested inside another flushSync', () => {
191 - let ops = [];
192 - let instance;
193 -
194 - class Component extends React.Component {
195 - state = {text: ''};
196 - push(val) {
197 - this.setState(state => ({text: state.text + val}));
198 - }
199 - componentDidUpdate() {
200 - ops.push(this.state.text);
201 - }
202 - render() {
203 - instance = this;
204 - return <span>{this.state.text}</span>;
205 - }
206 - }
130 + it('flushSync throws if already performing work', () => {
131 + class Component extends React.Component {
132 + componentDidUpdate() {
133 + ReactDOM.flushSync(() => {});
134 + }
135 + render() {
136 + return null;
137 + }
138 + }
139 +
140 + // Initial mount
141 + ReactDOM.render(<Component />, container);
142 + // Update
143 + expect(() => ReactDOM.render(<Component />, container)).toThrow(
144 + 'flushSync was called from inside a lifecycle method',
145 + );
146 + });
147
208 - ReactDOM.render(<Component />, container);
148 + describe('concurrent mode', () => {
149 + beforeEach(() => {
150 + jest.resetModules();
151 + ReactFeatureFlags = require('shared/ReactFeatureFlags');
152 + ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
153 + ReactDOM = require('react-dom');
154 + Scheduler = require('scheduler');
155 + });
156
210 - instance.push('A');
211 - expect(ops).toEqual(['A']);
212 - expect(container.textContent).toEqual('A');
157 + it.experimental('does not perform deferred updates synchronously', () => {
158 + let inputRef = React.createRef();
159 + let asyncValueRef = React.createRef();
160 + let syncValueRef = React.createRef();
161
214 - ReactDOM.flushSync(() => {
215 - instance.push('B');
216 - instance.push('C');
217 - // Not flushed yet
218 - expect(container.textContent).toEqual('A');
219 - expect(ops).toEqual(['A']);
162 + class Counter extends React.Component {
163 + state = {asyncValue: '', syncValue: ''};
164
221 - ReactDOM.flushSync(() => {
222 - instance.push('D');
165 + handleChange = e => {
166 + const nextValue = e.target.value;
167 + requestIdleCallback(() => {
168 + this.setState({
169 + asyncValue: nextValue,
170 + });
171 + // It should not be flushed yet.
172 + expect(asyncValueRef.current.textContent).toBe('');
173 });
224 - // The nested flushSync caused everything to flush.
225 - expect(container.textContent).toEqual('ABCD');
226 - expect(ops).toEqual(['A', 'ABCD']);
227 - });
228 - expect(container.textContent).toEqual('ABCD');
229 - expect(ops).toEqual(['A', 'ABCD']);
230 - });
231 -
232 - it('flushSync throws if already performing work', () => {
233 - class Component extends React.Component {
234 - componentDidUpdate() {
235 - ReactDOM.flushSync(() => {});
236 - }
237 - render() {
238 - return null;
239 - }
174 + this.setState({
175 + syncValue: nextValue,
176 + });
177 + };
178 +
179 + render() {
180 + return (
181 + <div>
182 + <input
183 + ref={inputRef}
184 + onChange={this.handleChange}
185 + defaultValue=""
186 + />
187 + <p ref={asyncValueRef}>{this.state.asyncValue}</p>
188 + <p ref={syncValueRef}>{this.state.syncValue}</p>
189 + </div>
190 + );
191 }
192 + }
193 + const root = ReactDOM.createRoot(container);
194 + root.render(<Counter />);
195 + Scheduler.unstable_flushAll();
196 + expect(asyncValueRef.current.textContent).toBe('');
197 + expect(syncValueRef.current.textContent).toBe('');
198 +
199 + setUntrackedInputValue.call(inputRef.current, 'hello');
200 + inputRef.current.dispatchEvent(new MouseEvent('input', {bubbles: true}));
201 + // Should only flush non-deferred update.
202 + expect(asyncValueRef.current.textContent).toBe('');
203 + expect(syncValueRef.current.textContent).toBe('hello');
204 +
205 + // Should flush both updates now.
206 + jest.runAllTimers();
207 + Scheduler.unstable_flushAll();
208 + expect(asyncValueRef.current.textContent).toBe('hello');
209 + expect(syncValueRef.current.textContent).toBe('hello');
210 + });
211
242 - // Initial mount
243 - ReactDOM.render(<Component />, container);
244 - // Update
245 - expect(() => ReactDOM.render(<Component />, container)).toThrow(
246 - 'flushSync was called from inside a lifecycle method',
247 - );
248 - });
249 -
250 - it('flushSync flushes updates before end of the tick', () => {
251 - let ops = [];
252 - let instance;
212 + it.experimental('top-level updates are concurrent', () => {
213 + const root = ReactDOM.createRoot(container);
214 + root.render(<div>Hi</div>);
215 + expect(container.textContent).toEqual('');
216 + Scheduler.unstable_flushAll();
217 + expect(container.textContent).toEqual('Hi');
218 +
219 + root.render(<div>Bye</div>);
220 + expect(container.textContent).toEqual('Hi');
221 + Scheduler.unstable_flushAll();
222 + expect(container.textContent).toEqual('Bye');
223 + });
224
254 - class Component extends React.Component {
255 - state = {text: ''};
256 - push(val) {
257 - this.setState(state => ({text: state.text + val}));
258 - }
259 - componentDidUpdate() {
260 - ops.push(this.state.text);
261 - }
262 - render() {
263 - instance = this;
264 - return <span>{this.state.text}</span>;
265 - }
225 + it.experimental('deep updates (setState) are concurrent', () => {
226 + let instance;
227 + class Component extends React.Component {
228 + state = {step: 0};
229 + render() {
230 + instance = this;
231 + return <div>{this.state.step}</div>;
232 }
233 + }
234 +
235 + const root = ReactDOM.createRoot(container);
236 + root.render(<Component />);
237 + expect(container.textContent).toEqual('');
238 + Scheduler.unstable_flushAll();
239 + expect(container.textContent).toEqual('0');
240 +
241 + instance.setState({step: 1});
242 + expect(container.textContent).toEqual('0');
243 + Scheduler.unstable_flushAll();
244 + expect(container.textContent).toEqual('1');
245 + });
246
268 - const root = ReactDOM.createRoot(container);
269 - root.render(<Component />);
270 - Scheduler.unstable_flushAll();
247 + it.experimental('flushSync flushes updates before end of the tick', () => {
248 + let ops = [];
249 + let instance;
250
272 - // Updates are async by default
273 - instance.push('A');
274 - expect(ops).toEqual([]);
275 - expect(container.textContent).toEqual('');
251 + class Component extends React.Component {
252 + state = {text: ''};
253 + push(val) {
254 + this.setState(state => ({text: state.text + val}));
255 + }
256 + componentDidUpdate() {
257 + ops.push(this.state.text);
258 + }
259 + render() {
260 + instance = this;
261 + return <span>{this.state.text}</span>;
262 + }
263 + }
264
277 - ReactDOM.flushSync(() => {
278 - instance.push('B');
279 - instance.push('C');
280 - // Not flushed yet
281 - expect(container.textContent).toEqual('');
282 - expect(ops).toEqual([]);
283 - });
284 - // Only the active updates have flushed
285 - expect(container.textContent).toEqual('BC');
286 - expect(ops).toEqual(['BC']);
265 + const root = ReactDOM.createRoot(container);
266 + root.render(<Component />);
267 + Scheduler.unstable_flushAll();
268
288 - instance.push('D');
289 - expect(container.textContent).toEqual('BC');
290 - expect(ops).toEqual(['BC']);
269 + // Updates are async by default
270 + instance.push('A');
271 + expect(ops).toEqual([]);
272 + expect(container.textContent).toEqual('');
273
292 - // Flush the async updates
293 - Scheduler.unstable_flushAll();
294 - expect(container.textContent).toEqual('ABCD');
295 - expect(ops).toEqual(['BC', 'ABCD']);
274 + ReactDOM.flushSync(() => {
275 + instance.push('B');
276 + instance.push('C');
277 + // Not flushed yet
278 + expect(container.textContent).toEqual('');
279 + expect(ops).toEqual([]);
280 });
281 + // Only the active updates have flushed
282 + expect(container.textContent).toEqual('BC');
283 + expect(ops).toEqual(['BC']);
284 +
285 + instance.push('D');
286 + expect(container.textContent).toEqual('BC');
287 + expect(ops).toEqual(['BC']);
288 +
289 + // Flush the async updates
290 + Scheduler.unstable_flushAll();
291 + expect(container.textContent).toEqual('ABCD');
292 + expect(ops).toEqual(['BC', 'ABCD']);
293 + });
294
298 - it('flushControlled flushes updates before yielding to browser', () => {
295 + it.experimental(
296 + 'flushControlled flushes updates before yielding to browser',
297 + () => {
298 let inst;
299 class Counter extends React.Component {
300 state = {counter: 0};
@@ -332,9 +331,12 @@ describe('ReactDOMFiberAsync', () => {
331 'end of outer flush: 1',
332 'after outer flush: 3',
333 ]);
335 - });
334 + },
335 + );
336
337 - it('flushControlled does not flush until end of outermost batchedUpdates', () => {
337 + it.experimental(
338 + 'flushControlled does not flush until end of outermost batchedUpdates',
339 + () => {
340 let inst;
341 class Counter extends React.Component {
342 state = {counter: 0};
@@ -362,32 +364,35 @@ describe('ReactDOMFiberAsync', () => {
364 'end of batchedUpdates fn: 0',
365 'after batchedUpdates: 2',
366 ]);
365 - });
366 -
367 - it('flushControlled returns nothing', () => {
368 - // In the future, we may want to return a thenable "work" object.
369 - let inst;
370 - class Counter extends React.Component {
371 - state = {counter: 0};
372 - increment = () =>
373 - this.setState(state => ({counter: state.counter + 1}));
374 - render() {
375 - inst = this;
376 - return this.state.counter;
377 - }
367 + },
368 + );
369 +
370 + it.experimental('flushControlled returns nothing', () => {
371 + // In the future, we may want to return a thenable "work" object.
372 + let inst;
373 + class Counter extends React.Component {
374 + state = {counter: 0};
375 + increment = () =>
376 + this.setState(state => ({counter: state.counter + 1}));
377 + render() {
378 + inst = this;
379 + return this.state.counter;
380 }
379 - ReactDOM.render(<Counter />, container);
380 - expect(container.textContent).toEqual('0');
381 + }
382 + ReactDOM.render(<Counter />, container);
383 + expect(container.textContent).toEqual('0');
384
382 - const returnValue = ReactDOM.unstable_flushControlled(() => {
383 - inst.increment();
384 - return 'something';
385 - });
386 - expect(container.textContent).toEqual('1');
387 - expect(returnValue).toBe(undefined);
385 + const returnValue = ReactDOM.unstable_flushControlled(() => {
386 + inst.increment();
387 + return 'something';
388 });
389 + expect(container.textContent).toEqual('1');
390 + expect(returnValue).toBe(undefined);
391 + });
392
390 - it('ignores discrete events on a pending removed element', () => {
393 + it.experimental(
394 + 'ignores discrete events on a pending removed element',
395 + () => {
396 const disableButtonRef = React.createRef();
397 const submitButtonRef = React.createRef();
398
@@ -446,9 +451,12 @@ describe('ReactDOMFiberAsync', () => {
451 expect(formSubmitted).toBe(false);
452
453 expect(submitButtonRef.current).toBe(null);
449 - });
454 + },
455 + );
456
451 - it('ignores discrete events on a pending removed event listener', () => {
457 + it.experimental(
458 + 'ignores discrete events on a pending removed event listener',
459 + () => {
460 const disableButtonRef = React.createRef();
461 const submitButtonRef = React.createRef();
462
@@ -512,9 +520,12 @@ describe('ReactDOMFiberAsync', () => {
520
521 // Therefore the form should never have been submitted.
522 expect(formSubmitted).toBe(false);
515 - });
523 + },
524 + );
525
517 - it('uses the newest discrete events on a pending changed event listener', () => {
526 + it.experimental(
527 + 'uses the newest discrete events on a pending changed event listener',
528 + () => {
529 const enableButtonRef = React.createRef();
530 const submitButtonRef = React.createRef();
531
@@ -572,33 +583,33 @@ describe('ReactDOMFiberAsync', () => {
583
584 // Therefore the form should have been submitted.
585 expect(formSubmitted).toBe(true);
575 - });
576 - });
577 -
578 - describe('createBlockingRoot', () => {
579 - it('updates flush without yielding in the next event', () => {
580 - const root = ReactDOM.createBlockingRoot(container);
581 -
582 - function Text(props) {
583 - Scheduler.unstable_yieldValue(props.text);
584 - return props.text;
585 - }
586 -
587 - root.render(
588 - <>
589 - <Text text="A" />
590 - <Text text="B" />
591 - <Text text="C" />
592 - </>,
593 - );
594 -
595 - // Nothing should have rendered yet
596 - expect(container.textContent).toEqual('');
586 + },
587 + );
588 + });
589
598 - // Everything should render immediately in the next event
599 - expect(Scheduler).toFlushExpired(['A', 'B', 'C']);
600 - expect(container.textContent).toEqual('ABC');
601 - });
590 + describe('createBlockingRoot', () => {
591 + it.experimental('updates flush without yielding in the next event', () => {
592 + const root = ReactDOM.createBlockingRoot(container);
593 +
594 + function Text(props) {
595 + Scheduler.unstable_yieldValue(props.text);
596 + return props.text;
597 + }
598 +
599 + root.render(
600 + <>
601 + <Text text="A" />
602 + <Text text="B" />
603 + <Text text="C" />
604 + </>,
605 + );
606 +
607 + // Nothing should have rendered yet
608 + expect(container.textContent).toEqual('');
609 +
610 + // Everything should render immediately in the next event
611 + expect(Scheduler).toFlushExpired(['A', 'B', 'C']);
612 + expect(container.textContent).toEqual('ABC');
613 });
603 - }
614 + });
615 });
packages/react-dom/src/__tests__/ReactDOMHooks-test.js
+5 -4
@@ -105,8 +105,9 @@ describe('ReactDOMHooks', () => {
105 expect(labelRef.current.innerHTML).toBe('abc');
106 });
107
108 - if (__EXPERIMENTAL__) {
109 - it('should not bail out when an update is scheduled from within an event handler in Concurrent Mode', () => {
108 + it.experimental(
109 + 'should not bail out when an update is scheduled from within an event handler in Concurrent Mode',
110 + () => {
111 const {createRef, useCallback, useState} = React;
112
113 const Example = ({inputRef, labelRef}) => {
@@ -139,6 +140,6 @@ describe('ReactDOMHooks', () => {
140 Scheduler.unstable_flushAll();
141
142 expect(labelRef.current.innerHTML).toBe('abc');
142 - });
143 - }
143 + },
144 + );
145 });
packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js
+5 -4
@@ -500,8 +500,9 @@ describe('ReactDOMServerHydration', () => {
500 expect(element.textContent).toBe('Hello world');
501 });
502
503 - if (__EXPERIMENTAL__) {
504 - it('does not re-enter hydration after committing the first one', () => {
503 + it.experimental(
504 + 'does not re-enter hydration after committing the first one',
505 + () => {
506 let finalHTML = ReactDOMServer.renderToString(<div />);
507 let container = document.createElement('div');
508 container.innerHTML = finalHTML;
@@ -514,8 +515,8 @@ describe('ReactDOMServerHydration', () => {
515 // warnings.
516 root.render(<div />);
517 Scheduler.unstable_flushAll();
517 - });
518 - }
518 + },
519 + );
520
521 it('Suspense + hydration in legacy mode', () => {
522 const element = document.createElement('div');
packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js
+20 -24
@@ -125,31 +125,27 @@ describe('ReactTestUtils.act()', () => {
125 ]);
126 });
127
128 - if (__EXPERIMENTAL__) {
129 - it('warns in blocking mode', () => {
130 - expect(() => {
131 - const root = ReactDOM.createBlockingRoot(
132 - document.createElement('div'),
133 - );
134 - root.render(<App />);
135 - Scheduler.unstable_flushAll();
136 - }).toWarnDev([
137 - 'An update to App ran an effect, but was not wrapped in act(...)',
138 - 'An update to App ran an effect, but was not wrapped in act(...)',
139 - ]);
140 - });
128 + it.experimental('warns in blocking mode', () => {
129 + expect(() => {
130 + const root = ReactDOM.createBlockingRoot(document.createElement('div'));
131 + root.render(<App />);
132 + Scheduler.unstable_flushAll();
133 + }).toWarnDev([
134 + 'An update to App ran an effect, but was not wrapped in act(...)',
135 + 'An update to App ran an effect, but was not wrapped in act(...)',
136 + ]);
137 + });
138
142 - it('warns in concurrent mode', () => {
143 - expect(() => {
144 - const root = ReactDOM.createRoot(document.createElement('div'));
145 - root.render(<App />);
146 - Scheduler.unstable_flushAll();
147 - }).toWarnDev([
148 - 'An update to App ran an effect, but was not wrapped in act(...)',
149 - 'An update to App ran an effect, but was not wrapped in act(...)',
150 - ]);
151 - });
152 - }
139 + it.experimental('warns in concurrent mode', () => {
140 + expect(() => {
141 + const root = ReactDOM.createRoot(document.createElement('div'));
142 + root.render(<App />);
143 + Scheduler.unstable_flushAll();
144 + }).toWarnDev([
145 + 'An update to App ran an effect, but was not wrapped in act(...)',
146 + 'An update to App ran an effect, but was not wrapped in act(...)',
147 + ]);
148 + });
149 });
150 });
151
packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.js
+26 -32
@@ -27,36 +27,30 @@ it('does not warn when rendering in legacy mode', () => {
27 }).toWarnDev([]);
28 });
29
30 -if (__EXPERIMENTAL__) {
31 - it('should warn when rendering in concurrent mode', () => {
32 - expect(() => {
33 - ReactDOM.createRoot(document.createElement('div')).render(<App />);
34 - }).toWarnDev(
35 - 'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
36 - 'to guarantee consistent behaviour across tests and browsers.',
37 - {withoutStack: true},
38 - );
39 - // does not warn twice
40 - expect(() => {
41 - ReactDOM.createRoot(document.createElement('div')).render(<App />);
42 - }).toWarnDev([]);
43 - });
30 +it.experimental('should warn when rendering in concurrent mode', () => {
31 + expect(() => {
32 + ReactDOM.createRoot(document.createElement('div')).render(<App />);
33 + }).toWarnDev(
34 + 'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
35 + 'to guarantee consistent behaviour across tests and browsers.',
36 + {withoutStack: true},
37 + );
38 + // does not warn twice
39 + expect(() => {
40 + ReactDOM.createRoot(document.createElement('div')).render(<App />);
41 + }).toWarnDev([]);
42 +});
43
45 - it('should warn when rendering in blocking mode', () => {
46 - expect(() => {
47 - ReactDOM.createBlockingRoot(document.createElement('div')).render(
48 - <App />,
49 - );
50 - }).toWarnDev(
51 - 'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
52 - 'to guarantee consistent behaviour across tests and browsers.',
53 - {withoutStack: true},
54 - );
55 - // does not warn twice
56 - expect(() => {
57 - ReactDOM.createBlockingRoot(document.createElement('div')).render(
58 - <App />,
59 - );
60 - }).toWarnDev([]);
61 - });
62 -}
44 +it.experimental('should warn when rendering in blocking mode', () => {
45 + expect(() => {
46 + ReactDOM.createBlockingRoot(document.createElement('div')).render(<App />);
47 + }).toWarnDev(
48 + 'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
49 + 'to guarantee consistent behaviour across tests and browsers.',
50 + {withoutStack: true},
51 + );
52 + // does not warn twice
53 + expect(() => {
54 + ReactDOM.createBlockingRoot(document.createElement('div')).render(<App />);
55 + }).toWarnDev([]);
56 +});
packages/react-dom/src/events/__tests__/ChangeEventPlugin-test.internal.js
+264 -263
@@ -474,281 +474,282 @@ describe('ChangeEventPlugin', () => {
474 }
475 });
476
477 - if (__EXPERIMENTAL__) {
478 - describe('concurrent mode', () => {
479 - beforeEach(() => {
480 - jest.resetModules();
481 - ReactFeatureFlags = require('shared/ReactFeatureFlags');
482 - ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
483 - React = require('react');
484 - ReactDOM = require('react-dom');
485 - TestUtils = require('react-dom/test-utils');
486 - Scheduler = require('scheduler');
487 - });
477 + describe('concurrent mode', () => {
478 + beforeEach(() => {
479 + jest.resetModules();
480 + ReactFeatureFlags = require('shared/ReactFeatureFlags');
481 + ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
482 + React = require('react');
483 + ReactDOM = require('react-dom');
484 + TestUtils = require('react-dom/test-utils');
485 + Scheduler = require('scheduler');
486 + });
487
489 - it('text input', () => {
490 - const root = ReactDOM.createRoot(container);
491 - let input;
492 -
493 - let ops = [];
494 -
495 - class ControlledInput extends React.Component {
496 - state = {value: 'initial'};
497 - onChange = event => this.setState({value: event.target.value});
498 - render() {
499 - ops.push(`render: ${this.state.value}`);
500 - const controlledValue =
501 - this.state.value === 'changed' ? 'changed [!]' : this.state.value;
502 - return (
503 - <input
504 - ref={el => (input = el)}
505 - type="text"
506 - value={controlledValue}
507 - onChange={this.onChange}
508 - />
509 - );
510 - }
511 - }
488 + it.experimental('text input', () => {
489 + const root = ReactDOM.createRoot(container);
490 + let input;
491
513 - // Initial mount. Test that this is async.
514 - root.render(<ControlledInput />);
515 - // Should not have flushed yet.
516 - expect(ops).toEqual([]);
517 - expect(input).toBe(undefined);
518 - // Flush callbacks.
519 - Scheduler.unstable_flushAll();
520 - expect(ops).toEqual(['render: initial']);
521 - expect(input.value).toBe('initial');
522 -
523 - ops = [];
524 -
525 - // Trigger a change event.
526 - setUntrackedValue.call(input, 'changed');
527 - input.dispatchEvent(
528 - new Event('input', {bubbles: true, cancelable: true}),
529 - );
530 - // Change should synchronously flush
531 - expect(ops).toEqual(['render: changed']);
532 - // Value should be the controlled value, not the original one
533 - expect(input.value).toBe('changed [!]');
534 - });
535 -
536 - it('checkbox input', () => {
537 - const root = ReactDOM.createRoot(container);
538 - let input;
539 -
540 - let ops = [];
541 -
542 - class ControlledInput extends React.Component {
543 - state = {checked: false};
544 - onChange = event => {
545 - this.setState({checked: event.target.checked});
546 - };
547 - render() {
548 - ops.push(`render: ${this.state.checked}`);
549 - const controlledValue = this.props.reverse
550 - ? !this.state.checked
551 - : this.state.checked;
552 - return (
553 - <input
554 - ref={el => (input = el)}
555 - type="checkbox"
556 - checked={controlledValue}
557 - onChange={this.onChange}
558 - />
559 - );
560 - }
492 + let ops = [];
493 +
494 + class ControlledInput extends React.Component {
495 + state = {value: 'initial'};
496 + onChange = event => this.setState({value: event.target.value});
497 + render() {
498 + ops.push(`render: ${this.state.value}`);
499 + const controlledValue =
500 + this.state.value === 'changed' ? 'changed [!]' : this.state.value;
501 + return (
502 + <input
503 + ref={el => (input = el)}
504 + type="text"
505 + value={controlledValue}
506 + onChange={this.onChange}
507 + />
508 + );
509 }
510 + }
511
563 - // Initial mount. Test that this is async.
564 - root.render(<ControlledInput reverse={false} />);
565 - // Should not have flushed yet.
566 - expect(ops).toEqual([]);
567 - expect(input).toBe(undefined);
568 - // Flush callbacks.
569 - Scheduler.unstable_flushAll();
570 - expect(ops).toEqual(['render: false']);
571 - expect(input.checked).toBe(false);
572 -
573 - ops = [];
574 -
575 - // Trigger a change event.
576 - input.dispatchEvent(
577 - new MouseEvent('click', {bubbles: true, cancelable: true}),
578 - );
579 - // Change should synchronously flush
580 - expect(ops).toEqual(['render: true']);
581 - expect(input.checked).toBe(true);
582 -
583 - // Now let's make sure we're using the controlled value.
584 - root.render(<ControlledInput reverse={true} />);
585 - Scheduler.unstable_flushAll();
586 -
587 - ops = [];
588 -
589 - // Trigger another change event.
590 - input.dispatchEvent(
591 - new MouseEvent('click', {bubbles: true, cancelable: true}),
592 - );
593 - // Change should synchronously flush
594 - expect(ops).toEqual(['render: true']);
595 - expect(input.checked).toBe(false);
596 - });
597 -
598 - it('textarea', () => {
599 - const root = ReactDOM.createRoot(container);
600 - let textarea;
601 -
602 - let ops = [];
603 -
604 - class ControlledTextarea extends React.Component {
605 - state = {value: 'initial'};
606 - onChange = event => this.setState({value: event.target.value});
607 - render() {
608 - ops.push(`render: ${this.state.value}`);
609 - const controlledValue =
610 - this.state.value === 'changed' ? 'changed [!]' : this.state.value;
611 - return (
612 - <textarea
613 - ref={el => (textarea = el)}
614 - type="text"
615 - value={controlledValue}
616 - onChange={this.onChange}
617 - />
618 - );
619 - }
512 + // Initial mount. Test that this is async.
513 + root.render(<ControlledInput />);
514 + // Should not have flushed yet.
515 + expect(ops).toEqual([]);
516 + expect(input).toBe(undefined);
517 + // Flush callbacks.
518 + Scheduler.unstable_flushAll();
519 + expect(ops).toEqual(['render: initial']);
520 + expect(input.value).toBe('initial');
521 +
522 + ops = [];
523 +
524 + // Trigger a change event.
525 + setUntrackedValue.call(input, 'changed');
526 + input.dispatchEvent(
527 + new Event('input', {bubbles: true, cancelable: true}),
528 + );
529 + // Change should synchronously flush
530 + expect(ops).toEqual(['render: changed']);
531 + // Value should be the controlled value, not the original one
532 + expect(input.value).toBe('changed [!]');
533 + });
534 +
535 + it.experimental('checkbox input', () => {
536 + const root = ReactDOM.createRoot(container);
537 + let input;
538 +
539 + let ops = [];
540 +
541 + class ControlledInput extends React.Component {
542 + state = {checked: false};
543 + onChange = event => {
544 + this.setState({checked: event.target.checked});
545 + };
546 + render() {
547 + ops.push(`render: ${this.state.checked}`);
548 + const controlledValue = this.props.reverse
549 + ? !this.state.checked
550 + : this.state.checked;
551 + return (
552 + <input
553 + ref={el => (input = el)}
554 + type="checkbox"
555 + checked={controlledValue}
556 + onChange={this.onChange}
557 + />
558 + );
559 }
560 + }
561
622 - // Initial mount. Test that this is async.
623 - root.render(<ControlledTextarea />);
624 - // Should not have flushed yet.
625 - expect(ops).toEqual([]);
626 - expect(textarea).toBe(undefined);
627 - // Flush callbacks.
628 - Scheduler.unstable_flushAll();
629 - expect(ops).toEqual(['render: initial']);
630 - expect(textarea.value).toBe('initial');
631 -
632 - ops = [];
633 -
634 - // Trigger a change event.
635 - setUntrackedTextareaValue.call(textarea, 'changed');
636 - textarea.dispatchEvent(
637 - new Event('input', {bubbles: true, cancelable: true}),
638 - );
639 - // Change should synchronously flush
640 - expect(ops).toEqual(['render: changed']);
641 - // Value should be the controlled value, not the original one
642 - expect(textarea.value).toBe('changed [!]');
643 - });
644 -
645 - it('parent of input', () => {
646 - const root = ReactDOM.createRoot(container);
647 - let input;
648 -
649 - let ops = [];
650 -
651 - class ControlledInput extends React.Component {
652 - state = {value: 'initial'};
653 - onChange = event => this.setState({value: event.target.value});
654 - render() {
655 - ops.push(`render: ${this.state.value}`);
656 - const controlledValue =
657 - this.state.value === 'changed' ? 'changed [!]' : this.state.value;
658 - return (
659 - <div onChange={this.onChange}>
660 - <input
661 - ref={el => (input = el)}
662 - type="text"
663 - value={controlledValue}
664 - onChange={() => {
665 - // Does nothing. Parent handler is responsible for updating.
666 - }}
667 - />
668 - </div>
669 - );
670 - }
562 + // Initial mount. Test that this is async.
563 + root.render(<ControlledInput reverse={false} />);
564 + // Should not have flushed yet.
565 + expect(ops).toEqual([]);
566 + expect(input).toBe(undefined);
567 + // Flush callbacks.
568 + Scheduler.unstable_flushAll();
569 + expect(ops).toEqual(['render: false']);
570 + expect(input.checked).toBe(false);
571 +
572 + ops = [];
573 +
574 + // Trigger a change event.
575 + input.dispatchEvent(
576 + new MouseEvent('click', {bubbles: true, cancelable: true}),
577 + );
578 + // Change should synchronously flush
579 + expect(ops).toEqual(['render: true']);
580 + expect(input.checked).toBe(true);
581 +
582 + // Now let's make sure we're using the controlled value.
583 + root.render(<ControlledInput reverse={true} />);
584 + Scheduler.unstable_flushAll();
585 +
586 + ops = [];
587 +
588 + // Trigger another change event.
589 + input.dispatchEvent(
590 + new MouseEvent('click', {bubbles: true, cancelable: true}),
591 + );
592 + // Change should synchronously flush
593 + expect(ops).toEqual(['render: true']);
594 + expect(input.checked).toBe(false);
595 + });
596 +
597 + it.experimental('textarea', () => {
598 + const root = ReactDOM.createRoot(container);
599 + let textarea;
600 +
601 + let ops = [];
602 +
603 + class ControlledTextarea extends React.Component {
604 + state = {value: 'initial'};
605 + onChange = event => this.setState({value: event.target.value});
606 + render() {
607 + ops.push(`render: ${this.state.value}`);
608 + const controlledValue =
609 + this.state.value === 'changed' ? 'changed [!]' : this.state.value;
610 + return (
611 + <textarea
612 + ref={el => (textarea = el)}
613 + type="text"
614 + value={controlledValue}
615 + onChange={this.onChange}
616 + />
617 + );
618 }
619 + }
620
673 - // Initial mount. Test that this is async.
674 - root.render(<ControlledInput />);
675 - // Should not have flushed yet.
676 - expect(ops).toEqual([]);
677 - expect(input).toBe(undefined);
678 - // Flush callbacks.
679 - Scheduler.unstable_flushAll();
680 - expect(ops).toEqual(['render: initial']);
681 - expect(input.value).toBe('initial');
682 -
683 - ops = [];
684 -
685 - // Trigger a change event.
686 - setUntrackedValue.call(input, 'changed');
687 - input.dispatchEvent(
688 - new Event('input', {bubbles: true, cancelable: true}),
689 - );
690 - // Change should synchronously flush
691 - expect(ops).toEqual(['render: changed']);
692 - // Value should be the controlled value, not the original one
693 - expect(input.value).toBe('changed [!]');
694 - });
695 -
696 - it('is async for non-input events', () => {
697 - const root = ReactDOM.createRoot(container);
698 - let input;
699 -
700 - let ops = [];
701 -
702 - class ControlledInput extends React.Component {
703 - state = {value: 'initial'};
704 - onChange = event => this.setState({value: event.target.value});
705 - reset = () => {
706 - this.setState({value: ''});
707 - };
708 - render() {
709 - ops.push(`render: ${this.state.value}`);
710 - const controlledValue =
711 - this.state.value === 'changed' ? 'changed [!]' : this.state.value;
712 - return (
621 + // Initial mount. Test that this is async.
622 + root.render(<ControlledTextarea />);
623 + // Should not have flushed yet.
624 + expect(ops).toEqual([]);
625 + expect(textarea).toBe(undefined);
626 + // Flush callbacks.
627 + Scheduler.unstable_flushAll();
628 + expect(ops).toEqual(['render: initial']);
629 + expect(textarea.value).toBe('initial');
630 +
631 + ops = [];
632 +
633 + // Trigger a change event.
634 + setUntrackedTextareaValue.call(textarea, 'changed');
635 + textarea.dispatchEvent(
636 + new Event('input', {bubbles: true, cancelable: true}),
637 + );
638 + // Change should synchronously flush
639 + expect(ops).toEqual(['render: changed']);
640 + // Value should be the controlled value, not the original one
641 + expect(textarea.value).toBe('changed [!]');
642 + });
643 +
644 + it.experimental('parent of input', () => {
645 + const root = ReactDOM.createRoot(container);
646 + let input;
647 +
648 + let ops = [];
649 +
650 + class ControlledInput extends React.Component {
651 + state = {value: 'initial'};
652 + onChange = event => this.setState({value: event.target.value});
653 + render() {
654 + ops.push(`render: ${this.state.value}`);
655 + const controlledValue =
656 + this.state.value === 'changed' ? 'changed [!]' : this.state.value;
657 + return (
658 + <div onChange={this.onChange}>
659 <input
660 ref={el => (input = el)}
661 type="text"
662 value={controlledValue}
717 - onChange={this.onChange}
718 - onClick={this.reset}
663 + onChange={() => {
664 + // Does nothing. Parent handler is responsible for updating.
665 + }}
666 />
720 - );
721 - }
667 + </div>
668 + );
669 + }
670 + }
671 +
672 + // Initial mount. Test that this is async.
673 + root.render(<ControlledInput />);
674 + // Should not have flushed yet.
675 + expect(ops).toEqual([]);
676 + expect(input).toBe(undefined);
677 + // Flush callbacks.
678 + Scheduler.unstable_flushAll();
679 + expect(ops).toEqual(['render: initial']);
680 + expect(input.value).toBe('initial');
681 +
682 + ops = [];
683 +
684 + // Trigger a change event.
685 + setUntrackedValue.call(input, 'changed');
686 + input.dispatchEvent(
687 + new Event('input', {bubbles: true, cancelable: true}),
688 + );
689 + // Change should synchronously flush
690 + expect(ops).toEqual(['render: changed']);
691 + // Value should be the controlled value, not the original one
692 + expect(input.value).toBe('changed [!]');
693 + });
694 +
695 + it.experimental('is async for non-input events', () => {
696 + const root = ReactDOM.createRoot(container);
697 + let input;
698 +
699 + let ops = [];
700 +
701 + class ControlledInput extends React.Component {
702 + state = {value: 'initial'};
703 + onChange = event => this.setState({value: event.target.value});
704 + reset = () => {
705 + this.setState({value: ''});
706 + };
707 + render() {
708 + ops.push(`render: ${this.state.value}`);
709 + const controlledValue =
710 + this.state.value === 'changed' ? 'changed [!]' : this.state.value;
711 + return (
712 + <input
713 + ref={el => (input = el)}
714 + type="text"
715 + value={controlledValue}
716 + onChange={this.onChange}
717 + onClick={this.reset}
718 + />
719 + );
720 }
721 + }
722 +
723 + // Initial mount. Test that this is async.
724 + root.render(<ControlledInput />);
725 + // Should not have flushed yet.
726 + expect(ops).toEqual([]);
727 + expect(input).toBe(undefined);
728 + // Flush callbacks.
729 + Scheduler.unstable_flushAll();
730 + expect(ops).toEqual(['render: initial']);
731 + expect(input.value).toBe('initial');
732 +
733 + ops = [];
734 +
735 + // Trigger a click event
736 + input.dispatchEvent(
737 + new Event('click', {bubbles: true, cancelable: true}),
738 + );
739 + // Nothing should have changed
740 + expect(ops).toEqual([]);
741 + expect(input.value).toBe('initial');
742 +
743 + // Flush callbacks.
744 + Scheduler.unstable_flushAll();
745 + // Now the click update has flushed.
746 + expect(ops).toEqual(['render: ']);
747 + expect(input.value).toBe('');
748 + });
749
724 - // Initial mount. Test that this is async.
725 - root.render(<ControlledInput />);
726 - // Should not have flushed yet.
727 - expect(ops).toEqual([]);
728 - expect(input).toBe(undefined);
729 - // Flush callbacks.
730 - Scheduler.unstable_flushAll();
731 - expect(ops).toEqual(['render: initial']);
732 - expect(input.value).toBe('initial');
733 -
734 - ops = [];
735 -
736 - // Trigger a click event
737 - input.dispatchEvent(
738 - new Event('click', {bubbles: true, cancelable: true}),
739 - );
740 - // Nothing should have changed
741 - expect(ops).toEqual([]);
742 - expect(input.value).toBe('initial');
743 -
744 - // Flush callbacks.
745 - Scheduler.unstable_flushAll();
746 - // Now the click update has flushed.
747 - expect(ops).toEqual(['render: ']);
748 - expect(input.value).toBe('');
749 - });
750 -
751 - it('mouse enter/leave should be user-blocking but not discrete', async () => {
750 + it.experimental(
751 + 'mouse enter/leave should be user-blocking but not discrete',
752 + async () => {
753 // This is currently behind a feature flag
754 jest.resetModules();
755 React = require('react');
@@ -788,7 +789,7 @@ describe('ChangeEventPlugin', () => {
789 Scheduler.unstable_advanceTime(3000);
790 expect(container.textContent).toEqual('hovered');
791 });
791 - });
792 - });
793 - }
792 + },
793 + );
794 + });
795 });
packages/react-dom/src/events/__tests__/DOMEventResponderSystem-test.internal.js
+41 -43
@@ -845,56 +845,54 @@ describe('DOMEventResponderSystem', () => {
845 buttonRef.current.dispatchEvent(createEvent('foobar'));
846 });
847
848 - if (__EXPERIMENTAL__) {
849 - it('should work with concurrent mode updates', async () => {
850 - const log = [];
851 - const TestResponder = createEventResponder({
852 - targetEventTypes: ['click'],
853 - onEvent(event, context, props) {
854 - log.push(props);
855 - },
856 - });
857 - const ref = React.createRef();
858 -
859 - function Test({counter}) {
860 - const listener = React.unstable_useResponder(TestResponder, {counter});
861 - Scheduler.unstable_yieldValue('Test');
862 - return (
863 - <button listeners={listener} ref={ref}>
864 - Press me
865 - </button>
866 - );
867 - }
848 + it.experimental('should work with concurrent mode updates', async () => {
849 + const log = [];
850 + const TestResponder = createEventResponder({
851 + targetEventTypes: ['click'],
852 + onEvent(event, context, props) {
853 + log.push(props);
854 + },
855 + });
856 + const ref = React.createRef();
857
869 - let root = ReactDOM.createRoot(container);
870 - root.render(<Test counter={0} />);
871 - expect(Scheduler).toFlushAndYield(['Test']);
858 + function Test({counter}) {
859 + const listener = React.unstable_useResponder(TestResponder, {counter});
860 + Scheduler.unstable_yieldValue('Test');
861 + return (
862 + <button listeners={listener} ref={ref}>
863 + Press me
864 + </button>
865 + );
866 + }
867
873 - // Click the button
874 - dispatchClickEvent(ref.current);
875 - expect(log).toEqual([{counter: 0}]);
868 + let root = ReactDOM.createRoot(container);
869 + root.render(<Test counter={0} />);
870 + expect(Scheduler).toFlushAndYield(['Test']);
871 +
872 + // Click the button
873 + dispatchClickEvent(ref.current);
874 + expect(log).toEqual([{counter: 0}]);
875
877 - // Clear log
878 - log.length = 0;
876 + // Clear log
877 + log.length = 0;
878
880 - // Increase counter
881 - root.render(<Test counter={1} />);
882 - // Yield before committing
883 - expect(Scheduler).toFlushAndYieldThrough(['Test']);
879 + // Increase counter
880 + root.render(<Test counter={1} />);
881 + // Yield before committing
882 + expect(Scheduler).toFlushAndYieldThrough(['Test']);
883
885 - // Click the button again
886 - dispatchClickEvent(ref.current);
887 - expect(log).toEqual([{counter: 0}]);
884 + // Click the button again
885 + dispatchClickEvent(ref.current);
886 + expect(log).toEqual([{counter: 0}]);
887
889 - // Clear log
890 - log.length = 0;
888 + // Clear log
889 + log.length = 0;
890
892 - // Commit
893 - expect(Scheduler).toFlushAndYield([]);
894 - dispatchClickEvent(ref.current);
895 - expect(log).toEqual([{counter: 1}]);
896 - });
897 - }
891 + // Commit
892 + expect(Scheduler).toFlushAndYield([]);
893 + dispatchClickEvent(ref.current);
894 + expect(log).toEqual([{counter: 1}]);
895 + });
896
897 it('should correctly pass through event properties', () => {
898 const timeStamps = [];
packages/react-dom/src/events/__tests__/SimpleEventPlugin-test.internal.js
+106 -110
@@ -230,17 +230,18 @@ describe('SimpleEventPlugin', function() {
230 expect(button.textContent).toEqual('Count: 3');
231 });
232
233 - if (__EXPERIMENTAL__) {
234 - describe('interactive events, in concurrent mode', () => {
235 - beforeEach(() => {
236 - jest.resetModules();
237 - ReactFeatureFlags = require('shared/ReactFeatureFlags');
238 - ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
239 - ReactDOM = require('react-dom');
240 - Scheduler = require('scheduler');
241 - });
233 + describe('interactive events, in concurrent mode', () => {
234 + beforeEach(() => {
235 + jest.resetModules();
236 + ReactFeatureFlags = require('shared/ReactFeatureFlags');
237 + ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
238 + ReactDOM = require('react-dom');
239 + Scheduler = require('scheduler');
240 + });
241
243 - it('flushes pending interactive work before extracting event handler', () => {
242 + it.experimental(
243 + 'flushes pending interactive work before extracting event handler',
244 + () => {
245 container = document.createElement('div');
246 const root = ReactDOM.createRoot(container);
247 document.body.appendChild(container);
@@ -318,9 +319,12 @@ describe('SimpleEventPlugin', function() {
319 click();
320 Scheduler.unstable_flushAll();
321 expect(ops).toEqual([]);
321 - });
322 + },
323 + );
324
323 - it('end result of many interactive updates is deterministic', () => {
325 + it.experimental(
326 + 'end result of many interactive updates is deterministic',
327 + () => {
328 container = document.createElement('div');
329 const root = ReactDOM.createRoot(container);
330 document.body.appendChild(container);
@@ -373,113 +377,105 @@ describe('SimpleEventPlugin', function() {
377 Scheduler.unstable_flushAll();
378 // The counter should equal the total number of clicks
379 expect(button.textContent).toEqual('Count: 7');
376 - });
377 -
378 - it('flushes discrete updates in order', () => {
379 - container = document.createElement('div');
380 - document.body.appendChild(container);
381 -
382 - let button;
383 - class Button extends React.Component {
384 - state = {lowPriCount: 0};
385 - render() {
386 - const text = `High-pri count: ${
387 - this.props.highPriCount
388 - }, Low-pri count: ${this.state.lowPriCount}`;
389 - Scheduler.unstable_yieldValue(text);
390 - return (
391 - <button
392 - ref={el => (button = el)}
393 - onClick={() => {
394 - Scheduler.unstable_next(() => {
395 - this.setState(state => ({
396 - lowPriCount: state.lowPriCount + 1,
397 - }));
398 - });
399 - }}>
400 - {text}
401 - </button>
402 - );
403 - }
404 - }
380 + },
381 + );
382
406 - class Wrapper extends React.Component {
407 - state = {highPriCount: 0};
408 - render() {
409 - return (
410 - <div
411 - onClick={
412 - // Intentionally not using the updater form here, to test
413 - // that updates are serially processed.
414 - () => {
415 - this.setState({highPriCount: this.state.highPriCount + 1});
416 - }
417 - }>
418 - <Button highPriCount={this.state.highPriCount} />
419 - </div>
420 - );
421 - }
383 + it.experimental('flushes discrete updates in order', () => {
384 + container = document.createElement('div');
385 + document.body.appendChild(container);
386 +
387 + let button;
388 + class Button extends React.Component {
389 + state = {lowPriCount: 0};
390 + render() {
391 + const text = `High-pri count: ${
392 + this.props.highPriCount
393 + }, Low-pri count: ${this.state.lowPriCount}`;
394 + Scheduler.unstable_yieldValue(text);
395 + return (
396 + <button
397 + ref={el => (button = el)}
398 + onClick={() => {
399 + Scheduler.unstable_next(() => {
400 + this.setState(state => ({
401 + lowPriCount: state.lowPriCount + 1,
402 + }));
403 + });
404 + }}>
405 + {text}
406 + </button>
407 + );
408 }
409 + }
410
424 - // Initial mount
425 - const root = ReactDOM.createRoot(container);
426 - root.render(<Wrapper />);
427 - expect(Scheduler).toFlushAndYield([
428 - 'High-pri count: 0, Low-pri count: 0',
429 - ]);
430 - expect(button.textContent).toEqual(
431 - 'High-pri count: 0, Low-pri count: 0',
432 - );
433 -
434 - function click() {
435 - button.dispatchEvent(
436 - new MouseEvent('click', {bubbles: true, cancelable: true}),
411 + class Wrapper extends React.Component {
412 + state = {highPriCount: 0};
413 + render() {
414 + return (
415 + <div
416 + onClick={
417 + // Intentionally not using the updater form here, to test
418 + // that updates are serially processed.
419 + () => {
420 + this.setState({highPriCount: this.state.highPriCount + 1});
421 + }
422 + }>
423 + <Button highPriCount={this.state.highPriCount} />
424 + </div>
425 );
426 }
427 + }
428
440 - // Click the button a single time
441 - click();
442 - // Nothing should flush on the first click.
443 - expect(Scheduler).toHaveYielded([]);
444 - // Click again. This will force the previous discrete update to flush. But
445 - // only the high-pri count will increase.
446 - click();
447 - expect(Scheduler).toHaveYielded([
448 - 'High-pri count: 1, Low-pri count: 0',
449 - ]);
450 - expect(button.textContent).toEqual(
451 - 'High-pri count: 1, Low-pri count: 0',
429 + // Initial mount
430 + const root = ReactDOM.createRoot(container);
431 + root.render(<Wrapper />);
432 + expect(Scheduler).toFlushAndYield([
433 + 'High-pri count: 0, Low-pri count: 0',
434 + ]);
435 + expect(button.textContent).toEqual('High-pri count: 0, Low-pri count: 0');
436 +
437 + function click() {
438 + button.dispatchEvent(
439 + new MouseEvent('click', {bubbles: true, cancelable: true}),
440 );
441 + }
442
454 - // Click the button many more times
455 - click();
456 - click();
457 - click();
458 - click();
459 - click();
460 - click();
461 -
462 - // Flush the remaining work.
463 - expect(Scheduler).toHaveYielded([
464 - 'High-pri count: 2, Low-pri count: 0',
465 - 'High-pri count: 3, Low-pri count: 0',
466 - 'High-pri count: 4, Low-pri count: 0',
467 - 'High-pri count: 5, Low-pri count: 0',
468 - 'High-pri count: 6, Low-pri count: 0',
469 - 'High-pri count: 7, Low-pri count: 0',
470 - ]);
471 -
472 - // At the end, both counters should equal the total number of clicks
473 - expect(Scheduler).toFlushAndYield([
474 - 'High-pri count: 8, Low-pri count: 0',
475 - 'High-pri count: 8, Low-pri count: 8',
476 - ]);
477 - expect(button.textContent).toEqual(
478 - 'High-pri count: 8, Low-pri count: 8',
479 - );
480 - });
443 + // Click the button a single time
444 + click();
445 + // Nothing should flush on the first click.
446 + expect(Scheduler).toHaveYielded([]);
447 + // Click again. This will force the previous discrete update to flush. But
448 + // only the high-pri count will increase.
449 + click();
450 + expect(Scheduler).toHaveYielded(['High-pri count: 1, Low-pri count: 0']);
451 + expect(button.textContent).toEqual('High-pri count: 1, Low-pri count: 0');
452 +
453 + // Click the button many more times
454 + click();
455 + click();
456 + click();
457 + click();
458 + click();
459 + click();
460 +
461 + // Flush the remaining work.
462 + expect(Scheduler).toHaveYielded([
463 + 'High-pri count: 2, Low-pri count: 0',
464 + 'High-pri count: 3, Low-pri count: 0',
465 + 'High-pri count: 4, Low-pri count: 0',
466 + 'High-pri count: 5, Low-pri count: 0',
467 + 'High-pri count: 6, Low-pri count: 0',
468 + 'High-pri count: 7, Low-pri count: 0',
469 + ]);
470 +
471 + // At the end, both counters should equal the total number of clicks
472 + expect(Scheduler).toFlushAndYield([
473 + 'High-pri count: 8, Low-pri count: 0',
474 + 'High-pri count: 8, Low-pri count: 8',
475 + ]);
476 + expect(button.textContent).toEqual('High-pri count: 8, Low-pri count: 8');
477 });
482 - }
478 + });
479
480 describe('iOS bubbling click fix', function() {
481 // See http://www.quirksmode.org/blog/archives/2010/09/click_event_del.html
packages/react-interactions/events/src/dom/__tests__/Input-test.internal.js
+169 -175
@@ -750,208 +750,202 @@ describe('Input event responder', () => {
750 }
751 });
752
753 - if (__EXPERIMENTAL__) {
754 - describe('concurrent mode', () => {
755 - it('text input', () => {
756 - const root = ReactDOM.createRoot(container);
757 - let input;
758 -
759 - let ops = [];
760 -
761 - function Component({innerRef, onChange, controlledValue}) {
762 - const listener = useInput({
763 - onChange,
764 - });
753 + describe('concurrent mode', () => {
754 + it.experimental('text input', () => {
755 + const root = ReactDOM.createRoot(container);
756 + let input;
757 +
758 + let ops = [];
759 +
760 + function Component({innerRef, onChange, controlledValue}) {
761 + const listener = useInput({
762 + onChange,
763 + });
764 + return (
765 + <input
766 + type="text"
767 + ref={innerRef}
768 + value={controlledValue}
769 + listeners={listener}
770 + />
771 + );
772 + }
773 +
774 + class ControlledInput extends React.Component {
775 + state = {value: 'initial'};
776 + onChange = event => this.setState({value: event.target.value});
777 + render() {
778 + ops.push(`render: ${this.state.value}`);
779 + const controlledValue =
780 + this.state.value === 'changed' ? 'changed [!]' : this.state.value;
781 return (
766 - <input
767 - type="text"
768 - ref={innerRef}
769 - value={controlledValue}
770 - listeners={listener}
782 + <Component
783 + onChange={this.onChange}
784 + innerRef={el => (input = el)}
785 + controlledValue={controlledValue}
786 />
787 );
788 }
789 + }
790
775 - class ControlledInput extends React.Component {
776 - state = {value: 'initial'};
777 - onChange = event => this.setState({value: event.target.value});
778 - render() {
779 - ops.push(`render: ${this.state.value}`);
780 - const controlledValue =
781 - this.state.value === 'changed'
782 - ? 'changed [!]'
783 - : this.state.value;
784 - return (
785 - <Component
786 - onChange={this.onChange}
787 - innerRef={el => (input = el)}
788 - controlledValue={controlledValue}
789 - />
790 - );
791 - }
792 - }
791 + // Initial mount. Test that this is async.
792 + root.render(<ControlledInput />);
793 + // Should not have flushed yet.
794 + expect(ops).toEqual([]);
795 + expect(input).toBe(undefined);
796 + // Flush callbacks.
797 + Scheduler.unstable_flushAll();
798 + expect(ops).toEqual(['render: initial']);
799 + expect(input.value).toBe('initial');
800 +
801 + ops = [];
802 +
803 + // Trigger a change event.
804 + setUntrackedValue.call(input, 'changed');
805 + input.dispatchEvent(
806 + new Event('input', {bubbles: true, cancelable: true}),
807 + );
808 + // Change should synchronously flush
809 + expect(ops).toEqual(['render: changed']);
810 + // Value should be the controlled value, not the original one
811 + expect(input.value).toBe('changed [!]');
812 + });
813
794 - // Initial mount. Test that this is async.
795 - root.render(<ControlledInput />);
796 - // Should not have flushed yet.
797 - expect(ops).toEqual([]);
798 - expect(input).toBe(undefined);
799 - // Flush callbacks.
800 - Scheduler.unstable_flushAll();
801 - expect(ops).toEqual(['render: initial']);
802 - expect(input.value).toBe('initial');
803 -
804 - ops = [];
805 -
806 - // Trigger a change event.
807 - setUntrackedValue.call(input, 'changed');
808 - input.dispatchEvent(
809 - new Event('input', {bubbles: true, cancelable: true}),
810 - );
811 - // Change should synchronously flush
812 - expect(ops).toEqual(['render: changed']);
813 - // Value should be the controlled value, not the original one
814 - expect(input.value).toBe('changed [!]');
815 - });
814 + it.experimental('checkbox input', () => {
815 + const root = ReactDOM.createRoot(container);
816 + let input;
817
817 - it('checkbox input', () => {
818 - const root = ReactDOM.createRoot(container);
819 - let input;
818 + let ops = [];
819
821 - let ops = [];
820 + function Component({innerRef, onChange, controlledValue}) {
821 + const listener = useInput({
822 + onChange,
823 + });
824 + return (
825 + <input
826 + type="checkbox"
827 + ref={innerRef}
828 + checked={controlledValue}
829 + listeners={listener}
830 + />
831 + );
832 + }
833
823 - function Component({innerRef, onChange, controlledValue}) {
824 - const listener = useInput({
825 - onChange,
826 - });
834 + class ControlledInput extends React.Component {
835 + state = {checked: false};
836 + onChange = event => {
837 + this.setState({checked: event.target.checked});
838 + };
839 + render() {
840 + ops.push(`render: ${this.state.checked}`);
841 + const controlledValue = this.props.reverse
842 + ? !this.state.checked
843 + : this.state.checked;
844 return (
828 - <input
829 - type="checkbox"
830 - ref={innerRef}
831 - checked={controlledValue}
832 - listeners={listener}
845 + <Component
846 + controlledValue={controlledValue}
847 + onChange={this.onChange}
848 + innerRef={el => (input = el)}
849 />
850 );
851 }
852 + }
853
837 - class ControlledInput extends React.Component {
838 - state = {checked: false};
839 - onChange = event => {
840 - this.setState({checked: event.target.checked});
841 - };
842 - render() {
843 - ops.push(`render: ${this.state.checked}`);
844 - const controlledValue = this.props.reverse
845 - ? !this.state.checked
846 - : this.state.checked;
847 - return (
848 - <Component
849 - controlledValue={controlledValue}
850 - onChange={this.onChange}
851 - innerRef={el => (input = el)}
852 - />
853 - );
854 - }
855 - }
854 + // Initial mount. Test that this is async.
855 + root.render(<ControlledInput reverse={false} />);
856 + // Should not have flushed yet.
857 + expect(ops).toEqual([]);
858 + expect(input).toBe(undefined);
859 + // Flush callbacks.
860 + Scheduler.unstable_flushAll();
861 + expect(ops).toEqual(['render: false']);
862 + expect(input.checked).toBe(false);
863 +
864 + ops = [];
865 +
866 + // Trigger a change event.
867 + input.dispatchEvent(
868 + new MouseEvent('click', {bubbles: true, cancelable: true}),
869 + );
870 + // Change should synchronously flush
871 + expect(ops).toEqual(['render: true']);
872 + expect(input.checked).toBe(true);
873
857 - // Initial mount. Test that this is async.
858 - root.render(<ControlledInput reverse={false} />);
859 - // Should not have flushed yet.
860 - expect(ops).toEqual([]);
861 - expect(input).toBe(undefined);
862 - // Flush callbacks.
863 - Scheduler.unstable_flushAll();
864 - expect(ops).toEqual(['render: false']);
865 - expect(input.checked).toBe(false);
866 -
867 - ops = [];
868 -
869 - // Trigger a change event.
870 - input.dispatchEvent(
871 - new MouseEvent('click', {bubbles: true, cancelable: true}),
872 - );
873 - // Change should synchronously flush
874 - expect(ops).toEqual(['render: true']);
875 - expect(input.checked).toBe(true);
874 + // Now let's make sure we're using the controlled value.
875 + root.render(<ControlledInput reverse={true} />);
876 + Scheduler.unstable_flushAll();
877
877 - // Now let's make sure we're using the controlled value.
878 - root.render(<ControlledInput reverse={true} />);
879 - Scheduler.unstable_flushAll();
878 + ops = [];
879
881 - ops = [];
880 + // Trigger another change event.
881 + input.dispatchEvent(
882 + new MouseEvent('click', {bubbles: true, cancelable: true}),
883 + );
884 + // Change should synchronously flush
885 + expect(ops).toEqual(['render: true']);
886 + expect(input.checked).toBe(false);
887 + });
888
883 - // Trigger another change event.
884 - input.dispatchEvent(
885 - new MouseEvent('click', {bubbles: true, cancelable: true}),
886 - );
887 - // Change should synchronously flush
888 - expect(ops).toEqual(['render: true']);
889 - expect(input.checked).toBe(false);
890 - });
889 + it.experimental('textarea', () => {
890 + const root = ReactDOM.createRoot(container);
891 + let textarea;
892
892 - it('textarea', () => {
893 - const root = ReactDOM.createRoot(container);
894 - let textarea;
893 + let ops = [];
894
896 - let ops = [];
895 + function Component({innerRef, onChange, controlledValue}) {
896 + const listener = useInput({
897 + onChange,
898 + });
899 + return (
900 + <textarea
901 + type="text"
902 + ref={innerRef}
903 + value={controlledValue}
904 + listeners={listener}
905 + />
906 + );
907 + }
908
898 - function Component({innerRef, onChange, controlledValue}) {
899 - const listener = useInput({
900 - onChange,
901 - });
909 + class ControlledTextarea extends React.Component {
910 + state = {value: 'initial'};
911 + onChange = event => this.setState({value: event.target.value});
912 + render() {
913 + ops.push(`render: ${this.state.value}`);
914 + const controlledValue =
915 + this.state.value === 'changed' ? 'changed [!]' : this.state.value;
916 return (
903 - <textarea
904 - type="text"
905 - ref={innerRef}
906 - value={controlledValue}
907 - listeners={listener}
917 + <Component
918 + onChange={this.onChange}
919 + innerRef={el => (textarea = el)}
920 + controlledValue={controlledValue}
921 />
922 );
923 }
924 + }
925
912 - class ControlledTextarea extends React.Component {
913 - state = {value: 'initial'};
914 - onChange = event => this.setState({value: event.target.value});
915 - render() {
916 - ops.push(`render: ${this.state.value}`);
917 - const controlledValue =
918 - this.state.value === 'changed'
919 - ? 'changed [!]'
920 - : this.state.value;
921 - return (
922 - <Component
923 - onChange={this.onChange}
924 - innerRef={el => (textarea = el)}
925 - controlledValue={controlledValue}
926 - />
927 - );
928 - }
929 - }
930 -
931 - // Initial mount. Test that this is async.
932 - root.render(<ControlledTextarea />);
933 - // Should not have flushed yet.
934 - expect(ops).toEqual([]);
935 - expect(textarea).toBe(undefined);
936 - // Flush callbacks.
937 - Scheduler.unstable_flushAll();
938 - expect(ops).toEqual(['render: initial']);
939 - expect(textarea.value).toBe('initial');
940 -
941 - ops = [];
942 -
943 - // Trigger a change event.
944 - setUntrackedTextareaValue.call(textarea, 'changed');
945 - textarea.dispatchEvent(
946 - new Event('input', {bubbles: true, cancelable: true}),
947 - );
948 - // Change should synchronously flush
949 - expect(ops).toEqual(['render: changed']);
950 - // Value should be the controlled value, not the original one
951 - expect(textarea.value).toBe('changed [!]');
952 - });
926 + // Initial mount. Test that this is async.
927 + root.render(<ControlledTextarea />);
928 + // Should not have flushed yet.
929 + expect(ops).toEqual([]);
930 + expect(textarea).toBe(undefined);
931 + // Flush callbacks.
932 + Scheduler.unstable_flushAll();
933 + expect(ops).toEqual(['render: initial']);
934 + expect(textarea.value).toBe('initial');
935 +
936 + ops = [];
937 +
938 + // Trigger a change event.
939 + setUntrackedTextareaValue.call(textarea, 'changed');
940 + textarea.dispatchEvent(
941 + new Event('input', {bubbles: true, cancelable: true}),
942 + );
943 + // Change should synchronously flush
944 + expect(ops).toEqual(['render: changed']);
945 + // Value should be the controlled value, not the original one
946 + expect(textarea.value).toBe('changed [!]');
947 });
954 - }
948 + });
949 });
950
951 it('expect displayName to show up for event component', () => {
packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.internal.js
+74 -69
@@ -1973,9 +1973,10 @@ describe('ReactHooksWithNoopRenderer', () => {
1973 expect(totalRefUpdates).toBe(2); // Should not increase since last time
1974 });
1975 });
1976 - if (__EXPERIMENTAL__) {
1977 - describe('useTransition', () => {
1978 - it('delays showing loading state until after timeout', async () => {
1976 + describe('useTransition', () => {
1977 + it.experimental(
1978 + 'delays showing loading state until after timeout',
1979 + async () => {
1980 let transition;
1981 function App() {
1982 const [show, setShow] = useState(false);
@@ -2037,8 +2038,11 @@ describe('ReactHooksWithNoopRenderer', () => {
2038 expect(ReactNoop.getChildren()).toEqual([
2039 span('After... Pending: false'),
2040 ]);
2040 - });
2041 - it('delays showing loading state until after busyDelayMs + busyMinDurationMs', async () => {
2041 + },
2042 + );
2043 + it.experimental(
2044 + 'delays showing loading state until after busyDelayMs + busyMinDurationMs',
2045 + async () => {
2046 let transition;
2047 function App() {
2048 const [show, setShow] = useState(false);
@@ -2105,81 +2109,82 @@ describe('ReactHooksWithNoopRenderer', () => {
2109 expect(ReactNoop.getChildren()).toEqual([
2110 span('After... Pending: false'),
2111 ]);
2108 - });
2109 - });
2110 - describe('useDeferredValue', () => {
2111 - it('defers text value until specified timeout', async () => {
2112 - function TextBox({text}) {
2113 - return <AsyncText ms={1000} text={text} />;
2114 - }
2115 -
2116 - let _setText;
2117 - function App() {
2118 - const [text, setText] = useState('A');
2119 - const deferredText = useDeferredValue(text, {
2120 - timeoutMs: 500,
2121 - });
2122 - _setText = setText;
2123 - return (
2124 - <>
2125 - <Text text={text} />
2126 - <Suspense fallback={<Text text={'Loading'} />}>
2127 - <TextBox text={deferredText} />
2128 - </Suspense>
2129 - </>
2130 - );
2131 - }
2112 + },
2113 + );
2114 + });
2115 + describe('useDeferredValue', () => {
2116 + it.experimental('defers text value until specified timeout', async () => {
2117 + function TextBox({text}) {
2118 + return <AsyncText ms={1000} text={text} />;
2119 + }
2120
2133 - act(() => {
2134 - ReactNoop.render(<App />);
2121 + let _setText;
2122 + function App() {
2123 + const [text, setText] = useState('A');
2124 + const deferredText = useDeferredValue(text, {
2125 + timeoutMs: 500,
2126 });
2127 + _setText = setText;
2128 + return (
2129 + <>
2130 + <Text text={text} />
2131 + <Suspense fallback={<Text text={'Loading'} />}>
2132 + <TextBox text={deferredText} />
2133 + </Suspense>
2134 + </>
2135 + );
2136 + }
2137
2137 - expect(Scheduler).toHaveYielded(['A', 'Suspend! [A]', 'Loading']);
2138 - expect(ReactNoop.getChildren()).toEqual([span('A'), span('Loading')]);
2138 + act(() => {
2139 + ReactNoop.render(<App />);
2140 + });
2141
2140 - Scheduler.unstable_advanceTime(1000);
2141 - await advanceTimers(1000);
2142 - expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2143 - expect(Scheduler).toFlushAndYield(['A']);
2144 - expect(ReactNoop.getChildren()).toEqual([span('A'), span('A')]);
2142 + expect(Scheduler).toHaveYielded(['A', 'Suspend! [A]', 'Loading']);
2143 + expect(ReactNoop.getChildren()).toEqual([span('A'), span('Loading')]);
2144
2146 - act(() => {
2147 - _setText('B');
2148 - });
2149 - expect(Scheduler).toHaveYielded([
2150 - 'B',
2151 - 'A',
2152 - 'B',
2153 - 'Suspend! [B]',
2154 - 'Loading',
2155 - ]);
2156 - expect(Scheduler).toFlushAndYield([]);
2157 - expect(ReactNoop.getChildren()).toEqual([span('B'), span('A')]);
2145 + Scheduler.unstable_advanceTime(1000);
2146 + await advanceTimers(1000);
2147 + expect(Scheduler).toHaveYielded(['Promise resolved [A]']);
2148 + expect(Scheduler).toFlushAndYield(['A']);
2149 + expect(ReactNoop.getChildren()).toEqual([span('A'), span('A')]);
2150
2159 - Scheduler.unstable_advanceTime(250);
2160 - await advanceTimers(250);
2161 - expect(Scheduler).toFlushAndYield([]);
2162 - expect(ReactNoop.getChildren()).toEqual([span('B'), span('A')]);
2151 + act(() => {
2152 + _setText('B');
2153 + });
2154 + expect(Scheduler).toHaveYielded([
2155 + 'B',
2156 + 'A',
2157 + 'B',
2158 + 'Suspend! [B]',
2159 + 'Loading',
2160 + ]);
2161 + expect(Scheduler).toFlushAndYield([]);
2162 + expect(ReactNoop.getChildren()).toEqual([span('B'), span('A')]);
2163
2164 - Scheduler.unstable_advanceTime(500);
2165 - await advanceTimers(500);
2166 - expect(ReactNoop.getChildren()).toEqual([
2167 - span('B'),
2168 - hiddenSpan('A'),
2169 - span('Loading'),
2170 - ]);
2164 + Scheduler.unstable_advanceTime(250);
2165 + await advanceTimers(250);
2166 + expect(Scheduler).toFlushAndYield([]);
2167 + expect(ReactNoop.getChildren()).toEqual([span('B'), span('A')]);
2168
2172 - Scheduler.unstable_advanceTime(250);
2173 - await advanceTimers(250);
2174 - expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
2169 + Scheduler.unstable_advanceTime(500);
2170 + await advanceTimers(500);
2171 + expect(ReactNoop.getChildren()).toEqual([
2172 + span('B'),
2173 + hiddenSpan('A'),
2174 + span('Loading'),
2175 + ]);
2176
2176 - act(() => {
2177 - expect(Scheduler).toFlushAndYield(['B']);
2178 - });
2179 - expect(ReactNoop.getChildren()).toEqual([span('B'), span('B')]);
2177 + Scheduler.unstable_advanceTime(250);
2178 + await advanceTimers(250);
2179 + expect(Scheduler).toHaveYielded(['Promise resolved [B]']);
2180 +
2181 + act(() => {
2182 + expect(Scheduler).toFlushAndYield(['B']);
2183 });
2184 + expect(ReactNoop.getChildren()).toEqual([span('B'), span('B')]);
2185 });
2182 - }
2186 + });
2187 +
2188 describe('progressive enhancement (not supported)', () => {
2189 it('mount additional state', () => {
2190 let updateA;