From 317c9558f24fba8cc0160e8675ccc6ecbe9f08c2 Mon Sep 17 00:00:00 2001 From: Kibana Machine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 28 Nov 2024 01:01:48 +1100 Subject: [PATCH] [8.x] [React18] Migrate test suites to account for testing library upgrades security-defend-workflows (#201174) (#201957) # Backport This will backport the following commits from `main` to `8.x`: - [[React18] Migrate test suites to account for testing library upgrades security-defend-workflows (#201174)](https://github.com/elastic/kibana/pull/201174) ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) Co-authored-by: Eyo O. Eyo <7893459+eokoneyo@users.noreply.github.com> --- .../use_is_osquery_available_simple.test.ts | 8 +- .../use_host_isolation_action.test.tsx | 94 +++++++++++-------- .../use_responder_action_data.test.ts | 63 +++++++------ .../mock/endpoint/app_context_render.tsx | 58 +++++++----- .../use_asset_criticality.test.ts | 16 ++-- .../components/artifact_flyout.test.tsx | 6 +- .../console_manager/console_manager.tsx | 10 +- .../console_manager.test.tsx | 56 ++++++----- .../hooks/agents/use_get_agent_status.test.ts | 45 ++++----- .../use_bulk_delete_artifact.test.tsx | 2 +- .../use_bulk_update_artifact.test.tsx | 2 +- .../artifacts/use_create_artifact.test.tsx | 2 +- .../artifacts/use_delete_artifact.test.tsx | 2 +- .../artifacts/use_get_updated_tags.test.tsx | 2 +- ..._host_isolation_exceptions_access.test.tsx | 14 +-- .../artifacts/use_update_artifact.test.tsx | 2 +- .../endpoint/use_get_endpoint_details.test.ts | 2 +- .../endpoint/use_get_endpoints_list.test.ts | 12 ++- .../hooks/endpoint/use_get_endpoints_list.ts | 2 +- .../policy/use_update_endpoint_policy.test.ts | 6 +- .../use_send_scan_request.test.ts | 4 +- .../public/management/hooks/test_utils.tsx | 8 +- 22 files changed, 232 insertions(+), 184 deletions(-) diff --git a/x-pack/plugins/osquery/public/shared_components/osquery_action/use_is_osquery_available_simple.test.ts b/x-pack/plugins/osquery/public/shared_components/osquery_action/use_is_osquery_available_simple.test.ts index 77c91957b10dc..c00335e1a397b 100644 --- a/x-pack/plugins/osquery/public/shared_components/osquery_action/use_is_osquery_available_simple.test.ts +++ b/x-pack/plugins/osquery/public/shared_components/osquery_action/use_is_osquery_available_simple.test.ts @@ -6,7 +6,7 @@ */ import { useKibana } from '../../common/lib/kibana'; import { useIsOsqueryAvailableSimple } from './use_is_osquery_available_simple'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook, waitFor } from '@testing-library/react'; import { createStartServicesMock } from '@kbn/triggers-actions-ui-plugin/public/common/lib/kibana/kibana_react.mock'; import { OSQUERY_INTEGRATION_NAME } from '../../../common'; import { httpServiceMock } from '@kbn/core/public/mocks'; @@ -41,15 +41,13 @@ describe('UseIsOsqueryAvailableSimple', () => { }); }); it('should expect response from API and return enabled flag', async () => { - const { result, waitForValueToChange } = renderHook(() => + const { result } = renderHook(() => useIsOsqueryAvailableSimple({ agentId: '3242332', }) ); expect(result.current).toBe(false); - await waitForValueToChange(() => result.current); - - expect(result.current).toBe(true); + await waitFor(() => expect(result.current).toBe(true)); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/use_host_isolation_action.test.tsx b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/use_host_isolation_action.test.tsx index c5bc4a01f5140..bb7f50b066ed9 100644 --- a/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/use_host_isolation_action.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/host_isolation/from_alerts/use_host_isolation_action.test.tsx @@ -4,7 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ - +import type React from 'react'; +import { act } from '@testing-library/react'; import type { UseHostIsolationActionProps } from './use_host_isolation_action'; import { useHostIsolationAction } from './use_host_isolation_action'; import type { AppContextTestRender, UserPrivilegesMockSetter } from '../../../../mock/endpoint'; @@ -15,7 +16,6 @@ import type { AlertTableContextMenuItem } from '../../../../../detections/compon import type { ResponseActionsApiCommandNames } from '../../../../../../common/endpoint/service/response_actions/constants'; import { agentStatusMocks } from '../../../../../../common/endpoint/service/response_actions/mocks/agent_status.mocks'; import { ISOLATE_HOST, UNISOLATE_HOST } from './translations'; -import type React from 'react'; import { HOST_ENDPOINT_UNENROLLED_TOOLTIP, LOADING_ENDPOINT_DATA_TOOLTIP, @@ -87,20 +87,22 @@ describe('useHostIsolationAction', () => { }); } - const { result, waitForValueToChange } = render(); - await waitForValueToChange(() => result.current); + const { result } = render(); - expect(result.current).toEqual([ - buildExpectedMenuItemResult({ - ...(command === 'unisolate' ? { name: UNISOLATE_HOST } : {}), - }), - ]); + await appContextMock.waitFor(() => + expect(result.current).toEqual([ + buildExpectedMenuItemResult({ + ...(command === 'unisolate' ? { name: UNISOLATE_HOST } : {}), + }), + ]) + ); } ); it('should call `closePopover` callback when menu item `onClick` is called', async () => { - const { result, waitForValueToChange } = render(); - await waitForValueToChange(() => result.current); + const { result } = render(); + await appContextMock.waitFor(() => expect(result.current[0].onClick).toBeDefined()); + result.current[0].onClick!({} as unknown as React.MouseEvent); expect(hookProps.closePopover).toHaveBeenCalled(); @@ -135,12 +137,14 @@ describe('useHostIsolationAction', () => { it('should return disabled menu item while loading agent status', async () => { const { result } = render(); - expect(result.current).toEqual([ - buildExpectedMenuItemResult({ - disabled: true, - toolTipContent: LOADING_ENDPOINT_DATA_TOOLTIP, - }), - ]); + await appContextMock.waitFor(() => + expect(result.current).toEqual([ + buildExpectedMenuItemResult({ + disabled: true, + toolTipContent: LOADING_ENDPOINT_DATA_TOOLTIP, + }), + ]) + ); }); it.each(['endpoint', 'non-endpoint'])( @@ -156,37 +160,51 @@ describe('useHostIsolationAction', () => { if (type === 'non-endpoint') { hookProps.detailsData = endpointAlertDataMock.generateSentinelOneAlertDetailsItemData(); } - const { result, waitForValueToChange } = render(); - await waitForValueToChange(() => result.current); - - expect(result.current).toEqual([ - buildExpectedMenuItemResult({ - disabled: true, - toolTipContent: - type === 'endpoint' ? HOST_ENDPOINT_UNENROLLED_TOOLTIP : NOT_FROM_ENDPOINT_HOST_TOOLTIP, - }), - ]); + const { result } = render(); + await appContextMock.waitFor(() => + expect(result.current).toEqual([ + buildExpectedMenuItemResult({ + disabled: true, + toolTipContent: + type === 'endpoint' + ? HOST_ENDPOINT_UNENROLLED_TOOLTIP + : NOT_FROM_ENDPOINT_HOST_TOOLTIP, + }), + ]) + ); } ); it('should call isolate API when agent is currently NOT isolated', async () => { - const { result, waitForValueToChange } = render(); - await waitForValueToChange(() => result.current); + const { result } = render(); + await appContextMock.waitFor(() => expect(result.current[0].onClick).toBeDefined()); result.current[0].onClick!({} as unknown as React.MouseEvent); expect(hookProps.onAddIsolationStatusClick).toHaveBeenCalledWith('isolateHost'); }); it('should call un-isolate API when agent is currently isolated', async () => { - apiMock.responseProvider.getAgentStatus.mockReturnValue( - agentStatusMocks.generateAgentStatusApiResponse({ - data: { 'abfe4a35-d5b4-42a0-a539-bd054c791769': { isolated: true } }, - }) - ); - const { result, waitForValueToChange } = render(); - await waitForValueToChange(() => result.current); - result.current[0].onClick!({} as unknown as React.MouseEvent); + apiMock.responseProvider.getAgentStatus.mockImplementation(({ query }) => { + const agentId = (query!.agentIds as string[])[0]; + + return agentStatusMocks.generateAgentStatusApiResponse({ + data: { [agentId]: { isolated: true } }, + }); + }); + + const { result } = render(); + + await appContextMock.waitFor(() => { + expect(apiMock.responseProvider.getAgentStatus).toHaveBeenCalled(); + expect(result.current[0].onClick).toBeDefined(); + }); + + act(() => { + result.current[0].onClick!({} as unknown as React.MouseEvent); + }); - expect(hookProps.onAddIsolationStatusClick).toHaveBeenCalledWith('unisolateHost'); + await appContextMock.waitFor(() => + expect(hookProps.onAddIsolationStatusClick).toHaveBeenCalledWith('unisolateHost') + ); }); }); diff --git a/x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/use_responder_action_data.test.ts b/x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/use_responder_action_data.test.ts index 3b68c0efdf9e6..6cbd0ced12387 100644 --- a/x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/use_responder_action_data.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/endpoint/responder/from_alerts/use_responder_action_data.test.ts @@ -25,7 +25,8 @@ import type { AppContextTestRender } from '../../../../mock/endpoint'; import { createAppRootMockRenderer, endpointAlertDataMock } from '../../../../mock/endpoint'; import { HOST_METADATA_LIST_ROUTE } from '../../../../../../common/endpoint/constants'; import { endpointMetadataHttpMocks } from '../../../../../management/pages/endpoint_hosts/mocks'; -import type { RenderHookResult } from '@testing-library/react-hooks/src/types'; +import type { RenderHookResult } from '@testing-library/react'; +import { waitFor, act } from '@testing-library/react'; import { createHttpFetchError } from '@kbn/core-http-browser-mocks'; import { HostStatus } from '../../../../../../common/endpoint/types'; import { @@ -61,17 +62,14 @@ describe('use responder action data hooks', () => { describe('useWithResponderActionDataFromAlert() hook', () => { let renderHook: () => RenderHookResult< - UseWithResponderActionDataFromAlertProps, - ResponderActionData + ResponderActionData, + UseWithResponderActionDataFromAlertProps >; let alertDetailItemData: TimelineEventsDetailsItem[]; beforeEach(() => { renderHook = () => { - return appContextMock.renderHook< - UseWithResponderActionDataFromAlertProps, - ResponderActionData - >(() => + return appContextMock.renderHook(() => useWithResponderActionDataFromAlert({ eventData: alertDetailItemData, onClick: onClickMock, @@ -95,7 +93,9 @@ describe('use responder action data hooks', () => { it('should call `onClick()` function prop when is pass to the hook', () => { alertDetailItemData = endpointAlertDataMock.generateSentinelOneAlertDetailsItemData(); const { result } = renderHook(); - result.current.handleResponseActionsClick(); + act(() => { + result.current.handleResponseActionsClick(); + }); expect(onClickMock).toHaveBeenCalled(); }); @@ -103,7 +103,9 @@ describe('use responder action data hooks', () => { it('should NOT call `onClick` if the action is disabled', () => { alertDetailItemData = endpointAlertDataMock.generateAlertDetailsItemDataForAgentType('foo'); const { result } = renderHook(); - result.current.handleResponseActionsClick(); + act(() => { + result.current.handleResponseActionsClick(); + }); expect(onClickMock).not.toHaveBeenCalled(); }); @@ -169,8 +171,8 @@ describe('use responder action data hooks', () => { }); it('should show action enabled if host metadata was retrieved and host is enrolled', async () => { - const { result, waitForValueToChange } = renderHook(); - await waitForValueToChange(() => result.current.isDisabled); + const { result } = renderHook(); + await waitFor(() => expect(result.current.isDisabled).toBe(false)); expect(result.current).toEqual(getExpectedResponderActionData()); }); @@ -181,8 +183,10 @@ describe('use responder action data hooks', () => { statusCode: 404, }); }); - const { result, waitForValueToChange } = renderHook(); - await waitForValueToChange(() => result.current.tooltip); + + const { result } = renderHook(); + + await waitFor(() => expect(result.current.tooltip).not.toEqual('Loading')); expect(result.current).toEqual( getExpectedResponderActionData({ @@ -199,8 +203,8 @@ describe('use responder action data hooks', () => { }; metadataApiMocks.responseProvider.metadataDetails.mockReturnValue(hostMetadata); - const { result, waitForValueToChange } = renderHook(); - await waitForValueToChange(() => result.current.tooltip); + const { result } = renderHook(); + await waitFor(() => expect(result.current.tooltip).not.toEqual('Loading')); expect(result.current).toEqual( getExpectedResponderActionData({ @@ -216,8 +220,8 @@ describe('use responder action data hooks', () => { statusCode: 500, }); }); - const { result, waitForValueToChange } = renderHook(); - await waitForValueToChange(() => result.current.tooltip); + const { result } = renderHook(); + await waitFor(() => expect(result.current.tooltip).not.toEqual('Loading')); expect(result.current).toEqual( getExpectedResponderActionData({ @@ -231,7 +235,7 @@ describe('use responder action data hooks', () => { describe('useResponderActionData() hook', () => { let hookProps: UseResponderActionDataProps; - let renderHook: () => RenderHookResult; + let renderHook: () => RenderHookResult; beforeEach(() => { endpointMetadataHttpMocks(appContextMock.coreStart.http); @@ -241,15 +245,13 @@ describe('use responder action data hooks', () => { onClick: onClickMock, }; renderHook = () => { - return appContextMock.renderHook(() => - useResponderActionData(hookProps) - ); + return appContextMock.renderHook(() => useResponderActionData(hookProps)); }; }); it('should show action enabled when agentType is Endpoint and host is enabled', async () => { - const { result, waitForValueToChange } = renderHook(); - await waitForValueToChange(() => result.current.isDisabled); + const { result } = renderHook(); + await waitFor(() => expect(result.current.isDisabled).toBe(false)); expect(result.current).toEqual(getExpectedResponderActionData()); }); @@ -266,9 +268,13 @@ describe('use responder action data hooks', () => { }); it('should call `onClick` prop when action is enabled', async () => { - const { result, waitForValueToChange } = renderHook(); - await waitForValueToChange(() => result.current.isDisabled); - result.current.handleResponseActionsClick(); + const { result } = renderHook(); + + await waitFor(() => expect(result.current.isDisabled).toBe(false)); + + act(() => { + result.current.handleResponseActionsClick(); + }); expect(onClickMock).toHaveBeenCalled(); }); @@ -276,7 +282,10 @@ describe('use responder action data hooks', () => { it('should not call `onCLick` prop when action is disabled', () => { hookProps.agentType = 'sentinel_one'; const { result } = renderHook(); - result.current.handleResponseActionsClick(); + + act(() => { + result.current.handleResponseActionsClick(); + }); expect(onClickMock).not.toHaveBeenCalled(); }); diff --git a/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx b/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx index 23abdab4d14f9..1787856989141 100644 --- a/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx +++ b/x-pack/plugins/security_solution/public/common/mock/endpoint/app_context_render.tsx @@ -9,18 +9,21 @@ import type { ReactPortal } from 'react'; import React from 'react'; import type { MemoryHistory } from 'history'; import { createMemoryHistory } from 'history'; -import type { RenderOptions, RenderResult } from '@testing-library/react'; -import { render as reactRender } from '@testing-library/react'; +import type { + RenderOptions, + RenderResult, + RenderHookResult, + RenderHookOptions, +} from '@testing-library/react'; +import { + render as reactRender, + waitFor, + renderHook as reactRenderHook, +} from '@testing-library/react'; import type { Action, Reducer, Store } from 'redux'; import { QueryClient } from '@tanstack/react-query'; import { coreMock } from '@kbn/core/public/mocks'; import { PLUGIN_ID } from '@kbn/fleet-plugin/common'; -import type { RenderHookOptions, RenderHookResult } from '@testing-library/react-hooks'; -import { renderHook as reactRenderHook } from '@testing-library/react-hooks'; -import type { - ReactHooksRenderer, - WrapperComponent, -} from '@testing-library/react-hooks/src/types/react'; import type { UseBaseQueryResult } from '@tanstack/react-query'; import ReactDOM from 'react-dom'; import type { DeepReadonly } from 'utility-types'; @@ -101,17 +104,16 @@ export type WaitForReactHookState = > | false; -type HookRendererFunction = (props: TProps) => TResult; +type HookRendererFunction = (props: TProps) => TResult; /** * A utility renderer for hooks that return React Query results */ export type ReactQueryHookRenderer< - // eslint-disable-next-line @typescript-eslint/no-explicit-any - TProps = any, + TProps = unknown, TResult extends UseBaseQueryResult = UseBaseQueryResult > = ( - hookFn: HookRendererFunction, + hookFn: HookRendererFunction, /** * If defined (default is `isSuccess`), the renderer will wait for the given react * query response state value to be true @@ -150,7 +152,15 @@ export interface AppContextTestRender { /** * Renders a hook within a mocked security solution app context */ - renderHook: ReactHooksRenderer['renderHook']; + renderHook: ( + hookFn: HookRendererFunction, + options?: RenderHookOptions + ) => RenderHookResult; + + /** + * Waits the return value of the callback provided to is truthy + */ + waitFor: typeof waitFor; /** * A helper utility for rendering specifically hooks that wrap ReactQuery @@ -305,12 +315,12 @@ export const createAppRootMockRenderer = (): AppContextTestRender => { }); }; - const renderHook: ReactHooksRenderer['renderHook'] = ( - hookFn: HookRendererFunction, + const renderHook = ( + hookFn: HookRendererFunction, options: RenderHookOptions = {} - ): RenderHookResult => { - return reactRenderHook(hookFn, { - wrapper: AppWrapper as WrapperComponent, + ) => { + return reactRenderHook(hookFn, { + wrapper: AppWrapper as React.FC, ...options, }); }; @@ -319,16 +329,17 @@ export const createAppRootMockRenderer = (): AppContextTestRender => { TProps, TResult extends UseBaseQueryResult = UseBaseQueryResult >( - hookFn: HookRendererFunction, + hookFn: HookRendererFunction, + /** + * If defined (default is `isSuccess`), the renderer will wait for the given react query to be truthy + */ waitForHook: WaitForReactHookState = 'isSuccess', options: RenderHookOptions = {} ) => { - const { result: hookResult, waitFor } = renderHook(hookFn, options); + const { result: hookResult } = renderHook(hookFn, options); if (waitForHook) { - await waitFor(() => { - return hookResult.current[waitForHook]; - }); + await waitFor(() => expect(hookResult.current[waitForHook]).toBe(true)); } return hookResult.current; @@ -400,6 +411,7 @@ export const createAppRootMockRenderer = (): AppContextTestRender => { setExperimentalFlag, getUserPrivilegesMockSetter, queryClient, + waitFor, }; }; diff --git a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/use_asset_criticality.test.ts b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/use_asset_criticality.test.ts index d4671a5bc628a..df9e39022f1e3 100644 --- a/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/use_asset_criticality.test.ts +++ b/x-pack/plugins/security_solution/public/entity_analytics/components/asset_criticality/use_asset_criticality.test.ts @@ -5,7 +5,11 @@ * 2.0. */ -import { renderMutation, renderQuery } from '../../../management/hooks/test_utils'; +import { + renderMutation, + renderQuery, + renderWrappedHook, +} from '../../../management/hooks/test_utils'; import type { Entity } from './use_asset_criticality'; import { useAssetCriticalityPrivileges, useAssetCriticalityData } from './use_asset_criticality'; @@ -69,10 +73,7 @@ describe('useAssetCriticality', () => { mockCreateAssetCriticality.mockResolvedValue({}); const entity: Entity = { name: 'test_entity_name', type: 'host' }; - const { mutation } = await renderQuery( - () => useAssetCriticalityData({ entity }), - 'isSuccess' - ); + const { mutation } = await renderWrappedHook(() => useAssetCriticalityData({ entity })); await renderMutation(async () => mutation.mutate({ @@ -91,10 +92,7 @@ describe('useAssetCriticality', () => { mockCreateAssetCriticality.mockResolvedValue({}); const entity: Entity = { name: 'test_entity_name', type: 'host' }; - const { mutation } = await renderQuery( - () => useAssetCriticalityData({ entity }), - 'isSuccess' - ); + const { mutation } = await renderWrappedHook(() => useAssetCriticalityData({ entity })); await renderMutation(async () => mutation.mutate({ diff --git a/x-pack/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_flyout.test.tsx b/x-pack/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_flyout.test.tsx index 83b8682190101..66f372b7903b0 100644 --- a/x-pack/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_flyout.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/artifact_list_page/components/artifact_flyout.test.tsx @@ -47,9 +47,9 @@ describe('When the flyout is opened in the ArtifactListPage component', () => { render = async (props = {}) => { renderResult = renderSetup.renderArtifactListPage(props); - await waitFor(async () => { - expect(renderResult.getByTestId('testPage-flyout')); - }); + await waitFor(async () => + expect(renderResult.getByTestId('testPage-flyout')).toBeInTheDocument() + ); return renderResult; }; diff --git a/x-pack/plugins/security_solution/public/management/components/console/components/console_manager/console_manager.tsx b/x-pack/plugins/security_solution/public/management/components/console/components/console_manager/console_manager.tsx index deb6967da2c27..7d85f5c11575c 100644 --- a/x-pack/plugins/security_solution/public/management/components/console/components/console_manager/console_manager.tsx +++ b/x-pack/plugins/security_solution/public/management/components/console/components/console_manager/console_manager.tsx @@ -113,13 +113,9 @@ export const ConsoleManager = memo(({ storage = {}, childre validateIdOrThrow(id); setConsoleStorage((prevState) => { - return { - ...prevState, - [id]: { - ...prevState[id], - isOpen: false, - }, - }; + const newState = { ...prevState }; + newState[id].isOpen = false; + return newState; }); }, [validateIdOrThrow] // << IMPORTANT: this callback should have only immutable dependencies diff --git a/x-pack/plugins/security_solution/public/management/components/console/components/console_manager/integration_tests/console_manager.test.tsx b/x-pack/plugins/security_solution/public/management/components/console/components/console_manager/integration_tests/console_manager.test.tsx index 8cf7b94f9bcd8..912b96b4406cf 100644 --- a/x-pack/plugins/security_solution/public/management/components/console/components/console_manager/integration_tests/console_manager.test.tsx +++ b/x-pack/plugins/security_solution/public/management/components/console/components/console_manager/integration_tests/console_manager.test.tsx @@ -5,8 +5,6 @@ * 2.0. */ -import type { RenderHookResult } from '@testing-library/react-hooks'; -import { renderHook as _renderHook, act } from '@testing-library/react-hooks'; import { useConsoleManager } from '../console_manager'; import React from 'react'; import type { @@ -22,12 +20,13 @@ import { getNewConsoleRegistrationMock, } from '../mocks'; import userEvent, { type UserEvent } from '@testing-library/user-event'; -import { waitFor } from '@testing-library/react'; +import type { RenderHookResult } from '@testing-library/react'; +import { waitFor, act, renderHook as _renderHook } from '@testing-library/react'; import { enterConsoleCommand } from '../../../mocks'; describe('When using ConsoleManager', () => { describe('and using the ConsoleManagerInterface via the hook', () => { - type RenderResultInterface = RenderHookResult; + type RenderResultInterface = RenderHookResult; let renderHook: () => RenderResultInterface; let renderResult: RenderResultInterface; @@ -103,20 +102,27 @@ describe('When using ConsoleManager', () => { ); }); - it('should hide a console by `id`', () => { + it('should hide a console by `id`', async () => { renderHook(); const { id: consoleId } = registerNewConsole(); + + let consoleClient: ReturnType; + + act(() => { + consoleClient = renderResult.result.current.getOne(consoleId); + }); + act(() => { renderResult.result.current.show(consoleId); }); - expect(renderResult.result.current.getOne(consoleId)!.isVisible()).toBe(true); + await waitFor(() => expect(consoleClient!.isVisible()).toBe(true)); act(() => { renderResult.result.current.hide(consoleId); }); - expect(renderResult.result.current.getOne(consoleId)!.isVisible()).toBe(false); + await waitFor(() => expect(consoleClient!.isVisible()).toBe(false)); }); it('should throw if attempting to hide a console with invalid `id`', () => { @@ -163,7 +169,9 @@ describe('When using ConsoleManager', () => { beforeEach(() => { renderHook(); ({ id: consoleId } = registerNewConsole()); - registeredConsole = renderResult.result.current.getOne(consoleId)!; + act(() => { + registeredConsole = renderResult.result.current.getOne(consoleId)!; + }); }); it('should have the expected interface', () => { @@ -178,27 +186,31 @@ describe('When using ConsoleManager', () => { }); it('should display the console when `.show()` is called', async () => { - registeredConsole.show(); - await renderResult.waitForNextUpdate(); - - expect(registeredConsole.isVisible()).toBe(true); + act(() => { + registeredConsole.show(); + }); + await waitFor(() => expect(registeredConsole.isVisible()).toBe(true)); }); it('should hide the console when `.hide()` is called', async () => { - registeredConsole.show(); - await renderResult.waitForNextUpdate(); - expect(registeredConsole.isVisible()).toBe(true); + act(() => { + registeredConsole.show(); + }); - registeredConsole.hide(); - await renderResult.waitForNextUpdate(); - expect(registeredConsole.isVisible()).toBe(false); + await waitFor(() => expect(registeredConsole.isVisible()).toBe(true)); + + act(() => { + registeredConsole.hide(); + }); + + await waitFor(() => expect(registeredConsole.isVisible()).toBe(false)); }); it('should un-register the console when `.terminate() is called', async () => { - registeredConsole.terminate(); - await renderResult.waitForNextUpdate(); - - expect(renderResult.result.current.getOne(consoleId)).toBeUndefined(); + act(() => { + registeredConsole.terminate(); + }); + await waitFor(() => expect(renderResult.result.current.getOne(consoleId)).toBeUndefined()); }); }); }); diff --git a/x-pack/plugins/security_solution/public/management/hooks/agents/use_get_agent_status.test.ts b/x-pack/plugins/security_solution/public/management/hooks/agents/use_get_agent_status.test.ts index 300fd11a700a3..ce8bb96a2c08c 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/agents/use_get_agent_status.test.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/agents/use_get_agent_status.test.ts @@ -10,14 +10,15 @@ import { createAppRootMockRenderer } from '../../../common/mock/endpoint'; import { useGetAgentStatus } from './use_get_agent_status'; import { agentStatusGetHttpMock } from '../../mocks'; import { AGENT_STATUS_ROUTE } from '../../../../common/endpoint/constants'; -import type { RenderHookResult } from '@testing-library/react-hooks/src/types'; +import type { RenderHookResult } from '@testing-library/react'; +import { waitFor } from '@testing-library/react'; describe('useGetAgentStatus hook', () => { let httpMock: AppContextTestRender['coreStart']['http']; let agentIdsProp: Parameters[0]; let optionsProp: Parameters[2]; let apiMock: ReturnType; - let renderHook: () => RenderHookResult>; + let renderHook: () => RenderHookResult, unknown>; beforeEach(() => { const appTestContext = createAppRootMockRenderer(); @@ -25,7 +26,7 @@ describe('useGetAgentStatus hook', () => { httpMock = appTestContext.coreStart.http; apiMock = agentStatusGetHttpMock(httpMock); renderHook = () => { - return appTestContext.renderHook>(() => + return appTestContext.renderHook(() => useGetAgentStatus(agentIdsProp, 'endpoint', optionsProp) ); }; @@ -63,28 +64,28 @@ describe('useGetAgentStatus hook', () => { }); it('should return expected data', async () => { - const { result, waitForValueToChange } = renderHook(); - await waitForValueToChange(() => result.current); - - expect(result.current.data).toEqual({ - '1-2-3': { - agentId: '1-2-3', - agentType: 'endpoint', - found: true, - isolated: false, - lastSeen: expect.any(String), - pendingActions: {}, - status: 'healthy', - }, - }); + const { result } = renderHook(); + await waitFor(() => + expect(result.current.data).toEqual({ + '1-2-3': { + agentId: '1-2-3', + agentType: 'endpoint', + found: true, + isolated: false, + lastSeen: expect.any(String), + pendingActions: {}, + status: 'healthy', + }, + }) + ); }); it('should NOT call agent status api if list of agent ids is empty', async () => { agentIdsProp = ['', ' ']; - const { result, waitForValueToChange } = renderHook(); - await waitForValueToChange(() => result.current); - - expect(result.current.data).toEqual({}); - expect(apiMock.responseProvider.getAgentStatus).not.toHaveBeenCalled(); + const { result } = renderHook(); + await waitFor(() => { + expect(result.current.data).toEqual({}); + expect(apiMock.responseProvider.getAgentStatus).not.toHaveBeenCalled(); + }); }); }); diff --git a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_bulk_delete_artifact.test.tsx b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_bulk_delete_artifact.test.tsx index 403731566ef3f..2c6c6ff92003e 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_bulk_delete_artifact.test.tsx +++ b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_bulk_delete_artifact.test.tsx @@ -15,7 +15,7 @@ import { renderMutation, } from '../test_utils'; import { getExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_item_schema.mock'; -import { act } from '@testing-library/react-hooks'; +import { act } from '@testing-library/react'; const apiVersion = '2023-10-31'; diff --git a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_bulk_update_artifact.test.tsx b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_bulk_update_artifact.test.tsx index 5db9734c61f9f..24f142773ae5e 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_bulk_update_artifact.test.tsx +++ b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_bulk_update_artifact.test.tsx @@ -15,7 +15,7 @@ import { renderMutation, } from '../test_utils'; import { getExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_item_schema.mock'; -import { act } from '@testing-library/react-hooks'; +import { act } from '@testing-library/react'; const apiVersion = '2023-10-31'; diff --git a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_create_artifact.test.tsx b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_create_artifact.test.tsx index 853ea0df96a3d..71d88d3732275 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_create_artifact.test.tsx +++ b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_create_artifact.test.tsx @@ -15,7 +15,7 @@ import { renderMutation, } from '../test_utils'; import { getExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_item_schema.mock'; -import { act } from '@testing-library/react-hooks'; +import { act } from '@testing-library/react'; describe('Create artifact hook', () => { let result: ReturnType; diff --git a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_delete_artifact.test.tsx b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_delete_artifact.test.tsx index 9473a50fa8a33..f988494bdceb7 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_delete_artifact.test.tsx +++ b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_delete_artifact.test.tsx @@ -15,7 +15,7 @@ import { renderMutation, } from '../test_utils'; import { getExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_item_schema.mock'; -import { act } from '@testing-library/react-hooks'; +import { act } from '@testing-library/react'; describe('Delete artifact hook', () => { let result: ReturnType; diff --git a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_get_updated_tags.test.tsx b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_get_updated_tags.test.tsx index 0fb481a489c2f..1461f68fd1aaf 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_get_updated_tags.test.tsx +++ b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_get_updated_tags.test.tsx @@ -6,7 +6,7 @@ */ import { useGetUpdatedTags } from '.'; -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; import type { TagFilter } from '../../../../common/endpoint/service/artifacts/utils'; diff --git a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_host_isolation_exceptions_access.test.tsx b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_host_isolation_exceptions_access.test.tsx index 2bd673f68095f..8cf4c30cdda71 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_host_isolation_exceptions_access.test.tsx +++ b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_host_isolation_exceptions_access.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook, waitFor } from '@testing-library/react'; import { useHostIsolationExceptionsAccess } from './use_host_isolation_exceptions_access'; import { checkArtifactHasData } from '../../services/exceptions_list/check_artifact_has_data'; @@ -29,7 +29,7 @@ describe('useHostIsolationExceptionsAccess', () => { }; test('should set access to true if canAccessHostIsolationExceptions is true', async () => { - const { result, waitFor } = setupHook(true, false); + const { result } = setupHook(true, false); await waitFor(() => expect(result.current.hasAccessToHostIsolationExceptions).toBe(true)); }); @@ -37,7 +37,7 @@ describe('useHostIsolationExceptionsAccess', () => { test('should check for artifact data if canReadHostIsolationExceptions is true and canAccessHostIsolationExceptions is false', async () => { mockArtifactHasData(); - const { result, waitFor } = setupHook(false, true); + const { result } = setupHook(false, true); await waitFor(() => { expect(checkArtifactHasData).toHaveBeenCalledWith(mockApiClient()); @@ -48,7 +48,7 @@ describe('useHostIsolationExceptionsAccess', () => { test('should set access to false if canReadHostIsolationExceptions is true but no artifact data exists', async () => { mockArtifactHasData(false); - const { result, waitFor } = setupHook(false, true); + const { result } = setupHook(false, true); await waitFor(() => { expect(checkArtifactHasData).toHaveBeenCalledWith(mockApiClient()); @@ -57,14 +57,14 @@ describe('useHostIsolationExceptionsAccess', () => { }); test('should set access to false if neither canAccessHostIsolationExceptions nor canReadHostIsolationExceptions is true', async () => { - const { result, waitFor } = setupHook(false, false); + const { result } = setupHook(false, false); await waitFor(() => { expect(result.current.hasAccessToHostIsolationExceptions).toBe(false); }); }); test('should not call checkArtifactHasData if canAccessHostIsolationExceptions is true', async () => { - const { result, waitFor } = setupHook(true, true); + const { result } = setupHook(true, true); await waitFor(() => { expect(checkArtifactHasData).not.toHaveBeenCalled(); @@ -73,7 +73,7 @@ describe('useHostIsolationExceptionsAccess', () => { }); test('should set loading state correctly while checking access', async () => { - const { result, waitFor } = setupHook(false, true); + const { result } = setupHook(false, true); expect(result.current.isHostIsolationExceptionsAccessLoading).toBe(true); diff --git a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_update_artifact.test.tsx b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_update_artifact.test.tsx index 14607a33f2098..4c932812c8c01 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_update_artifact.test.tsx +++ b/x-pack/plugins/security_solution/public/management/hooks/artifacts/use_update_artifact.test.tsx @@ -15,7 +15,7 @@ import { renderMutation, } from '../test_utils'; import { getExceptionListItemSchemaMock } from '@kbn/lists-plugin/common/schemas/response/exception_list_item_schema.mock'; -import { act } from '@testing-library/react-hooks'; +import { act } from '@testing-library/react'; describe('Update artifact hook', () => { let result: ReturnType; diff --git a/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoint_details.test.ts b/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoint_details.test.ts index 58222386da571..f96c7a42f6ceb 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoint_details.test.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoint_details.test.ts @@ -26,7 +26,7 @@ jest.mock('@tanstack/react-query', () => { describe('useGetEndpointDetails hook', () => { let renderReactQueryHook: ReactQueryHookRenderer< - Parameters, + Parameters[number], ReturnType >; let http: AppContextTestRender['coreStart']['http']; diff --git a/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoints_list.test.ts b/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoints_list.test.ts index d7f073b2a8338..3c9ed815536df 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoints_list.test.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoints_list.test.ts @@ -7,7 +7,7 @@ import type { AppContextTestRender, ReactQueryHookRenderer } from '../../../common/mock/endpoint'; import { createAppRootMockRenderer } from '../../../common/mock/endpoint'; -import { useGetEndpointsList } from './use_get_endpoints_list'; +import { useGetEndpointsList, PAGING_PARAMS } from './use_get_endpoints_list'; import { HOST_METADATA_LIST_ROUTE } from '../../../../common/endpoint/constants'; import { useQuery as _useQuery } from '@tanstack/react-query'; import { endpointMetadataHttpMocks } from '../../pages/endpoint_hosts/mocks'; @@ -117,13 +117,15 @@ describe('useGetEndpointsList hook', () => { it('should also list inactive agents', async () => { const getApiResponse = apiMocks.responseProvider.metadataList.getMockImplementation(); + const inActiveIndex = [0, 1, 3]; + // set a few of the agents as inactive/unenrolled apiMocks.responseProvider.metadataList.mockImplementation(() => { if (getApiResponse) { return { ...getApiResponse(), data: getApiResponse().data.map((item, i) => { - const isInactiveIndex = [0, 1, 3].includes(i); + const isInactiveIndex = inActiveIndex.includes(i); return { ...item, host_status: isInactiveIndex ? HostStatus.INACTIVE : item.host_status, @@ -154,7 +156,7 @@ describe('useGetEndpointsList hook', () => { const res = await renderReactQueryHook(() => useGetEndpointsList({ searchString: 'inactive' })); expect( res.data?.map((host) => host.name.split('-')[2]).filter((name) => name === 'inactive').length - ).toEqual(3); + ).toEqual(inActiveIndex.length); }); it('should only list 50 agents when more than 50 in the metadata list API', async () => { @@ -192,7 +194,7 @@ describe('useGetEndpointsList hook', () => { // verify useGetEndpointsList hook returns all 50 agents in the list const res = await renderReactQueryHook(() => useGetEndpointsList({ searchString: '' })); - expect(res.data?.length).toEqual(50); + expect(res.data?.length).toEqual(PAGING_PARAMS.default); }); it('should only list 10 more agents when 50 or more agents are already selected', async () => { @@ -232,7 +234,7 @@ describe('useGetEndpointsList hook', () => { const agentIdsToSelect = apiMocks.responseProvider .metadataList() .data.map((d) => d.metadata.agent.id) - .slice(0, 50); + .slice(0, PAGING_PARAMS.default); // call useGetEndpointsList with all 50 agents selected const res = await renderReactQueryHook(() => diff --git a/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoints_list.ts b/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoints_list.ts index 632b7d8ff48ae..b0072d73b832e 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoints_list.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/endpoint/use_get_endpoints_list.ts @@ -18,7 +18,7 @@ type GetEndpointsListResponse = Array<{ selected: boolean; }>; -const PAGING_PARAMS = Object.freeze({ +export const PAGING_PARAMS = Object.freeze({ default: 50, all: 10000, }); diff --git a/x-pack/plugins/security_solution/public/management/hooks/policy/use_update_endpoint_policy.test.ts b/x-pack/plugins/security_solution/public/management/hooks/policy/use_update_endpoint_policy.test.ts index 8b6be08b41b46..6d27b5ccf447a 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/policy/use_update_endpoint_policy.test.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/policy/use_update_endpoint_policy.test.ts @@ -13,7 +13,7 @@ import type { UseUpdateEndpointPolicyOptions, UseUpdateEndpointPolicyResult, } from './use_update_endpoint_policy'; -import type { RenderHookResult } from '@testing-library/react-hooks/src/types'; +import type { RenderHookResult } from '@testing-library/react'; import { useUpdateEndpointPolicy } from './use_update_endpoint_policy'; import type { PolicyData } from '../../../../common/endpoint/types'; import { FleetPackagePolicyGenerator } from '../../../../common/endpoint/data_generators/fleet_package_policy_generator'; @@ -37,8 +37,8 @@ describe('When using the `useFetchEndpointPolicyAgentSummary()` hook', () => { let apiMocks: ReturnType; let policy: PolicyData; let renderHook: () => RenderHookResult< - UseUpdateEndpointPolicyOptions, - UseUpdateEndpointPolicyResult + UseUpdateEndpointPolicyResult, + UseUpdateEndpointPolicyOptions >; beforeEach(() => { diff --git a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_scan_request.test.ts b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_scan_request.test.ts index f5b3f20d21d88..b6afc01e131a7 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_scan_request.test.ts +++ b/x-pack/plugins/security_solution/public/management/hooks/response_actions/use_send_scan_request.test.ts @@ -7,7 +7,7 @@ import { useMutation as _useMutation } from '@tanstack/react-query'; import type { AppContextTestRender } from '../../../common/mock/endpoint'; -import type { RenderHookResult } from '@testing-library/react-hooks/src/types'; +import type { RenderHookResult } from '@testing-library/react'; import { createAppRootMockRenderer } from '../../../common/mock/endpoint'; import { responseActionsHttpMocks } from '../../mocks/response_actions_http_mocks'; import { @@ -38,7 +38,7 @@ describe('When using the `useSendScanRequest()` hook', () => { let customOptions: ScanRequestCustomOptions; let http: AppContextTestRender['coreStart']['http']; let apiMocks: ReturnType; - let renderHook: () => RenderHookResult; + let renderHook: () => RenderHookResult; beforeEach(() => { const testContext = createAppRootMockRenderer(); diff --git a/x-pack/plugins/security_solution/public/management/hooks/test_utils.tsx b/x-pack/plugins/security_solution/public/management/hooks/test_utils.tsx index 149eee55872aa..cc674610bbaee 100644 --- a/x-pack/plugins/security_solution/public/management/hooks/test_utils.tsx +++ b/x-pack/plugins/security_solution/public/management/hooks/test_utils.tsx @@ -5,7 +5,7 @@ * 2.0. */ import React from 'react'; -import { renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import type { HttpSetup } from '@kbn/core/public'; import type { CreateExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; import { coreMock } from '@kbn/core/public/mocks'; @@ -39,10 +39,10 @@ export const renderQuery = async ( const wrapper = ({ children }: { children: React.ReactNode }): JSX.Element => ( {children} ); - const { result: resultHook, waitFor } = renderHook(() => hook(), { + const { result: resultHook } = renderHook(() => hook(), { wrapper, }); - await waitFor(() => resultHook.current[waitForHook]); + await waitFor(() => expect(resultHook.current[waitForHook]).toBeTruthy()); return resultHook.current; }; @@ -58,3 +58,5 @@ export const renderMutation = async ( }); return resultHook.current; }; + +export const renderWrappedHook = renderMutation;