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 ' +