Skip to content

Commit 986d0e6

Browse files
authored
[Scheduler] Add tests for isInputPending (#22140)
This feature was already implemented but we didn't have any tests. So I wrote some.
1 parent d54be90 commit 986d0e6

File tree

1 file changed

+217
-1
lines changed

1 file changed

+217
-1
lines changed

packages/scheduler/src/__tests__/Scheduler-test.js

+217-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ let runtime;
1717
let performance;
1818
let cancelCallback;
1919
let scheduleCallback;
20+
let requestPaint;
2021
let NormalPriority;
2122

2223
// The Scheduler implementation uses browser APIs like `MessageChannel` and
@@ -40,6 +41,7 @@ describe('SchedulerBrowser', () => {
4041
cancelCallback = Scheduler.unstable_cancelCallback;
4142
scheduleCallback = Scheduler.unstable_scheduleCallback;
4243
NormalPriority = Scheduler.unstable_NormalPriority;
44+
requestPaint = Scheduler.unstable_requestPaint;
4345
});
4446

4547
afterEach(() => {
@@ -52,6 +54,9 @@ describe('SchedulerBrowser', () => {
5254

5355
function installMockBrowserRuntime() {
5456
let hasPendingMessageEvent = false;
57+
let isFiringMessageEvent = false;
58+
let hasPendingDiscreteEvent = false;
59+
let hasPendingContinuousEvent = false;
5560

5661
let timerIDCounter = 0;
5762
// let timerIDs = new Map();
@@ -94,6 +99,23 @@ describe('SchedulerBrowser', () => {
9499
this.port2 = port2;
95100
};
96101

102+
const scheduling = {
103+
isInputPending(options) {
104+
if (this !== scheduling) {
105+
throw new Error(
106+
'isInputPending called with incorrect `this` context',
107+
);
108+
}
109+
110+
return (
111+
hasPendingDiscreteEvent ||
112+
(options && options.includeContinuous && hasPendingContinuousEvent)
113+
);
114+
},
115+
};
116+
117+
global.navigator = {scheduling};
118+
97119
function ensureLogIsEmpty() {
98120
if (eventLog.length !== 0) {
99121
throw Error('Log is not empty. Call assertLog before continuing.');
@@ -102,6 +124,9 @@ describe('SchedulerBrowser', () => {
102124
function advanceTime(ms) {
103125
currentTime += ms;
104126
}
127+
function resetTime() {
128+
currentTime = 0;
129+
}
105130
function fireMessageEvent() {
106131
ensureLogIsEmpty();
107132
if (!hasPendingMessageEvent) {
@@ -110,7 +135,35 @@ describe('SchedulerBrowser', () => {
110135
hasPendingMessageEvent = false;
111136
const onMessage = port1.onmessage;
112137
log('Message Event');
113-
onMessage();
138+
139+
isFiringMessageEvent = true;
140+
try {
141+
onMessage();
142+
} finally {
143+
isFiringMessageEvent = false;
144+
if (hasPendingDiscreteEvent) {
145+
log('Discrete Event');
146+
hasPendingDiscreteEvent = false;
147+
}
148+
if (hasPendingContinuousEvent) {
149+
log('Continuous Event');
150+
hasPendingContinuousEvent = false;
151+
}
152+
}
153+
}
154+
function scheduleDiscreteEvent() {
155+
if (isFiringMessageEvent) {
156+
hasPendingDiscreteEvent = true;
157+
} else {
158+
log('Discrete Event');
159+
}
160+
}
161+
function scheduleContinuousEvent() {
162+
if (isFiringMessageEvent) {
163+
hasPendingContinuousEvent = true;
164+
} else {
165+
log('Continuous Event');
166+
}
114167
}
115168
function log(val) {
116169
eventLog.push(val);
@@ -125,10 +178,13 @@ describe('SchedulerBrowser', () => {
125178
}
126179
return {
127180
advanceTime,
181+
resetTime,
128182
fireMessageEvent,
129183
log,
130184
isLogEmpty,
131185
assertLog,
186+
scheduleDiscreteEvent,
187+
scheduleContinuousEvent,
132188
};
133189
}
134190

@@ -144,6 +200,8 @@ describe('SchedulerBrowser', () => {
144200
it('task with continuation', () => {
145201
scheduleCallback(NormalPriority, () => {
146202
runtime.log('Task');
203+
// Request paint so that we yield at the end of the frame interval
204+
requestPaint();
147205
while (!Scheduler.unstable_shouldYield()) {
148206
runtime.advanceTime(1);
149207
}
@@ -259,4 +317,162 @@ describe('SchedulerBrowser', () => {
259317
runtime.fireMessageEvent();
260318
runtime.assertLog(['Message Event', 'B']);
261319
});
320+
321+
it('when isInputPending is available, we can wait longer before yielding', () => {
322+
function blockUntilSchedulerAsksToYield() {
323+
while (!Scheduler.unstable_shouldYield()) {
324+
runtime.advanceTime(1);
325+
}
326+
runtime.log(`Yield at ${performance.now()}ms`);
327+
}
328+
329+
// First show what happens when we don't request a paint
330+
scheduleCallback(NormalPriority, () => {
331+
runtime.log('Task with no pending input');
332+
blockUntilSchedulerAsksToYield();
333+
});
334+
runtime.assertLog(['Post Message']);
335+
336+
runtime.fireMessageEvent();
337+
runtime.assertLog([
338+
'Message Event',
339+
'Task with no pending input',
340+
// Even though there's no input, eventually Scheduler will yield
341+
// regardless in case there's a pending main thread task we don't know
342+
// about, like a network event.
343+
gate(flags =>
344+
flags.enableIsInputPending
345+
? 'Yield at 300ms'
346+
: // When isInputPending is disabled, we always yield quickly
347+
'Yield at 5ms',
348+
),
349+
]);
350+
351+
runtime.resetTime();
352+
353+
// Now do the same thing, but while the task is running, simulate an
354+
// input event.
355+
scheduleCallback(NormalPriority, () => {
356+
runtime.log('Task with pending input');
357+
runtime.scheduleDiscreteEvent();
358+
blockUntilSchedulerAsksToYield();
359+
});
360+
runtime.assertLog(['Post Message']);
361+
362+
runtime.fireMessageEvent();
363+
runtime.assertLog([
364+
'Message Event',
365+
'Task with pending input',
366+
// This time we yielded quickly to unblock the discrete event.
367+
'Yield at 5ms',
368+
'Discrete Event',
369+
]);
370+
});
371+
372+
it(
373+
'isInputPending will also check for continuous inputs, but after a ' +
374+
'slightly larger threshold',
375+
() => {
376+
function blockUntilSchedulerAsksToYield() {
377+
while (!Scheduler.unstable_shouldYield()) {
378+
runtime.advanceTime(1);
379+
}
380+
runtime.log(`Yield at ${performance.now()}ms`);
381+
}
382+
383+
// First show what happens when we don't request a paint
384+
scheduleCallback(NormalPriority, () => {
385+
runtime.log('Task with no pending input');
386+
blockUntilSchedulerAsksToYield();
387+
});
388+
runtime.assertLog(['Post Message']);
389+
390+
runtime.fireMessageEvent();
391+
runtime.assertLog([
392+
'Message Event',
393+
'Task with no pending input',
394+
// Even though there's no input, eventually Scheduler will yield
395+
// regardless in case there's a pending main thread task we don't know
396+
// about, like a network event.
397+
gate(flags =>
398+
flags.enableIsInputPending
399+
? 'Yield at 300ms'
400+
: // When isInputPending is disabled, we always yield quickly
401+
'Yield at 5ms',
402+
),
403+
]);
404+
405+
runtime.resetTime();
406+
407+
// Now do the same thing, but while the task is running, simulate a
408+
// continuous input event.
409+
scheduleCallback(NormalPriority, () => {
410+
runtime.log('Task with continuous input');
411+
runtime.scheduleContinuousEvent();
412+
blockUntilSchedulerAsksToYield();
413+
});
414+
runtime.assertLog(['Post Message']);
415+
416+
runtime.fireMessageEvent();
417+
runtime.assertLog([
418+
'Message Event',
419+
'Task with continuous input',
420+
// This time we yielded quickly to unblock the continuous event. But not
421+
// as quickly as for a discrete event.
422+
gate(flags =>
423+
flags.enableIsInputPending
424+
? 'Yield at 50ms'
425+
: // When isInputPending is disabled, we always yield quickly
426+
'Yield at 5ms',
427+
),
428+
'Continuous Event',
429+
]);
430+
},
431+
);
432+
433+
it('requestPaint forces a yield at the end of the next frame interval', () => {
434+
function blockUntilSchedulerAsksToYield() {
435+
while (!Scheduler.unstable_shouldYield()) {
436+
runtime.advanceTime(1);
437+
}
438+
runtime.log(`Yield at ${performance.now()}ms`);
439+
}
440+
441+
// First show what happens when we don't request a paint
442+
scheduleCallback(NormalPriority, () => {
443+
runtime.log('Task with no paint');
444+
blockUntilSchedulerAsksToYield();
445+
});
446+
runtime.assertLog(['Post Message']);
447+
448+
runtime.fireMessageEvent();
449+
runtime.assertLog([
450+
'Message Event',
451+
'Task with no paint',
452+
gate(flags =>
453+
flags.enableIsInputPending
454+
? 'Yield at 300ms'
455+
: // When isInputPending is disabled, we always yield quickly
456+
'Yield at 5ms',
457+
),
458+
]);
459+
460+
runtime.resetTime();
461+
462+
// Now do the same thing, but call requestPaint inside the task
463+
scheduleCallback(NormalPriority, () => {
464+
runtime.log('Task with paint');
465+
requestPaint();
466+
blockUntilSchedulerAsksToYield();
467+
});
468+
runtime.assertLog(['Post Message']);
469+
470+
runtime.fireMessageEvent();
471+
runtime.assertLog([
472+
'Message Event',
473+
'Task with paint',
474+
// This time we yielded quickly (5ms) because we requested a paint.
475+
'Yield at 5ms',
476+
]);
477+
});
262478
});

0 commit comments

Comments
 (0)