diff --git a/src/helpers/test/ipc.ts b/src/helpers/test/ipc.ts new file mode 100644 index 00000000..6dbb39c1 --- /dev/null +++ b/src/helpers/test/ipc.ts @@ -0,0 +1,54 @@ +/* +MIT License + +Copyright (c) 2021-present, Elastic NV + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +import type { RendererProcessIpc } from 'electron-better-ipc'; + +export function getMockIpc(overrides?: Partial): RendererProcessIpc { + return { + addListener: jest.fn(), + answerMain: jest.fn(), + callMain: jest.fn(), + emit: jest.fn(), + eventNames: jest.fn(), + getMaxListeners: jest.fn(), + invoke: jest.fn(), + listenerCount: jest.fn(), + listeners: jest.fn(), + off: jest.fn(), + on: jest.fn(), + once: jest.fn(), + postMessage: jest.fn(), + prependListener: jest.fn(), + prependOnceListener: jest.fn(), + rawListeners: jest.fn(), + removeAllListeners: jest.fn(), + removeListener: jest.fn(), + send: jest.fn(), + sendSync: jest.fn(), + sendTo: jest.fn(), + sendToHost: jest.fn(), + setMaxListeners: jest.fn(), + ...overrides, + }; +} diff --git a/src/hooks/useRecordingContext.test.ts b/src/hooks/useRecordingContext.test.ts new file mode 100644 index 00000000..d9fe7af2 --- /dev/null +++ b/src/hooks/useRecordingContext.test.ts @@ -0,0 +1,124 @@ +/* +MIT License + +Copyright (c) 2021-present, Elastic NV + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +/** + * NOTE: these tests can be improved. Right now, there is no test code for the `togglePause` + * and `toggleRecording` callbacks in the case of recording being in progress. It isn't possible + * to control this state outside of the hook/context, which is by design, as we don't want + * consuming code to be capable of managing the actual recording state, as this will lead to + * lots of potential for unwanted/bad side effects. + * + * The recording state is only recording when we are awaiting a call to `ipc.callMain`, so tests + * for this functionality will need to simulate that, as well as subsequent actions by the user. + * + * It's not strictly necessary to test these effects in unit testing, as they're covered by our e2e suite, + * but a contribution that does add these unit tests will be nice for the sake of completeness. + */ + +import { act, renderHook } from '@testing-library/react-hooks'; +import { RendererProcessIpc } from 'electron-better-ipc'; +import { RecorderSteps } from '../../common/types'; +import { RecordingStatus, Setter } from '../common/types'; +import { getMockIpc } from '../helpers/test/ipc'; +import { useRecordingContext } from './useRecordingContext'; + +describe('useRecordingContext', () => { + let ipc: RendererProcessIpc; + let setResult: (data: undefined) => void; + let setSteps: Setter; + let callMainMock: jest.Mock; + let sendMock: jest.Mock; + + beforeEach(() => { + callMainMock = jest.fn(); + sendMock = jest.fn(); + ipc = getMockIpc({ + callMain: callMainMock, + send: sendMock, + }); + setResult = jest.fn(); + setSteps = jest.fn(); + }); + + it('should initialize the recording context with the correct initial state', () => { + const { result } = renderHook(() => useRecordingContext(ipc, '', 0, setResult, setSteps)); + const recordingContext = result.current; + + expect(recordingContext.recordingStatus).toEqual(RecordingStatus.NotRecording); + expect(recordingContext.isStartOverModalVisible).toEqual(false); + }); + + it('should toggle the recording status when toggleRecording is called', async () => { + const renderHookResponse = renderHook(() => + useRecordingContext(ipc, '', 0, setResult, setSteps) + ); + const recordingContext = renderHookResponse.result.current; + + await act(async () => recordingContext.toggleRecording()); + + renderHookResponse.rerender(); + + expect(callMainMock).toHaveBeenCalledWith('record-journey', { url: '' }); + }); + + it('sets start over modal visible if a step exists', async () => { + const renderHookResponse = renderHook(() => + useRecordingContext(ipc, '', 1, setResult, setSteps) + ); + const recordingContext = renderHookResponse.result.current; + + await act(async () => recordingContext.toggleRecording()); + + renderHookResponse.rerender(); + + expect(renderHookResponse.result.current.isStartOverModalVisible).toBe(true); + }); + + it('startOver resets steps and runs a new recording session', async () => { + const renderHookResponse = renderHook(() => + useRecordingContext(ipc, 'https://test.com', 1, setResult, setSteps) + ); + const recordingContext = renderHookResponse.result.current; + + await act(async () => recordingContext.startOver()); + + renderHookResponse.rerender(); + + expect(callMainMock).toHaveBeenCalledWith('record-journey', { url: 'https://test.com' }); + expect(setSteps).toHaveBeenCalledWith([]); + }); + + it('togglePause does nothing when not recording', async () => { + const renderHookResponse = renderHook(() => + useRecordingContext(ipc, 'https://test.com', 1, setResult, setSteps) + ); + const recordingContext = renderHookResponse.result.current; + + await act(async () => recordingContext.togglePause()); + + renderHookResponse.rerender(); + + expect(callMainMock).not.toHaveBeenCalled(); + }); +}); diff --git a/src/hooks/useRecordingContext.ts b/src/hooks/useRecordingContext.ts index 534ad123..792e3bcf 100644 --- a/src/hooks/useRecordingContext.ts +++ b/src/hooks/useRecordingContext.ts @@ -63,7 +63,7 @@ export function useRecordingContext( setSteps([]); if (recordingStatus === RecordingStatus.NotRecording) { setRecordingStatus(RecordingStatus.Recording); - // Depends on the results context, because when we overwrite + // Depends on the result's context, because when we overwrite // a previous journey we need to discard its result status setResult(undefined); await ipc.callMain('record-journey', { url });