From 2f168bce99e1577ca3eb40888a9b3e4ad5c36cc0 Mon Sep 17 00:00:00 2001 From: Guillaume Duboc Date: Fri, 16 Dec 2022 17:35:20 +0100 Subject: [PATCH] feat(timer): add async method to advance timers --- .../vitest/src/integrations/mock/timers.ts | 23 ++ packages/vitest/src/integrations/vi.ts | 15 + test/core/test/timers.test.ts | 258 ++++++++++++++++++ 3 files changed, 296 insertions(+) diff --git a/packages/vitest/src/integrations/mock/timers.ts b/packages/vitest/src/integrations/mock/timers.ts index e8d88cbb9f97..2731204dc2e8 100644 --- a/packages/vitest/src/integrations/mock/timers.ts +++ b/packages/vitest/src/integrations/mock/timers.ts @@ -62,6 +62,11 @@ export class FakeTimers { this._clock.runToLast() } + async runOnlyPendingTimersAsync(): Promise { + if (this._checkFakeTimers()) + await this._clock.runToLastAsync() + } + advanceTimersToNextTimer(steps = 1): void { if (this._checkFakeTimers()) { for (let i = steps; i > 0; i--) { @@ -75,11 +80,29 @@ export class FakeTimers { } } + async advanceTimersToNextTimerAsync(steps = 1): Promise { + if (this._checkFakeTimers()) { + for (let i = steps; i > 0; i--) { + await this._clock.nextAsync() + // Fire all timers at this point: https://github.com/sinonjs/fake-timers/issues/250 + this._clock.tick(0) + + if (this._clock.countTimers() === 0) + break + } + } + } + advanceTimersByTime(msToRun: number): void { if (this._checkFakeTimers()) this._clock.tick(msToRun) } + async advanceTimersByTimeAsync(msToRun: number): Promise { + if (this._checkFakeTimers()) + await this._clock.tickAsync(msToRun) + } + runAllTicks(): void { if (this._checkFakeTimers()) { // @ts-expect-error method not exposed diff --git a/packages/vitest/src/integrations/vi.ts b/packages/vitest/src/integrations/vi.ts index a239a807dc9a..398fa4c47142 100644 --- a/packages/vitest/src/integrations/vi.ts +++ b/packages/vitest/src/integrations/vi.ts @@ -61,6 +61,11 @@ class VitestUtils { return this } + public async runOnlyPendingTimersAsync() { + await this._timers.runOnlyPendingTimersAsync() + return this + } + public runAllTimers() { this._timers.runAllTimers() return this @@ -81,11 +86,21 @@ class VitestUtils { return this } + public async advanceTimersByTimeAsync(ms: number) { + await this._timers.advanceTimersByTimeAsync(ms) + return this + } + public advanceTimersToNextTimer() { this._timers.advanceTimersToNextTimer() return this } + public async advanceTimersToNextTimerAsync() { + await this._timers.advanceTimersToNextTimerAsync() + return this + } + public getTimerCount() { return this._timers.getTimerCount() } diff --git a/test/core/test/timers.test.ts b/test/core/test/timers.test.ts index c3df8b8af98e..85d821ddffc9 100644 --- a/test/core/test/timers.test.ts +++ b/test/core/test/timers.test.ts @@ -515,6 +515,55 @@ describe('FakeTimers', () => { }) }) + describe('advanceTimersByTimeAsync', () => { + it('runs timers in order', async () => { + const global = { Date: FakeDate, clearTimeout, clearInterval, process, setTimeout, setInterval, Promise } + const timers = new FakeTimers({ global }) + timers.useFakeTimers() + + const runOrder = [] + const mock1 = vi.fn(() => runOrder.push('mock1')) + const mock2 = vi.fn(() => runOrder.push('mock2')) + const mock3 = vi.fn(() => runOrder.push('mock3')) + const mock4 = vi.fn(() => runOrder.push('mock4')) + + global.setTimeout(mock1, 100) + global.setTimeout(mock2, 0) + global.setTimeout(mock3, 0) + global.setInterval(() => { + mock4() + }, 200) + + // Move forward to t=50 + await timers.advanceTimersByTimeAsync(50) + expect(runOrder).toEqual(['mock2', 'mock3']) + + // Move forward to t=60 + await timers.advanceTimersByTimeAsync(10) + expect(runOrder).toEqual(['mock2', 'mock3']) + + // Move forward to t=100 + await timers.advanceTimersByTimeAsync(40) + expect(runOrder).toEqual(['mock2', 'mock3', 'mock1']) + + // Move forward to t=200 + await timers.advanceTimersByTimeAsync(100) + expect(runOrder).toEqual(['mock2', 'mock3', 'mock1', 'mock4']) + + // Move forward to t=400 + await timers.advanceTimersByTimeAsync(200) + expect(runOrder).toEqual(['mock2', 'mock3', 'mock1', 'mock4', 'mock4']) + }) + + it('does nothing when no timers have been scheduled', async () => { + const global = { Date: FakeDate, clearTimeout, process, setTimeout, Promise } + const timers = new FakeTimers({ global }) + timers.useFakeTimers() + + await timers.advanceTimersByTimeAsync(100) + }) + }) + describe('advanceTimersToNextTimer', () => { it('runs timers in order', () => { const global = { Date: FakeDate, clearTimeout, process, setTimeout } @@ -617,6 +666,108 @@ describe('FakeTimers', () => { }) }) + describe('advanceTimersToNextTimerAsync', () => { + it('runs timers in order', async () => { + const global = { Date: FakeDate, clearTimeout, process, setTimeout, Promise } + const timers = new FakeTimers({ global }) + timers.useFakeTimers() + + const runOrder: Array = [] + const mock1 = vi.fn(() => runOrder.push('mock1')) + const mock2 = vi.fn(() => runOrder.push('mock2')) + const mock3 = vi.fn(() => runOrder.push('mock3')) + const mock4 = vi.fn(() => runOrder.push('mock4')) + + global.setTimeout(mock1, 100) + global.setTimeout(mock2, 0) + global.setTimeout(mock3, 0) + global.setInterval(() => { + mock4() + }, 200) + + await timers.advanceTimersToNextTimer() + // Move forward to t=0 + expect(runOrder).toEqual(['mock2', 'mock3']) + + await timers.advanceTimersToNextTimer() + // Move forward to t=100 + expect(runOrder).toEqual(['mock2', 'mock3', 'mock1']) + + await timers.advanceTimersToNextTimer() + // Move forward to t=200 + expect(runOrder).toEqual(['mock2', 'mock3', 'mock1', 'mock4']) + + await timers.advanceTimersToNextTimer() + // Move forward to t=400 + expect(runOrder).toEqual(['mock2', 'mock3', 'mock1', 'mock4', 'mock4']) + }) + + it('run correct amount of steps', async () => { + const global = { Date: FakeDate, clearTimeout, process, setTimeout, Promise } + const timers = new FakeTimers({ global }) + timers.useFakeTimers() + + const runOrder: Array = [] + const mock1 = vi.fn(() => runOrder.push('mock1')) + const mock2 = vi.fn(() => runOrder.push('mock2')) + const mock3 = vi.fn(() => runOrder.push('mock3')) + const mock4 = vi.fn(() => runOrder.push('mock4')) + + global.setTimeout(mock1, 100) + global.setTimeout(mock2, 0) + global.setTimeout(mock3, 0) + global.setInterval(() => { + mock4() + }, 200) + + // Move forward to t=100 + await timers.advanceTimersToNextTimer(2) + expect(runOrder).toEqual(['mock2', 'mock3', 'mock1']) + + // Move forward to t=600 + await timers.advanceTimersToNextTimer(3) + expect(runOrder).toEqual([ + 'mock2', + 'mock3', + 'mock1', + 'mock4', + 'mock4', + 'mock4', + ]) + }) + + it('setTimeout inside setTimeout', async () => { + const global = { Date: FakeDate, clearTimeout, process, setTimeout, Promise } + const timers = new FakeTimers({ global }) + timers.useFakeTimers() + + const runOrder: Array = [] + const mock1 = vi.fn(() => runOrder.push('mock1')) + const mock2 = vi.fn(() => runOrder.push('mock2')) + const mock3 = vi.fn(() => runOrder.push('mock3')) + const mock4 = vi.fn(() => runOrder.push('mock4')) + + global.setTimeout(mock1, 0) + global.setTimeout(() => { + mock2() + global.setTimeout(mock3, 50) + }, 25) + global.setTimeout(mock4, 100) + + // Move forward to t=75 + await timers.advanceTimersToNextTimer(3) + expect(runOrder).toEqual(['mock1', 'mock2', 'mock3']) + }) + + it('does nothing when no timers have been scheduled', async () => { + const global = { Date: FakeDate, clearTimeout, process, setTimeout, Promise } + const timers = new FakeTimers({ global }) + timers.useFakeTimers() + + await timers.advanceTimersToNextTimer() + }) + }) + describe('reset', () => { it('resets all pending setTimeouts', () => { const global = { Date: FakeDate, clearTimeout, process, setTimeout } @@ -769,6 +920,113 @@ describe('FakeTimers', () => { }) }) + describe('runOnlyPendingTimersAsync', () => { + it('runs all existing timers', async () => { + const global = { + Date: FakeDate, + clearTimeout, + process, + setTimeout, + Promise, + } + + const timers = new FakeTimers({ global }) + timers.useFakeTimers() + + const spies = [vi.fn(), vi.fn()] + global.setTimeout(spies[0], 10) + global.setTimeout(spies[1], 50) + + await timers.runOnlyPendingTimersAsync() + + expect(spies[0]).toBeCalled() + expect(spies[1]).toBeCalled() + }) + + it('runs all timers in order', async () => { + const global = { + Date: FakeDate, + clearTimeout, + process, + setImmediate, + setTimeout, + Promise, + } + + const timers = new FakeTimers({ global }) + timers.useFakeTimers() + + const runOrder = [] + + global.setTimeout(function cb() { + runOrder.push('mock1') + global.setTimeout(cb, 100) + }, 100) + + global.setTimeout(function cb() { + runOrder.push('mock2') + global.setTimeout(cb, 50) + }, 0) + + global.setInterval(() => { + runOrder.push('mock3') + }, 200) + + global.setImmediate(() => { + runOrder.push('mock4') + }) + + global.setImmediate(function cb() { + runOrder.push('mock5') + global.setTimeout(cb, 400) + }) + + await timers.runOnlyPendingTimersAsync() + const firsRunOrder = [ + 'mock4', + 'mock5', + 'mock2', + 'mock2', + 'mock1', + 'mock2', + 'mock2', + 'mock3', + 'mock1', + 'mock2', + ] + + expect(runOrder).toEqual(firsRunOrder) + + await timers.runOnlyPendingTimersAsync() + expect(runOrder).toEqual([ + ...firsRunOrder, + 'mock2', + 'mock1', + 'mock2', + 'mock2', + 'mock3', + 'mock5', + 'mock1', + 'mock2', + ]) + }) + + it('does not run timers that were cleared in another timer', async () => { + const global = { Date: FakeDate, clearTimeout, process, setTimeout, Promise } + const timers = new FakeTimers({ global }) + timers.useFakeTimers() + + const fn = vi.fn() + const timer = global.setTimeout(fn, 10) + global.setTimeout(() => { + global.clearTimeout(timer) + }, 0) + + await timers.runOnlyPendingTimersAsync() + expect(fn).not.toBeCalled() + }) + }) + describe('useRealTimers', () => { it('resets native timer APIs', () => { const nativeSetTimeout = vi.fn()