Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ export interface Options {
/**
* A function to be called internally to advance your fake timers (if applicable)
*
* Fake timers installed by Vitest or Jest's modern fake timers are advanced
* automatically.
* Set this option if you use another implementation of fake timers,
* e.g. Jest's legacy fake timers.
*
* @example jest.advanceTimersByTime
*/
advanceTimers?: ((delay: number) => Promise<void>) | ((delay: number) => void)
Expand Down
3 changes: 2 additions & 1 deletion src/setup/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {defaultKeyMap as defaultKeyboardMap} from '../keyboard/keyMap'
import {defaultKeyMap as defaultPointerMap} from '../pointer/keyMap'
import {Options, PointerEventsCheckLevel} from '../options'
import {
advanceFakeTimers,
ApiLevel,
attachClipboardStubToView,
getDocumentFromNode,
Expand Down Expand Up @@ -32,7 +33,7 @@ const defaultOptionsDirect: Required<Options> = {
skipClick: false,
skipHover: false,
writeToClipboard: false,
advanceTimers: () => Promise.resolve(),
advanceTimers: advanceFakeTimers,
}

/**
Expand Down
1 change: 1 addition & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export * from './misc/isElementType'
export * from './misc/isVisible'
export * from './misc/isDisabled'
export * from './misc/level'
export * from './misc/timerDetection'
export * from './misc/wait'

export * from './pointer/cssPointerEvents'
22 changes: 22 additions & 0 deletions src/utils/misc/timerDetection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
interface FakeClock {
tick: (ms: number) => void
}

/**
* Advance fake timers if the test framework installed some.
*
* Jest's modern fake timers and Vitest's fake timers are both built on
* `@sinonjs/fake-timers`, which exposes the installed clock on each timer
* function it replaces.
* Looking for that clock instead of a `jest`/`vi` global detects fake timers
* however the framework is imported, and only while they are installed.
*/
export function advanceFakeTimers(delay: number): void {
const {clock} = globalThis.setTimeout as typeof globalThis.setTimeout & {
clock?: FakeClock
}

if (typeof clock?.tick === 'function') {
clock.tick(delay)
}
}
36 changes: 36 additions & 0 deletions tests/utils/misc/timerDetection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import {advanceFakeTimers} from '#src/utils'

test('advance fake timers installed on the global scope', () => {
timers.useFakeTimers()
try {
let fired = false
globalThis.setTimeout(() => {
fired = true
}, 100)

advanceFakeTimers(100)

expect(fired).toBe(true)
} finally {
timers.useRealTimers()
}
})

test('do nothing when fake timers are not installed', () => {
expect(globalThis.setTimeout).not.toHaveProperty('clock')

expect(() => advanceFakeTimers(100)).not.toThrow()
})

test('do nothing when the global timers are not faked', () => {
timers.useFakeTimers({toFake: ['Date']})
try {
const before = Date.now()

expect(() => advanceFakeTimers(100)).not.toThrow()

expect(Date.now()).toBe(before)
} finally {
timers.useRealTimers()
}
})
42 changes: 42 additions & 0 deletions tests/utils/misc/wait.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,45 @@ test('advances timers when set', async () => {
timers.useRealTimers()
expect(performance.now() - beforeReal).toBeLessThan(1000)
}, 10)

test('advances fake timers without being configured', async () => {
const beforeReal = performance.now()
timers.useFakeTimers()
try {
const beforeFake = performance.now()

const config = createConfig({delay: 500})
await wait(config)

expect(performance.now() - beforeFake).toBe(500)
} finally {
timers.useRealTimers()
}
expect(performance.now() - beforeReal).toBeLessThan(1000)
}, 10)

test('does not interfere with real timers', async () => {
expect(globalThis.setTimeout).not.toHaveProperty('clock')

const config = createConfig({delay: 1})
await wait(config)
})

test('configured function takes precedence over fake timer detection', async () => {
timers.useFakeTimers()
try {
const calls: number[] = []
const config = createConfig({
delay: 100,
advanceTimers: t => {
calls.push(t)
timers.advanceTimersByTime(t)
},
})
await wait(config)

expect(calls).toEqual([100])
} finally {
timers.useRealTimers()
}
}, 10)