From 79b2245c51c9231261f487b4a3bc2cb25ea4a62b 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 01/10] 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 0df0c4ca3d175f2abe93eafed552b0ca29a6f618 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 02/10] 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 1e1d3fae5d87afd23b92d5b02cda8959b5fc7cb7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Sep 2021 16:20:26 +1000 Subject: [PATCH 03/10] chore(deps-dev): bump prettier from 2.3.2 to 2.4.0 (#693) Bumps [prettier](https://github.com/prettier/prettier) from 2.3.2 to 2.4.0. - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/2.3.2...2.4.0) --- updated-dependencies: - dependency-name: prettier 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 eeef84a7..731c9545 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "eslint": "7.32.0", "get-pkg-repo": "4.1.1", "kcd-scripts": "11.2.0", - "prettier": "2.3.2", + "prettier": "2.4.0", "react": "17.0.2", "react-dom": "17.0.2", "react-test-renderer": "17.0.2", From 937b9c8637fb4c49905b03942d59431c07f1b3d3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Sep 2021 08:23:24 +0100 Subject: [PATCH 04/10] chore(deps-dev): bump @typescript-eslint/eslint-plugin (#697) Bumps [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) from 4.31.0 to 4.31.1. - [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.1/packages/eslint-plugin) --- updated-dependencies: - dependency-name: "@typescript-eslint/eslint-plugin" dependency-type: direct:development update-type: version-update:semver-patch ... 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 731c9545..2177137b 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "react-error-boundary": "^3.1.0" }, "devDependencies": { - "@typescript-eslint/eslint-plugin": "4.31.0", + "@typescript-eslint/eslint-plugin": "4.31.1", "@typescript-eslint/parser": "4.31.0", "all-contributors-cli": "6.20.0", "codecov": "3.8.3", From 4cb7dff148bbdbbe406352531a30cd0aeda9c289 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Sep 2021 08:47:54 +0100 Subject: [PATCH 05/10] chore(deps-dev): bump @typescript-eslint/parser from 4.31.0 to 4.31.1 (#696) Bumps [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) from 4.31.0 to 4.31.1. - [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.1/packages/parser) --- updated-dependencies: - dependency-name: "@typescript-eslint/parser" dependency-type: direct:development update-type: version-update:semver-patch ... 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 2177137b..44b108c2 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ }, "devDependencies": { "@typescript-eslint/eslint-plugin": "4.31.1", - "@typescript-eslint/parser": "4.31.0", + "@typescript-eslint/parser": "4.31.1", "all-contributors-cli": "6.20.0", "codecov": "3.8.3", "cross-env": "^7.0.3", From 17861103ffb011fe1854e4e302022c070f213afc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Sep 2021 13:40:45 +1000 Subject: [PATCH 06/10] chore(deps): bump codecov/codecov-action from 2.0.3 to 2.1.0 (#695) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 2.0.3 to 2.1.0. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/master/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v2.0.3...v2.1.0) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Josh <37798644+joshuaellis@users.noreply.github.com> --- .github/workflows/validate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 37aacd7e..50d61077 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -45,7 +45,7 @@ jobs: run: npm run validate - name: ⬆️ Upload coverage report - uses: codecov/codecov-action@v2.0.3 + uses: codecov/codecov-action@v2.1.0 release: needs: main From efb0ac93ea1ee4aa7c761416737a36cd6c0ceb4f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Sep 2021 08:24:56 +0100 Subject: [PATCH 07/10] chore(deps-dev): bump typescript from 4.4.2 to 4.4.3 (#694) Bumps [typescript](https://github.com/Microsoft/TypeScript) from 4.4.2 to 4.4.3. - [Release notes](https://github.com/Microsoft/TypeScript/releases) - [Commits](https://github.com/Microsoft/TypeScript/compare/v4.4.2...v4.4.3) --- updated-dependencies: - dependency-name: typescript dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Josh <37798644+joshuaellis@users.noreply.github.com> Co-authored-by: Michael Peyper --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 44b108c2..c0c156ac 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "react-dom": "17.0.2", "react-test-renderer": "17.0.2", "ts-node": "10.2.1", - "typescript": "4.4.2" + "typescript": "4.4.3" }, "peerDependencies": { "react": ">=16.9.0", From 1dcf74cd73de032744f749841f7cd8281c598d9a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 16 Sep 2021 13:37:00 +1000 Subject: [PATCH 08/10] chore(deps-dev): bump kcd-scripts from 11.2.0 to 11.2.2 (#698) Bumps [kcd-scripts](https://github.com/kentcdodds/kcd-scripts) from 11.2.0 to 11.2.2. - [Release notes](https://github.com/kentcdodds/kcd-scripts/releases) - [Changelog](https://github.com/kentcdodds/kcd-scripts/blob/main/CHANGELOG.md) - [Commits](https://github.com/kentcdodds/kcd-scripts/compare/v11.2.0...v11.2.2) --- updated-dependencies: - dependency-name: kcd-scripts dependency-type: direct:development update-type: version-update:semver-patch ... 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 c0c156ac..c78b724c 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "docz-utils": "2.3.0", "eslint": "7.32.0", "get-pkg-repo": "4.1.1", - "kcd-scripts": "11.2.0", + "kcd-scripts": "11.2.2", "prettier": "2.4.0", "react": "17.0.2", "react-dom": "17.0.2", From 8b72a87d18f8d6a0c26b622f77b1bf319f7751b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 19 Sep 2021 20:52:26 +1000 Subject: [PATCH 09/10] chore(deps-dev): bump prettier from 2.4.0 to 2.4.1 (#699) Bumps [prettier](https://github.com/prettier/prettier) from 2.4.0 to 2.4.1. - [Release notes](https://github.com/prettier/prettier/releases) - [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md) - [Commits](https://github.com/prettier/prettier/compare/2.4.0...2.4.1) --- updated-dependencies: - dependency-name: prettier dependency-type: direct:development update-type: version-update:semver-patch ... 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 c78b724c..fe038177 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "eslint": "7.32.0", "get-pkg-repo": "4.1.1", "kcd-scripts": "11.2.2", - "prettier": "2.4.0", + "prettier": "2.4.1", "react": "17.0.2", "react-dom": "17.0.2", "react-test-renderer": "17.0.2", From 732ec46da3b0bc5c83e618b06cf19ff7016f0982 Mon Sep 17 00:00:00 2001 From: Chris Chen Date: Sun, 19 Sep 2021 06:49:50 -0700 Subject: [PATCH 10/10] feat(async-utils): async utils advance fake timer automatically when waiting Fixes #631 * add jestFakeTimersAreEnabled and use it to detect faketimer in createTimeoutController (#688) * 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 * 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 * test(async-utils): enable test to test CI fix * test(async-utils): combine fake timer tests with async tests * refactor(async-utils): Move DEFAULT_TIMEOUT out of timeout controller * refactor(async-utils): move fake timer advancement into seperate utility * refactor(async-utils): simplify fake timer advancement logic * docs: add chris110408 as a contributor for code * refactor(async-utils): only advance timers on a single timeoutController BREAKING CHANGE: tests that used to manually advance fake timers and use async utilities may now fail as timer would advance further Co-authored-by: Lei Chen Co-authored-by: Michael Peyper --- .all-contributorsrc | 1 + README.md | 2 +- src/__tests__/asyncHook.fakeTimers.test.ts | 58 ---- src/__tests__/asyncHook.test.ts | 333 +++++++++++---------- src/core/asyncUtils.ts | 19 +- src/helpers/createTimeoutController.ts | 17 +- src/helpers/fakeTimers.ts | 24 ++ 7 files changed, 224 insertions(+), 230 deletions(-) delete mode 100644 src/__tests__/asyncHook.fakeTimers.test.ts create mode 100644 src/helpers/fakeTimers.ts 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

📖 diff --git a/src/__tests__/asyncHook.fakeTimers.test.ts b/src/__tests__/asyncHook.fakeTimers.test.ts deleted file mode 100644 index 98d6b2c9..00000000 --- a/src/__tests__/asyncHook.fakeTimers.test.ts +++ /dev/null @@ -1,58 +0,0 @@ -describe('async hook (fake timers) tests', () => { - 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) - }) - }) -}) - -// eslint-disable-next-line jest/no-export -export {} 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')) + }) }) }) }) diff --git a/src/core/asyncUtils.ts b/src/core/asyncUtils.ts index a7424036..1aa2854f 100644 --- a/src/core/asyncUtils.ts +++ b/src/core/asyncUtils.ts @@ -14,32 +14,35 @@ 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) => { + const wait = async ( + callback: () => boolean | void, + { interval, timeout }: Required + ) => { const checkResult = () => { const callbackResult = callback() return callbackResult ?? callbackResult === undefined } - const timeoutSignal = createTimeoutController(timeout) + const timeoutController = createTimeoutController(timeout, { allowFakeTimers: true }) const waitForResult = async () => { while (true) { - const intervalSignal = createTimeoutController(interval) - timeoutSignal.onTimeout(() => intervalSignal.cancel()) + const intervalController = createTimeoutController(interval) + 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 643d3768..6a5bda2a 100644 --- a/src/helpers/createTimeoutController.ts +++ b/src/helpers/createTimeoutController.ts @@ -1,8 +1,9 @@ -import { WaitOptions } from '../types' +import { fakeTimersAreEnabled, advanceTimers } from './fakeTimers' -function createTimeoutController(timeout: WaitOptions['timeout']) { +function createTimeoutController(timeout: number | false, { allowFakeTimers = false } = {}) { let timeoutId: NodeJS.Timeout const timeoutCallbacks: Array<() => void> = [] + let finished = false const timeoutController = { onTimeout(callback: () => void) { @@ -12,22 +13,30 @@ function createTimeoutController(timeout: WaitOptions['timeout']) { return new Promise((resolve, reject) => { timeoutController.timedOut = false timeoutController.onTimeout(resolve) - if (timeout) { timeoutId = setTimeout(() => { + finished = true timeoutController.timedOut = true timeoutCallbacks.forEach((callback) => callback()) resolve() }, timeout) } + if (fakeTimersAreEnabled() && allowFakeTimers) { + advanceTimers(() => finished) + } + promise .then(resolve) .catch(reject) - .finally(() => timeoutController.cancel()) + .finally(() => { + finished = true + timeoutController.cancel() + }) }) }, cancel() { + finished = true clearTimeout(timeoutId) }, timedOut: false diff --git a/src/helpers/fakeTimers.ts b/src/helpers/fakeTimers.ts new file mode 100644 index 00000000..60e60dd9 --- /dev/null +++ b/src/helpers/fakeTimers.ts @@ -0,0 +1,24 @@ +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(checkComplete: () => boolean) { + const advanceTime = async () => { + if (!checkComplete()) { + jest.advanceTimersByTime(1) + await Promise.resolve() + await advanceTime() + } + } + return advanceTime() +}