mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2025-06-07 11:04:38 -04:00
Build a mock Timer class and add tests for it
This commit is contained in:
parent
d1d5b80d55
commit
7f80fe17b3
4 changed files with 189 additions and 1 deletions
|
@ -90,6 +90,15 @@ class Timer {
|
|||
timeElapsed () {
|
||||
return this.nowObj.now() - this.startTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call a handler function after a specified amount of time has elapsed.
|
||||
* @param {function} handler - function to call after the timeout
|
||||
* @param {number} timeout - number of milliseconds to delay before calling the handler
|
||||
*/
|
||||
setTimeout (handler, timeout) {
|
||||
global.setTimeout(handler, timeout);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Timer;
|
||||
|
|
121
test/fixtures/mock-timer.js
vendored
Normal file
121
test/fixtures/mock-timer.js
vendored
Normal file
|
@ -0,0 +1,121 @@
|
|||
/**
|
||||
* Mimic the Timer class with external control of the "time" value, allowing tests to run more quickly and
|
||||
* reliably. Multiple instances of this class operate independently: they may report different time values, and
|
||||
* advancing one timer will not trigger timeouts set on another.
|
||||
*/
|
||||
class MockTimer {
|
||||
/**
|
||||
* Creates an instance of MockTimer.
|
||||
* @param {*} [nowObj=null] - alert the caller that this parameter, supported by Timer, is not supported here.
|
||||
* @memberof MockTimer
|
||||
*/
|
||||
constructor (nowObj = null) {
|
||||
if (nowObj) {
|
||||
throw new Error('nowObj is not implemented in MockTimer');
|
||||
}
|
||||
|
||||
/**
|
||||
* The fake "current time" value, in epoch milliseconds.
|
||||
* @type {number}
|
||||
*/
|
||||
this._mockTime = 0;
|
||||
|
||||
/**
|
||||
* Used to store the start time of a timer action.
|
||||
* Updated when calling `timer.start`.
|
||||
* @type {number}
|
||||
*/
|
||||
this.startTime = 0;
|
||||
|
||||
/**
|
||||
* Array of pending timeout callbacks
|
||||
* @type {Array.<Object>}
|
||||
*/
|
||||
this._timeouts = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Advance this MockTimer's idea of "current time", running timeout handlers if appropriate.
|
||||
* @param {number} delta - the amount of time to add to the current mock time value, in milliseconds.
|
||||
* @memberof MockTimer
|
||||
*/
|
||||
advanceMockTime (delta) {
|
||||
if (delta < 0) {
|
||||
throw new Error('Time may not move backward');
|
||||
}
|
||||
this._mockTime += delta;
|
||||
this._runTimeouts();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number} - current mock time elapsed since 1 January 1970 00:00:00 UTC.
|
||||
* @memberof MockTimer
|
||||
*/
|
||||
time () {
|
||||
return this._mockTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a time accurate relative to other times produced by this function.
|
||||
* @returns {number} ms-scale accurate time relative to other relative times.
|
||||
* @memberof MockTimer
|
||||
*/
|
||||
relativeTime () {
|
||||
return this._mockTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a timer for measuring elapsed time.
|
||||
* @memberof MockTimer
|
||||
*/
|
||||
start () {
|
||||
this.startTime = this._mockTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {number} - the time elapsed since `start()` was called.
|
||||
* @memberof MockTimer
|
||||
*/
|
||||
timeElapsed () {
|
||||
return this._mockTime - this.startTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call a handler function after a specified amount of time has elapsed.
|
||||
* Guaranteed to happen in between "ticks" of JavaScript.
|
||||
* @param {function} handler - function to call after the timeout
|
||||
* @param {number} timeout - number of milliseconds to delay before calling the handler
|
||||
* @memberof MockTimer
|
||||
*/
|
||||
setTimeout (handler, timeout) {
|
||||
this._timeouts.push({
|
||||
time: this._mockTime + timeout,
|
||||
handler
|
||||
});
|
||||
this._runTimeouts();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run any timeout handlers whose timeouts have expired.
|
||||
* @memberof MockTimer
|
||||
*/
|
||||
_runTimeouts () {
|
||||
const ready = [];
|
||||
const waiting = [];
|
||||
|
||||
// partition timeout records by whether or not they're ready to call
|
||||
this._timeouts.forEach(o => {
|
||||
const isReady = o.time <= this._mockTime;
|
||||
(isReady ? ready : waiting).push(o);
|
||||
});
|
||||
|
||||
this._timeouts = waiting;
|
||||
|
||||
// next tick, call everything that's ready
|
||||
global.setTimeout(() => {
|
||||
ready.forEach(o => o.handler());
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MockTimer;
|
58
test/unit/util_mock-timer.js
Normal file
58
test/unit/util_mock-timer.js
Normal file
|
@ -0,0 +1,58 @@
|
|||
const test = require('tap').test;
|
||||
const MockTimer = require('../fixtures/mock-timer');
|
||||
|
||||
test('spec', t => {
|
||||
const timer = new MockTimer();
|
||||
|
||||
t.type(MockTimer, 'function');
|
||||
t.type(timer, 'object');
|
||||
|
||||
t.type(timer.startTime, 'number');
|
||||
t.type(timer.time, 'function');
|
||||
t.type(timer.start, 'function');
|
||||
t.type(timer.timeElapsed, 'function');
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('time', t => {
|
||||
const timer = new MockTimer();
|
||||
|
||||
const time1 = timer.time();
|
||||
const time2 = timer.time();
|
||||
timer.advanceMockTime(1);
|
||||
const time3 = timer.time();
|
||||
|
||||
t.ok(time1 === time2);
|
||||
t.ok(time2 < time3);
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('start / timeElapsed', t => {
|
||||
const timer = new MockTimer();
|
||||
const halfDelay = 50;
|
||||
const fullDelay = halfDelay + halfDelay;
|
||||
|
||||
timer.start();
|
||||
|
||||
let timeoutCalled = 0;
|
||||
|
||||
// Wait and measure timer
|
||||
timer.setTimeout(() => {
|
||||
t.ok(timeoutCalled === 0);
|
||||
++timeoutCalled;
|
||||
|
||||
const timeElapsed = timer.timeElapsed();
|
||||
t.ok(timeElapsed === fullDelay);
|
||||
|
||||
t.end();
|
||||
}, fullDelay);
|
||||
|
||||
// this should not call the callback
|
||||
timer.advanceMockTime(halfDelay);
|
||||
|
||||
t.ok(timeoutCalled === 0);
|
||||
|
||||
// this should call the callback
|
||||
timer.advanceMockTime(halfDelay);
|
||||
});
|
|
@ -32,7 +32,7 @@ test('start / timeElapsed', t => {
|
|||
timer.start();
|
||||
|
||||
// Wait and measure timer
|
||||
setTimeout(() => {
|
||||
timer.setTimeout(() => {
|
||||
const timeElapsed = timer.timeElapsed();
|
||||
t.ok(timeElapsed >= 0);
|
||||
t.ok(timeElapsed >= (delay - threshold) &&
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue