diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/add/remote_clusters_add.helpers.js b/x-pack/plugins/remote_clusters/__jest__/client_integration/add/remote_clusters_add.helpers.js index c8badc92ab11c..f420e83adc031 100644 --- a/x-pack/plugins/remote_clusters/__jest__/client_integration/add/remote_clusters_add.helpers.js +++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/add/remote_clusters_add.helpers.js @@ -3,10 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { act } from 'react-dom/test-utils'; import { registerTestBed } from '../../../../../test_utils'; -/* eslint-disable @kbn/eslint/no-restricted-paths */ import { RemoteClusterAdd } from '../../../public/application/sections/remote_cluster_add'; import { createRemoteClustersStore } from '../../../public/application/store'; import { registerRouter } from '../../../public/application/services/routing'; @@ -24,8 +24,12 @@ export const setup = (props) => { const testBed = initTestBed(props); // User actions - const clickSaveForm = () => { - testBed.find('remoteClusterFormSaveButton').simulate('click'); + const clickSaveForm = async () => { + await act(async () => { + testBed.find('remoteClusterFormSaveButton').simulate('click'); + }); + + testBed.component.update(); }; return { diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/add/remote_clusters_add.test.js b/x-pack/plugins/remote_clusters/__jest__/client_integration/add/remote_clusters_add.test.js index 05a4a2e330325..545e3dd0ba969 100644 --- a/x-pack/plugins/remote_clusters/__jest__/client_integration/add/remote_clusters_add.test.js +++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/add/remote_clusters_add.test.js @@ -3,8 +3,9 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { act } from 'react-dom/test-utils'; -import { nextTick, setupEnvironment } from '../helpers'; +import { setupEnvironment } from '../helpers'; import { NON_ALPHA_NUMERIC_CHARS, ACCENTED_CHARS } from './special_characters'; import { setup } from './remote_clusters_add.helpers'; @@ -15,6 +16,7 @@ describe('Create Remote cluster', () => { let actions; let form; let server; + let component; beforeAll(() => { ({ server } = setupEnvironment()); @@ -24,8 +26,11 @@ describe('Create Remote cluster', () => { server.restore(); }); - beforeEach(() => { - ({ form, exists, find, actions } = setup()); + beforeEach(async () => { + await act(async () => { + ({ form, exists, find, actions, component } = setup()); + }); + component.update(); }); test('should have the title of the page set correctly', () => { @@ -45,7 +50,11 @@ describe('Create Remote cluster', () => { false ); - form.toggleEuiSwitch('remoteClusterFormSkipUnavailableFormToggle'); + act(() => { + form.toggleEuiSwitch('remoteClusterFormSkipUnavailableFormToggle'); + }); + + component.update(); expect(find('remoteClusterFormSkipUnavailableFormToggle').props()['aria-checked']).toBe(true); }); @@ -56,16 +65,20 @@ describe('Create Remote cluster', () => { // By default it should be set to "false" expect(find('remoteClusterFormConnectionModeToggle').props()['aria-checked']).toBe(false); - form.toggleEuiSwitch('remoteClusterFormConnectionModeToggle'); + act(() => { + form.toggleEuiSwitch('remoteClusterFormConnectionModeToggle'); + }); + + component.update(); expect(find('remoteClusterFormConnectionModeToggle').props()['aria-checked']).toBe(true); }); - test('should display errors and disable the save button when clicking "save" without filling the form', () => { + test('should display errors and disable the save button when clicking "save" without filling the form', async () => { expect(exists('remoteClusterFormGlobalError')).toBe(false); expect(find('remoteClusterFormSaveButton').props().disabled).toBe(false); - actions.clickSaveForm(); + await actions.clickSaveForm(); expect(exists('remoteClusterFormGlobalError')).toBe(true); expect(form.getErrorsMessages()).toEqual([ @@ -83,19 +96,22 @@ describe('Create Remote cluster', () => { let form; beforeEach(async () => { - ({ component, form, actions } = setup()); + await act(async () => { + ({ component, form, actions } = setup()); + }); - await nextTick(); component.update(); }); - test('should not allow spaces', () => { + test('should not allow spaces', async () => { form.setInputValue('remoteClusterFormNameInput', 'with space'); - actions.clickSaveForm(); + + await actions.clickSaveForm(); + expect(form.getErrorsMessages()).toContain('Spaces are not allowed in the name.'); }); - test('should only allow alpha-numeric characters, "-" (dash) and "_" (underscore)', () => { + test('should only allow alpha-numeric characters, "-" (dash) and "_" (underscore)', async () => { const expectInvalidChar = (char) => { if (char === '-' || char === '_') { return; @@ -103,6 +119,7 @@ describe('Create Remote cluster', () => { try { form.setInputValue('remoteClusterFormNameInput', `with${char}`); + expect(form.getErrorsMessages()).toContain( `Remove the character ${char} from the name.` ); @@ -111,7 +128,7 @@ describe('Create Remote cluster', () => { } }; - actions.clickSaveForm(); // display form errors + await actions.clickSaveForm(); // display form errors [...NON_ALPHA_NUMERIC_CHARS, ...ACCENTED_CHARS].forEach(expectInvalidChar); }); @@ -120,13 +137,20 @@ describe('Create Remote cluster', () => { describe('seeds', () => { let actions; let form; + let component; beforeEach(async () => { - ({ form, actions } = setup()); + await act(async () => { + ({ form, actions, component } = setup()); + }); + + component.update(); + + form.setInputValue('remoteClusterFormNameInput', 'remote_cluster_test'); }); - test('should only allow alpha-numeric characters and "-" (dash) in the node "host" part', () => { - actions.clickSaveForm(); // display form errors + test('should only allow alpha-numeric characters and "-" (dash) in the node "host" part', async () => { + await actions.clickSaveForm(); // display form errors const notInArray = (array) => (value) => array.indexOf(value) < 0; @@ -142,8 +166,8 @@ describe('Create Remote cluster', () => { .forEach(expectInvalidChar); }); - test('should require a numeric "port" to be set', () => { - actions.clickSaveForm(); + test('should require a numeric "port" to be set', async () => { + await actions.clickSaveForm(); form.setComboBoxValue('remoteClusterFormSeedsInput', '192.168.1.1'); expect(form.getErrorsMessages()).toContain('A port is required.'); @@ -156,16 +180,25 @@ describe('Create Remote cluster', () => { describe('proxy address', () => { let actions; let form; + let component; beforeEach(async () => { - ({ form, actions } = setup()); + await act(async () => { + ({ form, actions, component } = setup()); + }); - // Enable "proxy" mode - form.toggleEuiSwitch('remoteClusterFormConnectionModeToggle'); + component.update(); + + act(() => { + // Enable "proxy" mode + form.toggleEuiSwitch('remoteClusterFormConnectionModeToggle'); + }); + + component.update(); }); - test('should only allow alpha-numeric characters and "-" (dash) in the proxy address "host" part', () => { - actions.clickSaveForm(); // display form errors + test('should only allow alpha-numeric characters and "-" (dash) in the proxy address "host" part', async () => { + await actions.clickSaveForm(); // display form errors const notInArray = (array) => (value) => array.indexOf(value) < 0; @@ -181,8 +214,8 @@ describe('Create Remote cluster', () => { .forEach(expectInvalidChar); }); - test('should require a numeric "port" to be set', () => { - actions.clickSaveForm(); + test('should require a numeric "port" to be set', async () => { + await actions.clickSaveForm(); form.setInputValue('remoteClusterFormProxyAddressInput', '192.168.1.1'); expect(form.getErrorsMessages()).toContain('A port is required.'); diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/edit/remote_clusters_edit.helpers.js b/x-pack/plugins/remote_clusters/__jest__/client_integration/edit/remote_clusters_edit.helpers.js index b5402f3b017f0..331ef24d1d8a1 100644 --- a/x-pack/plugins/remote_clusters/__jest__/client_integration/edit/remote_clusters_edit.helpers.js +++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/edit/remote_clusters_edit.helpers.js @@ -6,7 +6,6 @@ import { registerTestBed } from '../../../../../test_utils'; -/* eslint-disable @kbn/eslint/no-restricted-paths */ import { RemoteClusterEdit } from '../../../public/application/sections/remote_cluster_edit'; import { createRemoteClustersStore } from '../../../public/application/store'; import { registerRouter } from '../../../public/application/services/routing'; diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/edit/remote_clusters_edit.test.js b/x-pack/plugins/remote_clusters/__jest__/client_integration/edit/remote_clusters_edit.test.js index b0e0832cb0831..d3dee936c68dc 100644 --- a/x-pack/plugins/remote_clusters/__jest__/client_integration/edit/remote_clusters_edit.test.js +++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/edit/remote_clusters_edit.test.js @@ -16,28 +16,23 @@ import { } from './remote_clusters_edit.helpers'; describe('Edit Remote cluster', () => { - let server; - let httpRequestsMockHelpers; let component; let find; let exists; - let waitFor; - beforeAll(() => { - ({ server, httpRequestsMockHelpers } = setupEnvironment()); - }); + const { server, httpRequestsMockHelpers } = setupEnvironment(); afterAll(() => { server.restore(); }); - beforeEach(async () => { - httpRequestsMockHelpers.setLoadRemoteClustersResponse([REMOTE_CLUSTER_EDIT]); + httpRequestsMockHelpers.setLoadRemoteClustersResponse([REMOTE_CLUSTER_EDIT]); + beforeEach(async () => { await act(async () => { - ({ component, find, exists, waitFor } = setup()); - await waitFor('remoteClusterForm'); + ({ component, find, exists } = setup()); }); + component.update(); }); test('should have the title of the page set correctly', () => { @@ -59,9 +54,10 @@ describe('Edit Remote cluster', () => { await act(async () => { addRemoteClusterTestBed = setupRemoteClustersAdd(); - addRemoteClusterTestBed.waitFor('remoteClusterAddPage'); }); + addRemoteClusterTestBed.component.update(); + const formEdit = component.find(RemoteClusterForm); const formAdd = addRemoteClusterTestBed.component.find(RemoteClusterForm); diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/setup_environment.js b/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/setup_environment.js index c912a4ddabc9d..de5c1e5290540 100644 --- a/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/setup_environment.js +++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/helpers/setup_environment.js @@ -3,20 +3,17 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import axios from 'axios'; +import axiosXhrAdapter from 'axios/lib/adapters/xhr'; import { notificationServiceMock, fatalErrorsServiceMock, docLinksServiceMock, - injectedMetadataServiceMock, } from '../../../../../../src/core/public/mocks'; import { usageCollectionPluginMock } from '../../../../../../src/plugins/usage_collection/public/mocks'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { HttpService } from '../../../../../../src/core/public/http'; - -/* eslint-disable @kbn/eslint/no-restricted-paths */ import { init as initBreadcrumb } from '../../../public/application/services/breadcrumb'; import { init as initHttp } from '../../../public/application/services/http'; import { init as initNotification } from '../../../public/application/services/notification'; @@ -25,10 +22,10 @@ import { init as initDocumentation } from '../../../public/application/services/ import { init as initHttpRequests } from './http_requests'; export const setupEnvironment = () => { - const httpServiceSetupMock = new HttpService().setup({ - injectedMetadata: injectedMetadataServiceMock.createSetupContract(), - fatalErrors: fatalErrorsServiceMock.createSetupContract(), - }); + // axios has a similar interface to HttpSetup, but we + // flatten out the response. + const mockHttpClient = axios.create({ adapter: axiosXhrAdapter }); + mockHttpClient.interceptors.response.use(({ data }) => data); initBreadcrumb(() => {}); initDocumentation(docLinksServiceMock.createStartContract()); @@ -37,7 +34,7 @@ export const setupEnvironment = () => { notificationServiceMock.createSetupContract().toasts, fatalErrorsServiceMock.createSetupContract() ); - initHttp(httpServiceSetupMock); + initHttp(mockHttpClient); const { server, httpRequestsMockHelpers } = initHttpRequests(); diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.helpers.js b/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.helpers.js index ce9638d95bd28..5f34728def3d3 100644 --- a/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.helpers.js +++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.helpers.js @@ -3,10 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ +import { act } from 'react-dom/test-utils'; import { registerTestBed, findTestSubject } from '../../../../../test_utils'; -/* eslint-disable @kbn/eslint/no-restricted-paths */ import { RemoteClusterList } from '../../../public/application/sections/remote_cluster_list'; import { createRemoteClustersStore } from '../../../public/application/store'; import { registerRouter } from '../../../public/application/services/routing'; @@ -29,15 +29,26 @@ export const setup = (props) => { const { rows } = testBed.table.getMetaData(EUI_TABLE); const row = rows[index]; const checkBox = row.reactWrapper.find('input').hostNodes(); - checkBox.simulate('change', { target: { checked: true } }); + + act(() => { + checkBox.simulate('change', { target: { checked: true } }); + }); + + testBed.component.update(); }; const clickBulkDeleteButton = () => { - testBed.find('remoteClusterBulkDeleteButton').simulate('click'); + const { find, component } = testBed; + act(() => { + find('remoteClusterBulkDeleteButton').simulate('click'); + }); + + component.update(); }; const clickRowActionButtonAt = (index = 0, action = 'delete') => { - const { rows } = testBed.table.getMetaData(EUI_TABLE); + const { table, component } = testBed; + const { rows } = table.getMetaData(EUI_TABLE); const indexLastColumn = rows[index].columns.length - 1; const tableCellActions = rows[index].columns[indexLastColumn].reactWrapper; @@ -45,32 +56,54 @@ export const setup = (props) => { if (action === 'delete') { button = findTestSubject(tableCellActions, 'remoteClusterTableRowRemoveButton'); } else if (action === 'edit') { - findTestSubject(tableCellActions, 'remoteClusterTableRowEditButton'); + button = findTestSubject(tableCellActions, 'remoteClusterTableRowEditButton'); } if (!button) { throw new Error(`Button for action "${action}" not found.`); } - button.simulate('click'); + act(() => { + button.simulate('click'); + }); + + component.update(); }; const clickConfirmModalDeleteRemoteCluster = () => { - const modal = testBed.find('remoteClustersDeleteConfirmModal'); - findTestSubject(modal, 'confirmModalConfirmButton').simulate('click'); + const { find, component } = testBed; + const modal = find('remoteClustersDeleteConfirmModal'); + + act(() => { + findTestSubject(modal, 'confirmModalConfirmButton').simulate('click'); + }); + + component.update(); }; const clickRemoteClusterAt = (index = 0) => { - const { rows } = testBed.table.getMetaData(EUI_TABLE); + const { table, component } = testBed; + const { rows } = table.getMetaData(EUI_TABLE); const remoteClusterLink = findTestSubject( rows[index].reactWrapper, 'remoteClustersTableListClusterLink' ); - remoteClusterLink.simulate('click'); + + act(() => { + remoteClusterLink.simulate('click'); + }); + + component.update(); }; const clickPaginationNextButton = () => { - testBed.find('remoteClusterListTable.pagination-button-next').simulate('click'); + const { find, component } = testBed; + + act(() => { + find('remoteClusterListTable.pagination-button-next').simulate('click'); + }); + + component.update(); }; return { diff --git a/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.test.js b/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.test.js index 3c393cd70009a..765da32260eb7 100644 --- a/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.test.js +++ b/x-pack/plugins/remote_clusters/__jest__/client_integration/list/remote_clusters_list.test.js @@ -10,26 +10,23 @@ import { getRemoteClusterMock } from '../../../fixtures/remote_cluster'; import { PROXY_MODE } from '../../../common/constants'; -import { setupEnvironment, nextTick, getRandomString, findTestSubject } from '../helpers'; +import { setupEnvironment, getRandomString, findTestSubject } from '../helpers'; import { setup } from './remote_clusters_list.helpers'; -// FLAKY: https://github.com/elastic/kibana/issues/58681 -describe.skip('', () => { - let server; - let httpRequestsMockHelpers; +describe('', () => { + const { server, httpRequestsMockHelpers } = setupEnvironment(); beforeAll(() => { - ({ server, httpRequestsMockHelpers } = setupEnvironment()); + jest.useFakeTimers(); }); afterAll(() => { + jest.useRealTimers(); server.restore(); }); - beforeEach(() => { - httpRequestsMockHelpers.setLoadRemoteClustersResponse([]); - }); + httpRequestsMockHelpers.setLoadRemoteClustersResponse([]); describe('on component mount', () => { let exists; @@ -48,9 +45,10 @@ describe.skip('', () => { let component; beforeEach(async () => { - ({ exists, component } = setup()); + await act(async () => { + ({ exists, component } = setup()); + }); - await nextTick(100); // We need to wait next tick for the mock server response to kick in component.update(); }); @@ -67,7 +65,7 @@ describe.skip('', () => { let find; let table; let actions; - let waitFor; + let component; let form; const remoteClusters = [ @@ -88,9 +86,10 @@ describe.skip('', () => { httpRequestsMockHelpers.setLoadRemoteClustersResponse(remoteClusters); await act(async () => { - ({ find, table, actions, waitFor, form } = setup()); - await waitFor('remoteClusterListTable'); + ({ find, table, actions, form, component } = setup()); }); + + component.update(); }); test('pagination works', () => { @@ -118,7 +117,6 @@ describe.skip('', () => { let actions; let tableCellsValues; let rows; - let waitFor; // For deterministic tests, we need to make sure that remoteCluster1 comes before remoteCluster2 // in the table list that is rendered. As the table orders alphabetically by index name @@ -152,11 +150,11 @@ describe.skip('', () => { httpRequestsMockHelpers.setLoadRemoteClustersResponse(remoteClusters); await act(async () => { - ({ component, find, exists, table, actions, waitFor } = setup()); - - await waitFor('remoteClusterListTable'); + ({ component, find, exists, table, actions } = setup()); }); + component.update(); + // Read the remote clusters list table ({ rows, tableCellsValues } = table.getMetaData('remoteClusterListTable')); }); @@ -283,10 +281,11 @@ describe.skip('', () => { actions.clickConfirmModalDeleteRemoteCluster(); await act(async () => { - await nextTick(600); // there is a 500ms timeout in the api action - component.update(); + jest.advanceTimersByTime(600); // there is a 500ms timeout in the api action }); + component.update(); + ({ rows } = table.getMetaData('remoteClusterListTable')); expect(rows.length).toBe(2); diff --git a/x-pack/test_utils/README.md b/x-pack/test_utils/README.md index 04c920c4ae834..a6ca1a5d86866 100644 --- a/x-pack/test_utils/README.md +++ b/x-pack/test_utils/README.md @@ -122,7 +122,7 @@ In order to prevent flakiness in component integration tests, please consider th - Be **synchronous** as much as possible. ​ - Hooks are delicate when it comes to state updates. Sometimes calling `act()` synchronously works, sometimes it doesn't. The reasoning behind this isn't clear yet. The best approach is to try synchrounsly first and if it fails, because of an `act()` error, then use the async version. + Hooks are delicate when it comes to state updates. Sometimes calling `act()` synchronously works, sometimes it doesn't. The reasoning behind this isn't clear yet. The best approach is to try synchronously first and if it fails, because of an `act()` error, then use the async version. ```js // First try this