Skip to content
Open
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
159 changes: 158 additions & 1 deletion packages/query-core/src/__tests__/timeoutManager.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import {
systemSetTimeoutZero,
timeoutManager,
} from '../timeoutManager'
import type { ManagedTimerId } from '../timeoutManager'
import type { MockInstance } from 'vitest'

describe('timeoutManager', () => {
function createMockProvider(name: string = 'custom') {
return {
__TEST_ONLY__name: name,
setTimeout: vi.fn(() => 123),
setTimeout: vi.fn((): ManagedTimerId => 123),
clearTimeout: vi.fn(),
setInterval: vi.fn(() => 456),
clearInterval: vi.fn(),
Expand Down Expand Up @@ -57,6 +58,50 @@ describe('timeoutManager', () => {
expect(clearIntervalSpy).toHaveBeenCalledWith(400)
})

describe('timer behavior', () => {
beforeEach(() => {
vi.useFakeTimers()
})

afterEach(() => {
vi.useRealTimers()
})

it('should invoke the callback after the given delay', async () => {
const callback = vi.fn()
manager.setTimeout(callback, 100)

await vi.advanceTimersByTimeAsync(99)
expect(callback).not.toHaveBeenCalled()

await vi.advanceTimersByTimeAsync(1)
expect(callback).toHaveBeenCalledTimes(1)
})

it('should not invoke the callback after clearTimeout', async () => {
const callback = vi.fn()
const timeoutId = manager.setTimeout(callback, 100)

manager.clearTimeout(timeoutId)
await vi.advanceTimersByTimeAsync(1000)

expect(callback).not.toHaveBeenCalled()
})

it('should invoke interval callbacks repeatedly until clearInterval', async () => {
const callback = vi.fn()
const intervalId = manager.setInterval(callback, 100)

await vi.advanceTimersByTimeAsync(350)
expect(callback).toHaveBeenCalledTimes(3)

manager.clearInterval(intervalId)
await vi.advanceTimersByTimeAsync(300)

expect(callback).toHaveBeenCalledTimes(3)
})
})

describe('setTimeoutProvider', () => {
it('proxies calls to the configured timeout provider', () => {
const customProvider = createMockProvider()
Expand Down Expand Up @@ -102,6 +147,93 @@ describe('timeoutManager', () => {
manager.setTimeoutProvider(customProvider3)
expect(consoleErrorSpy).not.toHaveBeenCalled()
})

it('warns when switching providers after an setInterval call', () => {
const customProvider = createMockProvider('custom')
manager.setTimeoutProvider(customProvider)
manager.setInterval(vi.fn(), 100)

const customProvider2 = createMockProvider('custom2')
manager.setTimeoutProvider(customProvider2)

expect(consoleErrorSpy).toHaveBeenCalledWith(
expect.stringMatching(
/\[timeoutManager\]: Switching .* might result in unexpected behavior\..*/,
),
{ previous: customProvider, provider: customProvider2 },
)
})

it('does not warn when re-setting the same provider after calls', () => {
const customProvider = createMockProvider()
manager.setTimeoutProvider(customProvider)
manager.setTimeout(vi.fn(), 100)

manager.setTimeoutProvider(customProvider)

expect(consoleErrorSpy).not.toHaveBeenCalled()
})

it('does not warn when only clear functions were called before switching', () => {
const customProvider = createMockProvider('custom')
manager.setTimeoutProvider(customProvider)
manager.clearTimeout(1)
manager.clearInterval(2)

const customProvider2 = createMockProvider('custom2')
manager.setTimeoutProvider(customProvider2)

expect(consoleErrorSpy).not.toHaveBeenCalled()
})

it('does not warn when switching providers after calls in production', () => {
try {
vi.stubEnv('NODE_ENV', 'production')

const customProvider = createMockProvider('custom')
manager.setTimeoutProvider(customProvider)
manager.setTimeout(vi.fn(), 100)

const customProvider2 = createMockProvider('custom2')
manager.setTimeoutProvider(customProvider2)

expect(consoleErrorSpy).not.toHaveBeenCalled()
} finally {
vi.unstubAllEnvs()
}
})

it('passes non-number timer ids through to the provider untouched', () => {
const customProvider = createMockProvider()
const objectTimerId = { [Symbol.toPrimitive]: () => 123 }
customProvider.setTimeout.mockReturnValueOnce(objectTimerId)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
manager.setTimeoutProvider(customProvider)

const timeoutId = manager.setTimeout(vi.fn(), 100)
expect(timeoutId).toBe(objectTimerId)

manager.clearTimeout(timeoutId)
expect(customProvider.clearTimeout).toHaveBeenCalledWith(objectTimerId)
})

it('forwards undefined timer ids to the clear functions as a no-op', () => {
const customProvider = createMockProvider()
manager.setTimeoutProvider(customProvider)

expect(() => manager.clearTimeout(undefined)).not.toThrow()
expect(customProvider.clearTimeout).toHaveBeenCalledWith(undefined)

expect(() => manager.clearInterval(undefined)).not.toThrow()
expect(customProvider.clearInterval).toHaveBeenCalledWith(undefined)
})

it('returns the timer ids produced by the provider', () => {
const customProvider = createMockProvider()
manager.setTimeoutProvider(customProvider)

expect(manager.setTimeout(vi.fn(), 100)).toBe(123)
expect(manager.setInterval(vi.fn(), 100)).toBe(456)
})
})
})

Expand Down Expand Up @@ -131,6 +263,31 @@ describe('timeoutManager', () => {
expect(spy).toHaveBeenCalledWith(callback, 0)
clearTimeout(spy.mock.results[0]?.value)
})

it('should not be mediated by the timeoutManager provider', () => {
const spy = vi.spyOn(globalThis, 'setTimeout')

const callback = vi.fn()
systemSetTimeoutZero(callback)

expect(spy).toHaveBeenCalledTimes(1)
expect(provider.setTimeout).not.toHaveBeenCalled()
clearTimeout(spy.mock.results[0]?.value)
})

it('should invoke the callback on the next event loop tick', async () => {
vi.useFakeTimers()
try {
const callback = vi.fn()
systemSetTimeoutZero(callback)
expect(callback).not.toHaveBeenCalled()

await vi.advanceTimersByTimeAsync(0)
expect(callback).toHaveBeenCalledTimes(1)
} finally {
vi.useRealTimers()
}
})
})
})
})