From 61dcb1f1e23c2233a24b31f3ab986cbaa66b00b0 Mon Sep 17 00:00:00 2001 From: Jaime Liz Date: Wed, 15 Jan 2020 20:31:10 -0600 Subject: [PATCH 1/8] test: add use copy to clipboard test --- tests/useCopyToClipboard.test.ts | 41 ++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 tests/useCopyToClipboard.test.ts diff --git a/tests/useCopyToClipboard.test.ts b/tests/useCopyToClipboard.test.ts new file mode 100644 index 00000000..a292fe81 --- /dev/null +++ b/tests/useCopyToClipboard.test.ts @@ -0,0 +1,41 @@ +import writeText from 'copy-to-clipboard'; +import { renderHook, act } from '@testing-library/react-hooks'; +import { useCopyToClipboard } from '../src'; + +jest.mock('copy-to-clipboard', () => jest.fn().mockImplementation((value: any) => typeof value === 'string')); + +describe('useCopyToClipboard', () => { + let hook; + + beforeEach(() => { + hook = renderHook(() => useCopyToClipboard()); + }); + + it('should be defined ', () => { + expect(useCopyToClipboard).toBeDefined(); + }); + + it('should pass a given value to copy to clipboard and update the state value if no error', () => { + const testValue = 'test'; + let [state, copyToClipboard] = hook.result.current; + act(() => copyToClipboard(testValue)); + [state, copyToClipboard] = hook.result.current; + + expect(writeText).toBeCalled(); + expect(state.value).toBe(testValue); + expect(state.noUserInteraction).toBe(true); + expect(state.error).not.toBeDefined(); + }); + + it('should set the corresponding noUserInteraction value if returned from copy to clipboard', () => { + const testValue = {}; // invalid value + let [state, copyToClipboard] = hook.result.current; + act(() => copyToClipboard(testValue)); + [state, copyToClipboard] = hook.result.current; + + expect(writeText).toBeCalled(); + expect(state.value).toBe(testValue); + expect(state.noUserInteraction).toBe(false); + expect(state.error).toBeDefined(); + }); +}); From b13667b55c2747a9285503f4bbf6c1c9b458dada Mon Sep 17 00:00:00 2001 From: Jaime Liz Date: Thu, 16 Jan 2020 18:55:50 -0600 Subject: [PATCH 2/8] test: add more test cases --- tests/useCopyToClipboard.test.ts | 39 ++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/tests/useCopyToClipboard.test.ts b/tests/useCopyToClipboard.test.ts index a292fe81..84910b22 100644 --- a/tests/useCopyToClipboard.test.ts +++ b/tests/useCopyToClipboard.test.ts @@ -2,7 +2,16 @@ import writeText from 'copy-to-clipboard'; import { renderHook, act } from '@testing-library/react-hooks'; import { useCopyToClipboard } from '../src'; -jest.mock('copy-to-clipboard', () => jest.fn().mockImplementation((value: any) => typeof value === 'string')); +const valueToRaiseMockException = 'fake input causing exception in copy to clipboard'; + +jest.mock('copy-to-clipboard', () => + jest.fn().mockImplementation(input => { + if (input === valueToRaiseMockException) { + throw new Error(input); + } + return true; + }) +); describe('useCopyToClipboard', () => { let hook; @@ -15,7 +24,7 @@ describe('useCopyToClipboard', () => { expect(useCopyToClipboard).toBeDefined(); }); - it('should pass a given value to copy to clipboard and update the state value if no error', () => { + it('should pass a given value to copy to clipboard and set state', () => { const testValue = 'test'; let [state, copyToClipboard] = hook.result.current; act(() => copyToClipboard(testValue)); @@ -27,15 +36,35 @@ describe('useCopyToClipboard', () => { expect(state.error).not.toBeDefined(); }); - it('should set the corresponding noUserInteraction value if returned from copy to clipboard', () => { + it('should only call writeText if passed a valid input and set state', () => { const testValue = {}; // invalid value let [state, copyToClipboard] = hook.result.current; act(() => copyToClipboard(testValue)); [state, copyToClipboard] = hook.result.current; - expect(writeText).toBeCalled(); + expect(writeText).not.toBeCalled(); expect(state.value).toBe(testValue); - expect(state.noUserInteraction).toBe(false); + expect(state.noUserInteraction).toBe(true); expect(state.error).toBeDefined(); }); + + it('should catch exception thrown by copy-to-clipboard and set state', () => { + let [state, copyToClipboard] = hook.result.current; + act(() => copyToClipboard(valueToRaiseMockException)); + [state, copyToClipboard] = hook.result.current; + + expect(writeText).toBeCalledWith(valueToRaiseMockException); + expect(state.value).toBe(valueToRaiseMockException); + expect(state.noUserInteraction).not.toBeDefined(); + expect(state.error).toStrictEqual(new Error(valueToRaiseMockException)); + }); + it('should return initial state while unmounted', () => { + hook.unmount(); + const [state, copyToClipboard] = hook.result.current; + + act(() => copyToClipboard('value')); + expect(state.value).not.toBeDefined(); + expect(state.error).not.toBeDefined(); + expect(state.noUserInteraction).toBe(true); + }); }); From 86489d7f2bda3361b98c422b7a0993f28b6d8c79 Mon Sep 17 00:00:00 2001 From: Jaime Liz Date: Thu, 16 Jan 2020 18:58:14 -0600 Subject: [PATCH 3/8] refactor: update use copy to clipboard --- src/useCopyToClipboard.ts | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/useCopyToClipboard.ts b/src/useCopyToClipboard.ts index fc3b296a..60f55b11 100644 --- a/src/useCopyToClipboard.ts +++ b/src/useCopyToClipboard.ts @@ -18,31 +18,33 @@ const useCopyToClipboard = (): [CopyToClipboardState, (value: string) => void] = }); const copyToClipboard = useCallback(value => { + if (!isMounted()) { + return; + } + let noUserInteraction; try { - if (process.env.NODE_ENV === 'development') { - if (typeof value !== 'string') { - console.error(`Cannot copy typeof ${typeof value} to clipboard, must be a string`); - } - } - - const noUserInteraction = writeText(value); - - if (!isMounted()) { + if (typeof value !== 'string' && typeof value !== 'number') { + const error = new Error(`Cannot copy typeof ${typeof value} to clipboard, must be a string`); + if (process.env.NODE_ENV === 'development') console.error(error); + setState({ + value, + error, + noUserInteraction: true, + }); return; } + const normalizedValue = value.toString(); + noUserInteraction = writeText(normalizedValue); setState({ - value, + value: normalizedValue, error: undefined, noUserInteraction, }); } catch (error) { - if (!isMounted()) { - return; - } setState({ - value: undefined, + value, error, - noUserInteraction: true, + noUserInteraction, }); } }, []); From 6d5624eee2d66e439623bb77169431ecf652f548 Mon Sep 17 00:00:00 2001 From: Jaime Liz Date: Thu, 16 Jan 2020 19:05:35 -0600 Subject: [PATCH 4/8] test: update case wording --- tests/useCopyToClipboard.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/useCopyToClipboard.test.ts b/tests/useCopyToClipboard.test.ts index 84910b22..39d467f7 100644 --- a/tests/useCopyToClipboard.test.ts +++ b/tests/useCopyToClipboard.test.ts @@ -36,7 +36,7 @@ describe('useCopyToClipboard', () => { expect(state.error).not.toBeDefined(); }); - it('should only call writeText if passed a valid input and set state', () => { + it('should not call writeText if passed an invalid input and set state', () => { const testValue = {}; // invalid value let [state, copyToClipboard] = hook.result.current; act(() => copyToClipboard(testValue)); @@ -58,6 +58,7 @@ describe('useCopyToClipboard', () => { expect(state.noUserInteraction).not.toBeDefined(); expect(state.error).toStrictEqual(new Error(valueToRaiseMockException)); }); + it('should return initial state while unmounted', () => { hook.unmount(); const [state, copyToClipboard] = hook.result.current; From d219ffa1ecd2912b32cdaeace8f8e1e0a871f068 Mon Sep 17 00:00:00 2001 From: Jaime Liz Date: Thu, 16 Jan 2020 19:17:50 -0600 Subject: [PATCH 5/8] test: add dev logging test case and reset mocks --- tests/useCopyToClipboard.test.ts | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/useCopyToClipboard.test.ts b/tests/useCopyToClipboard.test.ts index 39d467f7..2ea2ead3 100644 --- a/tests/useCopyToClipboard.test.ts +++ b/tests/useCopyToClipboard.test.ts @@ -12,6 +12,7 @@ jest.mock('copy-to-clipboard', () => return true; }) ); +jest.spyOn(global.console, 'error').mockImplementation(() => {}); describe('useCopyToClipboard', () => { let hook; @@ -20,6 +21,10 @@ describe('useCopyToClipboard', () => { hook = renderHook(() => useCopyToClipboard()); }); + afterAll(() => { + jest.restoreAllMocks(); + }); + it('should be defined ', () => { expect(useCopyToClipboard).toBeDefined(); }); @@ -68,4 +73,22 @@ describe('useCopyToClipboard', () => { expect(state.error).not.toBeDefined(); expect(state.noUserInteraction).toBe(true); }); + + it('should console error if in dev environment', () => { + const ORIGINAL_NODE_ENV = process.env.NODE_ENV; + const testValue = {}; // invalid value + + process.env.NODE_ENV = 'development'; + let [state, copyToClipboard] = hook.result.current; + act(() => copyToClipboard(testValue)); + process.env.NODE_ENV = ORIGINAL_NODE_ENV; + + [state, copyToClipboard] = hook.result.current; + + expect(writeText).not.toBeCalled(); + expect(console.error).toBeCalled(); + expect(state.value).toBe(testValue); + expect(state.noUserInteraction).toBe(true); + expect(state.error).toBeDefined(); + }); }); From 100b86898f19b1b4d9cf0145580aa43fca36db36 Mon Sep 17 00:00:00 2001 From: Jaime Liz Date: Thu, 16 Jan 2020 19:37:31 -0600 Subject: [PATCH 6/8] refactor: refuse empty strings --- src/useCopyToClipboard.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/useCopyToClipboard.ts b/src/useCopyToClipboard.ts index 60f55b11..fb00c034 100644 --- a/src/useCopyToClipboard.ts +++ b/src/useCopyToClipboard.ts @@ -22,7 +22,9 @@ const useCopyToClipboard = (): [CopyToClipboardState, (value: string) => void] = return; } let noUserInteraction; + let normalizedValue; try { + // only strings and numbers casted to strings can be copied to clipboard if (typeof value !== 'string' && typeof value !== 'number') { const error = new Error(`Cannot copy typeof ${typeof value} to clipboard, must be a string`); if (process.env.NODE_ENV === 'development') console.error(error); @@ -33,7 +35,18 @@ const useCopyToClipboard = (): [CopyToClipboardState, (value: string) => void] = }); return; } - const normalizedValue = value.toString(); + // empty strings are also considered invalid + else if (value === '') { + const error = new Error(`Cannot copy empty string to clipboard.`); + if (process.env.NODE_ENV === 'development') console.error(error); + setState({ + value, + error, + noUserInteraction: true, + }); + return; + } + normalizedValue = value.toString(); noUserInteraction = writeText(normalizedValue); setState({ value: normalizedValue, @@ -42,7 +55,7 @@ const useCopyToClipboard = (): [CopyToClipboardState, (value: string) => void] = }); } catch (error) { setState({ - value, + value: normalizedValue, error, noUserInteraction, }); From aaefdf85356450b88f9719903a4431535f6e4ace Mon Sep 17 00:00:00 2001 From: Jaime Liz Date: Thu, 16 Jan 2020 19:41:43 -0600 Subject: [PATCH 7/8] test: update test to include empty string as invalid --- tests/useCopyToClipboard.test.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/useCopyToClipboard.test.ts b/tests/useCopyToClipboard.test.ts index 2ea2ead3..4f73f8e0 100644 --- a/tests/useCopyToClipboard.test.ts +++ b/tests/useCopyToClipboard.test.ts @@ -42,7 +42,7 @@ describe('useCopyToClipboard', () => { }); it('should not call writeText if passed an invalid input and set state', () => { - const testValue = {}; // invalid value + let testValue = {}; // invalid value let [state, copyToClipboard] = hook.result.current; act(() => copyToClipboard(testValue)); [state, copyToClipboard] = hook.result.current; @@ -51,6 +51,15 @@ describe('useCopyToClipboard', () => { expect(state.value).toBe(testValue); expect(state.noUserInteraction).toBe(true); expect(state.error).toBeDefined(); + + testValue = ''; // emtpy string is also invalid + act(() => copyToClipboard(testValue)); + [state, copyToClipboard] = hook.result.current; + console.log(state); + expect(writeText).not.toBeCalled(); + expect(state.value).toBe(testValue); + expect(state.noUserInteraction).toBe(true); + expect(state.error).toBeDefined(); }); it('should catch exception thrown by copy-to-clipboard and set state', () => { From f17c8a0f8e63bfddb8f13a094edbea1e3ee9680b Mon Sep 17 00:00:00 2001 From: Jaime Liz Date: Thu, 16 Jan 2020 19:43:35 -0600 Subject: [PATCH 8/8] =?UTF-8?q?fix:=20remove=20console=20log=20?= =?UTF-8?q?=F0=9F=A4=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/useCopyToClipboard.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/useCopyToClipboard.test.ts b/tests/useCopyToClipboard.test.ts index 4f73f8e0..fd8de8eb 100644 --- a/tests/useCopyToClipboard.test.ts +++ b/tests/useCopyToClipboard.test.ts @@ -55,7 +55,7 @@ describe('useCopyToClipboard', () => { testValue = ''; // emtpy string is also invalid act(() => copyToClipboard(testValue)); [state, copyToClipboard] = hook.result.current; - console.log(state); + expect(writeText).not.toBeCalled(); expect(state.value).toBe(testValue); expect(state.noUserInteraction).toBe(true);