@samitouri / QOS-React-2 / commits / 147bdef11b

Port more tests to the Scheduler.unstable_yieldValue pattern and drop internal.js (#18549)

* Drop the .internal.js suffix on some files that don't need it anymore * Port some ops patterns to scheduler yield * Fix triangle test to avoid side-effects in constructor * Move replaying of setState updaters until after the effect Otherwise any warnings get silenced if they're deduped. * Drop .internal.js in more files * Don't check propTypes on a simple memo component unless it's lazy Comparing the elementType doesn't work for this because it will never be the same for a simple element. This caused us to double validate these. This was covered up because in internal tests this was deduped since they shared the prop types cache but since we now inline it, it doesn't get deduped.

Sebastian Markbåge committed Apr 8, 2020 at 20:54 UTC 147bdef11bbeb8f4aef18c56d53ec1591fce8653
21 files changed +493 -714
packages/create-subscription/src/__tests__/createSubscription-test.js renamed
packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js renamed
packages/react-dom/src/events/__tests__/ChangeEventPlugin-test.js renamed
+24 -52
@@ -470,7 +470,7 @@ describe('ChangeEventPlugin', () => {
470 beforeEach(() => {
471 jest.resetModules();
472 ReactFeatureFlags = require('shared/ReactFeatureFlags');
473 - ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
473 +
474 React = require('react');
475 ReactDOM = require('react-dom');
476 TestUtils = require('react-dom/test-utils');
@@ -481,13 +481,11 @@ describe('ChangeEventPlugin', () => {
481 const root = ReactDOM.createRoot(container);
482 let input;
483
484 - let ops = [];
485 -
484 class ControlledInput extends React.Component {
485 state = {value: 'initial'};
486 onChange = event => this.setState({value: event.target.value});
487 render() {
490 - ops.push(`render: ${this.state.value}`);
488 + Scheduler.unstable_yieldValue(`render: ${this.state.value}`);
489 const controlledValue =
490 this.state.value === 'changed' ? 'changed [!]' : this.state.value;
491 return (
@@ -504,22 +502,19 @@ describe('ChangeEventPlugin', () => {
502 // Initial mount. Test that this is async.
503 root.render(<ControlledInput />);
504 // Should not have flushed yet.
507 - expect(ops).toEqual([]);
505 + expect(Scheduler).toHaveYielded([]);
506 expect(input).toBe(undefined);
507 // Flush callbacks.
510 - Scheduler.unstable_flushAll();
511 - expect(ops).toEqual(['render: initial']);
508 + expect(Scheduler).toFlushAndYield(['render: initial']);
509 expect(input.value).toBe('initial');
510
514 - ops = [];
515 -
511 // Trigger a change event.
512 setUntrackedValue.call(input, 'changed');
513 input.dispatchEvent(
514 new Event('input', {bubbles: true, cancelable: true}),
515 );
516 // Change should synchronously flush
522 - expect(ops).toEqual(['render: changed']);
517 + expect(Scheduler).toHaveYielded(['render: changed']);
518 // Value should be the controlled value, not the original one
519 expect(input.value).toBe('changed [!]');
520 });
@@ -528,15 +523,13 @@ describe('ChangeEventPlugin', () => {
523 const root = ReactDOM.createRoot(container);
524 let input;
525
531 - let ops = [];
532 -
526 class ControlledInput extends React.Component {
527 state = {checked: false};
528 onChange = event => {
529 this.setState({checked: event.target.checked});
530 };
531 render() {
539 - ops.push(`render: ${this.state.checked}`);
532 + Scheduler.unstable_yieldValue(`render: ${this.state.checked}`);
533 const controlledValue = this.props.reverse
534 ? !this.state.checked
535 : this.state.checked;
@@ -554,35 +547,30 @@ describe('ChangeEventPlugin', () => {
547 // Initial mount. Test that this is async.
548 root.render(<ControlledInput reverse={false} />);
549 // Should not have flushed yet.
557 - expect(ops).toEqual([]);
550 + expect(Scheduler).toHaveYielded([]);
551 expect(input).toBe(undefined);
552 // Flush callbacks.
560 - Scheduler.unstable_flushAll();
561 - expect(ops).toEqual(['render: false']);
553 + expect(Scheduler).toFlushAndYield(['render: false']);
554 expect(input.checked).toBe(false);
555
564 - ops = [];
565 -
556 // Trigger a change event.
557 input.dispatchEvent(
558 new MouseEvent('click', {bubbles: true, cancelable: true}),
559 );
560 // Change should synchronously flush
571 - expect(ops).toEqual(['render: true']);
561 + expect(Scheduler).toHaveYielded(['render: true']);
562 expect(input.checked).toBe(true);
563
564 // Now let's make sure we're using the controlled value.
565 root.render(<ControlledInput reverse={true} />);
576 - Scheduler.unstable_flushAll();
577 -
578 - ops = [];
566 + expect(Scheduler).toFlushAndYield(['render: true']);
567
568 // Trigger another change event.
569 input.dispatchEvent(
570 new MouseEvent('click', {bubbles: true, cancelable: true}),
571 );
572 // Change should synchronously flush
585 - expect(ops).toEqual(['render: true']);
573 + expect(Scheduler).toHaveYielded(['render: true']);
574 expect(input.checked).toBe(false);
575 });
576
@@ -590,13 +578,11 @@ describe('ChangeEventPlugin', () => {
578 const root = ReactDOM.createRoot(container);
579 let textarea;
580
593 - let ops = [];
594 -
581 class ControlledTextarea extends React.Component {
582 state = {value: 'initial'};
583 onChange = event => this.setState({value: event.target.value});
584 render() {
599 - ops.push(`render: ${this.state.value}`);
585 + Scheduler.unstable_yieldValue(`render: ${this.state.value}`);
586 const controlledValue =
587 this.state.value === 'changed' ? 'changed [!]' : this.state.value;
588 return (
@@ -613,22 +599,19 @@ describe('ChangeEventPlugin', () => {
599 // Initial mount. Test that this is async.
600 root.render(<ControlledTextarea />);
601 // Should not have flushed yet.
616 - expect(ops).toEqual([]);
602 + expect(Scheduler).toHaveYielded([]);
603 expect(textarea).toBe(undefined);
604 // Flush callbacks.
619 - Scheduler.unstable_flushAll();
620 - expect(ops).toEqual(['render: initial']);
605 + expect(Scheduler).toFlushAndYield(['render: initial']);
606 expect(textarea.value).toBe('initial');
607
623 - ops = [];
624 -
608 // Trigger a change event.
609 setUntrackedTextareaValue.call(textarea, 'changed');
610 textarea.dispatchEvent(
611 new Event('input', {bubbles: true, cancelable: true}),
612 );
613 // Change should synchronously flush
631 - expect(ops).toEqual(['render: changed']);
614 + expect(Scheduler).toHaveYielded(['render: changed']);
615 // Value should be the controlled value, not the original one
616 expect(textarea.value).toBe('changed [!]');
617 });
@@ -637,13 +620,11 @@ describe('ChangeEventPlugin', () => {
620 const root = ReactDOM.createRoot(container);
621 let input;
622
640 - let ops = [];
641 -
623 class ControlledInput extends React.Component {
624 state = {value: 'initial'};
625 onChange = event => this.setState({value: event.target.value});
626 render() {
646 - ops.push(`render: ${this.state.value}`);
627 + Scheduler.unstable_yieldValue(`render: ${this.state.value}`);
628 const controlledValue =
629 this.state.value === 'changed' ? 'changed [!]' : this.state.value;
630 return (
@@ -664,22 +645,19 @@ describe('ChangeEventPlugin', () => {
645 // Initial mount. Test that this is async.
646 root.render(<ControlledInput />);
647 // Should not have flushed yet.
667 - expect(ops).toEqual([]);
648 + expect(Scheduler).toHaveYielded([]);
649 expect(input).toBe(undefined);
650 // Flush callbacks.
670 - Scheduler.unstable_flushAll();
671 - expect(ops).toEqual(['render: initial']);
651 + expect(Scheduler).toFlushAndYield(['render: initial']);
652 expect(input.value).toBe('initial');
653
674 - ops = [];
675 -
654 // Trigger a change event.
655 setUntrackedValue.call(input, 'changed');
656 input.dispatchEvent(
657 new Event('input', {bubbles: true, cancelable: true}),
658 );
659 // Change should synchronously flush
682 - expect(ops).toEqual(['render: changed']);
660 + expect(Scheduler).toHaveYielded(['render: changed']);
661 // Value should be the controlled value, not the original one
662 expect(input.value).toBe('changed [!]');
663 });
@@ -688,8 +666,6 @@ describe('ChangeEventPlugin', () => {
666 const root = ReactDOM.createRoot(container);
667 let input;
668
691 - let ops = [];
692 -
669 class ControlledInput extends React.Component {
670 state = {value: 'initial'};
671 onChange = event => this.setState({value: event.target.value});
@@ -697,7 +673,7 @@ describe('ChangeEventPlugin', () => {
673 this.setState({value: ''});
674 };
675 render() {
700 - ops.push(`render: ${this.state.value}`);
676 + Scheduler.unstable_yieldValue(`render: ${this.state.value}`);
677 const controlledValue =
678 this.state.value === 'changed' ? 'changed [!]' : this.state.value;
679 return (
@@ -715,27 +691,23 @@ describe('ChangeEventPlugin', () => {
691 // Initial mount. Test that this is async.
692 root.render(<ControlledInput />);
693 // Should not have flushed yet.
718 - expect(ops).toEqual([]);
694 + expect(Scheduler).toHaveYielded([]);
695 expect(input).toBe(undefined);
696 // Flush callbacks.
721 - Scheduler.unstable_flushAll();
722 - expect(ops).toEqual(['render: initial']);
697 + expect(Scheduler).toFlushAndYield(['render: initial']);
698 expect(input.value).toBe('initial');
699
725 - ops = [];
726 -
700 // Trigger a click event
701 input.dispatchEvent(
702 new Event('click', {bubbles: true, cancelable: true}),
703 );
704 // Nothing should have changed
732 - expect(ops).toEqual([]);
705 + expect(Scheduler).toHaveYielded([]);
706 expect(input.value).toBe('initial');
707
708 // Flush callbacks.
736 - Scheduler.unstable_flushAll();
709 // Now the click update has flushed.
738 - expect(ops).toEqual(['render: ']);
710 + expect(Scheduler).toFlushAndYield(['render: ']);
711 expect(input.value).toBe('');
712 });
713
packages/react-dom/src/events/__tests__/SimpleEventPlugin-test.js renamed
+11 -24
@@ -12,7 +12,6 @@
12 describe('SimpleEventPlugin', function() {
13 let React;
14 let ReactDOM;
15 - let ReactFeatureFlags;
15 let Scheduler;
16
17 let onClick;
@@ -184,7 +183,6 @@ describe('SimpleEventPlugin', function() {
183 container = document.createElement('div');
184 document.body.appendChild(container);
185
187 - const ops = [];
186 let button;
187 class Button extends React.Component {
188 state = {count: 0};
@@ -193,7 +191,7 @@ describe('SimpleEventPlugin', function() {
191 count: state.count + 1,
192 }));
193 componentDidUpdate() {
196 - ops.push(`didUpdate - Count: ${this.state.count}`);
194 + Scheduler.unstable_yieldValue(`didUpdate - Count: ${this.state.count}`);
195 }
196 render() {
197 return (
@@ -221,20 +219,19 @@ describe('SimpleEventPlugin', function() {
219
220 ReactDOM.render(<Button />, container);
221 expect(button.textContent).toEqual('Count: 0');
224 - expect(ops).toEqual([]);
222 + expect(Scheduler).toHaveYielded([]);
223
224 click();
225
226 // There should be exactly one update.
229 - expect(ops).toEqual(['didUpdate - Count: 3']);
227 + expect(Scheduler).toHaveYielded(['didUpdate - Count: 3']);
228 expect(button.textContent).toEqual('Count: 3');
229 });
230
231 describe('interactive events, in concurrent mode', () => {
232 beforeEach(() => {
233 jest.resetModules();
236 - ReactFeatureFlags = require('shared/ReactFeatureFlags');
237 - ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
234 +
235 ReactDOM = require('react-dom');
236 Scheduler = require('scheduler');
237 });
@@ -246,19 +243,17 @@ describe('SimpleEventPlugin', function() {
243 const root = ReactDOM.createRoot(container);
244 document.body.appendChild(container);
245
249 - let ops = [];
250 -
246 let button;
247 class Button extends React.Component {
248 state = {disabled: false};
249 onClick = () => {
250 // Perform some side-effect
256 - ops.push('Side-effect');
251 + Scheduler.unstable_yieldValue('Side-effect');
252 // Disable the button
253 this.setState({disabled: true});
254 };
255 render() {
261 - ops.push(
256 + Scheduler.unstable_yieldValue(
257 `render button: ${this.state.disabled ? 'disabled' : 'enabled'}`,
258 );
259 return (
@@ -274,13 +269,10 @@ describe('SimpleEventPlugin', function() {
269 // Initial mount
270 root.render(<Button />);
271 // Should not have flushed yet because it's async
277 - expect(ops).toEqual([]);
272 + expect(Scheduler).toHaveYielded([]);
273 expect(button).toBe(undefined);
274 // Flush async work
280 - Scheduler.unstable_flushAll();
281 - expect(ops).toEqual(['render button: enabled']);
282 -
283 - ops = [];
275 + expect(Scheduler).toFlushAndYield(['render button: enabled']);
276
277 function click() {
278 button.dispatchEvent(
@@ -290,17 +282,15 @@ describe('SimpleEventPlugin', function() {
282
283 // Click the button to trigger the side-effect
284 click();
293 - expect(ops).toEqual([
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
299 - ops = [];
300 -
291 // Click the button again
292 click();
303 - expect(ops).toEqual([
293 + expect(Scheduler).toHaveYielded([
294 // Before handling this second click event, the previous interactive
295 // update is flushed
296 'render button: disabled',
@@ -308,8 +298,6 @@ describe('SimpleEventPlugin', function() {
298 // side-effect
299 ]);
300
311 - ops = [];
312 -
301 // The handler should not fire again no matter how many times we
302 // click the handler.
303 click();
@@ -317,8 +305,7 @@ describe('SimpleEventPlugin', function() {
305 click();
306 click();
307 click();
320 - Scheduler.unstable_flushAll();
321 - expect(ops).toEqual([]);
308 + expect(Scheduler).toFlushAndYield([]);
309 },
310 );
311
packages/react-interactions/events/src/dom/__tests__/Input-test.internal.js
+15 -32
@@ -34,7 +34,7 @@ const setUntrackedTextareaValue = Object.getOwnPropertyDescriptor(
34 const modulesInit = () => {
35 ReactFeatureFlags = require('shared/ReactFeatureFlags');
36 ReactFeatureFlags.enableDeprecatedFlareAPI = true;
37 - ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
37 +
38 React = require('react');
39 ReactDOM = require('react-dom');
40 Scheduler = require('scheduler');
@@ -797,8 +797,6 @@ describe('Input event responder', () => {
797 const root = ReactDOM.createRoot(container);
798 let input;
799
800 - let ops = [];
801 -
800 function Component({innerRef, onChange, controlledValue}) {
801 const listener = useInput({
802 onChange,
@@ -817,7 +815,7 @@ describe('Input event responder', () => {
815 state = {value: 'initial'};
816 onChange = event => this.setState({value: event.target.value});
817 render() {
820 - ops.push(`render: ${this.state.value}`);
818 + Scheduler.unstable_yieldValue(`render: ${this.state.value}`);
819 const controlledValue =
820 this.state.value === 'changed' ? 'changed [!]' : this.state.value;
821 return (
@@ -833,22 +831,19 @@ describe('Input event responder', () => {
831 // Initial mount. Test that this is async.
832 root.render(<ControlledInput />);
833 // Should not have flushed yet.
836 - expect(ops).toEqual([]);
834 + expect(Scheduler).toHaveYielded([]);
835 expect(input).toBe(undefined);
836 // Flush callbacks.
839 - Scheduler.unstable_flushAll();
840 - expect(ops).toEqual(['render: initial']);
837 + expect(Scheduler).toFlushAndYield(['render: initial']);
838 expect(input.value).toBe('initial');
839
843 - ops = [];
844 -
840 // Trigger a change event.
841 setUntrackedValue.call(input, 'changed');
842 input.dispatchEvent(
843 new Event('input', {bubbles: true, cancelable: true}),
844 );
845 // Change should synchronously flush
851 - expect(ops).toEqual(['render: changed']);
846 + expect(Scheduler).toHaveYielded(['render: changed']);
847 // Value should be the controlled value, not the original one
848 expect(input.value).toBe('changed [!]');
849 });
@@ -857,8 +852,6 @@ describe('Input event responder', () => {
852 const root = ReactDOM.createRoot(container);
853 let input;
854
860 - let ops = [];
861 -
855 function Component({innerRef, onChange, controlledValue}) {
856 const listener = useInput({
857 onChange,
@@ -879,7 +872,7 @@ describe('Input event responder', () => {
872 this.setState({checked: event.target.checked});
873 };
874 render() {
882 - ops.push(`render: ${this.state.checked}`);
875 + Scheduler.unstable_yieldValue(`render: ${this.state.checked}`);
876 const controlledValue = this.props.reverse
877 ? !this.state.checked
878 : this.state.checked;
@@ -896,35 +889,30 @@ describe('Input event responder', () => {
889 // Initial mount. Test that this is async.
890 root.render(<ControlledInput reverse={false} />);
891 // Should not have flushed yet.
899 - expect(ops).toEqual([]);
892 + expect(Scheduler).toHaveYielded([]);
893 expect(input).toBe(undefined);
894 // Flush callbacks.
902 - Scheduler.unstable_flushAll();
903 - expect(ops).toEqual(['render: false']);
895 + expect(Scheduler).toFlushAndYield(['render: false']);
896 expect(input.checked).toBe(false);
897
906 - ops = [];
907 -
898 // Trigger a change event.
899 input.dispatchEvent(
900 new MouseEvent('click', {bubbles: true, cancelable: true}),
901 );
902 // Change should synchronously flush
913 - expect(ops).toEqual(['render: true']);
903 + expect(Scheduler).toHaveYielded(['render: true']);
904 expect(input.checked).toBe(true);
905
906 // Now let's make sure we're using the controlled value.
907 root.render(<ControlledInput reverse={true} />);
918 - Scheduler.unstable_flushAll();
919 -
920 - ops = [];
908 + expect(Scheduler).toFlushAndYield(['render: true']);
909
910 // Trigger another change event.
911 input.dispatchEvent(
912 new MouseEvent('click', {bubbles: true, cancelable: true}),
913 );
914 // Change should synchronously flush
927 - expect(ops).toEqual(['render: true']);
915 + expect(Scheduler).toHaveYielded(['render: true']);
916 expect(input.checked).toBe(false);
917 });
918
@@ -932,8 +920,6 @@ describe('Input event responder', () => {
920 const root = ReactDOM.createRoot(container);
921 let textarea;
922
935 - let ops = [];
936 -
923 function Component({innerRef, onChange, controlledValue}) {
924 const listener = useInput({
925 onChange,
@@ -952,7 +938,7 @@ describe('Input event responder', () => {
938 state = {value: 'initial'};
939 onChange = event => this.setState({value: event.target.value});
940 render() {
955 - ops.push(`render: ${this.state.value}`);
941 + Scheduler.unstable_yieldValue(`render: ${this.state.value}`);
942 const controlledValue =
943 this.state.value === 'changed' ? 'changed [!]' : this.state.value;
944 return (
@@ -968,22 +954,19 @@ describe('Input event responder', () => {
954 // Initial mount. Test that this is async.
955 root.render(<ControlledTextarea />);
956 // Should not have flushed yet.
971 - expect(ops).toEqual([]);
957 + expect(Scheduler).toHaveYielded([]);
958 expect(textarea).toBe(undefined);
959 // Flush callbacks.
974 - Scheduler.unstable_flushAll();
975 - expect(ops).toEqual(['render: initial']);
960 + expect(Scheduler).toFlushAndYield(['render: initial']);
961 expect(textarea.value).toBe('initial');
962
978 - ops = [];
979 -
963 // Trigger a change event.
964 setUntrackedTextareaValue.call(textarea, 'changed');
965 textarea.dispatchEvent(
966 new Event('input', {bubbles: true, cancelable: true}),
967 );
968 // Change should synchronously flush
986 - expect(ops).toEqual(['render: changed']);
969 + expect(Scheduler).toHaveYielded(['render: changed']);
970 // Value should be the controlled value, not the original one
971 expect(textarea.value).toBe('changed [!]');
972 });
packages/react-interactions/events/src/dom/__tests__/MixedResponders-test-internal.js
+9 -19
@@ -191,8 +191,6 @@ describe('mixing responders with the heritage event system', () => {
191
192 const button = React.createRef();
193
194 - const ops = [];
195 -
194 function MyComponent() {
195 const [pressesCount, updatePressesCount] = useState(0);
196 const [clicksCount, updateClicksCount] = useState(0);
@@ -204,7 +202,7 @@ describe('mixing responders with the heritage event system', () => {
202 const target = createEventTarget(button.current);
203 target.click();
204 // Text context should not have changed
207 - ops.push(newContainer.textContent);
205 + Scheduler.unstable_yieldValue(newContainer.textContent);
206 updatePressesCount(pressesCount + 1);
207 }
208
@@ -229,30 +227,26 @@ describe('mixing responders with the heritage event system', () => {
227 const root = ReactDOM.createRoot(newContainer);
228
229 root.render(<MyComponent />);
232 - Scheduler.unstable_flushAll();
230 + expect(Scheduler).toFlushWithoutYielding();
231 expect(newContainer.textContent).toEqual('Presses: 0, Clicks: 0');
232
233 const target = createEventTarget(button.current);
234 target.pointerdown({timeStamp: 100});
235 target.pointerup({timeStamp: 100});
236
239 - Scheduler.unstable_flushAll();
237 + expect(Scheduler).toHaveYielded(['Presses: 0, Clicks: 0']);
238 + expect(Scheduler).toFlushWithoutYielding();
239 expect(newContainer.textContent).toEqual('Presses: 1, Clicks: 1');
241 -
242 - expect(ops).toEqual(['Presses: 0, Clicks: 0']);
240 },
241 );
242
243 describe('mixing the Input and Press repsonders', () => {
244 it('is async for non-input events', () => {
248 - ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
245 const useTap = require('react-interactions/events/tap').useTap;
246 const useInput = require('react-interactions/events/input').useInput;
247 const root = ReactDOM.createRoot(container);
248 let input;
249
254 - let ops = [];
255 -
250 function Component({innerRef, onChange, controlledValue, listeners}) {
251 const inputListener = useInput({onChange});
252 return (
@@ -286,7 +280,7 @@ describe('mixing responders with the heritage event system', () => {
280 this.setState({value: ''});
281 };
282 render() {
289 - ops.push(`render: ${this.state.value}`);
283 + Scheduler.unstable_yieldValue(`render: ${this.state.value}`);
284 const controlledValue =
285 this.state.value === 'changed' ? 'changed [!]' : this.state.value;
286 return (
@@ -303,15 +297,12 @@ describe('mixing responders with the heritage event system', () => {
297 // Initial mount. Test that this is async.
298 root.render(<ControlledInput />);
299 // Should not have flushed yet.
306 - expect(ops).toEqual([]);
300 + expect(Scheduler).toHaveYielded([]);
301 expect(input).toBe(undefined);
302 // Flush callbacks.
309 - Scheduler.unstable_flushAll();
310 - expect(ops).toEqual(['render: initial']);
303 + expect(Scheduler).toFlushAndYield(['render: initial']);
304 expect(input.value).toBe('initial');
305
313 - ops = [];
314 -
306 // Trigger a click event
307 input.dispatchEvent(
308 new MouseEvent('mousedown', {
@@ -328,13 +319,12 @@ describe('mixing responders with the heritage event system', () => {
319 }),
320 );
321 // Nothing should have changed
331 - expect(ops).toEqual([]);
322 + expect(Scheduler).toHaveYielded([]);
323 expect(input.value).toBe('initial');
324
325 // Flush callbacks.
335 - Scheduler.unstable_flushAll();
326 // Now the click update has flushed.
337 - expect(ops).toEqual(['render: ']);
327 + expect(Scheduler).toFlushAndYield(['render: ']);
328 expect(input.value).toBe('');
329 });
330 });
packages/react-reconciler/src/ReactFiberBeginWork.js
+10 -10
@@ -499,17 +499,17 @@ function updateSimpleMemoComponent(
499 } catch (x) {
500 outerMemoType = null;
501 }
502 + // Inner propTypes will be validated in the function component path.
503 + const outerPropTypes = outerMemoType && (outerMemoType: any).propTypes;
504 + if (outerPropTypes) {
505 + checkPropTypes(
506 + outerPropTypes,
507 + nextProps, // Resolved (SimpleMemoComponent has no defaultProps)
508 + 'prop',
509 + getComponentName(outerMemoType),
510 + );
511 + }
512 }
503 - const outerPropTypes = outerMemoType && (outerMemoType: any).propTypes;
504 - if (outerPropTypes) {
505 - checkPropTypes(
506 - outerPropTypes,
507 - nextProps, // Resolved (SimpleMemoComponent has no defaultProps)
508 - 'prop',
509 - getComponentName(outerMemoType),
510 - );
511 - }
512 - // Inner propTypes will be validated in the function component path.
513 }
514 }
515 if (current !== null) {
packages/react-reconciler/src/ReactUpdateQueue.js
+6 -6
@@ -334,6 +334,9 @@ function getStateFromUpdate<State>(
334 // Updater function
335 if (__DEV__) {
336 enterDisallowedContextReadInDEV();
337 + }
338 + const nextState = payload.call(instance, prevState, nextProps);
339 + if (__DEV__) {
340 if (
341 debugRenderPhaseSideEffectsForStrictMode &&
342 workInProgress.mode & StrictMode
@@ -345,9 +348,6 @@ function getStateFromUpdate<State>(
348 reenableLogs();
349 }
350 }
348 - }
349 - const nextState = payload.call(instance, prevState, nextProps);
350 - if (__DEV__) {
351 exitDisallowedContextReadInDEV();
352 }
353 return nextState;
@@ -367,6 +367,9 @@ function getStateFromUpdate<State>(
367 // Updater function
368 if (__DEV__) {
369 enterDisallowedContextReadInDEV();
370 + }
371 + partialState = payload.call(instance, prevState, nextProps);
372 + if (__DEV__) {
373 if (
374 debugRenderPhaseSideEffectsForStrictMode &&
375 workInProgress.mode & StrictMode
@@ -378,9 +381,6 @@ function getStateFromUpdate<State>(
381 reenableLogs();
382 }
383 }
381 - }
382 - partialState = payload.call(instance, prevState, nextProps);
383 - if (__DEV__) {
384 exitDisallowedContextReadInDEV();
385 }
386 } else {
packages/react-reconciler/src/__tests__/ReactExpiration-test.js renamed
packages/react-reconciler/src/__tests__/ReactIncremental-test.js renamed
+309 -415
@@ -11,7 +11,6 @@
11 'use strict';
12
13 let React;
14 -let ReactFeatureFlags;
14 let ReactNoop;
15 let Scheduler;
16 let PropTypes;
@@ -19,8 +18,6 @@ let PropTypes;
18 describe('ReactIncremental', () => {
19 beforeEach(() => {
20 jest.resetModules();
22 - ReactFeatureFlags = require('shared/ReactFeatureFlags');
23 - ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
21 React = require('react');
22 ReactNoop = require('react-noop-renderer');
23 Scheduler = require('scheduler');
@@ -64,20 +61,18 @@ describe('ReactIncremental', () => {
61 });
62
63 it('updates a previous render', () => {
67 - let ops = [];
68 -
64 function Header() {
70 - ops.push('Header');
65 + Scheduler.unstable_yieldValue('Header');
66 return <h1>Hi</h1>;
67 }
68
69 function Content(props) {
75 - ops.push('Content');
70 + Scheduler.unstable_yieldValue('Content');
71 return <div>{props.children}</div>;
72 }
73
74 function Footer() {
80 - ops.push('Footer');
75 + Scheduler.unstable_yieldValue('Footer');
76 return <footer>Bye</footer>;
77 }
78
@@ -85,7 +80,7 @@ describe('ReactIncremental', () => {
80 const footer = <Footer />;
81
82 function Foo(props) {
88 - ops.push('Foo');
83 + Scheduler.unstable_yieldValue('Foo');
84 return (
85 <div>
86 {header}
@@ -96,11 +91,9 @@ describe('ReactIncremental', () => {
91 }
92
93 ReactNoop.render(<Foo text="foo" />, () =>
99 - ops.push('renderCallbackCalled'),
94 + Scheduler.unstable_yieldValue('renderCallbackCalled'),
95 );
101 - expect(Scheduler).toFlushWithoutYielding();
102 -
103 - expect(ops).toEqual([
96 + expect(Scheduler).toFlushAndYield([
97 'Foo',
98 'Header',
99 'Content',
@@ -108,21 +101,17 @@ describe('ReactIncremental', () => {
101 'renderCallbackCalled',
102 ]);
103
111 - ops = [];
112 -
104 ReactNoop.render(<Foo text="bar" />, () =>
114 - ops.push('firstRenderCallbackCalled'),
105 + Scheduler.unstable_yieldValue('firstRenderCallbackCalled'),
106 );
107 ReactNoop.render(<Foo text="bar" />, () =>
117 - ops.push('secondRenderCallbackCalled'),
108 + Scheduler.unstable_yieldValue('secondRenderCallbackCalled'),
109 );
119 - expect(Scheduler).toFlushWithoutYielding();
120 -
110 // TODO: Test bail out of host components. This is currently unobservable.
111
112 // Since this is an update, it should bail out and reuse the work from
113 // Header and Content.
125 - expect(ops).toEqual([
114 + expect(Scheduler).toFlushAndYield([
115 'Foo',
116 'Content',
117 'firstRenderCallbackCalled',
@@ -267,20 +256,18 @@ describe('ReactIncremental', () => {
256 });
257
258 it('can deprioritize a tree from without dropping work', () => {
270 - let ops = [];
271 -
259 function Bar(props) {
273 - ops.push('Bar');
260 + Scheduler.unstable_yieldValue('Bar');
261 return <div>{props.children}</div>;
262 }
263
264 function Middle(props) {
278 - ops.push('Middle');
265 + Scheduler.unstable_yieldValue('Middle');
266 return <span>{props.children}</span>;
267 }
268
269 function Foo(props) {
283 - ops.push('Foo');
270 + Scheduler.unstable_yieldValue('Foo');
271 return (
272 <div>
273 <Bar>{props.text}</Bar>
@@ -299,42 +286,35 @@ describe('ReactIncremental', () => {
286 ReactNoop.flushSync(() => {
287 ReactNoop.render(<Foo text="foo" />);
288 });
302 - expect(Scheduler).toFlushWithoutYielding();
303 - expect(ops).toEqual(['Foo', 'Bar', 'Bar', 'Middle', 'Middle']);
304 -
305 - ops = [];
289 + expect(Scheduler).toHaveYielded(['Foo', 'Bar', 'Bar']);
290 + expect(Scheduler).toFlushAndYield(['Middle', 'Middle']);
291
292 // Render the high priority work (everything except the hidden trees).
293 ReactNoop.flushSync(() => {
294 ReactNoop.render(<Foo text="foo" />);
295 });
311 - expect(ops).toEqual(['Foo', 'Bar', 'Bar']);
312 -
313 - ops = [];
296 + expect(Scheduler).toHaveYielded(['Foo', 'Bar', 'Bar']);
297
298 // The hidden content was deprioritized from high to low priority. A low
299 // priority callback should have been scheduled. Flush it now.
317 - expect(Scheduler).toFlushWithoutYielding();
318 - expect(ops).toEqual(['Middle', 'Middle']);
300 + expect(Scheduler).toFlushAndYield(['Middle', 'Middle']);
301 });
302
303 xit('can resume work in a subtree even when a parent bails out', () => {
322 - let ops = [];
323 -
304 function Bar(props) {
325 - ops.push('Bar');
305 + Scheduler.unstable_yieldValue('Bar');
306 return <div>{props.children}</div>;
307 }
308
309 function Tester() {
310 // This component is just here to ensure that the bail out is
311 // in fact in effect in the expected place for this test.
332 - ops.push('Tester');
312 + Scheduler.unstable_yieldValue('Tester');
313 return <div />;
314 }
315
316 function Middle(props) {
337 - ops.push('Middle');
317 + Scheduler.unstable_yieldValue('Middle');
318 return <span>{props.children}</span>;
319 }
320
@@ -350,7 +330,7 @@ describe('ReactIncremental', () => {
330 );
331
332 function Foo(props) {
353 - ops.push('Foo');
333 + Scheduler.unstable_yieldValue('Foo');
334 return (
335 <div>
336 <Bar>{props.text}</Bar>
@@ -364,28 +344,21 @@ describe('ReactIncremental', () => {
344 ReactNoop.render(<Foo text="foo" />);
345 ReactNoop.flushDeferredPri(52);
346
367 - expect(ops).toEqual(['Foo', 'Bar', 'Tester', 'Bar']);
368 -
369 - ops = [];
347 + expect(Scheduler).toHaveYielded(['Foo', 'Bar', 'Tester', 'Bar']);
348
349 // We're now rendering an update that will bail out on updating middle.
350 ReactNoop.render(<Foo text="bar" />);
351 ReactNoop.flushDeferredPri(45 + 5);
352
375 - expect(ops).toEqual(['Foo', 'Bar', 'Bar']);
376 -
377 - ops = [];
353 + expect(Scheduler).toHaveYielded(['Foo', 'Bar', 'Bar']);
354
355 // Flush the rest to make sure that the bailout didn't block this work.
380 - expect(Scheduler).toFlushWithoutYielding();
381 - expect(ops).toEqual(['Middle']);
356 + expect(Scheduler).toFlushAndYield(['Middle']);
357 });
358
359 xit('can resume work in a bailed subtree within one pass', () => {
385 - let ops = [];
386 -
360 function Bar(props) {
388 - ops.push('Bar');
361 + Scheduler.unstable_yieldValue('Bar');
362 return <div>{props.children}</div>;
363 }
364
@@ -396,13 +369,13 @@ describe('ReactIncremental', () => {
369 render() {
370 // This component is just here to ensure that the bail out is
371 // in fact in effect in the expected place for this test.
399 - ops.push('Tester');
372 + Scheduler.unstable_yieldValue('Tester');
373 return <div />;
374 }
375 }
376
377 function Middle(props) {
405 - ops.push('Middle');
378 + Scheduler.unstable_yieldValue('Middle');
379 return <span>{props.children}</span>;
380 }
381
@@ -425,7 +398,7 @@ describe('ReactIncremental', () => {
398 }
399
400 function Foo(props) {
428 - ops.push('Foo');
401 + Scheduler.unstable_yieldValue('Foo');
402 return (
403 <div hidden={props.text === 'bar'}>
404 <Bar>{props.text}</Bar>
@@ -439,48 +412,37 @@ describe('ReactIncremental', () => {
412 ReactNoop.render(<Foo text="foo" />);
413 ReactNoop.flushDeferredPri(52 + 5);
414
442 - expect(ops).toEqual(['Foo', 'Bar', 'Tester', 'Bar']);
443 -
444 - ops = [];
415 + expect(Scheduler).toHaveYielded(['Foo', 'Bar', 'Tester', 'Bar']);
416
417 // Make a quick update which will create a low pri tree on top of the
418 // already low pri tree.
419 ReactNoop.render(<Foo text="bar" />);
420 ReactNoop.flushDeferredPri(15);
421
451 - expect(ops).toEqual(['Foo']);
452 -
453 - ops = [];
422 + expect(Scheduler).toHaveYielded(['Foo']);
423
424 // At this point, middle will bail out but it has not yet fully rendered.
425 // Since that is the same priority as its parent tree. This should render
426 // as a single batch. Therefore, it is correct that Middle should be in the
427 // middle. If it occurs after the two "Bar" components then it was flushed
428 // after them which is not correct.
460 - expect(Scheduler).toFlushWithoutYielding();
461 - expect(ops).toEqual(['Bar', 'Middle', 'Bar']);
462 -
463 - ops = [];
429 + expect(Scheduler).toFlushAndYield(['Bar', 'Middle', 'Bar']);
430
431 // Let us try this again without fully finishing the first time. This will
432 // create a hanging subtree that is reconciling at the normal priority.
433 ReactNoop.render(<Foo text="foo" />);
434 ReactNoop.flushDeferredPri(40);
435
470 - expect(ops).toEqual(['Foo', 'Bar']);
471 -
472 - ops = [];
436 + expect(Scheduler).toHaveYielded(['Foo', 'Bar']);
437
438 // This update will create a tree that aborts that work and down-prioritizes
439 // it. If the priority levels aren't down-prioritized correctly this may
440 // abort rendering of the down-prioritized content.
441 ReactNoop.render(<Foo text="bar" />);
478 - expect(Scheduler).toFlushWithoutYielding();
479 - expect(ops).toEqual(['Foo', 'Bar', 'Bar']);
442 + expect(Scheduler).toFlushAndYield(['Foo', 'Bar', 'Bar']);
443 });
444
445 xit('can resume mounting a class component', () => {
483 - let ops = [];
446 let foo;
447 class Parent extends React.Component {
448 shouldComponentUpdate() {
@@ -495,33 +457,30 @@ describe('ReactIncremental', () => {
457 constructor(props) {
458 super(props);
459 // Test based on a www bug where props was null on resume
498 - ops.push('Foo constructor: ' + props.prop);
460 + Scheduler.unstable_yieldValue('Foo constructor: ' + props.prop);
461 }
462 render() {
463 foo = this;
502 - ops.push('Foo');
464 + Scheduler.unstable_yieldValue('Foo');
465 return <Bar />;
466 }
467 }
468
469 function Bar() {
508 - ops.push('Bar');
470 + Scheduler.unstable_yieldValue('Bar');
471 return <div />;
472 }
473
474 ReactNoop.render(<Parent prop="foo" />);
475 ReactNoop.flushDeferredPri(20);
514 - expect(ops).toEqual(['Foo constructor: foo', 'Foo']);
476 + expect(Scheduler).toHaveYielded(['Foo constructor: foo', 'Foo']);
477
478 foo.setState({value: 'bar'});
479
518 - ops = [];
519 - expect(Scheduler).toFlushWithoutYielding();
520 - expect(ops).toEqual(['Foo', 'Bar']);
480 + expect(Scheduler).toFlushAndYield(['Foo', 'Bar']);
481 });
482
483 xit('reuses the same instance when resuming a class instance', () => {
524 - let ops = [];
484 let foo;
485 class Parent extends React.Component {
486 shouldComponentUpdate() {
@@ -537,39 +496,43 @@ describe('ReactIncremental', () => {
496 constructor(props) {
497 super(props);
498 // Test based on a www bug where props was null on resume
540 - ops.push('constructor: ' + props.prop);
499 + Scheduler.unstable_yieldValue('constructor: ' + props.prop);
500 constructorCount++;
501 }
502 UNSAFE_componentWillMount() {
544 - ops.push('componentWillMount: ' + this.props.prop);
503 + Scheduler.unstable_yieldValue('componentWillMount: ' + this.props.prop);
504 }
505 UNSAFE_componentWillReceiveProps() {
547 - ops.push('componentWillReceiveProps: ' + this.props.prop);
506 + Scheduler.unstable_yieldValue(
507 + 'componentWillReceiveProps: ' + this.props.prop,
508 + );
509 }
510 componentDidMount() {
550 - ops.push('componentDidMount: ' + this.props.prop);
511 + Scheduler.unstable_yieldValue('componentDidMount: ' + this.props.prop);
512 }
513 UNSAFE_componentWillUpdate() {
553 - ops.push('componentWillUpdate: ' + this.props.prop);
514 + Scheduler.unstable_yieldValue(
515 + 'componentWillUpdate: ' + this.props.prop,
516 + );
517 }
518 componentDidUpdate() {
556 - ops.push('componentDidUpdate: ' + this.props.prop);
519 + Scheduler.unstable_yieldValue('componentDidUpdate: ' + this.props.prop);
520 }
521 render() {
522 foo = this;
560 - ops.push('render: ' + this.props.prop);
523 + Scheduler.unstable_yieldValue('render: ' + this.props.prop);
524 return <Bar />;
525 }
526 }
527
528 function Bar() {
566 - ops.push('Foo did complete');
529 + Scheduler.unstable_yieldValue('Foo did complete');
530 return <div />;
531 }
532
533 ReactNoop.render(<Parent prop="foo" />);
534 ReactNoop.flushDeferredPri(25);
572 - expect(ops).toEqual([
535 + expect(Scheduler).toHaveYielded([
536 'constructor: foo',
537 'componentWillMount: foo',
538 'render: foo',
@@ -578,10 +541,9 @@ describe('ReactIncremental', () => {
541
542 foo.setState({value: 'bar'});
543
581 - ops = [];
544 expect(Scheduler).toFlushWithoutYielding();
545 expect(constructorCount).toEqual(1);
584 - expect(ops).toEqual([
546 + expect(Scheduler).toHaveYielded([
547 'componentWillMount: foo',
548 'render: foo',
549 'Foo did complete',
@@ -590,15 +552,13 @@ describe('ReactIncremental', () => {
552 });
553
554 xit('can reuse work done after being preempted', () => {
593 - let ops = [];
594 -
555 function Bar(props) {
596 - ops.push('Bar');
556 + Scheduler.unstable_yieldValue('Bar');
557 return <div>{props.children}</div>;
558 }
559
560 function Middle(props) {
601 - ops.push('Middle');
561 + Scheduler.unstable_yieldValue('Middle');
562 return <span>{props.children}</span>;
563 }
564
@@ -619,7 +579,7 @@ describe('ReactIncremental', () => {
579 );
580
581 function Foo(props) {
622 - ops.push('Foo');
582 + Scheduler.unstable_yieldValue('Foo');
583 return (
584 <div>
585 <Bar>{props.text2}</Bar>
@@ -634,9 +594,7 @@ describe('ReactIncremental', () => {
594
595 // We only finish the higher priority work. So the low pri content
596 // has not yet finished mounting.
637 - expect(ops).toEqual(['Foo', 'Bar', 'Middle', 'Bar']);
638 -
639 - ops = [];
597 + expect(Scheduler).toHaveYielded(['Foo', 'Bar', 'Middle', 'Bar']);
598
599 // Interrupt the rendering with a quick update. This should not touch the
600 // middle content.
@@ -645,53 +603,43 @@ describe('ReactIncremental', () => {
603
604 // We've now rendered the entire tree but we didn't have to redo the work
605 // done by the first Middle and Bar already.
648 - expect(ops).toEqual(['Foo', 'Bar', 'Middle']);
649 -
650 - ops = [];
606 + expect(Scheduler).toHaveYielded(['Foo', 'Bar', 'Middle']);
607
608 // Make a quick update which will schedule low priority work to
609 // update the middle content.
610 ReactNoop.render(<Foo text="bar" text2="bar" step={1} />);
611 ReactNoop.flushDeferredPri(30 + 25 + 5);
612
657 - expect(ops).toEqual(['Foo', 'Bar']);
658 -
659 - ops = [];
613 + expect(Scheduler).toHaveYielded(['Foo', 'Bar']);
614
615 // The middle content is now pending rendering...
616 ReactNoop.flushDeferredPri(30 + 5);
663 - expect(ops).toEqual(['Middle', 'Bar']);
664 -
665 - ops = [];
617 + expect(Scheduler).toHaveYielded(['Middle', 'Bar']);
618
619 // but we'll interrupt it to render some higher priority work.
620 // The middle content will bailout so it remains untouched.
621 ReactNoop.render(<Foo text="foo" text2="bar" step={1} />);
622 ReactNoop.flushDeferredPri(30);
623
672 - expect(ops).toEqual(['Foo', 'Bar']);
673 -
674 - ops = [];
624 + expect(Scheduler).toHaveYielded(['Foo', 'Bar']);
625
626 // Since we did nothing to the middle subtree during the interruption,
627 // we should be able to reuse the reconciliation work that we already did
628 // without restarting.
679 - expect(Scheduler).toFlushWithoutYielding();
680 - expect(ops).toEqual(['Middle']);
629 + expect(Scheduler).toFlushAndYield(['Middle']);
630 });
631
632 xit('can reuse work that began but did not complete, after being preempted', () => {
684 - let ops = [];
633 let child;
634 let sibling;
635
636 function GreatGrandchild() {
689 - ops.push('GreatGrandchild');
637 + Scheduler.unstable_yieldValue('GreatGrandchild');
638 return <div />;
639 }
640
641 function Grandchild() {
694 - ops.push('Grandchild');
642 + Scheduler.unstable_yieldValue('Grandchild');
643 return <GreatGrandchild />;
644 }
645
@@ -699,21 +647,21 @@ describe('ReactIncremental', () => {
647 state = {step: 0};
648 render() {
649 child = this;
702 - ops.push('Child');
650 + Scheduler.unstable_yieldValue('Child');
651 return <Grandchild />;
652 }
653 }
654
655 class Sibling extends React.Component {
656 render() {
709 - ops.push('Sibling');
657 + Scheduler.unstable_yieldValue('Sibling');
658 sibling = this;
659 return <div />;
660 }
661 }
662
663 function Parent() {
716 - ops.push('Parent');
664 + Scheduler.unstable_yieldValue('Parent');
665 return [
666 // The extra div is necessary because when Parent bails out during the
667 // high priority update, its progressedPriority is set to high.
@@ -730,27 +678,25 @@ describe('ReactIncremental', () => {
678
679 ReactNoop.render(<Parent />);
680 expect(Scheduler).toFlushWithoutYielding();
733 - ops = [];
681
682 // Begin working on a low priority update to Child, but stop before
683 // GreatGrandchild. Child and Grandchild begin but don't complete.
684 child.setState({step: 1});
685 ReactNoop.flushDeferredPri(30);
739 - expect(ops).toEqual(['Child', 'Grandchild']);
686 + expect(Scheduler).toHaveYielded(['Child', 'Grandchild']);
687
688 // Interrupt the current low pri work with a high pri update elsewhere in
689 // the tree.
743 - ops = [];
690 +
691 ReactNoop.flushSync(() => {
692 sibling.setState({});
693 });
747 - expect(ops).toEqual(['Sibling']);
694 + expect(Scheduler).toHaveYielded(['Sibling']);
695
696 // Continue the low pri work. The work on Child and GrandChild was memoized
697 // so they should not be worked on again.
751 - ops = [];
752 - expect(Scheduler).toFlushWithoutYielding();
753 - expect(ops).toEqual([
698 +
699 + expect(Scheduler).toFlushAndYield([
700 // No Child
701 // No Grandchild
702 'GreatGrandchild',
@@ -758,10 +704,8 @@ describe('ReactIncremental', () => {
704 });
705
706 xit('can reuse work if shouldComponentUpdate is false, after being preempted', () => {
761 - let ops = [];
762 -
707 function Bar(props) {
764 - ops.push('Bar');
708 + Scheduler.unstable_yieldValue('Bar');
709 return <div>{props.children}</div>;
710 }
711
@@ -770,7 +714,7 @@ describe('ReactIncremental', () => {
714 return this.props.children !== nextProps.children;
715 }
716 render() {
773 - ops.push('Middle');
717 + Scheduler.unstable_yieldValue('Middle');
718 return <span>{this.props.children}</span>;
719 }
720 }
@@ -780,7 +724,7 @@ describe('ReactIncremental', () => {
724 return this.props.step !== nextProps.step;
725 }
726 render() {
783 - ops.push('Content');
727 + Scheduler.unstable_yieldValue('Content');
728 return (
729 <div>
730 <Middle>{this.props.step === 0 ? 'Hi' : 'Hello'}</Middle>
@@ -792,7 +736,7 @@ describe('ReactIncremental', () => {
736 }
737
738 function Foo(props) {
795 - ops.push('Foo');
739 + Scheduler.unstable_yieldValue('Foo');
740 return (
741 <div>
742 <Bar>{props.text}</Bar>
@@ -805,71 +749,62 @@ describe('ReactIncremental', () => {
749
750 // Init
751 ReactNoop.render(<Foo text="foo" step={0} />);
808 - expect(Scheduler).toFlushWithoutYielding();
809 -
810 - expect(ops).toEqual(['Foo', 'Bar', 'Content', 'Middle', 'Bar', 'Middle']);
811 -
812 - ops = [];
752 + expect(Scheduler).toFlushAndYield([
753 + 'Foo',
754 + 'Bar',
755 + 'Content',
756 + 'Middle',
757 + 'Bar',
758 + 'Middle',
759 + ]);
760
761 // Make a quick update which will schedule low priority work to
762 // update the middle content.
763 ReactNoop.render(<Foo text="bar" step={1} />);
764 ReactNoop.flushDeferredPri(30 + 5);
765
819 - expect(ops).toEqual(['Foo', 'Bar']);
820 -
821 - ops = [];
766 + expect(Scheduler).toHaveYielded(['Foo', 'Bar']);
767
768 // The middle content is now pending rendering...
769 ReactNoop.flushDeferredPri(30 + 25 + 5);
825 - expect(ops).toEqual(['Content', 'Middle', 'Bar']); // One more Middle left.
826 -
827 - ops = [];
770 + expect(Scheduler).toHaveYielded(['Content', 'Middle', 'Bar']); // One more Middle left.
771
772 // but we'll interrupt it to render some higher priority work.
773 // The middle content will bailout so it remains untouched.
774 ReactNoop.render(<Foo text="foo" step={1} />);
775 ReactNoop.flushDeferredPri(30);
776
834 - expect(ops).toEqual(['Foo', 'Bar']);
835 -
836 - ops = [];
777 + expect(Scheduler).toHaveYielded(['Foo', 'Bar']);
778
779 // Since we did nothing to the middle subtree during the interruption,
780 // we should be able to reuse the reconciliation work that we already did
781 // without restarting.
841 - expect(Scheduler).toFlushWithoutYielding();
842 - expect(ops).toEqual(['Middle']);
782 + expect(Scheduler).toFlushAndYield(['Middle']);
783 });
784
785 it('memoizes work even if shouldComponentUpdate returns false', () => {
846 - let ops = [];
786 class Foo extends React.Component {
787 shouldComponentUpdate(nextProps) {
788 // this.props is the memoized props. So this should return true for
789 // every update except the first one.
790 const shouldUpdate = this.props.step !== 1;
852 - ops.push('shouldComponentUpdate: ' + shouldUpdate);
791 + Scheduler.unstable_yieldValue('shouldComponentUpdate: ' + shouldUpdate);
792 return shouldUpdate;
793 }
794 render() {
856 - ops.push('render');
795 + Scheduler.unstable_yieldValue('render');
796 return <div />;
797 }
798 }
799
800 ReactNoop.render(<Foo step={1} />);
862 - expect(Scheduler).toFlushWithoutYielding();
801 + expect(Scheduler).toFlushAndYield(['render']);
802
864 - ops = [];
803 ReactNoop.render(<Foo step={2} />);
866 - expect(Scheduler).toFlushWithoutYielding();
867 - expect(ops).toEqual(['shouldComponentUpdate: false']);
804 + expect(Scheduler).toFlushAndYield(['shouldComponentUpdate: false']);
805
869 - ops = [];
806 ReactNoop.render(<Foo step={3} />);
871 - expect(Scheduler).toFlushWithoutYielding();
872 - expect(ops).toEqual([
807 + expect(Scheduler).toFlushAndYield([
808 // If the memoized props were not updated during last bail out, sCU will
809 // keep returning false.
810 'shouldComponentUpdate: true',
@@ -1041,10 +976,8 @@ describe('ReactIncremental', () => {
976 });
977
978 it('can forceUpdate', () => {
1044 - const ops = [];
1045 -
979 function Baz() {
1047 - ops.push('Baz');
980 + Scheduler.unstable_yieldValue('Baz');
981 return <div />;
982 }
983
@@ -1058,13 +991,13 @@ describe('ReactIncremental', () => {
991 return false;
992 }
993 render() {
1061 - ops.push('Bar');
994 + Scheduler.unstable_yieldValue('Bar');
995 return <Baz />;
996 }
997 }
998
999 function Foo() {
1067 - ops.push('Foo');
1000 + Scheduler.unstable_yieldValue('Foo');
1001 return (
1002 <div>
1003 <Bar />
@@ -1073,11 +1006,9 @@ describe('ReactIncremental', () => {
1006 }
1007
1008 ReactNoop.render(<Foo />);
1076 - expect(Scheduler).toFlushWithoutYielding();
1077 - expect(ops).toEqual(['Foo', 'Bar', 'Baz']);
1009 + expect(Scheduler).toFlushAndYield(['Foo', 'Bar', 'Baz']);
1010 instance.forceUpdate();
1079 - expect(Scheduler).toFlushWithoutYielding();
1080 - expect(ops).toEqual(['Foo', 'Bar', 'Baz', 'Bar', 'Baz']);
1011 + expect(Scheduler).toFlushAndYield(['Bar', 'Baz']);
1012 });
1013
1014 it('should clear forceUpdate after update is flushed', () => {
@@ -1104,8 +1035,6 @@ describe('ReactIncremental', () => {
1035 });
1036
1037 xit('can call sCU while resuming a partly mounted component', () => {
1107 - let ops = [];
1108 -
1038 const instances = new Set();
1039
1040 class Bar extends React.Component {
@@ -1118,13 +1047,13 @@ describe('ReactIncremental', () => {
1047 return this.props.x !== newProps.x || this.state.y !== newState.y;
1048 }
1049 render() {
1121 - ops.push('Bar:' + this.props.x);
1050 + Scheduler.unstable_yieldValue('Bar:' + this.props.x);
1051 return <span prop={'' + (this.props.x === this.state.y)} />;
1052 }
1053 }
1054
1055 function Foo(props) {
1127 - ops.push('Foo');
1056 + Scheduler.unstable_yieldValue('Foo');
1057 return [
1058 <Bar key="a" x="A" />,
1059 <Bar key="b" x={props.step === 0 ? 'B' : 'B2'} />,
@@ -1135,25 +1064,22 @@ describe('ReactIncremental', () => {
1064
1065 ReactNoop.render(<Foo step={0} />);
1066 ReactNoop.flushDeferredPri(40);
1138 - expect(ops).toEqual(['Foo', 'Bar:A', 'Bar:B', 'Bar:C']);
1067 + expect(Scheduler).toHaveYielded(['Foo', 'Bar:A', 'Bar:B', 'Bar:C']);
1068
1069 expect(instances.size).toBe(3);
1070
1142 - ops = [];
1143 -
1071 ReactNoop.render(<Foo step={1} />);
1072 ReactNoop.flushDeferredPri(50);
1073 // A was memoized and reused. B was memoized but couldn't be reused because
1074 // props differences. C was memoized and reused. D never even started so it
1075 // needed a new instance.
1149 - expect(ops).toEqual(['Foo', 'Bar:B2', 'Bar:D']);
1076 + expect(Scheduler).toHaveYielded(['Foo', 'Bar:B2', 'Bar:D']);
1077
1078 // We expect each rerender to correspond to a new instance.
1079 expect(instances.size).toBe(4);
1080 });
1081
1082 xit('gets new props when setting state on a partly updated component', () => {
1156 - let ops = [];
1083 const instances = [];
1084
1085 class Bar extends React.Component {
@@ -1168,7 +1094,9 @@ describe('ReactIncremental', () => {
1094 });
1095 }
1096 render() {
1171 - ops.push('Bar:' + this.props.x + '-' + this.props.step);
1097 + Scheduler.unstable_yieldValue(
1098 + 'Bar:' + this.props.x + '-' + this.props.step,
1099 + );
1100 return <span prop={'' + (this.props.x === this.state.y)} />;
1101 }
1102 }
@@ -1176,12 +1104,12 @@ describe('ReactIncremental', () => {
1104 function Baz() {
1105 // This component is used as a sibling to Foo so that we can fully
1106 // complete Foo, without committing.
1179 - ops.push('Baz');
1107 + Scheduler.unstable_yieldValue('Baz');
1108 return <div />;
1109 }
1110
1111 function Foo(props) {
1184 - ops.push('Foo');
1112 + Scheduler.unstable_yieldValue('Foo');
1113 return [
1114 <Bar key="a" x="A" step={props.step} />,
1115 <Bar key="b" x="B" step={props.step} />,
@@ -1197,8 +1125,6 @@ describe('ReactIncremental', () => {
1125 );
1126 expect(Scheduler).toFlushWithoutYielding();
1127
1200 - ops = [];
1201 -
1128 // Flush part way through with new props, fully completing the first Bar.
1129 // However, it doesn't commit yet.
1130 ReactNoop.render(
@@ -1209,33 +1135,32 @@ describe('ReactIncremental', () => {
1135 </div>,
1136 );
1137 ReactNoop.flushDeferredPri(45);
1212 - expect(ops).toEqual(['Foo', 'Bar:A-1', 'Bar:B-1', 'Baz']);
1138 + expect(Scheduler).toHaveYielded(['Foo', 'Bar:A-1', 'Bar:B-1', 'Baz']);
1139
1140 // Make an update to the same Bar.
1141 instances[0].performAction();
1142
1217 - ops = [];
1218 -
1219 - expect(Scheduler).toFlushWithoutYielding();
1220 - expect(ops).toEqual(['Bar:A-1', 'Baz']);
1143 + expect(Scheduler).toFlushAndYield(['Bar:A-1', 'Baz']);
1144 });
1145
1146 xit('calls componentWillMount twice if the initial render is aborted', () => {
1224 - let ops = [];
1225 -
1147 class LifeCycle extends React.Component {
1148 state = {x: this.props.x};
1149 UNSAFE_componentWillReceiveProps(nextProps) {
1229 - ops.push(
1150 + Scheduler.unstable_yieldValue(
1151 'componentWillReceiveProps:' + this.state.x + '-' + nextProps.x,
1152 );
1153 this.setState({x: nextProps.x});
1154 }
1155 UNSAFE_componentWillMount() {
1235 - ops.push('componentWillMount:' + this.state.x + '-' + this.props.x);
1156 + Scheduler.unstable_yieldValue(
1157 + 'componentWillMount:' + this.state.x + '-' + this.props.x,
1158 + );
1159 }
1160 componentDidMount() {
1238 - ops.push('componentDidMount:' + this.state.x + '-' + this.props.x);
1161 + Scheduler.unstable_yieldValue(
1162 + 'componentDidMount:' + this.state.x + '-' + this.props.x,
1163 + );
1164 }
1165 render() {
1166 return <span />;
@@ -1243,12 +1168,12 @@ describe('ReactIncremental', () => {
1168 }
1169
1170 function Trail() {
1246 - ops.push('Trail');
1171 + Scheduler.unstable_yieldValue('Trail');
1172 return null;
1173 }
1174
1175 function App(props) {
1251 - ops.push('App');
1176 + Scheduler.unstable_yieldValue('App');
1177 return (
1178 <div>
1179 <LifeCycle x={props.x} />
@@ -1260,14 +1185,10 @@ describe('ReactIncremental', () => {
1185 ReactNoop.render(<App x={0} />);
1186 ReactNoop.flushDeferredPri(30);
1187
1263 - expect(ops).toEqual(['App', 'componentWillMount:0-0']);
1264 -
1265 - ops = [];
1188 + expect(Scheduler).toHaveYielded(['App', 'componentWillMount:0-0']);
1189
1190 ReactNoop.render(<App x={1} />);
1268 - expect(Scheduler).toFlushWithoutYielding();
1269 -
1270 - expect(ops).toEqual([
1191 + expect(Scheduler).toFlushAndYield([
1192 'App',
1193 'componentWillReceiveProps:0-1',
1194 'componentWillMount:1-1',
@@ -1277,45 +1198,40 @@ describe('ReactIncremental', () => {
1198 });
1199
1200 xit('uses state set in componentWillMount even if initial render was aborted', () => {
1280 - let ops = [];
1281 -
1201 class LifeCycle extends React.Component {
1202 constructor(props) {
1203 super(props);
1204 this.state = {x: this.props.x + '(ctor)'};
1205 }
1206 UNSAFE_componentWillMount() {
1288 - ops.push('componentWillMount:' + this.state.x);
1207 + Scheduler.unstable_yieldValue('componentWillMount:' + this.state.x);
1208 this.setState({x: this.props.x + '(willMount)'});
1209 }
1210 componentDidMount() {
1292 - ops.push('componentDidMount:' + this.state.x);
1211 + Scheduler.unstable_yieldValue('componentDidMount:' + this.state.x);
1212 }
1213 render() {
1295 - ops.push('render:' + this.state.x);
1214 + Scheduler.unstable_yieldValue('render:' + this.state.x);
1215 return <span />;
1216 }
1217 }
1218
1219 function App(props) {
1301 - ops.push('App');
1220 + Scheduler.unstable_yieldValue('App');
1221 return <LifeCycle x={props.x} />;
1222 }
1223
1224 ReactNoop.render(<App x={0} />);
1225 ReactNoop.flushDeferredPri(20);
1226
1308 - expect(ops).toEqual([
1227 + expect(Scheduler).toHaveYielded([
1228 'App',
1229 'componentWillMount:0(ctor)',
1230 'render:0(willMount)',
1231 ]);
1232
1314 - ops = [];
1233 ReactNoop.render(<App x={1} />);
1316 - expect(Scheduler).toFlushWithoutYielding();
1317 -
1318 - expect(ops).toEqual([
1234 + expect(Scheduler).toFlushAndYield([
1235 'App',
1236 'componentWillMount:0(willMount)',
1237 'render:1(willMount)',
@@ -1324,32 +1240,36 @@ describe('ReactIncremental', () => {
1240 });
1241
1242 xit('calls componentWill* twice if an update render is aborted', () => {
1327 - let ops = [];
1328 -
1243 class LifeCycle extends React.Component {
1244 UNSAFE_componentWillMount() {
1331 - ops.push('componentWillMount:' + this.props.x);
1245 + Scheduler.unstable_yieldValue('componentWillMount:' + this.props.x);
1246 }
1247 componentDidMount() {
1334 - ops.push('componentDidMount:' + this.props.x);
1248 + Scheduler.unstable_yieldValue('componentDidMount:' + this.props.x);
1249 }
1250 UNSAFE_componentWillReceiveProps(nextProps) {
1337 - ops.push(
1251 + Scheduler.unstable_yieldValue(
1252 'componentWillReceiveProps:' + this.props.x + '-' + nextProps.x,
1253 );
1254 }
1255 shouldComponentUpdate(nextProps) {
1342 - ops.push('shouldComponentUpdate:' + this.props.x + '-' + nextProps.x);
1256 + Scheduler.unstable_yieldValue(
1257 + 'shouldComponentUpdate:' + this.props.x + '-' + nextProps.x,
1258 + );
1259 return true;
1260 }
1261 UNSAFE_componentWillUpdate(nextProps) {
1346 - ops.push('componentWillUpdate:' + this.props.x + '-' + nextProps.x);
1262 + Scheduler.unstable_yieldValue(
1263 + 'componentWillUpdate:' + this.props.x + '-' + nextProps.x,
1264 + );
1265 }
1266 componentDidUpdate(prevProps) {
1349 - ops.push('componentDidUpdate:' + this.props.x + '-' + prevProps.x);
1267 + Scheduler.unstable_yieldValue(
1268 + 'componentDidUpdate:' + this.props.x + '-' + prevProps.x,
1269 + );
1270 }
1271 render() {
1352 - ops.push('render:' + this.props.x);
1272 + Scheduler.unstable_yieldValue('render:' + this.props.x);
1273 return <span />;
1274 }
1275 }
@@ -1357,20 +1277,18 @@ describe('ReactIncremental', () => {
1277 function Sibling() {
1278 // The sibling is used to confirm that we've completed the first child,
1279 // but not yet flushed.
1360 - ops.push('Sibling');
1280 + Scheduler.unstable_yieldValue('Sibling');
1281 return <span />;
1282 }
1283
1284 function App(props) {
1365 - ops.push('App');
1285 + Scheduler.unstable_yieldValue('App');
1286
1287 return [<LifeCycle key="a" x={props.x} />, <Sibling key="b" />];
1288 }
1289
1290 ReactNoop.render(<App x={0} />);
1371 - expect(Scheduler).toFlushWithoutYielding();
1372 -
1373 - expect(ops).toEqual([
1291 + expect(Scheduler).toFlushAndYield([
1292 'App',
1293 'componentWillMount:0',
1294 'render:0',
@@ -1378,12 +1296,10 @@ describe('ReactIncremental', () => {
1296 'componentDidMount:0',
1297 ]);
1298
1381 - ops = [];
1382 -
1299 ReactNoop.render(<App x={1} />);
1300 ReactNoop.flushDeferredPri(30);
1301
1386 - expect(ops).toEqual([
1302 + expect(Scheduler).toHaveYielded([
1303 'App',
1304 'componentWillReceiveProps:0-1',
1305 'shouldComponentUpdate:0-1',
@@ -1393,12 +1309,8 @@ describe('ReactIncremental', () => {
1309 // no componentDidUpdate
1310 ]);
1311
1396 - ops = [];
1397 -
1312 ReactNoop.render(<App x={2} />);
1399 - expect(Scheduler).toFlushWithoutYielding();
1400 -
1401 - expect(ops).toEqual([
1313 + expect(Scheduler).toFlushAndYield([
1314 'App',
1315 'componentWillReceiveProps:1-2',
1316 'shouldComponentUpdate:1-2',
@@ -1411,40 +1323,33 @@ describe('ReactIncremental', () => {
1323 });
1324
1325 it('calls getDerivedStateFromProps even for state-only updates', () => {
1414 - let ops = [];
1326 let instance;
1327
1328 class LifeCycle extends React.Component {
1329 state = {};
1330 static getDerivedStateFromProps(props, prevState) {
1420 - ops.push('getDerivedStateFromProps');
1331 + Scheduler.unstable_yieldValue('getDerivedStateFromProps');
1332 return {foo: 'foo'};
1333 }
1334 changeState() {
1335 this.setState({foo: 'bar'});
1336 }
1337 componentDidUpdate() {
1427 - ops.push('componentDidUpdate');
1338 + Scheduler.unstable_yieldValue('componentDidUpdate');
1339 }
1340 render() {
1430 - ops.push('render');
1341 + Scheduler.unstable_yieldValue('render');
1342 instance = this;
1343 return null;
1344 }
1345 }
1346
1347 ReactNoop.render(<LifeCycle />);
1437 - expect(Scheduler).toFlushWithoutYielding();
1438 -
1439 - expect(ops).toEqual(['getDerivedStateFromProps', 'render']);
1348 + expect(Scheduler).toFlushAndYield(['getDerivedStateFromProps', 'render']);
1349 expect(instance.state).toEqual({foo: 'foo'});
1350
1442 - ops = [];
1443 -
1351 instance.changeState();
1445 - expect(Scheduler).toFlushWithoutYielding();
1446 -
1447 - expect(ops).toEqual([
1352 + expect(Scheduler).toFlushAndYield([
1353 'getDerivedStateFromProps',
1354 'render',
1355 'componentDidUpdate',
@@ -1486,8 +1391,6 @@ describe('ReactIncremental', () => {
1391 });
1392
1393 xit('does not call componentWillReceiveProps for state-only updates', () => {
1489 - let ops = [];
1490 -
1394 const instances = [];
1395
1396 class LifeCycle extends React.Component {
@@ -1499,26 +1402,32 @@ describe('ReactIncremental', () => {
1402 }
1403 UNSAFE_componentWillMount() {
1404 instances.push(this);
1502 - ops.push('componentWillMount:' + this.state.x);
1405 + Scheduler.unstable_yieldValue('componentWillMount:' + this.state.x);
1406 }
1407 componentDidMount() {
1505 - ops.push('componentDidMount:' + this.state.x);
1408 + Scheduler.unstable_yieldValue('componentDidMount:' + this.state.x);
1409 }
1410 UNSAFE_componentWillReceiveProps(nextProps) {
1508 - ops.push('componentWillReceiveProps');
1411 + Scheduler.unstable_yieldValue('componentWillReceiveProps');
1412 }
1413 shouldComponentUpdate(nextProps, nextState) {
1511 - ops.push('shouldComponentUpdate:' + this.state.x + '-' + nextState.x);
1414 + Scheduler.unstable_yieldValue(
1415 + 'shouldComponentUpdate:' + this.state.x + '-' + nextState.x,
1416 + );
1417 return true;
1418 }
1419 UNSAFE_componentWillUpdate(nextProps, nextState) {
1515 - ops.push('componentWillUpdate:' + this.state.x + '-' + nextState.x);
1420 + Scheduler.unstable_yieldValue(
1421 + 'componentWillUpdate:' + this.state.x + '-' + nextState.x,
1422 + );
1423 }
1424 componentDidUpdate(prevProps, prevState) {
1518 - ops.push('componentDidUpdate:' + this.state.x + '-' + prevState.x);
1425 + Scheduler.unstable_yieldValue(
1426 + 'componentDidUpdate:' + this.state.x + '-' + prevState.x,
1427 + );
1428 }
1429 render() {
1521 - ops.push('render:' + this.state.x);
1430 + Scheduler.unstable_yieldValue('render:' + this.state.x);
1431 return <span />;
1432 }
1433 }
@@ -1537,7 +1446,7 @@ describe('ReactIncremental', () => {
1446 });
1447 }
1448 render() {
1540 - ops.push('Wrap');
1449 + Scheduler.unstable_yieldValue('Wrap');
1450 return <LifeCycle y={this.state.y} />;
1451 }
1452 }
@@ -1545,19 +1454,17 @@ describe('ReactIncremental', () => {
1454 function Sibling() {
1455 // The sibling is used to confirm that we've completed the first child,
1456 // but not yet flushed.
1548 - ops.push('Sibling');
1457 + Scheduler.unstable_yieldValue('Sibling');
1458 return <span />;
1459 }
1460
1461 function App(props) {
1553 - ops.push('App');
1462 + Scheduler.unstable_yieldValue('App');
1463 return [<Wrap key="a" />, <Sibling key="b" />];
1464 }
1465
1466 ReactNoop.render(<App y={0} />);
1558 - expect(Scheduler).toFlushWithoutYielding();
1559 -
1560 - expect(ops).toEqual([
1467 + expect(Scheduler).toFlushAndYield([
1468 'App',
1469 'Wrap',
1470 'componentWillMount:0',
@@ -1566,14 +1473,12 @@ describe('ReactIncremental', () => {
1473 'componentDidMount:0',
1474 ]);
1475
1569 - ops = [];
1570 -
1476 // LifeCycle
1477 instances[1].tick();
1478
1479 ReactNoop.flushDeferredPri(25);
1480
1576 - expect(ops).toEqual([
1481 + expect(Scheduler).toHaveYielded([
1482 // no componentWillReceiveProps
1483 'shouldComponentUpdate:0-1',
1484 'componentWillUpdate:0-1',
@@ -1581,14 +1486,10 @@ describe('ReactIncremental', () => {
1486 // no componentDidUpdate
1487 ]);
1488
1584 - ops = [];
1585 -
1489 // LifeCycle
1490 instances[1].tick();
1491
1589 - expect(Scheduler).toFlushWithoutYielding();
1590 -
1591 - expect(ops).toEqual([
1492 + expect(Scheduler).toFlushAndYield([
1493 // no componentWillReceiveProps
1494 'shouldComponentUpdate:1-2',
1495 'componentWillUpdate:1-2',
@@ -1597,15 +1498,13 @@ describe('ReactIncremental', () => {
1498 'componentDidUpdate:2-0',
1499 ]);
1500
1600 - ops = [];
1601 -
1501 // Next we will update props of LifeCycle by updating its parent.
1502
1503 instances[0].tick();
1504
1505 ReactNoop.flushDeferredPri(30);
1506
1608 - expect(ops).toEqual([
1507 + expect(Scheduler).toHaveYielded([
1508 'Wrap',
1509 'componentWillReceiveProps',
1510 'shouldComponentUpdate:2-2',
@@ -1614,14 +1513,10 @@ describe('ReactIncremental', () => {
1513 // no componentDidUpdate
1514 ]);
1515
1617 - ops = [];
1618 -
1516 // Next we will update LifeCycle directly but not with new props.
1517 instances[1].tick();
1518
1622 - expect(Scheduler).toFlushWithoutYielding();
1623 -
1624 - expect(ops).toEqual([
1519 + expect(Scheduler).toFlushAndYield([
1520 // This should not trigger another componentWillReceiveProps because
1521 // we never got new props.
1522 'shouldComponentUpdate:2-3',
@@ -1635,37 +1530,35 @@ describe('ReactIncremental', () => {
1530 });
1531
1532 xit('skips will/DidUpdate when bailing unless an update was already in progress', () => {
1638 - let ops = [];
1639 -
1533 class LifeCycle extends React.Component {
1534 UNSAFE_componentWillMount() {
1642 - ops.push('componentWillMount');
1535 + Scheduler.unstable_yieldValue('componentWillMount');
1536 }
1537 componentDidMount() {
1645 - ops.push('componentDidMount');
1538 + Scheduler.unstable_yieldValue('componentDidMount');
1539 }
1540 UNSAFE_componentWillReceiveProps(nextProps) {
1648 - ops.push('componentWillReceiveProps');
1541 + Scheduler.unstable_yieldValue('componentWillReceiveProps');
1542 }
1543 shouldComponentUpdate(nextProps) {
1651 - ops.push('shouldComponentUpdate');
1544 + Scheduler.unstable_yieldValue('shouldComponentUpdate');
1545 // Bail
1546 return this.props.x !== nextProps.x;
1547 }
1548 UNSAFE_componentWillUpdate(nextProps) {
1656 - ops.push('componentWillUpdate');
1549 + Scheduler.unstable_yieldValue('componentWillUpdate');
1550 }
1551 componentDidUpdate(prevProps) {
1659 - ops.push('componentDidUpdate');
1552 + Scheduler.unstable_yieldValue('componentDidUpdate');
1553 }
1554 render() {
1662 - ops.push('render');
1555 + Scheduler.unstable_yieldValue('render');
1556 return <span />;
1557 }
1558 }
1559
1560 function Sibling() {
1668 - ops.push('render sibling');
1561 + Scheduler.unstable_yieldValue('render sibling');
1562 return <span />;
1563 }
1564
@@ -1674,22 +1567,16 @@ describe('ReactIncremental', () => {
1567 }
1568
1569 ReactNoop.render(<App x={0} />);
1677 - expect(Scheduler).toFlushWithoutYielding();
1678 -
1679 - expect(ops).toEqual([
1570 + expect(Scheduler).toFlushAndYield([
1571 'componentWillMount',
1572 'render',
1573 'render sibling',
1574 'componentDidMount',
1575 ]);
1576
1686 - ops = [];
1687 -
1577 // Update to same props
1578 ReactNoop.render(<App x={0} />);
1690 - expect(Scheduler).toFlushWithoutYielding();
1691 -
1692 - expect(ops).toEqual([
1579 + expect(Scheduler).toFlushAndYield([
1580 'componentWillReceiveProps',
1581 'shouldComponentUpdate',
1582 // no componentWillUpdate
@@ -1698,13 +1585,11 @@ describe('ReactIncremental', () => {
1585 // no componentDidUpdate
1586 ]);
1587
1701 - ops = [];
1702 -
1588 // Begin updating to new props...
1589 ReactNoop.render(<App x={1} />);
1590 ReactNoop.flushDeferredPri(30);
1591
1707 - expect(ops).toEqual([
1592 + expect(Scheduler).toHaveYielded([
1593 'componentWillReceiveProps',
1594 'shouldComponentUpdate',
1595 'componentWillUpdate',
@@ -1713,14 +1598,12 @@ describe('ReactIncremental', () => {
1598 // no componentDidUpdate yet
1599 ]);
1600
1716 - ops = [];
1717 -
1601 // ...but we'll interrupt it to rerender the same props.
1602 ReactNoop.render(<App x={1} />);
1603 expect(Scheduler).toFlushWithoutYielding();
1604
1605 // We can bail out this time, but we must call componentDidUpdate.
1723 - expect(ops).toEqual([
1606 + expect(Scheduler).toHaveYielded([
1607 'componentWillReceiveProps',
1608 'shouldComponentUpdate',
1609 // no componentWillUpdate
@@ -1731,7 +1614,6 @@ describe('ReactIncremental', () => {
1614 });
1615
1616 it('can nest batchedUpdates', () => {
1734 - let ops = [];
1617 let instance;
1618
1619 class Foo extends React.Component {
@@ -1744,24 +1626,31 @@ describe('ReactIncremental', () => {
1626
1627 ReactNoop.render(<Foo />);
1628 expect(Scheduler).toFlushWithoutYielding();
1747 - ops = [];
1629
1630 ReactNoop.flushSync(() => {
1631 ReactNoop.batchedUpdates(() => {
1751 - instance.setState({n: 1}, () => ops.push('setState 1'));
1752 - instance.setState({n: 2}, () => ops.push('setState 2'));
1632 + instance.setState({n: 1}, () =>
1633 + Scheduler.unstable_yieldValue('setState 1'),
1634 + );
1635 + instance.setState({n: 2}, () =>
1636 + Scheduler.unstable_yieldValue('setState 2'),
1637 + );
1638 ReactNoop.batchedUpdates(() => {
1754 - instance.setState({n: 3}, () => ops.push('setState 3'));
1755 - instance.setState({n: 4}, () => ops.push('setState 4'));
1756 - ops.push('end inner batchedUpdates');
1639 + instance.setState({n: 3}, () =>
1640 + Scheduler.unstable_yieldValue('setState 3'),
1641 + );
1642 + instance.setState({n: 4}, () =>
1643 + Scheduler.unstable_yieldValue('setState 4'),
1644 + );
1645 + Scheduler.unstable_yieldValue('end inner batchedUpdates');
1646 });
1758 - ops.push('end outer batchedUpdates');
1647 + Scheduler.unstable_yieldValue('end outer batchedUpdates');
1648 });
1649 });
1650
1651 // ReactNoop.flush() not needed because updates are synchronous
1652
1764 - expect(ops).toEqual([
1653 + expect(Scheduler).toHaveYielded([
1654 'end inner batchedUpdates',
1655 'end outer batchedUpdates',
1656 'setState 1',
@@ -1773,7 +1662,6 @@ describe('ReactIncremental', () => {
1662 });
1663
1664 it('can handle if setState callback throws', () => {
1776 - let ops = [];
1665 let instance;
1666
1667 class Foo extends React.Component {
@@ -1786,25 +1674,28 @@ describe('ReactIncremental', () => {
1674
1675 ReactNoop.render(<Foo />);
1676 expect(Scheduler).toFlushWithoutYielding();
1789 - ops = [];
1677
1678 function updater({n}) {
1679 return {n: n + 1};
1680 }
1681
1795 - instance.setState(updater, () => ops.push('first callback'));
1682 + instance.setState(updater, () =>
1683 + Scheduler.unstable_yieldValue('first callback'),
1684 + );
1685 instance.setState(updater, () => {
1797 - ops.push('second callback');
1686 + Scheduler.unstable_yieldValue('second callback');
1687 throw new Error('callback error');
1688 });
1800 - instance.setState(updater, () => ops.push('third callback'));
1689 + instance.setState(updater, () =>
1690 + Scheduler.unstable_yieldValue('third callback'),
1691 + );
1692
1693 expect(() => {
1694 expect(Scheduler).toFlushWithoutYielding();
1695 }).toThrow('callback error');
1696
1697 // The third callback isn't called because the second one throws
1807 - expect(ops).toEqual(['first callback', 'second callback']);
1698 + expect(Scheduler).toHaveYielded(['first callback', 'second callback']);
1699 expect(instance.state.n).toEqual(3);
1700 });
1701
@@ -1977,7 +1868,6 @@ describe('ReactIncremental', () => {
1868 });
1869
1870 it('does not leak own context into context provider', () => {
1980 - const ops = [];
1871 class Recurse extends React.Component {
1872 static contextTypes = {
1873 n: PropTypes.number,
@@ -1989,7 +1879,9 @@ describe('ReactIncremental', () => {
1879 return {n: (this.context.n || 3) - 1};
1880 }
1881 render() {
1992 - ops.push('Recurse ' + JSON.stringify(this.context));
1882 + Scheduler.unstable_yieldValue(
1883 + 'Recurse ' + JSON.stringify(this.context),
1884 + );
1885 if (this.context.n === 0) {
1886 return null;
1887 }
@@ -1998,30 +1890,30 @@ describe('ReactIncremental', () => {
1890 }
1891
1892 ReactNoop.render(<Recurse />);
2001 - expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev(
1893 + expect(() =>
1894 + expect(Scheduler).toFlushAndYield([
1895 + 'Recurse {}',
1896 + 'Recurse {"n":2}',
1897 + 'Recurse {"n":1}',
1898 + 'Recurse {"n":0}',
1899 + ]),
1900 + ).toErrorDev(
1901 'Legacy context API has been detected within a strict-mode tree.\n\n' +
1902 'The old API will be supported in all 16.x releases, but applications ' +
1903 'using it should migrate to the new version.\n\n' +
1904 'Please update the following components: Recurse',
1905 );
2007 - expect(ops).toEqual([
2008 - 'Recurse {}',
2009 - 'Recurse {"n":2}',
2010 - 'Recurse {"n":1}',
2011 - 'Recurse {"n":0}',
2012 - ]);
1906 });
1907
1908 if (!require('shared/ReactFeatureFlags').disableModulePatternComponents) {
1909 it('does not leak own context into context provider (factory components)', () => {
2017 - const ops = [];
1910 function Recurse(props, context) {
1911 return {
1912 getChildContext() {
1913 return {n: (context.n || 3) - 1};
1914 },
1915 render() {
2024 - ops.push('Recurse ' + JSON.stringify(context));
1916 + Scheduler.unstable_yieldValue('Recurse ' + JSON.stringify(context));
1917 if (context.n === 0) {
1918 return null;
1919 }
@@ -2037,7 +1929,14 @@ describe('ReactIncremental', () => {
1929 };
1930
1931 ReactNoop.render(<Recurse />);
2040 - expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev([
1932 + expect(() =>
1933 + expect(Scheduler).toFlushAndYield([
1934 + 'Recurse {}',
1935 + 'Recurse {"n":2}',
1936 + 'Recurse {"n":1}',
1937 + 'Recurse {"n":0}',
1938 + ]),
1939 + ).toErrorDev([
1940 'Warning: The <Recurse /> component appears to be a function component that returns a class instance. ' +
1941 'Change Recurse to a class that extends React.Component instead. ' +
1942 "If you can't use a class try assigning the prototype on the function as a workaround. " +
@@ -2048,12 +1947,6 @@ describe('ReactIncremental', () => {
1947 'using it should migrate to the new version.\n\n' +
1948 'Please update the following components: Recurse',
1949 ]);
2051 - expect(ops).toEqual([
2052 - 'Recurse {}',
2053 - 'Recurse {"n":2}',
2054 - 'Recurse {"n":1}',
2055 - 'Recurse {"n":0}',
2056 - ]);
1950 });
1951 }
1952
@@ -2118,7 +2011,6 @@ describe('ReactIncremental', () => {
2011 });
2012
2013 it('reads context when setState is below the provider', () => {
2121 - const ops = [];
2014 let statefulInst;
2015
2016 class Intl extends React.Component {
@@ -2129,11 +2021,15 @@ describe('ReactIncremental', () => {
2021 const childContext = {
2022 locale: this.props.locale,
2023 };
2132 - ops.push('Intl:provide ' + JSON.stringify(childContext));
2024 + Scheduler.unstable_yieldValue(
2025 + 'Intl:provide ' + JSON.stringify(childContext),
2026 + );
2027 return childContext;
2028 }
2029 render() {
2136 - ops.push('Intl:read ' + JSON.stringify(this.context));
2030 + Scheduler.unstable_yieldValue(
2031 + 'Intl:read ' + JSON.stringify(this.context),
2032 + );
2033 return this.props.children;
2034 }
2035 }
@@ -2143,13 +2039,17 @@ describe('ReactIncremental', () => {
2039 locale: PropTypes.string,
2040 };
2041 render() {
2146 - ops.push('ShowLocaleClass:read ' + JSON.stringify(this.context));
2042 + Scheduler.unstable_yieldValue(
2043 + 'ShowLocaleClass:read ' + JSON.stringify(this.context),
2044 + );
2045 return this.context.locale;
2046 }
2047 }
2048
2049 function ShowLocaleFn(props, context) {
2152 - ops.push('ShowLocaleFn:read ' + JSON.stringify(context));
2050 + Scheduler.unstable_yieldValue(
2051 + 'ShowLocaleFn:read ' + JSON.stringify(context),
2052 + );
2053 return context.locale;
2054 }
2055 ShowLocaleFn.contextTypes = {
@@ -2165,18 +2065,19 @@ describe('ReactIncremental', () => {
2065 }
2066
2067 function IndirectionFn(props, context) {
2168 - ops.push('IndirectionFn ' + JSON.stringify(context));
2068 + Scheduler.unstable_yieldValue('IndirectionFn ' + JSON.stringify(context));
2069 return props.children;
2070 }
2071
2072 class IndirectionClass extends React.Component {
2073 render() {
2174 - ops.push('IndirectionClass ' + JSON.stringify(this.context));
2074 + Scheduler.unstable_yieldValue(
2075 + 'IndirectionClass ' + JSON.stringify(this.context),
2076 + );
2077 return this.props.children;
2078 }
2079 }
2080
2179 - ops.length = 0;
2081 ReactNoop.render(
2082 <Intl locale="fr">
2083 <IndirectionFn>
@@ -2189,31 +2090,30 @@ describe('ReactIncremental', () => {
2090 </IndirectionFn>
2091 </Intl>,
2092 );
2192 - expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev(
2093 + expect(() =>
2094 + expect(Scheduler).toFlushAndYield([
2095 + 'Intl:read {}',
2096 + 'Intl:provide {"locale":"fr"}',
2097 + 'IndirectionFn {}',
2098 + 'IndirectionClass {}',
2099 + 'ShowLocaleClass:read {"locale":"fr"}',
2100 + 'ShowLocaleFn:read {"locale":"fr"}',
2101 + ]),
2102 + ).toErrorDev(
2103 'Legacy context API has been detected within a strict-mode tree.\n\n' +
2104 'The old API will be supported in all 16.x releases, but applications ' +
2105 'using it should migrate to the new version.\n\n' +
2106 'Please update the following components: Intl, ShowLocaleClass, ShowLocaleFn',
2107 );
2198 - expect(ops).toEqual([
2199 - 'Intl:read {}',
2200 - 'Intl:provide {"locale":"fr"}',
2201 - 'IndirectionFn {}',
2202 - 'IndirectionClass {}',
2203 - 'ShowLocaleClass:read {"locale":"fr"}',
2204 - 'ShowLocaleFn:read {"locale":"fr"}',
2205 - ]);
2108
2207 - ops.length = 0;
2109 statefulInst.setState({x: 1});
2110 expect(Scheduler).toFlushWithoutYielding();
2111 // All work has been memoized because setState()
2112 // happened below the context and could not have affected it.
2212 - expect(ops).toEqual([]);
2113 + expect(Scheduler).toHaveYielded([]);
2114 });
2115
2116 it('reads context when setState is above the provider', () => {
2216 - const ops = [];
2117 let statefulInst;
2118
2119 class Intl extends React.Component {
@@ -2224,11 +2124,15 @@ describe('ReactIncremental', () => {
2124 const childContext = {
2125 locale: this.props.locale,
2126 };
2227 - ops.push('Intl:provide ' + JSON.stringify(childContext));
2127 + Scheduler.unstable_yieldValue(
2128 + 'Intl:provide ' + JSON.stringify(childContext),
2129 + );
2130 return childContext;
2131 }
2132 render() {
2231 - ops.push('Intl:read ' + JSON.stringify(this.context));
2133 + Scheduler.unstable_yieldValue(
2134 + 'Intl:read ' + JSON.stringify(this.context),
2135 + );
2136 return this.props.children;
2137 }
2138 }
@@ -2238,13 +2142,17 @@ describe('ReactIncremental', () => {
2142 locale: PropTypes.string,
2143 };
2144 render() {
2241 - ops.push('ShowLocaleClass:read ' + JSON.stringify(this.context));
2145 + Scheduler.unstable_yieldValue(
2146 + 'ShowLocaleClass:read ' + JSON.stringify(this.context),
2147 + );
2148 return this.context.locale;
2149 }
2150 }
2151
2152 function ShowLocaleFn(props, context) {
2247 - ops.push('ShowLocaleFn:read ' + JSON.stringify(context));
2153 + Scheduler.unstable_yieldValue(
2154 + 'ShowLocaleFn:read ' + JSON.stringify(context),
2155 + );
2156 return context.locale;
2157 }
2158 ShowLocaleFn.contextTypes = {
@@ -2252,13 +2160,15 @@ describe('ReactIncremental', () => {
2160 };
2161
2162 function IndirectionFn(props, context) {
2255 - ops.push('IndirectionFn ' + JSON.stringify(context));
2163 + Scheduler.unstable_yieldValue('IndirectionFn ' + JSON.stringify(context));
2164 return props.children;
2165 }
2166
2167 class IndirectionClass extends React.Component {
2168 render() {
2261 - ops.push('IndirectionClass ' + JSON.stringify(this.context));
2169 + Scheduler.unstable_yieldValue(
2170 + 'IndirectionClass ' + JSON.stringify(this.context),
2171 + );
2172 return this.props.children;
2173 }
2174 }
@@ -2271,7 +2181,6 @@ describe('ReactIncremental', () => {
2181 }
2182 }
2183
2274 - ops.length = 0;
2184 ReactNoop.render(
2185 <Stateful>
2186 <IndirectionFn>
@@ -2282,25 +2191,24 @@ describe('ReactIncremental', () => {
2191 </IndirectionFn>
2192 </Stateful>,
2193 );
2285 - expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev(
2194 + expect(() =>
2195 + expect(Scheduler).toFlushAndYield([
2196 + 'Intl:read {}',
2197 + 'Intl:provide {"locale":"fr"}',
2198 + 'IndirectionFn {}',
2199 + 'IndirectionClass {}',
2200 + 'ShowLocaleClass:read {"locale":"fr"}',
2201 + 'ShowLocaleFn:read {"locale":"fr"}',
2202 + ]),
2203 + ).toErrorDev(
2204 'Legacy context API has been detected within a strict-mode tree.\n\n' +
2205 'The old API will be supported in all 16.x releases, but applications ' +
2206 'using it should migrate to the new version.\n\n' +
2207 'Please update the following components: Intl, ShowLocaleClass, ShowLocaleFn',
2208 );
2291 - expect(ops).toEqual([
2292 - 'Intl:read {}',
2293 - 'Intl:provide {"locale":"fr"}',
2294 - 'IndirectionFn {}',
2295 - 'IndirectionClass {}',
2296 - 'ShowLocaleClass:read {"locale":"fr"}',
2297 - 'ShowLocaleFn:read {"locale":"fr"}',
2298 - ]);
2209
2300 - ops.length = 0;
2210 statefulInst.setState({locale: 'gr'});
2302 - expect(Scheduler).toFlushWithoutYielding();
2303 - expect(ops).toEqual([
2211 + expect(Scheduler).toFlushAndYield([
2212 // Intl is below setState() so it might have been
2213 // affected by it. Therefore we re-render and recompute
2214 // its child context.
@@ -2421,38 +2329,47 @@ describe('ReactIncremental', () => {
2329 });
2330
2331 it('should not recreate masked context unless inputs have changed', () => {
2424 - const ops = [];
2425 -
2332 let scuCounter = 0;
2333
2334 class MyComponent extends React.Component {
2335 static contextTypes = {};
2336 componentDidMount(prevProps, prevState) {
2431 - ops.push('componentDidMount');
2337 + Scheduler.unstable_yieldValue('componentDidMount');
2338 this.setState({setStateInCDU: true});
2339 }
2340 componentDidUpdate(prevProps, prevState) {
2435 - ops.push('componentDidUpdate');
2341 + Scheduler.unstable_yieldValue('componentDidUpdate');
2342 if (this.state.setStateInCDU) {
2343 this.setState({setStateInCDU: false});
2344 }
2345 }
2346 UNSAFE_componentWillReceiveProps(nextProps) {
2441 - ops.push('componentWillReceiveProps');
2347 + Scheduler.unstable_yieldValue('componentWillReceiveProps');
2348 this.setState({setStateInCDU: true});
2349 }
2350 render() {
2445 - ops.push('render');
2351 + Scheduler.unstable_yieldValue('render');
2352 return null;
2353 }
2354 shouldComponentUpdate(nextProps, nextState) {
2449 - ops.push('shouldComponentUpdate');
2355 + Scheduler.unstable_yieldValue('shouldComponentUpdate');
2356 return scuCounter++ < 5; // Don't let test hang
2357 }
2358 }
2359
2360 ReactNoop.render(<MyComponent />);
2455 - expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev(
2361 + expect(() =>
2362 + expect(Scheduler).toFlushAndYield([
2363 + 'render',
2364 + 'componentDidMount',
2365 + 'shouldComponentUpdate',
2366 + 'render',
2367 + 'componentDidUpdate',
2368 + 'shouldComponentUpdate',
2369 + 'render',
2370 + 'componentDidUpdate',
2371 + ]),
2372 + ).toErrorDev(
2373 [
2374 'Using UNSAFE_componentWillReceiveProps in strict mode is not recommended',
2375 'Legacy context API has been detected within a strict-mode tree.\n\n' +
@@ -2462,17 +2379,6 @@ describe('ReactIncremental', () => {
2379 ],
2380 {withoutStack: 1},
2381 );
2465 -
2466 - expect(ops).toEqual([
2467 - 'render',
2468 - 'componentDidMount',
2469 - 'shouldComponentUpdate',
2470 - 'render',
2471 - 'componentDidUpdate',
2472 - 'shouldComponentUpdate',
2473 - 'render',
2474 - 'componentDidUpdate',
2475 - ]);
2382 });
2383
2384 xit('should reuse memoized work if pointers are updated before calling lifecycles', () => {
@@ -2559,7 +2465,6 @@ describe('ReactIncremental', () => {
2465 });
2466
2467 it('updates descendants with new context values', () => {
2562 - const rendered = [];
2468 let instance;
2469
2470 class TopContextProvider extends React.Component {
@@ -2590,7 +2495,7 @@ describe('ReactIncremental', () => {
2495 count: PropTypes.number,
2496 };
2497 render = () => {
2593 - rendered.push(`count:${this.context.count}`);
2498 + Scheduler.unstable_yieldValue(`count:${this.context.count}`);
2499 return null;
2500 };
2501 }
@@ -2603,20 +2508,17 @@ describe('ReactIncremental', () => {
2508 </TopContextProvider>,
2509 );
2510
2606 - expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev(
2511 + expect(() => expect(Scheduler).toFlushAndYield(['count:0'])).toErrorDev(
2512 'Legacy context API has been detected within a strict-mode tree.\n\n' +
2513 'The old API will be supported in all 16.x releases, but applications ' +
2514 'using it should migrate to the new version.\n\n' +
2515 'Please update the following components: Child, TopContextProvider',
2516 );
2612 - expect(rendered).toEqual(['count:0']);
2517 instance.updateCount();
2614 - expect(Scheduler).toFlushWithoutYielding();
2615 - expect(rendered).toEqual(['count:0', 'count:1']);
2518 + expect(Scheduler).toFlushAndYield(['count:1']);
2519 });
2520
2521 it('updates descendants with multiple context-providing ancestors with new context values', () => {
2619 - const rendered = [];
2522 let instance;
2523
2524 class TopContextProvider extends React.Component {
@@ -2653,7 +2555,7 @@ describe('ReactIncremental', () => {
2555 count: PropTypes.number,
2556 };
2557 render = () => {
2656 - rendered.push(`count:${this.context.count}`);
2558 + Scheduler.unstable_yieldValue(`count:${this.context.count}`);
2559 return null;
2560 };
2561 }
@@ -2666,20 +2568,17 @@ describe('ReactIncremental', () => {
2568 </TopContextProvider>,
2569 );
2570
2669 - expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev(
2571 + expect(() => expect(Scheduler).toFlushAndYield(['count:0'])).toErrorDev(
2572 'Legacy context API has been detected within a strict-mode tree.\n\n' +
2573 'The old API will be supported in all 16.x releases, but applications ' +
2574 'using it should migrate to the new version.\n\n' +
2575 'Please update the following components: Child, MiddleContextProvider, TopContextProvider',
2576 );
2675 - expect(rendered).toEqual(['count:0']);
2577 instance.updateCount();
2677 - expect(Scheduler).toFlushWithoutYielding();
2678 - expect(rendered).toEqual(['count:0', 'count:1']);
2578 + expect(Scheduler).toFlushAndYield(['count:1']);
2579 });
2580
2581 it('should not update descendants with new context values if shouldComponentUpdate returns false', () => {
2682 - const rendered = [];
2582 let instance;
2583
2584 class TopContextProvider extends React.Component {
@@ -2723,7 +2622,7 @@ describe('ReactIncremental', () => {
2622 count: PropTypes.number,
2623 };
2624 render = () => {
2726 - rendered.push(`count:${this.context.count}`);
2625 + Scheduler.unstable_yieldValue(`count:${this.context.count}`);
2626 return null;
2627 };
2628 }
@@ -2738,20 +2637,17 @@ describe('ReactIncremental', () => {
2637 </TopContextProvider>,
2638 );
2639
2741 - expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev(
2640 + expect(() => expect(Scheduler).toFlushAndYield(['count:0'])).toErrorDev(
2641 'Legacy context API has been detected within a strict-mode tree.\n\n' +
2642 'The old API will be supported in all 16.x releases, but applications ' +
2643 'using it should migrate to the new version.\n\n' +
2644 'Please update the following components: Child, MiddleContextProvider, TopContextProvider',
2645 );
2747 - expect(rendered).toEqual(['count:0']);
2646 instance.updateCount();
2647 expect(Scheduler).toFlushWithoutYielding();
2750 - expect(rendered).toEqual(['count:0']);
2648 });
2649
2650 it('should update descendants with new context values if setState() is called in the middle of the tree', () => {
2754 - const rendered = [];
2651 let middleInstance;
2652 let topInstance;
2653
@@ -2805,7 +2701,9 @@ describe('ReactIncremental', () => {
2701 name: PropTypes.string,
2702 };
2703 render = () => {
2808 - rendered.push(`count:${this.context.count}, name:${this.context.name}`);
2704 + Scheduler.unstable_yieldValue(
2705 + `count:${this.context.count}, name:${this.context.name}`,
2706 + );
2707 return null;
2708 };
2709 }
@@ -2820,22 +2718,18 @@ describe('ReactIncremental', () => {
2718 </TopContextProvider>,
2719 );
2720
2823 - expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev(
2721 + expect(() =>
2722 + expect(Scheduler).toFlushAndYield(['count:0, name:brian']),
2723 + ).toErrorDev(
2724 'Legacy context API has been detected within a strict-mode tree.\n\n' +
2725 'The old API will be supported in all 16.x releases, but applications ' +
2726 'using it should migrate to the new version.\n\n' +
2727 'Please update the following components: Child, MiddleContextProvider, TopContextProvider',
2728 );
2829 - expect(rendered).toEqual(['count:0, name:brian']);
2729 topInstance.updateCount();
2730 expect(Scheduler).toFlushWithoutYielding();
2832 - expect(rendered).toEqual(['count:0, name:brian']);
2731 middleInstance.updateName('not brian');
2834 - expect(Scheduler).toFlushWithoutYielding();
2835 - expect(rendered).toEqual([
2836 - 'count:0, name:brian',
2837 - 'count:1, name:not brian',
2838 - ]);
2732 + expect(Scheduler).toFlushAndYield(['count:1, name:not brian']);
2733 });
2734
2735 it('does not interrupt for update at same priority', () => {
packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js
+59 -85
@@ -20,7 +20,6 @@ describe('ReactIncrementalErrorHandling', () => {
20 beforeEach(() => {
21 jest.resetModules();
22 ReactFeatureFlags = require('shared/ReactFeatureFlags');
23 - ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
23 ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
24 PropTypes = require('prop-types');
25 React = require('react');
@@ -554,27 +553,26 @@ describe('ReactIncrementalErrorHandling', () => {
553 });
554
555 it('catches render error in a boundary during synchronous mounting', () => {
557 - const ops = [];
556 class ErrorBoundary extends React.Component {
557 state = {error: null};
558 componentDidCatch(error) {
561 - ops.push('ErrorBoundary componentDidCatch');
559 + Scheduler.unstable_yieldValue('ErrorBoundary componentDidCatch');
560 this.setState({error});
561 }
562 render() {
563 if (this.state.error) {
566 - ops.push('ErrorBoundary render error');
564 + Scheduler.unstable_yieldValue('ErrorBoundary render error');
565 return (
566 <span prop={`Caught an error: ${this.state.error.message}.`} />
567 );
568 }
571 - ops.push('ErrorBoundary render success');
569 + Scheduler.unstable_yieldValue('ErrorBoundary render success');
570 return this.props.children;
571 }
572 }
573
574 function BrokenRender(props) {
577 - ops.push('BrokenRender');
575 + Scheduler.unstable_yieldValue('BrokenRender');
576 throw new Error('Hello');
577 }
578
@@ -586,7 +584,7 @@ describe('ReactIncrementalErrorHandling', () => {
584 );
585 });
586
589 - expect(ops).toEqual([
587 + expect(Scheduler).toHaveYielded([
588 'ErrorBoundary render success',
589 'BrokenRender',
590
@@ -602,27 +600,26 @@ describe('ReactIncrementalErrorHandling', () => {
600 });
601
602 it('catches render error in a boundary during batched mounting', () => {
605 - const ops = [];
603 class ErrorBoundary extends React.Component {
604 state = {error: null};
605 componentDidCatch(error) {
609 - ops.push('ErrorBoundary componentDidCatch');
606 + Scheduler.unstable_yieldValue('ErrorBoundary componentDidCatch');
607 this.setState({error});
608 }
609 render() {
610 if (this.state.error) {
614 - ops.push('ErrorBoundary render error');
611 + Scheduler.unstable_yieldValue('ErrorBoundary render error');
612 return (
613 <span prop={`Caught an error: ${this.state.error.message}.`} />
614 );
615 }
619 - ops.push('ErrorBoundary render success');
616 + Scheduler.unstable_yieldValue('ErrorBoundary render success');
617 return this.props.children;
618 }
619 }
620
621 function BrokenRender(props) {
625 - ops.push('BrokenRender');
622 + Scheduler.unstable_yieldValue('BrokenRender');
623 throw new Error('Hello');
624 }
625
@@ -635,7 +632,7 @@ describe('ReactIncrementalErrorHandling', () => {
632 );
633 });
634
638 - expect(ops).toEqual([
635 + expect(Scheduler).toHaveYielded([
636 'ErrorBoundary render success',
637 'BrokenRender',
638
@@ -651,20 +648,19 @@ describe('ReactIncrementalErrorHandling', () => {
648 });
649
650 it('propagates an error from a noop error boundary during full deferred mounting', () => {
654 - const ops = [];
651 class RethrowErrorBoundary extends React.Component {
652 componentDidCatch(error) {
657 - ops.push('RethrowErrorBoundary componentDidCatch');
653 + Scheduler.unstable_yieldValue('RethrowErrorBoundary componentDidCatch');
654 throw error;
655 }
656 render() {
661 - ops.push('RethrowErrorBoundary render');
657 + Scheduler.unstable_yieldValue('RethrowErrorBoundary render');
658 return this.props.children;
659 }
660 }
661
662 function BrokenRender() {
667 - ops.push('BrokenRender');
663 + Scheduler.unstable_yieldValue('BrokenRender');
664 throw new Error('Hello');
665 }
666
@@ -675,19 +671,18 @@ describe('ReactIncrementalErrorHandling', () => {
671 );
672
673 expect(() => {
678 - expect(Scheduler).toFlushWithoutYielding();
679 - }).toThrow('Hello');
680 - expect(ops).toEqual([
681 - 'RethrowErrorBoundary render',
682 - 'BrokenRender',
674 + expect(Scheduler).toFlushAndYield([
675 + 'RethrowErrorBoundary render',
676 + 'BrokenRender',
677
684 - // React retries one more time
685 - 'RethrowErrorBoundary render',
686 - 'BrokenRender',
678 + // React retries one more time
679 + 'RethrowErrorBoundary render',
680 + 'BrokenRender',
681
688 - // Errored again on retry. Now handle it.
689 - 'RethrowErrorBoundary componentDidCatch',
690 - ]);
682 + // Errored again on retry. Now handle it.
683 + 'RethrowErrorBoundary componentDidCatch',
684 + ]);
685 + }).toThrow('Hello');
686 expect(ReactNoop.getChildren()).toEqual([]);
687 });
688
@@ -733,20 +728,19 @@ describe('ReactIncrementalErrorHandling', () => {
728 });
729
730 it('propagates an error from a noop error boundary during synchronous mounting', () => {
736 - const ops = [];
731 class RethrowErrorBoundary extends React.Component {
732 componentDidCatch(error) {
739 - ops.push('RethrowErrorBoundary componentDidCatch');
733 + Scheduler.unstable_yieldValue('RethrowErrorBoundary componentDidCatch');
734 throw error;
735 }
736 render() {
743 - ops.push('RethrowErrorBoundary render');
737 + Scheduler.unstable_yieldValue('RethrowErrorBoundary render');
738 return this.props.children;
739 }
740 }
741
742 function BrokenRender() {
749 - ops.push('BrokenRender');
743 + Scheduler.unstable_yieldValue('BrokenRender');
744 throw new Error('Hello');
745 }
746
@@ -759,7 +753,7 @@ describe('ReactIncrementalErrorHandling', () => {
753 );
754 });
755 }).toThrow('Hello');
762 - expect(ops).toEqual([
756 + expect(Scheduler).toHaveYielded([
757 'RethrowErrorBoundary render',
758 'BrokenRender',
759
@@ -774,20 +768,19 @@ describe('ReactIncrementalErrorHandling', () => {
768 });
769
770 it('propagates an error from a noop error boundary during batched mounting', () => {
777 - const ops = [];
771 class RethrowErrorBoundary extends React.Component {
772 componentDidCatch(error) {
780 - ops.push('RethrowErrorBoundary componentDidCatch');
773 + Scheduler.unstable_yieldValue('RethrowErrorBoundary componentDidCatch');
774 throw error;
775 }
776 render() {
784 - ops.push('RethrowErrorBoundary render');
777 + Scheduler.unstable_yieldValue('RethrowErrorBoundary render');
778 return this.props.children;
779 }
780 }
781
782 function BrokenRender() {
790 - ops.push('BrokenRender');
783 + Scheduler.unstable_yieldValue('BrokenRender');
784 throw new Error('Hello');
785 }
786
@@ -803,7 +796,7 @@ describe('ReactIncrementalErrorHandling', () => {
796 );
797 });
798 }).toThrow('Hello');
806 - expect(ops).toEqual([
799 + expect(Scheduler).toHaveYielded([
800 'RethrowErrorBoundary render',
801 'BrokenRender',
802
@@ -864,15 +857,13 @@ describe('ReactIncrementalErrorHandling', () => {
857 });
858
859 it('can schedule updates after uncaught error in render on mount', () => {
867 - let ops = [];
868 -
860 function BrokenRender() {
870 - ops.push('BrokenRender');
861 + Scheduler.unstable_yieldValue('BrokenRender');
862 throw new Error('Hello');
863 }
864
865 function Foo() {
875 - ops.push('Foo');
866 + Scheduler.unstable_yieldValue('Foo');
867 return null;
868 }
869
@@ -880,23 +871,19 @@ describe('ReactIncrementalErrorHandling', () => {
871 expect(() => {
872 expect(Scheduler).toFlushWithoutYielding();
873 }).toThrow('Hello');
883 - expect(ops).toEqual([
874 + ReactNoop.render(<Foo />);
875 + expect(Scheduler).toHaveYielded([
876 'BrokenRender',
877 // React retries one more time
878 'BrokenRender',
879 // Errored again on retry
880 ]);
889 - ops = [];
890 - ReactNoop.render(<Foo />);
891 - expect(Scheduler).toFlushWithoutYielding();
892 - expect(ops).toEqual(['Foo']);
881 + expect(Scheduler).toFlushAndYield(['Foo']);
882 });
883
884 it('can schedule updates after uncaught error in render on update', () => {
896 - let ops = [];
897 -
885 function BrokenRender(props) {
899 - ops.push('BrokenRender');
886 + Scheduler.unstable_yieldValue('BrokenRender');
887 if (props.throw) {
888 throw new Error('Hello');
889 }
@@ -904,34 +891,29 @@ describe('ReactIncrementalErrorHandling', () => {
891 }
892
893 function Foo() {
907 - ops.push('Foo');
894 + Scheduler.unstable_yieldValue('Foo');
895 return null;
896 }
897
898 ReactNoop.render(<BrokenRender throw={false} />);
912 - expect(Scheduler).toFlushWithoutYielding();
913 - ops = [];
899 + expect(Scheduler).toFlushAndYield(['BrokenRender']);
900
901 expect(() => {
902 ReactNoop.render(<BrokenRender throw={true} />);
903 expect(Scheduler).toFlushWithoutYielding();
904 }).toThrow('Hello');
919 - expect(ops).toEqual([
905 + expect(Scheduler).toHaveYielded([
906 'BrokenRender',
907 // React retries one more time
908 'BrokenRender',
909 // Errored again on retry
910 ]);
911
926 - ops = [];
912 ReactNoop.render(<Foo />);
928 - expect(Scheduler).toFlushWithoutYielding();
929 - expect(ops).toEqual(['Foo']);
913 + expect(Scheduler).toFlushAndYield(['Foo']);
914 });
915
916 it('can schedule updates after uncaught error during umounting', () => {
933 - let ops = [];
934 -
917 class BrokenComponentWillUnmount extends React.Component {
918 render() {
919 return <div />;
@@ -942,7 +924,7 @@ describe('ReactIncrementalErrorHandling', () => {
924 }
925
926 function Foo() {
945 - ops.push('Foo');
927 + Scheduler.unstable_yieldValue('Foo');
928 return null;
929 }
930
@@ -954,10 +936,8 @@ describe('ReactIncrementalErrorHandling', () => {
936 expect(Scheduler).toFlushWithoutYielding();
937 }).toThrow('Hello');
938
957 - ops = [];
939 ReactNoop.render(<Foo />);
959 - expect(Scheduler).toFlushWithoutYielding();
960 - expect(ops).toEqual(['Foo']);
940 + expect(Scheduler).toFlushAndYield(['Foo']);
941 });
942
943 it('should not attempt to recover an unmounting error boundary', () => {
@@ -1342,13 +1322,14 @@ describe('ReactIncrementalErrorHandling', () => {
1322 });
1323
1324 it('unmounts components with uncaught errors', () => {
1345 - const ops = [];
1325 let inst;
1326
1327 class BrokenRenderAndUnmount extends React.Component {
1328 state = {fail: false};
1329 componentWillUnmount() {
1351 - ops.push('BrokenRenderAndUnmount componentWillUnmount');
1330 + Scheduler.unstable_yieldValue(
1331 + 'BrokenRenderAndUnmount componentWillUnmount',
1332 + );
1333 }
1334 render() {
1335 inst = this;
@@ -1361,7 +1342,7 @@ describe('ReactIncrementalErrorHandling', () => {
1342
1343 class Parent extends React.Component {
1344 componentWillUnmount() {
1364 - ops.push('Parent componentWillUnmount [!]');
1345 + Scheduler.unstable_yieldValue('Parent componentWillUnmount [!]');
1346 throw new Error('One does not simply unmount me.');
1347 }
1348 render() {
@@ -1383,7 +1364,7 @@ describe('ReactIncrementalErrorHandling', () => {
1364 expect(Scheduler).toFlushWithoutYielding();
1365 }).toThrowError('Hello.');
1366
1386 - expect(ops).toEqual([
1367 + expect(Scheduler).toHaveYielded([
1368 // Attempt to clean up.
1369 // Errors in parents shouldn't stop children from unmounting.
1370 'Parent componentWillUnmount [!]',
@@ -1394,11 +1375,9 @@ describe('ReactIncrementalErrorHandling', () => {
1375 });
1376
1377 it('does not interrupt unmounting if detaching a ref throws', () => {
1397 - let ops = [];
1398 -
1378 class Bar extends React.Component {
1379 componentWillUnmount() {
1401 - ops.push('Bar unmount');
1380 + Scheduler.unstable_yieldValue('Bar unmount');
1381 }
1382 render() {
1383 return <span prop="Bar" />;
@@ -1407,10 +1386,10 @@ describe('ReactIncrementalErrorHandling', () => {
1386
1387 function barRef(inst) {
1388 if (inst === null) {
1410 - ops.push('barRef detach');
1389 + Scheduler.unstable_yieldValue('barRef detach');
1390 throw new Error('Detach error');
1391 }
1413 - ops.push('barRef attach');
1392 + Scheduler.unstable_yieldValue('barRef attach');
1393 }
1394
1395 function Foo(props) {
@@ -1418,16 +1397,13 @@ describe('ReactIncrementalErrorHandling', () => {
1397 }
1398
1399 ReactNoop.render(<Foo />);
1421 - expect(Scheduler).toFlushWithoutYielding();
1422 - expect(ops).toEqual(['barRef attach']);
1400 + expect(Scheduler).toFlushAndYield(['barRef attach']);
1401 expect(ReactNoop.getChildren()).toEqual([div(span('Bar'))]);
1402
1425 - ops = [];
1426 -
1403 // Unmount
1404 ReactNoop.render(<Foo hide={true} />);
1405 expect(Scheduler).toFlushAndThrow('Detach error');
1430 - expect(ops).toEqual([
1406 + expect(Scheduler).toHaveYielded([
1407 'barRef detach',
1408 // Bar should unmount even though its ref threw an error while detaching
1409 'Bar unmount',
@@ -1451,37 +1427,36 @@ describe('ReactIncrementalErrorHandling', () => {
1427 it('error boundaries capture non-errors', () => {
1428 spyOnProd(console, 'error');
1429 spyOnDev(console, 'error');
1454 - const ops = [];
1430
1431 class ErrorBoundary extends React.Component {
1432 state = {error: null};
1433 componentDidCatch(error) {
1434 // Should not be called
1460 - ops.push('componentDidCatch');
1435 + Scheduler.unstable_yieldValue('componentDidCatch');
1436 this.setState({error});
1437 }
1438 render() {
1439 if (this.state.error) {
1465 - ops.push('ErrorBoundary (catch)');
1440 + Scheduler.unstable_yieldValue('ErrorBoundary (catch)');
1441 return (
1442 <span
1443 prop={`Caught an error: ${this.state.error.nonStandardMessage}`}
1444 />
1445 );
1446 }
1472 - ops.push('ErrorBoundary (try)');
1447 + Scheduler.unstable_yieldValue('ErrorBoundary (try)');
1448 return this.props.children;
1449 }
1450 }
1451
1452 function Indirection(props) {
1478 - ops.push('Indirection');
1453 + Scheduler.unstable_yieldValue('Indirection');
1454 return props.children;
1455 }
1456
1457 const notAnError = {nonStandardMessage: 'oops'};
1458 function BadRender() {
1484 - ops.push('BadRender');
1459 + Scheduler.unstable_yieldValue('BadRender');
1460 throw notAnError;
1461 }
1462
@@ -1492,9 +1467,8 @@ describe('ReactIncrementalErrorHandling', () => {
1467 </Indirection>
1468 </ErrorBoundary>,
1469 );
1495 - expect(Scheduler).toFlushWithoutYielding();
1470
1497 - expect(ops).toEqual([
1471 + expect(Scheduler).toFlushAndYield([
1472 'ErrorBoundary (try)',
1473 'Indirection',
1474 'BadRender',
packages/react-reconciler/src/__tests__/ReactIncrementalReflection-test.js renamed
packages/react-reconciler/src/__tests__/ReactIncrementalScheduling-test.js renamed
+9 -15
@@ -11,15 +11,13 @@
11 'use strict';
12
13 let React;
14 -let ReactFeatureFlags;
14 let ReactNoop;
15 let Scheduler;
16
17 describe('ReactIncrementalScheduling', () => {
18 beforeEach(() => {
19 jest.resetModules();
21 - ReactFeatureFlags = require('shared/ReactFeatureFlags');
22 - ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
20 +
21 React = require('react');
22 ReactNoop = require('react-noop-renderer');
23 Scheduler = require('scheduler');
@@ -324,11 +322,10 @@ describe('ReactIncrementalScheduling', () => {
322
323 it('nested updates are always deferred, even inside unbatchedUpdates', () => {
324 let instance;
327 - const ops = [];
325 class Foo extends React.Component {
326 state = {step: 0};
327 componentDidUpdate() {
331 - ops.push('componentDidUpdate: ' + this.state.step);
328 + Scheduler.unstable_yieldValue('componentDidUpdate: ' + this.state.step);
329 if (this.state.step === 1) {
330 ReactNoop.unbatchedUpdates(() => {
331 // This is a nested state update, so it should not be
@@ -336,30 +333,27 @@ describe('ReactIncrementalScheduling', () => {
333 // in unbatchedUpdates.
334 this.setState({step: 2});
335 });
336 + expect(Scheduler).toHaveYielded([
337 + 'render: 1',
338 + 'componentDidUpdate: 1',
339 + ]);
340 expect(ReactNoop).toMatchRenderedOutput(<span prop={1} />);
341 }
342 }
343 render() {
343 - ops.push('render: ' + this.state.step);
344 + Scheduler.unstable_yieldValue('render: ' + this.state.step);
345 instance = this;
346 return <span prop={this.state.step} />;
347 }
348 }
349 ReactNoop.render(<Foo />);
349 - expect(Scheduler).toFlushWithoutYielding();
350 + expect(Scheduler).toFlushAndYield(['render: 0']);
351 expect(ReactNoop).toMatchRenderedOutput(<span prop={0} />);
352
353 ReactNoop.flushSync(() => {
354 instance.setState({step: 1});
355 });
356 + expect(Scheduler).toHaveYielded(['render: 2', 'componentDidUpdate: 2']);
357 expect(ReactNoop).toMatchRenderedOutput(<span prop={2} />);
356 -
357 - expect(ops).toEqual([
358 - 'render: 0',
359 - 'render: 1',
360 - 'componentDidUpdate: 1',
361 - 'render: 2',
362 - 'componentDidUpdate: 2',
363 - ]);
358 });
359 });
packages/react-reconciler/src/__tests__/ReactIncrementalSideEffects-test.js renamed
packages/react-reconciler/src/__tests__/ReactIncrementalTriangle-test.js renamed
+9 -9
@@ -11,15 +11,13 @@
11 'use strict';
12
13 let React;
14 -let ReactFeatureFlags;
14 let ReactNoop;
15 let Scheduler;
16
17 describe('ReactIncrementalTriangle', () => {
18 beforeEach(() => {
19 jest.resetModules();
21 - ReactFeatureFlags = require('shared/ReactFeatureFlags');
22 - ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
20 +
21 React = require('react');
22 ReactNoop = require('react-noop-renderer');
23 Scheduler = require('scheduler');
@@ -184,12 +182,6 @@ describe('ReactIncrementalTriangle', () => {
182 class Triangle extends React.Component {
183 constructor(props) {
184 super();
187 - this.index = triangles.length;
188 - triangles.push(this);
189 - if (props.remainingDepth === 0) {
190 - this.leafIndex = leafTriangles.length;
191 - leafTriangles.push(this);
192 - }
185 this.state = {isActive: false};
186 this.child = React.createRef(null);
187 }
@@ -206,6 +198,14 @@ describe('ReactIncrementalTriangle', () => {
198 this.state.isActive !== nextState.isActive
199 );
200 }
201 + componentDidMount() {
202 + this.index = triangles.length;
203 + triangles.push(this);
204 + if (this.props.remainingDepth === 0) {
205 + this.leafIndex = leafTriangles.length;
206 + leafTriangles.push(this);
207 + }
208 + }
209 componentDidUpdate() {
210 if (this.child.current !== null) {
211 const {prop: currentCounter} = JSON.parse(this.child.current.prop);
packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js renamed
+31 -46
@@ -11,15 +11,13 @@
11 'use strict';
12
13 let React;
14 -let ReactFeatureFlags;
14 let ReactNoop;
15 let Scheduler;
16
17 describe('ReactIncrementalUpdates', () => {
18 beforeEach(() => {
19 jest.resetModuleRegistry();
21 - ReactFeatureFlags = require('shared/ReactFeatureFlags');
22 - ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
20 +
21 React = require('react');
22 ReactNoop = require('react-noop-renderer');
23 Scheduler = require('scheduler');
@@ -79,24 +77,23 @@ describe('ReactIncrementalUpdates', () => {
77
78 it('only drops updates with equal or lesser priority when replaceState is called', () => {
79 let instance;
82 - let ops = [];
80 class Foo extends React.Component {
81 state = {};
82 componentDidMount() {
86 - ops.push('componentDidMount');
83 + Scheduler.unstable_yieldValue('componentDidMount');
84 }
85 componentDidUpdate() {
89 - ops.push('componentDidUpdate');
86 + Scheduler.unstable_yieldValue('componentDidUpdate');
87 }
88 render() {
92 - ops.push('render');
89 + Scheduler.unstable_yieldValue('render');
90 instance = this;
91 return <div />;
92 }
93 }
94
95 ReactNoop.render(<Foo />);
99 - expect(Scheduler).toFlushWithoutYielding();
96 + expect(Scheduler).toFlushAndYield(['render', 'componentDidMount']);
97
98 ReactNoop.flushSync(() => {
99 ReactNoop.deferredUpdates(() => {
@@ -114,19 +111,11 @@ describe('ReactIncrementalUpdates', () => {
111 // Even though a replaceState has been already scheduled, it hasn't been
112 // flushed yet because it has async priority.
113 expect(instance.state).toEqual({a: 'a', b: 'b'});
117 - expect(ops).toEqual([
118 - 'render',
119 - 'componentDidMount',
120 - 'render',
121 - 'componentDidUpdate',
122 - ]);
123 -
124 - ops = [];
114 + expect(Scheduler).toHaveYielded(['render', 'componentDidUpdate']);
115
126 - expect(Scheduler).toFlushWithoutYielding();
116 + expect(Scheduler).toFlushAndYield(['render', 'componentDidUpdate']);
117 // Now the rest of the updates are flushed, including the replaceState.
118 expect(instance.state).toEqual({c: 'c', d: 'd'});
129 - expect(ops).toEqual(['render', 'componentDidUpdate']);
119 });
120
121 it('can abort an update, schedule additional updates, and resume', () => {
@@ -298,27 +287,25 @@ describe('ReactIncrementalUpdates', () => {
287 });
288
289 it('does not call callbacks that are scheduled by another callback until a later commit', () => {
301 - const ops = [];
290 class Foo extends React.Component {
291 state = {};
292 componentDidMount() {
305 - ops.push('did mount');
293 + Scheduler.unstable_yieldValue('did mount');
294 this.setState({a: 'a'}, () => {
307 - ops.push('callback a');
295 + Scheduler.unstable_yieldValue('callback a');
296 this.setState({b: 'b'}, () => {
309 - ops.push('callback b');
297 + Scheduler.unstable_yieldValue('callback b');
298 });
299 });
300 }
301 render() {
314 - ops.push('render');
302 + Scheduler.unstable_yieldValue('render');
303 return <div />;
304 }
305 }
306
307 ReactNoop.render(<Foo />);
320 - expect(Scheduler).toFlushWithoutYielding();
321 - expect(ops).toEqual([
308 + expect(Scheduler).toFlushAndYield([
309 'render',
310 'did mount',
311 'render',
@@ -330,30 +317,27 @@ describe('ReactIncrementalUpdates', () => {
317
318 it('gives setState during reconciliation the same priority as whatever level is currently reconciling', () => {
319 let instance;
333 - let ops = [];
320
321 class Foo extends React.Component {
322 state = {};
323 UNSAFE_componentWillReceiveProps() {
338 - ops.push('componentWillReceiveProps');
324 + Scheduler.unstable_yieldValue('componentWillReceiveProps');
325 this.setState({b: 'b'});
326 }
327 render() {
342 - ops.push('render');
328 + Scheduler.unstable_yieldValue('render');
329 instance = this;
330 return <div />;
331 }
332 }
333 ReactNoop.render(<Foo />);
334 expect(() =>
349 - expect(Scheduler).toFlushWithoutYielding(),
335 + expect(Scheduler).toFlushAndYield(['render']),
336 ).toErrorDev(
337 'Using UNSAFE_componentWillReceiveProps in strict mode is not recommended',
338 {withoutStack: true},
339 );
340
355 - ops = [];
356 -
341 ReactNoop.flushSync(() => {
342 instance.setState({a: 'a'});
343
@@ -361,44 +345,45 @@ describe('ReactIncrementalUpdates', () => {
345 });
346
347 expect(instance.state).toEqual({a: 'a', b: 'b'});
364 - expect(ops).toEqual(['componentWillReceiveProps', 'render']);
348 + expect(Scheduler).toHaveYielded(['componentWillReceiveProps', 'render']);
349 });
350
351 it('enqueues setState inside an updater function as if the in-progress update is progressed (and warns)', () => {
352 let instance;
369 - const ops = [];
353 class Foo extends React.Component {
354 state = {};
355 render() {
373 - ops.push('render');
356 + Scheduler.unstable_yieldValue('render');
357 instance = this;
358 return <div />;
359 }
360 }
361
362 ReactNoop.render(<Foo />);
380 - expect(Scheduler).toFlushWithoutYielding();
363 + expect(Scheduler).toFlushAndYield([
364 + // Initial render
365 + 'render',
366 + ]);
367
368 instance.setState(function a() {
383 - ops.push('setState updater');
369 + Scheduler.unstable_yieldValue('setState updater');
370 this.setState({b: 'b'});
371 return {a: 'a'};
372 });
373
388 - expect(() => expect(Scheduler).toFlushWithoutYielding()).toErrorDev(
374 + expect(() =>
375 + expect(Scheduler).toFlushAndYield([
376 + 'setState updater',
377 + // Update b is enqueued with the same priority as update a, so it should
378 + // be flushed in the same commit.
379 + 'render',
380 + ]),
381 + ).toErrorDev(
382 'An update (setState, replaceState, or forceUpdate) was scheduled ' +
383 'from inside an update function. Update functions should be pure, ' +
384 'with zero side-effects. Consider using componentDidUpdate or a ' +
385 'callback.',
386 );
394 - expect(ops).toEqual([
395 - // Initial render
396 - 'render',
397 - 'setState updater',
398 - // Update b is enqueued with the same priority as update a, so it should
399 - // be flushed in the same commit.
400 - 'render',
401 - ]);
387 expect(instance.state).toEqual({a: 'a', b: 'b'});
388
389 // Test deduplication (no additional warnings expected)
@@ -406,7 +391,7 @@ describe('ReactIncrementalUpdates', () => {
391 this.setState({a: 'a'});
392 return {b: 'b'};
393 });
409 - expect(Scheduler).toFlushWithoutYielding();
394 + expect(Scheduler).toFlushAndYield(['render']);
395 });
396
397 it('getDerivedStateFromProps should update base state of updateQueue (based on product bug)', () => {
packages/react-reconciler/src/__tests__/ReactMemo-test.js renamed
packages/react-reconciler/src/__tests__/ReactNewContext-test.js renamed
+1 -1
@@ -1219,7 +1219,7 @@ describe('ReactNewContext', () => {
1219 spyOnDev(console, 'error');
1220 const Context = React.createContext(0);
1221 ReactNoop.render(<Context.Consumer />);
1222 - expect(Scheduler).toFlushAndThrow('render is not a function');
1222 + expect(Scheduler).toFlushAndThrow('is not a function');
1223 if (__DEV__) {
1224 expect(console.error.calls.argsFor(0)[0]).toContain(
1225 'A context consumer was rendered with multiple children, or a child ' +
packages/react-reconciler/src/__tests__/ReactSchedulerIntegration-test.js renamed
packages/react-test-renderer/src/__tests__/ReactTestRendererAsync-test.js renamed
packages/use-subscription/src/__tests__/useSubscription-test.js renamed