Skip to content
Draft
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
3 changes: 3 additions & 0 deletions src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ export interface Options {
/**
* A function to be called internally to advance your fake timers (if applicable)
*
* Fake timers installed by Jest or Vitest are advanced automatically.
* Set this option if you use another implementation of 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 @@ -16,6 +16,7 @@ import {System} from '../system'
import {userEventApi} from './api'
import {wrapAsync} from './wrapAsync'
import {DirectOptions} from './directApi'
import {advanceFakeTimers} from '../utils/misc/timerDetection'

/**
* Default options applied when API is called per `userEvent.anyApi()`
Expand All @@ -32,7 +33,7 @@ const defaultOptionsDirect: Required<Options> = {
skipClick: false,
skipHover: false,
writeToClipboard: false,
advanceTimers: () => Promise.resolve(),
advanceTimers: advanceFakeTimers,
}

/**
Expand Down
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)
}
}
26 changes: 26 additions & 0 deletions tests/utils/misc/timerDetection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import {advanceFakeTimers} from '#src/utils/misc/timerDetection'

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

advanceFakeTimers(100)

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

test('do nothing when fake timers are not installed', () => {
expect(() => advanceFakeTimers(100)).not.toThrow()
})

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

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

timers.useRealTimers()
})
36 changes: 36 additions & 0 deletions tests/utils/misc/wait.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,39 @@ 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()
const beforeFake = performance.now()

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

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

test('does not interfere with real timers', async () => {
const config = createConfig({delay: 1})

await wait(config)
})

test('configured function takes precedence over fake timer detection', async () => {
timers.useFakeTimers()

const calls: number[] = []
const config = createConfig({
delay: 100,
advanceTimers: t => {
calls.push(t)
timers.advanceTimersByTime(t)
},
})
await wait(config)

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