@samitouri / QOS-React-2 / commits / 65237a237e

Codemod it.experimental to gate pragma (#18582)

* Codemod it.experimental to gate pragma Find-and-replace followed by Prettier * Delete it.experimental Removes the API from our test setup script

Andrew Clark committed Apr 13, 2020 at 10:28 UTC 65237a237e15af3b3c983d46b401c6af988c5f74
20 files changed +1194 -1246
packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js
+230 -236
@@ -153,7 +153,8 @@ describe('ReactDOMFiberAsync', () => {
153 Scheduler = require('scheduler');
154 });
155
156 - it.experimental('does not perform deferred updates synchronously', () => {
156 + // @gate experimental
157 + it('does not perform deferred updates synchronously', () => {
158 const inputRef = React.createRef();
159 const asyncValueRef = React.createRef();
160 const syncValueRef = React.createRef();
@@ -208,7 +209,8 @@ describe('ReactDOMFiberAsync', () => {
209 expect(syncValueRef.current.textContent).toBe('hello');
210 });
211
211 - it.experimental('top-level updates are concurrent', () => {
212 + // @gate experimental
213 + it('top-level updates are concurrent', () => {
214 const root = ReactDOM.createRoot(container);
215 root.render(<div>Hi</div>);
216 expect(container.textContent).toEqual('');
@@ -221,7 +223,8 @@ describe('ReactDOMFiberAsync', () => {
223 expect(container.textContent).toEqual('Bye');
224 });
225
224 - it.experimental('deep updates (setState) are concurrent', () => {
226 + // @gate experimental
227 + it('deep updates (setState) are concurrent', () => {
228 let instance;
229 class Component extends React.Component {
230 state = {step: 0};
@@ -243,7 +246,8 @@ describe('ReactDOMFiberAsync', () => {
246 expect(container.textContent).toEqual('1');
247 });
248
246 - it.experimental('flushSync flushes updates before end of the tick', () => {
249 + // @gate experimental
250 + it('flushSync flushes updates before end of the tick', () => {
251 const ops = [];
252 let instance;
253
@@ -291,82 +295,79 @@ describe('ReactDOMFiberAsync', () => {
295 expect(ops).toEqual(['BC', 'ABCD']);
296 });
297
294 - it.experimental(
295 - 'flushControlled flushes updates before yielding to browser',
296 - () => {
297 - let inst;
298 - class Counter extends React.Component {
299 - state = {counter: 0};
300 - increment = () =>
301 - this.setState(state => ({counter: state.counter + 1}));
302 - render() {
303 - inst = this;
304 - return this.state.counter;
305 - }
298 + // @gate experimental
299 + it('flushControlled flushes updates before yielding to browser', () => {
300 + let inst;
301 + class Counter extends React.Component {
302 + state = {counter: 0};
303 + increment = () =>
304 + this.setState(state => ({counter: state.counter + 1}));
305 + render() {
306 + inst = this;
307 + return this.state.counter;
308 }
307 - const root = ReactDOM.createRoot(container);
308 - root.render(<Counter />);
309 - Scheduler.unstable_flushAll();
310 - expect(container.textContent).toEqual('0');
309 + }
310 + const root = ReactDOM.createRoot(container);
311 + root.render(<Counter />);
312 + Scheduler.unstable_flushAll();
313 + expect(container.textContent).toEqual('0');
314
312 - // Test that a normal update is async
313 - inst.increment();
314 - expect(container.textContent).toEqual('0');
315 - Scheduler.unstable_flushAll();
316 - expect(container.textContent).toEqual('1');
315 + // Test that a normal update is async
316 + inst.increment();
317 + expect(container.textContent).toEqual('0');
318 + Scheduler.unstable_flushAll();
319 + expect(container.textContent).toEqual('1');
320
318 - const ops = [];
321 + const ops = [];
322 + ReactDOM.unstable_flushControlled(() => {
323 + inst.increment();
324 ReactDOM.unstable_flushControlled(() => {
325 inst.increment();
321 - ReactDOM.unstable_flushControlled(() => {
322 - inst.increment();
323 - ops.push('end of inner flush: ' + container.textContent);
324 - });
325 - ops.push('end of outer flush: ' + container.textContent);
326 + ops.push('end of inner flush: ' + container.textContent);
327 });
327 - ops.push('after outer flush: ' + container.textContent);
328 - expect(ops).toEqual([
329 - 'end of inner flush: 1',
330 - 'end of outer flush: 1',
331 - 'after outer flush: 3',
332 - ]);
333 - },
334 - );
328 + ops.push('end of outer flush: ' + container.textContent);
329 + });
330 + ops.push('after outer flush: ' + container.textContent);
331 + expect(ops).toEqual([
332 + 'end of inner flush: 1',
333 + 'end of outer flush: 1',
334 + 'after outer flush: 3',
335 + ]);
336 + });
337
336 - it.experimental(
337 - 'flushControlled does not flush until end of outermost batchedUpdates',
338 - () => {
339 - let inst;
340 - class Counter extends React.Component {
341 - state = {counter: 0};
342 - increment = () =>
343 - this.setState(state => ({counter: state.counter + 1}));
344 - render() {
345 - inst = this;
346 - return this.state.counter;
347 - }
338 + // @gate experimental
339 + it('flushControlled does not flush until end of outermost batchedUpdates', () => {
340 + let inst;
341 + class Counter extends React.Component {
342 + state = {counter: 0};
343 + increment = () =>
344 + this.setState(state => ({counter: state.counter + 1}));
345 + render() {
346 + inst = this;
347 + return this.state.counter;
348 }
349 - ReactDOM.render(<Counter />, container);
349 + }
350 + ReactDOM.render(<Counter />, container);
351
351 - const ops = [];
352 - ReactDOM.unstable_batchedUpdates(() => {
352 + const ops = [];
353 + ReactDOM.unstable_batchedUpdates(() => {
354 + inst.increment();
355 + ReactDOM.unstable_flushControlled(() => {
356 inst.increment();
354 - ReactDOM.unstable_flushControlled(() => {
355 - inst.increment();
356 - ops.push('end of flushControlled fn: ' + container.textContent);
357 - });
358 - ops.push('end of batchedUpdates fn: ' + container.textContent);
357 + ops.push('end of flushControlled fn: ' + container.textContent);
358 });
360 - ops.push('after batchedUpdates: ' + container.textContent);
361 - expect(ops).toEqual([
362 - 'end of flushControlled fn: 0',
363 - 'end of batchedUpdates fn: 0',
364 - 'after batchedUpdates: 2',
365 - ]);
366 - },
367 - );
359 + ops.push('end of batchedUpdates fn: ' + container.textContent);
360 + });
361 + ops.push('after batchedUpdates: ' + container.textContent);
362 + expect(ops).toEqual([
363 + 'end of flushControlled fn: 0',
364 + 'end of batchedUpdates fn: 0',
365 + 'after batchedUpdates: 2',
366 + ]);
367 + });
368
369 - it.experimental('flushControlled returns nothing', () => {
369 + // @gate experimental
370 + it('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 {
@@ -389,201 +390,193 @@ describe('ReactDOMFiberAsync', () => {
390 expect(returnValue).toBe(undefined);
391 });
392
392 - it.experimental(
393 - 'ignores discrete events on a pending removed element',
394 - () => {
395 - const disableButtonRef = React.createRef();
396 - const submitButtonRef = React.createRef();
397 -
398 - let formSubmitted = false;
399 -
400 - class Form extends React.Component {
401 - state = {active: true};
402 - disableForm = () => {
403 - this.setState({active: false});
404 - };
405 - submitForm = () => {
406 - formSubmitted = true; // This should not get invoked
407 - };
408 - render() {
409 - return (
410 - <div>
411 - <button onClick={this.disableForm} ref={disableButtonRef}>
412 - Disable
393 + // @gate experimental
394 + it('ignores discrete events on a pending removed element', () => {
395 + const disableButtonRef = React.createRef();
396 + const submitButtonRef = React.createRef();
397 +
398 + let formSubmitted = false;
399 +
400 + class Form extends React.Component {
401 + state = {active: true};
402 + disableForm = () => {
403 + this.setState({active: false});
404 + };
405 + submitForm = () => {
406 + formSubmitted = true; // This should not get invoked
407 + };
408 + render() {
409 + return (
410 + <div>
411 + <button onClick={this.disableForm} ref={disableButtonRef}>
412 + Disable
413 + </button>
414 + {this.state.active ? (
415 + <button onClick={this.submitForm} ref={submitButtonRef}>
416 + Submit
417 </button>
414 - {this.state.active ? (
415 - <button onClick={this.submitForm} ref={submitButtonRef}>
416 - Submit
417 - </button>
418 - ) : null}
419 - </div>
420 - );
421 - }
418 + ) : null}
419 + </div>
420 + );
421 }
422 + }
423
424 - const root = ReactDOM.createRoot(container);
425 - root.render(<Form />);
426 - // Flush
427 - Scheduler.unstable_flushAll();
424 + const root = ReactDOM.createRoot(container);
425 + root.render(<Form />);
426 + // Flush
427 + Scheduler.unstable_flushAll();
428
429 - const disableButton = disableButtonRef.current;
430 - expect(disableButton.tagName).toBe('BUTTON');
429 + const disableButton = disableButtonRef.current;
430 + expect(disableButton.tagName).toBe('BUTTON');
431
432 - // Dispatch a click event on the Disable-button.
433 - const firstEvent = document.createEvent('Event');
434 - firstEvent.initEvent('click', true, true);
435 - disableButton.dispatchEvent(firstEvent);
432 + // Dispatch a click event on the Disable-button.
433 + const firstEvent = document.createEvent('Event');
434 + firstEvent.initEvent('click', true, true);
435 + disableButton.dispatchEvent(firstEvent);
436
437 - // There should now be a pending update to disable the form.
437 + // There should now be a pending update to disable the form.
438
439 - // This should not have flushed yet since it's in concurrent mode.
440 - const submitButton = submitButtonRef.current;
441 - expect(submitButton.tagName).toBe('BUTTON');
439 + // This should not have flushed yet since it's in concurrent mode.
440 + const submitButton = submitButtonRef.current;
441 + expect(submitButton.tagName).toBe('BUTTON');
442
443 - // In the meantime, we can dispatch a new client event on the submit button.
444 - const secondEvent = document.createEvent('Event');
445 - secondEvent.initEvent('click', true, true);
446 - // This should force the pending update to flush which disables the submit button before the event is invoked.
447 - submitButton.dispatchEvent(secondEvent);
443 + // In the meantime, we can dispatch a new client event on the submit button.
444 + const secondEvent = document.createEvent('Event');
445 + secondEvent.initEvent('click', true, true);
446 + // This should force the pending update to flush which disables the submit button before the event is invoked.
447 + submitButton.dispatchEvent(secondEvent);
448
449 - // Therefore the form should never have been submitted.
450 - expect(formSubmitted).toBe(false);
449 + // Therefore the form should never have been submitted.
450 + expect(formSubmitted).toBe(false);
451
452 - expect(submitButtonRef.current).toBe(null);
453 - },
454 - );
452 + expect(submitButtonRef.current).toBe(null);
453 + });
454
456 - it.experimental(
457 - 'ignores discrete events on a pending removed event listener',
458 - () => {
459 - const disableButtonRef = React.createRef();
460 - const submitButtonRef = React.createRef();
461 -
462 - let formSubmitted = false;
463 -
464 - class Form extends React.Component {
465 - state = {active: true};
466 - disableForm = () => {
467 - this.setState({active: false});
468 - };
469 - submitForm = () => {
470 - formSubmitted = true; // This should not get invoked
471 - };
472 - disabledSubmitForm = () => {
473 - // The form is disabled.
474 - };
475 - render() {
476 - return (
477 - <div>
478 - <button onClick={this.disableForm} ref={disableButtonRef}>
479 - Disable
480 - </button>
481 - <button
482 - onClick={
483 - this.state.active
484 - ? this.submitForm
485 - : this.disabledSubmitForm
486 - }
487 - ref={submitButtonRef}>
488 - Submit
489 - </button>
490 - </div>
491 - );
492 - }
455 + // @gate experimental
456 + it('ignores discrete events on a pending removed event listener', () => {
457 + const disableButtonRef = React.createRef();
458 + const submitButtonRef = React.createRef();
459 +
460 + let formSubmitted = false;
461 +
462 + class Form extends React.Component {
463 + state = {active: true};
464 + disableForm = () => {
465 + this.setState({active: false});
466 + };
467 + submitForm = () => {
468 + formSubmitted = true; // This should not get invoked
469 + };
470 + disabledSubmitForm = () => {
471 + // The form is disabled.
472 + };
473 + render() {
474 + return (
475 + <div>
476 + <button onClick={this.disableForm} ref={disableButtonRef}>
477 + Disable
478 + </button>
479 + <button
480 + onClick={
481 + this.state.active ? this.submitForm : this.disabledSubmitForm
482 + }
483 + ref={submitButtonRef}>
484 + Submit
485 + </button>
486 + </div>
487 + );
488 }
489 + }
490
495 - const root = ReactDOM.createRoot(container);
496 - root.render(<Form />);
497 - // Flush
498 - Scheduler.unstable_flushAll();
491 + const root = ReactDOM.createRoot(container);
492 + root.render(<Form />);
493 + // Flush
494 + Scheduler.unstable_flushAll();
495
500 - const disableButton = disableButtonRef.current;
501 - expect(disableButton.tagName).toBe('BUTTON');
496 + const disableButton = disableButtonRef.current;
497 + expect(disableButton.tagName).toBe('BUTTON');
498
503 - // Dispatch a click event on the Disable-button.
504 - const firstEvent = document.createEvent('Event');
505 - firstEvent.initEvent('click', true, true);
506 - disableButton.dispatchEvent(firstEvent);
499 + // Dispatch a click event on the Disable-button.
500 + const firstEvent = document.createEvent('Event');
501 + firstEvent.initEvent('click', true, true);
502 + disableButton.dispatchEvent(firstEvent);
503
508 - // There should now be a pending update to disable the form.
504 + // There should now be a pending update to disable the form.
505
510 - // This should not have flushed yet since it's in concurrent mode.
511 - const submitButton = submitButtonRef.current;
512 - expect(submitButton.tagName).toBe('BUTTON');
506 + // This should not have flushed yet since it's in concurrent mode.
507 + const submitButton = submitButtonRef.current;
508 + expect(submitButton.tagName).toBe('BUTTON');
509
514 - // In the meantime, we can dispatch a new client event on the submit button.
515 - const secondEvent = document.createEvent('Event');
516 - secondEvent.initEvent('click', true, true);
517 - // This should force the pending update to flush which disables the submit button before the event is invoked.
518 - submitButton.dispatchEvent(secondEvent);
510 + // In the meantime, we can dispatch a new client event on the submit button.
511 + const secondEvent = document.createEvent('Event');
512 + secondEvent.initEvent('click', true, true);
513 + // This should force the pending update to flush which disables the submit button before the event is invoked.
514 + submitButton.dispatchEvent(secondEvent);
515
520 - // Therefore the form should never have been submitted.
521 - expect(formSubmitted).toBe(false);
522 - },
523 - );
516 + // Therefore the form should never have been submitted.
517 + expect(formSubmitted).toBe(false);
518 + });
519
525 - it.experimental(
526 - 'uses the newest discrete events on a pending changed event listener',
527 - () => {
528 - const enableButtonRef = React.createRef();
529 - const submitButtonRef = React.createRef();
530 -
531 - let formSubmitted = false;
532 -
533 - class Form extends React.Component {
534 - state = {active: false};
535 - enableForm = () => {
536 - this.setState({active: true});
537 - };
538 - submitForm = () => {
539 - formSubmitted = true; // This should happen
540 - };
541 - render() {
542 - return (
543 - <div>
544 - <button onClick={this.enableForm} ref={enableButtonRef}>
545 - Enable
546 - </button>
547 - <button
548 - onClick={this.state.active ? this.submitForm : null}
549 - ref={submitButtonRef}>
550 - Submit
551 - </button>{' '}
552 - : null}
553 - </div>
554 - );
555 - }
520 + // @gate experimental
521 + it('uses the newest discrete events on a pending changed event listener', () => {
522 + const enableButtonRef = React.createRef();
523 + const submitButtonRef = React.createRef();
524 +
525 + let formSubmitted = false;
526 +
527 + class Form extends React.Component {
528 + state = {active: false};
529 + enableForm = () => {
530 + this.setState({active: true});
531 + };
532 + submitForm = () => {
533 + formSubmitted = true; // This should happen
534 + };
535 + render() {
536 + return (
537 + <div>
538 + <button onClick={this.enableForm} ref={enableButtonRef}>
539 + Enable
540 + </button>
541 + <button
542 + onClick={this.state.active ? this.submitForm : null}
543 + ref={submitButtonRef}>
544 + Submit
545 + </button>{' '}
546 + : null}
547 + </div>
548 + );
549 }
550 + }
551
558 - const root = ReactDOM.createRoot(container);
559 - root.render(<Form />);
560 - // Flush
561 - Scheduler.unstable_flushAll();
552 + const root = ReactDOM.createRoot(container);
553 + root.render(<Form />);
554 + // Flush
555 + Scheduler.unstable_flushAll();
556
563 - const enableButton = enableButtonRef.current;
564 - expect(enableButton.tagName).toBe('BUTTON');
557 + const enableButton = enableButtonRef.current;
558 + expect(enableButton.tagName).toBe('BUTTON');
559
566 - // Dispatch a click event on the Enable-button.
567 - const firstEvent = document.createEvent('Event');
568 - firstEvent.initEvent('click', true, true);
569 - enableButton.dispatchEvent(firstEvent);
560 + // Dispatch a click event on the Enable-button.
561 + const firstEvent = document.createEvent('Event');
562 + firstEvent.initEvent('click', true, true);
563 + enableButton.dispatchEvent(firstEvent);
564
571 - // There should now be a pending update to enable the form.
565 + // There should now be a pending update to enable the form.
566
573 - // This should not have flushed yet since it's in concurrent mode.
574 - const submitButton = submitButtonRef.current;
575 - expect(submitButton.tagName).toBe('BUTTON');
567 + // This should not have flushed yet since it's in concurrent mode.
568 + const submitButton = submitButtonRef.current;
569 + expect(submitButton.tagName).toBe('BUTTON');
570
577 - // In the meantime, we can dispatch a new client event on the submit button.
578 - const secondEvent = document.createEvent('Event');
579 - secondEvent.initEvent('click', true, true);
580 - // This should force the pending update to flush which enables the submit button before the event is invoked.
581 - submitButton.dispatchEvent(secondEvent);
571 + // In the meantime, we can dispatch a new client event on the submit button.
572 + const secondEvent = document.createEvent('Event');
573 + secondEvent.initEvent('click', true, true);
574 + // This should force the pending update to flush which enables the submit button before the event is invoked.
575 + submitButton.dispatchEvent(secondEvent);
576
583 - // Therefore the form should have been submitted.
584 - expect(formSubmitted).toBe(true);
585 - },
586 - );
577 + // Therefore the form should have been submitted.
578 + expect(formSubmitted).toBe(true);
579 + });
580 });
581
582 it('regression test: does not drop passive effects across roots (#17066)', () => {
@@ -617,7 +610,8 @@ describe('ReactDOMFiberAsync', () => {
610 });
611
612 describe('createBlockingRoot', () => {
620 - it.experimental('updates flush without yielding in the next event', () => {
613 + // @gate experimental
614 + it('updates flush without yielding in the next event', () => {
615 const root = ReactDOM.createBlockingRoot(container);
616
617 function Text(props) {
packages/react-dom/src/__tests__/ReactDOMHooks-test.js
+32 -34
@@ -105,41 +105,39 @@ describe('ReactDOMHooks', () => {
105 expect(labelRef.current.innerHTML).toBe('abc');
106 });
107
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}) => {
114 - const [text, setText] = useState('');
115 - const handleInput = useCallback(event => {
116 - setText(event.target.value);
117 - });
118 -
119 - return (
120 - <>
121 - <input ref={inputRef} onInput={handleInput} />
122 - <label ref={labelRef}>{text}</label>
123 - </>
124 - );
125 - };
126 -
127 - const inputRef = createRef();
128 - const labelRef = createRef();
129 -
130 - const root = ReactDOM.createRoot(container);
131 - root.render(<Example inputRef={inputRef} labelRef={labelRef} />);
132 -
133 - Scheduler.unstable_flushAll();
134 -
135 - inputRef.current.value = 'abc';
136 - inputRef.current.dispatchEvent(
137 - new Event('input', {bubbles: true, cancelable: true}),
108 + // @gate experimental
109 + it('should not bail out when an update is scheduled from within an event handler in Concurrent Mode', () => {
110 + const {createRef, useCallback, useState} = React;
111 +
112 + const Example = ({inputRef, labelRef}) => {
113 + const [text, setText] = useState('');
114 + const handleInput = useCallback(event => {
115 + setText(event.target.value);
116 + });
117 +
118 + return (
119 + <>
120 + <input ref={inputRef} onInput={handleInput} />
121 + <label ref={labelRef}>{text}</label>
122 + </>
123 );
124 + };
125
140 - Scheduler.unstable_flushAll();
126 + const inputRef = createRef();
127 + const labelRef = createRef();
128 +
129 + const root = ReactDOM.createRoot(container);
130 + root.render(<Example inputRef={inputRef} labelRef={labelRef} />);
131 +
132 + Scheduler.unstable_flushAll();
133 +
134 + inputRef.current.value = 'abc';
135 + inputRef.current.dispatchEvent(
136 + new Event('input', {bubbles: true, cancelable: true}),
137 + );
138
142 - expect(labelRef.current.innerHTML).toBe('abc');
143 - },
144 - );
139 + Scheduler.unstable_flushAll();
140 +
141 + expect(labelRef.current.innerHTML).toBe('abc');
142 + });
143 });
packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js
+15 -17
@@ -491,23 +491,21 @@ describe('ReactDOMServerHydration', () => {
491 expect(element.textContent).toBe('Hello world');
492 });
493
494 - it.experimental(
495 - 'does not re-enter hydration after committing the first one',
496 - () => {
497 - const finalHTML = ReactDOMServer.renderToString(<div />);
498 - const container = document.createElement('div');
499 - container.innerHTML = finalHTML;
500 - const root = ReactDOM.createRoot(container, {hydrate: true});
501 - root.render(<div />);
502 - Scheduler.unstable_flushAll();
503 - root.render(null);
504 - Scheduler.unstable_flushAll();
505 - // This should not reenter hydration state and therefore not trigger hydration
506 - // warnings.
507 - root.render(<div />);
508 - Scheduler.unstable_flushAll();
509 - },
510 - );
494 + // @gate experimental
495 + it('does not re-enter hydration after committing the first one', () => {
496 + const finalHTML = ReactDOMServer.renderToString(<div />);
497 + const container = document.createElement('div');
498 + container.innerHTML = finalHTML;
499 + const root = ReactDOM.createRoot(container, {hydrate: true});
500 + root.render(<div />);
501 + Scheduler.unstable_flushAll();
502 + root.render(null);
503 + Scheduler.unstable_flushAll();
504 + // This should not reenter hydration state and therefore not trigger hydration
505 + // warnings.
506 + root.render(<div />);
507 + Scheduler.unstable_flushAll();
508 + });
509
510 it('Suspense + hydration in legacy mode', () => {
511 const element = document.createElement('div');
packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js
+4 -2
@@ -124,7 +124,8 @@ describe('ReactTestUtils.act()', () => {
124 ]);
125 });
126
127 - it.experimental('warns in blocking mode', () => {
127 + // @gate experimental
128 + it('warns in blocking mode', () => {
129 expect(() => {
130 const root = ReactDOM.createBlockingRoot(document.createElement('div'));
131 root.render(<App />);
@@ -134,7 +135,8 @@ describe('ReactTestUtils.act()', () => {
135 ]);
136 });
137
137 - it.experimental('warns in concurrent mode', () => {
138 + // @gate experimental
139 + it('warns in concurrent mode', () => {
140 expect(() => {
141 const root = ReactDOM.createRoot(document.createElement('div'));
142 root.render(<App />);
packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.js
+4 -2
@@ -27,7 +27,8 @@ it('does not warn when rendering in legacy mode', () => {
27 }).toErrorDev([]);
28 });
29
30 -it.experimental('should warn when rendering in concurrent mode', () => {
30 +// @gate experimental
31 +it('should warn when rendering in concurrent mode', () => {
32 expect(() => {
33 ReactDOM.createRoot(document.createElement('div')).render(<App />);
34 }).toErrorDev(
@@ -41,7 +42,8 @@ it.experimental('should warn when rendering in concurrent mode', () => {
42 }).toErrorDev([]);
43 });
44
44 -it.experimental('should warn when rendering in blocking mode', () => {
45 +// @gate experimental
46 +it('should warn when rendering in blocking mode', () => {
47 expect(() => {
48 ReactDOM.createBlockingRoot(document.createElement('div')).render(<App />);
49 }).toErrorDev(
packages/react-dom/src/__tests__/ReactUpdates-test.js
+49 -51
@@ -1287,64 +1287,62 @@ describe('ReactUpdates', () => {
1287 expect(ops).toEqual(['Foo', 'Bar', 'Baz']);
1288 });
1289
1290 - it.experimental(
1291 - 'delays sync updates inside hidden subtrees in Concurrent Mode',
1292 - () => {
1293 - const container = document.createElement('div');
1294 -
1295 - function Baz() {
1296 - Scheduler.unstable_yieldValue('Baz');
1297 - return <p>baz</p>;
1298 - }
1299 -
1300 - let setCounter;
1301 - function Bar() {
1302 - const [counter, _setCounter] = React.useState(0);
1303 - setCounter = _setCounter;
1304 - Scheduler.unstable_yieldValue('Bar');
1305 - return <p>bar {counter}</p>;
1306 - }
1290 + // @gate experimental
1291 + it('delays sync updates inside hidden subtrees in Concurrent Mode', () => {
1292 + const container = document.createElement('div');
1293
1308 - function Foo() {
1309 - Scheduler.unstable_yieldValue('Foo');
1310 - React.useEffect(() => {
1311 - Scheduler.unstable_yieldValue('Foo#effect');
1312 - });
1313 - return (
1314 - <div>
1315 - <div hidden={true}>
1316 - <Bar />
1317 - </div>
1318 - <Baz />
1319 - </div>
1320 - );
1321 - }
1294 + function Baz() {
1295 + Scheduler.unstable_yieldValue('Baz');
1296 + return <p>baz</p>;
1297 + }
1298
1323 - const root = ReactDOM.createRoot(container);
1324 - let hiddenDiv;
1325 - act(() => {
1326 - root.render(<Foo />);
1327 - expect(Scheduler).toFlushAndYieldThrough(['Foo', 'Baz', 'Foo#effect']);
1328 - hiddenDiv = container.firstChild.firstChild;
1329 - expect(hiddenDiv.hidden).toBe(true);
1330 - expect(hiddenDiv.innerHTML).toBe('');
1331 - // Run offscreen update
1332 - expect(Scheduler).toFlushAndYield(['Bar']);
1333 - expect(hiddenDiv.hidden).toBe(true);
1334 - expect(hiddenDiv.innerHTML).toBe('<p>bar 0</p>');
1335 - });
1299 + let setCounter;
1300 + function Bar() {
1301 + const [counter, _setCounter] = React.useState(0);
1302 + setCounter = _setCounter;
1303 + Scheduler.unstable_yieldValue('Bar');
1304 + return <p>bar {counter}</p>;
1305 + }
1306
1337 - ReactDOM.flushSync(() => {
1338 - setCounter(1);
1307 + function Foo() {
1308 + Scheduler.unstable_yieldValue('Foo');
1309 + React.useEffect(() => {
1310 + Scheduler.unstable_yieldValue('Foo#effect');
1311 });
1340 - // Should not flush yet
1341 - expect(hiddenDiv.innerHTML).toBe('<p>bar 0</p>');
1312 + return (
1313 + <div>
1314 + <div hidden={true}>
1315 + <Bar />
1316 + </div>
1317 + <Baz />
1318 + </div>
1319 + );
1320 + }
1321
1322 + const root = ReactDOM.createRoot(container);
1323 + let hiddenDiv;
1324 + act(() => {
1325 + root.render(<Foo />);
1326 + expect(Scheduler).toFlushAndYieldThrough(['Foo', 'Baz', 'Foo#effect']);
1327 + hiddenDiv = container.firstChild.firstChild;
1328 + expect(hiddenDiv.hidden).toBe(true);
1329 + expect(hiddenDiv.innerHTML).toBe('');
1330 // Run offscreen update
1331 expect(Scheduler).toFlushAndYield(['Bar']);
1345 - expect(hiddenDiv.innerHTML).toBe('<p>bar 1</p>');
1346 - },
1347 - );
1332 + expect(hiddenDiv.hidden).toBe(true);
1333 + expect(hiddenDiv.innerHTML).toBe('<p>bar 0</p>');
1334 + });
1335 +
1336 + ReactDOM.flushSync(() => {
1337 + setCounter(1);
1338 + });
1339 + // Should not flush yet
1340 + expect(hiddenDiv.innerHTML).toBe('<p>bar 0</p>');
1341 +
1342 + // Run offscreen update
1343 + expect(Scheduler).toFlushAndYield(['Bar']);
1344 + expect(hiddenDiv.innerHTML).toBe('<p>bar 1</p>');
1345 + });
1346
1347 it('can render ridiculously large number of roots without triggering infinite update loop error', () => {
1348 class Foo extends React.Component {
packages/react-dom/src/events/__tests__/DOMModernPluginEventSystem-test.internal.js
+158 -166
@@ -432,87 +432,85 @@ describe('DOMModernPluginEventSystem', () => {
432 expect(log).toEqual(['second', 'first']);
433 });
434
435 - it.experimental(
436 - 'does not invoke an event on a parent tree when a subtree is dehydrated',
437 - async () => {
438 - let suspend = false;
439 - let resolve;
440 - const promise = new Promise(
441 - resolvePromise => (resolve = resolvePromise),
442 - );
435 + // @gate experimental
436 + it('does not invoke an event on a parent tree when a subtree is dehydrated', async () => {
437 + let suspend = false;
438 + let resolve;
439 + const promise = new Promise(
440 + resolvePromise => (resolve = resolvePromise),
441 + );
442
444 - let clicks = 0;
445 - const childSlotRef = React.createRef();
443 + let clicks = 0;
444 + const childSlotRef = React.createRef();
445
447 - function Parent() {
448 - return <div onClick={() => clicks++} ref={childSlotRef} />;
449 - }
446 + function Parent() {
447 + return <div onClick={() => clicks++} ref={childSlotRef} />;
448 + }
449
451 - function Child({text}) {
452 - if (suspend) {
453 - throw promise;
454 - } else {
455 - return <a>Click me</a>;
456 - }
450 + function Child({text}) {
451 + if (suspend) {
452 + throw promise;
453 + } else {
454 + return <a>Click me</a>;
455 }
456 + }
457
459 - function App() {
460 - // The root is a Suspense boundary.
461 - return (
462 - <React.Suspense fallback="Loading...">
463 - <Child />
464 - </React.Suspense>
465 - );
466 - }
458 + function App() {
459 + // The root is a Suspense boundary.
460 + return (
461 + <React.Suspense fallback="Loading...">
462 + <Child />
463 + </React.Suspense>
464 + );
465 + }
466
468 - suspend = false;
469 - const finalHTML = ReactDOMServer.renderToString(<App />);
467 + suspend = false;
468 + const finalHTML = ReactDOMServer.renderToString(<App />);
469
471 - const parentContainer = document.createElement('div');
472 - const childContainer = document.createElement('div');
470 + const parentContainer = document.createElement('div');
471 + const childContainer = document.createElement('div');
472
474 - // We need this to be in the document since we'll dispatch events on it.
475 - document.body.appendChild(parentContainer);
473 + // We need this to be in the document since we'll dispatch events on it.
474 + document.body.appendChild(parentContainer);
475
477 - // We're going to use a different root as a parent.
478 - // This lets us detect whether an event goes through React's event system.
479 - const parentRoot = ReactDOM.createRoot(parentContainer);
480 - parentRoot.render(<Parent />);
481 - Scheduler.unstable_flushAll();
476 + // We're going to use a different root as a parent.
477 + // This lets us detect whether an event goes through React's event system.
478 + const parentRoot = ReactDOM.createRoot(parentContainer);
479 + parentRoot.render(<Parent />);
480 + Scheduler.unstable_flushAll();
481
483 - childSlotRef.current.appendChild(childContainer);
482 + childSlotRef.current.appendChild(childContainer);
483
485 - childContainer.innerHTML = finalHTML;
484 + childContainer.innerHTML = finalHTML;
485
487 - const a = childContainer.getElementsByTagName('a')[0];
486 + const a = childContainer.getElementsByTagName('a')[0];
487
489 - suspend = true;
488 + suspend = true;
489
491 - // Hydrate asynchronously.
492 - const root = ReactDOM.createRoot(childContainer, {hydrate: true});
493 - root.render(<App />);
494 - jest.runAllTimers();
495 - Scheduler.unstable_flushAll();
490 + // Hydrate asynchronously.
491 + const root = ReactDOM.createRoot(childContainer, {hydrate: true});
492 + root.render(<App />);
493 + jest.runAllTimers();
494 + Scheduler.unstable_flushAll();
495
497 - // The Suspense boundary is not yet hydrated.
498 - a.click();
499 - expect(clicks).toBe(0);
496 + // The Suspense boundary is not yet hydrated.
497 + a.click();
498 + expect(clicks).toBe(0);
499
501 - // Resolving the promise so that rendering can complete.
502 - suspend = false;
503 - resolve();
504 - await promise;
500 + // Resolving the promise so that rendering can complete.
501 + suspend = false;
502 + resolve();
503 + await promise;
504
506 - Scheduler.unstable_flushAll();
507 - jest.runAllTimers();
505 + Scheduler.unstable_flushAll();
506 + jest.runAllTimers();
507
509 - // We're now full hydrated.
508 + // We're now full hydrated.
509
511 - expect(clicks).toBe(1);
510 + expect(clicks).toBe(1);
511
513 - document.body.removeChild(parentContainer);
514 - },
515 - );
512 + document.body.removeChild(parentContainer);
513 + });
514
515 it('handle click events on dynamic portals', () => {
516 const log = [];
@@ -1679,55 +1677,53 @@ describe('DOMModernPluginEventSystem', () => {
1677 expect(targetListerner4).toHaveBeenCalledTimes(1);
1678 });
1679
1682 - it.experimental(
1683 - 'should work with concurrent mode updates',
1684 - async () => {
1685 - const log = [];
1686 - const ref = React.createRef();
1680 + // @gate experimental
1681 + it('should work with concurrent mode updates', async () => {
1682 + const log = [];
1683 + const ref = React.createRef();
1684
1688 - function Test({counter}) {
1689 - const click = ReactDOM.unstable_useEvent('click');
1685 + function Test({counter}) {
1686 + const click = ReactDOM.unstable_useEvent('click');
1687
1691 - React.useLayoutEffect(() => {
1692 - click.setListener(ref.current, () => {
1693 - log.push({counter});
1694 - });
1688 + React.useLayoutEffect(() => {
1689 + click.setListener(ref.current, () => {
1690 + log.push({counter});
1691 });
1692 + });
1693
1697 - Scheduler.unstable_yieldValue('Test');
1698 - return <button ref={ref}>Press me</button>;
1699 - }
1694 + Scheduler.unstable_yieldValue('Test');
1695 + return <button ref={ref}>Press me</button>;
1696 + }
1697
1701 - const root = ReactDOM.createRoot(container);
1702 - root.render(<Test counter={0} />);
1698 + const root = ReactDOM.createRoot(container);
1699 + root.render(<Test counter={0} />);
1700
1704 - expect(Scheduler).toFlushAndYield(['Test']);
1701 + expect(Scheduler).toFlushAndYield(['Test']);
1702
1706 - // Click the button
1707 - dispatchClickEvent(ref.current);
1708 - expect(log).toEqual([{counter: 0}]);
1703 + // Click the button
1704 + dispatchClickEvent(ref.current);
1705 + expect(log).toEqual([{counter: 0}]);
1706
1710 - // Clear log
1711 - log.length = 0;
1707 + // Clear log
1708 + log.length = 0;
1709
1713 - // Increase counter
1714 - root.render(<Test counter={1} />);
1715 - // Yield before committing
1716 - expect(Scheduler).toFlushAndYieldThrough(['Test']);
1710 + // Increase counter
1711 + root.render(<Test counter={1} />);
1712 + // Yield before committing
1713 + expect(Scheduler).toFlushAndYieldThrough(['Test']);
1714
1718 - // Click the button again
1719 - dispatchClickEvent(ref.current);
1720 - expect(log).toEqual([{counter: 0}]);
1715 + // Click the button again
1716 + dispatchClickEvent(ref.current);
1717 + expect(log).toEqual([{counter: 0}]);
1718
1722 - // Clear log
1723 - log.length = 0;
1719 + // Clear log
1720 + log.length = 0;
1721
1725 - // Commit
1726 - expect(Scheduler).toFlushAndYield([]);
1727 - dispatchClickEvent(ref.current);
1728 - expect(log).toEqual([{counter: 1}]);
1729 - },
1730 - );
1722 + // Commit
1723 + expect(Scheduler).toFlushAndYield([]);
1724 + dispatchClickEvent(ref.current);
1725 + expect(log).toEqual([{counter: 1}]);
1726 + });
1727
1728 it('should correctly work for a basic "click" listener that upgrades', () => {
1729 const clickEvent = jest.fn();
@@ -2259,86 +2255,82 @@ describe('DOMModernPluginEventSystem', () => {
2255 expect(log).toEqual(['beforeblur', 'afterblur']);
2256 });
2257
2262 - it.experimental(
2263 - 'beforeblur and afterblur are called after a focused element is suspended',
2264 - () => {
2265 - const log = [];
2266 - // We have to persist here because we want to read relatedTarget later.
2267 - const onAfterBlur = jest.fn(e => {
2268 - e.persist();
2269 - log.push(e.type);
2270 - });
2271 - const onBeforeBlur = jest.fn(e => log.push(e.type));
2272 - const innerRef = React.createRef();
2273 - const Suspense = React.Suspense;
2274 - let suspend = false;
2275 - let resolve;
2276 - const promise = new Promise(
2277 - resolvePromise => (resolve = resolvePromise),
2278 - );
2258 + // @gate experimental
2259 + it('beforeblur and afterblur are called after a focused element is suspended', () => {
2260 + const log = [];
2261 + // We have to persist here because we want to read relatedTarget later.
2262 + const onAfterBlur = jest.fn(e => {
2263 + e.persist();
2264 + log.push(e.type);
2265 + });
2266 + const onBeforeBlur = jest.fn(e => log.push(e.type));
2267 + const innerRef = React.createRef();
2268 + const Suspense = React.Suspense;
2269 + let suspend = false;
2270 + let resolve;
2271 + const promise = new Promise(
2272 + resolvePromise => (resolve = resolvePromise),
2273 + );
2274
2280 - function Child() {
2281 - if (suspend) {
2282 - throw promise;
2283 - } else {
2284 - return <input ref={innerRef} />;
2285 - }
2275 + function Child() {
2276 + if (suspend) {
2277 + throw promise;
2278 + } else {
2279 + return <input ref={innerRef} />;
2280 }
2281 + }
2282
2288 - const Component = () => {
2289 - const ref = React.useRef(null);
2290 - const afterBlurHandle = ReactDOM.unstable_useEvent('afterblur');
2291 - const beforeBlurHandle = ReactDOM.unstable_useEvent(
2292 - 'beforeblur',
2293 - );
2283 + const Component = () => {
2284 + const ref = React.useRef(null);
2285 + const afterBlurHandle = ReactDOM.unstable_useEvent('afterblur');
2286 + const beforeBlurHandle = ReactDOM.unstable_useEvent('beforeblur');
2287
2295 - React.useEffect(() => {
2296 - afterBlurHandle.setListener(document, onAfterBlur);
2297 - beforeBlurHandle.setListener(ref.current, onBeforeBlur);
2298 - });
2288 + React.useEffect(() => {
2289 + afterBlurHandle.setListener(document, onAfterBlur);
2290 + beforeBlurHandle.setListener(ref.current, onBeforeBlur);
2291 + });
2292
2300 - return (
2301 - <div ref={ref}>
2302 - <Suspense fallback="Loading...">
2303 - <Child />
2304 - </Suspense>
2305 - </div>
2306 - );
2307 - };
2293 + return (
2294 + <div ref={ref}>
2295 + <Suspense fallback="Loading...">
2296 + <Child />
2297 + </Suspense>
2298 + </div>
2299 + );
2300 + };
2301
2309 - const container2 = document.createElement('div');
2310 - document.body.appendChild(container2);
2302 + const container2 = document.createElement('div');
2303 + document.body.appendChild(container2);
2304
2312 - const root = ReactDOM.createRoot(container2);
2305 + const root = ReactDOM.createRoot(container2);
2306
2314 - ReactTestUtils.act(() => {
2315 - root.render(<Component />);
2316 - });
2317 - jest.runAllTimers();
2307 + ReactTestUtils.act(() => {
2308 + root.render(<Component />);
2309 + });
2310 + jest.runAllTimers();
2311
2319 - const inner = innerRef.current;
2320 - const target = createEventTarget(inner);
2321 - target.focus();
2322 - expect(onBeforeBlur).toHaveBeenCalledTimes(0);
2323 - expect(onAfterBlur).toHaveBeenCalledTimes(0);
2312 + const inner = innerRef.current;
2313 + const target = createEventTarget(inner);
2314 + target.focus();
2315 + expect(onBeforeBlur).toHaveBeenCalledTimes(0);
2316 + expect(onAfterBlur).toHaveBeenCalledTimes(0);
2317
2325 - suspend = true;
2326 - ReactTestUtils.act(() => {
2327 - root.render(<Component />);
2328 - });
2329 - jest.runAllTimers();
2318 + suspend = true;
2319 + ReactTestUtils.act(() => {
2320 + root.render(<Component />);
2321 + });
2322 + jest.runAllTimers();
2323
2331 - expect(onBeforeBlur).toHaveBeenCalledTimes(1);
2332 - expect(onAfterBlur).toHaveBeenCalledTimes(1);
2333 - expect(onAfterBlur).toHaveBeenCalledWith(
2334 - expect.objectContaining({relatedTarget: inner}),
2335 - );
2336 - resolve();
2337 - expect(log).toEqual(['beforeblur', 'afterblur']);
2324 + expect(onBeforeBlur).toHaveBeenCalledTimes(1);
2325 + expect(onAfterBlur).toHaveBeenCalledTimes(1);
2326 + expect(onAfterBlur).toHaveBeenCalledWith(
2327 + expect.objectContaining({relatedTarget: inner}),
2328 + );
2329 + resolve();
2330 + expect(log).toEqual(['beforeblur', 'afterblur']);
2331
2339 - document.body.removeChild(container2);
2340 - },
2341 - );
2332 + document.body.removeChild(container2);
2333 + });
2334
2335 describe('Compatibility with Scopes API', () => {
2336 beforeEach(() => {
packages/react-dom/src/events/__tests__/DeprecatedDOMEventResponderSystem-test.internal.js
+2 -1
@@ -789,7 +789,8 @@ describe('DOMEventResponderSystem', () => {
789 buttonRef.current.dispatchEvent(createEvent('foobar'));
790 });
791
792 - it.experimental('should work with concurrent mode updates', async () => {
792 + // @gate experimental
793 + it('should work with concurrent mode updates', async () => {
794 const log = [];
795 const TestResponder = createEventResponder({
796 targetEventTypes: ['click'],
packages/react-dom/src/events/plugins/__tests__/ChangeEventPlugin-test.js
+49 -46
@@ -477,7 +477,8 @@ describe('ChangeEventPlugin', () => {
477 Scheduler = require('scheduler');
478 });
479
480 - it.experimental('text input', () => {
480 + // @gate experimental
481 + it('text input', () => {
482 const root = ReactDOM.createRoot(container);
483 let input;
484
@@ -519,7 +520,8 @@ describe('ChangeEventPlugin', () => {
520 expect(input.value).toBe('changed [!]');
521 });
522
522 - it.experimental('checkbox input', () => {
523 + // @gate experimental
524 + it('checkbox input', () => {
525 const root = ReactDOM.createRoot(container);
526 let input;
527
@@ -574,7 +576,8 @@ describe('ChangeEventPlugin', () => {
576 expect(input.checked).toBe(false);
577 });
578
577 - it.experimental('textarea', () => {
579 + // @gate experimental
580 + it('textarea', () => {
581 const root = ReactDOM.createRoot(container);
582 let textarea;
583
@@ -616,7 +619,8 @@ describe('ChangeEventPlugin', () => {
619 expect(textarea.value).toBe('changed [!]');
620 });
621
619 - it.experimental('parent of input', () => {
622 + // @gate experimental
623 + it('parent of input', () => {
624 const root = ReactDOM.createRoot(container);
625 let input;
626
@@ -662,7 +666,8 @@ describe('ChangeEventPlugin', () => {
666 expect(input.value).toBe('changed [!]');
667 });
668
665 - it.experimental('is async for non-input events', () => {
669 + // @gate experimental
670 + it('is async for non-input events', () => {
671 const root = ReactDOM.createRoot(container);
672 let input;
673
@@ -711,50 +716,48 @@ describe('ChangeEventPlugin', () => {
716 expect(input.value).toBe('');
717 });
718
714 - it.experimental(
715 - 'mouse enter/leave should be user-blocking but not discrete',
716 - async () => {
717 - // This is currently behind a feature flag
718 - jest.resetModules();
719 - React = require('react');
720 - ReactDOM = require('react-dom');
721 - TestUtils = require('react-dom/test-utils');
722 - Scheduler = require('scheduler');
723 -
724 - const {act} = TestUtils;
725 - const {useState} = React;
719 + // @gate experimental
720 + it('mouse enter/leave should be user-blocking but not discrete', async () => {
721 + // This is currently behind a feature flag
722 + jest.resetModules();
723 + React = require('react');
724 + ReactDOM = require('react-dom');
725 + TestUtils = require('react-dom/test-utils');
726 + Scheduler = require('scheduler');
727
727 - const root = ReactDOM.createRoot(container);
728 + const {act} = TestUtils;
729 + const {useState} = React;
730
729 - const target = React.createRef(null);
730 - function Foo() {
731 - const [isHover, setHover] = useState(false);
732 - return (
733 - <div
734 - ref={target}
735 - onMouseEnter={() => setHover(true)}
736 - onMouseLeave={() => setHover(false)}>
737 - {isHover ? 'hovered' : 'not hovered'}
738 - </div>
739 - );
740 - }
741 -
742 - await act(async () => {
743 - root.render(<Foo />);
744 - });
745 - expect(container.textContent).toEqual('not hovered');
731 + const root = ReactDOM.createRoot(container);
732
747 - await act(async () => {
748 - const mouseOverEvent = document.createEvent('MouseEvents');
749 - mouseOverEvent.initEvent('mouseover', true, true);
750 - target.current.dispatchEvent(mouseOverEvent);
733 + const target = React.createRef(null);
734 + function Foo() {
735 + const [isHover, setHover] = useState(false);
736 + return (
737 + <div
738 + ref={target}
739 + onMouseEnter={() => setHover(true)}
740 + onMouseLeave={() => setHover(false)}>
741 + {isHover ? 'hovered' : 'not hovered'}
742 + </div>
743 + );
744 + }
745
752 - // 3s should be enough to expire the updates
753 - Scheduler.unstable_advanceTime(3000);
754 - expect(Scheduler).toFlushExpired([]);
755 - expect(container.textContent).toEqual('hovered');
756 - });
757 - },
758 - );
746 + await act(async () => {
747 + root.render(<Foo />);
748 + });
749 + expect(container.textContent).toEqual('not hovered');
750 +
751 + await act(async () => {
752 + const mouseOverEvent = document.createEvent('MouseEvents');
753 + mouseOverEvent.initEvent('mouseover', true, true);
754 + target.current.dispatchEvent(mouseOverEvent);
755 +
756 + // 3s should be enough to expire the updates
757 + Scheduler.unstable_advanceTime(3000);
758 + expect(Scheduler).toFlushExpired([]);
759 + expect(container.textContent).toEqual('hovered');
760 + });
761 + });
762 });
763 });
packages/react-dom/src/events/plugins/__tests__/SimpleEventPlugin-test.js
+118 -121
@@ -236,138 +236,135 @@ describe('SimpleEventPlugin', function() {
236 Scheduler = require('scheduler');
237 });
238
239 - it.experimental(
240 - 'flushes pending interactive work before extracting event handler',
241 - () => {
242 - container = document.createElement('div');
243 - const root = ReactDOM.createRoot(container);
244 - document.body.appendChild(container);
245 -
246 - let button;
247 - class Button extends React.Component {
248 - state = {disabled: false};
249 - onClick = () => {
250 - // Perform some side-effect
251 - Scheduler.unstable_yieldValue('Side-effect');
252 - // Disable the button
253 - this.setState({disabled: true});
254 - };
255 - render() {
256 - Scheduler.unstable_yieldValue(
257 - `render button: ${this.state.disabled ? 'disabled' : 'enabled'}`,
258 - );
259 - return (
260 - <button
261 - ref={el => (button = el)}
262 - // Handler is removed after the first click
263 - onClick={this.state.disabled ? null : this.onClick}
264 - />
265 - );
266 - }
267 - }
239 + // @gate experimental
240 + it('flushes pending interactive work before extracting event handler', () => {
241 + container = document.createElement('div');
242 + const root = ReactDOM.createRoot(container);
243 + document.body.appendChild(container);
244
269 - // Initial mount
270 - root.render(<Button />);
271 - // Should not have flushed yet because it's async
272 - expect(Scheduler).toHaveYielded([]);
273 - expect(button).toBe(undefined);
274 - // Flush async work
275 - expect(Scheduler).toFlushAndYield(['render button: enabled']);
276 -
277 - function click() {
278 - button.dispatchEvent(
279 - new MouseEvent('click', {bubbles: true, cancelable: true}),
245 + let button;
246 + class Button extends React.Component {
247 + state = {disabled: false};
248 + onClick = () => {
249 + // Perform some side-effect
250 + Scheduler.unstable_yieldValue('Side-effect');
251 + // Disable the button
252 + this.setState({disabled: true});
253 + };
254 + render() {
255 + Scheduler.unstable_yieldValue(
256 + `render button: ${this.state.disabled ? 'disabled' : 'enabled'}`,
257 + );
258 + return (
259 + <button
260 + ref={el => (button = el)}
261 + // Handler is removed after the first click
262 + onClick={this.state.disabled ? null : this.onClick}
263 + />
264 );
265 }
266 + }
267
283 - // Click the button to trigger the side-effect
284 - click();
285 - expect(Scheduler).toHaveYielded([
286 - // The handler fired
287 - 'Side-effect',
288 - // but the component did not re-render yet, because it's async
289 - ]);
290 -
291 - // Click the button again
292 - click();
293 - expect(Scheduler).toHaveYielded([
294 - // Before handling this second click event, the previous interactive
295 - // update is flushed
296 - 'render button: disabled',
297 - // The event handler was removed from the button, so there's no second
298 - // side-effect
299 - ]);
300 -
301 - // The handler should not fire again no matter how many times we
302 - // click the handler.
303 - click();
304 - click();
305 - click();
306 - click();
307 - click();
308 - expect(Scheduler).toFlushAndYield([]);
309 - },
310 - );
268 + // Initial mount
269 + root.render(<Button />);
270 + // Should not have flushed yet because it's async
271 + expect(Scheduler).toHaveYielded([]);
272 + expect(button).toBe(undefined);
273 + // Flush async work
274 + expect(Scheduler).toFlushAndYield(['render button: enabled']);
275
312 - it.experimental(
313 - 'end result of many interactive updates is deterministic',
314 - () => {
315 - container = document.createElement('div');
316 - const root = ReactDOM.createRoot(container);
317 - document.body.appendChild(container);
276 + function click() {
277 + button.dispatchEvent(
278 + new MouseEvent('click', {bubbles: true, cancelable: true}),
279 + );
280 + }
281
319 - let button;
320 - class Button extends React.Component {
321 - state = {count: 0};
322 - render() {
323 - return (
324 - <button
325 - ref={el => (button = el)}
326 - onClick={() =>
327 - // Intentionally not using the updater form here
328 - this.setState({count: this.state.count + 1})
329 - }>
330 - Count: {this.state.count}
331 - </button>
332 - );
333 - }
334 - }
282 + // Click the button to trigger the side-effect
283 + click();
284 + expect(Scheduler).toHaveYielded([
285 + // The handler fired
286 + 'Side-effect',
287 + // but the component did not re-render yet, because it's async
288 + ]);
289 +
290 + // Click the button again
291 + click();
292 + expect(Scheduler).toHaveYielded([
293 + // Before handling this second click event, the previous interactive
294 + // update is flushed
295 + 'render button: disabled',
296 + // The event handler was removed from the button, so there's no second
297 + // side-effect
298 + ]);
299 +
300 + // The handler should not fire again no matter how many times we
301 + // click the handler.
302 + click();
303 + click();
304 + click();
305 + click();
306 + click();
307 + expect(Scheduler).toFlushAndYield([]);
308 + });
309 +
310 + // @gate experimental
311 + it('end result of many interactive updates is deterministic', () => {
312 + container = document.createElement('div');
313 + const root = ReactDOM.createRoot(container);
314 + document.body.appendChild(container);
315
336 - // Initial mount
337 - root.render(<Button />);
338 - // Should not have flushed yet because it's async
339 - expect(button).toBe(undefined);
340 - // Flush async work
341 - Scheduler.unstable_flushAll();
342 - expect(button.textContent).toEqual('Count: 0');
343 -
344 - function click() {
345 - button.dispatchEvent(
346 - new MouseEvent('click', {bubbles: true, cancelable: true}),
316 + let button;
317 + class Button extends React.Component {
318 + state = {count: 0};
319 + render() {
320 + return (
321 + <button
322 + ref={el => (button = el)}
323 + onClick={() =>
324 + // Intentionally not using the updater form here
325 + this.setState({count: this.state.count + 1})
326 + }>
327 + Count: {this.state.count}
328 + </button>
329 );
330 }
331 + }
332
350 - // Click the button a single time
351 - click();
352 - // The counter should not have updated yet because it's async
353 - expect(button.textContent).toEqual('Count: 0');
354 -
355 - // Click the button many more times
356 - click();
357 - click();
358 - click();
359 - click();
360 - click();
361 - click();
362 -
363 - // Flush the remaining work
364 - Scheduler.unstable_flushAll();
365 - // The counter should equal the total number of clicks
366 - expect(button.textContent).toEqual('Count: 7');
367 - },
368 - );
333 + // Initial mount
334 + root.render(<Button />);
335 + // Should not have flushed yet because it's async
336 + expect(button).toBe(undefined);
337 + // Flush async work
338 + Scheduler.unstable_flushAll();
339 + expect(button.textContent).toEqual('Count: 0');
340 +
341 + function click() {
342 + button.dispatchEvent(
343 + new MouseEvent('click', {bubbles: true, cancelable: true}),
344 + );
345 + }
346 +
347 + // Click the button a single time
348 + click();
349 + // The counter should not have updated yet because it's async
350 + expect(button.textContent).toEqual('Count: 0');
351 +
352 + // Click the button many more times
353 + click();
354 + click();
355 + click();
356 + click();
357 + click();
358 + click();
359 +
360 + // Flush the remaining work
361 + Scheduler.unstable_flushAll();
362 + // The counter should equal the total number of clicks
363 + expect(button.textContent).toEqual('Count: 7');
364 + });
365
370 - it.experimental('flushes discrete updates in order', () => {
366 + // @gate experimental
367 + it('flushes discrete updates in order', () => {
368 container = document.createElement('div');
369 document.body.appendChild(container);
370
packages/react-flight-dom-relay/src/__tests__/ReactFlightDOMRelay-test.internal.js
+2 -1
@@ -93,7 +93,8 @@ describe('ReactFlightDOMRelay', () => {
93 });
94 });
95
96 - it.experimental('can transfer a Block to the client and render there', () => {
96 + // @gate experimental
97 + it('can transfer a Block to the client and render there', () => {
98 function load(firstName, lastName) {
99 return {name: firstName + ' ' + lastName};
100 }
packages/react-flight-dom-webpack/src/__tests__/ReactFlightDOM-test.js
+8 -4
@@ -140,7 +140,8 @@ describe('ReactFlightDOM', () => {
140 });
141 });
142
143 - it.experimental('should resolve the root', async () => {
143 + // @gate experimental
144 + it('should resolve the root', async () => {
145 const {Suspense} = React;
146
147 // Model
@@ -191,7 +192,8 @@ describe('ReactFlightDOM', () => {
192 );
193 });
194
194 - it.experimental('should not get confused by $', async () => {
195 + // @gate experimental
196 + it('should not get confused by $', async () => {
197 const {Suspense} = React;
198
199 // Model
@@ -227,7 +229,8 @@ describe('ReactFlightDOM', () => {
229 expect(container.innerHTML).toBe('<p>$1</p>');
230 });
231
230 - it.experimental('should not get confused by @', async () => {
232 + // @gate experimental
233 + it('should not get confused by @', async () => {
234 const {Suspense} = React;
235
236 // Model
@@ -263,7 +266,8 @@ describe('ReactFlightDOM', () => {
266 expect(container.innerHTML).toBe('<p>@div</p>');
267 });
268
266 - it.experimental('should progressively reveal Blocks', async () => {
269 + // @gate experimental
270 + it('should progressively reveal Blocks', async () => {
271 const {Suspense} = React;
272
273 class ErrorBoundary extends React.Component {
packages/react-interactions/events/src/dom/__tests__/FocusWithin-test.internal.js
+55 -59
@@ -344,69 +344,65 @@ describe.each(table)('FocusWithin responder', hasPointerEvents => {
344 );
345 });
346
347 - it.experimental(
348 - 'is called after a focused suspended element is hidden',
349 - () => {
350 - const Suspense = React.Suspense;
351 - let suspend = false;
352 - let resolve;
353 - const promise = new Promise(
354 - resolvePromise => (resolve = resolvePromise),
355 - );
356 -
357 - function Child() {
358 - if (suspend) {
359 - throw promise;
360 - } else {
361 - return <input ref={innerRef} />;
362 - }
347 + // @gate experimental
348 + it('is called after a focused suspended element is hidden', () => {
349 + const Suspense = React.Suspense;
350 + let suspend = false;
351 + let resolve;
352 + const promise = new Promise(resolvePromise => (resolve = resolvePromise));
353 +
354 + function Child() {
355 + if (suspend) {
356 + throw promise;
357 + } else {
358 + return <input ref={innerRef} />;
359 }
360 + }
361
365 - const Component = ({show}) => {
366 - const listener = useFocusWithin({
367 - onBeforeBlurWithin,
368 - onBlurWithin,
369 - });
370 -
371 - return (
372 - <div DEPRECATED_flareListeners={listener}>
373 - <Suspense fallback="Loading...">
374 - <Child />
375 - </Suspense>
376 - </div>
377 - );
378 - };
379 -
380 - const container2 = document.createElement('div');
381 - document.body.appendChild(container2);
382 -
383 - const root = ReactDOM.createRoot(container2);
384 - root.render(<Component />);
385 - Scheduler.unstable_flushAll();
386 - jest.runAllTimers();
387 - expect(container2.innerHTML).toBe('<div><input></div>');
388 -
389 - const inner = innerRef.current;
390 - const target = createEventTarget(inner);
391 - target.keydown({key: 'Tab'});
392 - target.focus();
393 - expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0);
394 - expect(onBlurWithin).toHaveBeenCalledTimes(0);
395 -
396 - suspend = true;
397 - root.render(<Component />);
398 - Scheduler.unstable_flushAll();
399 - jest.runAllTimers();
400 - expect(container2.innerHTML).toBe(
401 - '<div><input style="display: none;">Loading...</div>',
362 + const Component = ({show}) => {
363 + const listener = useFocusWithin({
364 + onBeforeBlurWithin,
365 + onBlurWithin,
366 + });
367 +
368 + return (
369 + <div DEPRECATED_flareListeners={listener}>
370 + <Suspense fallback="Loading...">
371 + <Child />
372 + </Suspense>
373 + </div>
374 );
403 - expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1);
404 - expect(onBlurWithin).toHaveBeenCalledTimes(1);
405 - resolve();
375 + };
376 +
377 + const container2 = document.createElement('div');
378 + document.body.appendChild(container2);
379 +
380 + const root = ReactDOM.createRoot(container2);
381 + root.render(<Component />);
382 + Scheduler.unstable_flushAll();
383 + jest.runAllTimers();
384 + expect(container2.innerHTML).toBe('<div><input></div>');
385
407 - document.body.removeChild(container2);
408 - },
409 - );
386 + const inner = innerRef.current;
387 + const target = createEventTarget(inner);
388 + target.keydown({key: 'Tab'});
389 + target.focus();
390 + expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0);
391 + expect(onBlurWithin).toHaveBeenCalledTimes(0);
392 +
393 + suspend = true;
394 + root.render(<Component />);
395 + Scheduler.unstable_flushAll();
396 + jest.runAllTimers();
397 + expect(container2.innerHTML).toBe(
398 + '<div><input style="display: none;">Loading...</div>',
399 + );
400 + expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1);
401 + expect(onBlurWithin).toHaveBeenCalledTimes(1);
402 + resolve();
403 +
404 + document.body.removeChild(container2);
405 + });
406 });
407
408 it('expect displayName to show up for event component', () => {
packages/react-interactions/events/src/dom/__tests__/Input-test.internal.js
+6 -3
@@ -793,7 +793,8 @@ describe('Input event responder', () => {
793 });
794
795 describe('concurrent mode', () => {
796 - it.experimental('text input', () => {
796 + // @gate experimental
797 + it('text input', () => {
798 const root = ReactDOM.createRoot(container);
799 let input;
800
@@ -848,7 +849,8 @@ describe('Input event responder', () => {
849 expect(input.value).toBe('changed [!]');
850 });
851
851 - it.experimental('checkbox input', () => {
852 + // @gate experimental
853 + it('checkbox input', () => {
854 const root = ReactDOM.createRoot(container);
855 let input;
856
@@ -916,7 +918,8 @@ describe('Input event responder', () => {
918 expect(input.checked).toBe(false);
919 });
920
919 - it.experimental('textarea', () => {
921 + // @gate experimental
922 + it('textarea', () => {
923 const root = ReactDOM.createRoot(container);
924 let textarea;
925
packages/react-reconciler/src/__tests__/ReactBlocks-test.js
+97 -98
@@ -49,7 +49,8 @@ describe('ReactBlocks', () => {
49 };
50 });
51
52 - it.experimental('renders a simple component', () => {
52 + // @gate experimental
53 + it('renders a simple component', () => {
54 function User(props, data) {
55 return <div>{typeof data}</div>;
56 }
@@ -71,7 +72,8 @@ describe('ReactBlocks', () => {
72 expect(ReactNoop).toMatchRenderedOutput(<div>undefined</div>);
73 });
74
74 - it.experimental('prints the name of the render function in warnings', () => {
75 + // @gate experimental
76 + it('prints the name of the render function in warnings', () => {
77 function load(firstName) {
78 return {
79 name: firstName,
@@ -108,7 +110,8 @@ describe('ReactBlocks', () => {
110 );
111 });
112
111 - it.experimental('renders a component with a suspending load', async () => {
113 + // @gate experimental
114 + it('renders a component with a suspending load', async () => {
115 function load(id) {
116 return {
117 id: id,
@@ -147,115 +150,111 @@ describe('ReactBlocks', () => {
150 expect(ReactNoop).toMatchRenderedOutput(<span>Name: Sebastian</span>);
151 });
152
150 - it.experimental(
151 - 'does not support a lazy wrapper around a chunk',
152 - async () => {
153 - function load(id) {
154 - return {
155 - id: id,
156 - name: readString('Sebastian'),
157 - };
158 - }
159 -
160 - function Render(props, data) {
161 - return (
162 - <span>
163 - {props.title}: {data.name}
164 - </span>
165 - );
166 - }
167 -
168 - const loadUser = block(Render, load);
169 -
170 - function App({User}) {
171 - return (
172 - <Suspense fallback={'Loading...'}>
173 - <User title="Name" />
174 - </Suspense>
175 - );
176 - }
153 + // @gate experimental
154 + it('does not support a lazy wrapper around a chunk', async () => {
155 + function load(id) {
156 + return {
157 + id: id,
158 + name: readString('Sebastian'),
159 + };
160 + }
161
178 - let resolveLazy;
179 - const LazyUser = React.lazy(
180 - () =>
181 - new Promise(resolve => {
182 - resolveLazy = function() {
183 - resolve({
184 - default: loadUser(123),
185 - });
186 - };
187 - }),
162 + function Render(props, data) {
163 + return (
164 + <span>
165 + {props.title}: {data.name}
166 + </span>
167 );
168 + }
169
190 - await ReactNoop.act(async () => {
191 - ReactNoop.render(<App User={LazyUser} />);
192 - });
170 + const loadUser = block(Render, load);
171
194 - expect(ReactNoop).toMatchRenderedOutput('Loading...');
172 + function App({User}) {
173 + return (
174 + <Suspense fallback={'Loading...'}>
175 + <User title="Name" />
176 + </Suspense>
177 + );
178 + }
179
196 - // Resolve the component.
197 - await resolveLazy();
180 + let resolveLazy;
181 + const LazyUser = React.lazy(
182 + () =>
183 + new Promise(resolve => {
184 + resolveLazy = function() {
185 + resolve({
186 + default: loadUser(123),
187 + });
188 + };
189 + }),
190 + );
191
199 - expect(Scheduler).toFlushAndThrow(
200 - 'Element type is invalid. Received a promise that resolves to: [object Object]. ' +
201 - 'Lazy element type must resolve to a class or function.' +
202 - (__DEV__
203 - ? ' Did you wrap a component in React.lazy() more than once?'
204 - : ''),
205 - );
206 - },
207 - );
208 -
209 - it.experimental(
210 - 'can receive updated data for the same component',
211 - async () => {
212 - function load(firstName) {
213 - return {
214 - name: firstName,
215 - };
216 - }
192 + await ReactNoop.act(async () => {
193 + ReactNoop.render(<App User={LazyUser} />);
194 + });
195
218 - function Render(props, data) {
219 - const [initialName] = useState(data.name);
220 - return (
221 - <>
222 - <span>Initial name: {initialName}</span>
223 - <span>Latest name: {data.name}</span>
224 - </>
225 - );
226 - }
196 + expect(ReactNoop).toMatchRenderedOutput('Loading...');
197
228 - const loadUser = block(Render, load);
198 + // Resolve the component.
199 + await resolveLazy();
200
230 - function App({User}) {
231 - return (
232 - <Suspense fallback={'Loading...'}>
233 - <User title="Name" />
234 - </Suspense>
235 - );
236 - }
201 + expect(Scheduler).toFlushAndThrow(
202 + 'Element type is invalid. Received a promise that resolves to: [object Object]. ' +
203 + 'Lazy element type must resolve to a class or function.' +
204 + (__DEV__
205 + ? ' Did you wrap a component in React.lazy() more than once?'
206 + : ''),
207 + );
208 + });
209
238 - await ReactNoop.act(async () => {
239 - ReactNoop.render(<App User={loadUser('Sebastian')} />);
240 - });
210 + // @gate experimental
211 + it('can receive updated data for the same component', async () => {
212 + function load(firstName) {
213 + return {
214 + name: firstName,
215 + };
216 + }
217
242 - expect(ReactNoop).toMatchRenderedOutput(
218 + function Render(props, data) {
219 + const [initialName] = useState(data.name);
220 + return (
221 <>
244 - <span>Initial name: Sebastian</span>
245 - <span>Latest name: Sebastian</span>
246 - </>,
222 + <span>Initial name: {initialName}</span>
223 + <span>Latest name: {data.name}</span>
224 + </>
225 );
226 + }
227
249 - await ReactNoop.act(async () => {
250 - ReactNoop.render(<App User={loadUser('Dan')} />);
251 - });
228 + const loadUser = block(Render, load);
229
253 - expect(ReactNoop).toMatchRenderedOutput(
254 - <>
255 - <span>Initial name: Sebastian</span>
256 - <span>Latest name: Dan</span>
257 - </>,
230 + function App({User}) {
231 + return (
232 + <Suspense fallback={'Loading...'}>
233 + <User title="Name" />
234 + </Suspense>
235 );
259 - },
260 - );
236 + }
237 +
238 + await ReactNoop.act(async () => {
239 + ReactNoop.render(<App User={loadUser('Sebastian')} />);
240 + });
241 +
242 + expect(ReactNoop).toMatchRenderedOutput(
243 + <>
244 + <span>Initial name: Sebastian</span>
245 + <span>Latest name: Sebastian</span>
246 + </>,
247 + );
248 +
249 + await ReactNoop.act(async () => {
250 + ReactNoop.render(<App User={loadUser('Dan')} />);
251 + });
252 +
253 + expect(ReactNoop).toMatchRenderedOutput(
254 + <>
255 + <span>Initial name: Sebastian</span>
256 + <span>Latest name: Dan</span>
257 + </>,
258 + );
259 + });
260 });
packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.internal.js
+134 -136
@@ -798,7 +798,8 @@ describe('ReactHooksWithNoopRenderer', () => {
798 });
799
800 // TODO: This should probably warn
801 - it.experimental('calling startTransition inside render phase', async () => {
801 + // @gate experimental
802 + it('calling startTransition inside render phase', async () => {
803 let startTransition;
804 function App() {
805 const [counter, setCounter] = useState(0);
@@ -2728,152 +2729,149 @@ describe('ReactHooksWithNoopRenderer', () => {
2729 });
2730 });
2731 describe('useTransition', () => {
2731 - it.experimental(
2732 - 'delays showing loading state until after timeout',
2733 - async () => {
2734 - let transition;
2735 - function App() {
2736 - const [show, setShow] = useState(false);
2737 - const [startTransition, isPending] = useTransition({
2738 - timeoutMs: 1000,
2732 + // @gate experimental
2733 + it('delays showing loading state until after timeout', async () => {
2734 + let transition;
2735 + function App() {
2736 + const [show, setShow] = useState(false);
2737 + const [startTransition, isPending] = useTransition({
2738 + timeoutMs: 1000,
2739 + });
2740 + transition = () => {
2741 + startTransition(() => {
2742 + setShow(true);
2743 });
2740 - transition = () => {
2741 - startTransition(() => {
2742 - setShow(true);
2743 - });
2744 - };
2745 - return (
2746 - <Suspense
2747 - fallback={<Text text={`Loading... Pending: ${isPending}`} />}>
2748 - {show ? (
2749 - <AsyncText ms={2000} text={`After... Pending: ${isPending}`} />
2750 - ) : (
2751 - <Text text={`Before... Pending: ${isPending}`} />
2752 - )}
2753 - </Suspense>
2754 - );
2755 - }
2756 - ReactNoop.render(<App />);
2757 - expect(Scheduler).toFlushAndYield(['Before... Pending: false']);
2758 - expect(ReactNoop.getChildren()).toEqual([
2759 - span('Before... Pending: false'),
2760 - ]);
2744 + };
2745 + return (
2746 + <Suspense
2747 + fallback={<Text text={`Loading... Pending: ${isPending}`} />}>
2748 + {show ? (
2749 + <AsyncText ms={2000} text={`After... Pending: ${isPending}`} />
2750 + ) : (
2751 + <Text text={`Before... Pending: ${isPending}`} />
2752 + )}
2753 + </Suspense>
2754 + );
2755 + }
2756 + ReactNoop.render(<App />);
2757 + expect(Scheduler).toFlushAndYield(['Before... Pending: false']);
2758 + expect(ReactNoop.getChildren()).toEqual([
2759 + span('Before... Pending: false'),
2760 + ]);
2761
2762 - act(() => {
2763 - Scheduler.unstable_runWithPriority(
2764 - Scheduler.unstable_UserBlockingPriority,
2765 - transition,
2766 - );
2767 - });
2768 - Scheduler.unstable_advanceTime(500);
2769 - await advanceTimers(500);
2770 - expect(Scheduler).toHaveYielded([
2771 - 'Before... Pending: true',
2772 - 'Suspend! [After... Pending: false]',
2773 - 'Loading... Pending: false',
2774 - ]);
2775 - expect(ReactNoop.getChildren()).toEqual([
2776 - span('Before... Pending: true'),
2777 - ]);
2762 + act(() => {
2763 + Scheduler.unstable_runWithPriority(
2764 + Scheduler.unstable_UserBlockingPriority,
2765 + transition,
2766 + );
2767 + });
2768 + Scheduler.unstable_advanceTime(500);
2769 + await advanceTimers(500);
2770 + expect(Scheduler).toHaveYielded([
2771 + 'Before... Pending: true',
2772 + 'Suspend! [After... Pending: false]',
2773 + 'Loading... Pending: false',
2774 + ]);
2775 + expect(ReactNoop.getChildren()).toEqual([
2776 + span('Before... Pending: true'),
2777 + ]);
2778
2779 - Scheduler.unstable_advanceTime(1000);
2780 - await advanceTimers(1000);
2781 - expect(ReactNoop.getChildren()).toEqual([
2782 - hiddenSpan('Before... Pending: true'),
2783 - span('Loading... Pending: false'),
2784 - ]);
2779 + Scheduler.unstable_advanceTime(1000);
2780 + await advanceTimers(1000);
2781 + expect(ReactNoop.getChildren()).toEqual([
2782 + hiddenSpan('Before... Pending: true'),
2783 + span('Loading... Pending: false'),
2784 + ]);
2785
2786 - Scheduler.unstable_advanceTime(500);
2787 - await advanceTimers(500);
2788 - expect(Scheduler).toHaveYielded([
2789 - 'Promise resolved [After... Pending: false]',
2790 - ]);
2791 - expect(Scheduler).toFlushAndYield(['After... Pending: false']);
2792 - expect(ReactNoop.getChildren()).toEqual([
2793 - span('After... Pending: false'),
2794 - ]);
2795 - },
2796 - );
2797 - it.experimental(
2798 - 'delays showing loading state until after busyDelayMs + busyMinDurationMs',
2799 - async () => {
2800 - let transition;
2801 - function App() {
2802 - const [show, setShow] = useState(false);
2803 - const [startTransition, isPending] = useTransition({
2804 - busyDelayMs: 1000,
2805 - busyMinDurationMs: 2000,
2786 + Scheduler.unstable_advanceTime(500);
2787 + await advanceTimers(500);
2788 + expect(Scheduler).toHaveYielded([
2789 + 'Promise resolved [After... Pending: false]',
2790 + ]);
2791 + expect(Scheduler).toFlushAndYield(['After... Pending: false']);
2792 + expect(ReactNoop.getChildren()).toEqual([
2793 + span('After... Pending: false'),
2794 + ]);
2795 + });
2796 + // @gate experimental
2797 + it('delays showing loading state until after busyDelayMs + busyMinDurationMs', async () => {
2798 + let transition;
2799 + function App() {
2800 + const [show, setShow] = useState(false);
2801 + const [startTransition, isPending] = useTransition({
2802 + busyDelayMs: 1000,
2803 + busyMinDurationMs: 2000,
2804 + });
2805 + transition = () => {
2806 + startTransition(() => {
2807 + setShow(true);
2808 });
2807 - transition = () => {
2808 - startTransition(() => {
2809 - setShow(true);
2810 - });
2811 - };
2812 - return (
2813 - <Suspense
2814 - fallback={<Text text={`Loading... Pending: ${isPending}`} />}>
2815 - {show ? (
2816 - <AsyncText ms={2000} text={`After... Pending: ${isPending}`} />
2817 - ) : (
2818 - <Text text={`Before... Pending: ${isPending}`} />
2819 - )}
2820 - </Suspense>
2821 - );
2822 - }
2823 - ReactNoop.render(<App />);
2824 - expect(Scheduler).toFlushAndYield(['Before... Pending: false']);
2825 - expect(ReactNoop.getChildren()).toEqual([
2826 - span('Before... Pending: false'),
2827 - ]);
2809 + };
2810 + return (
2811 + <Suspense
2812 + fallback={<Text text={`Loading... Pending: ${isPending}`} />}>
2813 + {show ? (
2814 + <AsyncText ms={2000} text={`After... Pending: ${isPending}`} />
2815 + ) : (
2816 + <Text text={`Before... Pending: ${isPending}`} />
2817 + )}
2818 + </Suspense>
2819 + );
2820 + }
2821 + ReactNoop.render(<App />);
2822 + expect(Scheduler).toFlushAndYield(['Before... Pending: false']);
2823 + expect(ReactNoop.getChildren()).toEqual([
2824 + span('Before... Pending: false'),
2825 + ]);
2826
2829 - act(() => {
2830 - Scheduler.unstable_runWithPriority(
2831 - Scheduler.unstable_UserBlockingPriority,
2832 - transition,
2833 - );
2834 - });
2835 - Scheduler.unstable_advanceTime(1000);
2836 - await advanceTimers(1000);
2837 - expect(Scheduler).toHaveYielded([
2838 - 'Before... Pending: true',
2839 - 'Suspend! [After... Pending: false]',
2840 - 'Loading... Pending: false',
2841 - ]);
2842 - expect(ReactNoop.getChildren()).toEqual([
2843 - span('Before... Pending: true'),
2844 - ]);
2827 + act(() => {
2828 + Scheduler.unstable_runWithPriority(
2829 + Scheduler.unstable_UserBlockingPriority,
2830 + transition,
2831 + );
2832 + });
2833 + Scheduler.unstable_advanceTime(1000);
2834 + await advanceTimers(1000);
2835 + expect(Scheduler).toHaveYielded([
2836 + 'Before... Pending: true',
2837 + 'Suspend! [After... Pending: false]',
2838 + 'Loading... Pending: false',
2839 + ]);
2840 + expect(ReactNoop.getChildren()).toEqual([
2841 + span('Before... Pending: true'),
2842 + ]);
2843
2846 - // Resolve the promise. The whole tree has now completed. However,
2847 - // because we exceeded the busy threshold, we won't commit the
2848 - // result yet.
2849 - Scheduler.unstable_advanceTime(1000);
2850 - await advanceTimers(1000);
2851 - expect(Scheduler).toHaveYielded([
2852 - 'Promise resolved [After... Pending: false]',
2853 - ]);
2854 - expect(Scheduler).toFlushAndYield(['After... Pending: false']);
2855 - expect(ReactNoop.getChildren()).toEqual([
2856 - span('Before... Pending: true'),
2857 - ]);
2844 + // Resolve the promise. The whole tree has now completed. However,
2845 + // because we exceeded the busy threshold, we won't commit the
2846 + // result yet.
2847 + Scheduler.unstable_advanceTime(1000);
2848 + await advanceTimers(1000);
2849 + expect(Scheduler).toHaveYielded([
2850 + 'Promise resolved [After... Pending: false]',
2851 + ]);
2852 + expect(Scheduler).toFlushAndYield(['After... Pending: false']);
2853 + expect(ReactNoop.getChildren()).toEqual([
2854 + span('Before... Pending: true'),
2855 + ]);
2856
2859 - // Advance time until just before the `busyMinDuration` threshold.
2860 - Scheduler.unstable_advanceTime(999);
2861 - await advanceTimers(999);
2862 - expect(ReactNoop.getChildren()).toEqual([
2863 - span('Before... Pending: true'),
2864 - ]);
2857 + // Advance time until just before the `busyMinDuration` threshold.
2858 + Scheduler.unstable_advanceTime(999);
2859 + await advanceTimers(999);
2860 + expect(ReactNoop.getChildren()).toEqual([
2861 + span('Before... Pending: true'),
2862 + ]);
2863
2866 - // Advance time just a bit more. Now we complete the transition.
2867 - Scheduler.unstable_advanceTime(300);
2868 - await advanceTimers(300);
2869 - expect(ReactNoop.getChildren()).toEqual([
2870 - span('After... Pending: false'),
2871 - ]);
2872 - },
2873 - );
2864 + // Advance time just a bit more. Now we complete the transition.
2865 + Scheduler.unstable_advanceTime(300);
2866 + await advanceTimers(300);
2867 + expect(ReactNoop.getChildren()).toEqual([
2868 + span('After... Pending: false'),
2869 + ]);
2870 + });
2871 });
2872 describe('useDeferredValue', () => {
2876 - it.experimental('defers text value until specified timeout', async () => {
2873 + // @gate experimental
2874 + it('defers text value until specified timeout', async () => {
2875 function TextBox({text}) {
2876 return <AsyncText ms={1000} text={text} />;
2877 }
packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.internal.js
+106 -108
@@ -3738,127 +3738,125 @@ describe('ReactSuspenseWithNoopRenderer', () => {
3738 });
3739
3740 // Regression: https://github.com/facebook/react/issues/18486
3741 - it.experimental(
3742 - 'does not get stuck in pending state with render phase updates',
3743 - async () => {
3744 - let setTextWithShortTransition;
3745 - let setTextWithLongTransition;
3746 -
3747 - function App() {
3748 - const [startShortTransition, isPending1] = React.useTransition({
3749 - timeoutMs: 5000,
3750 - });
3751 - const [startLongTransition, isPending2] = React.useTransition({
3752 - timeoutMs: 30000,
3753 - });
3754 - const isPending = isPending1 || isPending2;
3755 - const [text, setText] = React.useState('');
3756 - const [mirror, setMirror] = React.useState('');
3757 -
3758 - if (text !== mirror) {
3759 - // Render phase update was needed to repro the bug.
3760 - setMirror(text);
3761 - }
3741 + // @gate experimental
3742 + it('does not get stuck in pending state with render phase updates', async () => {
3743 + let setTextWithShortTransition;
3744 + let setTextWithLongTransition;
3745
3763 - setTextWithShortTransition = value => {
3764 - startShortTransition(() => {
3765 - setText(value);
3766 - });
3767 - };
3768 - setTextWithLongTransition = value => {
3769 - startLongTransition(() => {
3770 - setText(value);
3771 - });
3772 - };
3773 -
3774 - return (
3775 - <>
3776 - {isPending ? <Text text="Pending..." /> : null}
3777 - {text !== '' ? <AsyncText text={text} /> : <Text text={text} />}
3778 - </>
3779 - );
3780 - }
3746 + function App() {
3747 + const [startShortTransition, isPending1] = React.useTransition({
3748 + timeoutMs: 5000,
3749 + });
3750 + const [startLongTransition, isPending2] = React.useTransition({
3751 + timeoutMs: 30000,
3752 + });
3753 + const isPending = isPending1 || isPending2;
3754 + const [text, setText] = React.useState('');
3755 + const [mirror, setMirror] = React.useState('');
3756
3782 - function Root() {
3783 - return (
3784 - <Suspense fallback={<Text text="Loading..." />}>
3785 - <App />
3786 - </Suspense>
3787 - );
3757 + if (text !== mirror) {
3758 + // Render phase update was needed to repro the bug.
3759 + setMirror(text);
3760 }
3761
3790 - const root = ReactNoop.createRoot();
3791 - await ReactNoop.act(async () => {
3792 - root.render(<Root />);
3793 - });
3794 - expect(Scheduler).toHaveYielded(['']);
3795 - expect(root).toMatchRenderedOutput(<span prop="" />);
3762 + setTextWithShortTransition = value => {
3763 + startShortTransition(() => {
3764 + setText(value);
3765 + });
3766 + };
3767 + setTextWithLongTransition = value => {
3768 + startLongTransition(() => {
3769 + setText(value);
3770 + });
3771 + };
3772
3797 - // Update to "a". That will suspend.
3798 - await ReactNoop.act(async () => {
3799 - setTextWithShortTransition('a');
3800 - expect(Scheduler).toFlushAndYield([
3801 - 'Pending...',
3802 - '',
3803 - 'Suspend! [a]',
3804 - 'Loading...',
3805 - ]);
3806 - });
3807 - expect(Scheduler).toHaveYielded([]);
3808 - expect(root).toMatchRenderedOutput(
3773 + return (
3774 <>
3810 - <span prop="Pending..." />
3811 - <span prop="" />
3812 - </>,
3775 + {isPending ? <Text text="Pending..." /> : null}
3776 + {text !== '' ? <AsyncText text={text} /> : <Text text={text} />}
3777 + </>
3778 );
3779 + }
3780
3815 - // Update to "b". That will suspend, too.
3816 - await ReactNoop.act(async () => {
3817 - setTextWithLongTransition('b');
3818 - expect(Scheduler).toFlushAndYield([
3819 - // Neither is resolved yet.
3820 - 'Pending...',
3821 - '',
3822 - 'Pending...',
3823 - 'Suspend! [a]',
3824 - 'Loading...',
3825 - 'Suspend! [b]',
3826 - 'Loading...',
3827 - ]);
3828 - });
3829 - expect(Scheduler).toHaveYielded([]);
3830 - expect(root).toMatchRenderedOutput(
3831 - <>
3832 - <span prop="Pending..." />
3833 - <span prop="" />
3834 - </>,
3781 + function Root() {
3782 + return (
3783 + <Suspense fallback={<Text text="Loading..." />}>
3784 + <App />
3785 + </Suspense>
3786 );
3787 + }
3788
3837 - // Resolve "a". But "b" is still pending.
3838 - await ReactNoop.act(async () => {
3839 - await resolveText('a');
3840 - });
3841 - expect(Scheduler).toHaveYielded([
3842 - 'Promise resolved [a]',
3789 + const root = ReactNoop.createRoot();
3790 + await ReactNoop.act(async () => {
3791 + root.render(<Root />);
3792 + });
3793 + expect(Scheduler).toHaveYielded(['']);
3794 + expect(root).toMatchRenderedOutput(<span prop="" />);
3795 +
3796 + // Update to "a". That will suspend.
3797 + await ReactNoop.act(async () => {
3798 + setTextWithShortTransition('a');
3799 + expect(Scheduler).toFlushAndYield([
3800 'Pending...',
3844 - 'a',
3801 + '',
3802 + 'Suspend! [a]',
3803 + 'Loading...',
3804 + ]);
3805 + });
3806 + expect(Scheduler).toHaveYielded([]);
3807 + expect(root).toMatchRenderedOutput(
3808 + <>
3809 + <span prop="Pending..." />
3810 + <span prop="" />
3811 + </>,
3812 + );
3813 +
3814 + // Update to "b". That will suspend, too.
3815 + await ReactNoop.act(async () => {
3816 + setTextWithLongTransition('b');
3817 + expect(Scheduler).toFlushAndYield([
3818 + // Neither is resolved yet.
3819 + 'Pending...',
3820 + '',
3821 + 'Pending...',
3822 + 'Suspend! [a]',
3823 + 'Loading...',
3824 'Suspend! [b]',
3825 'Loading...',
3826 ]);
3848 - expect(root).toMatchRenderedOutput(
3849 - <>
3850 - <span prop="Pending..." />
3851 - <span prop="a" />
3852 - </>,
3853 - );
3827 + });
3828 + expect(Scheduler).toHaveYielded([]);
3829 + expect(root).toMatchRenderedOutput(
3830 + <>
3831 + <span prop="Pending..." />
3832 + <span prop="" />
3833 + </>,
3834 + );
3835
3855 - // Resolve "b". This should remove the pending state.
3856 - await ReactNoop.act(async () => {
3857 - await resolveText('b');
3858 - });
3859 - expect(Scheduler).toHaveYielded(['Promise resolved [b]', 'b']);
3860 - // The bug was that the pending state got stuck forever.
3861 - expect(root).toMatchRenderedOutput(<span prop="b" />);
3862 - },
3863 - );
3836 + // Resolve "a". But "b" is still pending.
3837 + await ReactNoop.act(async () => {
3838 + await resolveText('a');
3839 + });
3840 + expect(Scheduler).toHaveYielded([
3841 + 'Promise resolved [a]',
3842 + 'Pending...',
3843 + 'a',
3844 + 'Suspend! [b]',
3845 + 'Loading...',
3846 + ]);
3847 + expect(root).toMatchRenderedOutput(
3848 + <>
3849 + <span prop="Pending..." />
3850 + <span prop="a" />
3851 + </>,
3852 + );
3853 +
3854 + // Resolve "b". This should remove the pending state.
3855 + await ReactNoop.act(async () => {
3856 + await resolveText('b');
3857 + });
3858 + expect(Scheduler).toHaveYielded(['Promise resolved [b]', 'b']);
3859 + // The bug was that the pending state got stuck forever.
3860 + expect(root).toMatchRenderedOutput(<span prop="b" />);
3861 + });
3862 });
packages/react-reconciler/src/__tests__/ReactTransition-test.internal.js
+35 -37
@@ -59,47 +59,45 @@ describe('ReactTransition', () => {
59 return Component;
60 }
61
62 - it.experimental(
63 - 'isPending works even if called from outside an input event',
64 - async () => {
65 - const Async = createAsyncText('Async');
66 - let start;
67 - function App() {
68 - const [show, setShow] = useState(false);
69 - const [startTransition, isPending] = useTransition();
70 - start = () => startTransition(() => setShow(true));
71 - return (
72 - <Suspense fallback={<Text text="Loading..." />}>
73 - {isPending ? <Text text="Pending..." /> : null}
74 - {show ? <Async /> : <Text text="(empty)" />}
75 - </Suspense>
76 - );
77 - }
62 + // @gate experimental
63 + it('isPending works even if called from outside an input event', async () => {
64 + const Async = createAsyncText('Async');
65 + let start;
66 + function App() {
67 + const [show, setShow] = useState(false);
68 + const [startTransition, isPending] = useTransition();
69 + start = () => startTransition(() => setShow(true));
70 + return (
71 + <Suspense fallback={<Text text="Loading..." />}>
72 + {isPending ? <Text text="Pending..." /> : null}
73 + {show ? <Async /> : <Text text="(empty)" />}
74 + </Suspense>
75 + );
76 + }
77
79 - const root = ReactNoop.createRoot();
78 + const root = ReactNoop.createRoot();
79
81 - await act(async () => {
82 - root.render(<App />);
83 - });
84 - expect(Scheduler).toHaveYielded(['(empty)']);
85 - expect(root).toMatchRenderedOutput('(empty)');
80 + await act(async () => {
81 + root.render(<App />);
82 + });
83 + expect(Scheduler).toHaveYielded(['(empty)']);
84 + expect(root).toMatchRenderedOutput('(empty)');
85
87 - await act(async () => {
88 - start();
86 + await act(async () => {
87 + start();
88
90 - expect(Scheduler).toFlushAndYield([
91 - 'Pending...',
92 - '(empty)',
93 - 'Suspend! [Async]',
94 - 'Loading...',
95 - ]);
89 + expect(Scheduler).toFlushAndYield([
90 + 'Pending...',
91 + '(empty)',
92 + 'Suspend! [Async]',
93 + 'Loading...',
94 + ]);
95
97 - expect(root).toMatchRenderedOutput('Pending...(empty)');
96 + expect(root).toMatchRenderedOutput('Pending...(empty)');
97
99 - await Async.resolve();
100 - });
101 - expect(Scheduler).toHaveYielded(['Async']);
102 - expect(root).toMatchRenderedOutput('Async');
103 - },
104 - );
98 + await Async.resolve();
99 + });
100 + expect(Scheduler).toHaveYielded(['Async']);
101 + expect(root).toMatchRenderedOutput('Async');
102 + });
103 });
packages/react/src/__tests__/ReactStrictMode-test.js
+90 -93
@@ -362,76 +362,75 @@ describe('Concurrent Mode', () => {
362 Scheduler = require('scheduler');
363 });
364
365 - it.experimental(
366 - 'should warn about unsafe legacy lifecycle methods anywhere in the tree',
367 - () => {
368 - class AsyncRoot extends React.Component {
369 - UNSAFE_componentWillMount() {}
370 - UNSAFE_componentWillUpdate() {}
371 - render() {
372 - return (
365 + // @gate experimental
366 + it('should warn about unsafe legacy lifecycle methods anywhere in the tree', () => {
367 + class AsyncRoot extends React.Component {
368 + UNSAFE_componentWillMount() {}
369 + UNSAFE_componentWillUpdate() {}
370 + render() {
371 + return (
372 + <div>
373 + <Wrapper>
374 + <Foo />
375 + </Wrapper>
376 <div>
374 - <Wrapper>
375 - <Foo />
376 - </Wrapper>
377 - <div>
378 - <Bar />
379 - <Foo />
380 - </div>
377 + <Bar />
378 + <Foo />
379 </div>
382 - );
383 - }
384 - }
385 - function Wrapper({children}) {
386 - return <div>{children}</div>;
380 + </div>
381 + );
382 }
388 - class Foo extends React.Component {
389 - UNSAFE_componentWillReceiveProps() {}
390 - render() {
391 - return null;
392 - }
383 + }
384 + function Wrapper({children}) {
385 + return <div>{children}</div>;
386 + }
387 + class Foo extends React.Component {
388 + UNSAFE_componentWillReceiveProps() {}
389 + render() {
390 + return null;
391 }
394 - class Bar extends React.Component {
395 - UNSAFE_componentWillReceiveProps() {}
396 - render() {
397 - return null;
398 - }
392 + }
393 + class Bar extends React.Component {
394 + UNSAFE_componentWillReceiveProps() {}
395 + render() {
396 + return null;
397 }
398 + }
399
401 - const container = document.createElement('div');
402 - const root = ReactDOM.createRoot(container);
403 - root.render(<AsyncRoot />);
404 - expect(() => Scheduler.unstable_flushAll()).toErrorDev(
405 - [
406 - /* eslint-disable max-len */
407 - `Warning: Using UNSAFE_componentWillMount in strict mode is not recommended and may indicate bugs in your code. See https://fb.me/react-unsafe-component-lifecycles for details.
400 + const container = document.createElement('div');
401 + const root = ReactDOM.createRoot(container);
402 + root.render(<AsyncRoot />);
403 + expect(() => Scheduler.unstable_flushAll()).toErrorDev(
404 + [
405 + /* eslint-disable max-len */
406 + `Warning: Using UNSAFE_componentWillMount in strict mode is not recommended and may indicate bugs in your code. See https://fb.me/react-unsafe-component-lifecycles for details.
407
408 * Move code with side effects to componentDidMount, and set initial state in the constructor.
409
410 Please update the following components: AsyncRoot`,
412 - `Warning: Using UNSAFE_componentWillReceiveProps in strict mode is not recommended and may indicate bugs in your code. See https://fb.me/react-unsafe-component-lifecycles for details.
411 + `Warning: Using UNSAFE_componentWillReceiveProps in strict mode is not recommended and may indicate bugs in your code. See https://fb.me/react-unsafe-component-lifecycles for details.
412
413 * Move data fetching code or side effects to componentDidUpdate.
414 * If you're updating state whenever props change, refactor your code to use memoization techniques or move it to static getDerivedStateFromProps. Learn more at: https://fb.me/react-derived-state
415
416 Please update the following components: Bar, Foo`,
418 - `Warning: Using UNSAFE_componentWillUpdate in strict mode is not recommended and may indicate bugs in your code. See https://fb.me/react-unsafe-component-lifecycles for details.
417 + `Warning: Using UNSAFE_componentWillUpdate in strict mode is not recommended and may indicate bugs in your code. See https://fb.me/react-unsafe-component-lifecycles for details.
418
419 * Move data fetching code or side effects to componentDidUpdate.
420
421 Please update the following components: AsyncRoot`,
423 - /* eslint-enable max-len */
424 - ],
425 - {withoutStack: true},
426 - );
422 + /* eslint-enable max-len */
423 + ],
424 + {withoutStack: true},
425 + );
426
428 - // Dedupe
429 - root.render(<AsyncRoot />);
430 - Scheduler.unstable_flushAll();
431 - },
432 - );
427 + // Dedupe
428 + root.render(<AsyncRoot />);
429 + Scheduler.unstable_flushAll();
430 + });
431
434 - it.experimental('should coalesce warnings by lifecycle name', () => {
432 + // @gate experimental
433 + it('should coalesce warnings by lifecycle name', () => {
434 class AsyncRoot extends React.Component {
435 UNSAFE_componentWillMount() {}
436 UNSAFE_componentWillUpdate() {}
@@ -513,52 +512,50 @@ Please update the following components: Parent`,
512 Scheduler.unstable_flushAll();
513 });
514
516 - it.experimental(
517 - 'should warn about components not present during the initial render',
518 - () => {
519 - class AsyncRoot extends React.Component {
520 - render() {
521 - return this.props.foo ? <Foo /> : <Bar />;
522 - }
523 - }
524 - class Foo extends React.Component {
525 - UNSAFE_componentWillMount() {}
526 - render() {
527 - return null;
528 - }
529 - }
530 - class Bar extends React.Component {
531 - UNSAFE_componentWillMount() {}
532 - render() {
533 - return null;
534 - }
535 - }
536 -
537 - const container = document.createElement('div');
538 - const root = ReactDOM.createRoot(container);
539 - root.render(<AsyncRoot foo={true} />);
540 - expect(() =>
541 - Scheduler.unstable_flushAll(),
542 - ).toErrorDev(
543 - 'Using UNSAFE_componentWillMount in strict mode is not recommended',
544 - {withoutStack: true},
545 - );
515 + // @gate experimental
516 + it('should warn about components not present during the initial render', () => {
517 + class AsyncRoot extends React.Component {
518 + render() {
519 + return this.props.foo ? <Foo /> : <Bar />;
520 + }
521 + }
522 + class Foo extends React.Component {
523 + UNSAFE_componentWillMount() {}
524 + render() {
525 + return null;
526 + }
527 + }
528 + class Bar extends React.Component {
529 + UNSAFE_componentWillMount() {}
530 + render() {
531 + return null;
532 + }
533 + }
534
547 - root.render(<AsyncRoot foo={false} />);
548 - expect(() =>
549 - Scheduler.unstable_flushAll(),
550 - ).toErrorDev(
551 - 'Using UNSAFE_componentWillMount in strict mode is not recommended',
552 - {withoutStack: true},
553 - );
535 + const container = document.createElement('div');
536 + const root = ReactDOM.createRoot(container);
537 + root.render(<AsyncRoot foo={true} />);
538 + expect(() =>
539 + Scheduler.unstable_flushAll(),
540 + ).toErrorDev(
541 + 'Using UNSAFE_componentWillMount in strict mode is not recommended',
542 + {withoutStack: true},
543 + );
544 +
545 + root.render(<AsyncRoot foo={false} />);
546 + expect(() =>
547 + Scheduler.unstable_flushAll(),
548 + ).toErrorDev(
549 + 'Using UNSAFE_componentWillMount in strict mode is not recommended',
550 + {withoutStack: true},
551 + );
552
555 - // Dedupe
556 - root.render(<AsyncRoot foo={true} />);
557 - Scheduler.unstable_flushAll();
558 - root.render(<AsyncRoot foo={false} />);
559 - Scheduler.unstable_flushAll();
560 - },
561 - );
553 + // Dedupe
554 + root.render(<AsyncRoot foo={true} />);
555 + Scheduler.unstable_flushAll();
556 + root.render(<AsyncRoot foo={false} />);
557 + Scheduler.unstable_flushAll();
558 + });
559
560 it('should also warn inside of "strict" mode trees', () => {
561 const {StrictMode} = React;
scripts/jest/setupTests.js
-31
@@ -267,37 +267,6 @@ if (process.env.REACT_CLASS_EQUIVALENCE_TEST) {
267 throw Error(errorMsg);
268 };
269
270 - // TODO: Deprecate these helpers in favor of @gate pragma
271 - const it = global.it;
272 - const fit = global.fit;
273 - const xit = global.xit;
274 - if (__EXPERIMENTAL__) {
275 - it.experimental = it;
276 - fit.experimental = it.only.experimental = it.experimental.only = fit;
277 - xit.experimental = it.skip.experimental = it.experimental.skip = xit;
278 - } else {
279 - const errorMessage =
280 - 'Tests marked experimental are expected to fail, but this one passed.';
281 - it.experimental = (message, callback) => {
282 - it(`[EXPERIMENTAL, SHOULD FAIL] ${message}`, () =>
283 - expectTestToFail(callback, errorMessage));
284 - };
285 - fit.experimental = it.only.experimental = it.experimental.only = (
286 - message,
287 - callback
288 - ) => {
289 - fit(`[EXPERIMENTAL, SHOULD FAIL] ${message}`, () =>
290 - expectTestToFail(callback, errorMessage));
291 - };
292 - xit.experimental = it.skip.experimental = it.experimental.skip = (
293 - message,
294 - callback
295 - ) => {
296 - xit(`[EXPERIMENTAL, SHOULD FAIL] ${message}`, () =>
297 - expectTestToFail(callback, errorMessage));
298 - };
299 - }
300 -
270 const gatedErrorMessage = 'Gated test was expected to fail, but it passed.';
271 global._test_gate = (gateFn, testName, callback) => {
272 let shouldPass;