@samitouri / QOS-React-2 / commits / 986d0e61d7

[Scheduler] Add tests for isInputPending (#22140)

This feature was already implemented but we didn't have any tests. So I wrote some.

Andrew Clark committed Aug 20, 2021 at 09:57 UTC 986d0e61d7085d4b8eea0386235d1b5ca9f47349
1 file changed +217 -1
packages/scheduler/src/__tests__/Scheduler-test.js
+217 -1
@@ -17,6 +17,7 @@ let runtime;
17 let performance;
18 let cancelCallback;
19 let scheduleCallback;
20 +let requestPaint;
21 let NormalPriority;
22
23 // The Scheduler implementation uses browser APIs like `MessageChannel` and
@@ -40,6 +41,7 @@ describe('SchedulerBrowser', () => {
41 cancelCallback = Scheduler.unstable_cancelCallback;
42 scheduleCallback = Scheduler.unstable_scheduleCallback;
43 NormalPriority = Scheduler.unstable_NormalPriority;
44 + requestPaint = Scheduler.unstable_requestPaint;
45 });
46
47 afterEach(() => {
@@ -52,6 +54,9 @@ describe('SchedulerBrowser', () => {
54
55 function installMockBrowserRuntime() {
56 let hasPendingMessageEvent = false;
57 + let isFiringMessageEvent = false;
58 + let hasPendingDiscreteEvent = false;
59 + let hasPendingContinuousEvent = false;
60
61 let timerIDCounter = 0;
62 // let timerIDs = new Map();
@@ -94,6 +99,23 @@ describe('SchedulerBrowser', () => {
99 this.port2 = port2;
100 };
101
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 +
119 function ensureLogIsEmpty() {
120 if (eventLog.length !== 0) {
121 throw Error('Log is not empty. Call assertLog before continuing.');
@@ -102,6 +124,9 @@ describe('SchedulerBrowser', () => {
124 function advanceTime(ms) {
125 currentTime += ms;
126 }
127 + function resetTime() {
128 + currentTime = 0;
129 + }
130 function fireMessageEvent() {
131 ensureLogIsEmpty();
132 if (!hasPendingMessageEvent) {
@@ -110,7 +135,35 @@ describe('SchedulerBrowser', () => {
135 hasPendingMessageEvent = false;
136 const onMessage = port1.onmessage;
137 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 + }
167 }
168 function log(val) {
169 eventLog.push(val);
@@ -125,10 +178,13 @@ describe('SchedulerBrowser', () => {
178 }
179 return {
180 advanceTime,
181 + resetTime,
182 fireMessageEvent,
183 log,
184 isLogEmpty,
185 assertLog,
186 + scheduleDiscreteEvent,
187 + scheduleContinuousEvent,
188 };
189 }
190
@@ -144,6 +200,8 @@ describe('SchedulerBrowser', () => {
200 it('task with continuation', () => {
201 scheduleCallback(NormalPriority, () => {
202 runtime.log('Task');
203 + // Request paint so that we yield at the end of the frame interval
204 + requestPaint();
205 while (!Scheduler.unstable_shouldYield()) {
206 runtime.advanceTime(1);
207 }
@@ -259,4 +317,162 @@ describe('SchedulerBrowser', () => {
317 runtime.fireMessageEvent();
318 runtime.assertLog(['Message Event', 'B']);
319 });
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 + });
478 });