From ebb25517a27413e2dfbe0627abebb51bb1d9af6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Wed, 17 Jul 2019 00:34:31 +0200 Subject: [PATCH 01/39] Update useToggle tests --- src/__tests__/useToggle.test.tsx | 92 +++++++++++++++++++++++--------- 1 file changed, 67 insertions(+), 25 deletions(-) diff --git a/src/__tests__/useToggle.test.tsx b/src/__tests__/useToggle.test.tsx index bcf1d78f..a4707e90 100644 --- a/src/__tests__/useToggle.test.tsx +++ b/src/__tests__/useToggle.test.tsx @@ -3,47 +3,89 @@ import useToggle from '../useToggle'; afterEach(cleanup); -describe('useToggle', () => { +describe('useToggle hook', () => { + const setUp = (initialValue: boolean) => renderHook(() => useToggle(initialValue)); + it('should be defined', () => { expect(useToggle).toBeDefined(); }); - const hook = renderHook(props => useToggle(props), { initialProps: false }); + it('should init state to true', () => { + const { result } = setUp(true); - it('should return initial state on initial render', () => { - expect(hook.result.current[0]).toBe(false); + expect(result.current[0]).toBe(true); + expect(typeof result.current[1]).toBe('function'); }); - it('should update state with correct value', () => { - hook.rerender(true); - expect(hook.result.current[0]).toBe(true); + it('should init state to false', () => { + const { result } = setUp(false); + + expect(result.current[0]).toBe(false); + expect(typeof result.current[1]).toBe('function'); + }); + + it('should set state to true', () => { + const { result } = setUp(false); + const [, toggle] = result.current; + + expect(result.current[0]).toBe(false); act(() => { - hook.result.current[1](false); + toggle(true); }); - expect(hook.result.current[0]).toBe(false); + expect(result.current[0]).toBe(true); }); - // it('should toggle state without a value parameter', () => { - // act(() => { - // hook.result.current[1](); - // }); + it('should set state to false', () => { + const { result } = setUp(true); + const [, toggle] = result.current; - // expect(hook.result.current[0]).toBe(true); - // }); + expect(result.current[0]).toBe(true); - // it('should ignore non-boolean parameters', () => { - // act(() => { - // hook.result.current[1]('string'); - // }); + act(() => { + toggle(false); + }); - // expect(hook.result.current[0]).toBe(true); + expect(result.current[0]).toBe(false); + }); - // act(() => { - // hook.result.current[1]({}); - // }); + it('should toggle state from true', () => { + const { result } = setUp(true); + const [, toggle] = result.current; - // expect(hook.result.current[0]).toBe(false); - // }); + act(() => { + toggle(); + }); + + expect(result.current[0]).toBe(false); + }); + + it('should toggle state from false', () => { + const { result } = setUp(false); + const [, toggle] = result.current; + + act(() => { + toggle(); + }); + + expect(result.current[0]).toBe(true); + }); + + it('should ignore non-boolean parameters and toggle state', () => { + const { result } = setUp(true); + const [, toggle] = result.current; + + act(() => { + toggle('string'); + }); + + expect(result.current[0]).toBe(false); + + act(() => { + toggle({}); + }); + + expect(result.current[0]).toBe(true); + }); }); From 0b4d1d6b5bfc25c15d2a7c3c948184d0ddd60c72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Wed, 17 Jul 2019 19:30:49 +0200 Subject: [PATCH 02/39] Simplify useToggle test file --- src/__tests__/useToggle.test.ts | 85 +++++++++++++++++++++++++++++ src/__tests__/useToggle.test.tsx | 91 -------------------------------- 2 files changed, 85 insertions(+), 91 deletions(-) create mode 100644 src/__tests__/useToggle.test.ts delete mode 100644 src/__tests__/useToggle.test.tsx diff --git a/src/__tests__/useToggle.test.ts b/src/__tests__/useToggle.test.ts new file mode 100644 index 00000000..c0c27e26 --- /dev/null +++ b/src/__tests__/useToggle.test.ts @@ -0,0 +1,85 @@ +import { act, cleanup, renderHook } from 'react-hooks-testing-library'; +import useToggle from '../useToggle'; + +afterEach(cleanup); + +const setUp = (initialValue: boolean) => renderHook(() => useToggle(initialValue)); + +it('should init state to true', () => { + const { result } = setUp(true); + + expect(result.current[0]).toBe(true); + expect(typeof result.current[1]).toBe('function'); +}); + +it('should init state to false', () => { + const { result } = setUp(false); + + expect(result.current[0]).toBe(false); + expect(typeof result.current[1]).toBe('function'); +}); + +it('should set state to true', () => { + const { result } = setUp(false); + const [, toggle] = result.current; + + expect(result.current[0]).toBe(false); + + act(() => { + toggle(true); + }); + + expect(result.current[0]).toBe(true); +}); + +it('should set state to false', () => { + const { result } = setUp(true); + const [, toggle] = result.current; + + expect(result.current[0]).toBe(true); + + act(() => { + toggle(false); + }); + + expect(result.current[0]).toBe(false); +}); + +it('should toggle state from true', () => { + const { result } = setUp(true); + const [, toggle] = result.current; + + act(() => { + toggle(); + }); + + expect(result.current[0]).toBe(false); +}); + +it('should toggle state from false', () => { + const { result } = setUp(false); + const [, toggle] = result.current; + + act(() => { + toggle(); + }); + + expect(result.current[0]).toBe(true); +}); + +it('should ignore non-boolean parameters and toggle state', () => { + const { result } = setUp(true); + const [, toggle] = result.current; + + act(() => { + toggle('string'); + }); + + expect(result.current[0]).toBe(false); + + act(() => { + toggle({}); + }); + + expect(result.current[0]).toBe(true); +}); diff --git a/src/__tests__/useToggle.test.tsx b/src/__tests__/useToggle.test.tsx deleted file mode 100644 index a4707e90..00000000 --- a/src/__tests__/useToggle.test.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { act, cleanup, renderHook } from 'react-hooks-testing-library'; -import useToggle from '../useToggle'; - -afterEach(cleanup); - -describe('useToggle hook', () => { - const setUp = (initialValue: boolean) => renderHook(() => useToggle(initialValue)); - - it('should be defined', () => { - expect(useToggle).toBeDefined(); - }); - - it('should init state to true', () => { - const { result } = setUp(true); - - expect(result.current[0]).toBe(true); - expect(typeof result.current[1]).toBe('function'); - }); - - it('should init state to false', () => { - const { result } = setUp(false); - - expect(result.current[0]).toBe(false); - expect(typeof result.current[1]).toBe('function'); - }); - - it('should set state to true', () => { - const { result } = setUp(false); - const [, toggle] = result.current; - - expect(result.current[0]).toBe(false); - - act(() => { - toggle(true); - }); - - expect(result.current[0]).toBe(true); - }); - - it('should set state to false', () => { - const { result } = setUp(true); - const [, toggle] = result.current; - - expect(result.current[0]).toBe(true); - - act(() => { - toggle(false); - }); - - expect(result.current[0]).toBe(false); - }); - - it('should toggle state from true', () => { - const { result } = setUp(true); - const [, toggle] = result.current; - - act(() => { - toggle(); - }); - - expect(result.current[0]).toBe(false); - }); - - it('should toggle state from false', () => { - const { result } = setUp(false); - const [, toggle] = result.current; - - act(() => { - toggle(); - }); - - expect(result.current[0]).toBe(true); - }); - - it('should ignore non-boolean parameters and toggle state', () => { - const { result } = setUp(true); - const [, toggle] = result.current; - - act(() => { - toggle('string'); - }); - - expect(result.current[0]).toBe(false); - - act(() => { - toggle({}); - }); - - expect(result.current[0]).toBe(true); - }); -}); From 8c25b50cc18fcc84216576bc70a34b1420ecb6a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Wed, 17 Jul 2019 20:03:48 +0200 Subject: [PATCH 03/39] Update react hooks testing library dependency and installing peer dependencies --- package.json | 3 +- src/__tests__/useAsync.test.tsx | 8 +-- src/__tests__/useAsyncFn.test.tsx | 4 +- src/__tests__/useObservable.test.tsx | 2 +- src/__tests__/usePrevious.test.tsx | 4 +- src/__tests__/useTitle.test.tsx | 4 +- src/__tests__/useToggle.test.ts | 6 +- src/__tests__/useWindowSize.test.tsx | 4 +- yarn.lock | 82 +++++++++------------------- 9 files changed, 39 insertions(+), 78 deletions(-) diff --git a/package.json b/package.json index f1e9f7b7..5916ee02 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,7 @@ "@storybook/addon-notes": "5.1.9", "@storybook/addon-options": "5.1.9", "@storybook/react": "5.1.9", + "@testing-library/react-hooks": "^1.1.0", "@types/jest": "24.0.15", "@types/react": "16.8.23", "babel-core": "6.26.3", @@ -90,8 +91,8 @@ "prettier": "1.17.1", "react": "16.8.6", "react-dom": "16.8.6", - "react-hooks-testing-library": "0.4.1", "react-spring": "8.0.27", + "react-test-renderer": "^16.8.6", "rebound": "0.1.0", "redux-logger": "3.0.6", "redux-thunk": "2.3.0", diff --git a/src/__tests__/useAsync.test.tsx b/src/__tests__/useAsync.test.tsx index 3764e42a..4e5ca61f 100644 --- a/src/__tests__/useAsync.test.tsx +++ b/src/__tests__/useAsync.test.tsx @@ -1,9 +1,7 @@ +import { renderHook } from '@testing-library/react-hooks'; import { useCallback } from 'react'; -import { cleanup, renderHook } from 'react-hooks-testing-library'; import useAsync from '../useAsync'; -afterEach(cleanup); - // NOTE: these tests cause console errors. // maybe we should test in a real environment instead // of a fake one? @@ -17,7 +15,7 @@ describe('useAsync', () => { let callCount = 0; const resolver = async () => { - return new Promise((resolve, reject) => { + return new Promise(resolve => { callCount++; const wait = setTimeout(() => { @@ -58,7 +56,7 @@ describe('useAsync', () => { let callCount = 0; const rejection = async () => { - return new Promise((resolve, reject) => { + return new Promise((_, reject) => { callCount++; const wait = setTimeout(() => { diff --git a/src/__tests__/useAsyncFn.test.tsx b/src/__tests__/useAsyncFn.test.tsx index 8e3826b5..f9c7d238 100644 --- a/src/__tests__/useAsyncFn.test.tsx +++ b/src/__tests__/useAsyncFn.test.tsx @@ -5,11 +5,9 @@ // does not automatically invoke the function // and it can take arguments. -import { cleanup, renderHook } from 'react-hooks-testing-library'; +import { renderHook } from '@testing-library/react-hooks'; import useAsyncFn, { AsyncState } from '../useAsyncFn'; -afterEach(cleanup); - type AdderFn = (a: number, b: number) => Promise; describe('useAsyncFn', () => { diff --git a/src/__tests__/useObservable.test.tsx b/src/__tests__/useObservable.test.tsx index 4166c986..f62a6c36 100644 --- a/src/__tests__/useObservable.test.tsx +++ b/src/__tests__/useObservable.test.tsx @@ -1,6 +1,6 @@ +import { act, renderHook } from '@testing-library/react-hooks'; import * as React from 'react'; import * as ReactDOM from 'react-dom'; -import { act, renderHook } from 'react-hooks-testing-library'; import { Subject } from 'rxjs'; import { useObservable } from '..'; diff --git a/src/__tests__/usePrevious.test.tsx b/src/__tests__/usePrevious.test.tsx index 70713653..024b4f56 100644 --- a/src/__tests__/usePrevious.test.tsx +++ b/src/__tests__/usePrevious.test.tsx @@ -1,8 +1,6 @@ -import { cleanup, renderHook } from 'react-hooks-testing-library'; +import { renderHook } from '@testing-library/react-hooks'; import usePrevious from '../usePrevious'; -afterEach(cleanup); - describe('usePrevious', () => { it('should be defined', () => { expect(usePrevious).toBeDefined(); diff --git a/src/__tests__/useTitle.test.tsx b/src/__tests__/useTitle.test.tsx index a98c5a13..b8cb626e 100644 --- a/src/__tests__/useTitle.test.tsx +++ b/src/__tests__/useTitle.test.tsx @@ -1,8 +1,6 @@ -import { cleanup, renderHook } from 'react-hooks-testing-library'; +import { renderHook } from '@testing-library/react-hooks'; import useTitle from '../useTitle'; -afterEach(cleanup); - describe('useTitle', () => { it('should be defined', () => { expect(useTitle).toBeDefined(); diff --git a/src/__tests__/useToggle.test.ts b/src/__tests__/useToggle.test.ts index c0c27e26..adafdc3b 100644 --- a/src/__tests__/useToggle.test.ts +++ b/src/__tests__/useToggle.test.ts @@ -1,8 +1,6 @@ -import { act, cleanup, renderHook } from 'react-hooks-testing-library'; +import { act, renderHook } from '@testing-library/react-hooks'; import useToggle from '../useToggle'; -afterEach(cleanup); - const setUp = (initialValue: boolean) => renderHook(() => useToggle(initialValue)); it('should init state to true', () => { @@ -16,7 +14,7 @@ it('should init state to false', () => { const { result } = setUp(false); expect(result.current[0]).toBe(false); - expect(typeof result.current[1]).toBe('function'); + expect(result.current[1]).toBeInstanceOf(Function); }); it('should set state to true', () => { diff --git a/src/__tests__/useWindowSize.test.tsx b/src/__tests__/useWindowSize.test.tsx index 9cc4be50..b3cb7fe5 100644 --- a/src/__tests__/useWindowSize.test.tsx +++ b/src/__tests__/useWindowSize.test.tsx @@ -1,4 +1,4 @@ -import { act, cleanup, renderHook } from 'react-hooks-testing-library'; +import { act, renderHook } from '@testing-library/react-hooks'; import useWindowSize from '../useWindowSize'; // simulate window resize @@ -17,8 +17,6 @@ function fireResize(type, value) { window.dispatchEvent(new Event('resize')); } -afterEach(cleanup); - describe('useWindowSize', () => { it('should be defined', () => { expect(useWindowSize).toBeDefined(); diff --git a/yarn.lock b/yarn.lock index 4ee3e358..6af6c681 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1465,7 +1465,7 @@ dependencies: regenerator-runtime "^0.13.2" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5": version "7.4.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.5.tgz#582bb531f5f9dc67d2fcb682979894f75e253f12" integrity sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ== @@ -1923,14 +1923,6 @@ source-map "^0.6.1" write-file-atomic "2.4.1" -"@jest/types@^24.7.0": - version "24.7.0" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.7.0.tgz#c4ec8d1828cdf23234d9b4ee31f5482a3f04f48b" - integrity sha512-ipJUa2rFWiKoBqMKP63Myb6h9+iT3FHRTF2M8OR6irxWzItisa8i4dcSg14IbvmXUnBlHBlUQPYUHWyX3UPpYA== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/yargs" "^12.0.9" - "@jest/types@^24.8.0": version "24.8.0" resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.8.0.tgz#f31e25948c58f0abd8c845ae26fcea1491dea7ad" @@ -2125,11 +2117,6 @@ into-stream "^4.0.0" lodash "^4.17.4" -"@sheerun/mutationobserver-shim@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.2.tgz#8013f2af54a2b7d735f71560ff360d3a8176a87b" - integrity sha512-vTCdPp/T/Q3oSqwHmZ5Kpa9oI7iLtGl3RQaA/NyLHikvcrPxACkkKVr/XzkSPJWXHRhKGzVvb0urJsbMlRxi1Q== - "@storybook/addon-actions@5.1.9": version "5.1.9" resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-5.1.9.tgz#a515b62b109cb886ccd75ef2f5b12f8c27b43dd3" @@ -2592,6 +2579,15 @@ "@svgr/plugin-svgo" "^4.0.3" loader-utils "^1.1.0" +"@testing-library/react-hooks@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-1.1.0.tgz#14b6b5c7c3d0e2cb3e55e9cbb248b44321641c64" + integrity sha512-piE/ceQoNf134FFVXBABDbttBJ8eLPD4eg7zIciVJv92RyvoIsBHCvvG8Vd4IG5pyuWYrkLsZTO8ucZBwa4twA== + dependencies: + "@babel/runtime" "^7.4.2" + "@types/react" "^16.8.22" + "@types/react-test-renderer" "^16.8.2" + "@types/babel__core@^7.1.0": version "7.1.0" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.0.tgz#710f2487dda4dcfd010ca6abb2b4dc7394365c51" @@ -2701,7 +2697,14 @@ resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8" integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw== -"@types/react@16.8.23": +"@types/react-test-renderer@^16.8.2": + version "16.8.2" + resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-16.8.2.tgz#ad544b5571ebfc5f182c320376f1431a2b725c5e" + integrity sha512-cm42QR9S9V3aOxLh7Fh7PUqQ8oSfSdnSni30T7TiTmlKvE6aUlo+LhQAzjnZBPriD9vYmgG8MXI8UDK4Nfb7gg== + dependencies: + "@types/react" "*" + +"@types/react@*", "@types/react@16.8.23", "@types/react@^16.8.22": version "16.8.23" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.8.23.tgz#ec6be3ceed6353a20948169b6cb4c97b65b97ad2" integrity sha512-abkEOIeljniUN9qB5onp++g0EY38h7atnDHxwKUFz1r3VH1+yG1OKi2sNPTyObL40goBmfKFpdii2lEzwLX1cA== @@ -5628,16 +5631,6 @@ dom-serializer@0: domelementtype "~1.1.1" entities "~1.1.1" -dom-testing-library@^3.19.0: - version "3.19.0" - resolved "https://registry.yarnpkg.com/dom-testing-library/-/dom-testing-library-3.19.0.tgz#652ade2cd93ae98beb52b2878eeb9420d7599832" - integrity sha512-gkGXP5GevcjC24Tk6Y6RwrZ7Nz0Ul4bchXV4yHLcnMidMp/EdBCvtHEgHTsZ2yZ4DhUpLowGbJv/1u1Z7bPvtw== - dependencies: - "@babel/runtime" "^7.3.4" - "@sheerun/mutationobserver-shim" "^0.3.2" - pretty-format "^24.5.0" - wait-for-expect "^1.1.0" - dom-walk@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" @@ -11219,16 +11212,6 @@ pretty-error@^2.1.1: renderkid "^2.0.1" utila "~0.4" -pretty-format@^24.5.0: - version "24.7.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.7.0.tgz#d23106bc2edcd776079c2daa5da02bcb12ed0c10" - integrity sha512-apen5cjf/U4dj7tHetpC7UEFCvtAgnNZnBDkfPv3fokzIqyOJckAG9OlAPC1BlFALnqT/lGB2tl9EJjlK6eCsA== - dependencies: - "@jest/types" "^24.7.0" - ansi-regex "^4.0.0" - ansi-styles "^3.2.0" - react-is "^16.8.4" - pretty-format@^24.8.0: version "24.8.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.8.0.tgz#8dae7044f58db7cb8be245383b565a963e3c27f2" @@ -11676,14 +11659,6 @@ react-helmet-async@^1.0.2: react-fast-compare "2.0.4" shallowequal "1.1.0" -react-hooks-testing-library@0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/react-hooks-testing-library/-/react-hooks-testing-library-0.4.1.tgz#481b960d647d3cc7c8bbaf410014daa8ce0b1360" - integrity sha512-ogmlyW7ycZe3+HIk1Y+FUV5ExpZwLi/RlF1qGLLbTkUdaAsGgp9fSF+wFNWi2KNKBTWG0FkkqOrLkcwq0z3ebQ== - dependencies: - "@babel/runtime" "^7.4.2" - react-testing-library "^6.0.3" - react-hotkeys@2.0.0-pre4: version "2.0.0-pre4" resolved "https://registry.yarnpkg.com/react-hotkeys/-/react-hotkeys-2.0.0-pre4.tgz#a1c248a51bdba4282c36bf3204f80d58abc73333" @@ -11712,7 +11687,7 @@ react-is@^16.7.0, react-is@^16.8.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.4.tgz#90f336a68c3a29a096a3d648ab80e87ec61482a2" integrity sha512-PVadd+WaUDOAciICm/J1waJaSvgq+4rHE/K70j0PFqKhkTBsPv/82UGQJNXAngz1fOQLLxI6z1sEDmJDQhCTAA== -react-is@^16.8.4: +react-is@^16.8.4, react-is@^16.8.6: version "16.8.6" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16" integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA== @@ -11785,13 +11760,15 @@ react-syntax-highlighter@^8.0.1: prismjs "^1.8.4" refractor "^2.4.1" -react-testing-library@^6.0.3: - version "6.1.2" - resolved "https://registry.yarnpkg.com/react-testing-library/-/react-testing-library-6.1.2.tgz#f6bba6eeecedac736eb00b22b4c70bae04535a4f" - integrity sha512-z69lhRDGe7u/NOjDCeFRoe1cB5ckJ4656n0tj/Fdcr6OoBUu7q9DBw0ftR7v5i3GRpdSWelnvl+feZFOyXyxwg== +react-test-renderer@^16.8.6: + version "16.8.6" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.8.6.tgz#188d8029b8c39c786f998aa3efd3ffe7642d5ba1" + integrity sha512-H2srzU5IWYT6cZXof6AhUcx/wEyJddQ8l7cLM/F7gDXYyPr4oq+vCIxJYXVGhId1J706sqziAjuOEjyNkfgoEw== dependencies: - "@babel/runtime" "^7.4.2" - dom-testing-library "^3.19.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + react-is "^16.8.6" + scheduler "^0.13.6" react-textarea-autosize@^7.1.0: version "7.1.0" @@ -14336,11 +14313,6 @@ w3c-hr-time@^1.0.1: dependencies: browser-process-hrtime "^0.1.2" -wait-for-expect@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/wait-for-expect/-/wait-for-expect-1.1.1.tgz#9cd10e07d52810af9e0aaf509872e38f3c3d81ae" - integrity sha512-vd9JOqqEcBbCDhARWhW85ecjaEcfBLuXgVBqatfS3iw6oU4kzAcs+sCNjF+TC9YHPImCW7ypsuQc+htscIAQCw== - walker@^1.0.7, walker@~1.0.5: version "1.0.7" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" From f2d105737bccb63e6605ced1d6930e510cd7bf74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Wed, 17 Jul 2019 20:07:16 +0200 Subject: [PATCH 04/39] Add useBoolean tests --- src/__tests__/useBoolean.test.ts | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/__tests__/useBoolean.test.ts diff --git a/src/__tests__/useBoolean.test.ts b/src/__tests__/useBoolean.test.ts new file mode 100644 index 00000000..8f633e9e --- /dev/null +++ b/src/__tests__/useBoolean.test.ts @@ -0,0 +1,6 @@ +import useBoolean from '../useBoolean'; +import useToggle from '../useToggle'; + +it('should be an alias for useToggle ', () => { + expect(useBoolean).toBe(useToggle); +}); From 7b2f877dd3b76367bc4ce5b1e8b9a44cc5bb24fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Sun, 21 Jul 2019 19:55:29 +0200 Subject: [PATCH 05/39] Wrap tests in describe block --- src/__tests__/useBoolean.test.ts | 6 +- src/__tests__/useToggle.test.ts | 116 ++++++++++++++++--------------- 2 files changed, 63 insertions(+), 59 deletions(-) diff --git a/src/__tests__/useBoolean.test.ts b/src/__tests__/useBoolean.test.ts index 8f633e9e..4dcafae0 100644 --- a/src/__tests__/useBoolean.test.ts +++ b/src/__tests__/useBoolean.test.ts @@ -1,6 +1,8 @@ import useBoolean from '../useBoolean'; import useToggle from '../useToggle'; -it('should be an alias for useToggle ', () => { - expect(useBoolean).toBe(useToggle); +describe('useBoolean hook', () => { + it('should be an alias for useToggle ', () => { + expect(useBoolean).toBe(useToggle); + }); }); diff --git a/src/__tests__/useToggle.test.ts b/src/__tests__/useToggle.test.ts index adafdc3b..32870fb4 100644 --- a/src/__tests__/useToggle.test.ts +++ b/src/__tests__/useToggle.test.ts @@ -1,83 +1,85 @@ import { act, renderHook } from '@testing-library/react-hooks'; import useToggle from '../useToggle'; -const setUp = (initialValue: boolean) => renderHook(() => useToggle(initialValue)); +describe('useToggle hook', () => { + const setUp = (initialValue: boolean) => renderHook(() => useToggle(initialValue)); -it('should init state to true', () => { - const { result } = setUp(true); + it('should init state to true', () => { + const { result } = setUp(true); - expect(result.current[0]).toBe(true); - expect(typeof result.current[1]).toBe('function'); -}); - -it('should init state to false', () => { - const { result } = setUp(false); - - expect(result.current[0]).toBe(false); - expect(result.current[1]).toBeInstanceOf(Function); -}); - -it('should set state to true', () => { - const { result } = setUp(false); - const [, toggle] = result.current; - - expect(result.current[0]).toBe(false); - - act(() => { - toggle(true); + expect(result.current[0]).toBe(true); + expect(typeof result.current[1]).toBe('function'); }); - expect(result.current[0]).toBe(true); -}); + it('should init state to false', () => { + const { result } = setUp(false); -it('should set state to false', () => { - const { result } = setUp(true); - const [, toggle] = result.current; - - expect(result.current[0]).toBe(true); - - act(() => { - toggle(false); + expect(result.current[0]).toBe(false); + expect(result.current[1]).toBeInstanceOf(Function); }); - expect(result.current[0]).toBe(false); -}); + it('should set state to true', () => { + const { result } = setUp(false); + const [, toggle] = result.current; -it('should toggle state from true', () => { - const { result } = setUp(true); - const [, toggle] = result.current; + expect(result.current[0]).toBe(false); - act(() => { - toggle(); + act(() => { + toggle(true); + }); + + expect(result.current[0]).toBe(true); }); - expect(result.current[0]).toBe(false); -}); + it('should set state to false', () => { + const { result } = setUp(true); + const [, toggle] = result.current; -it('should toggle state from false', () => { - const { result } = setUp(false); - const [, toggle] = result.current; + expect(result.current[0]).toBe(true); - act(() => { - toggle(); + act(() => { + toggle(false); + }); + + expect(result.current[0]).toBe(false); }); - expect(result.current[0]).toBe(true); -}); + it('should toggle state from true', () => { + const { result } = setUp(true); + const [, toggle] = result.current; -it('should ignore non-boolean parameters and toggle state', () => { - const { result } = setUp(true); - const [, toggle] = result.current; + act(() => { + toggle(); + }); - act(() => { - toggle('string'); + expect(result.current[0]).toBe(false); }); - expect(result.current[0]).toBe(false); + it('should toggle state from false', () => { + const { result } = setUp(false); + const [, toggle] = result.current; - act(() => { - toggle({}); + act(() => { + toggle(); + }); + + expect(result.current[0]).toBe(true); }); - expect(result.current[0]).toBe(true); + it('should ignore non-boolean parameters and toggle state', () => { + const { result } = setUp(true); + const [, toggle] = result.current; + + act(() => { + toggle('string'); + }); + + expect(result.current[0]).toBe(false); + + act(() => { + toggle({}); + }); + + expect(result.current[0]).toBe(true); + }); }); From 988e0894e26a76d8b124b0281072062c2b9a5ea9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Sun, 21 Jul 2019 20:15:03 +0200 Subject: [PATCH 06/39] Add useNumber tests --- src/__tests__/useNumber.test.ts | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/__tests__/useNumber.test.ts diff --git a/src/__tests__/useNumber.test.ts b/src/__tests__/useNumber.test.ts new file mode 100644 index 00000000..5667c2a9 --- /dev/null +++ b/src/__tests__/useNumber.test.ts @@ -0,0 +1,8 @@ +import useNumber from '../useNumber'; +import useCounter from '../useCounter'; + +describe('useNumber hook', () => { + it('should be an alias for useCounter', () => { + expect(useNumber).toBe(useCounter); + }); +}); From de62e1aa8d6f4ce785cd8eabef8c0d6266539e34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Sun, 21 Jul 2019 21:04:24 +0200 Subject: [PATCH 07/39] Add useCounter tests --- src/__tests__/useCounter.test.ts | 130 +++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 src/__tests__/useCounter.test.ts diff --git a/src/__tests__/useCounter.test.ts b/src/__tests__/useCounter.test.ts new file mode 100644 index 00000000..4a37b267 --- /dev/null +++ b/src/__tests__/useCounter.test.ts @@ -0,0 +1,130 @@ +import { act, renderHook } from '@testing-library/react-hooks'; +import useCounter from '../useCounter'; + +describe('useCounter hook', () => { + const setUp = (initialValue?: number) => renderHook(() => useCounter(initialValue)); + + it('should init counter', () => { + const { result } = setUp(5); + + expect(result.current[0]).toBe(5); + expect(result.current[1]).toEqual({ + inc: expect.any(Function), + dec: expect.any(Function), + get: expect.any(Function), + set: expect.any(Function), + reset: expect.any(Function), + }); + }); + + it('should init counter to 0 if not initial value received', () => { + const { result } = setUp(); + + expect(result.current[0]).toBe(0); + }); + + it('should init counter to negative number', () => { + const { result } = setUp(-2); + + expect(result.current[0]).toBe(-2); + }); + + it('should get current counter', () => { + const { result } = setUp(5); + const { get } = result.current[1]; + + expect(get()).toBe(5); + }); + + it('should increment by 1 if not value received', () => { + const { result } = setUp(5); + const { get, inc } = result.current[1]; + + act(() => inc()); + + expect(result.current[0]).toBe(6); + expect(get()).toBe(6); + }); + + it('should increment by value received', () => { + const { result } = setUp(5); + const { get, inc } = result.current[1]; + + act(() => inc(9)); + + expect(result.current[0]).toBe(14); + expect(get()).toBe(14); + }); + + it('should decrement by 1 if not value received', () => { + const { result } = setUp(5); + const { get, dec } = result.current[1]; + + act(() => dec()); + + expect(result.current[0]).toBe(4); + expect(get()).toBe(4); + }); + + it('should decrement by value received', () => { + const { result } = setUp(5); + const { get, dec } = result.current[1]; + + act(() => dec(9)); + + expect(result.current[0]).toBe(-4); + expect(get()).toBe(-4); + }); + + it('should set to value received', () => { + const { result } = setUp(5); + const { get, set } = result.current[1]; + + act(() => set(17)); + + expect(result.current[0]).toBe(17); + expect(get()).toBe(17); + }); + + it('should reset to original value', () => { + const { result } = setUp(5); + const { get, set, reset } = result.current[1]; + + // set different value than initial one... + act(() => set(17)); + expect(result.current[0]).toBe(17); + + // ... and reset it to initial one + act(() => reset()); + expect(result.current[0]).toBe(5); + expect(get()).toBe(5); + }); + + it('should reset and set new original value', () => { + const { result } = setUp(5); + const { get, set, reset } = result.current[1]; + + // set different value than initial one... + act(() => set(17)); + expect(result.current[0]).toBe(17); + + // ... now reset and set it to different than initial one... + act(() => reset(8)); + expect(result.current[0]).toBe(8); + + // ... and set different value than initial one again... + act(() => set(32)); + expect(result.current[0]).toBe(32); + + // ... and reset it to new initial value + act(() => reset()); + expect(result.current[0]).toBe(8); + expect(get()).toBe(8); + }); + + it.todo('should throw an error if initial value is other than a number'); + it.todo('should throw an error if increment value is other than a number'); + it.todo('should throw an error if increment value is a negative number'); + it.todo('should throw an error if decrement value is other than a number'); + it.todo('should throw an error if decrement value is a negative number'); +}); From 1b2509a44b4dc5962a7c2ff75430a5fe1aa6b939 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Sun, 21 Jul 2019 21:48:31 +0200 Subject: [PATCH 08/39] Add useGetSet tests --- src/__tests__/useGetSet.test.ts | 66 +++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/__tests__/useGetSet.test.ts diff --git a/src/__tests__/useGetSet.test.ts b/src/__tests__/useGetSet.test.ts new file mode 100644 index 00000000..f0925d01 --- /dev/null +++ b/src/__tests__/useGetSet.test.ts @@ -0,0 +1,66 @@ +import { act, renderHook } from '@testing-library/react-hooks'; +import useGetSet from '../useGetSet'; + +describe('useGetSet hook', () => { + const setUp = (initialValue: any) => renderHook(() => useGetSet(initialValue)); + + beforeEach(() => { + jest.useFakeTimers(); + }); + + it('should init getter and setter', () => { + const { result } = setUp('foo'); + const [get, set] = result.current; + + expect(get).toBeInstanceOf(Function); + expect(set).toBeInstanceOf(Function); + }); + + it('should get current state', () => { + const { result } = setUp('foo'); + const [get] = result.current; + + const currentState = get(); + + expect(currentState).toBe('foo'); + }); + + it('should set new state', () => { + const { result } = setUp('foo'); + const [get, set] = result.current; + + act(() => set('bar')); + + const currentState = get(); + expect(currentState).toBe('bar'); + }); + + /** + * This test implements the special demo in storybook that increments a number + * after 1 second on each click. + */ + it('should get and set expected values when used in nested functions', () => { + const onClick = jest.fn(() => { + setTimeout(() => { + set(get() + 1); + }, 1000); + }); + + const { result } = setUp(0); + const [get, set] = result.current; + + // simulate 3 clicks + onClick(); + onClick(); + onClick(); + + // fast-forward until all timers have been executed + act(() => { + jest.runAllTimers(); + }); + + const currentState = get(); + expect(currentState).toBe(3); + expect(onClick).toHaveBeenCalledTimes(3); + }); +}); From 7d080bac67903a5d5b45f15016de20d34deb3482 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Mon, 22 Jul 2019 00:17:25 +0200 Subject: [PATCH 09/39] Add useGetSetState tests --- src/__tests__/useGetSet.test.ts | 16 ++-- src/__tests__/useGetSetState.test.ts | 127 +++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 8 deletions(-) create mode 100644 src/__tests__/useGetSetState.test.ts diff --git a/src/__tests__/useGetSet.test.ts b/src/__tests__/useGetSet.test.ts index f0925d01..9e838f95 100644 --- a/src/__tests__/useGetSet.test.ts +++ b/src/__tests__/useGetSet.test.ts @@ -16,23 +16,23 @@ describe('useGetSet hook', () => { expect(set).toBeInstanceOf(Function); }); - it('should get current state', () => { + it('should get current value', () => { const { result } = setUp('foo'); const [get] = result.current; - const currentState = get(); + const currentValue = get(); - expect(currentState).toBe('foo'); + expect(currentValue).toBe('foo'); }); - it('should set new state', () => { + it('should set new value', () => { const { result } = setUp('foo'); const [get, set] = result.current; act(() => set('bar')); - const currentState = get(); - expect(currentState).toBe('bar'); + const currentValue = get(); + expect(currentValue).toBe('bar'); }); /** @@ -59,8 +59,8 @@ describe('useGetSet hook', () => { jest.runAllTimers(); }); - const currentState = get(); - expect(currentState).toBe(3); + const currentValue = get(); + expect(currentValue).toBe(3); expect(onClick).toHaveBeenCalledTimes(3); }); }); diff --git a/src/__tests__/useGetSetState.test.ts b/src/__tests__/useGetSetState.test.ts new file mode 100644 index 00000000..f3ea5fa7 --- /dev/null +++ b/src/__tests__/useGetSetState.test.ts @@ -0,0 +1,127 @@ +import { act, renderHook } from '@testing-library/react-hooks'; +import useGetSetState from '../useGetSetState'; + +const originalConsoleError = console.error; +const mockConsoleError = jest.fn(); + +describe('useGetSetState hook', () => { + const setUp = (initialState: any) => renderHook(() => useGetSetState(initialState)); + + beforeAll(() => { + console.error = mockConsoleError; + }); + + afterAll(() => { + console.error = originalConsoleError; + }); + + beforeEach(() => { + jest.useFakeTimers(); + }); + + it('should init getter and setter', () => { + const { result } = setUp({ foo: 'initialValue' }); + const [get, set] = result.current; + + expect(get).toBeInstanceOf(Function); + expect(set).toBeInstanceOf(Function); + }); + + it('should log an error if init with something different than an object', () => { + expect(mockConsoleError).not.toHaveBeenCalled(); + + setUp('not an object'); + + expect(mockConsoleError).toHaveBeenCalledTimes(1); + expect(mockConsoleError).toHaveBeenCalledWith('useGetSetState initial state must be an object.'); + }); + + it('should get current state', () => { + const { result } = setUp({ foo: 'a', bar: 'z' }); + const [get] = result.current; + + const currentState = get(); + + expect(currentState).toEqual({ foo: 'a', bar: 'z' }); + }); + + it('should set new state by applying partial patch with existing keys', () => { + const { result } = setUp({ foo: 'a', bar: 'z' }); + const [get, set] = result.current; + + act(() => set({ bar: 'y' })); + + const currentState = get(); + expect(currentState).toEqual({ foo: 'a', bar: 'y' }); + }); + + it('should set new state by applying partial patch with new keys', () => { + const { result } = setUp({ foo: 'a', bar: 'z' }); + const [get, set] = result.current; + + act(() => set({ qux: 'f' })); + + const currentState = get(); + expect(currentState).toEqual({ foo: 'a', bar: 'z', qux: 'f' }); + }); + + it('should set new state by applying partial patch with both new and old keys', () => { + const { result } = setUp({ foo: 'a', bar: 'z' }); + const [get, set] = result.current; + + act(() => set({ bar: 'y', qux: 'f' })); + + const currentState = get(); + expect(currentState).toEqual({ foo: 'a', bar: 'y', qux: 'f' }); + }); + + it('should NOT set new state if empty partial patch received', () => { + const { result } = setUp({ foo: 'a', bar: 'z' }); + const [get, set] = result.current; + + act(() => set({})); + + const currentState = get(); + expect(currentState).toEqual({ foo: 'a', bar: 'z' }); + }); + + it('should log an error if set with a patch different than an object', () => { + const { result } = setUp({ foo: 'a', bar: 'z' }); + const [, set] = result.current; + expect(mockConsoleError).not.toHaveBeenCalled(); + + // @ts-ignore + act(() => set('not an object')); + + expect(mockConsoleError).toHaveBeenCalledTimes(1); + expect(mockConsoleError).toHaveBeenCalledWith('useGetSetState setter patch must be an object.'); + }); + + /** + * This test is equivalent to demo one for `useGetSet` hook. + */ + it('should get and set expected state when used in nested functions', () => { + const onClick = jest.fn(() => { + setTimeout(() => { + set({ counter: get().counter + 1 }); + }, 1000); + }); + + const { result } = setUp({ counter: 0 }); + const [get, set] = result.current; + + // simulate 3 clicks + onClick(); + onClick(); + onClick(); + + // fast-forward until all timers have been executed + act(() => { + jest.runAllTimers(); + }); + + const currentState = get(); + expect(currentState).toEqual({ counter: 3 }); + expect(onClick).toHaveBeenCalledTimes(3); + }); +}); From 89f0b82f74b610cd03cc928924ff3a369c732162 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Mon, 22 Jul 2019 00:22:59 +0200 Subject: [PATCH 10/39] Add useGetSetState additional test --- src/__tests__/useGetSetState.test.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/__tests__/useGetSetState.test.ts b/src/__tests__/useGetSetState.test.ts index f3ea5fa7..217b8225 100644 --- a/src/__tests__/useGetSetState.test.ts +++ b/src/__tests__/useGetSetState.test.ts @@ -45,7 +45,7 @@ describe('useGetSetState hook', () => { expect(currentState).toEqual({ foo: 'a', bar: 'z' }); }); - it('should set new state by applying partial patch with existing keys', () => { + it('should set new state by applying patch with existing keys', () => { const { result } = setUp({ foo: 'a', bar: 'z' }); const [get, set] = result.current; @@ -55,7 +55,7 @@ describe('useGetSetState hook', () => { expect(currentState).toEqual({ foo: 'a', bar: 'y' }); }); - it('should set new state by applying partial patch with new keys', () => { + it('should set new state by applying patch with new keys', () => { const { result } = setUp({ foo: 'a', bar: 'z' }); const [get, set] = result.current; @@ -65,7 +65,7 @@ describe('useGetSetState hook', () => { expect(currentState).toEqual({ foo: 'a', bar: 'z', qux: 'f' }); }); - it('should set new state by applying partial patch with both new and old keys', () => { + it('should set new state by applying patch with both new and old keys', () => { const { result } = setUp({ foo: 'a', bar: 'z' }); const [get, set] = result.current; @@ -75,7 +75,7 @@ describe('useGetSetState hook', () => { expect(currentState).toEqual({ foo: 'a', bar: 'y', qux: 'f' }); }); - it('should NOT set new state if empty partial patch received', () => { + it('should NOT set new state if empty patch received', () => { const { result } = setUp({ foo: 'a', bar: 'z' }); const [get, set] = result.current; @@ -85,6 +85,17 @@ describe('useGetSetState hook', () => { expect(currentState).toEqual({ foo: 'a', bar: 'z' }); }); + it('should NOT set new state if no patch received', () => { + const { result } = setUp({ foo: 'a', bar: 'z' }); + const [get, set] = result.current; + + // @ts-ignore + act(() => set()); + + const currentState = get(); + expect(currentState).toEqual({ foo: 'a', bar: 'z' }); + }); + it('should log an error if set with a patch different than an object', () => { const { result } = setUp({ foo: 'a', bar: 'z' }); const [, set] = result.current; From 8ccc365dc1df7475da6cdd33b2a3b9451c554b6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Mon, 22 Jul 2019 01:23:18 +0200 Subject: [PATCH 11/39] Add createMemo tests --- src/__tests__/createMemo.test.ts | 52 ++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 src/__tests__/createMemo.test.ts diff --git a/src/__tests__/createMemo.test.ts b/src/__tests__/createMemo.test.ts new file mode 100644 index 00000000..b7cf7f39 --- /dev/null +++ b/src/__tests__/createMemo.test.ts @@ -0,0 +1,52 @@ +import { renderHook } from '@testing-library/react-hooks'; +import createMemo from '../createMemo'; + +const fibonacci = jest.fn((n: number) => { + if (n === 0) { + return 0; + } + if (n === 1) { + return 1; + } + return fibonacci(n - 1) + fibonacci(n - 2); +}); + +describe('createMemo hook factory', () => { + it('should init memoized hook', () => { + const useMemoFibonacci = createMemo(fibonacci); + + expect(useMemoFibonacci).toBeInstanceOf(Function); + }); + + describe('created memo hook', () => { + let useMemoFibonacci; + const setUp = (initialValue: any) => renderHook(() => useMemoFibonacci(initialValue)); + + beforeEach(() => { + useMemoFibonacci = createMemo(fibonacci); + }); + + it.each([[1], [3], [5]])('should return same result as original function for argument %d', (val: number) => { + const { result } = setUp(val); + expect(result.current).toBe(fibonacci(val)); + }); + + // MB: Is `createMemo` really memoizing original fn? Or problem mocking fn for tests here? + xit('should NOT call original function for same arguments', () => { + // call memo fn and get how many times original fn was called + setUp(5); + const memoFnCalledTimes = fibonacci.mock.calls.length; + console.log(fibonacci.mock.calls.length); + + fibonacci.mockClear(); + console.log(fibonacci.mock.calls.length); + + // call original fn and get how many times it was called + fibonacci(5); + const originalFnCalledTimes = fibonacci.mock.calls.length; + console.log(fibonacci.mock.calls.length); + + expect(memoFnCalledTimes).toBeLessThan(originalFnCalledTimes); + }); + }); +}); From 98581e9e8873f11da3529baa0285c49bae94246a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Mon, 22 Jul 2019 01:40:19 +0200 Subject: [PATCH 12/39] Improve createMemo tests --- src/__tests__/createMemo.test.ts | 53 +++++++++++++++----------------- 1 file changed, 24 insertions(+), 29 deletions(-) diff --git a/src/__tests__/createMemo.test.ts b/src/__tests__/createMemo.test.ts index b7cf7f39..caab5dbf 100644 --- a/src/__tests__/createMemo.test.ts +++ b/src/__tests__/createMemo.test.ts @@ -1,52 +1,47 @@ import { renderHook } from '@testing-library/react-hooks'; import createMemo from '../createMemo'; -const fibonacci = jest.fn((n: number) => { - if (n === 0) { - return 0; - } - if (n === 1) { - return 1; - } - return fibonacci(n - 1) + fibonacci(n - 2); -}); +const getDouble = jest.fn((n: number): number => n * 2); describe('createMemo hook factory', () => { it('should init memoized hook', () => { - const useMemoFibonacci = createMemo(fibonacci); + const useMemoGetDouble = createMemo(getDouble); - expect(useMemoFibonacci).toBeInstanceOf(Function); + expect(useMemoGetDouble).toBeInstanceOf(Function); }); describe('created memo hook', () => { - let useMemoFibonacci; - const setUp = (initialValue: any) => renderHook(() => useMemoFibonacci(initialValue)); + let useMemoGetDouble; beforeEach(() => { - useMemoFibonacci = createMemo(fibonacci); + useMemoGetDouble = createMemo(getDouble); }); it.each([[1], [3], [5]])('should return same result as original function for argument %d', (val: number) => { - const { result } = setUp(val); - expect(result.current).toBe(fibonacci(val)); + const { result } = renderHook(() => useMemoGetDouble(val)); + expect(result.current).toBe(getDouble(val)); }); - // MB: Is `createMemo` really memoizing original fn? Or problem mocking fn for tests here? - xit('should NOT call original function for same arguments', () => { - // call memo fn and get how many times original fn was called - setUp(5); - const memoFnCalledTimes = fibonacci.mock.calls.length; - console.log(fibonacci.mock.calls.length); + it('should NOT call original function for same arguments', () => { + let initialValue = 5; + expect(getDouble).not.toHaveBeenCalled(); - fibonacci.mockClear(); - console.log(fibonacci.mock.calls.length); + // it's called first time calculating for argument 5 + const { rerender } = renderHook(() => useMemoGetDouble(initialValue)); + expect(getDouble).toHaveBeenCalled(); - // call original fn and get how many times it was called - fibonacci(5); - const originalFnCalledTimes = fibonacci.mock.calls.length; - console.log(fibonacci.mock.calls.length); + getDouble.mockClear(); - expect(memoFnCalledTimes).toBeLessThan(originalFnCalledTimes); + // it's NOT called second time calculating for argument 5 + rerender(); + expect(getDouble).not.toHaveBeenCalled(); + + getDouble.mockClear(); + + // it's called again calculating for different argument + initialValue = 7; + rerender(); + expect(getDouble).toHaveBeenCalled(); }); }); }); From 4a945363e2662db6e98b388ebc17b5ccff324bf5 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 29 Jul 2019 17:29:58 +0000 Subject: [PATCH 13/39] chore(deps): update dependency husky to v3.0.2 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index a3604150..4a70a410 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "babel-plugin-dynamic-import-node": "2.3.0", "fork-ts-checker-webpack-plugin": "1.4.3", "gh-pages": "2.0.1", - "husky": "3.0.1", + "husky": "3.0.2", "jest": "24.8.0", "keyboardjs": "2.5.1", "lint-staged": "9.2.1", diff --git a/yarn.lock b/yarn.lock index d2de2751..62fbcdab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7373,10 +7373,10 @@ humanize-url@^1.0.0: normalize-url "^1.0.0" strip-url-auth "^1.0.0" -husky@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/husky/-/husky-3.0.1.tgz#06152c28e129622b05fa09c494209de8cf2dfb59" - integrity sha512-PXBv+iGKw23GHUlgELRlVX9932feFL407/wHFwtsGeArp0dDM4u+/QusSQwPKxmNgjpSL+ustbOdQ2jetgAZbA== +husky@3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/husky/-/husky-3.0.2.tgz#e78fd2ae16edca59fc88e56aeb8d70acdcc1c082" + integrity sha512-WXCtaME2x0o4PJlKY4ap8BzLA+D0zlvefqAvLCPriOOu+x0dpO5uc5tlB7CY6/0SE2EESmoZsj4jW5D09KrJoA== dependencies: chalk "^2.4.2" cosmiconfig "^5.2.1" From 26ce563cddc912b2829278bc9a9d0c8e587f1530 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 29 Jul 2019 23:00:54 +0000 Subject: [PATCH 14/39] chore(deps): update dependency @types/jest to v24.0.16 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 4a70a410..1203b800 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "@storybook/addon-notes": "5.1.9", "@storybook/addon-options": "5.1.9", "@storybook/react": "5.1.9", - "@types/jest": "24.0.15", + "@types/jest": "24.0.16", "@types/react": "16.8.23", "babel-core": "6.26.3", "babel-loader": "8.0.6", diff --git a/yarn.lock b/yarn.lock index 62fbcdab..3539f908 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2732,10 +2732,10 @@ resolved "https://registry.yarnpkg.com/@types/jest-diff/-/jest-diff-20.0.1.tgz#35cc15b9c4f30a18ef21852e255fdb02f6d59b89" integrity sha512-yALhelO3i0hqZwhjtcr6dYyaLoCHbAMshwtj6cGxTvHZAKXHsYGdff6E8EPw3xLKY0ELUTQ69Q1rQiJENnccMA== -"@types/jest@24.0.15": - version "24.0.15" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-24.0.15.tgz#6c42d5af7fe3b44ffff7cc65de7bf741e8fa427f" - integrity sha512-MU1HIvWUme74stAoc3mgAi+aMlgKOudgEvQDIm1v4RkrDudBh1T+NFp5sftpBAdXdx1J0PbdpJ+M2EsSOi1djA== +"@types/jest@24.0.16": + version "24.0.16" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-24.0.16.tgz#8d3e406ec0f0dc1688d6711af3062ff9bd428066" + integrity sha512-JrAiyV+PPGKZzw6uxbI761cHZ0G7QMOHXPhtSpcl08rZH6CswXaaejckn3goFKmF7M3nzEoJ0lwYCbqLMmjziQ== dependencies: "@types/jest-diff" "*" From 85fc3b270117710d9a61e5ffed530aa6546cacd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Tue, 30 Jul 2019 20:22:04 +0200 Subject: [PATCH 15/39] Fix tests after RTL scoped package update --- src/__tests__/useDefault.test.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/__tests__/useDefault.test.tsx b/src/__tests__/useDefault.test.tsx index 97de7d4b..47be52d4 100644 --- a/src/__tests__/useDefault.test.tsx +++ b/src/__tests__/useDefault.test.tsx @@ -1,8 +1,6 @@ -import { act, cleanup, renderHook } from 'react-hooks-testing-library'; +import { act, renderHook } from '@testing-library/react-hooks'; import useDefault from '../useDefault'; -afterEach(cleanup); - describe('useDefault', () => { test('should be defined', () => { expect(useDefault).toBeDefined(); From 259e517e37468c78f98745420fd0b65416b16f63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Tue, 30 Jul 2019 20:56:57 +0200 Subject: [PATCH 16/39] Update useDefault tests to new architecture --- src/__tests__/createMemo.test.ts | 60 +++---- src/__tests__/useBoolean.test.ts | 6 +- src/__tests__/useCounter.test.ts | 244 +++++++++++++------------ src/__tests__/useDefault.test.ts | 61 +++++++ src/__tests__/useDefault.test.tsx | 38 ---- src/__tests__/useGetSet.test.ts | 120 ++++++------- src/__tests__/useGetSetState.test.ts | 258 +++++++++++++-------------- src/__tests__/useNumber.test.ts | 6 +- src/__tests__/useToggle.test.ts | 156 ++++++++-------- 9 files changed, 479 insertions(+), 470 deletions(-) create mode 100644 src/__tests__/useDefault.test.ts delete mode 100644 src/__tests__/useDefault.test.tsx diff --git a/src/__tests__/createMemo.test.ts b/src/__tests__/createMemo.test.ts index caab5dbf..8056ed14 100644 --- a/src/__tests__/createMemo.test.ts +++ b/src/__tests__/createMemo.test.ts @@ -3,45 +3,43 @@ import createMemo from '../createMemo'; const getDouble = jest.fn((n: number): number => n * 2); -describe('createMemo hook factory', () => { - it('should init memoized hook', () => { - const useMemoGetDouble = createMemo(getDouble); +it('should init memoized hook', () => { + const useMemoGetDouble = createMemo(getDouble); - expect(useMemoGetDouble).toBeInstanceOf(Function); + expect(useMemoGetDouble).toBeInstanceOf(Function); +}); + +describe('created memo hook', () => { + let useMemoGetDouble; + + beforeEach(() => { + useMemoGetDouble = createMemo(getDouble); }); - describe('created memo hook', () => { - let useMemoGetDouble; + it.each([[1], [3], [5]])('should return same result as original function for argument %d', (val: number) => { + const { result } = renderHook(() => useMemoGetDouble(val)); + expect(result.current).toBe(getDouble(val)); + }); - beforeEach(() => { - useMemoGetDouble = createMemo(getDouble); - }); + it('should NOT call original function for same arguments', () => { + let initialValue = 5; + expect(getDouble).not.toHaveBeenCalled(); - it.each([[1], [3], [5]])('should return same result as original function for argument %d', (val: number) => { - const { result } = renderHook(() => useMemoGetDouble(val)); - expect(result.current).toBe(getDouble(val)); - }); + // it's called first time calculating for argument 5 + const { rerender } = renderHook(() => useMemoGetDouble(initialValue)); + expect(getDouble).toHaveBeenCalled(); - it('should NOT call original function for same arguments', () => { - let initialValue = 5; - expect(getDouble).not.toHaveBeenCalled(); + getDouble.mockClear(); - // it's called first time calculating for argument 5 - const { rerender } = renderHook(() => useMemoGetDouble(initialValue)); - expect(getDouble).toHaveBeenCalled(); + // it's NOT called second time calculating for argument 5 + rerender(); + expect(getDouble).not.toHaveBeenCalled(); - getDouble.mockClear(); + getDouble.mockClear(); - // it's NOT called second time calculating for argument 5 - rerender(); - expect(getDouble).not.toHaveBeenCalled(); - - getDouble.mockClear(); - - // it's called again calculating for different argument - initialValue = 7; - rerender(); - expect(getDouble).toHaveBeenCalled(); - }); + // it's called again calculating for different argument + initialValue = 7; + rerender(); + expect(getDouble).toHaveBeenCalled(); }); }); diff --git a/src/__tests__/useBoolean.test.ts b/src/__tests__/useBoolean.test.ts index 4dcafae0..8f633e9e 100644 --- a/src/__tests__/useBoolean.test.ts +++ b/src/__tests__/useBoolean.test.ts @@ -1,8 +1,6 @@ import useBoolean from '../useBoolean'; import useToggle from '../useToggle'; -describe('useBoolean hook', () => { - it('should be an alias for useToggle ', () => { - expect(useBoolean).toBe(useToggle); - }); +it('should be an alias for useToggle ', () => { + expect(useBoolean).toBe(useToggle); }); diff --git a/src/__tests__/useCounter.test.ts b/src/__tests__/useCounter.test.ts index 4a37b267..f5a13eeb 100644 --- a/src/__tests__/useCounter.test.ts +++ b/src/__tests__/useCounter.test.ts @@ -1,130 +1,128 @@ import { act, renderHook } from '@testing-library/react-hooks'; import useCounter from '../useCounter'; -describe('useCounter hook', () => { - const setUp = (initialValue?: number) => renderHook(() => useCounter(initialValue)); +const setUp = (initialValue?: number) => renderHook(() => useCounter(initialValue)); - it('should init counter', () => { - const { result } = setUp(5); +it('should init counter', () => { + const { result } = setUp(5); - expect(result.current[0]).toBe(5); - expect(result.current[1]).toEqual({ - inc: expect.any(Function), - dec: expect.any(Function), - get: expect.any(Function), - set: expect.any(Function), - reset: expect.any(Function), - }); + expect(result.current[0]).toBe(5); + expect(result.current[1]).toEqual({ + inc: expect.any(Function), + dec: expect.any(Function), + get: expect.any(Function), + set: expect.any(Function), + reset: expect.any(Function), }); - - it('should init counter to 0 if not initial value received', () => { - const { result } = setUp(); - - expect(result.current[0]).toBe(0); - }); - - it('should init counter to negative number', () => { - const { result } = setUp(-2); - - expect(result.current[0]).toBe(-2); - }); - - it('should get current counter', () => { - const { result } = setUp(5); - const { get } = result.current[1]; - - expect(get()).toBe(5); - }); - - it('should increment by 1 if not value received', () => { - const { result } = setUp(5); - const { get, inc } = result.current[1]; - - act(() => inc()); - - expect(result.current[0]).toBe(6); - expect(get()).toBe(6); - }); - - it('should increment by value received', () => { - const { result } = setUp(5); - const { get, inc } = result.current[1]; - - act(() => inc(9)); - - expect(result.current[0]).toBe(14); - expect(get()).toBe(14); - }); - - it('should decrement by 1 if not value received', () => { - const { result } = setUp(5); - const { get, dec } = result.current[1]; - - act(() => dec()); - - expect(result.current[0]).toBe(4); - expect(get()).toBe(4); - }); - - it('should decrement by value received', () => { - const { result } = setUp(5); - const { get, dec } = result.current[1]; - - act(() => dec(9)); - - expect(result.current[0]).toBe(-4); - expect(get()).toBe(-4); - }); - - it('should set to value received', () => { - const { result } = setUp(5); - const { get, set } = result.current[1]; - - act(() => set(17)); - - expect(result.current[0]).toBe(17); - expect(get()).toBe(17); - }); - - it('should reset to original value', () => { - const { result } = setUp(5); - const { get, set, reset } = result.current[1]; - - // set different value than initial one... - act(() => set(17)); - expect(result.current[0]).toBe(17); - - // ... and reset it to initial one - act(() => reset()); - expect(result.current[0]).toBe(5); - expect(get()).toBe(5); - }); - - it('should reset and set new original value', () => { - const { result } = setUp(5); - const { get, set, reset } = result.current[1]; - - // set different value than initial one... - act(() => set(17)); - expect(result.current[0]).toBe(17); - - // ... now reset and set it to different than initial one... - act(() => reset(8)); - expect(result.current[0]).toBe(8); - - // ... and set different value than initial one again... - act(() => set(32)); - expect(result.current[0]).toBe(32); - - // ... and reset it to new initial value - act(() => reset()); - expect(result.current[0]).toBe(8); - expect(get()).toBe(8); - }); - - it.todo('should throw an error if initial value is other than a number'); - it.todo('should throw an error if increment value is other than a number'); - it.todo('should throw an error if increment value is a negative number'); - it.todo('should throw an error if decrement value is other than a number'); - it.todo('should throw an error if decrement value is a negative number'); }); + +it('should init counter to 0 if not initial value received', () => { + const { result } = setUp(); + + expect(result.current[0]).toBe(0); +}); + +it('should init counter to negative number', () => { + const { result } = setUp(-2); + + expect(result.current[0]).toBe(-2); +}); + +it('should get current counter', () => { + const { result } = setUp(5); + const { get } = result.current[1]; + + expect(get()).toBe(5); +}); + +it('should increment by 1 if not value received', () => { + const { result } = setUp(5); + const { get, inc } = result.current[1]; + + act(() => inc()); + + expect(result.current[0]).toBe(6); + expect(get()).toBe(6); +}); + +it('should increment by value received', () => { + const { result } = setUp(5); + const { get, inc } = result.current[1]; + + act(() => inc(9)); + + expect(result.current[0]).toBe(14); + expect(get()).toBe(14); +}); + +it('should decrement by 1 if not value received', () => { + const { result } = setUp(5); + const { get, dec } = result.current[1]; + + act(() => dec()); + + expect(result.current[0]).toBe(4); + expect(get()).toBe(4); +}); + +it('should decrement by value received', () => { + const { result } = setUp(5); + const { get, dec } = result.current[1]; + + act(() => dec(9)); + + expect(result.current[0]).toBe(-4); + expect(get()).toBe(-4); +}); + +it('should set to value received', () => { + const { result } = setUp(5); + const { get, set } = result.current[1]; + + act(() => set(17)); + + expect(result.current[0]).toBe(17); + expect(get()).toBe(17); +}); + +it('should reset to original value', () => { + const { result } = setUp(5); + const { get, set, reset } = result.current[1]; + + // set different value than initial one... + act(() => set(17)); + expect(result.current[0]).toBe(17); + + // ... and reset it to initial one + act(() => reset()); + expect(result.current[0]).toBe(5); + expect(get()).toBe(5); +}); + +it('should reset and set new original value', () => { + const { result } = setUp(5); + const { get, set, reset } = result.current[1]; + + // set different value than initial one... + act(() => set(17)); + expect(result.current[0]).toBe(17); + + // ... now reset and set it to different than initial one... + act(() => reset(8)); + expect(result.current[0]).toBe(8); + + // ... and set different value than initial one again... + act(() => set(32)); + expect(result.current[0]).toBe(32); + + // ... and reset it to new initial value + act(() => reset()); + expect(result.current[0]).toBe(8); + expect(get()).toBe(8); +}); + +it.todo('should log an error if initial value is other than a number'); +it.todo('should log an error if increment value is other than a number'); +it.todo('should log an error if increment value is a negative number'); +it.todo('should log an error if decrement value is other than a number'); +it.todo('should log an error if decrement value is a negative number'); diff --git a/src/__tests__/useDefault.test.ts b/src/__tests__/useDefault.test.ts new file mode 100644 index 00000000..3e1fbd5a --- /dev/null +++ b/src/__tests__/useDefault.test.ts @@ -0,0 +1,61 @@ +import { act, renderHook } from '@testing-library/react-hooks'; +import useDefault from '../useDefault'; + +const setUp = (defaultValue: any, initialValue: any) => renderHook(() => useDefault(defaultValue, initialValue)); + +describe.each` + valueType | defaultValue | initialValue | anotherValue + ${'number'} | ${0} | ${5} | ${77} + ${'object'} | ${{}} | ${{ name: 'John Doe' }} | ${{ name: 'Solid Snake' }} + ${'boolean'} | ${false} | ${false} | ${true} + ${'string'} | ${''} | ${'foo'} | ${'bar'} +`('when value type is $valueType', ({ defaultValue, initialValue, anotherValue }) => { + it('should init state with initial value', () => { + const { result } = setUp(defaultValue, initialValue); + const [value, setValue] = result.current; + + expect(value).toBe(initialValue); + expect(setValue).toBeInstanceOf(Function); + }); + + it('should set state to another value', () => { + const { result } = setUp(defaultValue, initialValue); + const [, setValue] = result.current; + + act(() => setValue(anotherValue)); + + expect(result.current[0]).toBe(anotherValue); + }); + + it('should return default value if state set to null', () => { + const { result } = setUp(defaultValue, initialValue); + const [, setValue] = result.current; + + act(() => setValue(null)); + + expect(result.current[0]).toBe(defaultValue); + }); + + it('should return default value if state set to undefined', () => { + const { result } = setUp(defaultValue, initialValue); + const [, setValue] = result.current; + + act(() => setValue(undefined)); + + expect(result.current[0]).toBe(defaultValue); + }); + + it('should handle state properly after being set to nil and then to another value', () => { + const { result } = setUp(defaultValue, initialValue); + const [, setValue] = result.current; + + act(() => setValue(undefined)); + expect(result.current[0]).toBe(defaultValue); + + act(() => setValue(null)); + expect(result.current[0]).toBe(defaultValue); + + act(() => setValue(anotherValue)); + expect(result.current[0]).toBe(anotherValue); + }); +}); diff --git a/src/__tests__/useDefault.test.tsx b/src/__tests__/useDefault.test.tsx deleted file mode 100644 index 47be52d4..00000000 --- a/src/__tests__/useDefault.test.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { act, renderHook } from '@testing-library/react-hooks'; -import useDefault from '../useDefault'; - -describe('useDefault', () => { - test('should be defined', () => { - expect(useDefault).toBeDefined(); - }); - - const hook = renderHook(() => useDefault({ name: 'Marshall' }, { name: '' })); - - test('should return initial state on initial render', () => { - expect(hook.result.current[0].name).toBe(''); - }); - - test('should update state with correct value', () => { - hook.rerender(); - act(() => { - hook.result.current[1]({ name: 'Mathers' }); - }); - - expect(hook.result.current[0].name).toBe('Mathers'); - }); - - test('should return the default value when updated state is nil', () => { - hook.rerender(); - act(() => { - hook.result.current[1](null); - }); - - expect(hook.result.current[0].name).toBe('Marshall'); - - act(() => { - hook.result.current[1](undefined); - }); - - expect(hook.result.current[0].name).toBe('Marshall'); - }); -}); diff --git a/src/__tests__/useGetSet.test.ts b/src/__tests__/useGetSet.test.ts index 9e838f95..1f971e09 100644 --- a/src/__tests__/useGetSet.test.ts +++ b/src/__tests__/useGetSet.test.ts @@ -1,66 +1,64 @@ import { act, renderHook } from '@testing-library/react-hooks'; import useGetSet from '../useGetSet'; -describe('useGetSet hook', () => { - const setUp = (initialValue: any) => renderHook(() => useGetSet(initialValue)); +const setUp = (initialValue: any) => renderHook(() => useGetSet(initialValue)); - beforeEach(() => { - jest.useFakeTimers(); - }); - - it('should init getter and setter', () => { - const { result } = setUp('foo'); - const [get, set] = result.current; - - expect(get).toBeInstanceOf(Function); - expect(set).toBeInstanceOf(Function); - }); - - it('should get current value', () => { - const { result } = setUp('foo'); - const [get] = result.current; - - const currentValue = get(); - - expect(currentValue).toBe('foo'); - }); - - it('should set new value', () => { - const { result } = setUp('foo'); - const [get, set] = result.current; - - act(() => set('bar')); - - const currentValue = get(); - expect(currentValue).toBe('bar'); - }); - - /** - * This test implements the special demo in storybook that increments a number - * after 1 second on each click. - */ - it('should get and set expected values when used in nested functions', () => { - const onClick = jest.fn(() => { - setTimeout(() => { - set(get() + 1); - }, 1000); - }); - - const { result } = setUp(0); - const [get, set] = result.current; - - // simulate 3 clicks - onClick(); - onClick(); - onClick(); - - // fast-forward until all timers have been executed - act(() => { - jest.runAllTimers(); - }); - - const currentValue = get(); - expect(currentValue).toBe(3); - expect(onClick).toHaveBeenCalledTimes(3); - }); +beforeEach(() => { + jest.useFakeTimers(); +}); + +it('should init getter and setter', () => { + const { result } = setUp('foo'); + const [get, set] = result.current; + + expect(get).toBeInstanceOf(Function); + expect(set).toBeInstanceOf(Function); +}); + +it('should get current value', () => { + const { result } = setUp('foo'); + const [get] = result.current; + + const currentValue = get(); + + expect(currentValue).toBe('foo'); +}); + +it('should set new value', () => { + const { result } = setUp('foo'); + const [get, set] = result.current; + + act(() => set('bar')); + + const currentValue = get(); + expect(currentValue).toBe('bar'); +}); + +/** + * This test implements the special demo in storybook that increments a number + * after 1 second on each click. + */ +it('should get and set expected values when used in nested functions', () => { + const onClick = jest.fn(() => { + setTimeout(() => { + set(get() + 1); + }, 1000); + }); + + const { result } = setUp(0); + const [get, set] = result.current; + + // simulate 3 clicks + onClick(); + onClick(); + onClick(); + + // fast-forward until all timers have been executed + act(() => { + jest.runAllTimers(); + }); + + const currentValue = get(); + expect(currentValue).toBe(3); + expect(onClick).toHaveBeenCalledTimes(3); }); diff --git a/src/__tests__/useGetSetState.test.ts b/src/__tests__/useGetSetState.test.ts index 217b8225..e9eb6aef 100644 --- a/src/__tests__/useGetSetState.test.ts +++ b/src/__tests__/useGetSetState.test.ts @@ -4,135 +4,133 @@ import useGetSetState from '../useGetSetState'; const originalConsoleError = console.error; const mockConsoleError = jest.fn(); -describe('useGetSetState hook', () => { - const setUp = (initialState: any) => renderHook(() => useGetSetState(initialState)); +const setUp = (initialState: any) => renderHook(() => useGetSetState(initialState)); - beforeAll(() => { - console.error = mockConsoleError; - }); - - afterAll(() => { - console.error = originalConsoleError; - }); - - beforeEach(() => { - jest.useFakeTimers(); - }); - - it('should init getter and setter', () => { - const { result } = setUp({ foo: 'initialValue' }); - const [get, set] = result.current; - - expect(get).toBeInstanceOf(Function); - expect(set).toBeInstanceOf(Function); - }); - - it('should log an error if init with something different than an object', () => { - expect(mockConsoleError).not.toHaveBeenCalled(); - - setUp('not an object'); - - expect(mockConsoleError).toHaveBeenCalledTimes(1); - expect(mockConsoleError).toHaveBeenCalledWith('useGetSetState initial state must be an object.'); - }); - - it('should get current state', () => { - const { result } = setUp({ foo: 'a', bar: 'z' }); - const [get] = result.current; - - const currentState = get(); - - expect(currentState).toEqual({ foo: 'a', bar: 'z' }); - }); - - it('should set new state by applying patch with existing keys', () => { - const { result } = setUp({ foo: 'a', bar: 'z' }); - const [get, set] = result.current; - - act(() => set({ bar: 'y' })); - - const currentState = get(); - expect(currentState).toEqual({ foo: 'a', bar: 'y' }); - }); - - it('should set new state by applying patch with new keys', () => { - const { result } = setUp({ foo: 'a', bar: 'z' }); - const [get, set] = result.current; - - act(() => set({ qux: 'f' })); - - const currentState = get(); - expect(currentState).toEqual({ foo: 'a', bar: 'z', qux: 'f' }); - }); - - it('should set new state by applying patch with both new and old keys', () => { - const { result } = setUp({ foo: 'a', bar: 'z' }); - const [get, set] = result.current; - - act(() => set({ bar: 'y', qux: 'f' })); - - const currentState = get(); - expect(currentState).toEqual({ foo: 'a', bar: 'y', qux: 'f' }); - }); - - it('should NOT set new state if empty patch received', () => { - const { result } = setUp({ foo: 'a', bar: 'z' }); - const [get, set] = result.current; - - act(() => set({})); - - const currentState = get(); - expect(currentState).toEqual({ foo: 'a', bar: 'z' }); - }); - - it('should NOT set new state if no patch received', () => { - const { result } = setUp({ foo: 'a', bar: 'z' }); - const [get, set] = result.current; - - // @ts-ignore - act(() => set()); - - const currentState = get(); - expect(currentState).toEqual({ foo: 'a', bar: 'z' }); - }); - - it('should log an error if set with a patch different than an object', () => { - const { result } = setUp({ foo: 'a', bar: 'z' }); - const [, set] = result.current; - expect(mockConsoleError).not.toHaveBeenCalled(); - - // @ts-ignore - act(() => set('not an object')); - - expect(mockConsoleError).toHaveBeenCalledTimes(1); - expect(mockConsoleError).toHaveBeenCalledWith('useGetSetState setter patch must be an object.'); - }); - - /** - * This test is equivalent to demo one for `useGetSet` hook. - */ - it('should get and set expected state when used in nested functions', () => { - const onClick = jest.fn(() => { - setTimeout(() => { - set({ counter: get().counter + 1 }); - }, 1000); - }); - - const { result } = setUp({ counter: 0 }); - const [get, set] = result.current; - - // simulate 3 clicks - onClick(); - onClick(); - onClick(); - - // fast-forward until all timers have been executed - act(() => { - jest.runAllTimers(); - }); - - const currentState = get(); - expect(currentState).toEqual({ counter: 3 }); - expect(onClick).toHaveBeenCalledTimes(3); - }); +beforeAll(() => { + console.error = mockConsoleError; +}); + +afterAll(() => { + console.error = originalConsoleError; +}); + +beforeEach(() => { + jest.useFakeTimers(); +}); + +it('should init getter and setter', () => { + const { result } = setUp({ foo: 'initialValue' }); + const [get, set] = result.current; + + expect(get).toBeInstanceOf(Function); + expect(set).toBeInstanceOf(Function); +}); + +it('should log an error if init with something different than an object', () => { + expect(mockConsoleError).not.toHaveBeenCalled(); + + setUp('not an object'); + + expect(mockConsoleError).toHaveBeenCalledTimes(1); + expect(mockConsoleError).toHaveBeenCalledWith('useGetSetState initial state must be an object.'); +}); + +it('should get current state', () => { + const { result } = setUp({ foo: 'a', bar: 'z' }); + const [get] = result.current; + + const currentState = get(); + + expect(currentState).toEqual({ foo: 'a', bar: 'z' }); +}); + +it('should set new state by applying patch with existing keys', () => { + const { result } = setUp({ foo: 'a', bar: 'z' }); + const [get, set] = result.current; + + act(() => set({ bar: 'y' })); + + const currentState = get(); + expect(currentState).toEqual({ foo: 'a', bar: 'y' }); +}); + +it('should set new state by applying patch with new keys', () => { + const { result } = setUp({ foo: 'a', bar: 'z' }); + const [get, set] = result.current; + + act(() => set({ qux: 'f' })); + + const currentState = get(); + expect(currentState).toEqual({ foo: 'a', bar: 'z', qux: 'f' }); +}); + +it('should set new state by applying patch with both new and old keys', () => { + const { result } = setUp({ foo: 'a', bar: 'z' }); + const [get, set] = result.current; + + act(() => set({ bar: 'y', qux: 'f' })); + + const currentState = get(); + expect(currentState).toEqual({ foo: 'a', bar: 'y', qux: 'f' }); +}); + +it('should NOT set new state if empty patch received', () => { + const { result } = setUp({ foo: 'a', bar: 'z' }); + const [get, set] = result.current; + + act(() => set({})); + + const currentState = get(); + expect(currentState).toEqual({ foo: 'a', bar: 'z' }); +}); + +it('should NOT set new state if no patch received', () => { + const { result } = setUp({ foo: 'a', bar: 'z' }); + const [get, set] = result.current; + + // @ts-ignore + act(() => set()); + + const currentState = get(); + expect(currentState).toEqual({ foo: 'a', bar: 'z' }); +}); + +it('should log an error if set with a patch different than an object', () => { + const { result } = setUp({ foo: 'a', bar: 'z' }); + const [, set] = result.current; + expect(mockConsoleError).not.toHaveBeenCalled(); + + // @ts-ignore + act(() => set('not an object')); + + expect(mockConsoleError).toHaveBeenCalledTimes(1); + expect(mockConsoleError).toHaveBeenCalledWith('useGetSetState setter patch must be an object.'); +}); + +/** + * This test is equivalent to demo one for `useGetSet` hook. + */ +it('should get and set expected state when used in nested functions', () => { + const onClick = jest.fn(() => { + setTimeout(() => { + set({ counter: get().counter + 1 }); + }, 1000); + }); + + const { result } = setUp({ counter: 0 }); + const [get, set] = result.current; + + // simulate 3 clicks + onClick(); + onClick(); + onClick(); + + // fast-forward until all timers have been executed + act(() => { + jest.runAllTimers(); + }); + + const currentState = get(); + expect(currentState).toEqual({ counter: 3 }); + expect(onClick).toHaveBeenCalledTimes(3); }); diff --git a/src/__tests__/useNumber.test.ts b/src/__tests__/useNumber.test.ts index 5667c2a9..f646f931 100644 --- a/src/__tests__/useNumber.test.ts +++ b/src/__tests__/useNumber.test.ts @@ -1,8 +1,6 @@ import useNumber from '../useNumber'; import useCounter from '../useCounter'; -describe('useNumber hook', () => { - it('should be an alias for useCounter', () => { - expect(useNumber).toBe(useCounter); - }); +it('should be an alias for useCounter', () => { + expect(useNumber).toBe(useCounter); }); diff --git a/src/__tests__/useToggle.test.ts b/src/__tests__/useToggle.test.ts index 32870fb4..adafdc3b 100644 --- a/src/__tests__/useToggle.test.ts +++ b/src/__tests__/useToggle.test.ts @@ -1,85 +1,83 @@ import { act, renderHook } from '@testing-library/react-hooks'; import useToggle from '../useToggle'; -describe('useToggle hook', () => { - const setUp = (initialValue: boolean) => renderHook(() => useToggle(initialValue)); +const setUp = (initialValue: boolean) => renderHook(() => useToggle(initialValue)); - it('should init state to true', () => { - const { result } = setUp(true); +it('should init state to true', () => { + const { result } = setUp(true); - expect(result.current[0]).toBe(true); - expect(typeof result.current[1]).toBe('function'); - }); - - it('should init state to false', () => { - const { result } = setUp(false); - - expect(result.current[0]).toBe(false); - expect(result.current[1]).toBeInstanceOf(Function); - }); - - it('should set state to true', () => { - const { result } = setUp(false); - const [, toggle] = result.current; - - expect(result.current[0]).toBe(false); - - act(() => { - toggle(true); - }); - - expect(result.current[0]).toBe(true); - }); - - it('should set state to false', () => { - const { result } = setUp(true); - const [, toggle] = result.current; - - expect(result.current[0]).toBe(true); - - act(() => { - toggle(false); - }); - - expect(result.current[0]).toBe(false); - }); - - it('should toggle state from true', () => { - const { result } = setUp(true); - const [, toggle] = result.current; - - act(() => { - toggle(); - }); - - expect(result.current[0]).toBe(false); - }); - - it('should toggle state from false', () => { - const { result } = setUp(false); - const [, toggle] = result.current; - - act(() => { - toggle(); - }); - - expect(result.current[0]).toBe(true); - }); - - it('should ignore non-boolean parameters and toggle state', () => { - const { result } = setUp(true); - const [, toggle] = result.current; - - act(() => { - toggle('string'); - }); - - expect(result.current[0]).toBe(false); - - act(() => { - toggle({}); - }); - - expect(result.current[0]).toBe(true); - }); + expect(result.current[0]).toBe(true); + expect(typeof result.current[1]).toBe('function'); +}); + +it('should init state to false', () => { + const { result } = setUp(false); + + expect(result.current[0]).toBe(false); + expect(result.current[1]).toBeInstanceOf(Function); +}); + +it('should set state to true', () => { + const { result } = setUp(false); + const [, toggle] = result.current; + + expect(result.current[0]).toBe(false); + + act(() => { + toggle(true); + }); + + expect(result.current[0]).toBe(true); +}); + +it('should set state to false', () => { + const { result } = setUp(true); + const [, toggle] = result.current; + + expect(result.current[0]).toBe(true); + + act(() => { + toggle(false); + }); + + expect(result.current[0]).toBe(false); +}); + +it('should toggle state from true', () => { + const { result } = setUp(true); + const [, toggle] = result.current; + + act(() => { + toggle(); + }); + + expect(result.current[0]).toBe(false); +}); + +it('should toggle state from false', () => { + const { result } = setUp(false); + const [, toggle] = result.current; + + act(() => { + toggle(); + }); + + expect(result.current[0]).toBe(true); +}); + +it('should ignore non-boolean parameters and toggle state', () => { + const { result } = setUp(true); + const [, toggle] = result.current; + + act(() => { + toggle('string'); + }); + + expect(result.current[0]).toBe(false); + + act(() => { + toggle({}); + }); + + expect(result.current[0]).toBe(true); }); From 962f580d19c238628b35e94f39af0ad2deeabaec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Tue, 30 Jul 2019 22:49:43 +0200 Subject: [PATCH 17/39] Add createReducer tests --- src/__tests__/createMemo.test.ts | 4 +- src/__tests__/createReducer.test.ts | 112 ++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 src/__tests__/createReducer.test.ts diff --git a/src/__tests__/createMemo.test.ts b/src/__tests__/createMemo.test.ts index 8056ed14..2acb5d94 100644 --- a/src/__tests__/createMemo.test.ts +++ b/src/__tests__/createMemo.test.ts @@ -3,13 +3,13 @@ import createMemo from '../createMemo'; const getDouble = jest.fn((n: number): number => n * 2); -it('should init memoized hook', () => { +it('should init memo hook', () => { const useMemoGetDouble = createMemo(getDouble); expect(useMemoGetDouble).toBeInstanceOf(Function); }); -describe('created memo hook', () => { +describe('when using created memo hook', () => { let useMemoGetDouble; beforeEach(() => { diff --git a/src/__tests__/createReducer.test.ts b/src/__tests__/createReducer.test.ts new file mode 100644 index 00000000..42ae47c6 --- /dev/null +++ b/src/__tests__/createReducer.test.ts @@ -0,0 +1,112 @@ +import { act, renderHook } from '@testing-library/react-hooks'; +import createReducer from '../createReducer'; +import logger from 'redux-logger'; +import thunk from 'redux-thunk'; + +it('should init reducer hook function', () => { + const useSomeReducer = createReducer(); + expect(useSomeReducer).toBeInstanceOf(Function); +}); + +/** + * This test suite implements the special demo in storybook that creates a + * reducer with thunk and logger for using a simple counter + */ +describe('when using created reducer hook', () => { + const initialCount = 1; + let originalLog; + let originalGroup; + const mockLog = jest.fn(); + const mockGroup = jest.fn(); + + function reducer(state, action) { + switch (action.type) { + case 'increment': + return { count: state.count + 1 }; + case 'decrement': + return { count: state.count - 1 }; + case 'reset': + return { count: action.payload }; + default: + throw new Error(); + } + } + + // Action creator to increment count, wait a second and then reset + const addAndReset = () => { + return dispatch => { + dispatch({ type: 'increment' }); + + setTimeout(() => { + dispatch({ type: 'reset', payload: initialCount }); + }, 1000); + }; + }; + + const setUp = () => { + const useThunkReducer = createReducer(thunk, logger); + return renderHook(() => useThunkReducer(reducer, { count: initialCount })); + }; + + beforeAll(() => { + originalLog = console.log; + originalGroup = console.group; + console.log = mockLog; + console.group = mockGroup; + }); + + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + console.log = originalLog; + console.group = originalGroup; + }); + + it('should init state and dispatcher', () => { + const { result } = setUp(); + const [state, dispatch] = result.current; + + expect(state).toEqual({ count: 1 }); + expect(dispatch).toBeInstanceOf(Function); + }); + + it.each` + actionType | expectedCount | payload + ${'increment'} | ${2} | ${undefined} + ${'decrement'} | ${0} | ${undefined} + ${'reset'} | ${1} | ${1} + `('should handle "$actionType" action', ({ actionType, expectedCount, payload }) => { + const { result } = setUp(); + const [, dispatch] = result.current; + expect(mockLog).not.toHaveBeenCalled(); + + act(() => { + dispatch({ type: actionType, payload }); + }); + + expect(result.current[0]).toEqual({ count: expectedCount }); + expect(mockLog).toHaveBeenCalled(); + }); + + it('should handle async action with several middlewares', () => { + const { result } = setUp(); + const [, dispatch] = result.current; + expect(mockLog).not.toHaveBeenCalled(); + + act(() => { + dispatch(addAndReset()); + }); + + expect(result.current[0]).toEqual({ count: 2 }); + expect(mockLog).toHaveBeenCalled(); + + // fast-forward until all timers have been executed + act(() => { + jest.runAllTimers(); + }); + + expect(result.current[0]).toEqual({ count: 1 }); + }); +}); From 5350e8d9ee6d207f10d342bbf59d4d1cefb62990 Mon Sep 17 00:00:00 2001 From: xobotyi Date: Tue, 30 Jul 2019 23:58:21 +0300 Subject: [PATCH 18/39] useMountedState as a replacement for useRefMounted: - it has more obvious name; - returns a function that check mount state -> more handy ti use due to less to write; - has tests; --- README.md | 3 ++- docs/useMountedState.md | 25 ++++++++++++++++++++ src/__stories__/useMountedState.story.tsx | 17 ++++++++++++++ src/__tests__/useMountedState.test.tsx | 28 +++++++++++++++++++++++ src/index.ts | 2 ++ src/useMountedState.ts | 15 ++++++++++++ 6 files changed, 89 insertions(+), 1 deletion(-) create mode 100644 docs/useMountedState.md create mode 100644 src/__stories__/useMountedState.story.tsx create mode 100644 src/__tests__/useMountedState.test.tsx create mode 100644 src/useMountedState.ts diff --git a/README.md b/README.md index 73793bb9..e8e4f957 100644 --- a/README.md +++ b/README.md @@ -101,11 +101,12 @@ - [`useTitle`](./docs/useTitle.md) — sets title of the page. - [`usePermission`](./docs/usePermission.md) — query permission status for browser APIs.
-
+
- [**Lifecycles**](./docs/Lifecycles.md) - [`useEffectOnce`](./docs/useEffectOnce.md) — a modified [`useEffect`](https://reactjs.org/docs/hooks-reference.html#useeffect) hook that only runs once. - [`useEvent`](./docs/useEvent.md) — subscribe to events. - [`useLifecycles`](./docs/useLifecycles.md) — calls `mount` and `unmount` callbacks. + - [`useMountedState`](./docs/useMountedState.md) — tracks if component is mounted. - [`useRefMounted`](./docs/useRefMounted.md) — tracks if component is mounted. - [`usePromise`](./docs/usePromise.md) — resolves promise only while component is mounted. - [`useLogger`](./docs/useLogger.md) — logs in console as component goes through life-cycles. diff --git a/docs/useMountedState.md b/docs/useMountedState.md new file mode 100644 index 00000000..ae6ecace --- /dev/null +++ b/docs/useMountedState.md @@ -0,0 +1,25 @@ +# `useMountedState` + +Lifecycle hook providing ability to check component's mount state. +Gives a function that will return `true` if component mounted and `false` otherwise. + +## Usage + +```jsx +import * as React from 'react'; +import {useMountedState} from 'react-use'; + +const Demo = () => { + const isMounted = useMountedState(); + + React.useEffect(() => { + setTimeout(() => { + if (isMounted()) { + // ... + } else { + // ... + } + }, 1000); + }); +}; +``` diff --git a/src/__stories__/useMountedState.story.tsx b/src/__stories__/useMountedState.story.tsx new file mode 100644 index 00000000..a4bcac43 --- /dev/null +++ b/src/__stories__/useMountedState.story.tsx @@ -0,0 +1,17 @@ +import { storiesOf } from '@storybook/react'; +import * as React from 'react'; +import { useMountedState } from '..'; +import ShowDocs from './util/ShowDocs'; + +const Demo = () => { + const isMounted = useMountedState(); + const [, updateState] = React.useState(); + + requestAnimationFrame(updateState); + + return
This component is {isMounted() ? 'MOUNTED' : 'NOT MOUNTED'}
; +}; + +storiesOf('Lifecycle|useMountedState', module) + .add('Docs', () => ) + .add('Demo', () => ); diff --git a/src/__tests__/useMountedState.test.tsx b/src/__tests__/useMountedState.test.tsx new file mode 100644 index 00000000..56981a14 --- /dev/null +++ b/src/__tests__/useMountedState.test.tsx @@ -0,0 +1,28 @@ +import { renderHook } from 'react-hooks-testing-library'; +import useMountedState from '../useMountedState'; + +describe('useMountedState', () => { + it('should be defined', () => { + expect(useMountedState).toBeDefined(); + }); + + it('should return a function', () => { + const hook = renderHook(() => useMountedState(), { initialProps: false }); + + expect(typeof hook.result.current).toEqual('function'); + }); + + it('should return true if component is mounted', () => { + const hook = renderHook(() => useMountedState(), { initialProps: false }); + + expect(hook.result.current()).toBeTruthy(); + }); + + it('should return false if component is unmounted', () => { + const hook = renderHook(() => useMountedState(), { initialProps: false }); + + hook.unmount(); + + expect(hook.result.current()).toBeFalsy(); + }); +}); diff --git a/src/index.ts b/src/index.ts index 6578d00d..bb29fea1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -44,6 +44,7 @@ import useMedia from './useMedia'; import useMediaDevices from './useMediaDevices'; import useMotion from './useMotion'; import useMount from './useMount'; +import useMountedState from './useMountedState'; import useMouse from './useMouse'; import useMouseHovered from './useMouseHovered'; import useNetwork from './useNetwork'; @@ -124,6 +125,7 @@ export { useMediaDevices, useMotion, useMount, + useMountedState, useMouse, useMouseHovered, useNetwork, diff --git a/src/useMountedState.ts b/src/useMountedState.ts new file mode 100644 index 00000000..6236d415 --- /dev/null +++ b/src/useMountedState.ts @@ -0,0 +1,15 @@ +import { useEffect, useRef } from 'react'; + +export default function useMountedState(): () => boolean { + const mountedRef = useRef(false); + + useEffect(() => { + mountedRef.current = true; + + return () => { + mountedRef.current = false; + }; + }); + + return () => mountedRef.current; +} From 85f5d2615890830f7270bd887281f60e4a3aa095 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Wed, 31 Jul 2019 00:13:28 +0200 Subject: [PATCH 19/39] Add useList tests --- src/__tests__/useCounter.test.ts | 4 +- src/__tests__/useList.test.ts | 146 +++++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+), 2 deletions(-) create mode 100644 src/__tests__/useList.test.ts diff --git a/src/__tests__/useCounter.test.ts b/src/__tests__/useCounter.test.ts index f5a13eeb..41cc1e22 100644 --- a/src/__tests__/useCounter.test.ts +++ b/src/__tests__/useCounter.test.ts @@ -3,11 +3,11 @@ import useCounter from '../useCounter'; const setUp = (initialValue?: number) => renderHook(() => useCounter(initialValue)); -it('should init counter', () => { +it('should init counter and utils', () => { const { result } = setUp(5); expect(result.current[0]).toBe(5); - expect(result.current[1]).toEqual({ + expect(result.current[1]).toStrictEqual({ inc: expect.any(Function), dec: expect.any(Function), get: expect.any(Function), diff --git a/src/__tests__/useList.test.ts b/src/__tests__/useList.test.ts new file mode 100644 index 00000000..bbad31e3 --- /dev/null +++ b/src/__tests__/useList.test.ts @@ -0,0 +1,146 @@ +import { act, renderHook } from '@testing-library/react-hooks'; +import useList from '../useList'; + +const setUp = (initialList: any[]) => renderHook(() => useList(initialList)); + +it('should init list and utils', () => { + const { result } = setUp([1, 2, 3]); + const [list, utils] = result.current; + + expect(list).toEqual([1, 2, 3]); + expect(utils).toStrictEqual({ + set: expect.any(Function), + clear: expect.any(Function), + updateAt: expect.any(Function), + remove: expect.any(Function), + push: expect.any(Function), + filter: expect.any(Function), + sort: expect.any(Function), + }); +}); + +it('should set new list', () => { + const initList = [1, 2, 3]; + const { result } = setUp(initList); + const [, utils] = result.current; + + act(() => { + utils.set([4, 5, 6]); + }); + + expect(result.current[0]).toEqual([4, 5, 6]); + expect(result.current[0]).not.toBe(initList); // checking immutability +}); + +it('should clear current list', () => { + const initList = [1, 2, 3]; + const { result } = setUp(initList); + const [, utils] = result.current; + + act(() => { + utils.clear(); + }); + + expect(result.current[0]).toEqual([]); + expect(result.current[0]).not.toBe(initList); // checking immutability +}); + +it('should update element at specific position', () => { + const initList = [1, 2, 3]; + const { result } = setUp(initList); + const [, utils] = result.current; + + act(() => { + utils.updateAt(1, 'foo'); + }); + + expect(result.current[0]).toEqual([1, 'foo', 3]); + expect(result.current[0]).not.toBe(initList); // checking immutability +}); + +it('should remove element at specific position', () => { + const initList = [1, 2, 3]; + const { result } = setUp(initList); + const [, utils] = result.current; + + act(() => { + utils.remove(1); + }); + + expect(result.current[0]).toEqual([1, 3]); + expect(result.current[0]).not.toBe(initList); // checking immutability +}); + +it('should push new element at the end of the list', () => { + const initList = [1, 2, 3]; + const { result } = setUp(initList); + const [, utils] = result.current; + + act(() => { + utils.push(0); + }); + + expect(result.current[0]).toEqual([1, 2, 3, 0]); + expect(result.current[0]).not.toBe(initList); // checking immutability +}); + +it('should push duplicated element at the end of the list', () => { + const initList = [1, 2, 3]; + const { result } = setUp(initList); + const [, utils] = result.current; + + act(() => { + utils.push(2); + }); + + expect(result.current[0]).toEqual([1, 2, 3, 2]); + expect(result.current[0]).not.toBe(initList); // checking immutability +}); + +it('should filter current list by provided function', () => { + const initList = [1, -1, 2, -2, 3, -3]; + const { result } = setUp(initList); + const [, utils] = result.current; + + act(() => { + utils.filter(n => n < 0); + }); + + expect(result.current[0]).toEqual([-1, -2, -3]); + expect(result.current[0]).not.toBe(initList); // checking immutability +}); + +it('should sort current list by default order', () => { + const initList = ['March', 'Jan', 'Feb', 'Dec']; + const { result } = setUp(initList); + const [, utils] = result.current; + + act(() => { + utils.sort(); + }); + + expect(result.current[0]).toEqual(['Dec', 'Feb', 'Jan', 'March']); + expect(result.current[0]).not.toBe(initList); // checking immutability +}); + +it('should sort current list by provided function', () => { + const initList = ['March', 'Jan', 'Feb', 'Dec']; + const { result } = setUp(initList); + const [, utils] = result.current; + + act(() => { + utils.sort((a, b) => { + if (a < b) { + return 1; + } + if (a > b) { + return -1; + } + + return 0; + }); + }); + + expect(result.current[0]).toEqual(['March', 'Jan', 'Feb', 'Dec']); + expect(result.current[0]).not.toBe(initList); // checking immutability +}); From 93e76dc48fdff92a2a29bc8cd1602402bc692e29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Wed, 31 Jul 2019 00:15:19 +0200 Subject: [PATCH 20/39] Add useList additional test --- src/__tests__/useList.test.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/__tests__/useList.test.ts b/src/__tests__/useList.test.ts index bbad31e3..047e6e68 100644 --- a/src/__tests__/useList.test.ts +++ b/src/__tests__/useList.test.ts @@ -1,7 +1,7 @@ import { act, renderHook } from '@testing-library/react-hooks'; import useList from '../useList'; -const setUp = (initialList: any[]) => renderHook(() => useList(initialList)); +const setUp = (initialList?: any[]) => renderHook(() => useList(initialList)); it('should init list and utils', () => { const { result } = setUp([1, 2, 3]); @@ -19,6 +19,12 @@ it('should init list and utils', () => { }); }); +it('should init empty list if not initial list provided', () => { + const { result } = setUp(); + + expect(result.current[0]).toEqual([]); +}); + it('should set new list', () => { const initList = [1, 2, 3]; const { result } = setUp(initList); From f2b5c6c8ed99c3e371fe953d396eff4f74e09187 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 30 Jul 2019 22:40:42 +0000 Subject: [PATCH 21/39] chore(deps): update dependency semantic-release to v15.13.19 --- package.json | 2 +- yarn.lock | 15 ++++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 1203b800..0e3e6834 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,7 @@ "redux-thunk": "2.3.0", "rimraf": "2.6.3", "rxjs": "6.5.2", - "semantic-release": "15.13.18", + "semantic-release": "15.13.19", "ts-loader": "6.0.4", "ts-node": "8.3.0", "tslint": "5.18.0", diff --git a/yarn.lock b/yarn.lock index 3539f908..ac38b433 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9379,6 +9379,11 @@ marked@^0.6.0: resolved "https://registry.yarnpkg.com/marked/-/marked-0.6.1.tgz#a63addde477bca9613028de4b2bc3629e53a0562" integrity sha512-+H0L3ibcWhAZE02SKMqmvYsErLo4EAVJxu5h3bHBBDvvjeWXtl92rGUSBYHL2++5Y+RSNgl8dYOAXcYe7lp1fA== +marked@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/marked/-/marked-0.7.0.tgz#b64201f051d271b1edc10a04d1ae9b74bb8e5c0e" + integrity sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg== + material-colors@^1.2.1: version "1.2.6" resolved "https://registry.yarnpkg.com/material-colors/-/material-colors-1.2.6.tgz#6d1958871126992ceecc72f4bcc4d8f010865f46" @@ -12643,10 +12648,10 @@ select@^1.1.2: resolved "https://registry.yarnpkg.com/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d" integrity sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0= -semantic-release@15.13.18: - version "15.13.18" - resolved "https://registry.yarnpkg.com/semantic-release/-/semantic-release-15.13.18.tgz#72e284c6f7cb7817e1aaaa0a9d73600a9447d146" - integrity sha512-JtfdrhF1zRm91nJH/Rg3taftbWGwktJqqrJJdbmZGKYx63cfC4PoaS0jxRifGJUdmmgW/Kxz8f5bhtB+p1bu8A== +semantic-release@15.13.19: + version "15.13.19" + resolved "https://registry.yarnpkg.com/semantic-release/-/semantic-release-15.13.19.tgz#d1d05b3516fb8701d81f4e6b9be42bafffef13cb" + integrity sha512-6eqqAmzGaJWgP5R5IkWIQK9is+cWUp/A+pwzxf/YaG1hJv1eD25klUP7Y0fedsPOxxI8eLuDUVlEs7U8SOlK0Q== dependencies: "@semantic-release/commit-analyzer" "^6.1.0" "@semantic-release/error" "^2.2.0" @@ -12665,7 +12670,7 @@ semantic-release@15.13.18: hook-std "^2.0.0" hosted-git-info "^2.7.1" lodash "^4.17.4" - marked "^0.6.0" + marked "^0.7.0" marked-terminal "^3.2.0" p-locate "^4.0.0" p-reduce "^2.0.0" From 426d5becdaa73bdd27e89c09e79fa7c31f1b4e3a Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Wed, 31 Jul 2019 07:02:35 +0000 Subject: [PATCH 22/39] chore(deps): update dependency markdown-loader to v5.1.0 --- package.json | 2 +- yarn.lock | 15 +++++---------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/package.json b/package.json index 0e3e6834..70cc2bf8 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "jest": "24.8.0", "keyboardjs": "2.5.1", "lint-staged": "9.2.1", - "markdown-loader": "5.0.0", + "markdown-loader": "5.1.0", "prettier": "1.17.1", "react": "16.8.6", "react-dom": "16.8.6", diff --git a/yarn.lock b/yarn.lock index ac38b433..f08fe8d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9338,13 +9338,13 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" -markdown-loader@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/markdown-loader/-/markdown-loader-5.0.0.tgz#d6af0f2d5fe7713fb692cbd85b547ff0ef26baa8" - integrity sha512-CnRuBrTQNJ2VNlyfPJl+14QU6Sfscse4M6TpwuY0KDuCafMHv6vAcVYInphXFtdvtvjG5kMpF+PwN6CWke0M3A== +markdown-loader@5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/markdown-loader/-/markdown-loader-5.1.0.tgz#4efd5006b1514ca966141c661a47e542a9836e6e" + integrity sha512-xtQNozLEL+55ZSPTNwro8epZqf1h7HjAZd/69zNe8lbckDiGVHeLQm849bXzocln2pwRK2A/GrW/7MAmwjcFog== dependencies: loader-utils "^1.2.3" - marked "^0.6.0" + marked "^0.7.0" markdown-to-jsx@^6.9.1: version "6.9.2" @@ -9374,11 +9374,6 @@ marked-terminal@^3.2.0: node-emoji "^1.4.1" supports-hyperlinks "^1.0.1" -marked@^0.6.0: - version "0.6.1" - resolved "https://registry.yarnpkg.com/marked/-/marked-0.6.1.tgz#a63addde477bca9613028de4b2bc3629e53a0562" - integrity sha512-+H0L3ibcWhAZE02SKMqmvYsErLo4EAVJxu5h3bHBBDvvjeWXtl92rGUSBYHL2++5Y+RSNgl8dYOAXcYe7lp1fA== - marked@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/marked/-/marked-0.7.0.tgz#b64201f051d271b1edc10a04d1ae9b74bb8e5c0e" From 0b99c8c11e40f459b554b59f54f6f0506b2c8c42 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Wed, 31 Jul 2019 09:11:06 +0000 Subject: [PATCH 23/39] chore(deps): update storybook monorepo to v5.1.10 --- package.json | 10 +- yarn.lock | 252 +++++++++++++++++++++++++-------------------------- 2 files changed, 131 insertions(+), 131 deletions(-) diff --git a/package.json b/package.json index 70cc2bf8..37d9a4b4 100644 --- a/package.json +++ b/package.json @@ -67,11 +67,11 @@ "@semantic-release/changelog": "3.0.4", "@semantic-release/git": "7.0.16", "@semantic-release/npm": "5.1.13", - "@storybook/addon-actions": "5.1.9", - "@storybook/addon-knobs": "5.1.9", - "@storybook/addon-notes": "5.1.9", - "@storybook/addon-options": "5.1.9", - "@storybook/react": "5.1.9", + "@storybook/addon-actions": "5.1.10", + "@storybook/addon-knobs": "5.1.10", + "@storybook/addon-notes": "5.1.10", + "@storybook/addon-options": "5.1.10", + "@storybook/react": "5.1.10", "@types/jest": "24.0.16", "@types/react": "16.8.23", "babel-core": "6.26.3", diff --git a/yarn.lock b/yarn.lock index f08fe8d7..2455b027 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2193,16 +2193,16 @@ resolved "https://registry.yarnpkg.com/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.2.tgz#8013f2af54a2b7d735f71560ff360d3a8176a87b" integrity sha512-vTCdPp/T/Q3oSqwHmZ5Kpa9oI7iLtGl3RQaA/NyLHikvcrPxACkkKVr/XzkSPJWXHRhKGzVvb0urJsbMlRxi1Q== -"@storybook/addon-actions@5.1.9": - version "5.1.9" - resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-5.1.9.tgz#a515b62b109cb886ccd75ef2f5b12f8c27b43dd3" - integrity sha512-h/csHPotBESyEUYlML3yyF2jUlDChB+u3TUNC3Ztzh/x7HzLqy88SL0INSIdY0dCBGx4TK5Gh+rMI7z28Hfdyw== +"@storybook/addon-actions@5.1.10": + version "5.1.10" + resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-5.1.10.tgz#8ed4272a6afc68f4a30372da2eeff414f0fe6ecd" + integrity sha512-njl2AHBGi27NvisOB8LFnWH/3RcyJT/CW7tl1cvV2j5FH2oBjq5MsjxKyJIcKwC677k1Wr8G8fw/zSEHrPpmgA== dependencies: - "@storybook/addons" "5.1.9" - "@storybook/api" "5.1.9" - "@storybook/components" "5.1.9" - "@storybook/core-events" "5.1.9" - "@storybook/theming" "5.1.9" + "@storybook/addons" "5.1.10" + "@storybook/api" "5.1.10" + "@storybook/components" "5.1.10" + "@storybook/core-events" "5.1.10" + "@storybook/theming" "5.1.10" core-js "^3.0.1" fast-deep-equal "^2.0.1" global "^4.3.2" @@ -2213,16 +2213,16 @@ react-inspector "^3.0.2" uuid "^3.3.2" -"@storybook/addon-knobs@5.1.9": - version "5.1.9" - resolved "https://registry.yarnpkg.com/@storybook/addon-knobs/-/addon-knobs-5.1.9.tgz#74db07fd644b41e63274f8754fbfb18f43d4cf01" - integrity sha512-7/bICMYtR9CaTqfZX1kT2pBOTLZo3HxeslyQKWWsWlNElV33Ym2d0PPL5eS36eFxG/ZOp6lQWIFhunNnlmP5xg== +"@storybook/addon-knobs@5.1.10": + version "5.1.10" + resolved "https://registry.yarnpkg.com/@storybook/addon-knobs/-/addon-knobs-5.1.10.tgz#f5d9f21090e28046169a0aa0418de59bd92c21fd" + integrity sha512-j5wXBIPGQxK+guFDAi8xNBdUnyQglhDplVoC9SswkSMarqtWq02TT+OLN2VSBgpvzHmhLUW3autjJGfmwP4ltQ== dependencies: - "@storybook/addons" "5.1.9" - "@storybook/client-api" "5.1.9" - "@storybook/components" "5.1.9" - "@storybook/core-events" "5.1.9" - "@storybook/theming" "5.1.9" + "@storybook/addons" "5.1.10" + "@storybook/client-api" "5.1.10" + "@storybook/components" "5.1.10" + "@storybook/core-events" "5.1.10" + "@storybook/theming" "5.1.10" copy-to-clipboard "^3.0.8" core-js "^3.0.1" escape-html "^1.0.3" @@ -2235,18 +2235,18 @@ react-lifecycles-compat "^3.0.4" react-select "^2.2.0" -"@storybook/addon-notes@5.1.9": - version "5.1.9" - resolved "https://registry.yarnpkg.com/@storybook/addon-notes/-/addon-notes-5.1.9.tgz#7a19373589b4280b9a10a107a0357e63d2d56f5c" - integrity sha512-UE+/RNysKRo2ZqCaqllwwV59x2S1laZjF4Netcm0nf9dKNcnqOoSHlTs0t4m8WpmfBQKAde4p+8fJGP2bCM+8A== +"@storybook/addon-notes@5.1.10": + version "5.1.10" + resolved "https://registry.yarnpkg.com/@storybook/addon-notes/-/addon-notes-5.1.10.tgz#92609d64401b962c5eaea0b5235474daa59a0d3f" + integrity sha512-S2I9W7n8xQKuJFxk3dF1RO1o648L+yX/JHR9j9rMHbre+5LuZ/Bev5WOIhHHthKllPqy1/Wxe9ECsGR7L4W2CQ== dependencies: - "@storybook/addons" "5.1.9" - "@storybook/api" "5.1.9" - "@storybook/client-logger" "5.1.9" - "@storybook/components" "5.1.9" - "@storybook/core-events" "5.1.9" - "@storybook/router" "5.1.9" - "@storybook/theming" "5.1.9" + "@storybook/addons" "5.1.10" + "@storybook/api" "5.1.10" + "@storybook/client-logger" "5.1.10" + "@storybook/components" "5.1.10" + "@storybook/core-events" "5.1.10" + "@storybook/router" "5.1.10" + "@storybook/theming" "5.1.10" core-js "^3.0.1" global "^4.3.2" markdown-to-jsx "^6.9.3" @@ -2254,37 +2254,37 @@ prop-types "^15.7.2" util-deprecate "^1.0.2" -"@storybook/addon-options@5.1.9": - version "5.1.9" - resolved "https://registry.yarnpkg.com/@storybook/addon-options/-/addon-options-5.1.9.tgz#dc326b94c52a7ee27bf948fb7ca63a6ea3c9736d" - integrity sha512-wQIUw5Uc/5iREtI9eh2wPMlNG0O7jxaTaxGS8Bs6zDzkwiLHcLJCrfk1lPXU6O8ExEzWHpaCmxth8GyfKj8Qtw== +"@storybook/addon-options@5.1.10": + version "5.1.10" + resolved "https://registry.yarnpkg.com/@storybook/addon-options/-/addon-options-5.1.10.tgz#ae6cf58744cee4ed161ffc537bc7e29a7c7337d0" + integrity sha512-DA6r84JYMJ5zACGdPQ7utpig8pUU1wywMMZt43k2IhxKBFcamw0Nmc80vvlTMM4mUu+mevH1ZiuzWxTZU7IcxA== dependencies: - "@storybook/addons" "5.1.9" + "@storybook/addons" "5.1.10" core-js "^3.0.1" util-deprecate "^1.0.2" -"@storybook/addons@5.1.9": - version "5.1.9" - resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-5.1.9.tgz#ecf218d08508b97ca5e6e0f1ed361081385bd3ff" - integrity sha512-1bavbcS/NiE65DwyKj8c0DmWmz9VekOinB+has2Pqt2bOffZoZwVnbmepcz9hH3GUyvp5fQBYbxTEmTDvF2lLA== +"@storybook/addons@5.1.10": + version "5.1.10" + resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-5.1.10.tgz#2d8d8ca20b6d9b4652744f5fc00ead483f705435" + integrity sha512-M9b2PCp9RZxDC6wL7vVt2SCKCGXrrEAOsdpMvU569yB1zoUPEiiqElVDwb91O2eAGPnmd2yjImp90kOpKUW0EA== dependencies: - "@storybook/api" "5.1.9" - "@storybook/channels" "5.1.9" - "@storybook/client-logger" "5.1.9" + "@storybook/api" "5.1.10" + "@storybook/channels" "5.1.10" + "@storybook/client-logger" "5.1.10" core-js "^3.0.1" global "^4.3.2" util-deprecate "^1.0.2" -"@storybook/api@5.1.9": - version "5.1.9" - resolved "https://registry.yarnpkg.com/@storybook/api/-/api-5.1.9.tgz#eec5b2f775392ce0803930104c6ce14fa4931e8b" - integrity sha512-d1HhpOkW+706/WJ9lP5nCqOrp/icvbm0o+6jFFOGJ35AW5O9D8vDBxzvgMEO45jjN4I+rtbcNHQCxshSbPvP9w== +"@storybook/api@5.1.10": + version "5.1.10" + resolved "https://registry.yarnpkg.com/@storybook/api/-/api-5.1.10.tgz#5eeb5d9a7c268e5c89bd40c9a80293a7c72343b8" + integrity sha512-YeZe/71zLMmgT95IMAEZOc9AwL6Y23mWvkZMwFbkokxS9+bU/qmVlQ0B9c3JBzO3OSs7sXaRqyP1o3QkQgVsiw== dependencies: - "@storybook/channels" "5.1.9" - "@storybook/client-logger" "5.1.9" - "@storybook/core-events" "5.1.9" - "@storybook/router" "5.1.9" - "@storybook/theming" "5.1.9" + "@storybook/channels" "5.1.10" + "@storybook/client-logger" "5.1.10" + "@storybook/core-events" "5.1.10" + "@storybook/router" "5.1.10" + "@storybook/theming" "5.1.10" core-js "^3.0.1" fast-deep-equal "^2.0.1" global "^4.3.2" @@ -2298,33 +2298,33 @@ telejson "^2.2.1" util-deprecate "^1.0.2" -"@storybook/channel-postmessage@5.1.9": - version "5.1.9" - resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-5.1.9.tgz#bd710ca74d7998a234c6b1f38009020d7c34bbc0" - integrity sha512-H71PsnDKW81eflOS48Lv9yK4O8AcoqXL6ohsWvLdrHWIBsH4zpjOIhdWHtmAaT3hyfMy+l49DQ+uCHLECEt55g== +"@storybook/channel-postmessage@5.1.10": + version "5.1.10" + resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-5.1.10.tgz#e0a58461d56ef20a87d8bc4df1067e7afc76950e" + integrity sha512-kQZIwltN2cWDXluhCfdModFDK1LHV9ZhNQ1b/uD9vn1c65rQ9u7r4lRajCfS0X1dmAWqz48cBcEurAubNgmswg== dependencies: - "@storybook/channels" "5.1.9" - "@storybook/client-logger" "5.1.9" + "@storybook/channels" "5.1.10" + "@storybook/client-logger" "5.1.10" core-js "^3.0.1" global "^4.3.2" telejson "^2.2.1" -"@storybook/channels@5.1.9": - version "5.1.9" - resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-5.1.9.tgz#003cfca0b9f1ba6cf47ce68304aedd71bdb55e74" - integrity sha512-R6i7859FsXgY9XFFErVe7gS37wGYpQEEWsO1LzUW7YptGuFTUa8yLgKkNkgfy7Zs61Xm+GiBq8PvS/CWxjotPw== +"@storybook/channels@5.1.10": + version "5.1.10" + resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-5.1.10.tgz#04fd35c05032c675f7816ea1ca873c1a0415c6d9" + integrity sha512-w7n/bV1BLu51KI1eLc75lN9H1ssBc3PZMXk88GkMiKyBVRzPlJA5ixnzH86qwYGReE0dhRpsgHXZ5XmoKaVmPA== dependencies: core-js "^3.0.1" -"@storybook/client-api@5.1.9": - version "5.1.9" - resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-5.1.9.tgz#b598efe4ab07bffaeb4cb9e30ed9c21add739df1" - integrity sha512-J5HDtOS7x5YRpF/CMiHdxywV5NIh1i/03Xh2RhG15lmPy87VStIGpLzhF71uCRPLEJinYelcjuXRNAJgRzUOlg== +"@storybook/client-api@5.1.10": + version "5.1.10" + resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-5.1.10.tgz#a10f028f2d33d044e5c3b3daea5d8375323e6a66" + integrity sha512-v2PqiNUhwDlVDLYL94f6LFjdYMToTpuwWh9aeqzt/4PAJUnIcA+2P8+qXiYdJTqQy/u7P72HFMlc9Ru4tl3QFg== dependencies: - "@storybook/addons" "5.1.9" - "@storybook/client-logger" "5.1.9" - "@storybook/core-events" "5.1.9" - "@storybook/router" "5.1.9" + "@storybook/addons" "5.1.10" + "@storybook/client-logger" "5.1.10" + "@storybook/core-events" "5.1.10" + "@storybook/router" "5.1.10" common-tags "^1.8.0" core-js "^3.0.1" eventemitter3 "^3.1.0" @@ -2334,20 +2334,20 @@ memoizerific "^1.11.3" qs "^6.6.0" -"@storybook/client-logger@5.1.9": - version "5.1.9" - resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-5.1.9.tgz#87e2f7578416269adeccd407584010bc353f14d3" - integrity sha512-1+Otcn0EFgWNviDPNCR5LtUViADlboz9fmpZc7UY7bgaY5FVNIUO01E4T43tO7fduiRZoEvdltwTuQRm260Vjw== +"@storybook/client-logger@5.1.10": + version "5.1.10" + resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-5.1.10.tgz#f83a8717924dd222e0a6df82ae74701f27e0bb35" + integrity sha512-vB1NoFWRTgcERwodhbgoDwI00eqU8++nXI7GhMS1CY8haZaSp3gyKfHRWyfH+M+YjQuGBRUcvIk4gK6OtSrDOw== dependencies: core-js "^3.0.1" -"@storybook/components@5.1.9": - version "5.1.9" - resolved "https://registry.yarnpkg.com/@storybook/components/-/components-5.1.9.tgz#2a5258780fff07172d103287759946dbb4b13e2d" - integrity sha512-F4xcRlifSAfqkuFWtCKRvQDahXyfWBWV2Wa+kYy4YGwEfm3kKtIHVlgdgARL22g9BdYpRFEOJ+42juOu5YvIeQ== +"@storybook/components@5.1.10": + version "5.1.10" + resolved "https://registry.yarnpkg.com/@storybook/components/-/components-5.1.10.tgz#4b6436f0b5bb2483fb231bee263d173a9ed7d241" + integrity sha512-QUQeeQp1xNWiL4VlxFAea0kqn2zvBfmfPlUddOFO9lBhT6pVy0xYPjXjbTVWjVcYzZpyUNWw5GplqrR5jhlaCA== dependencies: - "@storybook/client-logger" "5.1.9" - "@storybook/theming" "5.1.9" + "@storybook/client-logger" "5.1.10" + "@storybook/theming" "5.1.10" core-js "^3.0.1" global "^4.3.2" markdown-to-jsx "^6.9.1" @@ -2365,32 +2365,32 @@ recompose "^0.30.0" simplebar-react "^1.0.0-alpha.6" -"@storybook/core-events@5.1.9": - version "5.1.9" - resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-5.1.9.tgz#441a6297e2ccfa743e15d1db1f4ac445b91f40d8" - integrity sha512-jHe2uyoLj9i6fntHtOj5azfGdLOb75LF0e1xXE8U2SX7Zp3uwbLAcfJ+dPStdc/q+f/wBiip3tH1dIjaNuUiMw== +"@storybook/core-events@5.1.10": + version "5.1.10" + resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-5.1.10.tgz#5aed88c572036b6bd6dfff28976ee96e6e175d7a" + integrity sha512-Lvu/rNcgS+XCkQKSGdNpUSWjpFF9AOSHPXsvkwHbRwJYdMDn3FznlXfDUiubOWtsziXHB6vl3wkKDlH+ckb32Q== dependencies: core-js "^3.0.1" -"@storybook/core@5.1.9": - version "5.1.9" - resolved "https://registry.yarnpkg.com/@storybook/core/-/core-5.1.9.tgz#8b30507676531fd41ac333b7c71b1c0db6b8da35" - integrity sha512-P3aavCnl3Cl3WMXVERjQqnqV1Z8tN0tyOTqqiGb1fMxITSE8uZNvp33Dl0K3jr1PBl9trW+2t7eHH4h0sguLlQ== +"@storybook/core@5.1.10": + version "5.1.10" + resolved "https://registry.yarnpkg.com/@storybook/core/-/core-5.1.10.tgz#53d23d07716aa2721e1572d44a7f05967d7da39e" + integrity sha512-zkNjufOFrLpFpmr73F/gaJh0W0vWqXIo5zrKvQt1LqmMeCU/v8MstHi4XidlK43UpeogfaXl5tjNCQDO/bd0Dw== dependencies: "@babel/plugin-proposal-class-properties" "^7.3.3" "@babel/plugin-proposal-object-rest-spread" "^7.3.2" "@babel/plugin-syntax-dynamic-import" "^7.2.0" "@babel/plugin-transform-react-constant-elements" "^7.2.0" "@babel/preset-env" "^7.4.5" - "@storybook/addons" "5.1.9" - "@storybook/channel-postmessage" "5.1.9" - "@storybook/client-api" "5.1.9" - "@storybook/client-logger" "5.1.9" - "@storybook/core-events" "5.1.9" - "@storybook/node-logger" "5.1.9" - "@storybook/router" "5.1.9" - "@storybook/theming" "5.1.9" - "@storybook/ui" "5.1.9" + "@storybook/addons" "5.1.10" + "@storybook/channel-postmessage" "5.1.10" + "@storybook/client-api" "5.1.10" + "@storybook/client-logger" "5.1.10" + "@storybook/core-events" "5.1.10" + "@storybook/node-logger" "5.1.10" + "@storybook/router" "5.1.10" + "@storybook/theming" "5.1.10" + "@storybook/ui" "5.1.10" airbnb-js-shims "^1 || ^2" autoprefixer "^9.4.9" babel-plugin-add-react-displayname "^0.0.5" @@ -2444,10 +2444,10 @@ webpack-dev-middleware "^3.7.0" webpack-hot-middleware "^2.25.0" -"@storybook/node-logger@5.1.9": - version "5.1.9" - resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-5.1.9.tgz#4aacf0096811fde1639fc9d1d2d521f7220dd4be" - integrity sha512-rcSuI5n53hDMHW83gl5TR0Yn885/i2XY0AzX1DsbTeGOl3x5LhrCSZsZWetKGcx7zsO4n7o5mQszLuN1JlyE8A== +"@storybook/node-logger@5.1.10": + version "5.1.10" + resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-5.1.10.tgz#92c80b46177687cd8fda1f93a055c22711984154" + integrity sha512-Z4UKh7QBOboQhUF5S/dKOx3OWWCNZGwYu8HZa/O+P68+XnQDhuZCYwqWG49xFhZd0Jb0W9gdUL2mWJw5POG9PA== dependencies: chalk "^2.4.2" core-js "^3.0.1" @@ -2455,16 +2455,16 @@ pretty-hrtime "^1.0.3" regenerator-runtime "^0.12.1" -"@storybook/react@5.1.9": - version "5.1.9" - resolved "https://registry.yarnpkg.com/@storybook/react/-/react-5.1.9.tgz#4052f4b88e91d5a823bb9cbb61104c530fcfb1a1" - integrity sha512-Byykpsttf6p2jv3LvqFtntEYfbUZSNTts0TjcZHNsHoUGmT7/M1PyqTeB7JUcYUNwSgdACY8FbowCrwZwDJDWQ== +"@storybook/react@5.1.10": + version "5.1.10" + resolved "https://registry.yarnpkg.com/@storybook/react/-/react-5.1.10.tgz#a5cf2b7d086e121c969d34100fb03fcfdc74cbed" + integrity sha512-wWy9l83KgbP8P2A8AbkwExEAdA0iznb4jEnCGzP1hAv8Q5LmL3MLPb1dIZqhWrg+E2m3tZei+7A7qu2Q8/cLLw== dependencies: "@babel/plugin-transform-react-constant-elements" "^7.2.0" "@babel/preset-flow" "^7.0.0" "@babel/preset-react" "^7.0.0" - "@storybook/core" "5.1.9" - "@storybook/node-logger" "5.1.9" + "@storybook/core" "5.1.10" + "@storybook/node-logger" "5.1.10" "@svgr/webpack" "^4.0.3" babel-plugin-add-react-displayname "^0.0.5" babel-plugin-named-asset-import "^0.3.1" @@ -2481,10 +2481,10 @@ semver "^6.0.0" webpack "^4.33.0" -"@storybook/router@5.1.9": - version "5.1.9" - resolved "https://registry.yarnpkg.com/@storybook/router/-/router-5.1.9.tgz#8cd97bea4f2acf8ec5f6694d06fb0633dde33417" - integrity sha512-eAmeerE/OTIwCV7WBnb1BPINVN1GTSMsUXLNWpqSISuyWJ+NZAJlObFkvXoc57QSQlv0cvXlm1FMkmRt8ku1Hw== +"@storybook/router@5.1.10": + version "5.1.10" + resolved "https://registry.yarnpkg.com/@storybook/router/-/router-5.1.10.tgz#d3cffd3f1105eb665882f389746ccabbb98c3c16" + integrity sha512-BdG6/essPZFHCP2ewCG0gYFQfmuuTSHXAB5fd/rwxLSYj1IzNznC5OxkvnSaTr4rgoxxaW/z1hbN1NuA0ivlFA== dependencies: "@reach/router" "^1.2.1" core-js "^3.0.1" @@ -2492,14 +2492,14 @@ memoizerific "^1.11.3" qs "^6.6.0" -"@storybook/theming@5.1.9": - version "5.1.9" - resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-5.1.9.tgz#c425f5867fae0db79e01112853b1808332a5f1a2" - integrity sha512-4jIFJwTWVf9tsv27noLoFHlKC2Jl9DHV3q+rxGPU8bTNbufCu4oby82SboO5GAKuS3eu1cxL1YY9pYad9WxfHg== +"@storybook/theming@5.1.10": + version "5.1.10" + resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-5.1.10.tgz#f9bd519cdf9cccf730656e3f5fd56a339dd07c9f" + integrity sha512-5cN1lmdVUwAR8U3T49Lfb8JW5RBvxBSPGZpUmbLGz1zi0tWBJgYXoGtw4RbTBjV9kCQOXkHGH12AsdDxHh931w== dependencies: "@emotion/core" "^10.0.9" "@emotion/styled" "^10.0.7" - "@storybook/client-logger" "5.1.9" + "@storybook/client-logger" "5.1.10" common-tags "^1.8.0" core-js "^3.0.1" deep-object-diff "^1.1.0" @@ -2510,19 +2510,19 @@ prop-types "^15.7.2" resolve-from "^5.0.0" -"@storybook/ui@5.1.9": - version "5.1.9" - resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-5.1.9.tgz#406667469e6dbdf320086647d8d80776bb051a51" - integrity sha512-guzKv4VYM+06BzMXeO3QqlX0IwUHyeS6lwdPCL8Oy2V4Gi2IYHHiD6Hr1NgnBO18j9luxE38f4Ii7gEIzXMFbQ== +"@storybook/ui@5.1.10": + version "5.1.10" + resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-5.1.10.tgz#4262b1b09efa43d125d694452ae879b89071edd1" + integrity sha512-ezkoVtzoKh93z2wzkqVIqyrIzTkj8tizgAkoPa7mUAbLCxu6LErHITODQoyEiJWI4Epy3yU9GYXFWwT71hdwsA== dependencies: - "@storybook/addons" "5.1.9" - "@storybook/api" "5.1.9" - "@storybook/channels" "5.1.9" - "@storybook/client-logger" "5.1.9" - "@storybook/components" "5.1.9" - "@storybook/core-events" "5.1.9" - "@storybook/router" "5.1.9" - "@storybook/theming" "5.1.9" + "@storybook/addons" "5.1.10" + "@storybook/api" "5.1.10" + "@storybook/channels" "5.1.10" + "@storybook/client-logger" "5.1.10" + "@storybook/components" "5.1.10" + "@storybook/core-events" "5.1.10" + "@storybook/router" "5.1.10" + "@storybook/theming" "5.1.10" copy-to-clipboard "^3.0.8" core-js "^3.0.1" core-js-pure "^3.0.1" From ea04eb09ae8a69ddf98e489bec8d69202dc25bda Mon Sep 17 00:00:00 2001 From: xobotyi Date: Wed, 31 Jul 2019 13:48:47 +0300 Subject: [PATCH 24/39] Much shortened and simplified the index file by directly reexporting hooks; --- src/index.ts | 236 +++++++++++++++++---------------------------------- 1 file changed, 78 insertions(+), 158 deletions(-) diff --git a/src/index.ts b/src/index.ts index 6578d00d..b9e84735 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,160 +1,80 @@ -import createMemo from './createMemo'; -import createReducer from './createReducer'; -import useAsync from './useAsync'; -import useAsyncFn from './useAsyncFn'; -import useAsyncRetry from './useAsyncRetry'; -import useAudio from './useAudio'; -import useBattery from './useBattery'; -import useBeforeUnload from './useBeforeUnload'; -import useBoolean from './useBoolean'; -import useClickAway from './useClickAway'; -import useCopyToClipboard from './useCopyToClipboard'; -import useCounter from './useCounter'; -import useCss from './useCss'; -import useDebounce from './useDebounce'; -import useDeepCompareEffect from './useDeepCompareEffect'; -import useDefault from './useDefault'; -import useDrop from './useDrop'; -import useDropArea from './useDropArea'; -import useEffectOnce from './useEffectOnce'; -import useEvent from './useEvent'; -import useFavicon from './useFavicon'; -import useFullscreen from './useFullscreen'; -import useGeolocation from './useGeolocation'; -import useGetSet from './useGetSet'; -import useGetSetState from './useGetSetState'; -import useHover from './useHover'; -import useHoverDirty from './useHoverDirty'; -import useIdle from './useIdle'; -import useInterval from './useInterval'; -import useIsomorphicLayoutEffect from './useIsomorphicLayoutEffect'; -import useKey from './useKey'; +export { default as createMemo } from './createMemo'; +export { default as createReducer } from './createReducer'; +export { default as useAsync } from './useAsync'; +export { default as useAsyncFn } from './useAsyncFn'; +export { default as useAsyncRetry } from './useAsyncRetry'; +export { default as useAudio } from './useAudio'; +export { default as useBattery } from './useBattery'; +export { default as useBeforeUnload } from './useBeforeUnload'; +export { default as useBoolean } from './useBoolean'; +export { default as useClickAway } from './useClickAway'; +export { default as useCopyToClipboard } from './useCopyToClipboard'; +export { default as useCounter } from './useCounter'; +export { default as useCss } from './useCss'; +export { default as useDebounce } from './useDebounce'; +export { default as useDeepCompareEffect } from './useDeepCompareEffect'; +export { default as useDefault } from './useDefault'; +export { default as useDrop } from './useDrop'; +export { default as useDropArea } from './useDropArea'; +export { default as useEffectOnce } from './useEffectOnce'; +export { default as useEvent } from './useEvent'; +export { default as useFavicon } from './useFavicon'; +export { default as useFullscreen } from './useFullscreen'; +export { default as useGeolocation } from './useGeolocation'; +export { default as useGetSet } from './useGetSet'; +export { default as useGetSetState } from './useGetSetState'; +export { default as useHover } from './useHover'; +export { default as useHoverDirty } from './useHoverDirty'; +export { default as useIdle } from './useIdle'; +export { default as useInterval } from './useInterval'; +export { default as useIsomorphicLayoutEffect } from './useIsomorphicLayoutEffect'; +export { default as useKey } from './useKey'; // not exported because of peer dependency -// import useKeyboardJs from './useKeyboardJs'; -import useKeyPress from './useKeyPress'; -import useKeyPressEvent from './useKeyPressEvent'; -import useLifecycles from './useLifecycles'; -import useList from './useList'; -import useLocalStorage from './useLocalStorage'; -import useLocation from './useLocation'; -import useLockBodyScroll from './useLockBodyScroll'; -import useLogger from './useLogger'; -import useMap from './useMap'; -import useMedia from './useMedia'; -import useMediaDevices from './useMediaDevices'; -import useMotion from './useMotion'; -import useMount from './useMount'; -import useMouse from './useMouse'; -import useMouseHovered from './useMouseHovered'; -import useNetwork from './useNetwork'; -import useNumber from './useNumber'; -import useObservable from './useObservable'; -import useOrientation from './useOrientation'; -import usePageLeave from './usePageLeave'; -import usePermission from './usePermission'; -import usePrevious from './usePrevious'; -import usePromise from './usePromise'; -import useRaf from './useRaf'; -import useRefMounted from './useRefMounted'; -import useScroll from './useScroll'; -import useScrolling from './useScrolling'; -import useSessionStorage from './useSessionStorage'; -import useSetState from './useSetState'; -import useSize from './useSize'; -import useSpeech from './useSpeech'; +// export { default as useKeyboardJs } from './useKeyboardJs'; +export { default as useKeyPress } from './useKeyPress'; +export { default as useKeyPressEvent } from './useKeyPressEvent'; +export { default as useLifecycles } from './useLifecycles'; +export { default as useList } from './useList'; +export { default as useLocalStorage } from './useLocalStorage'; +export { default as useLocation } from './useLocation'; +export { default as useLockBodyScroll } from './useLockBodyScroll'; +export { default as useLogger } from './useLogger'; +export { default as useMap } from './useMap'; +export { default as useMedia } from './useMedia'; +export { default as useMediaDevices } from './useMediaDevices'; +export { default as useMotion } from './useMotion'; +export { default as useMount } from './useMount'; +export { default as useMouse } from './useMouse'; +export { default as useMouseHovered } from './useMouseHovered'; +export { default as useNetwork } from './useNetwork'; +export { default as useNumber } from './useNumber'; +export { default as useObservable } from './useObservable'; +export { default as useOrientation } from './useOrientation'; +export { default as usePageLeave } from './usePageLeave'; +export { default as usePermission } from './usePermission'; +export { default as usePrevious } from './usePrevious'; +export { default as usePromise } from './usePromise'; +export { default as useRaf } from './useRaf'; +export { default as useRefMounted } from './useRefMounted'; +export { default as useScroll } from './useScroll'; +export { default as useScrolling } from './useScrolling'; +export { default as useSessionStorage } from './useSessionStorage'; +export { default as useSetState } from './useSetState'; +export { default as useSize } from './useSize'; +export { default as useSpeech } from './useSpeech'; // not exported because of peer dependency -// import useSpring from './useSpring'; -import useStartTyping from './useStartTyping'; -import useThrottle from './useThrottle'; -import useThrottleFn from './useThrottleFn'; -import useTimeout from './useTimeout'; -import useTitle from './useTitle'; -import useToggle from './useToggle'; -import useTween from './useTween'; -import useUnmount from './useUnmount'; -import useUpdate from './useUpdate'; -import useUpdateEffect from './useUpdateEffect'; -import useVideo from './useVideo'; -import { useWait, Waiter } from './useWait'; -import useWindowScroll from './useWindowScroll'; -import useWindowSize from './useWindowSize'; - -export { - createMemo, - createReducer, - useAsync, - useAsyncFn, - useAsyncRetry, - useAudio, - useBattery, - useBeforeUnload, - useBoolean, - useClickAway, - useCopyToClipboard, - useCounter, - useCss, - useDebounce, - useDeepCompareEffect, - useDefault, - useDrop, - useDropArea, - useEffectOnce, - useEvent, - useFavicon, - useFullscreen, - useGeolocation, - useGetSet, - useGetSetState, - useHover, - useHoverDirty, - useIdle, - useInterval, - useIsomorphicLayoutEffect, - useKey, - useKeyPress, - useKeyPressEvent, - useLifecycles, - useList, - useLocalStorage, - useLocation, - useLockBodyScroll, - useLogger, - useMap, - useMedia, - useMediaDevices, - useMotion, - useMount, - useMouse, - useMouseHovered, - useNetwork, - useNumber, - useObservable, - useOrientation, - usePageLeave, - usePermission, - usePrevious, - usePromise, - useRaf, - useRefMounted, - useScroll, - useScrolling, - useSessionStorage, - useSetState, - useSize, - useSpeech, - useStartTyping, - useThrottle, - useThrottleFn, - useTimeout, - useTitle, - useToggle, - useTween, - useUnmount, - useUpdate, - useUpdateEffect, - useVideo, - useWait, - useWindowScroll, - useWindowSize, - Waiter, -}; +// export { default as useSpring } from './useSpring'; +export { default as useStartTyping } from './useStartTyping'; +export { default as useThrottle } from './useThrottle'; +export { default as useThrottleFn } from './useThrottleFn'; +export { default as useTimeout } from './useTimeout'; +export { default as useTitle } from './useTitle'; +export { default as useToggle } from './useToggle'; +export { default as useTween } from './useTween'; +export { default as useUnmount } from './useUnmount'; +export { default as useUpdate } from './useUpdate'; +export { default as useUpdateEffect } from './useUpdateEffect'; +export { default as useVideo } from './useVideo'; +export { useWait, Waiter } from './useWait'; +export { default as useWindowScroll } from './useWindowScroll'; +export { default as useWindowSize } from './useWindowSize'; From a53c8c5e0e91e26b4fe0a5e4e05f623b2c0ec672 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Wed, 31 Jul 2019 20:13:26 +0000 Subject: [PATCH 25/39] chore(deps): update dependency gh-pages to v2.1.0 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 37d9a4b4..164cb384 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "babel-loader": "8.0.6", "babel-plugin-dynamic-import-node": "2.3.0", "fork-ts-checker-webpack-plugin": "1.4.3", - "gh-pages": "2.0.1", + "gh-pages": "2.1.0", "husky": "3.0.2", "jest": "24.8.0", "keyboardjs": "2.5.1", diff --git a/yarn.lock b/yarn.lock index 2455b027..e3740881 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6871,10 +6871,10 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" -gh-pages@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/gh-pages/-/gh-pages-2.0.1.tgz#aefe47a43b8d9d2aa3130576b33fe95641e29a2f" - integrity sha512-uFlk3bukljeiWKQ2XvPfjcSi/ou7IfoDf2p+Fj672saLAr8bnOdFVqI/JSgrSgInKpCg5BksxEwGUl++dbg8Dg== +gh-pages@2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/gh-pages/-/gh-pages-2.1.0.tgz#dcf519825d77d3a3ee78763076f4158403fc88c4" + integrity sha512-QmV1fh/2W5GZkfoLsG4g6dRTWiNYuCetMQmm8CL6Us8JVnAufYtS0uJPD8NYogmNB4UZzdRG44uPAL+jcBzEwQ== dependencies: async "^2.6.1" commander "^2.18.0" From d143859fc951328d8ff79bc22c7cd2403eda0cb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Thu, 1 Aug 2019 23:39:23 +0200 Subject: [PATCH 26/39] Add useMap tests --- src/__tests__/useMap.test.ts | 115 +++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 src/__tests__/useMap.test.ts diff --git a/src/__tests__/useMap.test.ts b/src/__tests__/useMap.test.ts new file mode 100644 index 00000000..242df55f --- /dev/null +++ b/src/__tests__/useMap.test.ts @@ -0,0 +1,115 @@ +import { act, renderHook } from '@testing-library/react-hooks'; +import useMap from '../useMap'; + +const setUp = (initialMap?: object) => renderHook(() => useMap(initialMap)); + +it('should init map and utils', () => { + const { result } = setUp({ foo: 'bar', a: 1 }); + const [map, utils] = result.current; + + expect(map).toEqual({ foo: 'bar', a: 1 }); + expect(utils).toStrictEqual({ + get: expect.any(Function), + set: expect.any(Function), + remove: expect.any(Function), + reset: expect.any(Function), + }); +}); + +it('should init empty map if not initial object provided', () => { + const { result } = setUp(); + + expect(result.current[0]).toEqual({}); +}); + +it('should get corresponding value for existing provided key', () => { + const { result } = setUp({ foo: 'bar', a: 1 }); + const [, utils] = result.current; + + let value; + act(() => { + // @ts-ignore + value = utils.get('a'); + }); + + expect(value).toBe(1); +}); + +it('should get undefined for non-existing provided key', () => { + const { result } = setUp({ foo: 'bar', a: 1 }); + const [, utils] = result.current; + + let value; + act(() => { + // @ts-ignore + value = utils.get('nonExisting'); + }); + + expect(value).toBeUndefined(); +}); + +it('should set new key-value pair', () => { + const { result } = setUp({ foo: 'bar', a: 1 }); + const [, utils] = result.current; + + act(() => { + // @ts-ignore + utils.set('newKey', 99); + }); + + expect(result.current[0]).toEqual({ foo: 'bar', a: 1, newKey: 99 }); +}); + +it('should override current value if setting existing key', () => { + const { result } = setUp({ foo: 'bar', a: 1 }); + const [, utils] = result.current; + + act(() => { + // @ts-ignore + utils.set('foo', 99); + }); + + expect(result.current[0]).toEqual({ foo: 99, a: 1 }); +}); + +it('should remove corresponding key-value pair for existing provided key', () => { + const { result } = setUp({ foo: 'bar', a: 1 }); + const [, utils] = result.current; + + act(() => { + // @ts-ignore + utils.remove('foo'); + }); + + expect(result.current[0]).toEqual({ a: 1 }); +}); + +it('should do nothing if removing non-existing provided key', () => { + const { result } = setUp({ foo: 'bar', a: 1 }); + const [, utils] = result.current; + + act(() => { + // @ts-ignore + utils.remove('nonExisting'); + }); + + expect(result.current[0]).toEqual({ foo: 'bar', a: 1 }); +}); + +it('should reset map to initial object provided', () => { + const { result } = setUp({ foo: 'bar', a: 1 }); + const [, utils] = result.current; + + act(() => { + // @ts-ignore + utils.set('z', 99); + }); + + expect(result.current[0]).toEqual({ foo: 'bar', a: 1, z: 99 }); + + act(() => { + utils.reset(); + }); + + expect(result.current[0]).toEqual({ foo: 'bar', a: 1 }); +}); From ebfba06f129cfd268c37f58c5a1234ba38aae5bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Fri, 2 Aug 2019 01:32:24 +0200 Subject: [PATCH 27/39] Update useObservable tests --- src/__tests__/useObservable-layout.spec.ts | 21 ----- src/__tests__/useObservable.test.tsx | 104 ++++++++++++++------- 2 files changed, 69 insertions(+), 56 deletions(-) delete mode 100644 src/__tests__/useObservable-layout.spec.ts diff --git a/src/__tests__/useObservable-layout.spec.ts b/src/__tests__/useObservable-layout.spec.ts deleted file mode 100644 index 3701401f..00000000 --- a/src/__tests__/useObservable-layout.spec.ts +++ /dev/null @@ -1,21 +0,0 @@ -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; -import { Subject } from 'rxjs'; -import { useObservable } from '..'; -import useIsomorphicLayoutEffect from '../useIsomorphicLayoutEffect'; - -jest.mock('../useIsomorphicLayoutEffect'); - -test('uses layout effect (to subscribe synchronously)', async () => { - const subject = new Subject(); - const container = document.createElement('div'); - - const Demo = ({ obs }) => { - const value = useObservable(obs); - return React.createElement(React.Fragment, {}, value); - }; - - expect(useIsomorphicLayoutEffect).toHaveBeenCalledTimes(0); - ReactDOM.render(React.createElement(Demo, { obs: subject }), container); - expect(useIsomorphicLayoutEffect).toHaveBeenCalledTimes(1); -}); diff --git a/src/__tests__/useObservable.test.tsx b/src/__tests__/useObservable.test.tsx index f62a6c36..696e9d68 100644 --- a/src/__tests__/useObservable.test.tsx +++ b/src/__tests__/useObservable.test.tsx @@ -1,36 +1,40 @@ import { act, renderHook } from '@testing-library/react-hooks'; import * as React from 'react'; -import * as ReactDOM from 'react-dom'; +import * as TestRenderer from 'react-test-renderer'; import { Subject } from 'rxjs'; import { useObservable } from '..'; +import * as useIsomorphicLayoutEffect from '../useIsomorphicLayoutEffect'; -let container: HTMLDivElement | null; - -beforeEach(() => { - container = document.createElement('div'); - document.body.appendChild(container); -}); - -afterEach(() => { - document.body.removeChild(container!); - container = null; -}); - -test('default initial value is undefined', () => { - const subject$ = new Subject(); - const { result } = renderHook(() => useObservable(subject$)); - - expect(result.current).toBe(undefined); -}); - -test('can specify initial value', () => { +it('should init to initial value provided', () => { const subject$ = new Subject(); const { result } = renderHook(() => useObservable(subject$, 123)); expect(result.current).toBe(123); }); -test('returns the latest value of observables', () => { +it('should init to undefined if not initial value provided', () => { + const subject$ = new Subject(); + const { result } = renderHook(() => useObservable(subject$)); + + expect(result.current).toBeUndefined(); +}); + +it('should use layout effect (to subscribe synchronously)', async () => { + const subject = new Subject(); + const spy = jest.spyOn(useIsomorphicLayoutEffect, 'default'); + + const Demo = ({ obs }) => { + const value = useObservable(obs); + return <>{value}; + }; + expect(spy).toHaveBeenCalledTimes(0); + + TestRenderer.create(); + + expect(spy).toHaveBeenCalledTimes(1); +}); + +it('should return latest value of observables', () => { const subject$ = new Subject(); const { result } = renderHook(() => useObservable(subject$, 123)); @@ -46,26 +50,25 @@ test('returns the latest value of observables', () => { expect(result.current).toBe(400); }); -test('subscribes to observable only once', async () => { +it('should subscribe to observable only once', () => { const subject = new Subject(); const spy = jest.spyOn(subject, 'subscribe'); - expect(spy).toHaveBeenCalledTimes(0); + expect(spy).not.toHaveBeenCalled(); const Demo = ({ obs }) => { const value = useObservable(obs); return <>{value}; }; - ReactDOM.render(, container); + TestRenderer.create(); expect(spy).toHaveBeenCalledTimes(1); - await new Promise(r => setTimeout(r, 1)); act(() => { subject.next('a'); }); - await new Promise(r => setTimeout(r, 1)); + act(() => { subject.next('b'); }); @@ -73,7 +76,7 @@ test('subscribes to observable only once', async () => { expect(spy).toHaveBeenCalledTimes(1); }); -test('re-renders component as obsevable changes', async () => { +it('should re-render component when observable changes', () => { const subject = new Subject(); let cnt = 0; @@ -83,25 +86,56 @@ test('re-renders component as obsevable changes', async () => { return <>{value}; }; - ReactDOM.render(, container); + const testRenderer = TestRenderer.create(); + const testInstance = testRenderer.root; - await new Promise(r => setTimeout(r, 1)); expect(cnt).toBe(1); - expect(container.innerHTML).toBe(''); + expect(testInstance.children).toEqual([]); act(() => { subject.next('a'); }); - await new Promise(r => setTimeout(r, 1)); expect(cnt).toBe(2); - expect(container.innerHTML).toBe('a'); + expect(testInstance.children).toEqual(['a']); act(() => { subject.next('b'); }); - await new Promise(r => setTimeout(r, 1)); expect(cnt).toBe(3); - expect(container.innerHTML).toBe('b'); + expect(testInstance.children).toEqual(['b']); +}); + +it('should unsubscribe from observable', () => { + const subject = new Subject(); + const unsubscribeMock = jest.fn(); + subject.subscribe = jest.fn().mockReturnValue({ + unsubscribe: unsubscribeMock, + }); + + expect(unsubscribeMock).not.toHaveBeenCalled(); + + const Demo = ({ obs }) => { + const value = useObservable(obs); + return <>{value}; + }; + + const testRenderer = TestRenderer.create(); + + expect(unsubscribeMock).not.toHaveBeenCalled(); + + act(() => { + subject.next('a'); + }); + + act(() => { + subject.next('b'); + }); + + expect(unsubscribeMock).not.toHaveBeenCalled(); + + testRenderer.unmount(); + + expect(unsubscribeMock).toHaveBeenCalledTimes(1); }); From 1264b2fba7003d7ac07d32cc6f38e5b6e4d101c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Fri, 2 Aug 2019 01:37:14 +0200 Subject: [PATCH 28/39] Update yarn.lock --- yarn.lock | 88 +++++++++++++++++++++---------------------------------- 1 file changed, 34 insertions(+), 54 deletions(-) diff --git a/yarn.lock b/yarn.lock index e3740881..e867b975 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1528,7 +1528,7 @@ dependencies: regenerator-runtime "^0.13.2" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5": version "7.4.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.5.tgz#582bb531f5f9dc67d2fcb682979894f75e253f12" integrity sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ== @@ -1986,14 +1986,6 @@ source-map "^0.6.1" write-file-atomic "2.4.1" -"@jest/types@^24.7.0": - version "24.7.0" - resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.7.0.tgz#c4ec8d1828cdf23234d9b4ee31f5482a3f04f48b" - integrity sha512-ipJUa2rFWiKoBqMKP63Myb6h9+iT3FHRTF2M8OR6irxWzItisa8i4dcSg14IbvmXUnBlHBlUQPYUHWyX3UPpYA== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/yargs" "^12.0.9" - "@jest/types@^24.8.0": version "24.8.0" resolved "https://registry.yarnpkg.com/@jest/types/-/types-24.8.0.tgz#f31e25948c58f0abd8c845ae26fcea1491dea7ad" @@ -2188,11 +2180,6 @@ into-stream "^4.0.0" lodash "^4.17.4" -"@sheerun/mutationobserver-shim@^0.3.2": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@sheerun/mutationobserver-shim/-/mutationobserver-shim-0.3.2.tgz#8013f2af54a2b7d735f71560ff360d3a8176a87b" - integrity sha512-vTCdPp/T/Q3oSqwHmZ5Kpa9oI7iLtGl3RQaA/NyLHikvcrPxACkkKVr/XzkSPJWXHRhKGzVvb0urJsbMlRxi1Q== - "@storybook/addon-actions@5.1.10": version "5.1.10" resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-5.1.10.tgz#8ed4272a6afc68f4a30372da2eeff414f0fe6ecd" @@ -2655,6 +2642,15 @@ "@svgr/plugin-svgo" "^4.0.3" loader-utils "^1.1.0" +"@testing-library/react-hooks@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@testing-library/react-hooks/-/react-hooks-1.1.0.tgz#14b6b5c7c3d0e2cb3e55e9cbb248b44321641c64" + integrity sha512-piE/ceQoNf134FFVXBABDbttBJ8eLPD4eg7zIciVJv92RyvoIsBHCvvG8Vd4IG5pyuWYrkLsZTO8ucZBwa4twA== + dependencies: + "@babel/runtime" "^7.4.2" + "@types/react" "^16.8.22" + "@types/react-test-renderer" "^16.8.2" + "@types/babel__core@^7.1.0": version "7.1.0" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.0.tgz#710f2487dda4dcfd010ca6abb2b4dc7394365c51" @@ -2764,6 +2760,13 @@ resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8" integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw== +"@types/react-test-renderer@^16.8.2": + version "16.8.3" + resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-16.8.3.tgz#b6ca14d5fe4c742fd6d68ef42d45e3b5c6dd470a" + integrity sha512-vEEYh6gFk3jkPHqRe7N5CxWA+yb92hCZxNyhq/1Wj3mClqVWLJYakJDMp3iFmntCgEq96m68N9Oad3wOHm+pdQ== + dependencies: + "@types/react" "*" + "@types/react-wait@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@types/react-wait/-/react-wait-0.3.0.tgz#6f7ef17571a17e72c7864ede8cf7d3aa525a005e" @@ -2779,6 +2782,14 @@ "@types/prop-types" "*" csstype "^2.2.0" +"@types/react@^16.8.22": + version "16.8.24" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.8.24.tgz#8d1ea1fcbfa214220da3d3c04e506f1077b0deac" + integrity sha512-VpFHUoD37YNY2+lr/+c7qL/tZsIU/bKuskUF3tmGUArbxIcQdb5j3zvo4cuuzu2A6UaVmVn7sJ4PgWYNFEBGzg== + dependencies: + "@types/prop-types" "*" + csstype "^2.2.0" + "@types/stack-utils@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e" @@ -5697,16 +5708,6 @@ dom-serializer@0: domelementtype "~1.1.1" entities "~1.1.1" -dom-testing-library@^3.19.0: - version "3.19.0" - resolved "https://registry.yarnpkg.com/dom-testing-library/-/dom-testing-library-3.19.0.tgz#652ade2cd93ae98beb52b2878eeb9420d7599832" - integrity sha512-gkGXP5GevcjC24Tk6Y6RwrZ7Nz0Ul4bchXV4yHLcnMidMp/EdBCvtHEgHTsZ2yZ4DhUpLowGbJv/1u1Z7bPvtw== - dependencies: - "@babel/runtime" "^7.3.4" - "@sheerun/mutationobserver-shim" "^0.3.2" - pretty-format "^24.5.0" - wait-for-expect "^1.1.0" - dom-walk@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" @@ -11307,16 +11308,6 @@ pretty-error@^2.1.1: renderkid "^2.0.1" utila "~0.4" -pretty-format@^24.5.0: - version "24.7.0" - resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.7.0.tgz#d23106bc2edcd776079c2daa5da02bcb12ed0c10" - integrity sha512-apen5cjf/U4dj7tHetpC7UEFCvtAgnNZnBDkfPv3fokzIqyOJckAG9OlAPC1BlFALnqT/lGB2tl9EJjlK6eCsA== - dependencies: - "@jest/types" "^24.7.0" - ansi-regex "^4.0.0" - ansi-styles "^3.2.0" - react-is "^16.8.4" - pretty-format@^24.8.0: version "24.8.0" resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.8.0.tgz#8dae7044f58db7cb8be245383b565a963e3c27f2" @@ -11759,14 +11750,6 @@ react-helmet-async@^1.0.2: react-fast-compare "2.0.4" shallowequal "1.1.0" -react-hooks-testing-library@0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/react-hooks-testing-library/-/react-hooks-testing-library-0.4.1.tgz#481b960d647d3cc7c8bbaf410014daa8ce0b1360" - integrity sha512-ogmlyW7ycZe3+HIk1Y+FUV5ExpZwLi/RlF1qGLLbTkUdaAsGgp9fSF+wFNWi2KNKBTWG0FkkqOrLkcwq0z3ebQ== - dependencies: - "@babel/runtime" "^7.4.2" - react-testing-library "^6.0.3" - react-hotkeys@2.0.0-pre4: version "2.0.0-pre4" resolved "https://registry.yarnpkg.com/react-hotkeys/-/react-hotkeys-2.0.0-pre4.tgz#a1c248a51bdba4282c36bf3204f80d58abc73333" @@ -11795,7 +11778,7 @@ react-is@^16.7.0, react-is@^16.8.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.4.tgz#90f336a68c3a29a096a3d648ab80e87ec61482a2" integrity sha512-PVadd+WaUDOAciICm/J1waJaSvgq+4rHE/K70j0PFqKhkTBsPv/82UGQJNXAngz1fOQLLxI6z1sEDmJDQhCTAA== -react-is@^16.8.4: +react-is@^16.8.4, react-is@^16.8.6: version "16.8.6" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16" integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA== @@ -11868,13 +11851,15 @@ react-syntax-highlighter@^8.0.1: prismjs "^1.8.4" refractor "^2.4.1" -react-testing-library@^6.0.3: - version "6.1.2" - resolved "https://registry.yarnpkg.com/react-testing-library/-/react-testing-library-6.1.2.tgz#f6bba6eeecedac736eb00b22b4c70bae04535a4f" - integrity sha512-z69lhRDGe7u/NOjDCeFRoe1cB5ckJ4656n0tj/Fdcr6OoBUu7q9DBw0ftR7v5i3GRpdSWelnvl+feZFOyXyxwg== +react-test-renderer@^16.8.6: + version "16.8.6" + resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.8.6.tgz#188d8029b8c39c786f998aa3efd3ffe7642d5ba1" + integrity sha512-H2srzU5IWYT6cZXof6AhUcx/wEyJddQ8l7cLM/F7gDXYyPr4oq+vCIxJYXVGhId1J706sqziAjuOEjyNkfgoEw== dependencies: - "@babel/runtime" "^7.4.2" - dom-testing-library "^3.19.0" + object-assign "^4.1.1" + prop-types "^15.6.2" + react-is "^16.8.6" + scheduler "^0.13.6" react-textarea-autosize@^7.1.0: version "7.1.0" @@ -14402,11 +14387,6 @@ w3c-hr-time@^1.0.1: dependencies: browser-process-hrtime "^0.1.2" -wait-for-expect@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/wait-for-expect/-/wait-for-expect-1.1.1.tgz#9cd10e07d52810af9e0aaf509872e38f3c3d81ae" - integrity sha512-vd9JOqqEcBbCDhARWhW85ecjaEcfBLuXgVBqatfS3iw6oU4kzAcs+sCNjF+TC9YHPImCW7ypsuQc+htscIAQCw== - walker@^1.0.7, walker@~1.0.5: version "1.0.7" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" From 8c820ce063b7a0218528dcbb76faa896101f6976 Mon Sep 17 00:00:00 2001 From: dabuside Date: Fri, 2 Aug 2019 12:19:20 +0800 Subject: [PATCH 29/39] fix(useKeyboardJs): fix argument type error --- docs/useKeyboardJs.md | 2 +- src/useKeyboardJs.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/useKeyboardJs.md b/docs/useKeyboardJs.md index 6dc09ef9..3df9d49d 100644 --- a/docs/useKeyboardJs.md +++ b/docs/useKeyboardJs.md @@ -37,5 +37,5 @@ yarn add keyboardjs ## Reference ```js -useKeyboardJs(combination: string): [isPressed: boolean, event?: KeyboardEvent] +useKeyboardJs(combination: string | string[]): [isPressed: boolean, event?: KeyboardEvent] ``` diff --git a/src/useKeyboardJs.ts b/src/useKeyboardJs.ts index c67d9f2f..3ae5cc46 100644 --- a/src/useKeyboardJs.ts +++ b/src/useKeyboardJs.ts @@ -1,7 +1,7 @@ import { useEffect, useState } from 'react'; import useMount from './useMount'; -const useKeyboardJs = (combination: string) => { +const useKeyboardJs = (combination: string | string[]) => { const [state, set] = useState<[boolean, null | KeyboardEvent]>([false, null]); const [keyboardJs, setKeyboardJs] = useState(null); From b7481f6cd5160cd0af5fe9ae9ad551b08137489b Mon Sep 17 00:00:00 2001 From: dabuside Date: Fri, 2 Aug 2019 13:45:47 +0800 Subject: [PATCH 30/39] fix(storybook): fix useKeyboardJs import path --- src/__stories__/useKeyboardJs.story.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__stories__/useKeyboardJs.story.tsx b/src/__stories__/useKeyboardJs.story.tsx index f9e83e9b..fd2f16cd 100644 --- a/src/__stories__/useKeyboardJs.story.tsx +++ b/src/__stories__/useKeyboardJs.story.tsx @@ -1,7 +1,7 @@ import { text, withKnobs } from '@storybook/addon-knobs'; import { storiesOf } from '@storybook/react'; import * as React from 'react'; -import { useKeyboardJs } from '..'; +import useKeyboardJs from '../useKeyboardJs'; import { CenterStory } from './util/CenterStory'; import ShowDocs from './util/ShowDocs'; From f058ff97391668d9b85ba6c867d1b10b6b4f3ccc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Fri, 2 Aug 2019 12:13:43 +0200 Subject: [PATCH 31/39] Move useObservable tests to react-hooks-testing-library --- src/__tests__/useObservable.test.ts | 108 ++++++++++++++++++++ src/__tests__/useObservable.test.tsx | 141 --------------------------- 2 files changed, 108 insertions(+), 141 deletions(-) create mode 100644 src/__tests__/useObservable.test.ts delete mode 100644 src/__tests__/useObservable.test.tsx diff --git a/src/__tests__/useObservable.test.ts b/src/__tests__/useObservable.test.ts new file mode 100644 index 00000000..7a4c2128 --- /dev/null +++ b/src/__tests__/useObservable.test.ts @@ -0,0 +1,108 @@ +import { act, renderHook } from '@testing-library/react-hooks'; +import { Subject } from 'rxjs'; +import useObservable, { Observable } from '../useObservable'; +import * as useIsomorphicLayoutEffect from '../useIsomorphicLayoutEffect'; + +const setUp = (observable: Observable, initialValue?: any) => + renderHook(() => useObservable(observable, initialValue)); + +it('should init to initial value provided', () => { + const subject$ = new Subject(); + const { result } = setUp(subject$, 123); + + expect(result.current).toBe(123); +}); + +it('should init to undefined if not initial value provided', () => { + const subject$ = new Subject(); + const { result } = setUp(subject$); + + expect(result.current).toBeUndefined(); +}); + +it('should return latest value of observables', () => { + const subject$ = new Subject(); + const { result } = setUp(subject$, 123); + + act(() => { + subject$.next(125); + }); + expect(result.current).toBe(125); + + act(() => { + subject$.next(300); + subject$.next(400); + }); + expect(result.current).toBe(400); +}); + +it('should use layout effect to subscribe synchronously', async () => { + const subject$ = new Subject(); + const spy = jest.spyOn(useIsomorphicLayoutEffect, 'default'); + + expect(spy).toHaveBeenCalledTimes(0); + + setUp(subject$, 123); + + expect(spy).toHaveBeenCalledTimes(1); +}); + +it('should subscribe to observable only once', () => { + const subject$ = new Subject(); + const spy = jest.spyOn(subject$, 'subscribe'); + expect(spy).not.toHaveBeenCalled(); + + setUp(subject$, 123); + + expect(spy).toHaveBeenCalledTimes(1); + + act(() => { + subject$.next('a'); + }); + + act(() => { + subject$.next('b'); + }); + + expect(spy).toHaveBeenCalledTimes(1); +}); + +it('should return updated value when observable changes', () => { + const subject$ = new Subject(); + const { result } = setUp(subject$); + expect(result.current).toBeUndefined(); + + act(() => { + subject$.next('foo'); + }); + expect(result.current).toBe('foo'); + + act(() => { + subject$.next('bar'); + }); + expect(result.current).toBe('bar'); +}); + +it('should unsubscribe from observable', () => { + const subject$ = new Subject(); + const unsubscribeMock = jest.fn(); + subject$.subscribe = jest.fn().mockReturnValue({ + unsubscribe: unsubscribeMock, + }); + + const { unmount } = setUp(subject$); + expect(unsubscribeMock).not.toHaveBeenCalled(); + + act(() => { + subject$.next('foo'); + }); + expect(unsubscribeMock).not.toHaveBeenCalled(); + + act(() => { + subject$.next('bar'); + }); + expect(unsubscribeMock).not.toHaveBeenCalled(); + + unmount(); + expect(unsubscribeMock).toHaveBeenCalledTimes(1); +}); diff --git a/src/__tests__/useObservable.test.tsx b/src/__tests__/useObservable.test.tsx deleted file mode 100644 index 696e9d68..00000000 --- a/src/__tests__/useObservable.test.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import { act, renderHook } from '@testing-library/react-hooks'; -import * as React from 'react'; -import * as TestRenderer from 'react-test-renderer'; -import { Subject } from 'rxjs'; -import { useObservable } from '..'; -import * as useIsomorphicLayoutEffect from '../useIsomorphicLayoutEffect'; - -it('should init to initial value provided', () => { - const subject$ = new Subject(); - const { result } = renderHook(() => useObservable(subject$, 123)); - - expect(result.current).toBe(123); -}); - -it('should init to undefined if not initial value provided', () => { - const subject$ = new Subject(); - const { result } = renderHook(() => useObservable(subject$)); - - expect(result.current).toBeUndefined(); -}); - -it('should use layout effect (to subscribe synchronously)', async () => { - const subject = new Subject(); - const spy = jest.spyOn(useIsomorphicLayoutEffect, 'default'); - - const Demo = ({ obs }) => { - const value = useObservable(obs); - return <>{value}; - }; - expect(spy).toHaveBeenCalledTimes(0); - - TestRenderer.create(); - - expect(spy).toHaveBeenCalledTimes(1); -}); - -it('should return latest value of observables', () => { - const subject$ = new Subject(); - const { result } = renderHook(() => useObservable(subject$, 123)); - - act(() => { - subject$.next(125); - }); - expect(result.current).toBe(125); - - act(() => { - subject$.next(300); - subject$.next(400); - }); - expect(result.current).toBe(400); -}); - -it('should subscribe to observable only once', () => { - const subject = new Subject(); - const spy = jest.spyOn(subject, 'subscribe'); - - expect(spy).not.toHaveBeenCalled(); - - const Demo = ({ obs }) => { - const value = useObservable(obs); - return <>{value}; - }; - - TestRenderer.create(); - - expect(spy).toHaveBeenCalledTimes(1); - - act(() => { - subject.next('a'); - }); - - act(() => { - subject.next('b'); - }); - - expect(spy).toHaveBeenCalledTimes(1); -}); - -it('should re-render component when observable changes', () => { - const subject = new Subject(); - - let cnt = 0; - const Demo = ({ obs }) => { - cnt++; - const value = useObservable(obs); - return <>{value}; - }; - - const testRenderer = TestRenderer.create(); - const testInstance = testRenderer.root; - - expect(cnt).toBe(1); - expect(testInstance.children).toEqual([]); - - act(() => { - subject.next('a'); - }); - - expect(cnt).toBe(2); - expect(testInstance.children).toEqual(['a']); - - act(() => { - subject.next('b'); - }); - - expect(cnt).toBe(3); - expect(testInstance.children).toEqual(['b']); -}); - -it('should unsubscribe from observable', () => { - const subject = new Subject(); - const unsubscribeMock = jest.fn(); - subject.subscribe = jest.fn().mockReturnValue({ - unsubscribe: unsubscribeMock, - }); - - expect(unsubscribeMock).not.toHaveBeenCalled(); - - const Demo = ({ obs }) => { - const value = useObservable(obs); - return <>{value}; - }; - - const testRenderer = TestRenderer.create(); - - expect(unsubscribeMock).not.toHaveBeenCalled(); - - act(() => { - subject.next('a'); - }); - - act(() => { - subject.next('b'); - }); - - expect(unsubscribeMock).not.toHaveBeenCalled(); - - testRenderer.unmount(); - - expect(unsubscribeMock).toHaveBeenCalledTimes(1); -}); From 4ae76748cdbe787e2d6512160d7ddebeca927791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Fri, 2 Aug 2019 14:38:06 +0200 Subject: [PATCH 32/39] Update usePrevious tests --- src/__tests__/usePrevious.test.tsx | 31 ++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/__tests__/usePrevious.test.tsx b/src/__tests__/usePrevious.test.tsx index 024b4f56..f6d63797 100644 --- a/src/__tests__/usePrevious.test.tsx +++ b/src/__tests__/usePrevious.test.tsx @@ -1,20 +1,23 @@ import { renderHook } from '@testing-library/react-hooks'; import usePrevious from '../usePrevious'; -describe('usePrevious', () => { - it('should be defined', () => { - expect(usePrevious).toBeDefined(); - }); +const setUp = () => renderHook(({ state }) => usePrevious(state), { initialProps: { state: 0 } }); - const hook = renderHook(props => usePrevious(props), { initialProps: 0 }); +it('should return undefined on initial render', () => { + const { result } = setUp(); - it('should return undefined on initial render', () => { - expect(hook.result.current).toBe(undefined); - }); - - it('should return previous state after update', () => { - hook.rerender(1); - hook.rerender(2); - expect(hook.result.current).toBe(1); - }); + expect(result.current).toBeUndefined(); +}); + +it('should always return previous state after each update', () => { + const { result, rerender } = setUp(); + + rerender({ state: 2 }); + expect(result.current).toBe(0); + + rerender({ state: 4 }); + expect(result.current).toBe(2); + + rerender({ state: 6 }); + expect(result.current).toBe(4); }); From 281c288c0417c5e9f54ff798636c1f34fb2edd25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Fri, 2 Aug 2019 14:41:20 +0200 Subject: [PATCH 33/39] Rename usePrevious tests file --- src/__tests__/{usePrevious.test.tsx => usePrevious.test.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/__tests__/{usePrevious.test.tsx => usePrevious.test.ts} (100%) diff --git a/src/__tests__/usePrevious.test.tsx b/src/__tests__/usePrevious.test.ts similarity index 100% rename from src/__tests__/usePrevious.test.tsx rename to src/__tests__/usePrevious.test.ts From 716a7d483da73e3ed4d1f9d19ded1c6d7bc3eda1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mario=20Beltra=CC=81n=20Alarco=CC=81n?= Date: Fri, 2 Aug 2019 16:01:08 +0200 Subject: [PATCH 34/39] Add useSetState tests --- src/__tests__/useSetState.test.ts | 42 +++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/__tests__/useSetState.test.ts diff --git a/src/__tests__/useSetState.test.ts b/src/__tests__/useSetState.test.ts new file mode 100644 index 00000000..684ba30b --- /dev/null +++ b/src/__tests__/useSetState.test.ts @@ -0,0 +1,42 @@ +import { act, renderHook } from '@testing-library/react-hooks'; +import useSetState from '../useSetState'; + +const setUp = (initialState?: object) => renderHook(() => useSetState(initialState)); + +it('should init state and setter', () => { + const { result } = setUp({ foo: 'bar' }); + const [state, setState] = result.current; + + expect(state).toEqual({ foo: 'bar' }); + expect(setState).toBeInstanceOf(Function); +}); + +it('should init empty state if not initial state provided', () => { + const { result } = setUp(); + + expect(result.current[0]).toEqual({}); +}); + +it('should merge changes into current state when providing object', () => { + const { result } = setUp({ foo: 'bar', count: 1 }); + const [state, setState] = result.current; + + act(() => { + // @ts-ignore + setState({ count: state.count + 1, someBool: true }); + }); + + expect(result.current[0]).toEqual({ foo: 'bar', count: 2, someBool: true }); +}); + +it('should merge changes into current state when providing function', () => { + const { result } = setUp({ foo: 'bar', count: 1 }); + const [, setState] = result.current; + + act(() => { + // @ts-ignore + setState(prevState => ({ count: prevState.count + 1, someBool: true })); + }); + + expect(result.current[0]).toEqual({ foo: 'bar', count: 2, someBool: true }); +}); From e0e8d57046a46698cadfeb5f2a2a31efa7625d1f Mon Sep 17 00:00:00 2001 From: streamich Date: Sat, 3 Aug 2019 00:45:17 +0200 Subject: [PATCH 35/39] =?UTF-8?q?chore:=20=F0=9F=A4=96=20fix=20gh-pages=20?= =?UTF-8?q?command=20after=20it=20was=20upgraded?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 52a4f38f..4d68a526 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "clean": "rimraf lib storybook-static esm", "storybook": "start-storybook -p 6008", "storybook:build": "build-storybook", - "storybook:upload": "gh-pages -d storybook-static", + "storybook:upload": "gh-pages -d storybook-static --git \"$(which git)\"", "storybook:clean": "rimraf storybook-static", "release": "semantic-release" }, From 192085a3a8f9f9c3c572f74940a1760298b73e1d Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 2 Aug 2019 22:48:31 +0000 Subject: [PATCH 36/39] chore(release): 10.3.1 [skip ci] ## [10.3.1](https://github.com/streamich/react-use/compare/v10.3.0...v10.3.1) (2019-08-02) ### Bug Fixes * **storybook:** fix useKeyboardJs import path ([b7481f6](https://github.com/streamich/react-use/commit/b7481f6)) * **useKeyboardJs:** fix argument type error ([8c820ce](https://github.com/streamich/react-use/commit/8c820ce)) * allow string list in useKeyboardJs hook ([aecbd0b](https://github.com/streamich/react-use/commit/aecbd0b)) --- CHANGELOG.md | 9 +++++++++ package.json | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f490b989..690eb7ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## [10.3.1](https://github.com/streamich/react-use/compare/v10.3.0...v10.3.1) (2019-08-02) + + +### Bug Fixes + +* **storybook:** fix useKeyboardJs import path ([b7481f6](https://github.com/streamich/react-use/commit/b7481f6)) +* **useKeyboardJs:** fix argument type error ([8c820ce](https://github.com/streamich/react-use/commit/8c820ce)) +* allow string list in useKeyboardJs hook ([aecbd0b](https://github.com/streamich/react-use/commit/aecbd0b)) + # [10.3.0](https://github.com/streamich/react-use/compare/v10.2.0...v10.3.0) (2019-07-26) diff --git a/package.json b/package.json index 4d68a526..f961629a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-use", - "version": "10.3.0", + "version": "10.3.1", "description": "Collection of React Hooks", "main": "lib/index.js", "module": "esm/index.js", From c2ebba828c68e89734a5af56087880a12f20dd3e Mon Sep 17 00:00:00 2001 From: Vadim Dalecky Date: Sat, 3 Aug 2019 00:58:22 +0200 Subject: [PATCH 37/39] docs: put useMountedState and useRefMouted on one line in README --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index e8e4f957..bea8dc23 100644 --- a/README.md +++ b/README.md @@ -106,8 +106,7 @@ - [`useEffectOnce`](./docs/useEffectOnce.md) — a modified [`useEffect`](https://reactjs.org/docs/hooks-reference.html#useeffect) hook that only runs once. - [`useEvent`](./docs/useEvent.md) — subscribe to events. - [`useLifecycles`](./docs/useLifecycles.md) — calls `mount` and `unmount` callbacks. - - [`useMountedState`](./docs/useMountedState.md) — tracks if component is mounted. - - [`useRefMounted`](./docs/useRefMounted.md) — tracks if component is mounted. + - [`useMountedState`](./docs/useMountedState.md) and [`useRefMounted`](./docs/useRefMounted.md) — track if component is mounted. - [`usePromise`](./docs/usePromise.md) — resolves promise only while component is mounted. - [`useLogger`](./docs/useLogger.md) — logs in console as component goes through life-cycles. - [`useMount`](./docs/useMount.md) — calls `mount` callbacks. From 9e82f1b1a4cb9cfa7b739487b8e734dcdb11829f Mon Sep 17 00:00:00 2001 From: streamich Date: Sat, 3 Aug 2019 01:22:17 +0200 Subject: [PATCH 38/39] =?UTF-8?q?chore:=20=F0=9F=A4=96=20fix=20master=20af?= =?UTF-8?q?ter=20late=20merge?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/__tests__/useMountedState.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/__tests__/useMountedState.test.tsx b/src/__tests__/useMountedState.test.tsx index 56981a14..ea3d8af9 100644 --- a/src/__tests__/useMountedState.test.tsx +++ b/src/__tests__/useMountedState.test.tsx @@ -1,4 +1,4 @@ -import { renderHook } from 'react-hooks-testing-library'; +import { renderHook } from '@testing-library/react-hooks'; import useMountedState from '../useMountedState'; describe('useMountedState', () => { From 8cb81c62a426d312afeca860e4943a5a879394fa Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Fri, 2 Aug 2019 23:26:11 +0000 Subject: [PATCH 39/39] chore(release): 10.4.0 [skip ci] # [10.4.0](https://github.com/streamich/react-use/compare/v10.3.1...v10.4.0) (2019-08-02) ### Features * add useMountedState hook ([9081b99](https://github.com/streamich/react-use/commit/9081b99)) --- CHANGELOG.md | 7 +++++++ package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 690eb7ca..15b1a3b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [10.4.0](https://github.com/streamich/react-use/compare/v10.3.1...v10.4.0) (2019-08-02) + + +### Features + +* add useMountedState hook ([9081b99](https://github.com/streamich/react-use/commit/9081b99)) + ## [10.3.1](https://github.com/streamich/react-use/compare/v10.3.0...v10.3.1) (2019-08-02) diff --git a/package.json b/package.json index f961629a..b0473dfd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-use", - "version": "10.3.1", + "version": "10.4.0", "description": "Collection of React Hooks", "main": "lib/index.js", "module": "esm/index.js",