mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
142 lines
4.7 KiB
TypeScript
142 lines
4.7 KiB
TypeScript
|
import {Interval} from 'app/common/Interval';
|
||
|
import {delay} from 'bluebird';
|
||
|
import {assert} from 'chai';
|
||
|
import * as sinon from 'sinon';
|
||
|
|
||
|
describe('Interval', function() {
|
||
|
const delayMs = 100;
|
||
|
const varianceMs = 50;
|
||
|
const promiseDelayMs = 200;
|
||
|
const delayBufferMs = 20;
|
||
|
|
||
|
let interval: Interval;
|
||
|
let spy: sinon.SinonSpy;
|
||
|
|
||
|
beforeEach(() => {
|
||
|
spy = sinon.spy();
|
||
|
});
|
||
|
|
||
|
afterEach(async () => {
|
||
|
if (interval) {
|
||
|
await interval.disableAndFinish();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
it('is not enabled by default', async function() {
|
||
|
interval = new Interval(spy, {delayMs}, {onError: () => { /* do nothing */ }});
|
||
|
assert.equal(spy.callCount, 0);
|
||
|
await delay(delayMs + delayBufferMs);
|
||
|
assert.equal(spy.callCount, 0);
|
||
|
});
|
||
|
|
||
|
it('can be disabled', async function() {
|
||
|
interval = new Interval(spy, {delayMs}, {onError: () => { /* do nothing */ }});
|
||
|
interval.enable();
|
||
|
await delay(delayMs + delayBufferMs);
|
||
|
assert.equal(spy.callCount, 1);
|
||
|
|
||
|
// Disable the interval, and check that the calls stop.
|
||
|
interval.disable();
|
||
|
await delay(delayMs + delayBufferMs);
|
||
|
assert.equal(spy.callCount, 1);
|
||
|
|
||
|
// Enable the interval again, and check that the calls resume.
|
||
|
interval.enable();
|
||
|
await delay(delayMs + delayBufferMs);
|
||
|
assert.equal(spy.callCount, 2);
|
||
|
spy.resetHistory();
|
||
|
});
|
||
|
|
||
|
it('calls onError if callback throws an error', async function() {
|
||
|
const callback = () => { throw new Error('Something bad happened.'); };
|
||
|
const onErrorSpy = sinon.spy();
|
||
|
interval = new Interval(callback, {delayMs}, {onError: onErrorSpy});
|
||
|
interval.enable();
|
||
|
|
||
|
// Check that onError is called when the callback throws.
|
||
|
assert.equal(onErrorSpy.callCount, 0);
|
||
|
await delay(delayMs + delayBufferMs);
|
||
|
assert.equal(onErrorSpy.callCount, 1);
|
||
|
|
||
|
// Check that the interval didn't stop (since the onError spy silenced the error).
|
||
|
await delay(delayMs + delayBufferMs);
|
||
|
assert.equal(onErrorSpy.callCount, 2);
|
||
|
});
|
||
|
|
||
|
describe('with a fixed delay', function() {
|
||
|
beforeEach(() => {
|
||
|
interval = new Interval(spy, {delayMs}, {onError: () => { /* do nothing */ }});
|
||
|
interval.enable();
|
||
|
});
|
||
|
|
||
|
it('calls the callback on a fixed interval', async function() {
|
||
|
await delay(delayMs + delayBufferMs);
|
||
|
assert.equal(spy.callCount, 1);
|
||
|
await delay(delayMs + delayBufferMs);
|
||
|
assert.equal(spy.callCount, 2);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('with a randomized delay', function() {
|
||
|
beforeEach(() => {
|
||
|
interval = new Interval(spy, {delayMs, varianceMs}, {
|
||
|
onError: () => { /* do nothing */ }
|
||
|
});
|
||
|
interval.enable();
|
||
|
});
|
||
|
|
||
|
it('calls the callback on a randomized interval', async function() {
|
||
|
const delays: number[] = [];
|
||
|
for (let i = 1; i <= 10; i++) {
|
||
|
// Get the current delay and check that it's within the expected range.
|
||
|
const currentDelayMs = interval.getDelayMs();
|
||
|
delays.push(currentDelayMs!);
|
||
|
assert.isDefined(currentDelayMs);
|
||
|
assert.isAtMost(currentDelayMs!, delayMs + varianceMs);
|
||
|
assert.isAtLeast(currentDelayMs!, delayMs - varianceMs);
|
||
|
|
||
|
// Wait for the delay, and check that the spy was called.
|
||
|
await delay(currentDelayMs!);
|
||
|
assert.equal(spy.callCount, i);
|
||
|
}
|
||
|
|
||
|
// Check that we didn't use the same delay all 10 times.
|
||
|
assert.notEqual([...new Set(delays)].length, 1);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('with a promise-based callback', function() {
|
||
|
let promiseSpy: sinon.SinonSpy;
|
||
|
|
||
|
beforeEach(() => {
|
||
|
const promise = () => delay(promiseDelayMs);
|
||
|
promiseSpy = sinon.spy(promise);
|
||
|
interval = new Interval(promiseSpy, {delayMs}, {onError: () => { /* do nothing */ }});
|
||
|
interval.enable();
|
||
|
});
|
||
|
|
||
|
it('waits for promises to settle before scheduling the next call', async function() {
|
||
|
assert.equal(promiseSpy.callCount, 0);
|
||
|
await delay(delayMs + delayBufferMs);
|
||
|
assert.equal(promiseSpy.callCount, 1);
|
||
|
await delay(delayMs + delayBufferMs);
|
||
|
assert.equal(promiseSpy.callCount, 1); // Still 1, because the first promise hasn't settled yet.
|
||
|
await delay(delayMs + delayBufferMs);
|
||
|
assert.equal(promiseSpy.callCount, 1); // Promise now settled, but there's still a 100ms delay.
|
||
|
await delay(delayMs + delayBufferMs);
|
||
|
assert.equal(promiseSpy.callCount, 2); // Now we finally call the callback again.
|
||
|
});
|
||
|
|
||
|
it('can wait for last promise to settle when disabling', async function() {
|
||
|
assert.equal(promiseSpy.callCount, 0);
|
||
|
await delay(delayMs + delayBufferMs);
|
||
|
assert.equal(promiseSpy.callCount, 1);
|
||
|
await interval.disableAndFinish();
|
||
|
|
||
|
// Check that once disabled, no more calls are scheduled.
|
||
|
await delay(promiseDelayMs + delayMs + delayBufferMs);
|
||
|
assert.equal(promiseSpy.callCount, 1);
|
||
|
});
|
||
|
});
|
||
|
});
|