From 3683cc28469a0acf4beb05898060db34db3f2f90 Mon Sep 17 00:00:00 2001 From: "Eyo O. Eyo" <7893459+eokoneyo@users.noreply.github.com> Date: Wed, 11 Dec 2024 14:46:09 +0100 Subject: [PATCH] [React18] Migrate test suites to account for testing library upgrades security-detection-engine (#201149) This PR migrates test suites that use `renderHook` from the library `@testing-library/react-hooks` to adopt the equivalent and replacement of `renderHook` from the export that is now available from `@testing-library/react`. This work is required for the planned migration to react18. ## Context In this PR, usages of `waitForNextUpdate` that previously could have been destructured from `renderHook` are now been replaced with `waitFor` exported from `@testing-library/react`, furthermore `waitFor` that would also have been destructured from the same renderHook result is now been replaced with `waitFor` from the export of `@testing-library/react`. ***Why is `waitFor` a sufficient enough replacement for `waitForNextUpdate`, and better for testing values subject to async computations?*** WaitFor will retry the provided callback if an error is returned, till the configured timeout elapses. By default the retry interval is `50ms` with a timeout value of `1000ms` that effectively translates to at least 20 retries for assertions placed within waitFor. See https://testing-library.com/docs/dom-testing-library/api-async/#waitfor for more information. This however means that for person's writing tests, said person has to be explicit about expectations that describe the internal state of the hook being tested. This implies checking for instance when a react query hook is being rendered, there's an assertion that said hook isn't loading anymore. In this PR you'd notice that this pattern has been adopted, with most existing assertions following an invocation of `waitForNextUpdate` being placed within a `waitFor` invocation. In some cases the replacement is simply a `waitFor(() => new Promise((resolve) => resolve(null)))` (many thanks to @kapral18, for point out exactly why this works), where this suffices the assertions that follow aren't placed within a waitFor so this PR doesn't get larger than it needs to be. It's also worth pointing out this PR might also contain changes to test and application code to improve said existing test. ### What to do next? 1. Review the changes in this PR. 2. If you think the changes are correct, approve the PR. ## Any questions? If you have any questions or need help with this PR, please leave comments in this PR. Co-authored-by: Elastic Machine --- .../__tests__/use_es_field.test.ts | 12 +- .../index.test.ts | 363 ++++++++---------- .../use_exception_item_card.test.ts | 2 +- .../edit_modal/use_edit_modal.test.ts | 2 +- .../src/list_header/use_list_header.test.ts | 3 +- .../src/pagination/use_pagination.test.ts | 2 +- .../use_value_with_space_warning.test.ts | 2 +- .../src/use_async/index.test.ts | 73 ++-- .../src/use_is_mounted/index.test.ts | 2 +- .../src/use_observable/index.test.ts | 2 +- .../src/use_cursor/index.test.ts | 2 +- .../src/use_find_lists/index.test.ts | 12 +- .../src/use_persist_exception_item/index.ts | 84 ++-- .../edit_package_policy_page/index.test.tsx | 227 +++++------ .../hooks/persist_exception_item.test.ts | 145 +++---- .../hooks/persist_exception_list.test.ts | 77 ++-- .../public/exceptions/hooks/use_api.test.ts | 355 ++++++++--------- .../hooks/use_exception_lists.test.ts | 339 +++++++--------- .../lists/hooks/use_create_list_index.test.ts | 37 +- .../lists/hooks/use_delete_list.test.ts | 12 +- .../lists/hooks/use_export_list.test.ts | 12 +- .../lists/hooks/use_import_list.test.ts | 36 +- .../lists/hooks/use_read_list_index.test.ts | 28 +- .../hooks/use_all_esql_rule_fields.test.ts | 2 +- .../hooks/use_esql_index.test.ts | 3 +- .../use_add_to_rules_table.test.tsx | 3 +- .../use_value_with_space_warning.test.ts | 2 +- ...tch_or_create_rule_exception_list.test.tsx | 221 ++++------- .../use_schedule_rule_run_mutation.test.tsx | 2 +- .../logic/use_schedule_rule_run.test.tsx | 64 ++- .../lists/use_lists_config.test.tsx | 2 +- 31 files changed, 943 insertions(+), 1185 deletions(-) diff --git a/packages/kbn-securitysolution-autocomplete/src/es_field_selector/__tests__/use_es_field.test.ts b/packages/kbn-securitysolution-autocomplete/src/es_field_selector/__tests__/use_es_field.test.ts index 49a4b0d5eb40f..942ccbc29c38e 100644 --- a/packages/kbn-securitysolution-autocomplete/src/es_field_selector/__tests__/use_es_field.test.ts +++ b/packages/kbn-securitysolution-autocomplete/src/es_field_selector/__tests__/use_es_field.test.ts @@ -9,11 +9,7 @@ import { DataViewFieldBase } from '@kbn/es-query'; import { ReactElement } from 'react'; -import { act } from '@testing-library/react'; - -import { renderHook } from '@testing-library/react-hooks'; -import TestRenderer from 'react-test-renderer'; -const { act: actTestRenderer } = TestRenderer; +import { act, renderHook } from '@testing-library/react'; import { fields } from '../../fields/index.mock'; import { useEsField } from '../use_es_field'; @@ -480,7 +476,7 @@ describe('useField', () => { useEsField({ indexPattern, onChange: onChangeMock, isRequired: true }) ); - actTestRenderer(() => { + act(() => { result.current.handleTouch(); }); expect(result.current.isInvalid).toBeTruthy(); @@ -490,7 +486,7 @@ describe('useField', () => { useEsField({ indexPattern, onChange: onChangeMock, isRequired: true, selectedField }) ); - actTestRenderer(() => { + act(() => { result.current.handleTouch(); }); expect(result.current.isInvalid).toBeFalsy(); @@ -498,7 +494,7 @@ describe('useField', () => { it('should return isInvalid equals false when isRequired is false', () => { const { result } = renderHook(() => useEsField({ indexPattern, onChange: onChangeMock })); - actTestRenderer(() => { + act(() => { result.current.handleTouch(); }); expect(result.current.isInvalid).toBeFalsy(); diff --git a/packages/kbn-securitysolution-autocomplete/src/hooks/use_field_value_autocomplete/index.test.ts b/packages/kbn-securitysolution-autocomplete/src/hooks/use_field_value_autocomplete/index.test.ts index aae858aa63087..836744f3ede28 100644 --- a/packages/kbn-securitysolution-autocomplete/src/hooks/use_field_value_autocomplete/index.test.ts +++ b/packages/kbn-securitysolution-autocomplete/src/hooks/use_field_value_autocomplete/index.test.ts @@ -7,14 +7,10 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook } from '@testing-library/react'; import { ListOperatorTypeEnum as OperatorTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; -import { - UseFieldValueAutocompleteProps, - UseFieldValueAutocompleteReturn, - useFieldValueAutocomplete, -} from '.'; +import { UseFieldValueAutocompleteReturn, useFieldValueAutocomplete } from '.'; import { getField } from '../../fields/index.mock'; import { autocompleteStartMock } from '../../autocomplete/index.mock'; import { DataViewFieldBase } from '@kbn/es-query'; @@ -46,140 +42,115 @@ describe('use_field_value_autocomplete', () => { }); test('initializes hook', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook< - UseFieldValueAutocompleteProps, - UseFieldValueAutocompleteReturn - >(() => - useFieldValueAutocomplete({ - autocompleteService: { - ...autocompleteStartMock, - getValueSuggestions: getValueSuggestionsMock, - }, - fieldValue: '', - indexPattern: undefined, - operatorType: OperatorTypeEnum.MATCH, - query: '', - selectedField: undefined, - }) - ); - await waitForNextUpdate(); - - expect(result.current).toEqual([false, true, [], result.current[3]]); - }); + const { result } = renderHook(() => + useFieldValueAutocomplete({ + autocompleteService: { + ...autocompleteStartMock, + getValueSuggestions: getValueSuggestionsMock, + }, + fieldValue: '', + indexPattern: undefined, + operatorType: OperatorTypeEnum.MATCH, + query: '', + selectedField: undefined, + }) + ); + await waitFor(() => expect(result.current).toEqual([false, true, [], result.current[3]])); }); test('does not call autocomplete service if "operatorType" is "exists"', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook< - UseFieldValueAutocompleteProps, - UseFieldValueAutocompleteReturn - >(() => - useFieldValueAutocomplete({ - autocompleteService: { - ...autocompleteStartMock, - getValueSuggestions: getValueSuggestionsMock, - }, - fieldValue: '', - indexPattern: stubIndexPatternWithFields, - operatorType: OperatorTypeEnum.EXISTS, - query: '', - selectedField: getField('machine.os'), - }) - ); - await waitForNextUpdate(); + const { result } = renderHook(() => + useFieldValueAutocomplete({ + autocompleteService: { + ...autocompleteStartMock, + getValueSuggestions: getValueSuggestionsMock, + }, + fieldValue: '', + indexPattern: stubIndexPatternWithFields, + operatorType: OperatorTypeEnum.EXISTS, + query: '', + selectedField: getField('machine.os'), + }) + ); + await waitFor(() => { const expectedResult: UseFieldValueAutocompleteReturn = [false, true, [], result.current[3]]; - expect(getValueSuggestionsMock).not.toHaveBeenCalled(); expect(result.current).toEqual(expectedResult); + expect(getValueSuggestionsMock).not.toHaveBeenCalled(); }); }); test('does not call autocomplete service if "selectedField" is undefined', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook< - UseFieldValueAutocompleteProps, - UseFieldValueAutocompleteReturn - >(() => - useFieldValueAutocomplete({ - autocompleteService: { - ...autocompleteStartMock, - getValueSuggestions: getValueSuggestionsMock, - }, - fieldValue: '', - indexPattern: stubIndexPatternWithFields, - operatorType: OperatorTypeEnum.EXISTS, - query: '', - selectedField: undefined, - }) - ); - await waitForNextUpdate(); + const { result } = renderHook(() => + useFieldValueAutocomplete({ + autocompleteService: { + ...autocompleteStartMock, + getValueSuggestions: getValueSuggestionsMock, + }, + fieldValue: '', + indexPattern: stubIndexPatternWithFields, + operatorType: OperatorTypeEnum.EXISTS, + query: '', + selectedField: undefined, + }) + ); + await waitFor(() => { const expectedResult: UseFieldValueAutocompleteReturn = [false, true, [], result.current[3]]; - expect(getValueSuggestionsMock).not.toHaveBeenCalled(); expect(result.current).toEqual(expectedResult); + expect(getValueSuggestionsMock).not.toHaveBeenCalled(); }); }); test('does not call autocomplete service if "indexPattern" is undefined', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook< - UseFieldValueAutocompleteProps, - UseFieldValueAutocompleteReturn - >(() => - useFieldValueAutocomplete({ - autocompleteService: { - ...autocompleteStartMock, - getValueSuggestions: getValueSuggestionsMock, - }, - fieldValue: '', - indexPattern: undefined, - operatorType: OperatorTypeEnum.EXISTS, - query: '', - selectedField: getField('machine.os'), - }) - ); - await waitForNextUpdate(); + const { result } = renderHook(() => + useFieldValueAutocomplete({ + autocompleteService: { + ...autocompleteStartMock, + getValueSuggestions: getValueSuggestionsMock, + }, + fieldValue: '', + indexPattern: undefined, + operatorType: OperatorTypeEnum.EXISTS, + query: '', + selectedField: getField('machine.os'), + }) + ); + await waitFor(() => { const expectedResult: UseFieldValueAutocompleteReturn = [false, true, [], result.current[3]]; - expect(getValueSuggestionsMock).not.toHaveBeenCalled(); expect(result.current).toEqual(expectedResult); + expect(getValueSuggestionsMock).not.toHaveBeenCalled(); }); }); test('it uses full path name for nested fields to fetch suggestions', async () => { const suggestionsMock = jest.fn().mockResolvedValue([]); - await act(async () => { - const selectedField: DataViewFieldBase | undefined = getField('nestedField.child'); - if (selectedField == null) { - throw new TypeError('selectedField for this test should always be defined'); - } - - const { signal } = new AbortController(); - const { waitForNextUpdate } = renderHook< - UseFieldValueAutocompleteProps, - UseFieldValueAutocompleteReturn - >(() => - useFieldValueAutocomplete({ - autocompleteService: { - ...autocompleteStartMock, - getValueSuggestions: suggestionsMock, - }, - fieldValue: '', - indexPattern: stubIndexPatternWithFields, - operatorType: OperatorTypeEnum.MATCH, - query: '', - selectedField: { ...selectedField, name: 'child' }, - }) - ); - // Note: initial `waitForNextUpdate` is hook initialization - await waitForNextUpdate(); - await waitForNextUpdate(); + const selectedField: DataViewFieldBase | undefined = getField('nestedField.child'); + if (selectedField == null) { + throw new TypeError('selectedField for this test should always be defined'); + } + + const { signal } = new AbortController(); + renderHook(() => + useFieldValueAutocomplete({ + autocompleteService: { + ...autocompleteStartMock, + getValueSuggestions: suggestionsMock, + }, + fieldValue: '', + indexPattern: stubIndexPatternWithFields, + operatorType: OperatorTypeEnum.MATCH, + query: '', + selectedField: { ...selectedField, name: 'child' }, + }) + ); + await waitFor(() => expect(suggestionsMock).toHaveBeenCalledWith({ field: { ...getField('nestedField.child'), name: 'nestedField.child' }, indexPattern: { @@ -199,63 +170,51 @@ describe('use_field_value_autocomplete', () => { query: '', signal, useTimeRange: false, - }); - }); + }) + ); }); test('returns "isSuggestingValues" of false if field type is boolean', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook< - UseFieldValueAutocompleteProps, - UseFieldValueAutocompleteReturn - >(() => - useFieldValueAutocomplete({ - autocompleteService: { - ...autocompleteStartMock, - getValueSuggestions: getValueSuggestionsMock, - }, - fieldValue: '', - indexPattern: stubIndexPatternWithFields, - operatorType: OperatorTypeEnum.MATCH, - query: '', - selectedField: getField('ssl'), - }) - ); - // Note: initial `waitForNextUpdate` is hook initialization - await waitForNextUpdate(); - await waitForNextUpdate(); + const { result } = renderHook(() => + useFieldValueAutocomplete({ + autocompleteService: { + ...autocompleteStartMock, + getValueSuggestions: getValueSuggestionsMock, + }, + fieldValue: '', + indexPattern: stubIndexPatternWithFields, + operatorType: OperatorTypeEnum.MATCH, + query: '', + selectedField: getField('ssl'), + }) + ); + await waitFor(() => { const expectedResult: UseFieldValueAutocompleteReturn = [false, false, [], result.current[3]]; - expect(getValueSuggestionsMock).not.toHaveBeenCalled(); expect(result.current).toEqual(expectedResult); + expect(getValueSuggestionsMock).not.toHaveBeenCalled(); }); }); test('returns "isSuggestingValues" of false to note that autocomplete service is not in use if no autocomplete suggestions available', async () => { const suggestionsMock = jest.fn().mockResolvedValue([]); - await act(async () => { - const { result, waitForNextUpdate } = renderHook< - UseFieldValueAutocompleteProps, - UseFieldValueAutocompleteReturn - >(() => - useFieldValueAutocomplete({ - autocompleteService: { - ...autocompleteStartMock, - getValueSuggestions: suggestionsMock, - }, - fieldValue: '', - indexPattern: stubIndexPatternWithFields, - operatorType: OperatorTypeEnum.MATCH, - query: '', - selectedField: getField('bytes'), - }) - ); - // Note: initial `waitForNextUpdate` is hook initialization - await waitForNextUpdate(); - await waitForNextUpdate(); + const { result } = renderHook(() => + useFieldValueAutocomplete({ + autocompleteService: { + ...autocompleteStartMock, + getValueSuggestions: suggestionsMock, + }, + fieldValue: '', + indexPattern: stubIndexPatternWithFields, + operatorType: OperatorTypeEnum.MATCH, + query: '', + selectedField: getField('bytes'), + }) + ); + await waitFor(() => { const expectedResult: UseFieldValueAutocompleteReturn = [false, false, [], result.current[3]]; expect(suggestionsMock).toHaveBeenCalled(); @@ -264,28 +223,22 @@ describe('use_field_value_autocomplete', () => { }); test('returns suggestions', async () => { - await act(async () => { - const { signal } = new AbortController(); - const { result, waitForNextUpdate } = renderHook< - UseFieldValueAutocompleteProps, - UseFieldValueAutocompleteReturn - >(() => - useFieldValueAutocomplete({ - autocompleteService: { - ...autocompleteStartMock, - getValueSuggestions: getValueSuggestionsMock, - }, - fieldValue: '', - indexPattern: stubIndexPatternWithFields, - operatorType: OperatorTypeEnum.MATCH, - query: '', - selectedField: getField('@tags'), - }) - ); - // Note: initial `waitForNextUpdate` is hook initialization - await waitForNextUpdate(); - await waitForNextUpdate(); + const { signal } = new AbortController(); + const { result } = renderHook(() => + useFieldValueAutocomplete({ + autocompleteService: { + ...autocompleteStartMock, + getValueSuggestions: getValueSuggestionsMock, + }, + fieldValue: '', + indexPattern: stubIndexPatternWithFields, + operatorType: OperatorTypeEnum.MATCH, + query: '', + selectedField: getField('@tags'), + }) + ); + await waitFor(() => { const expectedResult: UseFieldValueAutocompleteReturn = [ false, true, @@ -305,42 +258,34 @@ describe('use_field_value_autocomplete', () => { }); test('returns new suggestions on subsequent calls', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook< - UseFieldValueAutocompleteProps, - UseFieldValueAutocompleteReturn - >(() => - useFieldValueAutocomplete({ - autocompleteService: { - ...autocompleteStartMock, - getValueSuggestions: getValueSuggestionsMock, - }, - fieldValue: '', - indexPattern: stubIndexPatternWithFields, - operatorType: OperatorTypeEnum.MATCH, - query: '', - selectedField: getField('@tags'), - }) - ); - // Note: initial `waitForNextUpdate` is hook initialization - await waitForNextUpdate(); - await waitForNextUpdate(); - - expect(result.current[3]).not.toBeNull(); - - // Added check for typescripts sake, if null, - // would not reach below logic as test would stop above - if (result.current[3] != null) { - result.current[3]({ - fieldSelected: getField('@tags'), - patterns: stubIndexPatternWithFields, - searchQuery: '', - value: 'hello', - }); - } - - await waitForNextUpdate(); + const { result } = renderHook(() => + useFieldValueAutocomplete({ + autocompleteService: { + ...autocompleteStartMock, + getValueSuggestions: getValueSuggestionsMock, + }, + fieldValue: '', + indexPattern: stubIndexPatternWithFields, + operatorType: OperatorTypeEnum.MATCH, + query: '', + selectedField: getField('@tags'), + }) + ); + + await waitFor(() => expect(result.current[3]).not.toBeNull()); + + // Added check for typescripts sake, if null, + // would not reach below logic as test would stop above + if (result.current[3] != null) { + result.current[3]({ + fieldSelected: getField('@tags'), + patterns: stubIndexPatternWithFields, + searchQuery: '', + value: 'hello', + }); + } + await waitFor(() => { const expectedResult: UseFieldValueAutocompleteReturn = [ false, true, diff --git a/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/use_exception_item_card.test.ts b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/use_exception_item_card.test.ts index cd3e9d8ab5817..5c04c87727147 100644 --- a/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/use_exception_item_card.test.ts +++ b/packages/kbn-securitysolution-exception-list-components/src/exception_item_card/use_exception_item_card.test.ts @@ -8,7 +8,7 @@ */ import { ExceptionListTypeEnum } from '@kbn/securitysolution-io-ts-list-types'; -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { getExceptionListItemSchemaMock } from '../mocks/exception_list_item_schema.mock'; import { useExceptionItemCard } from './use_exception_item_card'; import * as i18n from './translations'; diff --git a/packages/kbn-securitysolution-exception-list-components/src/list_header/edit_modal/use_edit_modal.test.ts b/packages/kbn-securitysolution-exception-list-components/src/list_header/edit_modal/use_edit_modal.test.ts index e982637791200..65b532a55c834 100644 --- a/packages/kbn-securitysolution-exception-list-components/src/list_header/edit_modal/use_edit_modal.test.ts +++ b/packages/kbn-securitysolution-exception-list-components/src/list_header/edit_modal/use_edit_modal.test.ts @@ -8,7 +8,7 @@ */ import { ChangeEvent, SyntheticEvent } from 'react'; -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { useEditModal } from './use_edit_modal'; const listDetails = { name: 'test-name', description: 'test-description' }; diff --git a/packages/kbn-securitysolution-exception-list-components/src/list_header/use_list_header.test.ts b/packages/kbn-securitysolution-exception-list-components/src/list_header/use_list_header.test.ts index d3c17a4aefc48..e16681d2b184b 100644 --- a/packages/kbn-securitysolution-exception-list-components/src/list_header/use_list_header.test.ts +++ b/packages/kbn-securitysolution-exception-list-components/src/list_header/use_list_header.test.ts @@ -7,8 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { waitFor } from '@testing-library/react'; -import { act, renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook, act } from '@testing-library/react'; import { useExceptionListHeader } from './use_list_header'; describe('useExceptionListHeader', () => { diff --git a/packages/kbn-securitysolution-exception-list-components/src/pagination/use_pagination.test.ts b/packages/kbn-securitysolution-exception-list-components/src/pagination/use_pagination.test.ts index f05066bb61499..e90408f1e161b 100644 --- a/packages/kbn-securitysolution-exception-list-components/src/pagination/use_pagination.test.ts +++ b/packages/kbn-securitysolution-exception-list-components/src/pagination/use_pagination.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { usePagination } from './use_pagination'; const onPaginationChange = jest.fn(); diff --git a/packages/kbn-securitysolution-exception-list-components/src/value_with_space_warning/use_value_with_space_warning.test.ts b/packages/kbn-securitysolution-exception-list-components/src/value_with_space_warning/use_value_with_space_warning.test.ts index ab0c87c110dfb..51954c0140e61 100644 --- a/packages/kbn-securitysolution-exception-list-components/src/value_with_space_warning/use_value_with_space_warning.test.ts +++ b/packages/kbn-securitysolution-exception-list-components/src/value_with_space_warning/use_value_with_space_warning.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useValueWithSpaceWarning } from './use_value_with_space_warning'; describe('useValueWithSpaceWarning', () => { diff --git a/packages/kbn-securitysolution-hook-utils/src/use_async/index.test.ts b/packages/kbn-securitysolution-hook-utils/src/use_async/index.test.ts index a9f02622df306..99417c1fe3292 100644 --- a/packages/kbn-securitysolution-hook-utils/src/use_async/index.test.ts +++ b/packages/kbn-securitysolution-hook-utils/src/use_async/index.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook, act } from '@testing-library/react'; import { useAsync } from '.'; @@ -20,8 +20,8 @@ type TestReturn = Promise; describe('useAsync', () => { /** - * Timeout for both jest tests and for the waitForNextUpdate. - * jest tests default to 5 seconds and waitForNextUpdate defaults to 1 second. + * Timeout for both jest tests and for the waitFor. + * jest tests default to 5 seconds and waitFor defaults to 1 second. * 20_0000 = 20,000 milliseconds = 20 seconds */ const timeout = 20_000; @@ -42,43 +42,42 @@ describe('useAsync', () => { it( 'invokes the function when start is called', async () => { - const { result, waitForNextUpdate } = renderHook(() => useAsync(fn)); + const { result } = renderHook(() => useAsync(fn)); act(() => { result.current.start(args); }); - await waitForNextUpdate({ timeout }); - - expect(fn).toHaveBeenCalled(); + await waitFor(() => expect(fn).toHaveBeenCalled(), { timeout }); }, timeout ); it('invokes the function with start args', async () => { - const { result, waitForNextUpdate } = renderHook(() => useAsync(fn)); + const { result } = renderHook(() => useAsync(fn)); const expectedArgs = { ...args }; act(() => { result.current.start(args); }); - await waitForNextUpdate({ timeout }); - - expect(fn).toHaveBeenCalledWith(expectedArgs); + await waitFor(() => expect(fn).toHaveBeenCalledWith(expectedArgs), { timeout }); }); it( 'populates result with the resolved value of the fn', async () => { - const { result, waitForNextUpdate } = renderHook(() => useAsync(fn)); + const { result } = renderHook(() => useAsync(fn)); fn.mockResolvedValue({ resolved: 'value' }); act(() => { result.current.start(args); }); - await waitForNextUpdate({ timeout }); - - expect(result.current.result).toEqual({ resolved: 'value' }); - expect(result.current.error).toBeUndefined(); + await waitFor( + () => { + expect(result.current.result).toEqual({ resolved: 'value' }); + expect(result.current.error).toBeUndefined(); + }, + { timeout } + ); }, timeout ); @@ -87,15 +86,19 @@ describe('useAsync', () => { 'populates error if function rejects', async () => { fn.mockRejectedValue(new Error('whoops')); - const { result, waitForNextUpdate } = renderHook(() => useAsync(fn)); + const { result } = renderHook(() => useAsync(fn)); act(() => { result.current.start(args); }); - await waitForNextUpdate({ timeout }); - expect(result.current.result).toBeUndefined(); - expect(result.current.error).toEqual(new Error('whoops')); + await waitFor( + () => { + expect(result.current.result).toBeUndefined(); + expect(result.current.error).toEqual(new Error('whoops')); + }, + { timeout } + ); }, timeout ); @@ -106,7 +109,7 @@ describe('useAsync', () => { let resolve: () => void; fn.mockImplementation(() => new Promise((_resolve) => (resolve = _resolve))); - const { result, waitForNextUpdate } = renderHook(() => useAsync(fn)); + const { result } = renderHook(() => useAsync(fn)); act(() => { result.current.start(args); @@ -115,9 +118,7 @@ describe('useAsync', () => { expect(result.current.loading).toBe(true); act(() => resolve()); - await waitForNextUpdate({ timeout }); - - expect(result.current.loading).toBe(false); + await waitFor(() => expect(result.current.loading).toBe(false), { timeout }); }, timeout ); @@ -128,7 +129,7 @@ describe('useAsync', () => { let resolve: (result: string) => void; fn.mockImplementation(() => new Promise((_resolve) => (resolve = _resolve))); - const { result, waitForNextUpdate } = renderHook(() => useAsync(fn)); + const { result } = renderHook(() => useAsync(fn)); act(() => { result.current.start(args); @@ -137,10 +138,13 @@ describe('useAsync', () => { expect(result.current.loading).toBe(true); act(() => resolve('result')); - await waitForNextUpdate({ timeout }); - - expect(result.current.loading).toBe(false); - expect(result.current.result).toBe('result'); + await waitFor( + () => { + expect(result.current.loading).toBe(false); + expect(result.current.result).toBe('result'); + }, + { timeout } + ); act(() => { result.current.start(args); @@ -149,10 +153,13 @@ describe('useAsync', () => { expect(result.current.loading).toBe(true); expect(result.current.result).toBe(undefined); act(() => resolve('result')); - await waitForNextUpdate({ timeout }); - - expect(result.current.loading).toBe(false); - expect(result.current.result).toBe('result'); + await waitFor( + () => { + expect(result.current.loading).toBe(false); + expect(result.current.result).toBe('result'); + }, + { timeout } + ); }, timeout ); diff --git a/packages/kbn-securitysolution-hook-utils/src/use_is_mounted/index.test.ts b/packages/kbn-securitysolution-hook-utils/src/use_is_mounted/index.test.ts index 3435fc838382b..a8013a65441c9 100644 --- a/packages/kbn-securitysolution-hook-utils/src/use_is_mounted/index.test.ts +++ b/packages/kbn-securitysolution-hook-utils/src/use_is_mounted/index.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useIsMounted } from '.'; diff --git a/packages/kbn-securitysolution-hook-utils/src/use_observable/index.test.ts b/packages/kbn-securitysolution-hook-utils/src/use_observable/index.test.ts index d0608c5d10174..8d90bffeb3ee2 100644 --- a/packages/kbn-securitysolution-hook-utils/src/use_observable/index.test.ts +++ b/packages/kbn-securitysolution-hook-utils/src/use_observable/index.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { Subject, throwError } from 'rxjs'; import { useObservable } from '.'; diff --git a/packages/kbn-securitysolution-list-hooks/src/use_cursor/index.test.ts b/packages/kbn-securitysolution-list-hooks/src/use_cursor/index.test.ts index c4464704b7cd1..ca94eda81f950 100644 --- a/packages/kbn-securitysolution-list-hooks/src/use_cursor/index.test.ts +++ b/packages/kbn-securitysolution-list-hooks/src/use_cursor/index.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import { UseCursorProps, useCursor } from '.'; diff --git a/packages/kbn-securitysolution-list-hooks/src/use_find_lists/index.test.ts b/packages/kbn-securitysolution-list-hooks/src/use_find_lists/index.test.ts index fa68009be9d9b..fcce54ac3be32 100644 --- a/packages/kbn-securitysolution-list-hooks/src/use_find_lists/index.test.ts +++ b/packages/kbn-securitysolution-list-hooks/src/use_find_lists/index.test.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { waitFor, renderHook, act } from '@testing-library/react'; import { useFindLists } from '.'; import { httpServiceMock } from '@kbn/core-http-browser-mocks'; @@ -26,14 +26,14 @@ describe('useFindLists', () => { }); it('invokes Api.findLists', async () => { - const { result, waitForNextUpdate } = renderHook(() => useFindLists()); + const { result } = renderHook(() => useFindLists()); act(() => { result.current.start({ http: httpMock, pageIndex: 1, pageSize: 10 }); }); - await waitForNextUpdate(); - - expect(Api.findLists).toHaveBeenCalledWith( - expect.objectContaining({ http: httpMock, pageIndex: 1, pageSize: 10 }) + await waitFor(() => + expect(Api.findLists).toHaveBeenCalledWith( + expect.objectContaining({ http: httpMock, pageIndex: 1, pageSize: 10 }) + ) ); }); }); diff --git a/packages/kbn-securitysolution-list-hooks/src/use_persist_exception_item/index.ts b/packages/kbn-securitysolution-list-hooks/src/use_persist_exception_item/index.ts index e5166dc3f0f91..79f8748345fdf 100644 --- a/packages/kbn-securitysolution-list-hooks/src/use_persist_exception_item/index.ts +++ b/packages/kbn-securitysolution-list-hooks/src/use_persist_exception_item/index.ts @@ -7,7 +7,7 @@ * License v3.0 only", or the "Server Side Public License, v 1". */ -import { Dispatch, useEffect, useState } from 'react'; +import { Dispatch, useEffect, useRef, useState } from 'react'; import type { CreateExceptionListItemSchema, PersistHookProps, @@ -47,57 +47,63 @@ export const usePersistExceptionItem = ({ const [isLoading, setIsLoading] = useState(false); const isUpdateExceptionItem = (item: unknown): item is UpdateExceptionListItemSchema => Boolean(item && (item as UpdateExceptionListItemSchema).id != null); + const isSubscribed = useRef(false); useEffect(() => { - let isSubscribed = true; - const abortCtrl = new AbortController(); + let abortCtrl: AbortController | null = null; + isSubscribed.current = true; setIsSaved(false); const saveExceptionItem = async (): Promise => { - if (exceptionListItem != null) { - try { - setIsLoading(true); - - if (isUpdateExceptionItem(exceptionListItem)) { - // Please see `x-pack/plugins/lists/public/exceptions/transforms.ts` doc notes - // for context around the temporary `id` - const transformedList = transformOutput(exceptionListItem); - - await updateExceptionListItem({ - http, - listItem: transformedList, - signal: abortCtrl.signal, - }); - } else { - // Please see `x-pack/plugins/lists/public/exceptions/transforms.ts` doc notes - // for context around the temporary `id` - const transformedList = transformNewItemOutput(exceptionListItem); - - await addExceptionListItem({ - http, - listItem: transformedList, - signal: abortCtrl.signal, - }); - } - - if (isSubscribed) { - setIsSaved(true); - } - } catch (error) { - if (isSubscribed) { - onError(error); - } + if (exceptionListItem === null) { + return; + } + + try { + abortCtrl = new AbortController(); + setIsLoading(true); + + if (isUpdateExceptionItem(exceptionListItem)) { + // Please see `x-pack/plugins/lists/public/exceptions/transforms.ts` doc notes + // for context around the temporary `id` + const transformedList = transformOutput(exceptionListItem); + + await updateExceptionListItem({ + http, + listItem: transformedList, + signal: abortCtrl.signal, + }); + } else { + // Please see `x-pack/plugins/lists/public/exceptions/transforms.ts` doc notes + // for context around the temporary `id` + const transformedList = transformNewItemOutput(exceptionListItem); + + await addExceptionListItem({ + http, + listItem: transformedList, + signal: abortCtrl.signal, + }); } - if (isSubscribed) { + + if (isSubscribed.current) { + setIsSaved(true); + } + } catch (error) { + if (isSubscribed.current) { + onError(error); + } + } finally { + if (isSubscribed.current) { setIsLoading(false); } } }; saveExceptionItem(); + return (): void => { - isSubscribed = false; - abortCtrl.abort(); + isSubscribed.current = false; + abortCtrl?.abort(); }; }, [http, exceptionListItem, onError]); diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.test.tsx index 94bcf380f7086..8ddc2e80b0aa8 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.test.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.test.tsx @@ -6,7 +6,7 @@ */ import React from 'react'; -import { fireEvent, act, waitFor } from '@testing-library/react'; +import { fireEvent, waitFor } from '@testing-library/react'; import type { TestRenderer } from '../../../../../mock'; import { createFleetTestRendererMock } from '../../../../../mock'; @@ -291,23 +291,21 @@ describe('edit package policy page', () => { expect(renderResult.getByDisplayValue('Nginx description')).toBeInTheDocument(); }); - await act(async () => { - fireEvent.click(renderResult.getByText('Change defaults')); - }); + fireEvent.click(renderResult.getByText('Change defaults')); - await act(async () => { - fireEvent.change(renderResult.getByDisplayValue('/var/log/nginx/access.log*'), { - target: { value: '' }, - }); + fireEvent.change(renderResult.getByDisplayValue('/var/log/nginx/access.log*'), { + target: { value: '' }, }); - expect( - renderResult.getByText('Your integration policy has errors. Please fix them before saving.') - ).toBeInTheDocument(); - expect(renderResult.getByText(/Save integration/).closest('button')!).toBeDisabled(); + await waitFor(() => { + expect( + renderResult.getByText('Your integration policy has errors. Please fix them before saving.') + ).toBeInTheDocument(); + expect(renderResult.getByText(/Save integration/).closest('button')!).toBeDisabled(); - renderResult.getAllByRole('link', { name: 'Cancel' }).forEach((btn) => { - expect(btn).toHaveAttribute('href', '/navigate/path'); + renderResult.getAllByRole('link', { name: 'Cancel' }).forEach((btn) => { + expect(btn).toHaveAttribute('href', '/navigate/path'); + }); }); }); @@ -317,32 +315,31 @@ describe('edit package policy page', () => { await waitFor(() => { expect(renderResult.getByText('Collect logs from Nginx instances')).toBeInTheDocument(); }); - act(() => { - fireEvent.click(renderResult.getByRole('switch')); - }); - await act(async () => { - fireEvent.click(renderResult.getByText('Save integration').closest('button')!); - }); + fireEvent.click(renderResult.getByRole('switch')); - const { id, ...restProps } = mockPackagePolicy; - expect(sendUpdatePackagePolicy).toHaveBeenCalledWith('nginx-1', { - ...restProps, - vars: {}, - inputs: [ - { - ...mockPackagePolicy.inputs[0], - enabled: false, - streams: [ - { - ...mockPackagePolicy.inputs[0].streams[0], - enabled: false, - }, - ], - }, - ], + fireEvent.click(renderResult.getByText('Save integration').closest('button')!); + + await waitFor(() => { + const { id, ...restProps } = mockPackagePolicy; + expect(sendUpdatePackagePolicy).toHaveBeenCalledWith('nginx-1', { + ...restProps, + vars: {}, + inputs: [ + { + ...mockPackagePolicy.inputs[0], + enabled: false, + streams: [ + { + ...mockPackagePolicy.inputs[0].streams[0], + enabled: false, + }, + ], + }, + ], + }); + expect(useStartServices().application.navigateToUrl).toHaveBeenCalledWith('/navigate/path'); }); - expect(useStartServices().application.navigateToUrl).toHaveBeenCalledWith('/navigate/path'); }); it('should show out of date error on 409 statusCode on submit', async () => { @@ -353,23 +350,21 @@ describe('edit package policy page', () => { await waitFor(() => { expect(renderResult.getByText('Collect logs from Nginx instances')).toBeInTheDocument(); }); - act(() => { - fireEvent.click(renderResult.getByRole('switch')); - }); + fireEvent.click(renderResult.getByRole('switch')); - await act(async () => { - fireEvent.click(renderResult.getByText('Save integration').closest('button')!); - }); + fireEvent.click(renderResult.getByText('Save integration').closest('button')!); - expect(useStartServices().notifications.toasts.addError).toHaveBeenCalledWith( - { statusCode: 409 }, - { - title: "Error updating 'nginx-1'", - toastMessage: 'Data is out of date. Refresh the page to get the latest policy.', - } - ); + await waitFor(() => { + expect(useStartServices().notifications.toasts.addError).toHaveBeenCalledWith( + { statusCode: 409 }, + { + title: "Error updating 'nginx-1'", + toastMessage: 'Data is out of date. Refresh the page to get the latest policy.', + } + ); - expect(useStartServices().application.navigateToUrl).not.toHaveBeenCalled(); + expect(useStartServices().application.navigateToUrl).not.toHaveBeenCalled(); + }); }); it('should show generic error on other statusCode on submit', async () => { @@ -380,20 +375,19 @@ describe('edit package policy page', () => { await waitFor(() => { expect(renderResult.getByText('Collect logs from Nginx instances')).toBeInTheDocument(); }); - act(() => { - fireEvent.click(renderResult.getByRole('switch')); - }); - await act(async () => { - fireEvent.click(renderResult.getByText('Save integration').closest('button')!); - }); + fireEvent.click(renderResult.getByRole('switch')); + + fireEvent.click(renderResult.getByText('Save integration').closest('button')!); - expect(useStartServices().notifications.toasts.addError).toHaveBeenCalledWith( - { statusCode: 500 }, - { title: "Error updating 'nginx-1'" } - ); + await waitFor(() => { + expect(useStartServices().notifications.toasts.addError).toHaveBeenCalledWith( + { statusCode: 500 }, + { title: "Error updating 'nginx-1'" } + ); - expect(useStartServices().application.navigateToUrl).not.toHaveBeenCalled(); + expect(useStartServices().application.navigateToUrl).not.toHaveBeenCalled(); + }); }); it("throws when both 'package-policy-edit' and 'package-policy-replace-define-step' are defined", async () => { @@ -510,15 +504,12 @@ describe('edit package policy page', () => { await waitFor(() => { expect(renderResult.getByText('Collect logs from Nginx instances')).toBeInTheDocument(); }); - act(() => { - fireEvent.click(renderResult.getByRole('switch')); - }); - await act(async () => { - fireEvent.click(renderResult.getByText('Save integration').closest('button')!); - }); + fireEvent.click(renderResult.getByRole('switch')); - expect(sendUpdatePackagePolicy).toHaveBeenCalled(); + fireEvent.click(renderResult.getByText('Save integration').closest('button')!); + + await waitFor(() => expect(sendUpdatePackagePolicy).toHaveBeenCalled()); }); it('should hide the multiselect agent policies when agent policy is agentless', async () => { @@ -529,9 +520,8 @@ describe('edit package policy page', () => { isLoading: false, }); - await act(async () => { - render(); - }); + render(); + expect(renderResult.queryByTestId('agentPolicyMultiSelect')).not.toBeInTheDocument(); }); @@ -546,9 +536,7 @@ describe('edit package policy page', () => { }); it('should create agent policy with sys monitoring when new agent policy button is clicked', async () => { - await act(async () => { - render(); - }); + render(); await waitFor(() => { expect(renderResult.getByTestId('agentPolicyMultiItem')).toHaveAttribute( @@ -557,35 +545,32 @@ describe('edit package policy page', () => { ); }); - await act(async () => { - fireEvent.click(renderResult.getByTestId('createNewAgentPolicyButton')); - }); + fireEvent.click(renderResult.getByTestId('createNewAgentPolicyButton')); - await act(async () => { - fireEvent.click(renderResult.getByText(/Save integration/).closest('button')!); - }); + fireEvent.click(renderResult.getByText(/Save integration/).closest('button')!); + + fireEvent.click(renderResult.getAllByText(/Save and deploy changes/)[1].closest('button')!); - await act(async () => { - fireEvent.click(renderResult.getAllByText(/Save and deploy changes/)[1].closest('button')!); + await waitFor(() => { + expect(sendCreateAgentPolicy as jest.MockedFunction).toHaveBeenCalledWith( + { + description: '', + monitoring_enabled: ['logs', 'metrics', 'traces'], + name: 'Agent policy 2', + namespace: 'default', + inactivity_timeout: 1209600, + is_protected: false, + }, + { withSysMonitoring: true } + ); + expect(sendUpdatePackagePolicy).toHaveBeenCalledWith( + 'nginx-1', + expect.objectContaining({ + policy_ids: ['agent-policy-1', 'agent-policy-2'], + }) + ); + expect(sendGetAgentStatus).toHaveBeenCalledTimes(1); }); - expect(sendCreateAgentPolicy as jest.MockedFunction).toHaveBeenCalledWith( - { - description: '', - monitoring_enabled: ['logs', 'metrics', 'traces'], - name: 'Agent policy 2', - namespace: 'default', - inactivity_timeout: 1209600, - is_protected: false, - }, - { withSysMonitoring: true } - ); - expect(sendUpdatePackagePolicy).toHaveBeenCalledWith( - 'nginx-1', - expect.objectContaining({ - policy_ids: ['agent-policy-1', 'agent-policy-2'], - }) - ); - expect(sendGetAgentStatus).toHaveBeenCalledTimes(1); }); it('should not remove managed policy when policies are modified', async () => { @@ -613,33 +598,31 @@ describe('edit package policy page', () => { isLoading: false, }); - await act(async () => { - render(); - }); - expect(renderResult.getByTestId('agentPolicyMultiSelect')).toBeInTheDocument(); + render(); - await act(async () => { - renderResult.getByTestId('comboBoxToggleListButton').click(); + await waitFor(() => { + expect(renderResult.getByTestId('agentPolicyMultiSelect')).toBeInTheDocument(); }); - expect(renderResult.queryByText('Agent policy 1')).toBeNull(); + fireEvent.click(renderResult.getByTestId('comboBoxToggleListButton')); - await act(async () => { - fireEvent.click(renderResult.getByText('Fleet Server Policy')); - }); + await waitFor(() => expect(renderResult.queryByText('Agent policy 1')).toBeNull()); - await act(async () => { - fireEvent.click(renderResult.getByText(/Save integration/).closest('button')!); - }); - await act(async () => { - fireEvent.click(renderResult.getAllByText(/Save and deploy changes/)[1].closest('button')!); - }); + fireEvent.click(renderResult.getByText('Fleet Server Policy')); - expect(sendUpdatePackagePolicy).toHaveBeenCalledWith( - 'nginx-1', - expect.objectContaining({ - policy_ids: ['agent-policy-1', 'fleet-server-policy'], - }) + await waitFor(() => Promise.resolve(null)); + + fireEvent.click(renderResult.getByText(/Save integration/).closest('button')!); + + fireEvent.click(renderResult.getAllByText(/Save and deploy changes/)[1].closest('button')!); + + await waitFor(() => + expect(sendUpdatePackagePolicy).toHaveBeenCalledWith( + 'nginx-1', + expect.objectContaining({ + policy_ids: ['agent-policy-1', 'fleet-server-policy'], + }) + ) ); }); }); diff --git a/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_item.test.ts b/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_item.test.ts index 7cb2311c5e10a..95ca6285f0d74 100644 --- a/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_item.test.ts +++ b/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_item.test.ts @@ -5,14 +5,10 @@ * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks'; -import * as api from '@kbn/securitysolution-list-api'; -import { PersistHookProps } from '@kbn/securitysolution-io-ts-list-types'; -import { - ReturnPersistExceptionItem, - usePersistExceptionItem, -} from '@kbn/securitysolution-list-hooks'; +import { act, renderHook, waitFor } from '@testing-library/react'; import { coreMock } from '@kbn/core/public/mocks'; +import { usePersistExceptionItem } from '@kbn/securitysolution-list-hooks'; +import * as api from '@kbn/securitysolution-list-api/src/api'; import { ENTRIES_WITH_IDS } from '../../../common/constants.mock'; import { getCreateExceptionListItemSchemaMock } from '../../../common/schemas/request/create_exception_list_item_schema.mock'; @@ -20,16 +16,22 @@ import { getUpdateExceptionListItemSchemaMock } from '../../../common/schemas/re import { getExceptionListItemSchemaMock } from '../../../common/schemas/response/exception_list_item_schema.mock'; const mockKibanaHttpService = coreMock.createStart().http; -jest.mock('@kbn/securitysolution-list-api'); // TODO: Port this test over to packages/kbn-securitysolution-list-hooks/src/use_persist_exception_item/index.test.ts once the other mocks are added to the kbn package system describe('usePersistExceptionItem', () => { + let addExceptionListItemSpy: jest.SpyInstance>; + let updateExceptionListItemSpy: jest.SpyInstance>; const onError = jest.fn(); + beforeAll(() => { + addExceptionListItemSpy = jest.spyOn(api, 'addExceptionListItem'); + updateExceptionListItemSpy = jest.spyOn(api, 'updateExceptionListItem'); + }); + beforeEach(() => { - jest.spyOn(api, 'addExceptionListItem').mockResolvedValue(getExceptionListItemSchemaMock()); - jest.spyOn(api, 'updateExceptionListItem').mockResolvedValue(getExceptionListItemSchemaMock()); + addExceptionListItemSpy.mockResolvedValue(getExceptionListItemSchemaMock()); + updateExceptionListItemSpy.mockResolvedValue(getExceptionListItemSchemaMock()); }); afterEach(() => { @@ -37,7 +39,7 @@ describe('usePersistExceptionItem', () => { }); test('initializes hook', async () => { - const { result } = renderHook(() => + const { result } = renderHook(() => usePersistExceptionItem({ http: mockKibanaHttpService, onError }) ); @@ -45,101 +47,106 @@ describe('usePersistExceptionItem', () => { }); test('"isLoading" is "true" when exception item is being saved', async () => { - await act(async () => { - const { result, rerender, waitForNextUpdate } = renderHook< - PersistHookProps, - ReturnPersistExceptionItem - >(() => usePersistExceptionItem({ http: mockKibanaHttpService, onError })); + const { result } = renderHook(() => + usePersistExceptionItem({ http: mockKibanaHttpService, onError }) + ); - await waitForNextUpdate(); + await waitFor(() => + expect(result.current).toEqual([{ isLoading: false, isSaved: false }, result.current[1]]) + ); + + act(() => { result.current[1](getCreateExceptionListItemSchemaMock()); - rerender(); + }); - expect(result.current).toEqual([{ isLoading: true, isSaved: false }, result.current[1]]); + expect(result.current).toEqual([{ isLoading: true, isSaved: false }, result.current[1]]); + + await waitFor(() => { + expect(addExceptionListItemSpy).toHaveBeenCalled(); + expect(result.current).toEqual([{ isLoading: false, isSaved: true }, result.current[1]]); }); }); test('"isSaved" is "true" when exception item saved successfully', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook< - PersistHookProps, - ReturnPersistExceptionItem - >(() => usePersistExceptionItem({ http: mockKibanaHttpService, onError })); + const { result } = renderHook(() => + usePersistExceptionItem({ http: mockKibanaHttpService, onError }) + ); - await waitForNextUpdate(); + act(() => { result.current[1](getCreateExceptionListItemSchemaMock()); - await waitForNextUpdate(); - - expect(result.current).toEqual([{ isLoading: false, isSaved: true }, result.current[1]]); }); + + await waitFor(() => + expect(result.current).toEqual([{ isLoading: false, isSaved: true }, result.current[1]]) + ); }); test('it invokes "updateExceptionListItem" when payload has "id"', async () => { - const addExceptionListItem = jest.spyOn(api, 'addExceptionListItem'); - const updateExceptionListItem = jest.spyOn(api, 'updateExceptionListItem'); - await act(async () => { - const { result, waitForNextUpdate } = renderHook< - PersistHookProps, - ReturnPersistExceptionItem - >(() => usePersistExceptionItem({ http: mockKibanaHttpService, onError })); - - await waitForNextUpdate(); + const { result } = renderHook(() => + usePersistExceptionItem({ http: mockKibanaHttpService, onError }) + ); + + await waitFor(() => new Promise((resolve) => resolve(null))); + + act(() => { // NOTE: Take note here passing in an exception item where it's // entries have been enriched with ids to ensure that they get stripped // before the call goes through result.current[1]({ ...getUpdateExceptionListItemSchemaMock(), entries: ENTRIES_WITH_IDS }); - await waitForNextUpdate(); + }); + await waitFor(() => { expect(result.current).toEqual([{ isLoading: false, isSaved: true }, result.current[1]]); - expect(addExceptionListItem).not.toHaveBeenCalled(); - expect(updateExceptionListItem).toHaveBeenCalledWith({ - http: mockKibanaHttpService, - listItem: getUpdateExceptionListItemSchemaMock(), - signal: new AbortController().signal, - }); + }); + + expect(addExceptionListItemSpy).not.toHaveBeenCalled(); + expect(updateExceptionListItemSpy).toHaveBeenCalledWith({ + http: mockKibanaHttpService, + listItem: getUpdateExceptionListItemSchemaMock(), + signal: expect.any(AbortSignal), }); }); test('it invokes "addExceptionListItem" when payload does not have "id"', async () => { - const updateExceptionListItem = jest.spyOn(api, 'updateExceptionListItem'); - const addExceptionListItem = jest.spyOn(api, 'addExceptionListItem'); - await act(async () => { - const { result, waitForNextUpdate } = renderHook< - PersistHookProps, - ReturnPersistExceptionItem - >(() => usePersistExceptionItem({ http: mockKibanaHttpService, onError })); - - await waitForNextUpdate(); + const { result } = renderHook(() => + usePersistExceptionItem({ http: mockKibanaHttpService, onError }) + ); + + await waitFor(() => new Promise((resolve) => resolve(null))); + + act(() => { // NOTE: Take note here passing in an exception item where it's // entries have been enriched with ids to ensure that they get stripped // before the call goes through result.current[1]({ ...getCreateExceptionListItemSchemaMock(), entries: ENTRIES_WITH_IDS }); - await waitForNextUpdate(); + }); + await waitFor(() => { expect(result.current).toEqual([{ isLoading: false, isSaved: true }, result.current[1]]); - expect(updateExceptionListItem).not.toHaveBeenCalled(); - expect(addExceptionListItem).toHaveBeenCalledWith({ - http: mockKibanaHttpService, - listItem: getCreateExceptionListItemSchemaMock(), - signal: new AbortController().signal, - }); + }); + + expect(updateExceptionListItemSpy).not.toHaveBeenCalled(); + expect(addExceptionListItemSpy).toHaveBeenCalledWith({ + http: mockKibanaHttpService, + listItem: getCreateExceptionListItemSchemaMock(), + signal: expect.any(AbortSignal), }); }); test('"onError" callback is invoked and "isSaved" is "false" when api call fails', async () => { const error = new Error('persist rule failed'); - jest.spyOn(api, 'addExceptionListItem').mockRejectedValue(error); - await act(async () => { - const { result, waitForNextUpdate } = renderHook< - PersistHookProps, - ReturnPersistExceptionItem - >(() => usePersistExceptionItem({ http: mockKibanaHttpService, onError })); + addExceptionListItemSpy.mockRejectedValue(error); - await waitForNextUpdate(); - result.current[1](getCreateExceptionListItemSchemaMock()); - await waitForNextUpdate(); + const { result } = renderHook(() => + usePersistExceptionItem({ http: mockKibanaHttpService, onError }) + ); + await waitFor(() => new Promise((resolve) => resolve(null))); + act(() => { + result.current[1](getCreateExceptionListItemSchemaMock()); + }); + await waitFor(() => { expect(result.current).toEqual([{ isLoading: false, isSaved: false }, result.current[1]]); expect(onError).toHaveBeenCalledWith(error); }); diff --git a/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_list.test.ts b/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_list.test.ts index 09257c489a895..c27888b6d8cc6 100644 --- a/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_list.test.ts +++ b/x-pack/plugins/lists/public/exceptions/hooks/persist_exception_list.test.ts @@ -5,13 +5,9 @@ * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { act, renderHook, waitFor } from '@testing-library/react'; import * as api from '@kbn/securitysolution-list-api'; -import { PersistHookProps } from '@kbn/securitysolution-io-ts-list-types'; -import { - ReturnPersistExceptionList, - usePersistExceptionList, -} from '@kbn/securitysolution-list-hooks'; +import { usePersistExceptionList } from '@kbn/securitysolution-list-hooks'; import { coreMock } from '@kbn/core/public/mocks'; import { getCreateExceptionListSchemaMock } from '../../../common/schemas/request/create_exception_list_schema.mock'; @@ -37,7 +33,7 @@ describe('usePersistExceptionList', () => { }); test('initializes hook', async () => { - const { result } = renderHook(() => + const { result } = renderHook(() => usePersistExceptionList({ http: mockKibanaHttpService, onError }) ); @@ -45,46 +41,48 @@ describe('usePersistExceptionList', () => { }); test('"isLoading" is "true" when exception item is being saved', async () => { - await act(async () => { - const { result, rerender, waitForNextUpdate } = renderHook< - PersistHookProps, - ReturnPersistExceptionList - >(() => usePersistExceptionList({ http: mockKibanaHttpService, onError })); - await waitForNextUpdate(); + const { result, rerender } = renderHook(() => + usePersistExceptionList({ http: mockKibanaHttpService, onError }) + ); + await waitFor(() => new Promise((resolve) => resolve(null))); + act(() => { result.current[1](getCreateExceptionListSchemaMock()); - rerender(); - - expect(result.current).toEqual([{ isLoading: true, isSaved: false }, result.current[1]]); }); + rerender(); + + expect(result.current).toEqual([{ isLoading: true, isSaved: false }, result.current[1]]); }); test('"isSaved" is "true" when exception item saved successfully', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook< - PersistHookProps, - ReturnPersistExceptionList - >(() => usePersistExceptionList({ http: mockKibanaHttpService, onError })); - await waitForNextUpdate(); - result.current[1](getCreateExceptionListSchemaMock()); - await waitForNextUpdate(); + const { result } = renderHook(() => + usePersistExceptionList({ http: mockKibanaHttpService, onError }) + ); - expect(result.current).toEqual([{ isLoading: false, isSaved: true }, result.current[1]]); + await waitFor(() => new Promise((resolve) => resolve(null))); + + act(() => { + result.current[1](getCreateExceptionListSchemaMock()); }); + + await waitFor(() => + expect(result.current).toEqual([{ isLoading: false, isSaved: true }, result.current[1]]) + ); }); test('it invokes "updateExceptionList" when payload has "id"', async () => { const addException = jest.spyOn(api, 'addExceptionList'); const updateException = jest.spyOn(api, 'updateExceptionList'); - await act(async () => { - const { result, waitForNextUpdate } = renderHook< - PersistHookProps, - ReturnPersistExceptionList - >(() => usePersistExceptionList({ http: mockKibanaHttpService, onError })); + const { result } = renderHook(() => + usePersistExceptionList({ http: mockKibanaHttpService, onError }) + ); + + await waitFor(() => new Promise((resolve) => resolve(null))); - await waitForNextUpdate(); + act(() => { result.current[1](getUpdateExceptionListSchemaMock()); - await waitForNextUpdate(); + }); + await waitFor(() => { expect(result.current).toEqual([{ isLoading: false, isSaved: true }, result.current[1]]); expect(addException).not.toHaveBeenCalled(); expect(updateException).toHaveBeenCalled(); @@ -95,15 +93,14 @@ describe('usePersistExceptionList', () => { const error = new Error('persist rule failed'); jest.spyOn(api, 'addExceptionList').mockRejectedValue(error); - await act(async () => { - const { result, waitForNextUpdate } = renderHook< - PersistHookProps, - ReturnPersistExceptionList - >(() => usePersistExceptionList({ http: mockKibanaHttpService, onError })); - await waitForNextUpdate(); + const { result } = renderHook(() => + usePersistExceptionList({ http: mockKibanaHttpService, onError }) + ); + await waitFor(() => new Promise((resolve) => resolve(null))); + act(() => { result.current[1](getCreateExceptionListSchemaMock()); - await waitForNextUpdate(); - + }); + await waitFor(() => { expect(result.current).toEqual([{ isLoading: false, isSaved: false }, result.current[1]]); expect(onError).toHaveBeenCalledWith(error); }); diff --git a/x-pack/plugins/lists/public/exceptions/hooks/use_api.test.ts b/x-pack/plugins/lists/public/exceptions/hooks/use_api.test.ts index 55c8ebde7cebd..a980261ba5f86 100644 --- a/x-pack/plugins/lists/public/exceptions/hooks/use_api.test.ts +++ b/x-pack/plugins/lists/public/exceptions/hooks/use_api.test.ts @@ -5,9 +5,9 @@ * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { act, renderHook, waitFor } from '@testing-library/react'; import * as api from '@kbn/securitysolution-list-api'; -import { ExceptionsApi, useApi } from '@kbn/securitysolution-list-hooks'; +import { useApi } from '@kbn/securitysolution-list-hooks'; import type { AddExceptionListItemProps, ApiCallByIdProps, @@ -16,7 +16,6 @@ import type { UpdateExceptionListItemProps, } from '@kbn/securitysolution-io-ts-list-types'; import { coreMock } from '@kbn/core/public/mocks'; -import { HttpStart } from '@kbn/core/public'; import { ENTRIES_WITH_IDS } from '../../../common/constants.mock'; import { getUpdateExceptionListItemSchemaMock } from '../../../common/schemas/request/update_exception_list_item_schema.mock'; @@ -51,54 +50,50 @@ describe('useApi', () => { .spyOn(api, 'deleteExceptionListItemById') .mockResolvedValue(payload); - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useApi(mockKibanaHttpService) - ); - await waitForNextUpdate(); + const { result } = renderHook(() => useApi(mockKibanaHttpService)); + await waitFor(() => new Promise((resolve) => resolve(null))); - const { id, namespace_type: namespaceType } = payload; + const { id, namespace_type: namespaceType } = payload; + await act(async () => { await result.current.deleteExceptionItem({ id, namespaceType, onError: jest.fn(), onSuccess: onSuccessMock, }); + }); - const expected: ApiCallByIdProps = { - http: mockKibanaHttpService, - id, - namespaceType, - signal: new AbortController().signal, - }; + const expected: ApiCallByIdProps = { + http: mockKibanaHttpService, + id, + namespaceType, + signal: new AbortController().signal, + }; - expect(spyOnDeleteExceptionListItemById).toHaveBeenCalledWith(expected); - expect(onSuccessMock).toHaveBeenCalled(); - }); + expect(spyOnDeleteExceptionListItemById).toHaveBeenCalledWith(expected); + expect(onSuccessMock).toHaveBeenCalled(); }); test('invokes "onError" callback if "deleteExceptionListItemById" fails', async () => { const mockError = new Error('failed to delete item'); jest.spyOn(api, 'deleteExceptionListItemById').mockRejectedValue(mockError); - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useApi(mockKibanaHttpService) - ); - await waitForNextUpdate(); + const { result } = renderHook(() => useApi(mockKibanaHttpService)); + await waitFor(() => new Promise((resolve) => resolve(null))); - const { id, namespace_type: namespaceType } = getExceptionListItemSchemaMock(); + const { id, namespace_type: namespaceType } = getExceptionListItemSchemaMock(); + await act(async () => { await result.current.deleteExceptionItem({ id, namespaceType, onError: onErrorMock, onSuccess: jest.fn(), }); - - expect(onErrorMock).toHaveBeenCalledWith(mockError); }); + + expect(onErrorMock).toHaveBeenCalledWith(mockError); }); }); @@ -110,54 +105,50 @@ describe('useApi', () => { .spyOn(api, 'deleteExceptionListById') .mockResolvedValue(payload); - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useApi(mockKibanaHttpService) - ); - await waitForNextUpdate(); + const { result } = renderHook(() => useApi(mockKibanaHttpService)); + await waitFor(() => new Promise((resolve) => resolve(null))); - const { id, namespace_type: namespaceType } = payload; + const { id, namespace_type: namespaceType } = payload; + await act(async () => { await result.current.deleteExceptionList({ id, namespaceType, onError: jest.fn(), onSuccess: onSuccessMock, }); + }); - const expected: ApiCallByIdProps = { - http: mockKibanaHttpService, - id, - namespaceType, - signal: new AbortController().signal, - }; + const expected: ApiCallByIdProps = { + http: mockKibanaHttpService, + id, + namespaceType, + signal: new AbortController().signal, + }; - expect(spyOnDeleteExceptionListById).toHaveBeenCalledWith(expected); - expect(onSuccessMock).toHaveBeenCalled(); - }); + expect(spyOnDeleteExceptionListById).toHaveBeenCalledWith(expected); + expect(onSuccessMock).toHaveBeenCalled(); }); test('invokes "onError" callback if "deleteExceptionListById" fails', async () => { const mockError = new Error('failed to delete item'); jest.spyOn(api, 'deleteExceptionListById').mockRejectedValue(mockError); - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useApi(mockKibanaHttpService) - ); - await waitForNextUpdate(); + const { result } = renderHook(() => useApi(mockKibanaHttpService)); + await waitFor(() => new Promise((resolve) => resolve(null))); - const { id, namespace_type: namespaceType } = getExceptionListSchemaMock(); + const { id, namespace_type: namespaceType } = getExceptionListSchemaMock(); + await act(async () => { await result.current.deleteExceptionList({ id, namespaceType, onError: onErrorMock, onSuccess: jest.fn(), }); - - expect(onErrorMock).toHaveBeenCalledWith(mockError); }); + + expect(onErrorMock).toHaveBeenCalledWith(mockError); }); }); @@ -169,58 +160,54 @@ describe('useApi', () => { .spyOn(api, 'fetchExceptionListItemById') .mockResolvedValue(payload); - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useApi(mockKibanaHttpService) - ); - await waitForNextUpdate(); + const { result } = renderHook(() => useApi(mockKibanaHttpService)); + await waitFor(() => new Promise((resolve) => resolve(null))); - const { id, namespace_type: namespaceType } = payload; + const { id, namespace_type: namespaceType } = payload; + await act(async () => { await result.current.getExceptionItem({ id, namespaceType, onError: jest.fn(), onSuccess: onSuccessMock, }); - - const expected: ApiCallByIdProps = { - http: mockKibanaHttpService, - id, - namespaceType, - signal: new AbortController().signal, - }; - const expectedExceptionListItem = { - ...getExceptionListItemSchemaMock(), - entries: ENTRIES_WITH_IDS, - }; - - expect(spyOnFetchExceptionListItemById).toHaveBeenCalledWith(expected); - expect(onSuccessMock).toHaveBeenCalledWith(expectedExceptionListItem); }); + + const expected: ApiCallByIdProps = { + http: mockKibanaHttpService, + id, + namespaceType, + signal: new AbortController().signal, + }; + const expectedExceptionListItem = { + ...getExceptionListItemSchemaMock(), + entries: ENTRIES_WITH_IDS, + }; + + expect(spyOnFetchExceptionListItemById).toHaveBeenCalledWith(expected); + expect(onSuccessMock).toHaveBeenCalledWith(expectedExceptionListItem); }); test('invokes "onError" callback if "fetchExceptionListItemById" fails', async () => { const mockError = new Error('failed to delete item'); jest.spyOn(api, 'fetchExceptionListItemById').mockRejectedValue(mockError); - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useApi(mockKibanaHttpService) - ); - await waitForNextUpdate(); + const { result } = renderHook(() => useApi(mockKibanaHttpService)); + await waitFor(() => new Promise((resolve) => resolve(null))); - const { id, namespace_type: namespaceType } = getExceptionListSchemaMock(); + const { id, namespace_type: namespaceType } = getExceptionListSchemaMock(); + await act(async () => { await result.current.getExceptionItem({ id, namespaceType, onError: onErrorMock, onSuccess: jest.fn(), }); - - expect(onErrorMock).toHaveBeenCalledWith(mockError); }); + + expect(onErrorMock).toHaveBeenCalledWith(mockError); }); }); @@ -232,54 +219,50 @@ describe('useApi', () => { .spyOn(api, 'fetchExceptionListById') .mockResolvedValue(payload); - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useApi(mockKibanaHttpService) - ); - await waitForNextUpdate(); + const { result } = renderHook(() => useApi(mockKibanaHttpService)); + await waitFor(() => new Promise((resolve) => resolve(null))); - const { id, namespace_type: namespaceType } = payload; + const { id, namespace_type: namespaceType } = payload; + await act(async () => { await result.current.getExceptionList({ id, namespaceType, onError: jest.fn(), onSuccess: onSuccessMock, }); + }); - const expected: ApiCallByIdProps = { - http: mockKibanaHttpService, - id, - namespaceType, - signal: new AbortController().signal, - }; + const expected: ApiCallByIdProps = { + http: mockKibanaHttpService, + id, + namespaceType, + signal: new AbortController().signal, + }; - expect(spyOnFetchExceptionListById).toHaveBeenCalledWith(expected); - expect(onSuccessMock).toHaveBeenCalled(); - }); + expect(spyOnFetchExceptionListById).toHaveBeenCalledWith(expected); + expect(onSuccessMock).toHaveBeenCalled(); }); test('invokes "onError" callback if "fetchExceptionListById" fails', async () => { const mockError = new Error('failed to delete item'); jest.spyOn(api, 'fetchExceptionListById').mockRejectedValue(mockError); - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useApi(mockKibanaHttpService) - ); - await waitForNextUpdate(); + const { result } = renderHook(() => useApi(mockKibanaHttpService)); + await waitFor(() => new Promise((resolve) => resolve(null))); - const { id, namespace_type: namespaceType } = getExceptionListSchemaMock(); + const { id, namespace_type: namespaceType } = getExceptionListSchemaMock(); + await act(async () => { await result.current.getExceptionList({ id, namespaceType, onError: onErrorMock, onSuccess: jest.fn(), }); - - expect(onErrorMock).toHaveBeenCalledWith(mockError); }); + + expect(onErrorMock).toHaveBeenCalledWith(mockError); }); }); @@ -291,12 +274,10 @@ describe('useApi', () => { .spyOn(api, 'fetchExceptionListsItemsByListIds') .mockResolvedValue(output); - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useApi(mockKibanaHttpService) - ); - await waitForNextUpdate(); + const { result } = renderHook(() => useApi(mockKibanaHttpService)); + await waitFor(() => new Promise((resolve) => resolve(null))); + await act(async () => { await result.current.getExceptionListsItems({ lists: [ { id: 'myListId', listId: 'list_id', namespaceType: 'single', type: 'detection' }, @@ -311,28 +292,28 @@ describe('useApi', () => { showDetectionsListsOnly: false, showEndpointListsOnly: false, }); + }); - const expected: ApiCallByListIdProps = { - http: mockKibanaHttpService, - listIds: ['list_id'], - namespaceTypes: ['single'], - pagination: { - page: 1, - perPage: 1, - total: 0, - }, - signal: new AbortController().signal, - }; - - expect(spyOnFetchExceptionListsItemsByListIds).toHaveBeenCalledWith(expected); - expect(onSuccessMock).toHaveBeenCalledWith({ - exceptions: [{ ...getExceptionListItemSchemaMock(), entries: ENTRIES_WITH_IDS }], - pagination: { - page: 1, - perPage: 1, - total: 1, - }, - }); + const expected: ApiCallByListIdProps = { + http: mockKibanaHttpService, + listIds: ['list_id'], + namespaceTypes: ['single'], + pagination: { + page: 1, + perPage: 1, + total: 0, + }, + signal: new AbortController().signal, + }; + + expect(spyOnFetchExceptionListsItemsByListIds).toHaveBeenCalledWith(expected); + expect(onSuccessMock).toHaveBeenCalledWith({ + exceptions: [{ ...getExceptionListItemSchemaMock(), entries: ENTRIES_WITH_IDS }], + pagination: { + page: 1, + perPage: 1, + total: 1, + }, }); }); @@ -343,12 +324,10 @@ describe('useApi', () => { .spyOn(api, 'fetchExceptionListsItemsByListIds') .mockResolvedValue(output); - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useApi(mockKibanaHttpService) - ); - await waitForNextUpdate(); + const { result } = renderHook(() => useApi(mockKibanaHttpService)); + await waitFor(() => new Promise((resolve) => resolve(null))); + await act(async () => { await result.current.getExceptionListsItems({ lists: [ { id: 'myListId', listId: 'list_id', namespaceType: 'single', type: 'detection' }, @@ -363,16 +342,16 @@ describe('useApi', () => { showDetectionsListsOnly: false, showEndpointListsOnly: true, }); + }); - expect(spyOnFetchExceptionListsItemsByListIds).not.toHaveBeenCalled(); - expect(onSuccessMock).toHaveBeenCalledWith({ - exceptions: [], - pagination: { - page: 0, - perPage: 20, - total: 0, - }, - }); + expect(spyOnFetchExceptionListsItemsByListIds).not.toHaveBeenCalled(); + expect(onSuccessMock).toHaveBeenCalledWith({ + exceptions: [], + pagination: { + page: 0, + perPage: 20, + total: 0, + }, }); }); @@ -380,12 +359,10 @@ describe('useApi', () => { const mockError = new Error('failed to delete item'); jest.spyOn(api, 'fetchExceptionListsItemsByListIds').mockRejectedValue(mockError); - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useApi(mockKibanaHttpService) - ); - await waitForNextUpdate(); + const { result } = renderHook(() => useApi(mockKibanaHttpService)); + await waitFor(() => new Promise((resolve) => resolve(null))); + await act(async () => { await result.current.getExceptionListsItems({ lists: [ { id: 'myListId', listId: 'list_id', namespaceType: 'single', type: 'detection' }, @@ -400,9 +377,9 @@ describe('useApi', () => { showDetectionsListsOnly: false, showEndpointListsOnly: false, }); - - expect(onErrorMock).toHaveBeenCalledWith(mockError); }); + + expect(onErrorMock).toHaveBeenCalledWith(mockError); }); }); @@ -414,24 +391,22 @@ describe('useApi', () => { .spyOn(api, 'addExceptionListItem') .mockResolvedValue(payload); - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useApi(mockKibanaHttpService) - ); - await waitForNextUpdate(); + const { result } = renderHook(() => useApi(mockKibanaHttpService)); + await waitFor(() => new Promise((resolve) => resolve(null))); + await act(async () => { await result.current.addExceptionListItem({ listItem: itemToCreate, }); + }); - const expected: AddExceptionListItemProps = { - http: mockKibanaHttpService, - listItem: getCreateExceptionListItemSchemaMock(), - signal: new AbortController().signal, - }; + const expected: AddExceptionListItemProps = { + http: mockKibanaHttpService, + listItem: getCreateExceptionListItemSchemaMock(), + signal: new AbortController().signal, + }; - expect(spyOnFetchExceptionListItemById).toHaveBeenCalledWith(expected); - }); + expect(spyOnFetchExceptionListItemById).toHaveBeenCalledWith(expected); }); }); @@ -443,24 +418,22 @@ describe('useApi', () => { .spyOn(api, 'updateExceptionListItem') .mockResolvedValue(payload); - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useApi(mockKibanaHttpService) - ); - await waitForNextUpdate(); + const { result } = renderHook(() => useApi(mockKibanaHttpService)); + await waitFor(() => new Promise((resolve) => resolve(null))); + await act(async () => { await result.current.updateExceptionListItem({ listItem: itemToUpdate, }); + }); - const expected: UpdateExceptionListItemProps = { - http: mockKibanaHttpService, - listItem: getUpdateExceptionListItemSchemaMock(), - signal: new AbortController().signal, - }; + const expected: UpdateExceptionListItemProps = { + http: mockKibanaHttpService, + listItem: getUpdateExceptionListItemSchemaMock(), + signal: new AbortController().signal, + }; - expect(spyOnUpdateExceptionListItem).toHaveBeenCalledWith(expected); - }); + expect(spyOnUpdateExceptionListItem).toHaveBeenCalledWith(expected); }); }); @@ -471,12 +444,10 @@ describe('useApi', () => { .spyOn(api, 'duplicateExceptionList') .mockResolvedValue(getExceptionListSchemaMock()); - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useApi(mockKibanaHttpService) - ); - await waitForNextUpdate(); + const { result } = renderHook(() => useApi(mockKibanaHttpService)); + await waitFor(() => new Promise((resolve) => resolve(null))); + await act(async () => { await result.current.duplicateExceptionList({ includeExpiredExceptions: false, listId: 'my_list', @@ -484,30 +455,28 @@ describe('useApi', () => { onError: jest.fn(), onSuccess: onSuccessMock, }); + }); - const expected: DuplicateExceptionListProps = { - http: mockKibanaHttpService, - includeExpiredExceptions: false, - listId: 'my_list', - namespaceType: 'single', - signal: new AbortController().signal, - }; + const expected: DuplicateExceptionListProps = { + http: mockKibanaHttpService, + includeExpiredExceptions: false, + listId: 'my_list', + namespaceType: 'single', + signal: new AbortController().signal, + }; - expect(spyOnDuplicateExceptionList).toHaveBeenCalledWith(expected); - expect(onSuccessMock).toHaveBeenCalled(); - }); + expect(spyOnDuplicateExceptionList).toHaveBeenCalledWith(expected); + expect(onSuccessMock).toHaveBeenCalled(); }); test('invokes "onError" callback if "duplicateExceptionList" fails', async () => { const mockError = new Error('failed to duplicate item'); jest.spyOn(api, 'duplicateExceptionList').mockRejectedValue(mockError); - await act(async () => { - const { result, waitForNextUpdate } = renderHook(() => - useApi(mockKibanaHttpService) - ); - await waitForNextUpdate(); + const { result } = renderHook(() => useApi(mockKibanaHttpService)); + await waitFor(() => new Promise((resolve) => resolve(null))); + await act(async () => { await result.current.duplicateExceptionList({ includeExpiredExceptions: false, listId: 'my_list', @@ -515,9 +484,9 @@ describe('useApi', () => { onError: onErrorMock, onSuccess: jest.fn(), }); - - expect(onErrorMock).toHaveBeenCalledWith(mockError); }); + + expect(onErrorMock).toHaveBeenCalledWith(mockError); }); }); }); diff --git a/x-pack/plugins/lists/public/exceptions/hooks/use_exception_lists.test.ts b/x-pack/plugins/lists/public/exceptions/hooks/use_exception_lists.test.ts index 0148feff83d58..6c684d199e538 100644 --- a/x-pack/plugins/lists/public/exceptions/hooks/use_exception_lists.test.ts +++ b/x-pack/plugins/lists/public/exceptions/hooks/use_exception_lists.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, waitFor } from '@testing-library/react'; import type { ExceptionListSchema, UseExceptionListsProps, @@ -32,66 +32,53 @@ describe('useExceptionLists', () => { }); test('initializes hook', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook< - UseExceptionListsProps, - ReturnExceptionLists - >(() => - useExceptionLists({ - errorMessage: 'Uh oh', - filterOptions: {}, - http: mockKibanaHttpService, - initialPagination: { - page: 1, - perPage: 20, - total: 0, - }, - namespaceTypes: ['single', 'agnostic'], - notifications: mockKibanaNotificationsService, - }) - ); - await waitForNextUpdate(); - - expect(result.current).toEqual([ - true, - [], - { + const { result } = renderHook(() => + useExceptionLists({ + errorMessage: 'Uh oh', + filterOptions: {}, + http: mockKibanaHttpService, + initialPagination: { page: 1, perPage: 20, total: 0, }, - expect.any(Function), - expect.any(Function), - { field: 'created_at', order: 'desc' }, - expect.any(Function), - ]); - }); + namespaceTypes: ['single', 'agnostic'], + notifications: mockKibanaNotificationsService, + }) + ); + + expect(result.current).toEqual([ + true, + [], + { + page: 1, + perPage: 20, + total: 0, + }, + expect.any(Function), + expect.any(Function), + { field: 'created_at', order: 'desc' }, + expect.any(Function), + ]); }); test('fetches exception lists', async () => { - await act(async () => { - const { result, waitForNextUpdate } = renderHook< - UseExceptionListsProps, - ReturnExceptionLists - >(() => - useExceptionLists({ - errorMessage: 'Uh oh', - filterOptions: {}, - http: mockKibanaHttpService, - initialPagination: { - page: 1, - perPage: 20, - total: 0, - }, - namespaceTypes: ['single', 'agnostic'], - notifications: mockKibanaNotificationsService, - }) - ); - // NOTE: First `waitForNextUpdate` is initialization - // Second call applies the params - await waitForNextUpdate(); - await waitForNextUpdate(); + const { result } = renderHook(() => + useExceptionLists({ + errorMessage: 'Uh oh', + filterOptions: {}, + http: mockKibanaHttpService, + initialPagination: { + page: 1, + perPage: 20, + total: 0, + }, + namespaceTypes: ['single', 'agnostic'], + notifications: mockKibanaNotificationsService, + }) + ); + await waitFor(() => { const expectedListItemsResult: ExceptionListSchema[] = getFoundExceptionListSchemaMock().data; expect(result.current).toEqual([ @@ -113,27 +100,23 @@ describe('useExceptionLists', () => { test('does not fetch specific list id if it is added to the hideLists array', async () => { const spyOnfetchExceptionLists = jest.spyOn(api, 'fetchExceptionLists'); - await act(async () => { - const { waitForNextUpdate } = renderHook(() => - useExceptionLists({ - errorMessage: 'Uh oh', - filterOptions: {}, - hideLists: ['listId-1'], - http: mockKibanaHttpService, - initialPagination: { - page: 1, - perPage: 20, - total: 0, - }, - namespaceTypes: ['single', 'agnostic'], - notifications: mockKibanaNotificationsService, - }) - ); - // NOTE: First `waitForNextUpdate` is initialization - // Second call applies the params - await waitForNextUpdate(); - await waitForNextUpdate(); + renderHook(() => + useExceptionLists({ + errorMessage: 'Uh oh', + filterOptions: {}, + hideLists: ['listId-1'], + http: mockKibanaHttpService, + initialPagination: { + page: 1, + perPage: 20, + total: 0, + }, + namespaceTypes: ['single', 'agnostic'], + notifications: mockKibanaNotificationsService, + }) + ); + await waitFor(() => expect(spyOnfetchExceptionLists).toHaveBeenCalledWith({ filters: '(not exception-list.attributes.list_id: listId-1* AND not exception-list-agnostic.attributes.list_id: listId-1*)', @@ -142,37 +125,33 @@ describe('useExceptionLists', () => { pagination: { page: 1, perPage: 20 }, signal: new AbortController().signal, sort: { field: 'created_at', order: 'desc' }, - }); - }); + }) + ); }); test('applies filters to query', async () => { const spyOnfetchExceptionLists = jest.spyOn(api, 'fetchExceptionLists'); - await act(async () => { - const { waitForNextUpdate } = renderHook(() => - useExceptionLists({ - errorMessage: 'Uh oh', - filterOptions: { - created_by: 'Moi', - name: 'Sample Endpoint', - }, - hideLists: ['listId-1'], - http: mockKibanaHttpService, - initialPagination: { - page: 1, - perPage: 20, - total: 0, - }, - namespaceTypes: ['single', 'agnostic'], - notifications: mockKibanaNotificationsService, - }) - ); - // NOTE: First `waitForNextUpdate` is initialization - // Second call applies the params - await waitForNextUpdate(); - await waitForNextUpdate(); + renderHook(() => + useExceptionLists({ + errorMessage: 'Uh oh', + filterOptions: { + created_by: 'Moi', + name: 'Sample Endpoint', + }, + hideLists: ['listId-1'], + http: mockKibanaHttpService, + initialPagination: { + page: 1, + perPage: 20, + total: 0, + }, + namespaceTypes: ['single', 'agnostic'], + notifications: mockKibanaNotificationsService, + }) + ); + await waitFor(() => expect(spyOnfetchExceptionLists).toHaveBeenCalledWith({ filters: '(exception-list.attributes.created_by:Moi OR exception-list-agnostic.attributes.created_by:Moi) AND (exception-list.attributes.name.text:Sample Endpoint OR exception-list-agnostic.attributes.name.text:Sample Endpoint) AND (not exception-list.attributes.list_id: listId-1* AND not exception-list-agnostic.attributes.list_id: listId-1*)', @@ -184,47 +163,60 @@ describe('useExceptionLists', () => { field: 'created_at', order: 'desc', }, - }); - }); + }) + ); }); test('fetches a new exception list and its items when props change', async () => { const spyOnfetchExceptionLists = jest.spyOn(api, 'fetchExceptionLists'); - await act(async () => { - const { rerender, waitForNextUpdate } = renderHook< - UseExceptionListsProps, - ReturnExceptionLists - >( - ({ errorMessage, filterOptions, http, initialPagination, namespaceTypes, notifications }) => - useExceptionLists({ - errorMessage, - filterOptions, - http, - initialPagination, - namespaceTypes, - notifications, - }), - { - initialProps: { - errorMessage: 'Uh oh', - filterOptions: {}, - http: mockKibanaHttpService, - initialPagination: { - page: 1, - perPage: 20, - total: 0, - }, - namespaceTypes: ['single'], - notifications: mockKibanaNotificationsService, + const { rerender } = renderHook( + ({ errorMessage, filterOptions, http, initialPagination, namespaceTypes, notifications }) => + useExceptionLists({ + errorMessage, + filterOptions, + http, + initialPagination, + namespaceTypes, + notifications, + }), + { + initialProps: { + errorMessage: 'Uh oh', + filterOptions: {}, + http: mockKibanaHttpService, + initialPagination: { + page: 1, + perPage: 20, + total: 0, }, - } - ); - // NOTE: First `waitForNextUpdate` is initialization - // Second call applies the params - await waitForNextUpdate(); - await waitForNextUpdate(); + namespaceTypes: ['single' as const], + notifications: mockKibanaNotificationsService, + }, + } + ); - rerender({ + await waitFor(() => new Promise((resolve) => resolve(null))); + + rerender({ + errorMessage: 'Uh oh', + filterOptions: {}, + http: mockKibanaHttpService, + initialPagination: { + page: 1, + perPage: 20, + total: 0, + }, + namespaceTypes: ['single' as const, 'agnostic' as const], + notifications: mockKibanaNotificationsService, + }); + + await waitFor(() => expect(spyOnfetchExceptionLists).toHaveBeenCalledTimes(2)); + }); + + test('fetches list when refreshExceptionList callback invoked', async () => { + const spyOnfetchExceptionLists = jest.spyOn(api, 'fetchExceptionLists'); + const { result } = renderHook(() => + useExceptionLists({ errorMessage: 'Uh oh', filterOptions: {}, http: mockKibanaHttpService, @@ -235,49 +227,18 @@ describe('useExceptionLists', () => { }, namespaceTypes: ['single', 'agnostic'], notifications: mockKibanaNotificationsService, - }); - // NOTE: Only need one call here because hook already initilaized - await waitForNextUpdate(); + }) + ); - expect(spyOnfetchExceptionLists).toHaveBeenCalledTimes(2); - }); - }); - - test('fetches list when refreshExceptionList callback invoked', async () => { - const spyOnfetchExceptionLists = jest.spyOn(api, 'fetchExceptionLists'); - await act(async () => { - const { result, waitForNextUpdate } = renderHook< - UseExceptionListsProps, - ReturnExceptionLists - >(() => - useExceptionLists({ - errorMessage: 'Uh oh', - filterOptions: {}, - http: mockKibanaHttpService, - initialPagination: { - page: 1, - perPage: 20, - total: 0, - }, - namespaceTypes: ['single', 'agnostic'], - notifications: mockKibanaNotificationsService, - }) - ); - // NOTE: First `waitForNextUpdate` is initialization - // Second call applies the params - await waitForNextUpdate(); - await waitForNextUpdate(); + await waitFor(() => new Promise((resolve) => resolve(null))); - expect(typeof result.current[3]).toEqual('function'); + expect(typeof result.current[3]).toEqual('function'); - if (result.current[4] != null) { - result.current[4](); - } - // NOTE: Only need one call here because hook already initilaized - await waitForNextUpdate(); + if (result.current[4] != null) { + result.current[4](); + } - expect(spyOnfetchExceptionLists).toHaveBeenCalledTimes(2); - }); + await waitFor(() => expect(spyOnfetchExceptionLists).toHaveBeenCalledTimes(2)); }); test('invokes notifications service if "fetchExceptionLists" fails', async () => { @@ -285,26 +246,22 @@ describe('useExceptionLists', () => { const spyOnfetchExceptionLists = jest .spyOn(api, 'fetchExceptionLists') .mockRejectedValue(mockError); - await act(async () => { - const { waitForNextUpdate } = renderHook(() => - useExceptionLists({ - errorMessage: 'Uh oh', - filterOptions: {}, - http: mockKibanaHttpService, - initialPagination: { - page: 1, - perPage: 20, - total: 0, - }, - namespaceTypes: ['single', 'agnostic'], - notifications: mockKibanaNotificationsService, - }) - ); - // NOTE: First `waitForNextUpdate` is initialization - // Second call applies the params - await waitForNextUpdate(); - await waitForNextUpdate(); + renderHook(() => + useExceptionLists({ + errorMessage: 'Uh oh', + filterOptions: {}, + http: mockKibanaHttpService, + initialPagination: { + page: 1, + perPage: 20, + total: 0, + }, + namespaceTypes: ['single', 'agnostic'], + notifications: mockKibanaNotificationsService, + }) + ); + await waitFor(() => { expect(mockKibanaNotificationsService.toasts.addError).toHaveBeenCalledWith(mockError, { title: 'Uh oh', }); diff --git a/x-pack/plugins/lists/public/lists/hooks/use_create_list_index.test.ts b/x-pack/plugins/lists/public/lists/hooks/use_create_list_index.test.ts index 3e91e9b269830..7021d49940f26 100644 --- a/x-pack/plugins/lists/public/lists/hooks/use_create_list_index.test.ts +++ b/x-pack/plugins/lists/public/lists/hooks/use_create_list_index.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { act, renderHook, waitFor } from '@testing-library/react'; import { useCreateListIndex } from '@kbn/securitysolution-list-hooks'; import * as Api from '@kbn/securitysolution-list-api'; import { httpServiceMock } from '@kbn/core/public/mocks'; @@ -28,52 +28,49 @@ describe('useCreateListIndex', () => { }); it('should call Api.createListIndex when start() executes', async () => { - const { result, waitForNextUpdate } = renderHook(() => useCreateListIndex({ http: httpMock }), { + const { result } = renderHook(() => useCreateListIndex({ http: httpMock }), { wrapper: queryWrapper, }); act(() => { result.current.start(); }); - await waitForNextUpdate(); - - expect(Api.createListIndex).toHaveBeenCalledWith(expect.objectContaining({ http: httpMock })); + await waitFor(() => + expect(Api.createListIndex).toHaveBeenCalledWith(expect.objectContaining({ http: httpMock })) + ); }); it('should call onError callback when Api.createListIndex fails', async () => { const onError = jest.fn(); jest.spyOn(Api, 'createListIndex').mockRejectedValue(new Error('Mocked error')); - const { result, waitForNextUpdate } = renderHook( - () => useCreateListIndex({ http: httpMock, onError }), - { wrapper: queryWrapper } - ); + const { result } = renderHook(() => useCreateListIndex({ http: httpMock, onError }), { + wrapper: queryWrapper, + }); act(() => { result.current.start(); }); - await waitForNextUpdate(); - - expect(onError).toHaveBeenCalledWith(new Error('Mocked error'), undefined, undefined); + await waitFor(() => + expect(onError).toHaveBeenCalledWith(new Error('Mocked error'), undefined, undefined) + ); }); it('should not invalidate read index query on failure', async () => { jest.spyOn(Api, 'createListIndex').mockRejectedValue(new Error('Mocked error')); const invalidateQueriesSpy = jest.spyOn(queryClient, 'invalidateQueries'); - const { result, waitForNextUpdate } = renderHook(() => useCreateListIndex({ http: httpMock }), { + const { result } = renderHook(() => useCreateListIndex({ http: httpMock }), { wrapper: queryWrapper, }); act(() => { result.current.start(); }); - await waitForNextUpdate(); - - expect(invalidateQueriesSpy).not.toHaveBeenCalled(); + await waitFor(() => expect(invalidateQueriesSpy).not.toHaveBeenCalled()); }); it('should invalidate read index query on success', async () => { - const { result, waitForNextUpdate } = renderHook(() => useCreateListIndex({ http: httpMock }), { + const { result } = renderHook(() => useCreateListIndex({ http: httpMock }), { wrapper: queryWrapper, }); const invalidateQueriesSpy = jest.spyOn(queryClient, 'invalidateQueries'); @@ -81,8 +78,8 @@ describe('useCreateListIndex', () => { act(() => { result.current.start(); }); - await waitForNextUpdate(); - - expect(invalidateQueriesSpy).toHaveBeenCalledWith(['detectionEngine', 'listIndex']); + await waitFor(() => + expect(invalidateQueriesSpy).toHaveBeenCalledWith(['detectionEngine', 'listIndex']) + ); }); }); diff --git a/x-pack/plugins/lists/public/lists/hooks/use_delete_list.test.ts b/x-pack/plugins/lists/public/lists/hooks/use_delete_list.test.ts index aac604a89bc8a..7a339f3e12a4a 100644 --- a/x-pack/plugins/lists/public/lists/hooks/use_delete_list.test.ts +++ b/x-pack/plugins/lists/public/lists/hooks/use_delete_list.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { act, renderHook, waitFor } from '@testing-library/react'; import { useDeleteList } from '@kbn/securitysolution-list-hooks'; import * as Api from '@kbn/securitysolution-list-api'; import { httpServiceMock } from '@kbn/core/public/mocks'; @@ -25,14 +25,14 @@ describe('useDeleteList', () => { }); it('invokes Api.deleteList', async () => { - const { result, waitForNextUpdate } = renderHook(() => useDeleteList()); + const { result } = renderHook(() => useDeleteList()); act(() => { result.current.start({ http: httpMock, id: 'list' }); }); - await waitForNextUpdate(); - - expect(Api.deleteList).toHaveBeenCalledWith( - expect.objectContaining({ http: httpMock, id: 'list' }) + await waitFor(() => + expect(Api.deleteList).toHaveBeenCalledWith( + expect.objectContaining({ http: httpMock, id: 'list' }) + ) ); }); }); diff --git a/x-pack/plugins/lists/public/lists/hooks/use_export_list.test.ts b/x-pack/plugins/lists/public/lists/hooks/use_export_list.test.ts index 4977d585fe43b..8f08ad5c8bd03 100644 --- a/x-pack/plugins/lists/public/lists/hooks/use_export_list.test.ts +++ b/x-pack/plugins/lists/public/lists/hooks/use_export_list.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { act, renderHook, waitFor } from '@testing-library/react'; import { useExportList } from '@kbn/securitysolution-list-hooks'; import * as Api from '@kbn/securitysolution-list-api'; import { httpServiceMock } from '@kbn/core/public/mocks'; @@ -23,14 +23,14 @@ describe('useExportList', () => { }); it('invokes Api.exportList', async () => { - const { result, waitForNextUpdate } = renderHook(() => useExportList()); + const { result } = renderHook(() => useExportList()); act(() => { result.current.start({ http: httpMock, listId: 'list' }); }); - await waitForNextUpdate(); - - expect(Api.exportList).toHaveBeenCalledWith( - expect.objectContaining({ http: httpMock, listId: 'list' }) + await waitFor(() => + expect(Api.exportList).toHaveBeenCalledWith( + expect.objectContaining({ http: httpMock, listId: 'list' }) + ) ); }); }); diff --git a/x-pack/plugins/lists/public/lists/hooks/use_import_list.test.ts b/x-pack/plugins/lists/public/lists/hooks/use_import_list.test.ts index 4a1557e94fe79..d56478a0321be 100644 --- a/x-pack/plugins/lists/public/lists/hooks/use_import_list.test.ts +++ b/x-pack/plugins/lists/public/lists/hooks/use_import_list.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { act, renderHook, waitFor } from '@testing-library/react'; import { useImportList } from '@kbn/securitysolution-list-hooks'; import * as Api from '@kbn/securitysolution-list-api'; import { httpServiceMock } from '@kbn/core/public/mocks'; @@ -32,7 +32,7 @@ describe('useImportList', () => { it('invokes Api.importList', async () => { const fileMock = 'my file' as unknown as File; - const { result, waitForNextUpdate } = renderHook(() => useImportList()); + const { result } = renderHook(() => useImportList()); act(() => { result.current.start({ @@ -42,21 +42,21 @@ describe('useImportList', () => { type: 'keyword', }); }); - await waitForNextUpdate(); - - expect(Api.importList).toHaveBeenCalledWith( - expect.objectContaining({ - file: fileMock, - listId: 'my_list_id', - type: 'keyword', - }) + await waitFor(() => + expect(Api.importList).toHaveBeenCalledWith( + expect.objectContaining({ + file: fileMock, + listId: 'my_list_id', + type: 'keyword', + }) + ) ); }); it('populates result with the response of Api.importList', async () => { const fileMock = 'my file' as unknown as File; - const { result, waitForNextUpdate } = renderHook(() => useImportList()); + const { result } = renderHook(() => useImportList()); act(() => { result.current.start({ @@ -66,15 +66,13 @@ describe('useImportList', () => { type: 'keyword', }); }); - await waitForNextUpdate(); - - expect(result.current.result).toEqual(getListResponseMock()); + await waitFor(() => expect(result.current.result).toEqual(getListResponseMock())); }); it('error is populated if importList rejects', async () => { const fileMock = 'my file' as unknown as File; (Api.importList as jest.Mock).mockRejectedValue(new Error('whoops')); - const { result, waitForNextUpdate } = renderHook(() => useImportList()); + const { result } = renderHook(() => useImportList()); act(() => { result.current.start({ @@ -85,9 +83,9 @@ describe('useImportList', () => { }); }); - await waitForNextUpdate(); - - expect(result.current.result).toBeUndefined(); - expect(result.current.error).toEqual(new Error('whoops')); + await waitFor(() => { + expect(result.current.result).toBeUndefined(); + expect(result.current.error).toEqual(new Error('whoops')); + }); }); }); diff --git a/x-pack/plugins/lists/public/lists/hooks/use_read_list_index.test.ts b/x-pack/plugins/lists/public/lists/hooks/use_read_list_index.test.ts index dc57825d9acfa..643ac2df5b05b 100644 --- a/x-pack/plugins/lists/public/lists/hooks/use_read_list_index.test.ts +++ b/x-pack/plugins/lists/public/lists/hooks/use_read_list_index.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook, waitFor } from '@testing-library/react'; import { useReadListIndex } from '@kbn/securitysolution-list-hooks'; import * as Api from '@kbn/securitysolution-list-api'; import { httpServiceMock } from '@kbn/core/public/mocks'; @@ -30,16 +30,11 @@ describe.skip('useReadListIndex', () => { }); it('should call Api.readListIndex when is enabled', async () => { - const { waitForNextUpdate } = renderHook( - () => useReadListIndex({ http: httpMock, isEnabled: true }), - { - wrapper: queryWrapper, - } - ); - - await waitForNextUpdate(); + renderHook(() => useReadListIndex({ http: httpMock, isEnabled: true }), { + wrapper: queryWrapper, + }); - expect(Api.readListIndex).toHaveBeenCalled(); + await waitFor(() => expect(Api.readListIndex).toHaveBeenCalled()); }); it('should not call Api.readListIndex when is not enabled', async () => { @@ -54,15 +49,10 @@ describe.skip('useReadListIndex', () => { const onError = jest.fn(); jest.spyOn(Api, 'readListIndex').mockRejectedValue(new Error('Mocked error')); - const { waitForNextUpdate } = renderHook( - () => useReadListIndex({ http: httpMock, isEnabled: true, onError }), - { - wrapper: queryWrapper, - } - ); - - await waitForNextUpdate(); + renderHook(() => useReadListIndex({ http: httpMock, isEnabled: true, onError }), { + wrapper: queryWrapper, + }); - expect(onError).toHaveBeenCalledWith(new Error('Mocked error')); + await waitFor(() => expect(onError).toHaveBeenCalledWith(new Error('Mocked error'))); }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_all_esql_rule_fields.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_all_esql_rule_fields.test.ts index 51d9c145a97b3..d857665878672 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_all_esql_rule_fields.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_all_esql_rule_fields.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook, act } from '@testing-library/react-hooks'; +import { renderHook, act } from '@testing-library/react'; import type { DataViewFieldBase } from '@kbn/es-query'; import { useQuery } from '@tanstack/react-query'; import { useAllEsqlRuleFields } from './use_all_esql_rule_fields'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_esql_index.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_esql_index.test.ts index eabd2050e7ba2..3d29970ad0547 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_esql_index.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_creation_ui/hooks/use_esql_index.test.ts @@ -4,7 +4,8 @@ * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; + +import { renderHook } from '@testing-library/react'; import { useEsqlIndex } from './use_esql_index'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/use_add_to_rules_table.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/use_add_to_rules_table.test.tsx index 8862c626ea6e4..55b10a34333aa 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/use_add_to_rules_table.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/flyout_components/add_to_rules_table/use_add_to_rules_table.test.tsx @@ -6,8 +6,7 @@ */ import React from 'react'; -import { fireEvent, render as rTLRender, waitFor, act } from '@testing-library/react'; -import { renderHook } from '@testing-library/react-hooks'; +import { fireEvent, render as rTLRender, waitFor, act, renderHook } from '@testing-library/react'; import type { EuiTableFieldDataColumnType } from '@elastic/eui'; import type { Rule } from '../../../../rule_management/logic/types'; import { getRulesSchemaMock } from '../../../../../../common/api/detection_engine/model/rule_schema/mocks'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/value_with_space_warning/__tests__/use_value_with_space_warning.test.ts b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/value_with_space_warning/__tests__/use_value_with_space_warning.test.ts index 4c0df5f03e098..92a5e60bb5590 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/value_with_space_warning/__tests__/use_value_with_space_warning.test.ts +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/components/value_with_space_warning/__tests__/use_value_with_space_warning.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useValueWithSpaceWarning } from '../use_value_with_space_warning'; describe('useValueWithSpaceWarning', () => { diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_fetch_or_create_rule_exception_list.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_fetch_or_create_rule_exception_list.test.tsx index 6cca3e02e3223..8ec41f5cb70f7 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_fetch_or_create_rule_exception_list.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_exceptions/logic/use_fetch_or_create_rule_exception_list.test.tsx @@ -5,8 +5,8 @@ * 2.0. */ -import type { RenderHookResult } from '@testing-library/react-hooks'; -import { act, renderHook } from '@testing-library/react-hooks'; +import type { RenderHookResult } from '@testing-library/react'; +import { waitFor, renderHook } from '@testing-library/react'; import { coreMock } from '@kbn/core/public/mocks'; import * as rulesApi from '../../rule_management/api/api'; @@ -40,8 +40,8 @@ describe('useFetchOrCreateRuleExceptionList', () => { let render: ( listType?: UseFetchOrCreateRuleExceptionListProps['exceptionListType'] ) => RenderHookResult< - UseFetchOrCreateRuleExceptionListProps, - ReturnUseFetchOrCreateRuleExceptionList + ReturnUseFetchOrCreateRuleExceptionList, + UseFetchOrCreateRuleExceptionListProps >; const onError = jest.fn(); const onSuccess = jest.fn(); @@ -94,15 +94,14 @@ describe('useFetchOrCreateRuleExceptionList', () => { .mockResolvedValue(detectionExceptionList); render = (listType = detectionListType) => - renderHook( - () => - useFetchOrCreateRuleExceptionList({ - http: mockKibanaHttpService, - ruleId, - exceptionListType: listType, - onError, - onSuccess, - }) + renderHook(() => + useFetchOrCreateRuleExceptionList({ + http: mockKibanaHttpService, + ruleId, + exceptionListType: listType, + onError, + onSuccess, + }) ); }); @@ -111,20 +110,17 @@ describe('useFetchOrCreateRuleExceptionList', () => { }); it('initializes hook', async () => { - const { result, waitForNextUpdate } = render(); + const { result } = render(); // Should set isLoading to true while fetching expect(result.current).toEqual([true, null]); - await waitForNextUpdate(); - expect(result.current).toEqual([false, detectionExceptionList]); + await waitFor(() => expect(result.current).toEqual([false, detectionExceptionList])); }); it('fetches the rule with the given ruleId', async () => { - await act(async () => { - const { waitForNextUpdate } = render(); - await waitForNextUpdate(); - await waitForNextUpdate(); + render(); + await waitFor(() => { expect(fetchRuleById).toHaveBeenCalledTimes(1); expect(fetchRuleById).toHaveBeenCalledWith({ id: ruleId, @@ -141,78 +137,48 @@ describe('useFetchOrCreateRuleExceptionList', () => { }); it('does not fetch the exceptions lists', async () => { - await act(async () => { - const { waitForNextUpdate } = render(); - await waitForNextUpdate(); - expect(fetchExceptionListById).not.toHaveBeenCalled(); - }); + render(); + await waitFor(() => expect(fetchExceptionListById).not.toHaveBeenCalled()); }); + it('should create a new exception list', async () => { - await act(async () => { - const { waitForNextUpdate } = render(); - await waitForNextUpdate(); - await waitForNextUpdate(); - expect(addExceptionList).toHaveBeenCalledTimes(1); - }); + render(); + await waitFor(() => expect(addExceptionList).toHaveBeenCalledTimes(1)); }); it('should update the rule', async () => { - await act(async () => { - const { waitForNextUpdate } = render(); - await waitForNextUpdate(); - await waitForNextUpdate(); - await waitForNextUpdate(); - expect(patchRule).toHaveBeenCalledTimes(1); - }); + render(); + await waitFor(() => expect(patchRule).toHaveBeenCalledTimes(1)); }); it('invokes onSuccess', async () => { - await act(async () => { - const { waitForNextUpdate } = render(); - await waitForNextUpdate(); - await waitForNextUpdate(); - await waitForNextUpdate(); - expect(onSuccess).toHaveBeenCalledWith(false); - }); + render(); + await waitFor(() => expect(onSuccess).toHaveBeenCalledWith(false)); }); }); describe("when the rule has exception list references and 'detection' is passed in", () => { it('fetches the exceptions lists', async () => { - await act(async () => { - const { waitForNextUpdate } = render(); - await waitForNextUpdate(); - await waitForNextUpdate(); - expect(fetchExceptionListById).toHaveBeenCalledTimes(2); - }); + render(); + await waitFor(() => expect(fetchExceptionListById).toHaveBeenCalledTimes(2)); }); + it('does not create a new exception list', async () => { - await act(async () => { - const { waitForNextUpdate } = render(); - await waitForNextUpdate(); - expect(addExceptionList).not.toHaveBeenCalled(); - }); + render(); + await waitFor(() => expect(addExceptionList).not.toHaveBeenCalled()); }); + it('does not update the rule', async () => { - await act(async () => { - const { waitForNextUpdate } = render(); - await waitForNextUpdate(); - expect(patchRule).not.toHaveBeenCalled(); - }); + render(); + await waitFor(() => expect(patchRule).not.toHaveBeenCalled()); }); + it('should set the exception list to be the fetched list', async () => { - await act(async () => { - const { result, waitForNextUpdate } = render(); - await waitForNextUpdate(); - await waitForNextUpdate(); - expect(result.current[1]).toEqual(detectionExceptionList); - }); + const { result } = render(); + await waitFor(() => expect(result.current[1]).toEqual(detectionExceptionList)); }); + it('invokes onSuccess indicating', async () => { - await act(async () => { - const { waitForNextUpdate } = render(); - await waitForNextUpdate(); - await waitForNextUpdate(); - expect(onSuccess).toHaveBeenCalledWith(false); - }); + render(); + await waitFor(() => expect(onSuccess).toHaveBeenCalledWith(false)); }); describe("but the rule does not have a reference to 'detection' type exception list", () => { @@ -223,30 +189,16 @@ describe('useFetchOrCreateRuleExceptionList', () => { }); it('should create a new exception list', async () => { - await act(async () => { - const { waitForNextUpdate } = render(); - await waitForNextUpdate(); - await waitForNextUpdate(); - expect(addExceptionList).toHaveBeenCalledTimes(1); - }); + render(); + await waitFor(() => expect(addExceptionList).toHaveBeenCalledTimes(1)); }); it('should update the rule', async () => { - await act(async () => { - const { waitForNextUpdate } = render(); - await waitForNextUpdate(); - await waitForNextUpdate(); - await waitForNextUpdate(); - expect(patchRule).toHaveBeenCalledTimes(1); - }); + render(); + await waitFor(() => expect(patchRule).toHaveBeenCalledTimes(1)); }); it('should set the exception list to be the newly created list', async () => { - await act(async () => { - const { result, waitForNextUpdate } = render(); - await waitForNextUpdate(); - await waitForNextUpdate(); - await waitForNextUpdate(); - expect(result.current[1]).toEqual(newDetectionExceptionList); - }); + const { result } = render(); + await waitFor(() => expect(result.current[1]).toEqual(newDetectionExceptionList)); }); }); }); @@ -263,34 +215,20 @@ describe('useFetchOrCreateRuleExceptionList', () => { }); it('fetches the exceptions lists', async () => { - await act(async () => { - const { waitForNextUpdate } = render(endpointListType); - await waitForNextUpdate(); - await waitForNextUpdate(); - expect(fetchExceptionListById).toHaveBeenCalledTimes(2); - }); + render(endpointListType); + await waitFor(() => expect(fetchExceptionListById).toHaveBeenCalledTimes(2)); }); it('does not create a new exception list', async () => { - await act(async () => { - const { waitForNextUpdate } = render(endpointListType); - await waitForNextUpdate(); - expect(addExceptionList).not.toHaveBeenCalled(); - }); + render(endpointListType); + await waitFor(() => expect(addExceptionList).not.toHaveBeenCalled()); }); it('does not update the rule', async () => { - await act(async () => { - const { waitForNextUpdate } = render(endpointListType); - await waitForNextUpdate(); - expect(patchRule).not.toHaveBeenCalled(); - }); + render(endpointListType); + await waitFor(() => expect(patchRule).not.toHaveBeenCalled()); }); it('should set the exception list to be the fetched list', async () => { - await act(async () => { - const { result, waitForNextUpdate } = render(endpointListType); - await waitForNextUpdate(); - await waitForNextUpdate(); - expect(result.current[1]).toEqual(endpointExceptionList); - }); + const { result } = render(endpointListType); + await waitFor(() => expect(result.current[1]).toEqual(endpointExceptionList)); }); describe("but the rule does not have a reference to 'endpoint' type exception list", () => { @@ -301,30 +239,16 @@ describe('useFetchOrCreateRuleExceptionList', () => { }); it('should create a new exception list', async () => { - await act(async () => { - const { waitForNextUpdate } = render(endpointListType); - await waitForNextUpdate(); - await waitForNextUpdate(); - expect(addEndpointExceptionList).toHaveBeenCalledTimes(1); - }); + render(endpointListType); + await waitFor(() => expect(addEndpointExceptionList).toHaveBeenCalledTimes(1)); }); it('should update the rule', async () => { - await act(async () => { - const { waitForNextUpdate } = render(endpointListType); - await waitForNextUpdate(); - await waitForNextUpdate(); - await waitForNextUpdate(); - expect(patchRule).toHaveBeenCalledTimes(1); - }); + render(endpointListType); + await waitFor(() => expect(patchRule).toHaveBeenCalledTimes(1)); }); it('should set the exception list to be the newly created list', async () => { - await act(async () => { - const { result, waitForNextUpdate } = render(endpointListType); - await waitForNextUpdate(); - await waitForNextUpdate(); - await waitForNextUpdate(); - expect(result.current[1]).toEqual(newEndpointExceptionList); - }); + const { result } = render(endpointListType); + await waitFor(() => expect(result.current[1]).toEqual(newEndpointExceptionList)); }); }); }); @@ -335,37 +259,26 @@ describe('useFetchOrCreateRuleExceptionList', () => { }); it('exception list should be null', async () => { - await act(async () => { - const { result, waitForNextUpdate } = render(); - await waitForNextUpdate(); - expect(result.current[1]).toBeNull(); - }); + const { result } = render(); + await waitFor(() => expect(result.current[1]).toBeNull()); }); it('isLoading should be false', async () => { - await act(async () => { - const { result, waitForNextUpdate } = render(); - await waitForNextUpdate(); - expect(result.current[0]).toEqual(false); - }); + const { result } = render(); + await waitFor(() => expect(result.current[0]).toEqual(false)); }); it('should call error callback', async () => { - await act(async () => { - const { waitForNextUpdate } = render(); - await waitForNextUpdate(); - await waitForNextUpdate(); + render(); + await waitFor(() => { expect(onError).toHaveBeenCalledTimes(1); expect(onError).toHaveBeenCalledWith(error, null, null); }); }); it('does not call onSuccess', async () => { - await act(async () => { - const { waitForNextUpdate } = render(); - await waitForNextUpdate(); - expect(onSuccess).not.toHaveBeenCalled(); - }); + render(); + await waitFor(() => expect(onSuccess).not.toHaveBeenCalled()); }); }); }); diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/api/hooks/use_schedule_rule_run_mutation.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/api/hooks/use_schedule_rule_run_mutation.test.tsx index 6c641ceb2ad9e..e67e479c38258 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/api/hooks/use_schedule_rule_run_mutation.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/api/hooks/use_schedule_rule_run_mutation.test.tsx @@ -7,7 +7,7 @@ import moment from 'moment'; -import { act } from '@testing-library/react-hooks'; +import { act } from '@testing-library/react'; import { useScheduleRuleRunMutation } from './use_schedule_rule_run_mutation'; import { renderMutation } from '../../../../management/hooks/test_utils'; import { scheduleRuleRunMock } from '../../logic/__mocks__/mock'; diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/logic/use_schedule_rule_run.test.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/logic/use_schedule_rule_run.test.tsx index ce70bc08bd722..d20d8e8e7642d 100644 --- a/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/logic/use_schedule_rule_run.test.tsx +++ b/x-pack/plugins/security_solution/public/detection_engine/rule_gaps/logic/use_schedule_rule_run.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { act, renderHook } from '@testing-library/react-hooks'; +import { renderHook, act, waitFor } from '@testing-library/react'; import moment from 'moment'; import { useKibana } from '../../../common/lib/kibana'; import { useKibana as mockUseKibana } from '../../../common/lib/kibana/__mocks__'; @@ -41,7 +41,7 @@ describe('When using the `useScheduleRuleRun()` hook', () => { }); it('should send schedule rule run request', async () => { - const { result, waitFor } = renderHook(() => useScheduleRuleRun(), { + const { result } = renderHook(() => useScheduleRuleRun(), { wrapper: TestProviders, }); @@ -50,20 +50,18 @@ describe('When using the `useScheduleRuleRun()` hook', () => { result.current.scheduleRuleRun({ ruleIds: ['rule-1'], timeRange }); }); - await waitFor(() => { - return mockUseScheduleRuleRunMutation.mock.calls.length > 0; - }); - - expect(mockUseScheduleRuleRunMutation).toHaveBeenCalledWith( - expect.objectContaining({ - ruleIds: ['rule-1'], - timeRange, - }) + await waitFor(() => + expect(mockUseScheduleRuleRunMutation).toHaveBeenCalledWith( + expect.objectContaining({ + ruleIds: ['rule-1'], + timeRange, + }) + ) ); }); it('should call reportEvent with success status on success', async () => { - const { result, waitFor } = renderHook(() => useScheduleRuleRun(), { + const { result } = renderHook(() => useScheduleRuleRun(), { wrapper: TestProviders, }); @@ -74,22 +72,20 @@ describe('When using the `useScheduleRuleRun()` hook', () => { result.current.scheduleRuleRun({ ruleIds: ['rule-1'], timeRange }); }); - await waitFor(() => { - return mockUseScheduleRuleRunMutation.mock.calls.length > 0; - }); - - expect(mockedUseKibana.services.telemetry.reportEvent).toHaveBeenCalledWith( - ManualRuleRunEventTypes.ManualRuleRunExecute, - { - rangeInMs: timeRange.endDate.diff(timeRange.startDate), - status: 'success', - rulesCount: 1, - } + await waitFor(() => + expect(mockedUseKibana.services.telemetry.reportEvent).toHaveBeenCalledWith( + ManualRuleRunEventTypes.ManualRuleRunExecute, + { + rangeInMs: timeRange.endDate.diff(timeRange.startDate), + status: 'success', + rulesCount: 1, + } + ) ); }); it('should call reportEvent with error status on failure', async () => { - const { result, waitFor } = renderHook(() => useScheduleRuleRun(), { + const { result } = renderHook(() => useScheduleRuleRun(), { wrapper: TestProviders, }); @@ -100,17 +96,15 @@ describe('When using the `useScheduleRuleRun()` hook', () => { result.current.scheduleRuleRun({ ruleIds: ['rule-1'], timeRange }); }); - await waitFor(() => { - return mockUseScheduleRuleRunMutation.mock.calls.length > 0; - }); - - expect(mockedUseKibana.services.telemetry.reportEvent).toHaveBeenCalledWith( - ManualRuleRunEventTypes.ManualRuleRunExecute, - { - rangeInMs: timeRange.endDate.diff(timeRange.startDate), - status: 'error', - rulesCount: 1, - } + await waitFor(() => + expect(mockedUseKibana.services.telemetry.reportEvent).toHaveBeenCalledWith( + ManualRuleRunEventTypes.ManualRuleRunExecute, + { + rangeInMs: timeRange.endDate.diff(timeRange.startDate), + status: 'error', + rulesCount: 1, + } + ) ); }); }); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/lists/use_lists_config.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/lists/use_lists_config.test.tsx index db1cd501bc3fc..48ae76514c288 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/lists/use_lists_config.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/lists/use_lists_config.test.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import { renderHook } from '@testing-library/react-hooks'; +import { renderHook } from '@testing-library/react'; import { useKibana } from '../../../../common/lib/kibana'; import { useListsIndex } from './use_lists_index';