Skip to content

Commit

Permalink
feat(timer): add async method to advance timers
Browse files Browse the repository at this point in the history
  • Loading branch information
guillaumeduboc committed Jan 13, 2023
1 parent e857e0a commit 2f168bc
Show file tree
Hide file tree
Showing 3 changed files with 296 additions and 0 deletions.
23 changes: 23 additions & 0 deletions packages/vitest/src/integrations/mock/timers.ts
Expand Up @@ -62,6 +62,11 @@ export class FakeTimers {
this._clock.runToLast()
}

async runOnlyPendingTimersAsync(): Promise<void> {
if (this._checkFakeTimers())
await this._clock.runToLastAsync()
}

advanceTimersToNextTimer(steps = 1): void {
if (this._checkFakeTimers()) {
for (let i = steps; i > 0; i--) {
Expand All @@ -75,11 +80,29 @@ export class FakeTimers {
}
}

async advanceTimersToNextTimerAsync(steps = 1): Promise<void> {
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<void> {
if (this._checkFakeTimers())
await this._clock.tickAsync(msToRun)
}

runAllTicks(): void {
if (this._checkFakeTimers()) {
// @ts-expect-error method not exposed
Expand Down
15 changes: 15 additions & 0 deletions packages/vitest/src/integrations/vi.ts
Expand Up @@ -61,6 +61,11 @@ class VitestUtils {
return this
}

public async runOnlyPendingTimersAsync() {
await this._timers.runOnlyPendingTimersAsync()
return this
}

public runAllTimers() {
this._timers.runAllTimers()
return this
Expand All @@ -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()
}
Expand Down
258 changes: 258 additions & 0 deletions test/core/test/timers.test.ts
Expand Up @@ -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 }
Expand Down Expand Up @@ -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<string> = []
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<string> = []
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<string> = []
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 }
Expand Down Expand Up @@ -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()
Expand Down

0 comments on commit 2f168bc

Please sign in to comment.