diff --git a/packages/core/src/shared/utilities/timeoutUtils.ts b/packages/core/src/shared/utilities/timeoutUtils.ts index f009b0676c9..8dc38cfef5c 100644 --- a/packages/core/src/shared/utilities/timeoutUtils.ts +++ b/packages/core/src/shared/utilities/timeoutUtils.ts @@ -184,6 +184,34 @@ export class Timeout { } } +export class Interval { + private _setCompleted: (() => void) | undefined + private _nextCompletion: Promise + private ref: NodeJS.Timer | number | undefined + + constructor(intervalMillis: number, onCompletion: () => Promise) { + this._nextCompletion = new Promise((resolve) => { + this._setCompleted = () => resolve() + }) + this.ref = globals.clock.setInterval(async () => { + await onCompletion() + this._setCompleted!() + this._nextCompletion = new Promise((resolve) => { + this._setCompleted = () => resolve() + }) + }, intervalMillis) + } + + /** Allows to wait for the next interval to finish running */ + public async nextCompletion() { + await this._nextCompletion + } + + public dispose() { + globals.clock.clearInterval(this.ref) + } +} + interface WaitUntilOptions { /** Timeout in ms (default: 5000) */ readonly timeout?: number diff --git a/packages/core/src/test/shared/utilities/timeoutUtils.test.ts b/packages/core/src/test/shared/utilities/timeoutUtils.test.ts index 473c476b4d2..c518b1cfae1 100644 --- a/packages/core/src/test/shared/utilities/timeoutUtils.test.ts +++ b/packages/core/src/test/shared/utilities/timeoutUtils.test.ts @@ -8,15 +8,21 @@ import * as FakeTimers from '@sinonjs/fake-timers' import * as timeoutUtils from '../../../shared/utilities/timeoutUtils' import { installFakeClock, tickPromise } from '../../../test/testUtil' import { sleep } from '../../../shared/utilities/timeoutUtils' +import { SinonStub, SinonSandbox, createSandbox } from 'sinon' // We export this describe() so it can be used in the web tests as well export const timeoutUtilsDescribe = describe('timeoutUtils', async function () { let clock: FakeTimers.InstalledClock + let sandbox: SinonSandbox before(function () { clock = installFakeClock() }) + beforeEach(function () { + sandbox = createSandbox() + }) + after(function () { clock.uninstall() }) @@ -24,6 +30,7 @@ export const timeoutUtilsDescribe = describe('timeoutUtils', async function () { afterEach(function () { clock.reset() this.timer?.dispose() + sandbox.restore() }) describe('Timeout', async function () { @@ -192,6 +199,53 @@ export const timeoutUtilsDescribe = describe('timeoutUtils', async function () { }) }) + describe('Interval', async function () { + let interval: timeoutUtils.Interval + let onCompletionStub: SinonStub + + beforeEach(async function () { + onCompletionStub = sandbox.stub() + interval = new timeoutUtils.Interval(1000, onCompletionStub) + }) + + afterEach(async function () { + interval?.dispose() + }) + + it('Executes the callback on an interval', async function () { + await clock.tickAsync(999) + assert.strictEqual(onCompletionStub.callCount, 0) + await clock.tickAsync(1) + assert.strictEqual(onCompletionStub.callCount, 1) + + await clock.tickAsync(500) + assert.strictEqual(onCompletionStub.callCount, 1) + await clock.tickAsync(500) + assert.strictEqual(onCompletionStub.callCount, 2) + + await clock.tickAsync(1000) + assert.strictEqual(onCompletionStub.callCount, 3) + }) + + it('allows to wait for next completion', async function () { + clock.uninstall() + + let curr = 'Did Not Change' + + const realInterval = new timeoutUtils.Interval(50, async () => { + await sleep(50) + curr = 'Did Change' + }) + + const withoutWait = curr + await realInterval.nextCompletion() + const withWait = curr + + assert.strictEqual(withoutWait, 'Did Not Change') + assert.strictEqual(withWait, 'Did Change') + }) + }) + describe('waitUntil', async function () { const testSettings = { callCounter: 0,