Skip to content

Commit 53e787b

Browse files
authored
Replace noop's fake Scheduler implementation with mock Scheduler build (#14969)
* Replace noop's fake Scheduler implementation with mock Scheduler build The noop renderer has its own mock implementation of the Scheduler interface, with the ability to partially render work in tests. Now that this functionality has been lifted into a proper mock Scheduler build, we can use that instead. Most of the existing noop tests were unaffected, but I did have to make some changes. The biggest one involved passive effects: previously, they were scheduled on a separate queue from the queue that handles rendering. After this change, both rendering and effects are scheduled in the Scheduler queue. I think this is a better approach because tests no longer have to worry about the difference; if you call `flushAll`, all the work is flushed, both rendering and effects. But for those few tests that do care to flush the rendering without the effects, that's still possible using the `yieldValue` API. Follow-up: Do the same for test renderer. * Fix import to scheduler/unstable_mock
1 parent 3ada82b commit 53e787b

14 files changed

Lines changed: 479 additions & 470 deletions

‎packages/react-noop-renderer/src/createReactNoop.js‎

Lines changed: 19 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import type {Fiber} from 'react-reconciler/src/ReactFiber';
1818
importtype{UpdateQueue}from'react-reconciler/src/ReactUpdateQueue';
1919
importtype{ReactNodeList}from'shared/ReactTypes';
2020

21+
import*asSchedulerfrom'scheduler/unstable_mock';
2122
import{createPortal}from'shared/ReactPortal';
2223
importexpectfrom'expect';
2324
import{REACT_FRAGMENT_TYPE,REACT_ELEMENT_TYPE}from'shared/ReactSymbols';
@@ -51,9 +52,6 @@ if (__DEV__) {
5152
}
5253

5354
functioncreateReactNoop(reconciler: Function,useMutation: boolean){
54-
letscheduledCallback=null;
55-
letscheduledCallbackTimeout=-1;
56-
letscheduledPassiveCallback=null;
5755
letinstanceCounter=0;
5856
lethostDiffCounter=0;
5957
lethostUpdateCounter=0;
@@ -218,8 +216,6 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
218216
);
219217
}
220218

221-
letelapsedTimeInMs=0;
222-
223219
constsharedHostConfig={
224220
getRootHostContext(){
225221
returnNO_CONTEXT;
@@ -308,66 +304,23 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
308304
returninst;
309305
},
310306

311-
scheduleDeferredCallback(callback,options){
312-
if(scheduledCallback){
313-
thrownewError(
314-
'Scheduling a callback twice is excessive. Instead, keep track of '+
315-
'whether the callback has already been scheduled.',
316-
);
317-
}
318-
scheduledCallback=callback;
319-
if(
320-
typeofoptions==='object'&&
321-
options!==null&&
322-
typeofoptions.timeout==='number'
323-
){
324-
const newTimeout =options.timeout;
325-
if(
326-
scheduledCallbackTimeout===-1||
327-
scheduledCallbackTimeout>newTimeout
328-
){
329-
scheduledCallbackTimeout =elapsedTimeInMs+newTimeout;
330-
}
331-
}
332-
return0;
333-
},
334-
335-
cancelDeferredCallback(){
336-
if(scheduledCallback===null){
337-
thrownewError('No callback is scheduled.');
338-
}
339-
scheduledCallback =null;
340-
scheduledCallbackTimeout=-1;
341-
},
307+
scheduleDeferredCallback: Scheduler.unstable_scheduleCallback,
308+
cancelDeferredCallback: Scheduler.unstable_cancelCallback,
342309

343-
shouldYield,
310+
shouldYield: Scheduler.unstable_shouldYield,
344311

345312
scheduleTimeout: setTimeout,
346313
cancelTimeout: clearTimeout,
347314
noTimeout: -1,
348-
schedulePassiveEffects(callback){
349-
if(scheduledCallback){
350-
thrownewError(
351-
'Scheduling a callback twice is excessive. Instead, keep track of '+
352-
'whether the callback has already been scheduled.',
353-
);
354-
}
355-
scheduledPassiveCallback=callback;
356-
},
357-
cancelPassiveEffects(){
358-
if(scheduledPassiveCallback===null){
359-
thrownewError('No passive effects callback is scheduled.');
360-
}
361-
scheduledPassiveCallback =null;
362-
},
315+
316+
schedulePassiveEffects: Scheduler.unstable_scheduleCallback,
317+
cancelPassiveEffects: Scheduler.unstable_cancelCallback,
363318

364319
prepareForCommit(): void{},
365320

366321
resetAfterCommit(): void{},
367322

368-
now(): number{
369-
returnelapsedTimeInMs;
370-
},
323+
now: Scheduler.unstable_now,
371324

372325
isPrimaryRenderer: true,
373326
supportsHydration: false,
@@ -534,71 +487,6 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
534487
constroots=newMap();
535488
constDEFAULT_ROOT_ID= '<default>';
536489

537-
letyieldedValues: Array<mixed>=[];
538-
let didStop: boolean=false;
539-
let expectedNumberOfYields: number=-1;
540-
541-
functionshouldYield(){
542-
if(
543-
expectedNumberOfYields!==-1&&
544-
yieldedValues.length>=expectedNumberOfYields&&
545-
(scheduledCallbackTimeout===-1||
546-
elapsedTimeInMs<scheduledCallbackTimeout)
547-
){
548-
// We yielded at least as many values as expected. Stop rendering.
549-
didStop=true;
550-
returntrue;
551-
}
552-
// Keep rendering.
553-
returnfalse;
554-
}
555-
556-
functionflushAll(): Array<mixed>{
557-
yieldedValues=[];
558-
while(scheduledCallback!==null){
559-
constcb=scheduledCallback;
560-
scheduledCallback=null;
561-
constdidTimeout=
562-
scheduledCallbackTimeout!==-1&&
563-
scheduledCallbackTimeout<elapsedTimeInMs;
564-
cb(didTimeout);
565-
}
566-
constvalues=yieldedValues;
567-
yieldedValues=[];
568-
returnvalues;
569-
}
570-
571-
functionflushNumberOfYields(count: number): Array<mixed>{
572-
expectedNumberOfYields=count;
573-
didStop=false;
574-
yieldedValues=[];
575-
try{
576-
while(scheduledCallback!==null&&!didStop){
577-
constcb=scheduledCallback;
578-
scheduledCallback=null;
579-
constdidTimeout=
580-
scheduledCallbackTimeout!==-1&&
581-
scheduledCallbackTimeout<elapsedTimeInMs;
582-
cb(didTimeout);
583-
}
584-
returnyieldedValues;
585-
}finally{
586-
expectedNumberOfYields=-1;
587-
didStop=false;
588-
yieldedValues=[];
589-
}
590-
}
591-
592-
functionyieldValue(value: mixed): void{
593-
yieldedValues.push(value);
594-
}
595-
596-
functionclearYields(): Array<mixed>{
597-
constvalues=yieldedValues;
598-
yieldedValues=[];
599-
returnvalues;
600-
}
601-
602490
functionchildToJSX(child,text){
603491
if(text!==null){
604492
returntext;
@@ -653,6 +541,8 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
653541
}
654542

655543
constReactNoop={
544+
_Scheduler: Scheduler,
545+
656546
getChildren(rootID: string=DEFAULT_ROOT_ID){
657547
constcontainer=rootContainers.get(rootID);
658548
if(container){
@@ -763,14 +653,9 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
763653
returnNoopRenderer.findHostInstance(component);
764654
},
765655

766-
// TODO: Should only be used via a Jest plugin (like we do with the
767-
// test renderer).
768-
unstable_flushWithoutYielding: flushAll,
769-
unstable_flushNumberOfYields: flushNumberOfYields,
770-
unstable_clearYields: clearYields,
771-
772656
flushNextYield(): Array<mixed>{
773-
returnflushNumberOfYields(1);
657+
Scheduler.unstable_flushNumberOfYields(1);
658+
returnScheduler.unstable_clearYields();
774659
},
775660

776661
flushWithHostCounters(
@@ -788,7 +673,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
788673
hostUpdateCounter=0;
789674
hostCloneCounter=0;
790675
try{
791-
flushAll();
676+
Scheduler.flushAll();
792677
returnuseMutation
793678
? {
794679
hostDiffCounter,
@@ -805,24 +690,13 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
805690
}
806691
},
807692

808-
expire(ms: number): Array<mixed>{
809-
ReactNoop.advanceTime(ms);
810-
returnReactNoop.flushExpired();
811-
},
812-
813-
advanceTime(ms: number): void{
814-
elapsedTimeInMs +=ms;
815-
},
693+
expire: Scheduler.advanceTime,
816694

817695
flushExpired(): Array<mixed>{
818-
returnflushNumberOfYields(0);
696+
returnScheduler.unstable_flushExpired();
819697
},
820698

821-
yield: yieldValue,
822-
823-
hasScheduledCallback(){
824-
return!!scheduledCallback;
825-
},
699+
yield: Scheduler.yieldValue,
826700

827701
batchedUpdates: NoopRenderer.batchedUpdates,
828702

@@ -870,9 +744,7 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
870744
},
871745

872746
flushSync(fn: ()=>mixed){
873-
yieldedValues=[];
874747
NoopRenderer.flushSync(fn);
875-
returnyieldedValues;
876748
},
877749

878750
flushPassiveEffects(){
@@ -997,12 +869,13 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
997869
_next: null,
998870
};
999871
root.firstBatch=batch;
1000-
constactual=flushAll();
872+
Scheduler.unstable_flushWithoutYielding();
873+
constactual=Scheduler.unstable_clearYields();
1001874
expect(actual).toEqual(expectedFlush);
1002875
return(expectedCommit: Array<mixed>)=>{
1003876
batch._defer=false;
1004877
NoopRenderer.flushRoot(root,expiration);
1005-
expect(yieldedValues).toEqual(expectedCommit);
878+
expect(Scheduler.unstable_clearYields()).toEqual(expectedCommit);
1006879
};
1007880
},
1008881

‎packages/react-reconciler/src/__tests__/ReactExpiration-test.internal.js‎

Lines changed: 44 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
letReact;
1313
letReactFeatureFlags;
1414
letReactNoop;
15+
letScheduler;
1516

1617
describe('ReactExpiration',()=>{
1718
beforeEach(()=>{
@@ -20,6 +21,7 @@ describe('ReactExpiration', () => {
2021
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode=false;
2122
React=require('react');
2223
ReactNoop=require('react-noop-renderer');
24+
Scheduler=require('scheduler');
2325
});
2426

2527
functionspan(prop){
@@ -60,19 +62,33 @@ describe('ReactExpiration', () => {
6062
}
6163
}
6264

65+
functioninterrupt(){
66+
ReactNoop.flushSync(()=>{
67+
ReactNoop.renderToRootWithID(null,'other-root');
68+
});
69+
}
70+
6371
// First, show what happens for updates in two separate events.
6472
// Schedule an update.
6573
ReactNoop.render(<Texttext="A"/>);
66-
// Advance the timer and flush any work that expired. Flushing the expired
67-
// work signals to the renderer that the event has ended.
68-
ReactNoop.advanceTime(2000);
74+
// Advance the timer.
75+
Scheduler.advanceTime(2000);
76+
// Partially flush the the first update, then interrupt it.
77+
expect(ReactNoop).toFlushAndYieldThrough(['A [render]']);
78+
interrupt();
79+
6980
// Don't advance time by enough to expire the first update.
70-
expect(ReactNoop.flushExpired()).toEqual([]);
81+
expect(Scheduler).toHaveYielded([]);
7182
expect(ReactNoop.getChildren()).toEqual([]);
83+
7284
// Schedule another update.
7385
ReactNoop.render(<Texttext="B"/>);
7486
// The updates should flush in separate batches, since sufficient time
7587
// passed in between them *and* they occurred in separate events.
88+
// Note: This isn't necessarily the ideal behavior. It might be better to
89+
// batch these two updates together. The fact that they aren't batched
90+
// is an implementation detail. The important part of this unit test is that
91+
// they are batched if it's possible that they happened in the same event.
7692
expect(ReactNoop).toFlushAndYield([
7793
'A [render]',
7894
'A [commit]',
@@ -84,10 +100,7 @@ describe('ReactExpiration', () => {
84100
// Now do the same thing again, except this time don't flush any work in
85101
// between the two updates.
86102
ReactNoop.render(<Texttext="A"/>);
87-
// Advance the timer, but don't flush the expired work. Because we still
88-
// haven't entered an idle callback, the scheduler must assume that we're
89-
// inside the same event.
90-
ReactNoop.advanceTime(2000);
103+
Scheduler.advanceTime(2000);
91104
expect(ReactNoop).toHaveYielded([]);
92105
expect(ReactNoop.getChildren()).toEqual([span('B')]);
93106
// Schedule another update.
@@ -114,19 +127,33 @@ describe('ReactExpiration', () => {
114127
}
115128
}
116129

130+
functioninterrupt(){
131+
ReactNoop.flushSync(()=>{
132+
ReactNoop.renderToRootWithID(null,'other-root');
133+
});
134+
}
135+
117136
// First, show what happens for updates in two separate events.
118137
// Schedule an update.
119138
ReactNoop.render(<Texttext="A"/>);
120-
// Advance the timer and flush any work that expired. Flushing the expired
121-
// work signals to the renderer that the event has ended.
122-
ReactNoop.advanceTime(2000);
139+
// Advance the timer.
140+
Scheduler.advanceTime(2000);
141+
// Partially flush the the first update, then interrupt it.
142+
expect(ReactNoop).toFlushAndYieldThrough(['A [render]']);
143+
interrupt();
144+
123145
// Don't advance time by enough to expire the first update.
124-
expect(ReactNoop.flushExpired()).toEqual([]);
146+
expect(Scheduler).toHaveYielded([]);
125147
expect(ReactNoop.getChildren()).toEqual([]);
148+
126149
// Schedule another update.
127150
ReactNoop.render(<Texttext="B"/>);
128151
// The updates should flush in separate batches, since sufficient time
129152
// passed in between them *and* they occurred in separate events.
153+
// Note: This isn't necessarily the ideal behavior. It might be better to
154+
// batch these two updates together. The fact that they aren't batched
155+
// is an implementation detail. The important part of this unit test is that
156+
// they are batched if it's possible that they happened in the same event.
130157
expect(ReactNoop).toFlushAndYield([
131158
'A [render]',
132159
'A [commit]',
@@ -138,21 +165,15 @@ describe('ReactExpiration', () => {
138165
// Now do the same thing again, except this time don't flush any work in
139166
// between the two updates.
140167
ReactNoop.render(<Texttext="A"/>);
141-
// Advance the timer, but don't flush the expired work. Because we still
142-
// haven't entered an idle callback, the scheduler must assume that we're
143-
// inside the same event.
144-
ReactNoop.advanceTime(2000);
168+
Scheduler.advanceTime(2000);
145169
expect(ReactNoop).toHaveYielded([]);
146170
expect(ReactNoop.getChildren()).toEqual([span('B')]);
147171

148-
// Perform some synchronous work. Again, the scheduler must assume we're
149-
// inside the same event.
150-
ReactNoop.flushSync(()=>{
151-
ReactNoop.renderToRootWithID('1','second-root');
152-
});
172+
// Perform some synchronous work. The scheduler must assume we're inside
173+
// the same event.
174+
interrupt();
153175

154-
// Even though React flushed a sync update, it should not have updated the
155-
// current time. Schedule another update.
176+
// Schedule another update.
156177
ReactNoop.render(<Texttext="B"/>);
157178
// The updates should flush in the same batch, since as far as the scheduler
158179
// knows, they may have occurred inside the same event.

0 commit comments

Comments
 (0)