diff --git a/src/__tests__/asyncHook.fakeTimers.test.ts b/src/__tests__/asyncHook.fakeTimers.test.ts index 98d6b2c9..69bff39d 100644 --- a/src/__tests__/asyncHook.fakeTimers.test.ts +++ b/src/__tests__/asyncHook.fakeTimers.test.ts @@ -51,6 +51,41 @@ describe('async hook (fake timers) tests', () => { expect(complete).toBe(true) }) + + test('should waitFor arbitrary expectation to pass when fake timers are not advanced explicitly', async () => { + const fn = jest.fn().mockReturnValueOnce(false).mockReturnValueOnce(true) + + const { waitFor } = renderHook(() => null) + + await waitFor(() => { + expect(fn()).toBe(true) + }) + }) + + test('should reject if timeout is passed close to when promise resolves', async () => { + const { waitFor } = renderHook(() => null) + + let actual = 0 + const expected = 1 + + setTimeout(() => { + actual = expected + }, 101) + + let complete = false + + await expect( + waitFor( + () => { + expect(actual).toBe(expected) + complete = true + }, + { timeout: 100, interval: 50 } + ) + ).rejects.toThrow(Error('Timed out in waitFor after 100ms.')) + + expect(complete).toBe(false) + }) }) }) diff --git a/src/core/asyncUtils.ts b/src/core/asyncUtils.ts index a7424036..e759e54a 100644 --- a/src/core/asyncUtils.ts +++ b/src/core/asyncUtils.ts @@ -7,11 +7,10 @@ import { AsyncUtils } from '../types' -import { createTimeoutController } from '../helpers/createTimeoutController' +import { createTimeoutController, DEFAULT_TIMEOUT } from '../helpers/createTimeoutController' import { TimeoutError } from '../helpers/error' const DEFAULT_INTERVAL = 50 -const DEFAULT_TIMEOUT = 1000 function asyncUtils(act: Act, addResolver: (callback: () => void) => void): AsyncUtils { const wait = async (callback: () => boolean | void, { interval, timeout }: WaitOptions) => { @@ -20,11 +19,11 @@ function asyncUtils(act: Act, addResolver: (callback: () => void) => void): Asyn return callbackResult ?? callbackResult === undefined } - const timeoutSignal = createTimeoutController(timeout) + const timeoutSignal = createTimeoutController(timeout, false) const waitForResult = async () => { while (true) { - const intervalSignal = createTimeoutController(interval) + const intervalSignal = createTimeoutController(interval, true) timeoutSignal.onTimeout(() => intervalSignal.cancel()) await intervalSignal.wrap(new Promise(addResolver)) diff --git a/src/helpers/createTimeoutController.ts b/src/helpers/createTimeoutController.ts index 643d3768..c7fe9326 100644 --- a/src/helpers/createTimeoutController.ts +++ b/src/helpers/createTimeoutController.ts @@ -1,9 +1,18 @@ import { WaitOptions } from '../types' +import { jestFakeTimersAreEnabled } from './jestFakeTimersAreEnabled' +const DEFAULT_TIMEOUT = 1000 -function createTimeoutController(timeout: WaitOptions['timeout']) { +function createTimeoutController( + timeout: WaitOptions['timeout'] = DEFAULT_TIMEOUT, + allowFakeTimers: boolean +) { let timeoutId: NodeJS.Timeout const timeoutCallbacks: Array<() => void> = [] + const advanceTime = async () => { + jest.advanceTimersByTime(timeout as number) + await Promise.resolve() + } const timeoutController = { onTimeout(callback: () => void) { timeoutCallbacks.push(callback) @@ -19,12 +28,18 @@ function createTimeoutController(timeout: WaitOptions['timeout']) { timeoutCallbacks.forEach((callback) => callback()) resolve() }, timeout) + + if (jestFakeTimersAreEnabled() && allowFakeTimers) { + advanceTime() + } } promise .then(resolve) .catch(reject) - .finally(() => timeoutController.cancel()) + .finally(() => { + timeoutController.cancel() + }) }) }, cancel() { @@ -36,4 +51,4 @@ function createTimeoutController(timeout: WaitOptions['timeout']) { return timeoutController } -export { createTimeoutController } +export { createTimeoutController, DEFAULT_TIMEOUT } diff --git a/src/helpers/jestFakeTimersAreEnabled.ts b/src/helpers/jestFakeTimersAreEnabled.ts new file mode 100644 index 00000000..5a0e2a88 --- /dev/null +++ b/src/helpers/jestFakeTimersAreEnabled.ts @@ -0,0 +1,13 @@ +export const jestFakeTimersAreEnabled = () => { + /* istanbul ignore else */ + if (typeof jest !== 'undefined' && jest !== null) { + return ( + // legacy timers + jest.isMockFunction(setTimeout) || + // modern timers + Object.prototype.hasOwnProperty.call(setTimeout, 'clock') + ) + } + // istanbul ignore next + return false +}