From 026a19d658667d39be39f14b4a8a92a1321ef097 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Tue, 31 Jan 2023 14:45:51 -0500 Subject: [PATCH 1/2] Extract test render helper to exported component for use as a wrapper when rendering hooks. --- src/helpers/test/render.tsx | 65 ++++++++++++++++++++++++------------- 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/src/helpers/test/render.tsx b/src/helpers/test/render.tsx index 3553d5ea..22380e67 100644 --- a/src/helpers/test/render.tsx +++ b/src/helpers/test/render.tsx @@ -41,57 +41,78 @@ import { } from './defaults'; import { RenderContexts } from './RenderContexts'; -export function render( - component: React.ReactElement, - options?: { - renderOptions?: Omit; - contextOverrides?: { - communication?: Partial; - recording?: Partial; - steps?: Partial; - url?: Partial; - test?: Partial; - toast?: Partial; - }; - } -): RenderResult { +export function TestContextWrapper({ + component, + contextOverrides, +}: { + component: + | React.ReactNode + | React.ReactElement>; + contextOverrides?: { + communication?: Partial; + recording?: Partial; + steps?: Partial; + url?: Partial; + test?: Partial; + toast?: Partial; + }; +}) { const contexts = [ { defaults: getRecordingContextDefaults(), Context: RecordingContext, - overrides: options?.contextOverrides?.recording, + overrides: contextOverrides?.recording, }, { defaults: getUrlContextDefaults(), Context: UrlContext, - overrides: options?.contextOverrides?.url, + overrides: contextOverrides?.url, }, { defaults: getStepsContextDefaults(), Context: StepsContext, - overrides: options?.contextOverrides?.steps, + overrides: contextOverrides?.steps, }, { defaults: getCommunicationContextDefaults(), Context: CommunicationContext, - overrides: options?.contextOverrides?.communication, + overrides: contextOverrides?.communication, }, { defaults: getTestContextDefaults(), Context: TestContext, - overrides: options?.contextOverrides?.test, + overrides: contextOverrides?.test, }, { defaults: getToastContextDefaults(), Context: ToastContext, - overrides: options?.contextOverrides?.toast, + overrides: contextOverrides?.toast, }, ]; - return rtlRender( + return ( {component} - , + + ); +} + +export function render( + component: React.ReactElement, + options?: { + renderOptions?: Omit; + contextOverrides?: { + communication?: Partial; + recording?: Partial; + steps?: Partial; + url?: Partial; + test?: Partial; + toast?: Partial; + }; + } +): RenderResult { + return rtlRender( + , options?.renderOptions ); } From 7498367f5e60c361575c2bc19ce76a18453a9127 Mon Sep 17 00:00:00 2001 From: Justin Kambic Date: Tue, 31 Jan 2023 14:46:48 -0500 Subject: [PATCH 2/2] Add tests for `useSyntheticsTests`. --- src/hooks/useSyntheticsTest.test.tsx | 155 +++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 src/hooks/useSyntheticsTest.test.tsx diff --git a/src/hooks/useSyntheticsTest.test.tsx b/src/hooks/useSyntheticsTest.test.tsx new file mode 100644 index 00000000..a3777acf --- /dev/null +++ b/src/hooks/useSyntheticsTest.test.tsx @@ -0,0 +1,155 @@ +/* +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. +*/ + +/** + * These tests can be improved with the implementation for some tests to the + * `onTest` callback this hook returns. It is complicated to test using mocks + * because it relies on waiting for events in the backend to complete, and + * performs several state updates during the callback, such as setting the + * `isTestInProgress` flag to on and back to off again. + * + * Unit-level tests for this functionality aren't strictly necessary, as it + * is one of the key things covered in our e2e tests. + */ + +import React from 'react'; +import { act, renderHook } from '@testing-library/react-hooks'; +import { RendererProcessIpc } from 'electron-better-ipc'; +import { getMockIpc } from '../helpers/test/ipc'; +import { TestContextWrapper } from '../helpers/test/render'; +import { useSyntheticsTest } from './useSyntheticsTest'; +import { RecorderSteps, Result } from '../../common/types'; +import { createSteps } from '../../common/helper/test/createAction'; +import * as communicationHelpers from '../common/shared'; + +describe('useSyntheticsTest', () => { + let ipc: RendererProcessIpc; + + beforeEach(() => { + ipc = getMockIpc(); + }); + + const wrapper = ({ children }: { children?: React.ReactNode }) => ( + + ); + + it('should initiailize the test context with the correct state', () => { + const { result } = renderHook(() => useSyntheticsTest([]), { + wrapper, + }); + const context = result.current; + expect(context.codeBlocks).toBe(''); + expect(context.isResultFlyoutVisible).toBe(false); + expect(context.isTestInProgress).toBe(false); + }); + + it(`can use \`setResult\` to update the hook's state when there are > 0 steps`, () => { + const resultMock: Result = { + failed: 0, + skipped: 0, + succeeded: 1, + journey: { + status: 'succeeded', + steps: [ + { + duration: 1203, + name: 'first', + status: 'succeeded', + }, + ], + type: 'inline', + }, + }; + const response = renderHook(() => useSyntheticsTest(createSteps([['step 1']])), { + wrapper, + }); + const { setResult } = response.result.current; + act(() => setResult(resultMock)); + response.rerender(); + expect(response.result.current.result).toEqual(resultMock); + }); + + it('will set result to `undefined` if there are no steps', () => { + const resultMock: Result = { + failed: 0, + skipped: 0, + succeeded: 1, + journey: { + status: 'succeeded', + steps: [ + { + duration: 1203, + name: 'first', + status: 'succeeded', + }, + ], + type: 'inline', + }, + }; + const response = renderHook((steps: RecorderSteps) => useSyntheticsTest(steps), { + initialProps: createSteps([['step1']]), + wrapper, + }); + const { setResult } = response.result.current; + act(() => setResult(resultMock)); + response.rerender([]); + expect(response.result.current.result).toEqual(undefined); + }); + + it('will return the rendered code for a failed step in the result', async () => { + const mockCodeBlocks = `import lib from 'lib';`; + const getCodeSpy = jest.spyOn(communicationHelpers, 'getCodeForFailedResult'); + getCodeSpy.mockImplementation(async () => mockCodeBlocks); + + const resultMock: Result = { + failed: 0, + skipped: 0, + succeeded: 1, + journey: { + status: 'failed', + steps: [ + { + duration: 1203, + name: 'first', + status: 'failed', + }, + ], + type: 'inline', + }, + }; + const initialProps = createSteps([['step1']]); + const response = renderHook((steps: RecorderSteps) => useSyntheticsTest(steps), { + initialProps, + wrapper, + }); + const { setResult } = response.result.current; + act(() => setResult(resultMock)); + + response.rerender(initialProps); + + await response.waitForNextUpdate(); + + expect(response.result.current.codeBlocks).toBe(mockCodeBlocks); + }); +});