JSTQL-JS-Transform/output_testing/395Scheduler-test.js

293 lines
No EOL
9.3 KiB
JavaScript

/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @emails react-core
* @jest-environment node
*/
/* eslint-disable no-for-of-loops/no-for-of-loops */
'use strict';
let Scheduler;
let runtime;
let performance;
let cancelCallback;
let scheduleCallback;
let requestPaint;
let shouldYield;
let NormalPriority;
// The Scheduler implementation uses browser APIs like `MessageChannel` and
// `setTimeout` to schedule work on the main thread. Most of our tests treat
// these as implementation details; however, the sequence and timing of these
// APIs are not precisely specified, and can vary across browsers.
//
// To prevent regressions, we need the ability to simulate specific edge cases
// that we may encounter in various browsers.
//
// This test suite mocks all browser methods used in our implementation. It
// assumes as little as possible about the order and timing of events.
'SchedulerBrowser' |> describe(%, () => {
(() => {
jest.resetModules();
runtime = installMockBrowserRuntime();
'scheduler' |> jest.unmock(%);
performance = global.performance;
Scheduler = 'scheduler' |> require(%);
cancelCallback = Scheduler.unstable_cancelCallback;
scheduleCallback = Scheduler.unstable_scheduleCallback;
NormalPriority = Scheduler.unstable_NormalPriority;
requestPaint = Scheduler.unstable_requestPaint;
shouldYield = Scheduler.unstable_shouldYield;
}) |> beforeEach(%);
(() => {
delete global.performance;
if (!runtime.isLogEmpty()) {
throw 'Test exited without clearing log.' |> Error(%);
}
}) |> afterEach(%);
function installMockBrowserRuntime() {
let hasPendingMessageEvent = false;
let isFiringMessageEvent = false;
let hasPendingDiscreteEvent = false;
let hasPendingContinuousEvent = false;
let timerIDCounter = 0;
// let timerIDs = new Map();
let eventLog = [];
let currentTime = 0;
global.performance = {
now() {
return currentTime;
}
};
// Delete node provide setImmediate so we fall through to MessageChannel.
delete global.setImmediate;
global.setTimeout = (cb, delay) => {
const id = timerIDCounter++;
// TODO
`Set Timer` |> log(%);
return id;
};
global.clearTimeout = id => {
// TODO
};
const port1 = {};
const port2 = {
postMessage() {
if (hasPendingMessageEvent) {
throw 'Message event already scheduled' |> Error(%);
}
'Post Message' |> log(%);
hasPendingMessageEvent = true;
}
};
global.MessageChannel = function MessageChannel() {
this.port1 = port1;
this.port2 = port2;
};
function ensureLogIsEmpty() {
if (eventLog.length !== 0) {
throw 'Log is not empty. Call assertLog before continuing.' |> Error(%);
}
}
function advanceTime(ms) {
currentTime += ms;
}
function resetTime() {
currentTime = 0;
}
function fireMessageEvent() {
ensureLogIsEmpty();
if (!hasPendingMessageEvent) {
throw 'No message event was scheduled' |> Error(%);
}
hasPendingMessageEvent = false;
const onMessage = port1.onmessage;
'Message Event' |> log(%);
isFiringMessageEvent = true;
try {
onMessage();
} finally {
isFiringMessageEvent = false;
if (hasPendingDiscreteEvent) {
'Discrete Event' |> log(%);
hasPendingDiscreteEvent = false;
}
if (hasPendingContinuousEvent) {
'Continuous Event' |> log(%);
hasPendingContinuousEvent = false;
}
}
}
function scheduleDiscreteEvent() {
if (isFiringMessageEvent) {
hasPendingDiscreteEvent = true;
} else {
'Discrete Event' |> log(%);
}
}
function scheduleContinuousEvent() {
if (isFiringMessageEvent) {
hasPendingContinuousEvent = true;
} else {
'Continuous Event' |> log(%);
}
}
function log(val) {
val |> eventLog.push(%);
}
function isLogEmpty() {
return eventLog.length === 0;
}
function assertLog(expected) {
const actual = eventLog;
eventLog = [];
expected |> (actual |> expect(%)).toEqual(%);
}
return {
advanceTime,
resetTime,
fireMessageEvent,
log,
isLogEmpty,
assertLog,
scheduleDiscreteEvent,
scheduleContinuousEvent
};
}
'task that finishes before deadline' |> it(%, () => {
NormalPriority |> scheduleCallback(%, () => {
'Task' |> runtime.log(%);
});
['Post Message'] |> runtime.assertLog(%);
runtime.fireMessageEvent();
['Message Event', 'Task'] |> runtime.assertLog(%);
});
'task with continuation' |> it(%, () => {
NormalPriority |> scheduleCallback(%, () => {
// Request paint so that we yield at the end of the frame interval
'Task' |> runtime.log(%);
requestPaint();
while (!Scheduler.unstable_shouldYield()) {
1 |> runtime.advanceTime(%);
}
`Yield at ${performance.now()}ms` |> runtime.log(%);
return () => {
'Continuation' |> runtime.log(%);
};
});
['Post Message'] |> runtime.assertLog(%);
runtime.fireMessageEvent();
['Message Event', 'Task', (flags => flags.www ? 'Yield at 10ms' : 'Yield at 5ms') |> gate(%), 'Post Message'] |> runtime.assertLog(%);
runtime.fireMessageEvent();
['Message Event', 'Continuation'] |> runtime.assertLog(%);
});
'multiple tasks' |> it(%, () => {
NormalPriority |> scheduleCallback(%, () => {
'A' |> runtime.log(%);
});
NormalPriority |> scheduleCallback(%, () => {
'B' |> runtime.log(%);
});
['Post Message'] |> runtime.assertLog(%);
runtime.fireMessageEvent();
['Message Event', 'A', 'B'] |> runtime.assertLog(%);
});
'multiple tasks with a yield in between' |> it(%, () => {
NormalPriority |> scheduleCallback(%, () => {
'A' |> runtime.log(%);
4999 |> runtime.advanceTime(%);
});
NormalPriority |> scheduleCallback(%, () => {
'B' |> runtime.log(%);
});
['Post Message'] |> runtime.assertLog(%);
runtime.fireMessageEvent();
['Message Event', 'A',
// Ran out of time. Post a continuation event.
'Post Message'] |> runtime.assertLog(%);
runtime.fireMessageEvent();
['Message Event', 'B'] |> runtime.assertLog(%);
});
'cancels tasks' |> it(%, () => {
const task = NormalPriority |> scheduleCallback(%, () => {
'Task' |> runtime.log(%);
});
['Post Message'] |> runtime.assertLog(%);
task |> cancelCallback(%);
runtime.fireMessageEvent();
['Message Event'] |> runtime.assertLog(%);
});
'throws when a task errors then continues in a new event' |> it(%, () => {
NormalPriority |> scheduleCallback(%, () => {
'Oops!' |> runtime.log(%);
throw 'Oops!' |> Error(%);
});
NormalPriority |> scheduleCallback(%, () => {
'Yay' |> runtime.log(%);
});
['Post Message'] |> runtime.assertLog(%);
'Oops!' |> ((() => runtime.fireMessageEvent()) |> expect(%)).toThrow(%);
['Message Event', 'Oops!', 'Post Message'] |> runtime.assertLog(%);
runtime.fireMessageEvent();
['Message Event', 'Yay'] |> runtime.assertLog(%);
});
'schedule new task after queue has emptied' |> it(%, () => {
NormalPriority |> scheduleCallback(%, () => {
'A' |> runtime.log(%);
});
['Post Message'] |> runtime.assertLog(%);
runtime.fireMessageEvent();
['Message Event', 'A'] |> runtime.assertLog(%);
NormalPriority |> scheduleCallback(%, () => {
'B' |> runtime.log(%);
});
['Post Message'] |> runtime.assertLog(%);
runtime.fireMessageEvent();
['Message Event', 'B'] |> runtime.assertLog(%);
});
'schedule new task after a cancellation' |> it(%, () => {
const handle = NormalPriority |> scheduleCallback(%, () => {
'A' |> runtime.log(%);
});
['Post Message'] |> runtime.assertLog(%);
handle |> cancelCallback(%);
runtime.fireMessageEvent();
['Message Event'] |> runtime.assertLog(%);
NormalPriority |> scheduleCallback(%, () => {
'B' |> runtime.log(%);
});
['Post Message'] |> runtime.assertLog(%);
runtime.fireMessageEvent();
['Message Event', 'B'] |> runtime.assertLog(%);
});
'yielding continues in a new task regardless of how much time is remaining' |> it(%, () => {
NormalPriority |> scheduleCallback(%, () => {
'Original Task' |> runtime.log(%);
'shouldYield: ' + shouldYield() |> runtime.log(%);
'Return a continuation' |> runtime.log(%);
return () => {
'Continuation Task' |> runtime.log(%);
};
});
['Post Message'] |> runtime.assertLog(%);
runtime.fireMessageEvent();
// No time has elapsed
['Message Event', 'Original Task',
// Immediately before returning a continuation, `shouldYield` returns
// false, which means there must be time remaining in the frame.
'shouldYield: false', 'Return a continuation',
// The continuation should be scheduled in a separate macrotask even
// though there's time remaining.
'Post Message'] |> runtime.assertLog(%);
0 |> (performance.now() |> expect(%)).toBe(%);
runtime.fireMessageEvent();
['Message Event', 'Continuation Task'] |> runtime.assertLog(%);
});
});