Skip to content

Commit 147bdef

Browse files
authored
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.
1 parent b014e2d commit 147bdef

21 files changed

+493
-714
lines changed

packages/react-dom/src/events/__tests__/ChangeEventPlugin-test.internal.js renamed to packages/react-dom/src/events/__tests__/ChangeEventPlugin-test.js

+24-52
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,7 @@ describe('ChangeEventPlugin', () => {
470470
beforeEach(() => {
471471
jest.resetModules();
472472
ReactFeatureFlags = require('shared/ReactFeatureFlags');
473-
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
473+
474474
React = require('react');
475475
ReactDOM = require('react-dom');
476476
TestUtils = require('react-dom/test-utils');
@@ -481,13 +481,11 @@ describe('ChangeEventPlugin', () => {
481481
const root = ReactDOM.createRoot(container);
482482
let input;
483483

484-
let ops = [];
485-
486484
class ControlledInput extends React.Component {
487485
state = {value: 'initial'};
488486
onChange = event => this.setState({value: event.target.value});
489487
render() {
490-
ops.push(`render: ${this.state.value}`);
488+
Scheduler.unstable_yieldValue(`render: ${this.state.value}`);
491489
const controlledValue =
492490
this.state.value === 'changed' ? 'changed [!]' : this.state.value;
493491
return (
@@ -504,22 +502,19 @@ describe('ChangeEventPlugin', () => {
504502
// Initial mount. Test that this is async.
505503
root.render(<ControlledInput />);
506504
// Should not have flushed yet.
507-
expect(ops).toEqual([]);
505+
expect(Scheduler).toHaveYielded([]);
508506
expect(input).toBe(undefined);
509507
// Flush callbacks.
510-
Scheduler.unstable_flushAll();
511-
expect(ops).toEqual(['render: initial']);
508+
expect(Scheduler).toFlushAndYield(['render: initial']);
512509
expect(input.value).toBe('initial');
513510

514-
ops = [];
515-
516511
// Trigger a change event.
517512
setUntrackedValue.call(input, 'changed');
518513
input.dispatchEvent(
519514
new Event('input', {bubbles: true, cancelable: true}),
520515
);
521516
// Change should synchronously flush
522-
expect(ops).toEqual(['render: changed']);
517+
expect(Scheduler).toHaveYielded(['render: changed']);
523518
// Value should be the controlled value, not the original one
524519
expect(input.value).toBe('changed [!]');
525520
});
@@ -528,15 +523,13 @@ describe('ChangeEventPlugin', () => {
528523
const root = ReactDOM.createRoot(container);
529524
let input;
530525

531-
let ops = [];
532-
533526
class ControlledInput extends React.Component {
534527
state = {checked: false};
535528
onChange = event => {
536529
this.setState({checked: event.target.checked});
537530
};
538531
render() {
539-
ops.push(`render: ${this.state.checked}`);
532+
Scheduler.unstable_yieldValue(`render: ${this.state.checked}`);
540533
const controlledValue = this.props.reverse
541534
? !this.state.checked
542535
: this.state.checked;
@@ -554,49 +547,42 @@ describe('ChangeEventPlugin', () => {
554547
// Initial mount. Test that this is async.
555548
root.render(<ControlledInput reverse={false} />);
556549
// Should not have flushed yet.
557-
expect(ops).toEqual([]);
550+
expect(Scheduler).toHaveYielded([]);
558551
expect(input).toBe(undefined);
559552
// Flush callbacks.
560-
Scheduler.unstable_flushAll();
561-
expect(ops).toEqual(['render: false']);
553+
expect(Scheduler).toFlushAndYield(['render: false']);
562554
expect(input.checked).toBe(false);
563555

564-
ops = [];
565-
566556
// Trigger a change event.
567557
input.dispatchEvent(
568558
new MouseEvent('click', {bubbles: true, cancelable: true}),
569559
);
570560
// Change should synchronously flush
571-
expect(ops).toEqual(['render: true']);
561+
expect(Scheduler).toHaveYielded(['render: true']);
572562
expect(input.checked).toBe(true);
573563

574564
// Now let's make sure we're using the controlled value.
575565
root.render(<ControlledInput reverse={true} />);
576-
Scheduler.unstable_flushAll();
577-
578-
ops = [];
566+
expect(Scheduler).toFlushAndYield(['render: true']);
579567

580568
// Trigger another change event.
581569
input.dispatchEvent(
582570
new MouseEvent('click', {bubbles: true, cancelable: true}),
583571
);
584572
// Change should synchronously flush
585-
expect(ops).toEqual(['render: true']);
573+
expect(Scheduler).toHaveYielded(['render: true']);
586574
expect(input.checked).toBe(false);
587575
});
588576

589577
it.experimental('textarea', () => {
590578
const root = ReactDOM.createRoot(container);
591579
let textarea;
592580

593-
let ops = [];
594-
595581
class ControlledTextarea extends React.Component {
596582
state = {value: 'initial'};
597583
onChange = event => this.setState({value: event.target.value});
598584
render() {
599-
ops.push(`render: ${this.state.value}`);
585+
Scheduler.unstable_yieldValue(`render: ${this.state.value}`);
600586
const controlledValue =
601587
this.state.value === 'changed' ? 'changed [!]' : this.state.value;
602588
return (
@@ -613,22 +599,19 @@ describe('ChangeEventPlugin', () => {
613599
// Initial mount. Test that this is async.
614600
root.render(<ControlledTextarea />);
615601
// Should not have flushed yet.
616-
expect(ops).toEqual([]);
602+
expect(Scheduler).toHaveYielded([]);
617603
expect(textarea).toBe(undefined);
618604
// Flush callbacks.
619-
Scheduler.unstable_flushAll();
620-
expect(ops).toEqual(['render: initial']);
605+
expect(Scheduler).toFlushAndYield(['render: initial']);
621606
expect(textarea.value).toBe('initial');
622607

623-
ops = [];
624-
625608
// Trigger a change event.
626609
setUntrackedTextareaValue.call(textarea, 'changed');
627610
textarea.dispatchEvent(
628611
new Event('input', {bubbles: true, cancelable: true}),
629612
);
630613
// Change should synchronously flush
631-
expect(ops).toEqual(['render: changed']);
614+
expect(Scheduler).toHaveYielded(['render: changed']);
632615
// Value should be the controlled value, not the original one
633616
expect(textarea.value).toBe('changed [!]');
634617
});
@@ -637,13 +620,11 @@ describe('ChangeEventPlugin', () => {
637620
const root = ReactDOM.createRoot(container);
638621
let input;
639622

640-
let ops = [];
641-
642623
class ControlledInput extends React.Component {
643624
state = {value: 'initial'};
644625
onChange = event => this.setState({value: event.target.value});
645626
render() {
646-
ops.push(`render: ${this.state.value}`);
627+
Scheduler.unstable_yieldValue(`render: ${this.state.value}`);
647628
const controlledValue =
648629
this.state.value === 'changed' ? 'changed [!]' : this.state.value;
649630
return (
@@ -664,22 +645,19 @@ describe('ChangeEventPlugin', () => {
664645
// Initial mount. Test that this is async.
665646
root.render(<ControlledInput />);
666647
// Should not have flushed yet.
667-
expect(ops).toEqual([]);
648+
expect(Scheduler).toHaveYielded([]);
668649
expect(input).toBe(undefined);
669650
// Flush callbacks.
670-
Scheduler.unstable_flushAll();
671-
expect(ops).toEqual(['render: initial']);
651+
expect(Scheduler).toFlushAndYield(['render: initial']);
672652
expect(input.value).toBe('initial');
673653

674-
ops = [];
675-
676654
// Trigger a change event.
677655
setUntrackedValue.call(input, 'changed');
678656
input.dispatchEvent(
679657
new Event('input', {bubbles: true, cancelable: true}),
680658
);
681659
// Change should synchronously flush
682-
expect(ops).toEqual(['render: changed']);
660+
expect(Scheduler).toHaveYielded(['render: changed']);
683661
// Value should be the controlled value, not the original one
684662
expect(input.value).toBe('changed [!]');
685663
});
@@ -688,16 +666,14 @@ describe('ChangeEventPlugin', () => {
688666
const root = ReactDOM.createRoot(container);
689667
let input;
690668

691-
let ops = [];
692-
693669
class ControlledInput extends React.Component {
694670
state = {value: 'initial'};
695671
onChange = event => this.setState({value: event.target.value});
696672
reset = () => {
697673
this.setState({value: ''});
698674
};
699675
render() {
700-
ops.push(`render: ${this.state.value}`);
676+
Scheduler.unstable_yieldValue(`render: ${this.state.value}`);
701677
const controlledValue =
702678
this.state.value === 'changed' ? 'changed [!]' : this.state.value;
703679
return (
@@ -715,27 +691,23 @@ describe('ChangeEventPlugin', () => {
715691
// Initial mount. Test that this is async.
716692
root.render(<ControlledInput />);
717693
// Should not have flushed yet.
718-
expect(ops).toEqual([]);
694+
expect(Scheduler).toHaveYielded([]);
719695
expect(input).toBe(undefined);
720696
// Flush callbacks.
721-
Scheduler.unstable_flushAll();
722-
expect(ops).toEqual(['render: initial']);
697+
expect(Scheduler).toFlushAndYield(['render: initial']);
723698
expect(input.value).toBe('initial');
724699

725-
ops = [];
726-
727700
// Trigger a click event
728701
input.dispatchEvent(
729702
new Event('click', {bubbles: true, cancelable: true}),
730703
);
731704
// Nothing should have changed
732-
expect(ops).toEqual([]);
705+
expect(Scheduler).toHaveYielded([]);
733706
expect(input.value).toBe('initial');
734707

735708
// Flush callbacks.
736-
Scheduler.unstable_flushAll();
737709
// Now the click update has flushed.
738-
expect(ops).toEqual(['render: ']);
710+
expect(Scheduler).toFlushAndYield(['render: ']);
739711
expect(input.value).toBe('');
740712
});
741713

packages/react-dom/src/events/__tests__/SimpleEventPlugin-test.internal.js renamed to packages/react-dom/src/events/__tests__/SimpleEventPlugin-test.js

+11-24
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
describe('SimpleEventPlugin', function() {
1313
let React;
1414
let ReactDOM;
15-
let ReactFeatureFlags;
1615
let Scheduler;
1716

1817
let onClick;
@@ -184,7 +183,6 @@ describe('SimpleEventPlugin', function() {
184183
container = document.createElement('div');
185184
document.body.appendChild(container);
186185

187-
const ops = [];
188186
let button;
189187
class Button extends React.Component {
190188
state = {count: 0};
@@ -193,7 +191,7 @@ describe('SimpleEventPlugin', function() {
193191
count: state.count + 1,
194192
}));
195193
componentDidUpdate() {
196-
ops.push(`didUpdate - Count: ${this.state.count}`);
194+
Scheduler.unstable_yieldValue(`didUpdate - Count: ${this.state.count}`);
197195
}
198196
render() {
199197
return (
@@ -221,20 +219,19 @@ describe('SimpleEventPlugin', function() {
221219

222220
ReactDOM.render(<Button />, container);
223221
expect(button.textContent).toEqual('Count: 0');
224-
expect(ops).toEqual([]);
222+
expect(Scheduler).toHaveYielded([]);
225223

226224
click();
227225

228226
// There should be exactly one update.
229-
expect(ops).toEqual(['didUpdate - Count: 3']);
227+
expect(Scheduler).toHaveYielded(['didUpdate - Count: 3']);
230228
expect(button.textContent).toEqual('Count: 3');
231229
});
232230

233231
describe('interactive events, in concurrent mode', () => {
234232
beforeEach(() => {
235233
jest.resetModules();
236-
ReactFeatureFlags = require('shared/ReactFeatureFlags');
237-
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
234+
238235
ReactDOM = require('react-dom');
239236
Scheduler = require('scheduler');
240237
});
@@ -246,19 +243,17 @@ describe('SimpleEventPlugin', function() {
246243
const root = ReactDOM.createRoot(container);
247244
document.body.appendChild(container);
248245

249-
let ops = [];
250-
251246
let button;
252247
class Button extends React.Component {
253248
state = {disabled: false};
254249
onClick = () => {
255250
// Perform some side-effect
256-
ops.push('Side-effect');
251+
Scheduler.unstable_yieldValue('Side-effect');
257252
// Disable the button
258253
this.setState({disabled: true});
259254
};
260255
render() {
261-
ops.push(
256+
Scheduler.unstable_yieldValue(
262257
`render button: ${this.state.disabled ? 'disabled' : 'enabled'}`,
263258
);
264259
return (
@@ -274,13 +269,10 @@ describe('SimpleEventPlugin', function() {
274269
// Initial mount
275270
root.render(<Button />);
276271
// Should not have flushed yet because it's async
277-
expect(ops).toEqual([]);
272+
expect(Scheduler).toHaveYielded([]);
278273
expect(button).toBe(undefined);
279274
// Flush async work
280-
Scheduler.unstable_flushAll();
281-
expect(ops).toEqual(['render button: enabled']);
282-
283-
ops = [];
275+
expect(Scheduler).toFlushAndYield(['render button: enabled']);
284276

285277
function click() {
286278
button.dispatchEvent(
@@ -290,35 +282,30 @@ describe('SimpleEventPlugin', function() {
290282

291283
// Click the button to trigger the side-effect
292284
click();
293-
expect(ops).toEqual([
285+
expect(Scheduler).toHaveYielded([
294286
// The handler fired
295287
'Side-effect',
296288
// but the component did not re-render yet, because it's async
297289
]);
298290

299-
ops = [];
300-
301291
// Click the button again
302292
click();
303-
expect(ops).toEqual([
293+
expect(Scheduler).toHaveYielded([
304294
// Before handling this second click event, the previous interactive
305295
// update is flushed
306296
'render button: disabled',
307297
// The event handler was removed from the button, so there's no second
308298
// side-effect
309299
]);
310300

311-
ops = [];
312-
313301
// The handler should not fire again no matter how many times we
314302
// click the handler.
315303
click();
316304
click();
317305
click();
318306
click();
319307
click();
320-
Scheduler.unstable_flushAll();
321-
expect(ops).toEqual([]);
308+
expect(Scheduler).toFlushAndYield([]);
322309
},
323310
);
324311

0 commit comments

Comments
 (0)