From ac1b5597fba3398336f1087d0fbddd3ab982aade Mon Sep 17 00:00:00 2001 From: Lei Chen Date: Mon, 6 Sep 2021 12:33:29 -0700 Subject: [PATCH 01/11] fix fakeTimer problem add new fakeTimer test and revise the function add advanceTime revise the advanceTime use jest.advanceTimersByTime change timeout type fix converage and revise type --- src/__tests__/asyncHook.fakeTimers.test.ts | 35 ++++++++++++++++++++++ src/core/asyncUtils.ts | 6 ++-- src/helpers/createTimeoutController.ts | 10 +++++-- src/helpers/jestFakeTimersAreEnabled.ts | 13 ++++++++ 4 files changed, 58 insertions(+), 6 deletions(-) create mode 100644 src/helpers/jestFakeTimersAreEnabled.ts 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..a62e5940 100644 --- a/src/core/asyncUtils.ts +++ b/src/core/asyncUtils.ts @@ -10,8 +10,8 @@ import { import { createTimeoutController } from '../helpers/createTimeoutController' import { TimeoutError } from '../helpers/error' -const DEFAULT_INTERVAL = 50 const DEFAULT_TIMEOUT = 1000 +const DEFAULT_INTERVAL = 50 function asyncUtils(act: Act, addResolver: (callback: () => void) => void): AsyncUtils { const wait = async (callback: () => boolean | void, { interval, timeout }: WaitOptions) => { @@ -20,11 +20,11 @@ function asyncUtils(act: Act, addResolver: (callback: () => void) => void): Asyn return callbackResult ?? callbackResult === undefined } - const timeoutSignal = createTimeoutController(timeout) + const timeoutSignal = createTimeoutController(timeout as number | boolean, false) const waitForResult = async () => { while (true) { - const intervalSignal = createTimeoutController(interval) + const intervalSignal = createTimeoutController(interval as number | boolean, 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..033d052f 100644 --- a/src/helpers/createTimeoutController.ts +++ b/src/helpers/createTimeoutController.ts @@ -1,6 +1,6 @@ -import { WaitOptions } from '../types' +import { jestFakeTimersAreEnabled } from './jestFakeTimersAreEnabled' -function createTimeoutController(timeout: WaitOptions['timeout']) { +function createTimeoutController(timeout: number | boolean, allowFakeTimers: boolean) { let timeoutId: NodeJS.Timeout const timeoutCallbacks: Array<() => void> = [] @@ -18,7 +18,11 @@ function createTimeoutController(timeout: WaitOptions['timeout']) { timeoutController.timedOut = true timeoutCallbacks.forEach((callback) => callback()) resolve() - }, timeout) + }, timeout as number) + + if (jestFakeTimersAreEnabled() && allowFakeTimers) { + jest.advanceTimersByTime(timeout as number) + } } promise 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 +} From 70610e419e9dfef326c06932f5515193ef57b84b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Sep 2021 10:28:54 +0100 Subject: [PATCH 02/11] chore(deps-dev): bump @typescript-eslint/parser from 4.30.0 to 4.31.0 (#689) Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 4.30.0 to 4.31.0. - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/parser/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.31.0/packages/parser) --- updated-dependencies: - dependency-name: "@typescript-eslint/parser" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3a854f40..7c26c013 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ }, "devDependencies": { "@typescript-eslint/eslint-plugin": "4.30.0", - "@typescript-eslint/parser": "4.30.0", + "@typescript-eslint/parser": "4.31.0", "all-contributors-cli": "6.20.0", "codecov": "3.8.3", "cross-env": "^7.0.3", From 035b437924314517d93d2fea6725506fc9c93e12 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 7 Sep 2021 11:02:49 +0100 Subject: [PATCH 03/11] chore(deps-dev): bump @typescript-eslint/eslint-plugin (#690) Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.30.0 to 4.31.0. - [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases) - [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/CHANGELOG.md) - [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v4.31.0/packages/eslint-plugin) --- updated-dependencies: - dependency-name: "@typescript-eslint/eslint-plugin" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7c26c013..eeef84a7 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "react-error-boundary": "^3.1.0" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "4.30.0", + "@typescript-eslint/eslint-plugin": "4.31.0", "@typescript-eslint/parser": "4.31.0", "all-contributors-cli": "6.20.0", "codecov": "3.8.3", From b5ae5f8ae6285400e27c35977514c312c36b96dc Mon Sep 17 00:00:00 2001 From: Michael Peyper Date: Thu, 9 Sep 2021 20:28:22 +1000 Subject: [PATCH 04/11] test(fake-timers): add more tests to test suite for fake timers fix the code after code review and clean up fix the test timeout is false clean up fix coverage add skip for pass checkers add comment --- src/__tests__/asyncHook.fakeTimers.test.ts | 264 ++++++++++++++++++++- src/core/asyncUtils.ts | 22 +- src/helpers/createTimeoutController.ts | 39 ++- 3 files changed, 300 insertions(+), 25 deletions(-) diff --git a/src/__tests__/asyncHook.fakeTimers.test.ts b/src/__tests__/asyncHook.fakeTimers.test.ts index 69bff39d..1bffd9e6 100644 --- a/src/__tests__/asyncHook.fakeTimers.test.ts +++ b/src/__tests__/asyncHook.fakeTimers.test.ts @@ -1,4 +1,27 @@ +import { useState, useRef, useEffect } from 'react' + describe('async hook (fake timers) tests', () => { + const useSequence = (values: string[], intervalMs = 50) => { + const [first, ...otherValues] = values + const [value, setValue] = useState(() => first) + const index = useRef(0) + + useEffect(() => { + const interval = setInterval(() => { + setValue(otherValues[index.current++]) + if (index.current >= otherValues.length) { + clearInterval(interval) + } + }, intervalMs) + return () => { + clearInterval(interval) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, otherValues) + + return value + } + beforeEach(() => { jest.useFakeTimers() }) @@ -70,7 +93,7 @@ describe('async hook (fake timers) tests', () => { setTimeout(() => { actual = expected - }, 101) + }, 30) let complete = false @@ -80,14 +103,243 @@ describe('async hook (fake timers) tests', () => { expect(actual).toBe(expected) complete = true }, - { timeout: 100, interval: 50 } + { timeout: 29, interval: 10 } ) - ).rejects.toThrow(Error('Timed out in waitFor after 100ms.')) + ).rejects.toThrow(Error('Timed out in waitFor after 29ms.')) expect(complete).toBe(false) }) + + test('should wait for next update', async () => { + const { result, waitForNextUpdate } = renderHook(() => useSequence(['first', 'second'])) + + expect(result.current).toBe('first') + + await waitForNextUpdate() + + expect(result.current).toBe('second') + }) + + test('should wait for multiple updates', async () => { + const { result, waitForNextUpdate } = renderHook(() => + useSequence(['first', 'second', 'third']) + ) + + expect(result.current).toBe('first') + + await waitForNextUpdate() + + expect(result.current).toBe('second') + + await waitForNextUpdate() + + expect(result.current).toBe('third') + }) + + test('should reject if timeout exceeded when waiting for next update', async () => { + const { result, waitForNextUpdate } = renderHook(() => useSequence(['first', 'second'])) + + expect(result.current).toBe('first') + + await expect(waitForNextUpdate({ timeout: 10 })).rejects.toThrow( + Error('Timed out in waitForNextUpdate after 10ms.') + ) + }) + // the test run successfully after remove skip in local. However, it will not pass the checkers on github + test.skip('should not reject when waiting for next update if timeout has been disabled', async () => { + const { result, waitForNextUpdate } = renderHook(() => useSequence(['first', 'second'], 1100)) + + expect(result.current).toBe('first') + + await waitForNextUpdate({ timeout: false }) + + expect(result.current).toBe('second') + }) + + test('should wait for expectation to pass', async () => { + const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'])) + + expect(result.current).toBe('first') + + let complete = false + await waitFor(() => { + expect(result.current).toBe('third') + complete = true + }) + expect(complete).toBe(true) + }) + + test('should wait for arbitrary expectation to pass', async () => { + const { waitFor } = renderHook(() => null) + + let actual = 0 + const expected = 1 + + setTimeout(() => { + actual = expected + }, 200) + + let complete = false + await waitFor(() => { + expect(actual).toBe(expected) + complete = true + }) + + expect(complete).toBe(true) + }) + + test('should not hang if expectation is already passing', async () => { + const { result, waitFor } = renderHook(() => useSequence(['first', 'second'])) + + expect(result.current).toBe('first') + + let complete = false + await waitFor(() => { + expect(result.current).toBe('first') + complete = true + }) + expect(complete).toBe(true) + }) + + test('should wait for truthy value', async () => { + const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'])) + + expect(result.current).toBe('first') + + await waitFor(() => result.current === 'third') + + expect(result.current).toBe('third') + }) + + test('should wait for arbitrary truthy value', async () => { + const { waitFor } = renderHook(() => null) + + let actual = 0 + const expected = 1 + + setTimeout(() => { + actual = expected + }, 200) + + await waitFor(() => actual === 1) + + expect(actual).toBe(expected) + }) + + test('should reject if timeout exceeded when waiting for expectation to pass', async () => { + const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'])) + + expect(result.current).toBe('first') + + await expect( + waitFor( + () => { + expect(result.current).toBe('third') + }, + { timeout: 75 } + ) + ).rejects.toThrow(Error('Timed out in waitFor after 75ms.')) + }) + + test('should not reject when waiting for expectation to pass if timeout has been disabled', async () => { + const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'], 550)) + + expect(result.current).toBe('first') + + await waitFor( + () => { + expect(result.current).toBe('third') + }, + { timeout: false } + ) + + expect(result.current).toBe('third') + }) + + test('should check on interval when waiting for expectation to pass', async () => { + const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'])) + + let checks = 0 + + await waitFor( + () => { + checks++ + return result.current === 'third' + }, + { interval: 100 } + ) + + expect(checks).toBe(3) + }) + + test('should wait for value to change', async () => { + const { result, waitForValueToChange } = renderHook(() => + useSequence(['first', 'second', 'third']) + ) + + expect(result.current).toBe('first') + + await waitForValueToChange(() => result.current === 'third') + + expect(result.current).toBe('third') + }) + + test('should wait for arbitrary value to change', async () => { + const { waitForValueToChange } = renderHook(() => null) + + let actual = 0 + const expected = 1 + + setTimeout(() => { + actual = expected + }, 200) + + await waitForValueToChange(() => actual) + + expect(actual).toBe(expected) + }) + + test('should reject if timeout exceeded when waiting for value to change', async () => { + const { result, waitForValueToChange } = renderHook(() => + useSequence(['first', 'second', 'third']) + ) + + expect(result.current).toBe('first') + + await expect( + waitForValueToChange(() => result.current === 'third', { + timeout: 75 + }) + ).rejects.toThrow(Error('Timed out in waitForValueToChange after 75ms.')) + }) + + test('should not reject when waiting for value to change if timeout is disabled', async () => { + const { result, waitForValueToChange } = renderHook(() => + useSequence(['first', 'second', 'third'], 550) + ) + + expect(result.current).toBe('first') + + await waitForValueToChange(() => result.current === 'third', { + timeout: false + }) + + expect(result.current).toBe('third') + }) + + test('should reject if selector throws error', async () => { + const { result, waitForValueToChange } = renderHook(() => useSequence(['first', 'second'])) + + expect(result.current).toBe('first') + + await expect( + waitForValueToChange(() => { + if (result.current === 'second') { + throw new Error('Something Unexpected') + } + return result.current + }) + ).rejects.toThrow(Error('Something Unexpected')) + }) }) }) - -// eslint-disable-next-line jest/no-export -export {} diff --git a/src/core/asyncUtils.ts b/src/core/asyncUtils.ts index a62e5940..07c795fa 100644 --- a/src/core/asyncUtils.ts +++ b/src/core/asyncUtils.ts @@ -7,39 +7,41 @@ import { AsyncUtils } from '../types' -import { createTimeoutController } from '../helpers/createTimeoutController' +import { createTimeoutController, DEFAULT_TIMEOUT } from '../helpers/createTimeoutController' import { TimeoutError } from '../helpers/error' -const DEFAULT_TIMEOUT = 1000 const DEFAULT_INTERVAL = 50 function asyncUtils(act: Act, addResolver: (callback: () => void) => void): AsyncUtils { - const wait = async (callback: () => boolean | void, { interval, timeout }: WaitOptions) => { + const wait = async ( + callback: () => boolean | void, + { interval, timeout }: Required + ) => { const checkResult = () => { const callbackResult = callback() return callbackResult ?? callbackResult === undefined } - const timeoutSignal = createTimeoutController(timeout as number | boolean, false) + const timeoutController = createTimeoutController(timeout, { allowFakeTimers: !interval }) const waitForResult = async () => { while (true) { - const intervalSignal = createTimeoutController(interval as number | boolean, true) - timeoutSignal.onTimeout(() => intervalSignal.cancel()) + const intervalController = createTimeoutController(interval, { allowFakeTimers: true }) + timeoutController.onTimeout(() => intervalController.cancel()) - await intervalSignal.wrap(new Promise(addResolver)) + await intervalController.wrap(new Promise(addResolver)) - if (checkResult() || timeoutSignal.timedOut) { + if (checkResult() || timeoutController.timedOut) { return } } } if (!checkResult()) { - await act(() => timeoutSignal.wrap(waitForResult())) + await act(() => timeoutController.wrap(waitForResult())) } - return !timeoutSignal.timedOut + return !timeoutController.timedOut } const waitFor = async ( diff --git a/src/helpers/createTimeoutController.ts b/src/helpers/createTimeoutController.ts index 033d052f..5f8b5108 100644 --- a/src/helpers/createTimeoutController.ts +++ b/src/helpers/createTimeoutController.ts @@ -1,8 +1,27 @@ import { jestFakeTimersAreEnabled } from './jestFakeTimersAreEnabled' -function createTimeoutController(timeout: number | boolean, allowFakeTimers: boolean) { +const DEFAULT_TIMEOUT = 1000 + +function createTimeoutController(timeout: number | false, options: { allowFakeTimers: boolean }) { let timeoutId: NodeJS.Timeout const timeoutCallbacks: Array<() => void> = [] + let finished = false + + const { allowFakeTimers } = options + + const advanceTime = async (currentMs: number) => { + // eslint-disable-next-line no-negated-condition + if (currentMs < (!timeout ? DEFAULT_TIMEOUT : timeout)) { + jest.advanceTimersByTime(1) + + await Promise.resolve() + + if (finished) { + return + } + await advanceTime(currentMs + 1) + } + } const timeoutController = { onTimeout(callback: () => void) { @@ -12,26 +31,28 @@ function createTimeoutController(timeout: number | boolean, allowFakeTimers: boo return new Promise((resolve, reject) => { timeoutController.timedOut = false timeoutController.onTimeout(resolve) - if (timeout) { timeoutId = setTimeout(() => { timeoutController.timedOut = true timeoutCallbacks.forEach((callback) => callback()) resolve() - }, timeout as number) - - if (jestFakeTimersAreEnabled() && allowFakeTimers) { - jest.advanceTimersByTime(timeout as number) - } + }, timeout) + } + if (jestFakeTimersAreEnabled() && allowFakeTimers) { + advanceTime(0) } promise .then(resolve) .catch(reject) - .finally(() => timeoutController.cancel()) + .finally(() => { + finished = true + timeoutController.cancel() + }) }) }, cancel() { + finished = true clearTimeout(timeoutId) }, timedOut: false @@ -40,4 +61,4 @@ function createTimeoutController(timeout: number | boolean, allowFakeTimers: boo return timeoutController } -export { createTimeoutController } +export { createTimeoutController, DEFAULT_TIMEOUT } From 625c128c41bd3957476c1dc708f3e3e54595fcd8 Mon Sep 17 00:00:00 2001 From: Michael Peyper Date: Sun, 12 Sep 2021 21:56:28 +1000 Subject: [PATCH 05/11] test(async-utils): enable test to test CI fix --- src/__tests__/asyncHook.fakeTimers.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/__tests__/asyncHook.fakeTimers.test.ts b/src/__tests__/asyncHook.fakeTimers.test.ts index 1bffd9e6..9c1c4559 100644 --- a/src/__tests__/asyncHook.fakeTimers.test.ts +++ b/src/__tests__/asyncHook.fakeTimers.test.ts @@ -145,8 +145,8 @@ describe('async hook (fake timers) tests', () => { Error('Timed out in waitForNextUpdate after 10ms.') ) }) - // the test run successfully after remove skip in local. However, it will not pass the checkers on github - test.skip('should not reject when waiting for next update if timeout has been disabled', async () => { + + test('should not reject when waiting for next update if timeout has been disabled', async () => { const { result, waitForNextUpdate } = renderHook(() => useSequence(['first', 'second'], 1100)) expect(result.current).toBe('first') From 826ff2482c019e57e01cee8c1b59abc2c6acb640 Mon Sep 17 00:00:00 2001 From: Michael Peyper Date: Sun, 19 Sep 2021 22:17:51 +1000 Subject: [PATCH 06/11] test(async-utils): combine fake timer tests with async tests --- src/__tests__/asyncHook.fakeTimers.test.ts | 345 --------------------- src/__tests__/asyncHook.test.ts | 333 ++++++++++---------- 2 files changed, 174 insertions(+), 504 deletions(-) delete mode 100644 src/__tests__/asyncHook.fakeTimers.test.ts diff --git a/src/__tests__/asyncHook.fakeTimers.test.ts b/src/__tests__/asyncHook.fakeTimers.test.ts deleted file mode 100644 index 9c1c4559..00000000 --- a/src/__tests__/asyncHook.fakeTimers.test.ts +++ /dev/null @@ -1,345 +0,0 @@ -import { useState, useRef, useEffect } from 'react' - -describe('async hook (fake timers) tests', () => { - const useSequence = (values: string[], intervalMs = 50) => { - const [first, ...otherValues] = values - const [value, setValue] = useState(() => first) - const index = useRef(0) - - useEffect(() => { - const interval = setInterval(() => { - setValue(otherValues[index.current++]) - if (index.current >= otherValues.length) { - clearInterval(interval) - } - }, intervalMs) - return () => { - clearInterval(interval) - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, otherValues) - - return value - } - - beforeEach(() => { - jest.useFakeTimers() - }) - - afterEach(() => { - jest.useRealTimers() - }) - - runForRenderers(['default', 'dom', 'native', 'server/hydrated'], ({ renderHook }) => { - test('should wait for arbitrary expectation to pass when using advanceTimersByTime()', async () => { - const { waitFor } = renderHook(() => null) - - let actual = 0 - const expected = 1 - - setTimeout(() => { - actual = expected - }, 200) - - let complete = false - - jest.advanceTimersByTime(200) - - await waitFor(() => { - expect(actual).toBe(expected) - complete = true - }) - - expect(complete).toBe(true) - }) - - test('should wait for arbitrary expectation to pass when using runOnlyPendingTimers()', async () => { - const { waitFor } = renderHook(() => null) - - let actual = 0 - const expected = 1 - - setTimeout(() => { - actual = expected - }, 200) - - let complete = false - - jest.runOnlyPendingTimers() - - await waitFor(() => { - expect(actual).toBe(expected) - complete = true - }) - - 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 - }, 30) - - let complete = false - - await expect( - waitFor( - () => { - expect(actual).toBe(expected) - complete = true - }, - { timeout: 29, interval: 10 } - ) - ).rejects.toThrow(Error('Timed out in waitFor after 29ms.')) - - expect(complete).toBe(false) - }) - - test('should wait for next update', async () => { - const { result, waitForNextUpdate } = renderHook(() => useSequence(['first', 'second'])) - - expect(result.current).toBe('first') - - await waitForNextUpdate() - - expect(result.current).toBe('second') - }) - - test('should wait for multiple updates', async () => { - const { result, waitForNextUpdate } = renderHook(() => - useSequence(['first', 'second', 'third']) - ) - - expect(result.current).toBe('first') - - await waitForNextUpdate() - - expect(result.current).toBe('second') - - await waitForNextUpdate() - - expect(result.current).toBe('third') - }) - - test('should reject if timeout exceeded when waiting for next update', async () => { - const { result, waitForNextUpdate } = renderHook(() => useSequence(['first', 'second'])) - - expect(result.current).toBe('first') - - await expect(waitForNextUpdate({ timeout: 10 })).rejects.toThrow( - Error('Timed out in waitForNextUpdate after 10ms.') - ) - }) - - test('should not reject when waiting for next update if timeout has been disabled', async () => { - const { result, waitForNextUpdate } = renderHook(() => useSequence(['first', 'second'], 1100)) - - expect(result.current).toBe('first') - - await waitForNextUpdate({ timeout: false }) - - expect(result.current).toBe('second') - }) - - test('should wait for expectation to pass', async () => { - const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'])) - - expect(result.current).toBe('first') - - let complete = false - await waitFor(() => { - expect(result.current).toBe('third') - complete = true - }) - expect(complete).toBe(true) - }) - - test('should wait for arbitrary expectation to pass', async () => { - const { waitFor } = renderHook(() => null) - - let actual = 0 - const expected = 1 - - setTimeout(() => { - actual = expected - }, 200) - - let complete = false - await waitFor(() => { - expect(actual).toBe(expected) - complete = true - }) - - expect(complete).toBe(true) - }) - - test('should not hang if expectation is already passing', async () => { - const { result, waitFor } = renderHook(() => useSequence(['first', 'second'])) - - expect(result.current).toBe('first') - - let complete = false - await waitFor(() => { - expect(result.current).toBe('first') - complete = true - }) - expect(complete).toBe(true) - }) - - test('should wait for truthy value', async () => { - const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'])) - - expect(result.current).toBe('first') - - await waitFor(() => result.current === 'third') - - expect(result.current).toBe('third') - }) - - test('should wait for arbitrary truthy value', async () => { - const { waitFor } = renderHook(() => null) - - let actual = 0 - const expected = 1 - - setTimeout(() => { - actual = expected - }, 200) - - await waitFor(() => actual === 1) - - expect(actual).toBe(expected) - }) - - test('should reject if timeout exceeded when waiting for expectation to pass', async () => { - const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'])) - - expect(result.current).toBe('first') - - await expect( - waitFor( - () => { - expect(result.current).toBe('third') - }, - { timeout: 75 } - ) - ).rejects.toThrow(Error('Timed out in waitFor after 75ms.')) - }) - - test('should not reject when waiting for expectation to pass if timeout has been disabled', async () => { - const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'], 550)) - - expect(result.current).toBe('first') - - await waitFor( - () => { - expect(result.current).toBe('third') - }, - { timeout: false } - ) - - expect(result.current).toBe('third') - }) - - test('should check on interval when waiting for expectation to pass', async () => { - const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'])) - - let checks = 0 - - await waitFor( - () => { - checks++ - return result.current === 'third' - }, - { interval: 100 } - ) - - expect(checks).toBe(3) - }) - - test('should wait for value to change', async () => { - const { result, waitForValueToChange } = renderHook(() => - useSequence(['first', 'second', 'third']) - ) - - expect(result.current).toBe('first') - - await waitForValueToChange(() => result.current === 'third') - - expect(result.current).toBe('third') - }) - - test('should wait for arbitrary value to change', async () => { - const { waitForValueToChange } = renderHook(() => null) - - let actual = 0 - const expected = 1 - - setTimeout(() => { - actual = expected - }, 200) - - await waitForValueToChange(() => actual) - - expect(actual).toBe(expected) - }) - - test('should reject if timeout exceeded when waiting for value to change', async () => { - const { result, waitForValueToChange } = renderHook(() => - useSequence(['first', 'second', 'third']) - ) - - expect(result.current).toBe('first') - - await expect( - waitForValueToChange(() => result.current === 'third', { - timeout: 75 - }) - ).rejects.toThrow(Error('Timed out in waitForValueToChange after 75ms.')) - }) - - test('should not reject when waiting for value to change if timeout is disabled', async () => { - const { result, waitForValueToChange } = renderHook(() => - useSequence(['first', 'second', 'third'], 550) - ) - - expect(result.current).toBe('first') - - await waitForValueToChange(() => result.current === 'third', { - timeout: false - }) - - expect(result.current).toBe('third') - }) - - test('should reject if selector throws error', async () => { - const { result, waitForValueToChange } = renderHook(() => useSequence(['first', 'second'])) - - expect(result.current).toBe('first') - - await expect( - waitForValueToChange(() => { - if (result.current === 'second') { - throw new Error('Something Unexpected') - } - return result.current - }) - ).rejects.toThrow(Error('Something Unexpected')) - }) - }) -}) diff --git a/src/__tests__/asyncHook.test.ts b/src/__tests__/asyncHook.test.ts index 17979ae2..29869c08 100644 --- a/src/__tests__/asyncHook.test.ts +++ b/src/__tests__/asyncHook.test.ts @@ -21,238 +21,253 @@ describe('async hook tests', () => { return value } + describe.each([ + { timerType: 'real timer', setTimer: () => jest.useRealTimers() }, + { timerType: 'fake timer (legacy)', setTimer: () => jest.useFakeTimers('legacy') }, + { timerType: 'fake timer (modern)', setTimer: () => jest.useFakeTimers('modern') } + ])('$timerType', ({ setTimer }) => { + beforeEach(() => { + setTimer() + }) - runForRenderers(['default', 'dom', 'native', 'server/hydrated'], ({ renderHook }) => { - test('should wait for next update', async () => { - const { result, waitForNextUpdate } = renderHook(() => useSequence(['first', 'second'])) + afterEach(() => { + jest.useRealTimers() + }) - expect(result.current).toBe('first') + runForRenderers(['default', 'dom', 'native', 'server/hydrated'], ({ renderHook }) => { + test('should wait for next update', async () => { + const { result, waitForNextUpdate } = renderHook(() => useSequence(['first', 'second'])) - await waitForNextUpdate() + expect(result.current).toBe('first') - expect(result.current).toBe('second') - }) + await waitForNextUpdate() - test('should wait for multiple updates', async () => { - const { result, waitForNextUpdate } = renderHook(() => - useSequence(['first', 'second', 'third']) - ) + expect(result.current).toBe('second') + }) - expect(result.current).toBe('first') + test('should wait for multiple updates', async () => { + const { result, waitForNextUpdate } = renderHook(() => + useSequence(['first', 'second', 'third']) + ) - await waitForNextUpdate() + expect(result.current).toBe('first') - expect(result.current).toBe('second') + await waitForNextUpdate() - await waitForNextUpdate() + expect(result.current).toBe('second') - expect(result.current).toBe('third') - }) + await waitForNextUpdate() - test('should reject if timeout exceeded when waiting for next update', async () => { - const { result, waitForNextUpdate } = renderHook(() => useSequence(['first', 'second'])) + expect(result.current).toBe('third') + }) - expect(result.current).toBe('first') + test('should reject if timeout exceeded when waiting for next update', async () => { + const { result, waitForNextUpdate } = renderHook(() => useSequence(['first', 'second'])) - await expect(waitForNextUpdate({ timeout: 10 })).rejects.toThrow( - Error('Timed out in waitForNextUpdate after 10ms.') - ) - }) + expect(result.current).toBe('first') - test('should not reject when waiting for next update if timeout has been disabled', async () => { - const { result, waitForNextUpdate } = renderHook(() => useSequence(['first', 'second'], 1100)) + await expect(waitForNextUpdate({ timeout: 10 })).rejects.toThrow( + Error('Timed out in waitForNextUpdate after 10ms.') + ) + }) - expect(result.current).toBe('first') + test('should not reject when waiting for next update if timeout has been disabled', async () => { + const { result, waitForNextUpdate } = renderHook(() => + useSequence(['first', 'second'], 1100) + ) - await waitForNextUpdate({ timeout: false }) + expect(result.current).toBe('first') - expect(result.current).toBe('second') - }) + await waitForNextUpdate({ timeout: false }) - test('should wait for expectation to pass', async () => { - const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'])) + expect(result.current).toBe('second') + }) - expect(result.current).toBe('first') + test('should wait for expectation to pass', async () => { + const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'])) - let complete = false - await waitFor(() => { - expect(result.current).toBe('third') - complete = true + expect(result.current).toBe('first') + + let complete = false + await waitFor(() => { + expect(result.current).toBe('third') + complete = true + }) + expect(complete).toBe(true) }) - expect(complete).toBe(true) - }) - test('should wait for arbitrary expectation to pass', async () => { - const { waitFor } = renderHook(() => null) + test('should wait for arbitrary expectation to pass', async () => { + const { waitFor } = renderHook(() => null) - let actual = 0 - const expected = 1 + let actual = 0 + const expected = 1 - setTimeout(() => { - actual = expected - }, 200) + setTimeout(() => { + actual = expected + }, 200) - let complete = false - await waitFor(() => { - expect(actual).toBe(expected) - complete = true + let complete = false + await waitFor(() => { + expect(actual).toBe(expected) + complete = true + }) + + expect(complete).toBe(true) }) - expect(complete).toBe(true) - }) + test('should not hang if expectation is already passing', async () => { + const { result, waitFor } = renderHook(() => useSequence(['first', 'second'])) - test('should not hang if expectation is already passing', async () => { - const { result, waitFor } = renderHook(() => useSequence(['first', 'second'])) + expect(result.current).toBe('first') - expect(result.current).toBe('first') + let complete = false + await waitFor(() => { + expect(result.current).toBe('first') + complete = true + }) + expect(complete).toBe(true) + }) + + test('should wait for truthy value', async () => { + const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'])) - let complete = false - await waitFor(() => { expect(result.current).toBe('first') - complete = true - }) - expect(complete).toBe(true) - }) - test('should wait for truthy value', async () => { - const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'])) + await waitFor(() => result.current === 'third') - expect(result.current).toBe('first') + expect(result.current).toBe('third') + }) - await waitFor(() => result.current === 'third') + test('should wait for arbitrary truthy value', async () => { + const { waitFor } = renderHook(() => null) - expect(result.current).toBe('third') - }) + let actual = 0 + const expected = 1 + + setTimeout(() => { + actual = expected + }, 200) - test('should wait for arbitrary truthy value', async () => { - const { waitFor } = renderHook(() => null) + await waitFor(() => actual === 1) - let actual = 0 - const expected = 1 + expect(actual).toBe(expected) + }) - setTimeout(() => { - actual = expected - }, 200) + test('should reject if timeout exceeded when waiting for expectation to pass', async () => { + const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'])) - await waitFor(() => actual === 1) + expect(result.current).toBe('first') - expect(actual).toBe(expected) - }) + await expect( + waitFor( + () => { + expect(result.current).toBe('third') + }, + { timeout: 75 } + ) + ).rejects.toThrow(Error('Timed out in waitFor after 75ms.')) + }) - test('should reject if timeout exceeded when waiting for expectation to pass', async () => { - const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'])) + test('should not reject when waiting for expectation to pass if timeout has been disabled', async () => { + const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'], 550)) - expect(result.current).toBe('first') + expect(result.current).toBe('first') - await expect( - waitFor( + await waitFor( () => { expect(result.current).toBe('third') }, - { timeout: 75 } + { timeout: false } ) - ).rejects.toThrow(Error('Timed out in waitFor after 75ms.')) - }) - test('should not reject when waiting for expectation to pass if timeout has been disabled', async () => { - const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'], 550)) + expect(result.current).toBe('third') + }) - expect(result.current).toBe('first') + test('should check on interval when waiting for expectation to pass', async () => { + const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'])) - await waitFor( - () => { - expect(result.current).toBe('third') - }, - { timeout: false } - ) + let checks = 0 - expect(result.current).toBe('third') - }) + await waitFor( + () => { + checks++ + return result.current === 'third' + }, + { interval: 100 } + ) - test('should check on interval when waiting for expectation to pass', async () => { - const { result, waitFor } = renderHook(() => useSequence(['first', 'second', 'third'])) + expect(checks).toBe(3) + }) - let checks = 0 + test('should wait for value to change', async () => { + const { result, waitForValueToChange } = renderHook(() => + useSequence(['first', 'second', 'third']) + ) - await waitFor( - () => { - checks++ - return result.current === 'third' - }, - { interval: 100 } - ) + expect(result.current).toBe('first') - expect(checks).toBe(3) - }) + await waitForValueToChange(() => result.current === 'third') - test('should wait for value to change', async () => { - const { result, waitForValueToChange } = renderHook(() => - useSequence(['first', 'second', 'third']) - ) + expect(result.current).toBe('third') + }) - expect(result.current).toBe('first') + test('should wait for arbitrary value to change', async () => { + const { waitForValueToChange } = renderHook(() => null) - await waitForValueToChange(() => result.current === 'third') + let actual = 0 + const expected = 1 - expect(result.current).toBe('third') - }) + setTimeout(() => { + actual = expected + }, 200) - test('should wait for arbitrary value to change', async () => { - const { waitForValueToChange } = renderHook(() => null) + await waitForValueToChange(() => actual) - let actual = 0 - const expected = 1 + expect(actual).toBe(expected) + }) - setTimeout(() => { - actual = expected - }, 200) + test('should reject if timeout exceeded when waiting for value to change', async () => { + const { result, waitForValueToChange } = renderHook(() => + useSequence(['first', 'second', 'third']) + ) - await waitForValueToChange(() => actual) + expect(result.current).toBe('first') - expect(actual).toBe(expected) - }) + await expect( + waitForValueToChange(() => result.current === 'third', { + timeout: 75 + }) + ).rejects.toThrow(Error('Timed out in waitForValueToChange after 75ms.')) + }) - test('should reject if timeout exceeded when waiting for value to change', async () => { - const { result, waitForValueToChange } = renderHook(() => - useSequence(['first', 'second', 'third']) - ) + test('should not reject when waiting for value to change if timeout is disabled', async () => { + const { result, waitForValueToChange } = renderHook(() => + useSequence(['first', 'second', 'third'], 550) + ) - expect(result.current).toBe('first') + expect(result.current).toBe('first') - await expect( - waitForValueToChange(() => result.current === 'third', { - timeout: 75 + await waitForValueToChange(() => result.current === 'third', { + timeout: false }) - ).rejects.toThrow(Error('Timed out in waitForValueToChange after 75ms.')) - }) - - test('should not reject when waiting for value to change if timeout is disabled', async () => { - const { result, waitForValueToChange } = renderHook(() => - useSequence(['first', 'second', 'third'], 550) - ) - - expect(result.current).toBe('first') - await waitForValueToChange(() => result.current === 'third', { - timeout: false + expect(result.current).toBe('third') }) - expect(result.current).toBe('third') - }) + test('should reject if selector throws error', async () => { + const { result, waitForValueToChange } = renderHook(() => useSequence(['first', 'second'])) - test('should reject if selector throws error', async () => { - const { result, waitForValueToChange } = renderHook(() => useSequence(['first', 'second'])) - - expect(result.current).toBe('first') + expect(result.current).toBe('first') - await expect( - waitForValueToChange(() => { - if (result.current === 'second') { - throw new Error('Something Unexpected') - } - return result.current - }) - ).rejects.toThrow(Error('Something Unexpected')) + await expect( + waitForValueToChange(() => { + if (result.current === 'second') { + throw new Error('Something Unexpected') + } + return result.current + }) + ).rejects.toThrow(Error('Something Unexpected')) + }) }) }) }) From 7d383db1d7d2bcd2500de7bad10274f8e47a110b Mon Sep 17 00:00:00 2001 From: Michael Peyper Date: Sun, 19 Sep 2021 22:22:32 +1000 Subject: [PATCH 07/11] refactor(async-utils): Move DEFAULT_TIMEOUT out of timeout controller --- src/core/asyncUtils.ts | 3 ++- src/helpers/createTimeoutController.ts | 7 ++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/core/asyncUtils.ts b/src/core/asyncUtils.ts index 07c795fa..d0d4b12a 100644 --- a/src/core/asyncUtils.ts +++ b/src/core/asyncUtils.ts @@ -7,10 +7,11 @@ import { AsyncUtils } from '../types' -import { createTimeoutController, DEFAULT_TIMEOUT } from '../helpers/createTimeoutController' +import { createTimeoutController } 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 ( diff --git a/src/helpers/createTimeoutController.ts b/src/helpers/createTimeoutController.ts index 5f8b5108..6b16c8af 100644 --- a/src/helpers/createTimeoutController.ts +++ b/src/helpers/createTimeoutController.ts @@ -1,7 +1,5 @@ import { jestFakeTimersAreEnabled } from './jestFakeTimersAreEnabled' -const DEFAULT_TIMEOUT = 1000 - function createTimeoutController(timeout: number | false, options: { allowFakeTimers: boolean }) { let timeoutId: NodeJS.Timeout const timeoutCallbacks: Array<() => void> = [] @@ -10,8 +8,7 @@ function createTimeoutController(timeout: number | false, options: { allowFakeTi const { allowFakeTimers } = options const advanceTime = async (currentMs: number) => { - // eslint-disable-next-line no-negated-condition - if (currentMs < (!timeout ? DEFAULT_TIMEOUT : timeout)) { + if (!timeout || currentMs < timeout) { jest.advanceTimersByTime(1) await Promise.resolve() @@ -61,4 +58,4 @@ function createTimeoutController(timeout: number | false, options: { allowFakeTi return timeoutController } -export { createTimeoutController, DEFAULT_TIMEOUT } +export { createTimeoutController } From 04d6a184566e088ab24a75ed3c787259d87815ec Mon Sep 17 00:00:00 2001 From: Michael Peyper Date: Sun, 19 Sep 2021 22:31:38 +1000 Subject: [PATCH 08/11] refactor(async-utils): move fake timer advancement into seperate utility --- src/helpers/createTimeoutController.ts | 20 ++++------------- src/helpers/fakeTimers.ts | 29 +++++++++++++++++++++++++ src/helpers/jestFakeTimersAreEnabled.ts | 13 ----------- 3 files changed, 33 insertions(+), 29 deletions(-) create mode 100644 src/helpers/fakeTimers.ts delete mode 100644 src/helpers/jestFakeTimersAreEnabled.ts diff --git a/src/helpers/createTimeoutController.ts b/src/helpers/createTimeoutController.ts index 6b16c8af..846f17b3 100644 --- a/src/helpers/createTimeoutController.ts +++ b/src/helpers/createTimeoutController.ts @@ -1,4 +1,4 @@ -import { jestFakeTimersAreEnabled } from './jestFakeTimersAreEnabled' +import { fakeTimersAreEnabled, advanceTimers } from './fakeTimers' function createTimeoutController(timeout: number | false, options: { allowFakeTimers: boolean }) { let timeoutId: NodeJS.Timeout @@ -7,19 +7,6 @@ function createTimeoutController(timeout: number | false, options: { allowFakeTi const { allowFakeTimers } = options - const advanceTime = async (currentMs: number) => { - if (!timeout || currentMs < timeout) { - jest.advanceTimersByTime(1) - - await Promise.resolve() - - if (finished) { - return - } - await advanceTime(currentMs + 1) - } - } - const timeoutController = { onTimeout(callback: () => void) { timeoutCallbacks.push(callback) @@ -35,8 +22,9 @@ function createTimeoutController(timeout: number | false, options: { allowFakeTi resolve() }, timeout) } - if (jestFakeTimersAreEnabled() && allowFakeTimers) { - advanceTime(0) + + if (fakeTimersAreEnabled() && allowFakeTimers) { + advanceTimers(timeout, () => finished) } promise diff --git a/src/helpers/fakeTimers.ts b/src/helpers/fakeTimers.ts new file mode 100644 index 00000000..8ef3f5ea --- /dev/null +++ b/src/helpers/fakeTimers.ts @@ -0,0 +1,29 @@ +export const fakeTimersAreEnabled = () => { + /* 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 +} + +export function advanceTimers(timeout: number | false, checkComplete: () => boolean) { + const advanceTime = async (currentMs: number) => { + if (!timeout || currentMs < timeout) { + jest.advanceTimersByTime(1) + + await Promise.resolve() + + if (checkComplete()) { + return + } + await advanceTime(currentMs + 1) + } + } + return advanceTime(0) +} diff --git a/src/helpers/jestFakeTimersAreEnabled.ts b/src/helpers/jestFakeTimersAreEnabled.ts deleted file mode 100644 index 5a0e2a88..00000000 --- a/src/helpers/jestFakeTimersAreEnabled.ts +++ /dev/null @@ -1,13 +0,0 @@ -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 -} From 7d83467a7088f32f2d08da2d1d968d6cfaaf9fb8 Mon Sep 17 00:00:00 2001 From: Michael Peyper Date: Sun, 19 Sep 2021 22:52:03 +1000 Subject: [PATCH 09/11] refactor(async-utils): simplify fake timer advancement logic --- src/helpers/createTimeoutController.ts | 3 ++- src/helpers/fakeTimers.ts | 15 +++++---------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/helpers/createTimeoutController.ts b/src/helpers/createTimeoutController.ts index 846f17b3..0fafe277 100644 --- a/src/helpers/createTimeoutController.ts +++ b/src/helpers/createTimeoutController.ts @@ -17,6 +17,7 @@ function createTimeoutController(timeout: number | false, options: { allowFakeTi timeoutController.onTimeout(resolve) if (timeout) { timeoutId = setTimeout(() => { + finished = true timeoutController.timedOut = true timeoutCallbacks.forEach((callback) => callback()) resolve() @@ -24,7 +25,7 @@ function createTimeoutController(timeout: number | false, options: { allowFakeTi } if (fakeTimersAreEnabled() && allowFakeTimers) { - advanceTimers(timeout, () => finished) + advanceTimers(() => finished) } promise diff --git a/src/helpers/fakeTimers.ts b/src/helpers/fakeTimers.ts index 8ef3f5ea..60e60dd9 100644 --- a/src/helpers/fakeTimers.ts +++ b/src/helpers/fakeTimers.ts @@ -12,18 +12,13 @@ export const fakeTimersAreEnabled = () => { return false } -export function advanceTimers(timeout: number | false, checkComplete: () => boolean) { - const advanceTime = async (currentMs: number) => { - if (!timeout || currentMs < timeout) { +export function advanceTimers(checkComplete: () => boolean) { + const advanceTime = async () => { + if (!checkComplete()) { jest.advanceTimersByTime(1) - await Promise.resolve() - - if (checkComplete()) { - return - } - await advanceTime(currentMs + 1) + await advanceTime() } } - return advanceTime(0) + return advanceTime() } From 972981afd5f28f4368668b4c931be5a52766ee2d Mon Sep 17 00:00:00 2001 From: Michael Peyper Date: Sun, 19 Sep 2021 23:00:09 +1000 Subject: [PATCH 10/11] docs: add chris110408 as a contributor for code --- .all-contributorsrc | 1 + README.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.all-contributorsrc b/.all-contributorsrc index 5ea4dc36..51bccf3d 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -582,6 +582,7 @@ "avatar_url": "https://avatars.githubusercontent.com/u/10645051?v=4", "profile": "https://github.com/chris110408", "contributions": [ + "code", "test" ] }, diff --git a/README.md b/README.md index 88f0944b..7eaab8b3 100644 --- a/README.md +++ b/README.md @@ -247,7 +247,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
andyrooger

💻
Bryan Wain

🐛 👀
Robert Snow

⚠️ -
Chris Chen

⚠️ +
Chris Chen

💻 ⚠️
Masious

📖 From d62357269ae6b0e966467b5eccba2a605e59afdb Mon Sep 17 00:00:00 2001 From: Michael Peyper Date: Sun, 19 Sep 2021 23:16:58 +1000 Subject: [PATCH 11/11] refactor(async-utils): only advance timers on a single timeoutController --- src/core/asyncUtils.ts | 4 ++-- src/helpers/createTimeoutController.ts | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/core/asyncUtils.ts b/src/core/asyncUtils.ts index d0d4b12a..1aa2854f 100644 --- a/src/core/asyncUtils.ts +++ b/src/core/asyncUtils.ts @@ -23,11 +23,11 @@ function asyncUtils(act: Act, addResolver: (callback: () => void) => void): Asyn return callbackResult ?? callbackResult === undefined } - const timeoutController = createTimeoutController(timeout, { allowFakeTimers: !interval }) + const timeoutController = createTimeoutController(timeout, { allowFakeTimers: true }) const waitForResult = async () => { while (true) { - const intervalController = createTimeoutController(interval, { allowFakeTimers: true }) + const intervalController = createTimeoutController(interval) timeoutController.onTimeout(() => intervalController.cancel()) await intervalController.wrap(new Promise(addResolver)) diff --git a/src/helpers/createTimeoutController.ts b/src/helpers/createTimeoutController.ts index 0fafe277..6a5bda2a 100644 --- a/src/helpers/createTimeoutController.ts +++ b/src/helpers/createTimeoutController.ts @@ -1,12 +1,10 @@ import { fakeTimersAreEnabled, advanceTimers } from './fakeTimers' -function createTimeoutController(timeout: number | false, options: { allowFakeTimers: boolean }) { +function createTimeoutController(timeout: number | false, { allowFakeTimers = false } = {}) { let timeoutId: NodeJS.Timeout const timeoutCallbacks: Array<() => void> = [] let finished = false - const { allowFakeTimers } = options - const timeoutController = { onTimeout(callback: () => void) { timeoutCallbacks.push(callback) @@ -23,7 +21,7 @@ function createTimeoutController(timeout: number | false, options: { allowFakeTi resolve() }, timeout) } - + if (fakeTimersAreEnabled() && allowFakeTimers) { advanceTimers(() => finished) }