From b7c28dc33bf928f6306379d9f800cf3336a03d62 Mon Sep 17 00:00:00 2001 From: Braydon Hall <40751395+nobrayner@users.noreply.github.com> Date: Tue, 8 Dec 2020 21:54:49 +0200 Subject: [PATCH 01/56] Added tsconfig.json. Modified eslint config --- .eslintrc | 13 +++++++++---- jest.config.js | 1 + package.json | 2 ++ test/autoCleanup.disabled.js | 1 + test/autoCleanup.noAfterEach.js | 1 + tsconfig.json | 6 ++++++ 6 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 tsconfig.json diff --git a/.eslintrc b/.eslintrc index d6c4554f..a7a4ea02 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,10 +1,15 @@ { - "extends": "./node_modules/kcd-scripts/eslint.js", + "extends": [ + "./node_modules/kcd-scripts/eslint.js", + "plugin:@typescript-eslint/recommended" + ], "rules": { "max-lines-per-function": "off", "no-constant-condition": "off", "no-await-in-loop": "off", "react-hooks/rules-of-hooks": "off", - "no-console": "off" - } -} + "no-console": "off", + "no-use-before-define": "off", + "@typescript-eslint/no-use-before-define": "warn" + }, +} \ No newline at end of file diff --git a/jest.config.js b/jest.config.js index c2e6a069..46c43d71 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,3 +1,4 @@ +// eslint-disable-next-line const { jest: jestConfig } = require('kcd-scripts/config') module.exports = Object.assign(jestConfig, { diff --git a/package.json b/package.json index cb1f5f57..24eb78b1 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,8 @@ "@types/testing-library__react-hooks": "^3.4.0" }, "devDependencies": { + "@typescript-eslint/eslint-plugin": "^4.9.1", + "@typescript-eslint/parser": "^4.9.1", "all-contributors-cli": "6.19.0", "codecov": "3.8.1", "docz": "2.3.1", diff --git a/test/autoCleanup.disabled.js b/test/autoCleanup.disabled.js index d11f9314..174a3135 100644 --- a/test/autoCleanup.disabled.js +++ b/test/autoCleanup.disabled.js @@ -8,6 +8,7 @@ describe('skip auto cleanup (disabled) tests', () => { beforeAll(() => { process.env.RHTL_SKIP_AUTO_CLEANUP = 'true' + // eslint-disable-next-line renderHook = require('../src').renderHook }) diff --git a/test/autoCleanup.noAfterEach.js b/test/autoCleanup.noAfterEach.js index 9b894e00..2531c657 100644 --- a/test/autoCleanup.noAfterEach.js +++ b/test/autoCleanup.noAfterEach.js @@ -9,6 +9,7 @@ describe('skip auto cleanup (no afterEach) tests', () => { beforeAll(() => { // eslint-disable-next-line no-global-assign afterEach = false + // eslint-disable-next-line renderHook = require('../').renderHook }) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..9fdd876b --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "./node_modules/kcd-scripts/shared-tsconfig.json", + "compilerOptions": { + "allowJs": true + } +} From a7b9412444e6aa8dab1fce7ca58b0160dc2c3ab0 Mon Sep 17 00:00:00 2001 From: Braydon Hall <40751395+nobrayner@users.noreply.github.com> Date: Thu, 10 Dec 2020 09:34:15 +1100 Subject: [PATCH 02/56] Convert src/cleanup.js to TypeScript --- src/{cleanup.js => cleanup.ts} | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) rename src/{cleanup.js => cleanup.ts} (62%) diff --git a/src/cleanup.js b/src/cleanup.ts similarity index 62% rename from src/cleanup.js rename to src/cleanup.ts index d3172c46..ac8fe099 100644 --- a/src/cleanup.js +++ b/src/cleanup.ts @@ -1,4 +1,6 @@ -let cleanupCallbacks = [] +type CleanupCallback = () => Promise + +let cleanupCallbacks: CleanupCallback[] = [] async function cleanup() { for (const callback of cleanupCallbacks) { @@ -7,12 +9,12 @@ async function cleanup() { cleanupCallbacks = [] } -function addCleanup(callback) { +function addCleanup(callback: CleanupCallback) { cleanupCallbacks.unshift(callback) return () => removeCleanup(callback) } -function removeCleanup(callback) { +function removeCleanup(callback: CleanupCallback) { cleanupCallbacks = cleanupCallbacks.filter((cb) => cb !== callback) } From cdfca10410405a8abfd06cb2ecfb21b76862c594 Mon Sep 17 00:00:00 2001 From: Braydon Hall <40751395+nobrayner@users.noreply.github.com> Date: Thu, 10 Dec 2020 09:41:21 +1100 Subject: [PATCH 03/56] Change src/cleanup.ts to use inline types --- src/cleanup.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/cleanup.ts b/src/cleanup.ts index ac8fe099..7972347f 100644 --- a/src/cleanup.ts +++ b/src/cleanup.ts @@ -1,6 +1,4 @@ -type CleanupCallback = () => Promise - -let cleanupCallbacks: CleanupCallback[] = [] +let cleanupCallbacks: (() => Promise)[] = [] async function cleanup() { for (const callback of cleanupCallbacks) { @@ -9,12 +7,12 @@ async function cleanup() { cleanupCallbacks = [] } -function addCleanup(callback: CleanupCallback) { +function addCleanup(callback: () => Promise) { cleanupCallbacks.unshift(callback) return () => removeCleanup(callback) } -function removeCleanup(callback: CleanupCallback) { +function removeCleanup(callback: () => Promise) { cleanupCallbacks = cleanupCallbacks.filter((cb) => cb !== callback) } From 5e5a248ce1be4f05aa386707898dbd187b2cf784 Mon Sep 17 00:00:00 2001 From: Amr Gad Date: Thu, 10 Dec 2020 00:43:29 +0200 Subject: [PATCH 04/56] revert eslintrc --- .eslintrc | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/.eslintrc b/.eslintrc index a7a4ea02..d6c4554f 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,15 +1,10 @@ { - "extends": [ - "./node_modules/kcd-scripts/eslint.js", - "plugin:@typescript-eslint/recommended" - ], + "extends": "./node_modules/kcd-scripts/eslint.js", "rules": { "max-lines-per-function": "off", "no-constant-condition": "off", "no-await-in-loop": "off", "react-hooks/rules-of-hooks": "off", - "no-console": "off", - "no-use-before-define": "off", - "@typescript-eslint/no-use-before-define": "warn" - }, -} \ No newline at end of file + "no-console": "off" + } +} From 07278d0d64b919254fff812a47f93bac976d5901 Mon Sep 17 00:00:00 2001 From: Amr Gad Date: Thu, 10 Dec 2020 00:54:14 +0200 Subject: [PATCH 05/56] disable eslint for cleanup import this can be reverted when `pure.js` is converted to typescript --- src/pure.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pure.js b/src/pure.js index 435d0736..c165fc19 100644 --- a/src/pure.js +++ b/src/pure.js @@ -1,6 +1,8 @@ import React, { Suspense } from 'react' import { act, create } from 'react-test-renderer' import asyncUtils from './asyncUtils' +// TODO: remove this line when it's converted to typescript +// eslint-disable-next-line import/no-unresolved import { cleanup, addCleanup, removeCleanup } from './cleanup' function TestHook({ callback, hookProps, onError, children }) { From e8b9daedbe997acca58ed22ff12749be2697a941 Mon Sep 17 00:00:00 2001 From: Amr Gad Date: Thu, 10 Dec 2020 01:01:41 +0200 Subject: [PATCH 06/56] convert index to typescript MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit just a rename 😅 --- src/{index.js => index.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{index.js => index.ts} (100%) diff --git a/src/index.js b/src/index.ts similarity index 100% rename from src/index.js rename to src/index.ts From bc2a6c334681ba6f6f44d53b951633a20f5646e2 Mon Sep 17 00:00:00 2001 From: Juhana Jauhiainen Date: Thu, 10 Dec 2020 18:42:22 +0200 Subject: [PATCH 07/56] Worked on typing asyncUtils. Removed deprecated wait. Disabled import/no-unresolved for now --- .eslintrc | 3 +- src/{asyncUtils.js => asyncUtils.ts} | 66 ++++++++++----------- test/asyncHook.js | 88 ---------------------------- 3 files changed, 33 insertions(+), 124 deletions(-) rename src/{asyncUtils.js => asyncUtils.ts} (59%) diff --git a/.eslintrc b/.eslintrc index d6c4554f..a9ed07f2 100644 --- a/.eslintrc +++ b/.eslintrc @@ -5,6 +5,7 @@ "no-constant-condition": "off", "no-await-in-loop": "off", "react-hooks/rules-of-hooks": "off", - "no-console": "off" + "no-console": "off", + "import/no-unresolved": "off" } } diff --git a/src/asyncUtils.js b/src/asyncUtils.ts similarity index 59% rename from src/asyncUtils.js rename to src/asyncUtils.ts index afb5ac7e..864f38c5 100644 --- a/src/asyncUtils.js +++ b/src/asyncUtils.ts @@ -1,28 +1,37 @@ import { act } from 'react-test-renderer' -function createTimeoutError(utilName, { timeout }) { - const timeoutError = new Error(`Timed out in ${utilName} after ${timeout}ms.`) - timeoutError.timeout = true +export interface WaitOptions { + interval?: number + timeout?: number + suppressErrors?: boolean +} + +class TimeoutError extends Error { + timeout = true +} + +function createTimeoutError(utilName: string, { timeout }: Pick) { + //eslint-disable-next-line + const timeoutError = new TimeoutError(`Timed out in ${utilName} after ${timeout}ms.`) return timeoutError } -function resolveAfter(ms) { +function resolveAfter(ms: number) { return new Promise((resolve) => { setTimeout(resolve, ms) }) } -let hasWarnedDeprecatedWait = false +// eslint-disable-next-line +function asyncUtils(addResolver: any) { + let nextUpdatePromise: Promise | null = null -function asyncUtils(addResolver) { - let nextUpdatePromise = null - - const waitForNextUpdate = async (options = {}) => { + const waitForNextUpdate = async (options: Pick = {}) => { if (!nextUpdatePromise) { nextUpdatePromise = new Promise((resolve, reject) => { - let timeoutId - if (options.timeout > 0) { - timeoutId = setTimeout( + let timeoutId: number + if (options.timeout && options.timeout > 0) { + timeoutId = window.setTimeout( () => reject(createTimeoutError('waitForNextUpdate', options)), options.timeout ) @@ -33,17 +42,21 @@ function asyncUtils(addResolver) { resolve() }) }) - await act(() => nextUpdatePromise) + // eslint-disable-next-line + await act(() => nextUpdatePromise!) } await nextUpdatePromise } - const waitFor = async (callback, { interval, timeout, suppressErrors = true } = {}) => { + const waitFor = async ( + callback: () => boolean | void | undefined, + { interval, timeout, suppressErrors = true }: WaitOptions = {} + ) => { // eslint-disable-next-line consistent-return const checkResult = () => { try { const callbackResult = callback() - return callbackResult || callbackResult === undefined + return callbackResult ?? callbackResult === undefined } catch (e) { if (!suppressErrors) { throw e @@ -53,6 +66,7 @@ function asyncUtils(addResolver) { const waitForResult = async () => { const initialTimeout = timeout + // eslint-disable-next-line while (true) { const startTime = Date.now() try { @@ -71,7 +85,7 @@ function asyncUtils(addResolver) { } throw e } - timeout -= Date.now() - startTime + if (timeout) timeout -= Date.now() - startTime } } @@ -80,7 +94,7 @@ function asyncUtils(addResolver) { } } - const waitForValueToChange = async (selector, options = {}) => { + const waitForValueToChange = async (selector: () => unknown, options = {}) => { const initialValue = selector() try { await waitFor(() => selector() !== initialValue, { @@ -95,25 +109,7 @@ function asyncUtils(addResolver) { } } - const wait = async (callback, { timeout, suppressErrors } = {}) => { - if (!hasWarnedDeprecatedWait) { - hasWarnedDeprecatedWait = true - console.warn( - '`wait` has been deprecated. Use `waitFor` instead: https://react-hooks-testing-library.com/reference/api#waitfor.' - ) - } - try { - await waitFor(callback, { timeout, suppressErrors }) - } catch (e) { - if (e.timeout) { - throw createTimeoutError('wait', { timeout }) - } - throw e - } - } - return { - wait, waitFor, waitForNextUpdate, waitForValueToChange diff --git a/test/asyncHook.js b/test/asyncHook.js index 74d321a6..e735c624 100644 --- a/test/asyncHook.js +++ b/test/asyncHook.js @@ -266,92 +266,4 @@ describe('async hook tests', () => { expect(result.current).toBe('third') }) - - test('should wait for expectation to pass (deprecated)', async () => { - const { result, wait } = renderHook(() => useSequence('first', 'second', 'third')) - - expect(result.current).toBe('first') - - let complete = false - await wait(() => { - expect(result.current).toBe('third') - complete = true - }) - expect(complete).toBe(true) - }) - - test('should not hang if expectation is already passing (deprecated)', async () => { - const { result, wait } = renderHook(() => useSequence('first', 'second')) - - expect(result.current).toBe('first') - - let complete = false - await wait(() => { - expect(result.current).toBe('first') - complete = true - }) - expect(complete).toBe(true) - }) - - test('should reject if callback throws error (deprecated)', async () => { - const { result, wait } = renderHook(() => useSequence('first', 'second', 'third')) - - expect(result.current).toBe('first') - - await expect( - wait( - () => { - if (result.current === 'second') { - throw new Error('Something Unexpected') - } - return result.current === 'third' - }, - { - suppressErrors: false - } - ) - ).rejects.toThrow(Error('Something Unexpected')) - }) - - test('should reject if callback immediately throws error (deprecated)', async () => { - const { result, wait } = renderHook(() => useSequence('first', 'second', 'third')) - - expect(result.current).toBe('first') - - await expect( - wait( - () => { - throw new Error('Something Unexpected') - }, - { - suppressErrors: false - } - ) - ).rejects.toThrow(Error('Something Unexpected')) - }) - - test('should wait for truthy value (deprecated)', async () => { - const { result, wait } = renderHook(() => useSequence('first', 'second', 'third')) - - expect(result.current).toBe('first') - - await wait(() => result.current === 'third') - - expect(result.current).toBe('third') - }) - - test('should reject if timeout exceeded when waiting for expectation to pass (deprecated)', async () => { - const { result, wait } = renderHook(() => useSequence('first', 'second', 'third')) - - expect(result.current).toBe('first') - - await expect( - wait( - () => { - expect(result.current).toBe('third') - }, - { timeout: 75 } - ) - ).rejects.toThrow(Error('Timed out in wait after 75ms.')) - }) }) From f58e7f00bca4b0dd73d805505b53a5298b57e624 Mon Sep 17 00:00:00 2001 From: Braydon Hall <40751395+nobrayner@users.noreply.github.com> Date: Fri, 11 Dec 2020 07:56:02 +1100 Subject: [PATCH 08/56] Fix incorrect cleanup callback type --- src/cleanup.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cleanup.ts b/src/cleanup.ts index 7972347f..a9385eff 100644 --- a/src/cleanup.ts +++ b/src/cleanup.ts @@ -1,4 +1,4 @@ -let cleanupCallbacks: (() => Promise)[] = [] +let cleanupCallbacks: (() => Promise | void)[] = [] async function cleanup() { for (const callback of cleanupCallbacks) { @@ -7,12 +7,12 @@ async function cleanup() { cleanupCallbacks = [] } -function addCleanup(callback: () => Promise) { +function addCleanup(callback: () => Promise | void) { cleanupCallbacks.unshift(callback) return () => removeCleanup(callback) } -function removeCleanup(callback: () => Promise) { +function removeCleanup(callback: () => Promise | void) { cleanupCallbacks = cleanupCallbacks.filter((cb) => cb !== callback) } From a8ece2989023253a625c72c0043ea2ebbfe0c8d9 Mon Sep 17 00:00:00 2001 From: tigerabrodi Date: Thu, 10 Dec 2020 22:31:18 +0100 Subject: [PATCH 09/56] asyncUtils has been updated. Two lines are disabled for eslint. --- src/asyncUtils.ts | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/asyncUtils.ts b/src/asyncUtils.ts index 864f38c5..c34dec50 100644 --- a/src/asyncUtils.ts +++ b/src/asyncUtils.ts @@ -11,8 +11,7 @@ class TimeoutError extends Error { } function createTimeoutError(utilName: string, { timeout }: Pick) { - //eslint-disable-next-line - const timeoutError = new TimeoutError(`Timed out in ${utilName} after ${timeout}ms.`) + const timeoutError = new TimeoutError(`Timed out in ${utilName} after ${timeout as number}ms.`) return timeoutError } @@ -22,9 +21,8 @@ function resolveAfter(ms: number) { }) } -// eslint-disable-next-line -function asyncUtils(addResolver: any) { - let nextUpdatePromise: Promise | null = null +function asyncUtils(addResolver: (callback: () => void) => void) { + let nextUpdatePromise: Promise | null = null const waitForNextUpdate = async (options: Pick = {}) => { if (!nextUpdatePromise) { @@ -42,14 +40,13 @@ function asyncUtils(addResolver: any) { resolve() }) }) - // eslint-disable-next-line - await act(() => nextUpdatePromise!) + await act(() => nextUpdatePromise as Promise) } await nextUpdatePromise } - const waitFor = async ( - callback: () => boolean | void | undefined, + const waitFor = async ( + callback: () => T | Promise | null, { interval, timeout, suppressErrors = true }: WaitOptions = {} ) => { // eslint-disable-next-line consistent-return @@ -57,16 +54,16 @@ function asyncUtils(addResolver: any) { try { const callbackResult = callback() return callbackResult ?? callbackResult === undefined - } catch (e) { + } catch (error: unknown) { if (!suppressErrors) { - throw e + throw error as Error } } } const waitForResult = async () => { const initialTimeout = timeout - // eslint-disable-next-line + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition while (true) { const startTime = Date.now() try { @@ -79,11 +76,11 @@ function asyncUtils(addResolver: any) { if (checkResult()) { return } - } catch (e) { - if (e.timeout) { + } catch (error: unknown) { + if (error instanceof TimeoutError) { throw createTimeoutError('waitFor', { timeout: initialTimeout }) } - throw e + throw error as Error } if (timeout) timeout -= Date.now() - startTime } @@ -101,11 +98,11 @@ function asyncUtils(addResolver: any) { suppressErrors: false, ...options }) - } catch (e) { - if (e.timeout) { + } catch (error: unknown) { + if (error instanceof TimeoutError) { throw createTimeoutError('waitForValueToChange', options) } - throw e + throw error as Error } } From 7e45893780dd6875fd94c31ba551f413c80d5af6 Mon Sep 17 00:00:00 2001 From: Amr Gad Date: Fri, 11 Dec 2020 00:26:23 +0200 Subject: [PATCH 10/56] remove unnecessary eslint disable --- src/pure.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pure.js b/src/pure.js index c165fc19..435d0736 100644 --- a/src/pure.js +++ b/src/pure.js @@ -1,8 +1,6 @@ import React, { Suspense } from 'react' import { act, create } from 'react-test-renderer' import asyncUtils from './asyncUtils' -// TODO: remove this line when it's converted to typescript -// eslint-disable-next-line import/no-unresolved import { cleanup, addCleanup, removeCleanup } from './cleanup' function TestHook({ callback, hookProps, onError, children }) { From b8ddea3066caedf1b726272d216461f859b2f85a Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Thu, 10 Dec 2020 18:52:36 -0600 Subject: [PATCH 11/56] Prettier was stripping try catch types - see: https://prettier.io/blog/2020/08/24/2.1.0.html\#type-annotations-on-catch-clauses-8805httpsgithubcomprettierprettierpull8805-by-fiskerhttpsgithubcomfisker - Updated prettier in local devDeps to resolve directly to newest supported version --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 24eb78b1..6cc8d613 100644 --- a/package.json +++ b/package.json @@ -47,11 +47,12 @@ "docz": "2.3.1", "docz-theme-default": "1.2.0", "docz-utils": "2.3.0", + "eslint": "7.15.0", "kcd-scripts": "7.5.2", + "prettier": "^2.2.1", "react": "17.0.1", "react-test-renderer": "17.0.1", - "typescript": "4.1.2", - "eslint": "7.15.0" + "typescript": "4.1.2" }, "peerDependencies": { "react": ">=16.9.0", From 44452739ef4956424ede9e7fb6fee17ec4b55060 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Thu, 10 Dec 2020 19:34:24 -0600 Subject: [PATCH 12/56] Type for callback handle generic input & undefined handled by nullish operator TS expecting explicit return of undefined from arrow function with type Expected to return a value at the end of arrow function.eslintconsistent-return resolved by passing return in catch with undefined, same behavior more explicit --- src/asyncUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/asyncUtils.ts b/src/asyncUtils.ts index c34dec50..f09cfb70 100644 --- a/src/asyncUtils.ts +++ b/src/asyncUtils.ts @@ -46,10 +46,9 @@ function asyncUtils(addResolver: (callback: () => void) => void) { } const waitFor = async ( - callback: () => T | Promise | null, + callback: () => T | Promise | undefined, { interval, timeout, suppressErrors = true }: WaitOptions = {} ) => { - // eslint-disable-next-line consistent-return const checkResult = () => { try { const callbackResult = callback() @@ -58,6 +57,7 @@ function asyncUtils(addResolver: (callback: () => void) => void) { if (!suppressErrors) { throw error as Error } + return undefined } } From 05e9a388c419b3aa4155087a40e07117c8f5993e Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Thu, 10 Dec 2020 19:37:20 -0600 Subject: [PATCH 13/56] comments for review --- src/asyncUtils.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/asyncUtils.ts b/src/asyncUtils.ts index f09cfb70..fd392ff7 100644 --- a/src/asyncUtils.ts +++ b/src/asyncUtils.ts @@ -45,6 +45,8 @@ function asyncUtils(addResolver: (callback: () => void) => void) { await nextUpdatePromise } + // TODO: Discuss with Kent and Maintainers about behavior of returning nothing currently there are tests handling this behavior that may be an anti-pattern. + // ? Should waitFor() always expect something returned const waitFor = async ( callback: () => T | Promise | undefined, { interval, timeout, suppressErrors = true }: WaitOptions = {} From 6879cfdbd1c3a18bb96ba2c459afe7988a196180 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Thu, 10 Dec 2020 19:54:39 -0600 Subject: [PATCH 14/56] VoidFunction in place for void until decided behavior for waitFor() --- src/asyncUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/asyncUtils.ts b/src/asyncUtils.ts index fd392ff7..214eb50c 100644 --- a/src/asyncUtils.ts +++ b/src/asyncUtils.ts @@ -48,7 +48,7 @@ function asyncUtils(addResolver: (callback: () => void) => void) { // TODO: Discuss with Kent and Maintainers about behavior of returning nothing currently there are tests handling this behavior that may be an anti-pattern. // ? Should waitFor() always expect something returned const waitFor = async ( - callback: () => T | Promise | undefined, + callback: () => T | Promise | undefined | VoidFunction, { interval, timeout, suppressErrors = true }: WaitOptions = {} ) => { const checkResult = () => { From 8f902ea4cf796dcf201e922ca9e67dcaf91d4c60 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Thu, 10 Dec 2020 20:04:23 -0600 Subject: [PATCH 15/56] Utilizing OR operator with generic Types allows for desired behavior and previous tests pass --- src/asyncUtils.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/asyncUtils.ts b/src/asyncUtils.ts index 214eb50c..bdf23a40 100644 --- a/src/asyncUtils.ts +++ b/src/asyncUtils.ts @@ -48,13 +48,13 @@ function asyncUtils(addResolver: (callback: () => void) => void) { // TODO: Discuss with Kent and Maintainers about behavior of returning nothing currently there are tests handling this behavior that may be an anti-pattern. // ? Should waitFor() always expect something returned const waitFor = async ( - callback: () => T | Promise | undefined | VoidFunction, + callback: () => T | Promise, { interval, timeout, suppressErrors = true }: WaitOptions = {} ) => { const checkResult = () => { try { const callbackResult = callback() - return callbackResult ?? callbackResult === undefined + return callbackResult || callbackResult === undefined } catch (error: unknown) { if (!suppressErrors) { throw error as Error @@ -65,7 +65,6 @@ function asyncUtils(addResolver: (callback: () => void) => void) { const waitForResult = async () => { const initialTimeout = timeout - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition while (true) { const startTime = Date.now() try { From 2c2908bf79294d0d23679e47e39eca9859b21642 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Thu, 10 Dec 2020 20:08:53 -0600 Subject: [PATCH 16/56] mistakenly left out linter ignores in last commit --- src/asyncUtils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/asyncUtils.ts b/src/asyncUtils.ts index bdf23a40..f71973a3 100644 --- a/src/asyncUtils.ts +++ b/src/asyncUtils.ts @@ -65,6 +65,7 @@ function asyncUtils(addResolver: (callback: () => void) => void) { const waitForResult = async () => { const initialTimeout = timeout + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition while (true) { const startTime = Date.now() try { From 58b9b37d3b637ec038c6220b63d198d982e24d07 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Thu, 10 Dec 2020 22:50:35 -0600 Subject: [PATCH 17/56] Minimum types started for Pure file - File needs better, improved typing and Refactoring for Linter Disables to be removed IF POSSIBLE --- src/{pure.js => pure.tsx} | 44 ++++++++++++++++++++++++++------------- tsconfig.json | 4 +++- 2 files changed, 32 insertions(+), 16 deletions(-) rename src/{pure.js => pure.tsx} (51%) diff --git a/src/pure.js b/src/pure.tsx similarity index 51% rename from src/pure.js rename to src/pure.tsx index 435d0736..fc061892 100644 --- a/src/pure.js +++ b/src/pure.tsx @@ -1,12 +1,18 @@ -import React, { Suspense } from 'react' -import { act, create } from 'react-test-renderer' +import React, { ReactElement, ReactNode, Suspense } from 'react' +import { act, create, ReactTestRenderer } from 'react-test-renderer' import asyncUtils from './asyncUtils' import { cleanup, addCleanup, removeCleanup } from './cleanup' -function TestHook({ callback, hookProps, onError, children }) { + +// TODO: Add better type, currently file is utilizing minimum types to work +// TODO: Attempt to refactor code to remove ESLint disables if possible +type Props = {callback: (arg: unknown) => {}, hookProps: unknown, onError: CallableFunction, children: CallableFunction} +function TestHook({ callback, hookProps, onError, children }: Props) { try { children(callback(hookProps)) + // eslint-disable-next-line @typescript-eslint/no-implicit-any-catch } catch (err) { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (err.then) { throw err } else { @@ -21,8 +27,8 @@ function Fallback() { } function resultContainer() { - const results = [] - const resolvers = [] + const results: Array> = [] + const resolvers: Array = [] const result = { get all() { @@ -41,26 +47,26 @@ function resultContainer() { } } - const updateResult = (value, error) => { + const updateResult = (value: unknown, error?: unknown) => { results.push({ value, error }) resolvers.splice(0, resolvers.length).forEach((resolve) => resolve()) } return { result, - addResolver: (resolver) => { + addResolver: (resolver: VoidFunction) => { resolvers.push(resolver) }, - setValue: (value) => updateResult(value), - setError: (error) => updateResult(undefined, error) + setValue: (value: unknown) => updateResult(value), + setError: (error: unknown) => updateResult(undefined, error) } } -function renderHook(callback, { initialProps, wrapper } = {}) { +function renderHook(callback: () => {}, { initialProps, wrapper }: {initialProps?: (React.InputHTMLAttributes & React.ClassAttributes) , wrapper?: React.ComponentType} = {}) { const { result, setValue, setError, addResolver } = resultContainer() const hookProps = { current: initialProps } - const wrapUiIfNeeded = (innerElement) => + const wrapUiIfNeeded = (innerElement: ReactNode) => wrapper ? React.createElement(wrapper, hookProps.current, innerElement) : innerElement const toRender = () => @@ -70,22 +76,30 @@ function renderHook(callback, { initialProps, wrapper } = {}) { {setValue} - ) + ) as ReactElement + + - let testRenderer - act(() => { + + // eslint-disable-next-line no-undef-init + let testRenderer: ReactTestRenderer | undefined + // eslint-disable-next-line @typescript-eslint/no-floating-promises + act(() => { testRenderer = create(toRender()) }) - const { unmount, update } = testRenderer + // eslint-disable-next-line @typescript-eslint/unbound-method + const { unmount, update } = testRenderer as ReactTestRenderer function rerenderHook(newProps = hookProps.current) { hookProps.current = newProps + // eslint-disable-next-line @typescript-eslint/no-floating-promises act(() => { update(toRender()) }) } function unmountHook() { + // eslint-disable-next-line @typescript-eslint/no-floating-promises act(() => { removeCleanup(unmountHook) unmount() diff --git a/tsconfig.json b/tsconfig.json index 9fdd876b..a4ae1f0e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,8 @@ { + "extends": "./node_modules/kcd-scripts/shared-tsconfig.json", "compilerOptions": { - "allowJs": true + "allowJs": true, + "target": "ES6" } } From 4818cedbffa1f3234d5ee5efab99df79ec16b62b Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Fri, 11 Dec 2020 13:53:35 -0600 Subject: [PATCH 18/56] removed comments -- answered in PR - Types and OR check should handle expected behavior --- src/asyncUtils.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/asyncUtils.ts b/src/asyncUtils.ts index f71973a3..a7732d11 100644 --- a/src/asyncUtils.ts +++ b/src/asyncUtils.ts @@ -45,8 +45,6 @@ function asyncUtils(addResolver: (callback: () => void) => void) { await nextUpdatePromise } - // TODO: Discuss with Kent and Maintainers about behavior of returning nothing currently there are tests handling this behavior that may be an anti-pattern. - // ? Should waitFor() always expect something returned const waitFor = async ( callback: () => T | Promise, { interval, timeout, suppressErrors = true }: WaitOptions = {} From 4c8d1e21391838a69e41359b3d479097d684b5f8 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Fri, 11 Dec 2020 14:07:14 -0600 Subject: [PATCH 19/56] Generic HTML types for initialProps --- src/pure.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pure.tsx b/src/pure.tsx index fc061892..a7c02882 100644 --- a/src/pure.tsx +++ b/src/pure.tsx @@ -62,7 +62,7 @@ function resultContainer() { } } -function renderHook(callback: () => {}, { initialProps, wrapper }: {initialProps?: (React.InputHTMLAttributes & React.ClassAttributes) , wrapper?: React.ComponentType} = {}) { +function renderHook(callback: () => {}, { initialProps, wrapper }: {initialProps?: (React.HTMLAttributes & React.ClassAttributes) , wrapper?: React.ComponentType} = {}) { const { result, setValue, setError, addResolver } = resultContainer() const hookProps = { current: initialProps } From 3f9d853691c111071a7cdae1969777377fea79aa Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Fri, 11 Dec 2020 14:08:34 -0600 Subject: [PATCH 20/56] Generic HTML types for initialProps --- src/pure.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pure.tsx b/src/pure.tsx index a7c02882..75dda9c6 100644 --- a/src/pure.tsx +++ b/src/pure.tsx @@ -62,7 +62,7 @@ function resultContainer() { } } -function renderHook(callback: () => {}, { initialProps, wrapper }: {initialProps?: (React.HTMLAttributes & React.ClassAttributes) , wrapper?: React.ComponentType} = {}) { +function renderHook(callback: () => {}, { initialProps, wrapper }: {initialProps?: T , wrapper?: React.ComponentType} = {}) { const { result, setValue, setError, addResolver } = resultContainer() const hookProps = { current: initialProps } From d2dadf555bfcb6d35731454c1a2aec5379cc978a Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Fri, 11 Dec 2020 14:18:48 -0600 Subject: [PATCH 21/56] Generic type for callback and initialProps --- src/pure.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pure.tsx b/src/pure.tsx index 75dda9c6..62020f66 100644 --- a/src/pure.tsx +++ b/src/pure.tsx @@ -62,7 +62,7 @@ function resultContainer() { } } -function renderHook(callback: () => {}, { initialProps, wrapper }: {initialProps?: T , wrapper?: React.ComponentType} = {}) { +function renderHook(callback: (props: T) => R, { initialProps, wrapper }: {initialProps?: T , wrapper?: React.ComponentType} = {}) { const { result, setValue, setError, addResolver } = resultContainer() const hookProps = { current: initialProps } From 1dba932683cf271f7023fe68081755e6a1b5d230 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Fri, 11 Dec 2020 14:54:19 -0600 Subject: [PATCH 22/56] Generics added and CB toplevel same generic pattern added --- .eslintrc | 8 +++++++- src/pure.tsx | 5 ++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.eslintrc b/.eslintrc index a9ed07f2..04b7604d 100644 --- a/.eslintrc +++ b/.eslintrc @@ -6,6 +6,12 @@ "no-await-in-loop": "off", "react-hooks/rules-of-hooks": "off", "no-console": "off", - "import/no-unresolved": "off" + "import/no-unresolved": "off", + "@typescript-eslint/unbound-method": [ + "error", + { + "ignoreStatic": true + } + ] } } diff --git a/src/pure.tsx b/src/pure.tsx index 62020f66..9c602ef3 100644 --- a/src/pure.tsx +++ b/src/pure.tsx @@ -6,7 +6,8 @@ import { cleanup, addCleanup, removeCleanup } from './cleanup' // TODO: Add better type, currently file is utilizing minimum types to work // TODO: Attempt to refactor code to remove ESLint disables if possible -type Props = {callback: (arg: unknown) => {}, hookProps: unknown, onError: CallableFunction, children: CallableFunction} +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type Props = {callback: (props: T) => R, hookProps: unknown, onError: CallableFunction, children: CallableFunction} function TestHook({ callback, hookProps, onError, children }: Props) { try { children(callback(hookProps)) @@ -62,6 +63,7 @@ function resultContainer() { } } +// eslint-disable-next-line @typescript-eslint/no-explicit-any function renderHook(callback: (props: T) => R, { initialProps, wrapper }: {initialProps?: T , wrapper?: React.ComponentType} = {}) { const { result, setValue, setError, addResolver } = resultContainer() const hookProps = { current: initialProps } @@ -87,6 +89,7 @@ function renderHook(callback: (props: T) => R, { initialProps, act(() => { testRenderer = create(toRender()) }) + // eslint-disable-next-line @typescript-eslint/unbound-method const { unmount, update } = testRenderer as ReactTestRenderer From 6b19d1b1370ace34abc0b7c30aec17826f911787 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Fri, 11 Dec 2020 19:20:45 -0600 Subject: [PATCH 23/56] force rebuild with unbound method eslint ignore --- src/pure.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pure.tsx b/src/pure.tsx index 9c602ef3..a0bb4f0c 100644 --- a/src/pure.tsx +++ b/src/pure.tsx @@ -89,7 +89,6 @@ function renderHook(callback: (props: T) => R, { initialProps, act(() => { testRenderer = create(toRender()) }) - // eslint-disable-next-line @typescript-eslint/unbound-method const { unmount, update } = testRenderer as ReactTestRenderer From 9604dd7c980ddafcb04472bef7f2681d40a80806 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Fri, 11 Dec 2020 19:25:10 -0600 Subject: [PATCH 24/56] parser issue -- attempt to fix by removing specific TS eslint rule --- .eslintrc | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.eslintrc b/.eslintrc index 04b7604d..1c3e6721 100644 --- a/.eslintrc +++ b/.eslintrc @@ -7,11 +7,5 @@ "react-hooks/rules-of-hooks": "off", "no-console": "off", "import/no-unresolved": "off", - "@typescript-eslint/unbound-method": [ - "error", - { - "ignoreStatic": true - } - ] } } From b84a0de3b279cd4e64eda0017a84e2da3496b115 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Fri, 11 Dec 2020 19:25:59 -0600 Subject: [PATCH 25/56] Newer versions of ESLint might resolve the parser issue --- .eslintrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintrc b/.eslintrc index 1c3e6721..a9ed07f2 100644 --- a/.eslintrc +++ b/.eslintrc @@ -6,6 +6,6 @@ "no-await-in-loop": "off", "react-hooks/rules-of-hooks": "off", "no-console": "off", - "import/no-unresolved": "off", + "import/no-unresolved": "off" } } From 319cbba24a1406a918f31bbf654eef8c41e80d0a Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Fri, 11 Dec 2020 19:57:34 -0600 Subject: [PATCH 26/56] Added configs to get started on test conversion --- .eslintrc | 2 +- jest.config.js | 2 +- tsconfig.json | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.eslintrc b/.eslintrc index a9ed07f2..d80c5b77 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,5 +1,5 @@ { - "extends": "./node_modules/kcd-scripts/eslint.js", + "extends":[ "./node_modules/kcd-scripts/eslint.js"], "rules": { "max-lines-per-function": "off", "no-constant-condition": "off", diff --git a/jest.config.js b/jest.config.js index 46c43d71..bb6a1d85 100644 --- a/jest.config.js +++ b/jest.config.js @@ -3,5 +3,5 @@ const { jest: jestConfig } = require('kcd-scripts/config') module.exports = Object.assign(jestConfig, { roots: ['/src', '/test'], - testMatch: ['/test/*.js'] + testMatch: ['/test/*.(ts|tsx|js)'] }) diff --git a/tsconfig.json b/tsconfig.json index a4ae1f0e..81cc29e6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,7 @@ "extends": "./node_modules/kcd-scripts/shared-tsconfig.json", "compilerOptions": { "allowJs": true, - "target": "ES6" - } + "target": "ES6", + }, + "include": ["./test"] } From ec18c2a58a4dec442f1bc5bfe0ce553f84596d69 Mon Sep 17 00:00:00 2001 From: Amr Gad Date: Sat, 12 Dec 2020 16:10:44 +0200 Subject: [PATCH 27/56] Generic type for resultContainer this should allow typescript to infer the type of result.current further improvments needed to add type guards --- src/pure.tsx | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/src/pure.tsx b/src/pure.tsx index a0bb4f0c..cf2782ad 100644 --- a/src/pure.tsx +++ b/src/pure.tsx @@ -3,15 +3,19 @@ import { act, create, ReactTestRenderer } from 'react-test-renderer' import asyncUtils from './asyncUtils' import { cleanup, addCleanup, removeCleanup } from './cleanup' - // TODO: Add better type, currently file is utilizing minimum types to work // TODO: Attempt to refactor code to remove ESLint disables if possible // eslint-disable-next-line @typescript-eslint/no-explicit-any -type Props = {callback: (props: T) => R, hookProps: unknown, onError: CallableFunction, children: CallableFunction} +type Props = { + callback: (props: T) => R + hookProps: unknown + onError: CallableFunction + children: CallableFunction +} function TestHook({ callback, hookProps, onError, children }: Props) { try { children(callback(hookProps)) - // eslint-disable-next-line @typescript-eslint/no-implicit-any-catch + // eslint-disable-next-line @typescript-eslint/no-implicit-any-catch } catch (err) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access if (err.then) { @@ -27,13 +31,13 @@ function Fallback() { return null } -function resultContainer() { - const results: Array> = [] +function resultContainer() { + const results: Array<{ value?: R; error?: Error }> = [] const resolvers: Array = [] const result = { get all() { - return results.map(({ value, error }) => error || value) + return results.map(({ value, error }) => error ?? value) }, get current() { const { value, error } = results[results.length - 1] @@ -48,7 +52,7 @@ function resultContainer() { } } - const updateResult = (value: unknown, error?: unknown) => { + const updateResult = (value?: R, error?: Error) => { results.push({ value, error }) resolvers.splice(0, resolvers.length).forEach((resolve) => resolve()) } @@ -58,14 +62,17 @@ function resultContainer() { addResolver: (resolver: VoidFunction) => { resolvers.push(resolver) }, - setValue: (value: unknown) => updateResult(value), - setError: (error: unknown) => updateResult(undefined, error) + setValue: (value: R) => updateResult(value), + setError: (error: Error) => updateResult(undefined, error) } } // eslint-disable-next-line @typescript-eslint/no-explicit-any -function renderHook(callback: (props: T) => R, { initialProps, wrapper }: {initialProps?: T , wrapper?: React.ComponentType} = {}) { - const { result, setValue, setError, addResolver } = resultContainer() +function renderHook( + callback: (props: T) => R, + { initialProps, wrapper }: { initialProps?: T; wrapper?: React.ComponentType } = {} +) { + const { result, setValue, setError, addResolver } = resultContainer() const hookProps = { current: initialProps } const wrapUiIfNeeded = (innerElement: ReactNode) => @@ -78,15 +85,12 @@ function renderHook(callback: (props: T) => R, { initialProps, {setValue} - ) as ReactElement - - - + ) as ReactElement // eslint-disable-next-line no-undef-init let testRenderer: ReactTestRenderer | undefined - // eslint-disable-next-line @typescript-eslint/no-floating-promises - act(() => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + act(() => { testRenderer = create(toRender()) }) // eslint-disable-next-line @typescript-eslint/unbound-method From 7c8ddee84403a484dcd6d98f27716161801b4e36 Mon Sep 17 00:00:00 2001 From: Amr Gad Date: Sat, 12 Dec 2020 16:33:41 +0200 Subject: [PATCH 28/56] convert tests that require no changes renamed tests that didn't require code changes to the tests --- test/{asyncHook.js => asyncHook.ts} | 2 +- test/{autoCleanup.js => autoCleanup.ts} | 0 test/{resultHistory.js => resultHistory.ts} | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename test/{asyncHook.js => asyncHook.ts} (99%) rename test/{autoCleanup.js => autoCleanup.ts} (100%) rename test/{resultHistory.js => resultHistory.ts} (100%) diff --git a/test/asyncHook.js b/test/asyncHook.ts similarity index 99% rename from test/asyncHook.js rename to test/asyncHook.ts index e735c624..5479db82 100644 --- a/test/asyncHook.js +++ b/test/asyncHook.ts @@ -2,7 +2,7 @@ import { useState, useRef, useEffect } from 'react' import { renderHook } from '../src' describe('async hook tests', () => { - const useSequence = (...values) => { + const useSequence = (...values: string[]) => { const [first, ...otherValues] = values const [value, setValue] = useState(first) const index = useRef(0) diff --git a/test/autoCleanup.js b/test/autoCleanup.ts similarity index 100% rename from test/autoCleanup.js rename to test/autoCleanup.ts diff --git a/test/resultHistory.js b/test/resultHistory.ts similarity index 100% rename from test/resultHistory.js rename to test/resultHistory.ts From e3ecb248ece038a737bcfde5e75899acd4bf3c0a Mon Sep 17 00:00:00 2001 From: Amr Gad Date: Sat, 12 Dec 2020 17:21:12 +0200 Subject: [PATCH 29/56] add types to cleanup test --- test/{cleanup.js => cleanup.ts} | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) rename test/{cleanup.js => cleanup.ts} (92%) diff --git a/test/cleanup.js b/test/cleanup.ts similarity index 92% rename from test/cleanup.js rename to test/cleanup.ts index 05dba6dc..1eafffbf 100644 --- a/test/cleanup.js +++ b/test/cleanup.ts @@ -21,8 +21,8 @@ describe('cleanup tests', () => { }) test('should cleanup all rendered hooks', async () => { - const cleanupCalled = [] - const hookWithCleanup = (id) => { + const cleanupCalled: boolean[] = [] + const hookWithCleanup = (id: number) => { useEffect(() => { return () => { cleanupCalled[id] = true @@ -40,7 +40,7 @@ describe('cleanup tests', () => { }) test('should call cleanups in reverse order', async () => { - const callSequence = [] + const callSequence: string[] = [] addCleanup(() => { callSequence.push('cleanup') }) @@ -62,7 +62,7 @@ describe('cleanup tests', () => { }) test('should wait for async cleanup', async () => { - const callSequence = [] + const callSequence: string[] = [] addCleanup(() => { callSequence.push('cleanup') }) @@ -85,7 +85,7 @@ describe('cleanup tests', () => { }) test('should remove cleanup using removeCleanup', async () => { - const callSequence = [] + const callSequence: string[] = [] addCleanup(() => { callSequence.push('cleanup') }) @@ -110,7 +110,7 @@ describe('cleanup tests', () => { }) test('should remove cleanup using returned handler', async () => { - const callSequence = [] + const callSequence: string[] = [] addCleanup(() => { callSequence.push('cleanup') }) From d74e947032f175712ae43c2215e5eb018bb64173 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Sat, 12 Dec 2020 12:34:36 -0600 Subject: [PATCH 30/56] Test TS Overhaul - Amr, Tiger and myself worked on these commits - Other Raid members in chat assisted - All tests are strongly typed with minimal types to allow for working and made sure tests types were easily usable with types in Pure and Utils file, allow for good UX & DX --- src/pure.tsx | 4 ++-- .../{autoCleanup.disabled.js => autoCleanup.disabled.ts} | 2 +- ...Cleanup.noAfterEach.js => autoCleanup.noAfterEach.ts} | 3 ++- test/{customHook.js => customHook.ts} | 1 + test/{errorHook.js => errorHook.ts} | 9 +++++---- test/{suspenseHook.js => suspenseHook.ts} | 9 +++++---- test/{useContext.js => useContext.tsx} | 7 ++++--- test/{useEffect.js => useEffect.ts} | 0 test/{useMemo.js => useMemo.ts} | 0 test/{useReducer.js => useReducer.ts} | 4 +++- test/{useRef.js => useRef.ts} | 2 +- test/{useState.js => useState.ts} | 6 ++++-- 12 files changed, 28 insertions(+), 19 deletions(-) rename test/{autoCleanup.disabled.js => autoCleanup.disabled.ts} (93%) rename test/{autoCleanup.noAfterEach.js => autoCleanup.noAfterEach.ts} (84%) rename test/{customHook.js => customHook.ts} (92%) rename test/{errorHook.js => errorHook.ts} (94%) rename test/{suspenseHook.js => suspenseHook.ts} (77%) rename test/{useContext.js => useContext.tsx} (89%) rename test/{useEffect.js => useEffect.ts} (100%) rename test/{useMemo.js => useMemo.ts} (100%) rename test/{useReducer.js => useReducer.ts} (69%) rename test/{useRef.js => useRef.ts} (91%) rename test/{useState.js => useState.ts} (77%) diff --git a/src/pure.tsx b/src/pure.tsx index cf2782ad..fb246100 100644 --- a/src/pure.tsx +++ b/src/pure.tsx @@ -44,7 +44,7 @@ function resultContainer() { if (error) { throw error } - return value + return value as R }, get error() { const { error } = results[results.length - 1] @@ -70,7 +70,7 @@ function resultContainer() { // eslint-disable-next-line @typescript-eslint/no-explicit-any function renderHook( callback: (props: T) => R, - { initialProps, wrapper }: { initialProps?: T; wrapper?: React.ComponentType } = {} + { initialProps, wrapper }: { initialProps?: T; wrapper?: React.ComponentType } = {} ) { const { result, setValue, setError, addResolver } = resultContainer() const hookProps = { current: initialProps } diff --git a/test/autoCleanup.disabled.js b/test/autoCleanup.disabled.ts similarity index 93% rename from test/autoCleanup.disabled.js rename to test/autoCleanup.disabled.ts index 174a3135..35cbf91a 100644 --- a/test/autoCleanup.disabled.js +++ b/test/autoCleanup.disabled.ts @@ -4,7 +4,7 @@ import { useEffect } from 'react' // then we DON'T auto-wire up the afterEach for folks describe('skip auto cleanup (disabled) tests', () => { let cleanupCalled = false - let renderHook + let renderHook: (arg0: () => void) => void beforeAll(() => { process.env.RHTL_SKIP_AUTO_CLEANUP = 'true' diff --git a/test/autoCleanup.noAfterEach.js b/test/autoCleanup.noAfterEach.ts similarity index 84% rename from test/autoCleanup.noAfterEach.js rename to test/autoCleanup.noAfterEach.ts index 2531c657..cd30a841 100644 --- a/test/autoCleanup.noAfterEach.js +++ b/test/autoCleanup.noAfterEach.ts @@ -4,9 +4,10 @@ import { useEffect } from 'react' // then we DON'T auto-wire up the afterEach for folks describe('skip auto cleanup (no afterEach) tests', () => { let cleanupCalled = false - let renderHook + let renderHook: (arg0: () => void) => void beforeAll(() => { + // @ts-expect-error Turning off AfterEach -- ignore Jest LifeCycle Type // eslint-disable-next-line no-global-assign afterEach = false // eslint-disable-next-line diff --git a/test/customHook.js b/test/customHook.ts similarity index 92% rename from test/customHook.js rename to test/customHook.ts index 871c5619..4875efee 100644 --- a/test/customHook.js +++ b/test/customHook.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-floating-promises */ import { useState, useCallback } from 'react' import { renderHook, act } from '../src' diff --git a/test/errorHook.js b/test/errorHook.ts similarity index 94% rename from test/errorHook.js rename to test/errorHook.ts index 55e425e2..811c1b7b 100644 --- a/test/errorHook.js +++ b/test/errorHook.ts @@ -1,16 +1,17 @@ import { useState, useEffect } from 'react' import { renderHook } from '../src' +type throwError = Error | boolean | undefined describe('error hook tests', () => { - function useError(throwError) { + function useError(throwError: throwError) { if (throwError) { throw new Error('expected') } return true } - function useAsyncError(throwError) { - const [value, setValue] = useState() + function useAsyncError(throwError: throwError) { + const [value, setValue] = useState() useEffect(() => { const timeout = setTimeout(() => setValue(throwError), 100) return () => clearTimeout(timeout) @@ -18,7 +19,7 @@ describe('error hook tests', () => { return useError(value) } - function useEffectError(throwError) { + function useEffectError(throwError: throwError) { useEffect(() => { useError(throwError) }, [throwError]) diff --git a/test/suspenseHook.js b/test/suspenseHook.ts similarity index 77% rename from test/suspenseHook.js rename to test/suspenseHook.ts index 6dcfdeae..2853d13d 100644 --- a/test/suspenseHook.js +++ b/test/suspenseHook.ts @@ -1,10 +1,10 @@ import { renderHook } from '../src' describe('suspense hook tests', () => { - const cache = {} - const fetchName = (isSuccessful) => { + const cache: { value?: Promise | string | Error } = {} + const fetchName = (isSuccessful: boolean) => { if (!cache.value) { - cache.value = new Promise((resolve, reject) => { + cache.value = new Promise((resolve, reject) => { setTimeout(() => { if (isSuccessful) { resolve('Bob') @@ -21,7 +21,8 @@ describe('suspense hook tests', () => { const useFetchName = (isSuccessful = true) => { const name = fetchName(isSuccessful) - if (typeof name.then === 'function' || name instanceof Error) { + if (name instanceof Promise || name instanceof Error) { + // eslint-disable-next-line @typescript-eslint/no-throw-literal throw name } return name diff --git a/test/useContext.js b/test/useContext.tsx similarity index 89% rename from test/useContext.js rename to test/useContext.tsx index 4bcbe774..03bc19f4 100644 --- a/test/useContext.js +++ b/test/useContext.tsx @@ -15,7 +15,7 @@ describe('useContext tests', () => { test('should get value from context provider', () => { const TestContext = createContext('foo') - const wrapper = ({ children }) => ( + const wrapper: React.FC = ({ children } ) => ( {children} ) @@ -29,7 +29,7 @@ describe('useContext tests', () => { const value = { current: 'bar' } - const wrapper = ({ children }) => ( + const wrapper: React.FC = ({ children }) => ( {children} ) @@ -45,7 +45,8 @@ describe('useContext tests', () => { test('should update value in context when props are updated', () => { const TestContext = createContext('foo') - const wrapper = ({ current, children }) => ( + + const wrapper: React.FC<{current: string}> = ({ current, children }) => ( {children} ) diff --git a/test/useEffect.js b/test/useEffect.ts similarity index 100% rename from test/useEffect.js rename to test/useEffect.ts diff --git a/test/useMemo.js b/test/useMemo.ts similarity index 100% rename from test/useMemo.js rename to test/useMemo.ts diff --git a/test/useReducer.js b/test/useReducer.ts similarity index 69% rename from test/useReducer.js rename to test/useReducer.ts index 114f579b..43add078 100644 --- a/test/useReducer.js +++ b/test/useReducer.ts @@ -3,13 +3,15 @@ import { renderHook, act } from '../src' describe('useReducer tests', () => { test('should handle useReducer hook', () => { - const reducer = (state, action) => (action.type === 'inc' ? state + 1 : state) + const reducer = (state: number, action: { type: string }) => + action.type === 'inc' ? state + 1 : state const { result } = renderHook(() => useReducer(reducer, 0)) const [initialState, dispatch] = result.current expect(initialState).toBe(0) + // eslint-disable-next-line @typescript-eslint/no-floating-promises act(() => dispatch({ type: 'inc' })) const [state] = result.current diff --git a/test/useRef.js b/test/useRef.ts similarity index 91% rename from test/useRef.js rename to test/useRef.ts index b9dbefe3..6434d77d 100644 --- a/test/useRef.js +++ b/test/useRef.ts @@ -13,7 +13,7 @@ describe('useHook tests', () => { test('should handle useImperativeHandle hook', () => { const { result } = renderHook(() => { - const ref = useRef() + const ref = useRef>({}) useImperativeHandle(ref, () => ({ fakeImperativeMethod: () => true })) diff --git a/test/useState.js b/test/useState.ts similarity index 77% rename from test/useState.js rename to test/useState.ts index 42f3f8b0..fe7726cf 100644 --- a/test/useState.js +++ b/test/useState.ts @@ -5,7 +5,7 @@ describe('useState tests', () => { test('should use setState value', () => { const { result } = renderHook(() => useState('foo')) - const [value] = result.current + const [value] = result.current expect(value).toBe('foo') }) @@ -13,9 +13,11 @@ describe('useState tests', () => { test('should update setState value using setter', () => { const { result } = renderHook(() => useState('foo')) + const [ignoredValue, setValue] = result.current - act(() => setValue('bar')) + // eslint-disable-next-line @typescript-eslint/no-floating-promises + act(() => setValue('bar')) const [value] = result.current From d85fb8511fa443d457a8d41a95f66c25cab69da0 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Sat, 12 Dec 2020 12:44:14 -0600 Subject: [PATCH 31/56] Amr updated types useEffect --- test/useEffect.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/useEffect.ts b/test/useEffect.ts index 9e120e07..266e9aff 100644 --- a/test/useEffect.ts +++ b/test/useEffect.ts @@ -6,7 +6,7 @@ describe('useEffect tests', () => { const sideEffect = { 1: false, 2: false } const { rerender, unmount } = renderHook( - ({ id }) => { + ({ id }: { id: 1 | 2 }) => { useEffect(() => { sideEffect[id] = true return () => { @@ -35,7 +35,7 @@ describe('useEffect tests', () => { const sideEffect = { 1: false, 2: false } const { rerender, unmount } = renderHook( - ({ id }) => { + ({ id }: { id: 1 | 2 }) => { useLayoutEffect(() => { sideEffect[id] = true return () => { From ce607352f87d270c48a65f40b78c10eb9be52f95 Mon Sep 17 00:00:00 2001 From: Jacob Evans Date: Sat, 12 Dec 2020 12:46:32 -0600 Subject: [PATCH 32/56] Jens suggestion for more generic number key type --- test/useEffect.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/useEffect.ts b/test/useEffect.ts index 266e9aff..cad9d0f3 100644 --- a/test/useEffect.ts +++ b/test/useEffect.ts @@ -3,10 +3,10 @@ import { renderHook } from '../src' describe('useEffect tests', () => { test('should handle useEffect hook', () => { - const sideEffect = { 1: false, 2: false } + const sideEffect: { [key: number]: boolean } = { 1: false, 2: false } const { rerender, unmount } = renderHook( - ({ id }: { id: 1 | 2 }) => { + ({ id }) => { useEffect(() => { sideEffect[id] = true return () => { @@ -32,10 +32,10 @@ describe('useEffect tests', () => { }) test('should handle useLayoutEffect hook', () => { - const sideEffect = { 1: false, 2: false } + const sideEffect: { [key: number]: boolean } = { 1: false, 2: false } const { rerender, unmount } = renderHook( - ({ id }: { id: 1 | 2 }) => { + ({ id }) => { useLayoutEffect(() => { sideEffect[id] = true return () => { From 74793cd339fce587d02fa371f8c84e1ec3f1fadd Mon Sep 17 00:00:00 2001 From: Braydon Hall <40751395+nobrayner@users.noreply.github.com> Date: Sun, 13 Dec 2020 08:05:21 +1100 Subject: [PATCH 33/56] Remove wait reference from docs --- docs/api-reference.md | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/docs/api-reference.md b/docs/api-reference.md index a3cc05e5..fa7daf4b 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -259,28 +259,3 @@ The maximum amount of time in milliseconds (ms) to wait. By default, no timeout If this option is set to `true`, any errors that occur while waiting are treated as a failed check. If this option is set to `false`, any errors that occur while waiting cause the promise to be rejected. By default, errors are not suppressed for this utility. - -### `wait` - -_(DEPRECATED, use [`waitFor`](/reference/api#waitfor) instead)_ - -```js -function wait(callback: function(): boolean|void, options?: { - timeout?: number, - suppressErrors?: boolean -}): Promise -``` - -Returns a `Promise` that resolves if the provided callback executes without exception and returns a -truthy or `undefined` value. It is safe to use the [`result` of `renderHook`](/reference/api#result) -in the callback to perform assertion or to test values. - -#### `timeout` - -The maximum amount of time in milliseconds (ms) to wait. By default, no timeout is applied. - -#### `suppressErrors` - -If this option is set to `true`, any errors that occur while waiting are treated as a failed check. -If this option is set to `false`, any errors that occur while waiting cause the promise to be -rejected. By default, errors are suppressed for this utility. From acfed0d28bec8656cfc59289a1efa0b238928b3c Mon Sep 17 00:00:00 2001 From: Braydon Hall <40751395+nobrayner@users.noreply.github.com> Date: Sun, 13 Dec 2020 08:33:43 +1100 Subject: [PATCH 34/56] Add nobrayner to contributors --- .all-contributorsrc | 9 +++++++++ README.md | 3 +++ 2 files changed, 12 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index fd2fd14a..1cc172b9 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -212,6 +212,15 @@ "contributions": [ "code" ] + }, + { + "login": "nobrayner", + "name": "Braydon Hall", + "avatar_url": "https://avatars2.githubusercontent.com/u/40751395?v=4", + "profile": "https://github.com/nobrayner", + "contributions": [ + "code" + ] } ], "commitConvention": "none" diff --git a/README.md b/README.md index 72ef55a9..b6297fe0 100644 --- a/README.md +++ b/README.md @@ -192,6 +192,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Josh

📖
Na'aman Hirschfeld

💻 + +
Braydon Hall

💻 + From b77b17af176e971fa6d7030c7125248bdd5a0507 Mon Sep 17 00:00:00 2001 From: Braydon Hall <40751395+nobrayner@users.noreply.github.com> Date: Sun, 13 Dec 2020 10:33:17 +1100 Subject: [PATCH 35/56] Add JacobMGEvans to contributors --- .all-contributorsrc | 10 ++++++++++ README.md | 1 + 2 files changed, 11 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 1cc172b9..7161db0f 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -221,6 +221,16 @@ "contributions": [ "code" ] + }, + { + "login": "JacobMGEvans", + "name": "Jacob M-G Evans", + "avatar_url": "https://avatars1.githubusercontent.com/u/27247160?v=4", + "profile": "https://dev.to/jacobmgevans", + "contributions": [ + "code", + "test" + ] } ], "commitConvention": "none" diff --git a/README.md b/README.md index b6297fe0..9cd99dce 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Braydon Hall

💻 +
Jacob M-G Evans

💻 ⚠️ From b5977f68a3097fe55cc2cf351fcff5a686ddbeed Mon Sep 17 00:00:00 2001 From: Jacob M-G Evans <27247160+JacobMGEvans@users.noreply.github.com> Date: Sat, 12 Dec 2020 19:01:47 -0600 Subject: [PATCH 36/56] Update src/pure.tsx --- src/pure.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pure.tsx b/src/pure.tsx index fb246100..e06e6fcb 100644 --- a/src/pure.tsx +++ b/src/pure.tsx @@ -3,8 +3,6 @@ import { act, create, ReactTestRenderer } from 'react-test-renderer' import asyncUtils from './asyncUtils' import { cleanup, addCleanup, removeCleanup } from './cleanup' -// TODO: Add better type, currently file is utilizing minimum types to work -// TODO: Attempt to refactor code to remove ESLint disables if possible // eslint-disable-next-line @typescript-eslint/no-explicit-any type Props = { callback: (props: T) => R From e6997ca7ee0a2dbad75b77458397bbe0db9f028c Mon Sep 17 00:00:00 2001 From: Braydon Hall <40751395+nobrayner@users.noreply.github.com> Date: Sun, 13 Dec 2020 14:21:03 +1100 Subject: [PATCH 37/56] Add tigerabrodi to contributors --- .all-contributorsrc | 10 ++++++++++ README.md | 1 + 2 files changed, 11 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 7161db0f..4bdc0e35 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -231,6 +231,16 @@ "code", "test" ] + }, + { + "login": "tigerabrodi", + "name": "Tiger Abrodi", + "avatar_url": "https://avatars1.githubusercontent.com/u/49603590?v=4", + "profile": "https://tigerabrodi.dev/", + "contributions": [ + "code", + "test" + ] } ], "commitConvention": "none" diff --git a/README.md b/README.md index 9cd99dce..bc651e46 100644 --- a/README.md +++ b/README.md @@ -195,6 +195,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Braydon Hall

💻
Jacob M-G Evans

💻 ⚠️ +
Tiger Abrodi

💻 ⚠️ From 3485a0264c469f343699335e2ea027055e1e1c52 Mon Sep 17 00:00:00 2001 From: Braydon Hall <40751395+nobrayner@users.noreply.github.com> Date: Sun, 13 Dec 2020 17:02:25 +1100 Subject: [PATCH 38/56] Add Amr, Juhana, and Jens to contributors --- .all-contributorsrc | 29 +++++++++++++++++++++++++++++ README.md | 3 +++ 2 files changed, 32 insertions(+) diff --git a/.all-contributorsrc b/.all-contributorsrc index 4bdc0e35..d7d137c5 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -241,6 +241,35 @@ "code", "test" ] + }, + { + "login": "merodiro", + "name": "Amr A.Mohammed", + "avatar_url": "https://avatars1.githubusercontent.com/u/17033502?v=4", + "profile": "https://github.com/merodiro", + "contributions": [ + "code", + "test" + ] + }, + { + "login": "juhanakristian", + "name": "Juhana Jauhiainen", + "avatar_url": "https://avatars1.githubusercontent.com/u/544386?v=4", + "profile": "https://github.com/juhanakristian", + "contributions": [ + "code" + ] + }, + { + "login": "jensmeindertsma", + "name": "Jens Meindertsma", + "avatar_url": "https://avatars3.githubusercontent.com/u/64677517?v=4", + "profile": "https://github.com/jensmeindertsma", + "contributions": [ + "code", + "test" + ] } ], "commitConvention": "none" diff --git a/README.md b/README.md index bc651e46..b62c279d 100644 --- a/README.md +++ b/README.md @@ -196,6 +196,9 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
Braydon Hall

💻
Jacob M-G Evans

💻 ⚠️
Tiger Abrodi

💻 ⚠️ +
Amr A.Mohammed

💻 ⚠️ +
Juhana Jauhiainen

💻 +
Jens Meindertsma

💻 ⚠️ From ed2bb4aa2020dcd8c8865df953b9188370153c3a Mon Sep 17 00:00:00 2001 From: tigerabrodi Date: Sun, 13 Dec 2020 13:42:31 +0100 Subject: [PATCH 39/56] update suspenseHook, cache type and the type of the error in catch. --- test/suspenseHook.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/suspenseHook.ts b/test/suspenseHook.ts index 2853d13d..c4aef046 100644 --- a/test/suspenseHook.ts +++ b/test/suspenseHook.ts @@ -1,7 +1,7 @@ import { renderHook } from '../src' describe('suspense hook tests', () => { - const cache: { value?: Promise | string | Error } = {} + const cache: { value?: Promise | string | Error } = {} const fetchName = (isSuccessful: boolean) => { if (!cache.value) { cache.value = new Promise((resolve, reject) => { @@ -14,7 +14,7 @@ describe('suspense hook tests', () => { }, 50) }) .then((value) => (cache.value = value)) - .catch((e) => (cache.value = e)) + .catch((e: Error) => (cache.value = e)) } return cache.value } From 65e4d0cf808cae61d942bbd360666cf24b530546 Mon Sep 17 00:00:00 2001 From: tigerabrodi Date: Sun, 13 Dec 2020 13:45:32 +0100 Subject: [PATCH 40/56] cleanup.ts, update the way addCleanup adds another callback to cleanupCallbacks. --- src/cleanup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cleanup.ts b/src/cleanup.ts index a9385eff..8309bd04 100644 --- a/src/cleanup.ts +++ b/src/cleanup.ts @@ -8,7 +8,7 @@ async function cleanup() { } function addCleanup(callback: () => Promise | void) { - cleanupCallbacks.unshift(callback) + cleanupCallbacks = [callback, ...cleanupCallbacks] return () => removeCleanup(callback) } From d51ddc1d5883e5e59c6b3d449284e5e654161faa Mon Sep 17 00:00:00 2001 From: Braydon Hall <40751395+nobrayner@users.noreply.github.com> Date: Mon, 14 Dec 2020 12:42:14 +1100 Subject: [PATCH 41/56] Made generics more descriptive, made TestHook generic --- src/pure.tsx | 45 +++++++++++++++++++++++++++------------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/src/pure.tsx b/src/pure.tsx index e06e6fcb..07adf9b7 100644 --- a/src/pure.tsx +++ b/src/pure.tsx @@ -4,15 +4,25 @@ import asyncUtils from './asyncUtils' import { cleanup, addCleanup, removeCleanup } from './cleanup' // eslint-disable-next-line @typescript-eslint/no-explicit-any -type Props = { - callback: (props: T) => R - hookProps: unknown - onError: CallableFunction - children: CallableFunction +type TestHookProps = { + callback: (props: TProps) => TResult + hookProps: TProps | undefined + onError: (error: Error) => void + children: (value: TResult) => void } -function TestHook({ callback, hookProps, onError, children }: Props) { +function TestHook({ + callback, + hookProps, + onError, + children +}: TestHookProps) { try { - children(callback(hookProps)) + if (hookProps) { + children(callback(hookProps)) + } else { + // coerce into undefined, so it maintains the previous behaviour + children(callback((undefined as unknown) as TProps)) + } // eslint-disable-next-line @typescript-eslint/no-implicit-any-catch } catch (err) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access @@ -29,8 +39,8 @@ function Fallback() { return null } -function resultContainer() { - const results: Array<{ value?: R; error?: Error }> = [] +function resultContainer() { + const results: Array<{ value?: TValue; error?: Error }> = [] const resolvers: Array = [] const result = { @@ -42,7 +52,7 @@ function resultContainer() { if (error) { throw error } - return value as R + return value as TValue }, get error() { const { error } = results[results.length - 1] @@ -50,7 +60,7 @@ function resultContainer() { } } - const updateResult = (value?: R, error?: Error) => { + const updateResult = (value?: TValue, error?: Error) => { results.push({ value, error }) resolvers.splice(0, resolvers.length).forEach((resolve) => resolve()) } @@ -60,17 +70,16 @@ function resultContainer() { addResolver: (resolver: VoidFunction) => { resolvers.push(resolver) }, - setValue: (value: R) => updateResult(value), + setValue: (value: TValue) => updateResult(value), setError: (error: Error) => updateResult(undefined, error) } } -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function renderHook( - callback: (props: T) => R, - { initialProps, wrapper }: { initialProps?: T; wrapper?: React.ComponentType } = {} +function renderHook( + callback: (props: TProps) => TResult, + { initialProps, wrapper }: { initialProps?: TProps; wrapper?: React.ComponentType } = {} ) { - const { result, setValue, setError, addResolver } = resultContainer() + const { result, setValue, setError, addResolver } = resultContainer() const hookProps = { current: initialProps } const wrapUiIfNeeded = (innerElement: ReactNode) => @@ -94,7 +103,7 @@ function renderHook( // eslint-disable-next-line @typescript-eslint/unbound-method const { unmount, update } = testRenderer as ReactTestRenderer - function rerenderHook(newProps = hookProps.current) { + function rerenderHook(newProps: typeof initialProps = hookProps.current) { hookProps.current = newProps // eslint-disable-next-line @typescript-eslint/no-floating-promises act(() => { From fb93f4e11331a86ff12f40809e601016c3f92cd0 Mon Sep 17 00:00:00 2001 From: Braydon Hall <40751395+nobrayner@users.noreply.github.com> Date: Mon, 14 Dec 2020 12:46:54 +1100 Subject: [PATCH 42/56] Remove some eslint disables that didn't do anything --- src/pure.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pure.tsx b/src/pure.tsx index 07adf9b7..e54c0f72 100644 --- a/src/pure.tsx +++ b/src/pure.tsx @@ -3,7 +3,6 @@ import { act, create, ReactTestRenderer } from 'react-test-renderer' import asyncUtils from './asyncUtils' import { cleanup, addCleanup, removeCleanup } from './cleanup' -// eslint-disable-next-line @typescript-eslint/no-explicit-any type TestHookProps = { callback: (props: TProps) => TResult hookProps: TProps | undefined @@ -94,7 +93,6 @@ function renderHook( ) as ReactElement - // eslint-disable-next-line no-undef-init let testRenderer: ReactTestRenderer | undefined // eslint-disable-next-line @typescript-eslint/no-floating-promises act(() => { From 1779a85b5adc0f0b8b65542848a48947edfd4e3c Mon Sep 17 00:00:00 2001 From: Braydon Hall <40751395+nobrayner@users.noreply.github.com> Date: Mon, 14 Dec 2020 17:41:50 +1100 Subject: [PATCH 43/56] Remove DefinitelyTyped reference in CONTRIBUTING.md --- CONTRIBUTING.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8924985f..24384b04 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,11 +27,6 @@ select the added contribution type. Please make sure to run the tests before you commit your changes. You can do so by running `npm test`. -### Update Typings - -The TypeScript type definitions can be found in the -[DefinitelyTyped repo](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/testing-library__react-hooks). - ## Help needed Please check out the From 78c146e4662de1ae166a4a700e3961ab8067c7fb Mon Sep 17 00:00:00 2001 From: marcosvega91 Date: Mon, 14 Dec 2020 12:58:29 +0100 Subject: [PATCH 44/56] chore: disable declaration for tests --- .eslintrc | 5 ++++- package.json | 1 + test/tsconfig.json | 8 ++++++++ tsconfig.json | 5 ++--- 4 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 test/tsconfig.json diff --git a/.eslintrc b/.eslintrc index d80c5b77..61a9e0e3 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,5 +1,5 @@ { - "extends":[ "./node_modules/kcd-scripts/eslint.js"], + "extends": ["./node_modules/kcd-scripts/eslint.js"], "rules": { "max-lines-per-function": "off", "no-constant-condition": "off", @@ -7,5 +7,8 @@ "react-hooks/rules-of-hooks": "off", "no-console": "off", "import/no-unresolved": "off" + }, + "parserOptions": { + "project": ["./tsconfig.json", "./test/tsconfig.json"] } } diff --git a/package.json b/package.json index 6cc8d613..3c04f860 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.0.0-semantically-released", "description": "Simple and complete React hooks testing utilities that encourage good testing practices.", "main": "lib/index.js", + "types": "lib/index.d.ts", "keywords": [ "testing", "react", diff --git a/test/tsconfig.json b/test/tsconfig.json new file mode 100644 index 00000000..48209b56 --- /dev/null +++ b/test/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "declaration": false + }, + "exclude": [], + "include": ["."] +} diff --git a/tsconfig.json b/tsconfig.json index 81cc29e6..1337ac30 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,8 @@ { - "extends": "./node_modules/kcd-scripts/shared-tsconfig.json", "compilerOptions": { "allowJs": true, - "target": "ES6", + "target": "ES6" }, - "include": ["./test"] + "exclude": ["./test"] } From 9d957ba9a42c103c34b1c2e5173dc4043f8ba4a6 Mon Sep 17 00:00:00 2001 From: Juhana Jauhiainen Date: Mon, 14 Dec 2020 18:18:07 +0200 Subject: [PATCH 45/56] Removes createTimeoutError. Adds constructor to TimeoutError. Adds typeing to waitForValueToChange options --- src/asyncUtils.ts | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/asyncUtils.ts b/src/asyncUtils.ts index a7732d11..068480ed 100644 --- a/src/asyncUtils.ts +++ b/src/asyncUtils.ts @@ -7,12 +7,9 @@ export interface WaitOptions { } class TimeoutError extends Error { - timeout = true -} - -function createTimeoutError(utilName: string, { timeout }: Pick) { - const timeoutError = new TimeoutError(`Timed out in ${utilName} after ${timeout as number}ms.`) - return timeoutError + constructor(utilName: string, { timeout }: Pick) { + super(`Timed out in ${utilName} after ${timeout as number}ms.`) + } } function resolveAfter(ms: number) { @@ -27,10 +24,10 @@ function asyncUtils(addResolver: (callback: () => void) => void) { const waitForNextUpdate = async (options: Pick = {}) => { if (!nextUpdatePromise) { nextUpdatePromise = new Promise((resolve, reject) => { - let timeoutId: number + let timeoutId: ReturnType if (options.timeout && options.timeout > 0) { - timeoutId = window.setTimeout( - () => reject(createTimeoutError('waitForNextUpdate', options)), + timeoutId = setTimeout( + () => reject(new TimeoutError('waitForNextUpdate', options)), options.timeout ) } @@ -78,7 +75,7 @@ function asyncUtils(addResolver: (callback: () => void) => void) { } } catch (error: unknown) { if (error instanceof TimeoutError) { - throw createTimeoutError('waitFor', { timeout: initialTimeout }) + throw new TimeoutError('waitFor', { timeout: initialTimeout }) } throw error as Error } @@ -91,7 +88,7 @@ function asyncUtils(addResolver: (callback: () => void) => void) { } } - const waitForValueToChange = async (selector: () => unknown, options = {}) => { + const waitForValueToChange = async (selector: () => unknown, options: WaitOptions = {}) => { const initialValue = selector() try { await waitFor(() => selector() !== initialValue, { @@ -100,7 +97,7 @@ function asyncUtils(addResolver: (callback: () => void) => void) { }) } catch (error: unknown) { if (error instanceof TimeoutError) { - throw createTimeoutError('waitForValueToChange', options) + throw new TimeoutError('waitForValueToChange', options) } throw error as Error } From d1c36bda66aa3bb0e1adb289c4422f158b1bfafc Mon Sep 17 00:00:00 2001 From: Michael Peyper Date: Tue, 15 Dec 2020 13:01:19 +1100 Subject: [PATCH 46/56] Remove conditional in TestHook for hookProps --- src/pure.tsx | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/pure.tsx b/src/pure.tsx index e54c0f72..d2fff53f 100644 --- a/src/pure.tsx +++ b/src/pure.tsx @@ -16,12 +16,8 @@ function TestHook({ children }: TestHookProps) { try { - if (hookProps) { - children(callback(hookProps)) - } else { - // coerce into undefined, so it maintains the previous behaviour - children(callback((undefined as unknown) as TProps)) - } + // coerce undefined into TProps, so it maintains the previous behaviour + children(callback(hookProps as TProps)) // eslint-disable-next-line @typescript-eslint/no-implicit-any-catch } catch (err) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access From 21a1c6e67d8bd6746f43590a715725f9e69e9b32 Mon Sep 17 00:00:00 2001 From: Michael Peyper Date: Tue, 15 Dec 2020 13:05:08 +1100 Subject: [PATCH 47/56] Replace old types defs with referenced types in dependencies --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 3c04f860..572dd51b 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,8 @@ }, "dependencies": { "@babel/runtime": "^7.12.5", - "@types/testing-library__react-hooks": "^3.4.0" + "@types/react": ">=16.9.0", + "@types/react-test-renderer": ">=16.9.0" }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^4.9.1", From d621cda95bdd2c08d091ae7d29339f722a21c5fc Mon Sep 17 00:00:00 2001 From: Michael Peyper Date: Tue, 15 Dec 2020 13:06:18 +1100 Subject: [PATCH 48/56] Remove destructuring of `testRenderer` --- src/pure.tsx | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/pure.tsx b/src/pure.tsx index d2fff53f..f1860e18 100644 --- a/src/pure.tsx +++ b/src/pure.tsx @@ -89,19 +89,17 @@ function renderHook( ) as ReactElement - let testRenderer: ReactTestRenderer | undefined + let testRenderer: ReactTestRenderer // eslint-disable-next-line @typescript-eslint/no-floating-promises act(() => { testRenderer = create(toRender()) }) - // eslint-disable-next-line @typescript-eslint/unbound-method - const { unmount, update } = testRenderer as ReactTestRenderer - + function rerenderHook(newProps: typeof initialProps = hookProps.current) { hookProps.current = newProps // eslint-disable-next-line @typescript-eslint/no-floating-promises act(() => { - update(toRender()) + testRenderer.update(toRender()) }) } @@ -109,7 +107,7 @@ function renderHook( // eslint-disable-next-line @typescript-eslint/no-floating-promises act(() => { removeCleanup(unmountHook) - unmount() + testRenderer.unmount() }) } From 900e89c1ac3fd82998c55abeb2fb75f99818cb7d Mon Sep 17 00:00:00 2001 From: Michael Peyper Date: Tue, 15 Dec 2020 13:13:38 +1100 Subject: [PATCH 49/56] Disabled floating promise lint rule globally --- .eslintrc | 3 ++- src/pure.tsx | 5 +---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.eslintrc b/.eslintrc index 61a9e0e3..b725c9e7 100644 --- a/.eslintrc +++ b/.eslintrc @@ -6,7 +6,8 @@ "no-await-in-loop": "off", "react-hooks/rules-of-hooks": "off", "no-console": "off", - "import/no-unresolved": "off" + "import/no-unresolved": "off", + "@typescript-eslint/no-floating-promises": "off" }, "parserOptions": { "project": ["./tsconfig.json", "./test/tsconfig.json"] diff --git a/src/pure.tsx b/src/pure.tsx index f1860e18..7a8b1b4b 100644 --- a/src/pure.tsx +++ b/src/pure.tsx @@ -90,21 +90,18 @@ function renderHook( ) as ReactElement let testRenderer: ReactTestRenderer - // eslint-disable-next-line @typescript-eslint/no-floating-promises act(() => { testRenderer = create(toRender()) }) - + function rerenderHook(newProps: typeof initialProps = hookProps.current) { hookProps.current = newProps - // eslint-disable-next-line @typescript-eslint/no-floating-promises act(() => { testRenderer.update(toRender()) }) } function unmountHook() { - // eslint-disable-next-line @typescript-eslint/no-floating-promises act(() => { removeCleanup(unmountHook) testRenderer.unmount() From 4ead36f7e3fdfd2347b3441274880039ce0102d9 Mon Sep 17 00:00:00 2001 From: Michael Peyper Date: Tue, 15 Dec 2020 13:16:30 +1100 Subject: [PATCH 50/56] Refactor TestHook catch to not disable lint rules --- src/pure.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/pure.tsx b/src/pure.tsx index 7a8b1b4b..2dbc91d0 100644 --- a/src/pure.tsx +++ b/src/pure.tsx @@ -3,12 +3,17 @@ import { act, create, ReactTestRenderer } from 'react-test-renderer' import asyncUtils from './asyncUtils' import { cleanup, addCleanup, removeCleanup } from './cleanup' +function isPromise(value: unknown): boolean { + return typeof (value as PromiseLike).then === 'function' +} + type TestHookProps = { callback: (props: TProps) => TResult hookProps: TProps | undefined onError: (error: Error) => void children: (value: TResult) => void } + function TestHook({ callback, hookProps, @@ -18,13 +23,11 @@ function TestHook({ try { // coerce undefined into TProps, so it maintains the previous behaviour children(callback(hookProps as TProps)) - // eslint-disable-next-line @typescript-eslint/no-implicit-any-catch - } catch (err) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (err.then) { + } catch (err: unknown) { + if (isPromise(err)) { throw err } else { - onError(err) + onError(err as Error) } } return null From 25509bab27a0f15bb88a623b4a034c3d7452401a Mon Sep 17 00:00:00 2001 From: Michael Peyper Date: Tue, 15 Dec 2020 13:23:19 +1100 Subject: [PATCH 51/56] Disabled eslint error for while(true) --- .eslintrc | 3 ++- src/asyncUtils.ts | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.eslintrc b/.eslintrc index b725c9e7..6b483ba4 100644 --- a/.eslintrc +++ b/.eslintrc @@ -7,7 +7,8 @@ "react-hooks/rules-of-hooks": "off", "no-console": "off", "import/no-unresolved": "off", - "@typescript-eslint/no-floating-promises": "off" + "@typescript-eslint/no-floating-promises": "off", + "@typescript-eslint/no-unnecessary-condition": ["error", { "allowConstantLoopConditions": true }] }, "parserOptions": { "project": ["./tsconfig.json", "./test/tsconfig.json"] diff --git a/src/asyncUtils.ts b/src/asyncUtils.ts index 068480ed..22921721 100644 --- a/src/asyncUtils.ts +++ b/src/asyncUtils.ts @@ -60,7 +60,6 @@ function asyncUtils(addResolver: (callback: () => void) => void) { const waitForResult = async () => { const initialTimeout = timeout - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition while (true) { const startTime = Date.now() try { From db8a6f30c6a8e85a46b9276118080390960985af Mon Sep 17 00:00:00 2001 From: Michael Peyper Date: Tue, 15 Dec 2020 13:35:54 +1100 Subject: [PATCH 52/56] Cleaned up some line warnings from tests --- test/customHook.ts | 1 - test/suspenseHook.ts | 3 +-- test/useReducer.ts | 1 - test/useState.ts | 6 ++---- 4 files changed, 3 insertions(+), 8 deletions(-) diff --git a/test/customHook.ts b/test/customHook.ts index 4875efee..871c5619 100644 --- a/test/customHook.ts +++ b/test/customHook.ts @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/no-floating-promises */ import { useState, useCallback } from 'react' import { renderHook, act } from '../src' diff --git a/test/suspenseHook.ts b/test/suspenseHook.ts index c4aef046..8d696927 100644 --- a/test/suspenseHook.ts +++ b/test/suspenseHook.ts @@ -22,8 +22,7 @@ describe('suspense hook tests', () => { const useFetchName = (isSuccessful = true) => { const name = fetchName(isSuccessful) if (name instanceof Promise || name instanceof Error) { - // eslint-disable-next-line @typescript-eslint/no-throw-literal - throw name + throw name as unknown } return name } diff --git a/test/useReducer.ts b/test/useReducer.ts index 43add078..7b98431a 100644 --- a/test/useReducer.ts +++ b/test/useReducer.ts @@ -11,7 +11,6 @@ describe('useReducer tests', () => { expect(initialState).toBe(0) - // eslint-disable-next-line @typescript-eslint/no-floating-promises act(() => dispatch({ type: 'inc' })) const [state] = result.current diff --git a/test/useState.ts b/test/useState.ts index fe7726cf..42f3f8b0 100644 --- a/test/useState.ts +++ b/test/useState.ts @@ -5,7 +5,7 @@ describe('useState tests', () => { test('should use setState value', () => { const { result } = renderHook(() => useState('foo')) - const [value] = result.current + const [value] = result.current expect(value).toBe('foo') }) @@ -13,11 +13,9 @@ describe('useState tests', () => { test('should update setState value using setter', () => { const { result } = renderHook(() => useState('foo')) - const [ignoredValue, setValue] = result.current - // eslint-disable-next-line @typescript-eslint/no-floating-promises - act(() => setValue('bar')) + act(() => setValue('bar')) const [value] = result.current From f0d080e309fe4c1d41cd0827ab5f3cf008d12be0 Mon Sep 17 00:00:00 2001 From: Michael Peyper Date: Tue, 15 Dec 2020 13:49:48 +1100 Subject: [PATCH 53/56] Added "typecheck" kcd-script to improve "validate" script --- .eslintrc | 4 ++-- package.json | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.eslintrc b/.eslintrc index 6b483ba4..052c9caa 100644 --- a/.eslintrc +++ b/.eslintrc @@ -4,11 +4,11 @@ "max-lines-per-function": "off", "no-constant-condition": "off", "no-await-in-loop": "off", - "react-hooks/rules-of-hooks": "off", "no-console": "off", "import/no-unresolved": "off", + "react-hooks/rules-of-hooks": "off", "@typescript-eslint/no-floating-promises": "off", - "@typescript-eslint/no-unnecessary-condition": ["error", { "allowConstantLoopConditions": true }] + "@typescript-eslint/no-unnecessary-condition": "off" }, "parserOptions": { "project": ["./tsconfig.json", "./test/tsconfig.json"] diff --git a/package.json b/package.json index 572dd51b..5e90940a 100644 --- a/package.json +++ b/package.json @@ -28,10 +28,11 @@ "validate": "kcd-scripts validate", "prepare": "npm run build", "build": "kcd-scripts build --out-dir lib", + "test": "kcd-scripts test", + "typecheck": "kcd-scripts typecheck", "lint": "kcd-scripts lint", "format": "kcd-scripts format", "coverage": "codecov", - "test": "kcd-scripts test", "docs:dev": "docz dev", "docs:build": "docz build", "contributors:add": "all-contributors add" From 84a40add0d1713d4a44deaec5e2ecbcf3e256663 Mon Sep 17 00:00:00 2001 From: Michael Peyper Date: Tue, 15 Dec 2020 13:55:00 +1100 Subject: [PATCH 54/56] Clean up ThrowError type in errorHook tests --- test/errorHook.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test/errorHook.ts b/test/errorHook.ts index 811c1b7b..e507bb92 100644 --- a/test/errorHook.ts +++ b/test/errorHook.ts @@ -1,17 +1,16 @@ import { useState, useEffect } from 'react' import { renderHook } from '../src' -type throwError = Error | boolean | undefined describe('error hook tests', () => { - function useError(throwError: throwError) { + function useError(throwError: boolean) { if (throwError) { throw new Error('expected') } return true } - function useAsyncError(throwError: throwError) { - const [value, setValue] = useState() + function useAsyncError(throwError: boolean) { + const [value, setValue] = useState() useEffect(() => { const timeout = setTimeout(() => setValue(throwError), 100) return () => clearTimeout(timeout) @@ -19,7 +18,7 @@ describe('error hook tests', () => { return useError(value) } - function useEffectError(throwError: throwError) { + function useEffectError(throwError: boolean) { useEffect(() => { useError(throwError) }, [throwError]) From f33e4576aacf66ee31cbd817fd29a19789d7190b Mon Sep 17 00:00:00 2001 From: Michael Peyper Date: Tue, 15 Dec 2020 14:12:19 +1100 Subject: [PATCH 55/56] Replace VoidFunction with () => void --- src/pure.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pure.tsx b/src/pure.tsx index 2dbc91d0..a1c14897 100644 --- a/src/pure.tsx +++ b/src/pure.tsx @@ -39,7 +39,7 @@ function Fallback() { function resultContainer() { const results: Array<{ value?: TValue; error?: Error }> = [] - const resolvers: Array = [] + const resolvers: Array<() => void> = [] const result = { get all() { @@ -65,7 +65,7 @@ function resultContainer() { return { result, - addResolver: (resolver: VoidFunction) => { + addResolver: (resolver: () => void) => { resolvers.push(resolver) }, setValue: (value: TValue) => updateResult(value), From a5754f2f65cb6ee17f5d7f27b1f9f153ddd16ed6 Mon Sep 17 00:00:00 2001 From: Michael Peyper Date: Tue, 15 Dec 2020 14:15:31 +1100 Subject: [PATCH 56/56] Replace CallableFunction with a more explicit function type --- test/useRef.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/useRef.ts b/test/useRef.ts index 6434d77d..9d3851ff 100644 --- a/test/useRef.ts +++ b/test/useRef.ts @@ -13,7 +13,7 @@ describe('useHook tests', () => { test('should handle useImperativeHandle hook', () => { const { result } = renderHook(() => { - const ref = useRef>({}) + const ref = useRef boolean>>({}) useImperativeHandle(ref, () => ({ fakeImperativeMethod: () => true }))