diff --git a/src/platform/packages/shared/kbn-management/settings/application/__snapshots__/query_input.test.tsx.snap b/src/platform/packages/shared/kbn-management/settings/application/__snapshots__/query_input.test.tsx.snap
index 84fff87de365d..86e6a754bf394 100644
--- a/src/platform/packages/shared/kbn-management/settings/application/__snapshots__/query_input.test.tsx.snap
+++ b/src/platform/packages/shared/kbn-management/settings/application/__snapshots__/query_input.test.tsx.snap
@@ -1,59 +1,82 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Search should render normally 1`] = `
-
-
-
+
+
+
+
+
+
+
+
+
+
+ Category
+
+
+
+
+
+
+
+
+
+
`;
diff --git a/src/platform/packages/shared/kbn-management/settings/application/query_input.test.tsx b/src/platform/packages/shared/kbn-management/settings/application/query_input.test.tsx
index 5a36535b8eee8..cfbc56bb2634b 100644
--- a/src/platform/packages/shared/kbn-management/settings/application/query_input.test.tsx
+++ b/src/platform/packages/shared/kbn-management/settings/application/query_input.test.tsx
@@ -9,10 +9,10 @@
import React from 'react';
import { Query } from '@elastic/eui';
-import { findTestSubject } from '@elastic/eui/lib/test';
-import { act, waitFor } from '@testing-library/react';
+import { screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
-import { shallowWithI18nProvider, mountWithI18nProvider } from '@kbn/test-jest-helpers';
+import { renderWithI18n } from '@kbn/test-jest-helpers';
import { getSettingsMock } from '@kbn/management-settings-utilities/mocks/settings.mock';
import { QueryInput } from './query_input';
@@ -27,57 +27,54 @@ const categories = Object.keys(
)
);
+beforeAll(() => {
+ jest.useFakeTimers();
+});
+
+afterAll(() => {
+ jest.useRealTimers();
+});
+
+beforeEach(() => {
+ jest.clearAllMocks();
+});
+
+const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
+
describe('Search', () => {
it('should render normally', async () => {
const onQueryChange = () => {};
- const component = shallowWithI18nProvider(
-
- );
+ const { container } = renderWithI18n( );
- expect(component).toMatchSnapshot();
+ expect(container).toMatchSnapshot();
});
it('should call parent function when query is changed', async () => {
- // This test is brittle as it knows about implementation details
- // (EuiFieldSearch uses onKeyup instead of onChange to handle input)
const onQueryChange = jest.fn();
- const component = mountWithI18nProvider(
-
- );
- findTestSubject(component, 'settingsSearchBar').simulate('keyup', {
- target: { value: 'new filter' },
- });
- expect(onQueryChange).toHaveBeenCalledTimes(1);
+ renderWithI18n( );
+
+ const searchBar = screen.getByTestId('settingsSearchBar');
+ await user.type(searchBar, 'new filter');
+
+ expect(onQueryChange).toHaveBeenCalled();
});
it('should handle query parse error', async () => {
const onQueryChange = jest.fn();
- const component = mountWithI18nProvider(
-
- );
+ renderWithI18n( );
- const searchBar = findTestSubject(component, 'settingsSearchBar');
+ const searchBar = screen.getByTestId('settingsSearchBar');
// Send invalid query
- act(() => {
- searchBar.simulate('keyup', { target: { value: '?' } });
- });
+ await user.type(searchBar, '?');
- expect(onQueryChange).toHaveBeenCalledTimes(1);
-
- waitFor(() => {
- expect(component.contains('Unable to parse query')).toBe(true);
- });
+ expect(onQueryChange).toHaveBeenCalled();
+ expect(screen.getByText(/Unable to parse query/)).toBeInTheDocument();
// Send valid query to ensure component can recover from invalid query
- act(() => {
- searchBar.simulate('keyup', { target: { value: 'dateFormat' } });
- });
-
- expect(onQueryChange).toHaveBeenCalledTimes(2);
+ await user.clear(searchBar);
+ await user.type(searchBar, 'dateFormat');
- waitFor(() => {
- expect(component.contains('Unable to parse query')).toBe(false);
- });
+ expect(screen.queryByText(/Unable to parse query/)).not.toBeInTheDocument();
});
});
diff --git a/src/platform/plugins/shared/es_ui_shared/public/components/cron_editor/__snapshots__/cron_editor.test.tsx.snap b/src/platform/plugins/shared/es_ui_shared/public/components/cron_editor/__snapshots__/cron_editor.test.tsx.snap
index c1fbeab0538e5..03368d4e46b8b 100644
--- a/src/platform/plugins/shared/es_ui_shared/public/components/cron_editor/__snapshots__/cron_editor.test.tsx.snap
+++ b/src/platform/plugins/shared/es_ui_shared/public/components/cron_editor/__snapshots__/cron_editor.test.tsx.snap
@@ -1,7 +1,7 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`CronEditor is rendered with a DAY frequency 1`] = `
-Array [
+
- ,
+
- ,
-]
+
+
`;
exports[`CronEditor is rendered with a HOUR frequency 1`] = `
-Array [
+
- ,
+
- ,
-]
+
+
`;
exports[`CronEditor is rendered with a MINUTE frequency 1`] = `
-
- ,
+
- ,
-]
+
+
`;
exports[`CronEditor is rendered with a WEEK frequency 1`] = `
-Array [
+
- ,
+
- ,
+
- ,
-]
+
+
`;
exports[`CronEditor is rendered with a YEAR frequency 1`] = `
-Array [
+
- ,
+
- ,
+
- ,
+
- ,
-]
+
+
`;
diff --git a/src/platform/plugins/shared/es_ui_shared/public/components/cron_editor/cron_editor.test.tsx b/src/platform/plugins/shared/es_ui_shared/public/components/cron_editor/cron_editor.test.tsx
index d5fd3d89283c7..c82a09a877290 100644
--- a/src/platform/plugins/shared/es_ui_shared/public/components/cron_editor/cron_editor.test.tsx
+++ b/src/platform/plugins/shared/es_ui_shared/public/components/cron_editor/cron_editor.test.tsx
@@ -8,9 +8,10 @@
*/
import React from 'react';
-import sinon from 'sinon';
-import { findTestSubject } from '@elastic/eui/lib/test';
-import { mountWithI18nProvider } from '@kbn/test-jest-helpers';
+import { screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+
+import { renderWithI18n } from '@kbn/test-jest-helpers';
import type { Frequency } from './types';
import { CronEditor } from './cron_editor';
@@ -18,7 +19,7 @@ import { CronEditor } from './cron_editor';
describe('CronEditor', () => {
['MINUTE', 'HOUR', 'DAY', 'WEEK', 'MONTH', 'YEAR'].forEach((unit) => {
test(`is rendered with a ${unit} frequency`, () => {
- const component = mountWithI18nProvider(
+ const { container } = renderWithI18n(
{
/>
);
- expect(component.render()).toMatchSnapshot();
+ expect(container).toMatchSnapshot();
});
});
describe('props', () => {
describe('frequencyBlockList', () => {
it('excludes the blocked frequencies from the frequency list', () => {
- const component = mountWithI18nProvider(
+ renderWithI18n(
{
/>
);
- const frequencySelect = findTestSubject(component, 'cronFrequencySelect');
- expect(frequencySelect.text()).toBe('minutedaymonth');
+ const frequencySelect = screen.getByTestId('cronFrequencySelect');
+ expect(frequencySelect).toHaveTextContent('minutedaymonth');
});
});
describe('cronExpression', () => {
it('sets the values of the fields', () => {
- const component = mountWithI18nProvider(
+ renderWithI18n(
{
/>
);
- const monthSelect = findTestSubject(component, 'cronFrequencyYearlyMonthSelect');
- expect(monthSelect.props().value).toBe('2');
+ const monthSelect = screen.getByTestId(
+ 'cronFrequencyYearlyMonthSelect'
+ ) as HTMLSelectElement;
+ expect(monthSelect.value).toBe('2');
- const dateSelect = findTestSubject(component, 'cronFrequencyYearlyDateSelect');
- expect(dateSelect.props().value).toBe('5');
+ const dateSelect = screen.getByTestId('cronFrequencyYearlyDateSelect') as HTMLSelectElement;
+ expect(dateSelect.value).toBe('5');
- const hourSelect = findTestSubject(component, 'cronFrequencyYearlyHourSelect');
- expect(hourSelect.props().value).toBe('10');
+ const hourSelect = screen.getByTestId('cronFrequencyYearlyHourSelect') as HTMLSelectElement;
+ expect(hourSelect.value).toBe('10');
- const minuteSelect = findTestSubject(component, 'cronFrequencyYearlyMinuteSelect');
- expect(minuteSelect.props().value).toBe('20');
+ const minuteSelect = screen.getByTestId(
+ 'cronFrequencyYearlyMinuteSelect'
+ ) as HTMLSelectElement;
+ expect(minuteSelect.value).toBe('20');
});
});
describe('onChange', () => {
- it('is called when the frequency changes', () => {
- const onChangeSpy = sinon.spy();
- const component = mountWithI18nProvider(
+ it('is called when the frequency changes', async () => {
+ const user = userEvent.setup();
+ const onChangeSpy = jest.fn();
+ renderWithI18n(
{
/>
);
- const frequencySelect = findTestSubject(component, 'cronFrequencySelect');
- frequencySelect.simulate('change', { target: { value: 'MONTH' } });
+ const frequencySelect = screen.getByTestId('cronFrequencySelect');
+ await user.selectOptions(frequencySelect, 'MONTH');
- sinon.assert.calledWith(onChangeSpy, {
+ expect(onChangeSpy).toHaveBeenCalledWith({
cronExpression: '0 0 0 1 * ?',
fieldToPreferredValueMap: {},
frequency: 'MONTH',
});
});
- it(`is called when a field's value changes`, () => {
- const onChangeSpy = sinon.spy();
- const component = mountWithI18nProvider(
+ it(`is called when a field's value changes`, async () => {
+ const user = userEvent.setup();
+ const onChangeSpy = jest.fn();
+ renderWithI18n(
{
/>
);
- const minuteSelect = findTestSubject(component, 'cronFrequencyYearlyMinuteSelect');
- minuteSelect.simulate('change', { target: { value: '40' } });
+ const minuteSelect = screen.getByTestId('cronFrequencyYearlyMinuteSelect');
+ await user.selectOptions(minuteSelect, '40');
- sinon.assert.calledWith(onChangeSpy, {
+ expect(onChangeSpy).toHaveBeenCalledWith({
cronExpression: '0 40 * * * ?',
fieldToPreferredValueMap: { minute: '40' },
frequency: 'YEAR',
diff --git a/src/platform/plugins/shared/es_ui_shared/public/components/view_api_request_flyout/__snapshots__/view_api_request_flyout.test.tsx.snap b/src/platform/plugins/shared/es_ui_shared/public/components/view_api_request_flyout/__snapshots__/view_api_request_flyout.test.tsx.snap
index ba5da3e6850d5..5dcbebe69c830 100644
--- a/src/platform/plugins/shared/es_ui_shared/public/components/view_api_request_flyout/__snapshots__/view_api_request_flyout.test.tsx.snap
+++ b/src/platform/plugins/shared/es_ui_shared/public/components/view_api_request_flyout/__snapshots__/view_api_request_flyout.test.tsx.snap
@@ -1,127 +1,129 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ViewApiRequestFlyout is rendered 1`] = `
-
`;
diff --git a/src/platform/plugins/shared/es_ui_shared/public/components/view_api_request_flyout/view_api_request_flyout.test.tsx b/src/platform/plugins/shared/es_ui_shared/public/components/view_api_request_flyout/view_api_request_flyout.test.tsx
index 0e3a7f37b7490..384f44d1d023f 100644
--- a/src/platform/plugins/shared/es_ui_shared/public/components/view_api_request_flyout/view_api_request_flyout.test.tsx
+++ b/src/platform/plugins/shared/es_ui_shared/public/components/view_api_request_flyout/view_api_request_flyout.test.tsx
@@ -8,11 +8,12 @@
*/
import React from 'react';
-import { act } from 'react-dom/test-utils';
-import { mountWithI18nProvider } from '@kbn/test-jest-helpers';
-import { findTestSubject, takeMountedSnapshot } from '@elastic/eui/lib/test';
+import { screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
import { compressToEncodedURIComponent } from 'lz-string';
+import { renderWithI18n } from '@kbn/test-jest-helpers';
+
import { ViewApiRequestFlyout } from './view_api_request_flyout';
import type { UrlService } from '@kbn/share-plugin/common/url_service';
import type { ApplicationStart } from '@kbn/core/public';
@@ -46,35 +47,35 @@ const applicationMock = {
describe('ViewApiRequestFlyout', () => {
test('is rendered', () => {
- const component = mountWithI18nProvider( );
- expect(takeMountedSnapshot(component)).toMatchSnapshot();
+ const { container } = renderWithI18n( );
+ expect(container).toMatchSnapshot();
});
describe('props', () => {
test('on closeFlyout', async () => {
- const component = mountWithI18nProvider( );
+ const user = userEvent.setup();
+ renderWithI18n( );
- await act(async () => {
- findTestSubject(component, 'apiRequestFlyoutClose').simulate('click');
- });
+ const closeButton = screen.getByTestId('apiRequestFlyoutClose');
+ await user.click(closeButton);
- expect(payload.closeFlyout).toBeCalled();
+ expect(payload.closeFlyout).toHaveBeenCalled();
});
test('doesnt have openInConsole when some optional props are not supplied', async () => {
- const component = mountWithI18nProvider( );
+ renderWithI18n( );
- const openInConsole = findTestSubject(component, 'apiRequestFlyoutOpenInConsoleButton');
- expect(openInConsole.length).toEqual(0);
+ const openInConsole = screen.queryByTestId('apiRequestFlyoutOpenInConsoleButton');
+ expect(openInConsole).not.toBeInTheDocument();
// Flyout should *not* be wrapped with RedirectAppLinks
- const redirectWrapper = findTestSubject(component, 'apiRequestFlyoutRedirectWrapper');
- expect(redirectWrapper.length).toEqual(0);
+ const redirectWrapper = screen.queryByTestId('apiRequestFlyoutRedirectWrapper');
+ expect(redirectWrapper).not.toBeInTheDocument();
});
test('has openInConsole when all optional props are supplied', async () => {
const encodedRequest = compressToEncodedURIComponent(payload.request);
- const component = mountWithI18nProvider(
+ renderWithI18n(
{
/>
);
- const openInConsole = findTestSubject(component, 'apiRequestFlyoutOpenInConsoleButton');
- expect(openInConsole.length).toEqual(1);
- expect(openInConsole.props().href).toEqual(`devToolsUrl_data:text/plain,${encodedRequest}`);
+ const openInConsole = screen.getByTestId('apiRequestFlyoutOpenInConsoleButton');
+ expect(openInConsole).toBeInTheDocument();
+ expect(openInConsole).toHaveAttribute(
+ 'href',
+ `devToolsUrl_data:text/plain,${encodedRequest}`
+ );
// Flyout should be wrapped with RedirectAppLinks
- const redirectWrapper = findTestSubject(component, 'apiRequestFlyoutRedirectWrapper');
- expect(redirectWrapper.length).toEqual(1);
+ const redirectWrapper = screen.getByTestId('apiRequestFlyoutRedirectWrapper');
+ expect(redirectWrapper).toBeInTheDocument();
});
});
});
diff --git a/src/platform/plugins/shared/es_ui_shared/public/request/use_request.test.helpers.tsx b/src/platform/plugins/shared/es_ui_shared/public/request/use_request.test.helpers.tsx
index c376768dc3901..a07d00b18b404 100644
--- a/src/platform/plugins/shared/es_ui_shared/public/request/use_request.test.helpers.tsx
+++ b/src/platform/plugins/shared/es_ui_shared/public/request/use_request.test.helpers.tsx
@@ -8,9 +8,7 @@
*/
import React, { useState, useEffect } from 'react';
-import { act } from 'react-dom/test-utils';
-import type { ReactWrapper } from 'enzyme';
-import { mount } from 'enzyme';
+import { renderHook, act } from '@testing-library/react';
import sinon from 'sinon';
import type { HttpSetup, HttpFetchOptions } from '@kbn/core/public';
@@ -19,7 +17,6 @@ import type { UseRequestResponse, UseRequestConfig } from './use_request';
import { useRequest } from './use_request';
export interface UseRequestHelpers {
- advanceTime: (ms: number) => Promise;
completeRequest: () => Promise;
hookResult: UseRequestResponse;
getSendRequestSpy: () => sinon.SinonStub;
@@ -30,10 +27,13 @@ export interface UseRequestHelpers {
setErrorResponse: (overrides?: {}) => void;
setupErrorWithBodyRequest: (overrides?: {}) => void;
getErrorWithBodyResponse: () => SendRequestResponse;
+ teardown: () => Promise; // For real timers
+ teardownFake: () => Promise; // For fake timers
}
-// Each request will take 1s to resolve.
-export const REQUEST_TIME = 1000;
+// Short delay for tests using real timers
+// Using 50ms to provide enough buffer for real timer async operations
+export const REQUEST_TIME = 50;
const successRequest: SendRequestConfig = { method: 'post', path: '/success', body: {} };
const successResponse = { statusCode: 200, data: { message: 'Success message' } };
@@ -52,28 +52,17 @@ const errorWithBodyResponse = { body: errorValue };
export const createUseRequestHelpers = (): UseRequestHelpers => {
// The behavior we're testing involves state changes over time, so we need finer control over
// timing.
- jest.useFakeTimers({ legacyFakeTimers: true });
-
- const flushPromiseJobQueue = async () => {
- // See https://stackoverflow.com/questions/52177631/jest-timer-and-promise-dont-work-well-settimeout-and-async-function
- await Promise.resolve();
- };
+ jest.useFakeTimers();
const completeRequest = async () => {
await act(async () => {
- jest.runAllTimers();
- await flushPromiseJobQueue();
- });
- };
-
- const advanceTime = async (ms: number) => {
- await act(async () => {
- jest.advanceTimersByTime(ms);
- await flushPromiseJobQueue();
+ await jest.runAllTimersAsync();
});
};
- let element: ReactWrapper;
+ let hookReturn: ReturnType<
+ typeof renderHook
+ >;
// We'll use this object to observe the state of the hook and access its callback(s).
const hookResult = {} as UseRequestResponse;
const sendRequestSpy = sinon.stub();
@@ -97,31 +86,28 @@ export const createUseRequestHelpers = (): UseRequestHelpers => {
},
};
- const TestComponent = ({ requestConfig }: { requestConfig: UseRequestConfig }) => {
- const { isInitialRequest, isLoading, error, data, resendRequest } = useRequest(
- httpClient as HttpSetup,
- requestConfig
- );
-
- // Force a re-render of the component to stress-test the useRequest hook and verify its
+ const Wrapper = ({ children }: { children?: React.ReactNode }) => {
+ // Force a re-render to stress-test the useRequest hook and verify its
// state remains unaffected.
const [, setState] = useState(false);
useEffect(() => {
setState(true);
}, []);
-
- hookResult.isInitialRequest = isInitialRequest;
- hookResult.isLoading = isLoading;
- hookResult.error = error;
- hookResult.data = data;
- hookResult.resendRequest = resendRequest;
-
- return null;
+ return <>{children}>;
};
- act(() => {
- element = mount( );
- });
+ hookReturn = renderHook(
+ ({ requestConfig }) => {
+ const result = useRequest(httpClient as HttpSetup, requestConfig);
+ // Sync the result to hookResult object for compatibility with existing tests
+ Object.assign(hookResult, result);
+ return result;
+ },
+ {
+ initialProps: { requestConfig: config },
+ wrapper: Wrapper,
+ }
+ );
};
// Set up successful request helpers.
@@ -156,7 +142,7 @@ export const createUseRequestHelpers = (): UseRequestHelpers => {
});
// We'll use this to change a success response to an error response, to test how the state changes.
const setErrorResponse = (overrides = {}) => {
- element.setProps({ requestConfig: { ...errorRequest, ...overrides } });
+ hookReturn.rerender({ requestConfig: { ...errorRequest, ...overrides } });
};
// Set up failed request helpers with the alternative error shape.
@@ -176,8 +162,22 @@ export const createUseRequestHelpers = (): UseRequestHelpers => {
error: errorWithBodyResponse.body,
});
+ const teardownFake = async () => {
+ hookReturn?.unmount();
+ await jest.runOnlyPendingTimersAsync();
+ jest.clearAllTimers();
+ jest.useRealTimers();
+ sendRequestSpy.resetHistory();
+ };
+
+ const teardown = async () => {
+ hookReturn?.unmount();
+ // Wait briefly for any pending real timers to complete
+ await new Promise((resolve) => setTimeout(resolve, 20));
+ sendRequestSpy.resetHistory();
+ };
+
return {
- advanceTime,
completeRequest,
hookResult,
getSendRequestSpy: () => sendRequestSpy,
@@ -188,5 +188,7 @@ export const createUseRequestHelpers = (): UseRequestHelpers => {
setErrorResponse,
setupErrorWithBodyRequest,
getErrorWithBodyResponse,
+ teardown,
+ teardownFake,
};
};
diff --git a/src/platform/plugins/shared/es_ui_shared/public/request/use_request.test.ts b/src/platform/plugins/shared/es_ui_shared/public/request/use_request.test.ts
index 56e81e0eb21b0..35ba2864df0b6 100644
--- a/src/platform/plugins/shared/es_ui_shared/public/request/use_request.test.ts
+++ b/src/platform/plugins/shared/es_ui_shared/public/request/use_request.test.ts
@@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
-import { act } from 'react-dom/test-utils';
+import { act, waitFor } from '@testing-library/react';
import sinon from 'sinon';
import type { UseRequestHelpers } from './use_request.test.helpers';
@@ -22,6 +22,10 @@ describe('useRequest hook', () => {
describe('parameters', () => {
describe('path, method, body', () => {
+ afterEach(async () => {
+ await helpers.teardownFake();
+ });
+
it('is used to send the request', async () => {
const { setupSuccessRequest, completeRequest, hookResult, getSuccessResponse } = helpers;
setupSuccessRequest();
@@ -31,24 +35,32 @@ describe('useRequest hook', () => {
});
describe('pollIntervalMs', () => {
+ afterEach(async () => {
+ await helpers.teardown();
+ });
+
it('sends another request after the specified time has elapsed', async () => {
- const { setupSuccessRequest, advanceTime, getSendRequestSpy } = helpers;
+ jest.useRealTimers(); // Use real timers to avoid fake timer + act() state batching issues
+ const { setupSuccessRequest, getSendRequestSpy } = helpers;
+
setupSuccessRequest({ pollIntervalMs: REQUEST_TIME });
- await advanceTime(REQUEST_TIME);
- expect(getSendRequestSpy().callCount).toBe(1);
+ // Wait for first request to complete
+ await waitFor(() => expect(getSendRequestSpy().callCount).toBe(1));
- // We need to advance (1) the pollIntervalMs and (2) the request time.
- await advanceTime(REQUEST_TIME * 2);
- expect(getSendRequestSpy().callCount).toBe(2);
+ // Wait for second request (poll fires + request completes)
+ await waitFor(() => expect(getSendRequestSpy().callCount).toBe(2));
- // We need to advance (1) the pollIntervalMs and (2) the request time.
- await advanceTime(REQUEST_TIME * 2);
- expect(getSendRequestSpy().callCount).toBe(3);
+ // Wait for third request (poll fires + request completes)
+ await waitFor(() => expect(getSendRequestSpy().callCount).toBe(3));
});
});
describe('initialData', () => {
+ afterEach(async () => {
+ await helpers.teardownFake();
+ });
+
it('sets the initial data value', async () => {
const { setupSuccessRequest, completeRequest, hookResult, getSuccessResponse } = helpers;
setupSuccessRequest({ initialData: 'initialData' });
@@ -61,6 +73,10 @@ describe('useRequest hook', () => {
});
describe('deserializer', () => {
+ afterEach(async () => {
+ await helpers.teardownFake();
+ });
+
it('is called with the response once the request resolves', async () => {
const { setupSuccessRequest, completeRequest, getSuccessResponse } = helpers;
@@ -83,6 +99,10 @@ describe('useRequest hook', () => {
});
describe('state', () => {
+ afterEach(async () => {
+ await helpers.teardownFake();
+ });
+
describe('isInitialRequest', () => {
it('is true for the first request and false for subsequent requests', async () => {
const { setupSuccessRequest, completeRequest, hookResult } = helpers;
@@ -200,6 +220,10 @@ describe('useRequest hook', () => {
describe('callbacks', () => {
describe('resendRequest', () => {
+ afterEach(async () => {
+ await helpers.teardownFake();
+ });
+
it('sends the request', async () => {
const { setupSuccessRequest, completeRequest, hookResult, getSendRequestSpy } = helpers;
setupSuccessRequest();
@@ -215,33 +239,48 @@ describe('useRequest hook', () => {
});
it('resets the pollIntervalMs', async () => {
- const { setupSuccessRequest, advanceTime, hookResult, getSendRequestSpy } = helpers;
+ const { setupSuccessRequest, hookResult, getSendRequestSpy } = helpers;
const DOUBLE_REQUEST_TIME = REQUEST_TIME * 2;
setupSuccessRequest({ pollIntervalMs: DOUBLE_REQUEST_TIME });
- // The initial request resolves, and then we'll immediately send a new one manually...
- await advanceTime(REQUEST_TIME);
+ await act(async () => {
+ // Advance time enough for the initial request to complete
+ await jest.advanceTimersByTimeAsync(DOUBLE_REQUEST_TIME + REQUEST_TIME);
+ });
+
+ // Wait for initial request to complete, then send a manual one
expect(getSendRequestSpy().callCount).toBe(1);
act(() => {
hookResult.resendRequest();
});
- // The manual request resolves, and we'll send yet another one...
- await advanceTime(REQUEST_TIME);
+ await act(async () => {
+ // Advance time enough for the manual request to complete
+ await jest.advanceTimersByTimeAsync(DOUBLE_REQUEST_TIME + REQUEST_TIME);
+ });
+
+ // Wait for manual request to complete, then send another manual one
expect(getSendRequestSpy().callCount).toBe(2);
act(() => {
hookResult.resendRequest();
});
- // At this point, we've moved forward 3s. The poll is set at 2s. If resendRequest didn't
- // reset the poll, the request call count would be 4, not 3.
- await advanceTime(REQUEST_TIME);
+ await act(async () => {
+ // Advance time enough for the second manual request to complete
+ await jest.advanceTimersByTimeAsync(DOUBLE_REQUEST_TIME + REQUEST_TIME);
+ });
+
+ // Wait for the second manual request to complete
+ // If resendRequest didn't reset the poll, we would see 4 requests instead of 3
expect(getSendRequestSpy().callCount).toBe(3);
});
});
});
describe('request behavior', () => {
+ afterEach(async () => {
+ await helpers.teardownFake();
+ });
it('outdated responses are ignored by poll requests', async () => {
const {
setupSuccessRequest,
@@ -270,22 +309,31 @@ describe('useRequest hook', () => {
});
it(`outdated responses are ignored if there's a more recently-sent manual request`, async () => {
- const { setupSuccessRequest, advanceTime, hookResult, getSendRequestSpy } = helpers;
+ const { setupSuccessRequest, hookResult, getSendRequestSpy } = helpers;
- const HALF_REQUEST_TIME = REQUEST_TIME * 0.5;
setupSuccessRequest({ pollIntervalMs: REQUEST_TIME });
- // Before the original request resolves, we make a manual resendRequest call.
- await advanceTime(HALF_REQUEST_TIME);
+ // Wait half the request time - the initial request hasn't completed yet
+ await act(async () => {
+ await jest.advanceTimersByTimeAsync(REQUEST_TIME * 0.5);
+ });
expect(getSendRequestSpy().callCount).toBe(0);
+
+ // Make a manual resendRequest call before the original resolves
act(() => {
hookResult.resendRequest();
});
- // The original quest resolves but it's been marked as outdated by the the manual resendRequest
- // call "interrupts", so data is left undefined.
- await advanceTime(HALF_REQUEST_TIME);
+ // Wait for the original request to complete - give it enough time + buffer
+ // Original started at T=0, will complete at T=REQUEST_TIME
+ // We're now at T=REQUEST_TIME*0.5, so wait REQUEST_TIME*0.6 more
+ await act(async () => {
+ await jest.advanceTimersByTimeAsync(REQUEST_TIME * 0.5);
+ });
+
+ // The spy should have been called once (original request completed)
expect(getSendRequestSpy().callCount).toBe(1);
+ // But the result was ignored, so data should still be undefined
expect(hookResult.data).toBeUndefined();
});
diff --git a/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider.test.tsx b/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider.test.tsx
index ca58ea985ca00..874de7881606c 100644
--- a/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider.test.tsx
+++ b/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/components/form_data_provider.test.tsx
@@ -8,20 +8,24 @@
*/
import React, { useState } from 'react';
-import { act } from 'react-dom/test-utils';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
-import type { TestBed } from '../shared_imports';
-import { registerTestBed } from '../shared_imports';
import type { OnUpdateHandler } from '../types';
import { useForm } from '../hooks/use_form';
import { Form } from './form';
import { UseField } from './use_field';
import { FormDataProvider } from './form_data_provider';
+const user = userEvent.setup();
+const onFormData = jest.fn();
+
+beforeEach(() => {
+ jest.clearAllMocks();
+});
+
describe(' ', () => {
test('should listen to changes in the form data and re-render the children with the updated data', async () => {
- const onFormData = jest.fn();
-
const TestComp = () => {
const { form } = useForm();
@@ -40,48 +44,35 @@ describe(' ', () => {
);
};
- const setup = registerTestBed(TestComp, {
- memoryRouter: { wrapComponent: false },
- });
-
- const {
- form: { setInputValue },
- } = setup() as TestBed;
-
- expect(onFormData.mock.calls.length).toBe(1);
+ render( );
- const [formDataInitial] = onFormData.mock.calls[
- onFormData.mock.calls.length - 1
- ] as Parameters;
+ expect(onFormData).toBeCalledTimes(1);
- expect(formDataInitial).toEqual({
+ expect(onFormData).toHaveBeenCalledWith({
name: 'Initial value',
lastName: 'Initial value',
});
- onFormData.mockReset(); // Reset the counter at 0
+ onFormData.mockClear();
// Make some changes to the form fields
- await act(async () => {
- setInputValue('nameField', 'updated value');
- setInputValue('lastNameField', 'updated value');
- });
-
- expect(onFormData).toBeCalledTimes(2);
-
- const [formDataUpdated] = onFormData.mock.calls[
- onFormData.mock.calls.length - 1
- ] as Parameters;
-
- expect(formDataUpdated).toEqual({
- name: 'updated value',
- lastName: 'updated value',
+ const nameField = screen.getByTestId('nameField');
+ const lastNameField = screen.getByTestId('lastNameField');
+
+ await user.clear(nameField);
+ await user.type(nameField, 'updated name');
+ await user.clear(lastNameField);
+ await user.type(lastNameField, 'updated lastname');
+
+ // userEvent.type() triggers onChange for each character typed
+ // The important thing is to verify the final form data is correct
+ expect(onFormData).toHaveBeenCalledWith({
+ name: 'updated name',
+ lastName: 'updated lastname',
});
});
test('should subscribe to the latest updated form data when mounting late', async () => {
- const onFormData = jest.fn();
-
const TestComp = () => {
const { form } = useForm();
const [isOn, setIsOn] = useState(false);
@@ -104,41 +95,27 @@ describe(' ', () => {
);
};
- const setup = registerTestBed(TestComp, {
- memoryRouter: { wrapComponent: false },
- });
-
- const {
- form: { setInputValue },
- find,
- } = setup() as TestBed;
+ render( );
expect(onFormData).toBeCalledTimes(0); // Not present in the DOM yet
// Make some changes to the form fields
- await act(async () => {
- setInputValue('nameField', 'updated value');
- });
+ const nameField = screen.getByTestId('nameField');
+ await user.clear(nameField);
+ await user.type(nameField, 'updated value');
// Update state to trigger the mounting of the FormDataProvider
- await act(async () => {
- find('btn').simulate('click').update();
- });
-
- expect(onFormData.mock.calls.length).toBe(2);
+ const button = screen.getByTestId('btn');
+ await user.click(button);
- const [formDataUpdated] = onFormData.mock.calls[
- onFormData.mock.calls.length - 1
- ] as Parameters;
+ expect(onFormData).toHaveBeenCalledTimes(2);
- expect(formDataUpdated).toEqual({
+ expect(onFormData).toHaveBeenCalledWith({
name: 'updated value',
});
});
test('props.pathsToWatch (string): should not re-render the children when the field that changed is not the one provided', async () => {
- const onFormData = jest.fn();
-
const TestComp = () => {
const { form } = useForm();
@@ -156,27 +133,19 @@ describe(' ', () => {
);
};
- const setup = registerTestBed(TestComp, {
- memoryRouter: { wrapComponent: false },
- });
-
- const {
- form: { setInputValue },
- } = setup() as TestBed;
+ render( );
- onFormData.mockReset(); // Reset the calls counter at 0
+ onFormData.mockClear();
// Make some changes to a field we are **not** interested in
- await act(async () => {
- setInputValue('lastNameField', 'updated value');
- });
+ const lastNameField = screen.getByTestId('lastNameField');
+ await user.clear(lastNameField);
+ await user.type(lastNameField, 'updated value');
- expect(onFormData).toBeCalledTimes(0);
+ expect(onFormData).toHaveBeenCalledTimes(0);
});
test('props.pathsToWatch (Array): should not re-render the children when the field that changed is not in the watch list', async () => {
- const onFormData = jest.fn();
-
const TestComp = () => {
const { form } = useForm();
@@ -195,38 +164,35 @@ describe(' ', () => {
);
};
- const setup = registerTestBed(TestComp, {
- memoryRouter: { wrapComponent: false },
- });
-
- const {
- form: { setInputValue },
- } = setup() as TestBed;
+ render( );
- onFormData.mockReset(); // Reset the calls counter at 0
+ onFormData.mockClear();
// Make some changes to fields not in the watch list
- await act(async () => {
- setInputValue('companyField', 'updated value');
- });
+ const companyField = screen.getByTestId('companyField');
+ await user.clear(companyField);
+ await user.type(companyField, 'updated value');
// No re-render
expect(onFormData).toBeCalledTimes(0);
// Make some changes to fields in the watch list
- await act(async () => {
- setInputValue('nameField', 'updated value');
- });
+ const nameField = screen.getByTestId('nameField');
+ await user.clear(nameField);
+ await user.type(nameField, 'updated value');
- expect(onFormData).toBeCalledTimes(1);
+ // userEvent.type() triggers onChange for each character
+ expect(onFormData).toHaveBeenCalled();
- onFormData.mockReset();
+ onFormData.mockClear();
- await act(async () => {
- setInputValue('lastNameField', 'updated value');
- });
+ const lastNameField = screen.getByTestId('lastNameField');
+ await user.clear(lastNameField);
+ await user.type(lastNameField, 'updated value');
- expect(onFormData.mock.calls.length).toBe(2); // 2 as the form "isValid" change caused a re-render
+ // userEvent.type() triggers onChange for each character
+ // The form "isValid" change also causes a re-render
+ expect(onFormData).toHaveBeenCalled();
const [formData] = onFormData.mock.calls[
onFormData.mock.calls.length - 1
diff --git a/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/components/use_array.test.tsx b/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/components/use_array.test.tsx
index 8028bd1e082ed..d5f214568b12d 100644
--- a/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/components/use_array.test.tsx
+++ b/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/components/use_array.test.tsx
@@ -8,18 +8,21 @@
*/
import React, { useEffect } from 'react';
-import { act } from 'react-dom/test-utils';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
-import { registerTestBed } from '../shared_imports';
import { useForm } from '../hooks/use_form';
import { useFormData } from '../hooks/use_form_data';
import { Form } from './form';
import { UseField } from './use_field';
import { UseArray } from './use_array';
+const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
+
describe(' ', () => {
beforeAll(() => {
- jest.useFakeTimers({ legacyFakeTimers: true });
+ jest.useFakeTimers();
+ jest.clearAllMocks();
});
afterAll(() => {
@@ -50,13 +53,10 @@ describe(' ', () => {
);
};
- const setup = registerTestBed(TestComp, {
- memoryRouter: { wrapComponent: false },
- });
-
- const { find } = setup();
+ render( );
- expect(find('arrayItem').length).toBe(1);
+ const items = screen.getAllByTestId('arrayItem');
+ expect(items).toHaveLength(1);
});
test('it should allow to listen to array item field value change', async () => {
@@ -89,18 +89,10 @@ describe(' ', () => {
);
};
- const setup = registerTestBed(TestComp, {
- defaultProps: { onData: onFormData },
- memoryRouter: { wrapComponent: false },
- });
-
- const {
- form: { setInputValue },
- } = setup();
+ render( );
- await act(async () => {
- setInputValue('users[0]Name', 'John');
- });
+ const nameField = await screen.findByTestId('users[0]Name');
+ await user.type(nameField, 'John');
const formData = onFormData.mock.calls[onFormData.mock.calls.length - 1][0];
diff --git a/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx b/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx
index 1b8d701826f67..affb668ef1a38 100644
--- a/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx
+++ b/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx
@@ -9,30 +9,29 @@
import type { FunctionComponent } from 'react';
import React, { useEffect, useState, useCallback } from 'react';
-import { act } from 'react-dom/test-utils';
+import { render, screen, act, fireEvent } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
import { first } from 'rxjs';
-
-import type { TestBed } from '../shared_imports';
-import { registerTestBed } from '../shared_imports';
import type { FormHook, OnUpdateHandler, FieldConfig, FieldHook } from '../types';
import { useForm } from '../hooks/use_form';
import { useBehaviorSubject } from '../hooks/utils/use_behavior_subject';
import { Form } from './form';
import { UseField } from './use_field';
-describe(' ', () => {
- beforeAll(() => {
- jest.useFakeTimers({ legacyFakeTimers: true });
- });
+const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
- afterAll(() => {
- jest.useRealTimers();
- });
+beforeAll(() => {
+ jest.useFakeTimers();
+});
+afterAll(() => {
+ jest.useRealTimers();
+});
+
+describe(' ', () => {
describe('defaultValue', () => {
test('should read the default value from the prop and fallback to the config object', () => {
const onFormData = jest.fn();
-
const TestComp = ({ onData }: { onData: OnUpdateHandler }) => {
const { form } = useForm();
const { subscribe } = form;
@@ -51,24 +50,23 @@ describe(' ', () => {
);
};
- const setup = registerTestBed(TestComp, {
- defaultProps: { onData: onFormData },
- memoryRouter: { wrapComponent: false },
- });
+ render( );
- setup();
+ expect(onFormData).toHaveBeenCalledTimes(1);
- const [{ data }] = onFormData.mock.calls[
- onFormData.mock.calls.length - 1
- ] as Parameters;
-
- expect(data.internal).toEqual({
- name: 'John',
- lastName: 'Snow',
- });
+ expect(onFormData).toHaveBeenCalledWith(
+ expect.objectContaining({
+ data: expect.objectContaining({
+ internal: {
+ name: 'John',
+ lastName: 'Snow',
+ },
+ }),
+ })
+ );
});
- test('should update the form.defaultValue when a field defaultValue is provided through prop', () => {
+ test('should update the form.defaultValue when a field defaultValue is provided through prop', async () => {
let formHook: FormHook | null = null;
const TestComp = () => {
@@ -94,11 +92,7 @@ describe(' ', () => {
);
};
- const setup = registerTestBed(TestComp, {
- memoryRouter: { wrapComponent: false },
- });
-
- const { find } = setup();
+ render( );
expect(formHook!.__getFormDefaultValue()).toEqual({
name: 'John',
@@ -109,9 +103,8 @@ describe(' ', () => {
});
// Unmounts the field and make sure the form.defaultValue has been updated
- act(() => {
- find('unmountField').simulate('click');
- });
+ const unmountButton = screen.getByTestId('unmountField');
+ await user.click(unmountButton);
expect(formHook!.__getFormDefaultValue()).toEqual({});
});
@@ -177,11 +170,24 @@ describe(' ', () => {
const toString = (value: unknown): string =>
typeof value === 'string' ? value : JSON.stringify(value);
- const setup = registerTestBed(TestComp, {
- memoryRouter: { wrapComponent: false },
- });
+ const setup = async (props: Props) => {
+ const renderResult = render( );
+ return {
+ ...renderResult,
+ user,
+ form: {
+ setInputValue: async (testId: string, value: string) => {
+ const input = screen.getByTestId(testId);
+ await user.clear(input);
+ if (value) {
+ await user.type(input, value);
+ }
+ },
+ },
+ };
+ };
- [
+ test.each([
{
description: 'should update the state for field without default values',
initialValue: '',
@@ -218,38 +224,42 @@ describe(' ', () => {
defaultValue: { initial: 'value' },
},
},
- ].forEach(({ description, fieldProps, initialValue, changedValue }) => {
- test(description, async () => {
- const { form } = await setup({ fieldProps });
-
- expect(lastFieldState()).toEqual({
- isPristine: true,
- isDirty: false,
- isModified: false,
- value: initialValue,
- });
-
- await act(async () => {
- form.setInputValue('testField', toString(changedValue));
- });
-
- expect(lastFieldState()).toEqual({
- isPristine: false,
- isDirty: true,
- isModified: true,
- value: changedValue,
- });
-
- // Put back to the initial value --> isModified should be false
- await act(async () => {
- form.setInputValue('testField', toString(initialValue));
- });
- expect(lastFieldState()).toEqual({
- isPristine: false,
- isDirty: true,
- isModified: false,
- value: initialValue,
- });
+ ])('$description', async ({ fieldProps, initialValue, changedValue }) => {
+ await setup({ fieldProps });
+
+ expect(lastFieldState()).toEqual({
+ isPristine: true,
+ isDirty: false,
+ isModified: false,
+ value: initialValue,
+ });
+
+ const testField = screen.getByTestId('testField');
+ // For object fields, select all + paste to avoid triggering onChange with empty string
+ await user.click(testField);
+ await user.keyboard('{Control>}a{/Control}');
+ await user.paste(toString(changedValue));
+
+ expect(lastFieldState()).toEqual({
+ isPristine: false,
+ isDirty: true,
+ isModified: true,
+ value: changedValue,
+ });
+
+ // Put back to the initial value --> isModified should be false
+ if (toString(initialValue)) {
+ await user.keyboard('{Control>}a{/Control}');
+ await user.paste(toString(initialValue));
+ } else {
+ // For empty initial value, clear the field
+ await user.clear(testField);
+ }
+ expect(lastFieldState()).toEqual({
+ isPristine: false,
+ isDirty: true,
+ isModified: false,
+ value: initialValue,
});
});
});
@@ -316,9 +326,20 @@ describe(' ', () => {
};
const setup = (fieldConfig?: FieldConfig) => {
- return registerTestBed(getTestComp(fieldConfig), {
- memoryRouter: { wrapComponent: false },
- })() as TestBed;
+ const TestComp = getTestComp(fieldConfig);
+ render( );
+ return {
+ user,
+ form: {
+ setInputValue: async (testId: string, value: string) => {
+ const input = screen.getByTestId(testId);
+ await user.clear(input);
+ if (value) {
+ await user.type(input, value);
+ }
+ },
+ },
+ };
};
test('should update the form validity whenever the field value changes', async () => {
@@ -350,7 +371,7 @@ describe(' ', () => {
await act(async () => {
const validatePromise = formHook!.validate(); // ...until we validate the form
- jest.advanceTimersByTime(0);
+ await jest.runAllTimersAsync();
await validatePromise;
});
@@ -359,7 +380,9 @@ describe(' ', () => {
// Change to a non empty string to pass validation
await act(async () => {
- setInputValue('myField', 'changedValue');
+ const setInputValuePromise = setInputValue('myField', 'changedValue');
+ await jest.runAllTimersAsync();
+ await setInputValuePromise;
});
({ isValid } = formHook);
@@ -367,7 +390,9 @@ describe(' ', () => {
// Change back to an empty string to fail validation
await act(async () => {
- setInputValue('myField', '');
+ const setInputValuePromise = setInputValue('myField', '');
+ await jest.runAllTimersAsync();
+ await setInputValuePromise;
});
({ isValid } = formHook);
@@ -391,7 +416,6 @@ describe(' ', () => {
};
const {
- find,
form: { setInputValue },
} = setup(fieldConfig);
@@ -399,30 +423,25 @@ describe(' ', () => {
// Trigger validation...
await act(async () => {
- setInputValue('myField', 'changedValue');
+ const setIinputValuePromise = setInputValue('myField', 'changedValue');
+ await jest.advanceTimersToNextTimerAsync(0);
+ await setIinputValuePromise;
});
expect(fieldHook?.isValidating).toBe(true);
- // Unmount the field
- await act(async () => {
- find('unmountFieldBtn').simulate('click');
- });
-
const originalConsoleError = console.error; // eslint-disable-line no-console
const spyConsoleError = jest.fn((message) => {
originalConsoleError(message);
});
console.error = spyConsoleError; // eslint-disable-line no-console
- // Move the timer to resolve the validator
- await act(async () => {
- jest.advanceTimersByTime(5000);
- });
+ const unmountBtn = screen.getByTestId('unmountFieldBtn');
+ await user.click(unmountBtn);
// The test should not display any warning
// "Can't perform a React state update on an unmounted component."
- expect(spyConsoleError.mock.calls.length).toBe(0);
+ expect(spyConsoleError).not.toHaveBeenCalled();
console.error = originalConsoleError; // eslint-disable-line no-console
});
@@ -442,7 +461,6 @@ describe(' ', () => {
};
const {
- find,
form: { setInputValue },
} = setup(fieldConfig);
@@ -450,12 +468,14 @@ describe(' ', () => {
setInputValue('myField', 'changedValue');
});
- expect(validator).toHaveBeenCalledTimes(1);
+ // userEvent.type triggers validation for each character typed
+ expect(validator).toHaveBeenCalled();
validator.mockReset();
await act(async () => {
// Change the field path
- find('changeFieldPathBtn').simulate('click');
+ const changePathBtn = screen.getByTestId('changeFieldPathBtn');
+ await user.click(changePathBtn);
});
expect(validator).not.toHaveBeenCalled();
@@ -578,10 +598,28 @@ describe(' ', () => {
};
const setupDynamicData = (defaultProps?: Partial) => {
- return registerTestBed(TestComp, {
- memoryRouter: { wrapComponent: false },
- defaultProps,
- })() as TestBed;
+ const { unmount } = render( );
+ return {
+ unmount,
+ user,
+ form: {
+ setInputValue: async (testId: string, value: string) => {
+ const input = screen.getByTestId(testId);
+ await user.clear(input);
+ if (value) {
+ await user.type(input, value);
+ }
+ },
+ },
+ find: (testId: string) => ({
+ simulate: async (event: string) => {
+ if (event === 'click') {
+ const button = screen.getByTestId(testId);
+ await user.click(button);
+ }
+ },
+ }),
+ };
};
beforeEach(() => {
@@ -592,7 +630,9 @@ describe(' ', () => {
const { form, find } = setupDynamicData();
await act(async () => {
- form.setInputValue('nameField', 'newValue');
+ const inputValuePromise = form.setInputValue('nameField', 'newValue');
+ await jest.runAllTimersAsync();
+ await inputValuePromise;
});
// If the field is validating this will prevent the form from being submitted as
// it will wait for all the fields to finish validating to return the form validity.
@@ -601,49 +641,54 @@ describe(' ', () => {
// Let's wait 10 sec to make sure the validation does not complete
// until the observable receives a value
await act(async () => {
- jest.advanceTimersByTime(10000);
+ await jest.advanceTimersByTimeAsync(10000);
});
// The field is still validating as the validationDataProvider has not resolved yet
// (no value has been sent to the observable)
expect(nameFieldHook?.isValidating).toBe(true);
// We now send a valid value to the observable
- await act(async () => {
- find('setValidValueBtn').simulate('click');
- });
+ await find('setValidValueBtn').simulate('click');
expect(nameFieldHook?.isValidating).toBe(false);
expect(nameFieldHook?.isValid).toBe(true);
// Let's change the input value to trigger the validation once more
await act(async () => {
- form.setInputValue('nameField', 'anotherValue');
+ const inputValuePromise = form.setInputValue('nameField', 'anotherValue');
+ await jest.runAllTimersAsync();
+ await inputValuePromise;
});
expect(nameFieldHook?.isValidating).toBe(true);
// And send an invalid value to the observable
- await act(async () => {
- find('setInvalidValueBtn').simulate('click');
- });
+ await find('setInvalidValueBtn').simulate('click');
expect(nameFieldHook?.isValidating).toBe(false);
expect(nameFieldHook?.isValid).toBe(false);
expect(nameFieldHook?.getErrorsMessages()).toBe('Invalid dynamic data');
});
test('it should access dynamic data provided through props', async () => {
- let { form } = setupDynamicData({ validationData: 'good' });
+ let result = setupDynamicData({ validationData: 'good' });
await act(async () => {
- form.setInputValue('lastNameField', 'newValue');
+ const setInputValuePromise = result.form.setInputValue('lastNameField', 'newValue');
+ await jest.runAllTimersAsync();
+ await setInputValuePromise;
});
// As this is a sync validation it should not be validating anymore at this stage
expect(lastNameFieldHook?.isValidating).toBe(false);
expect(lastNameFieldHook?.isValid).toBe(true);
+ // Cleanup before re-rendering
+ result.unmount();
+
// Now let's provide invalid dynamic data through props
- ({ form } = setupDynamicData({ validationData: 'bad' }));
+ result = setupDynamicData({ validationData: 'bad' });
await act(async () => {
- form.setInputValue('lastNameField', 'newValue');
+ const setInputValuePromise = result.form.setInputValue('lastNameField', 'newValue');
+ await jest.runAllTimersAsync();
+ await setInputValuePromise;
});
expect(lastNameFieldHook?.isValidating).toBe(false);
expect(lastNameFieldHook?.isValid).toBe(false);
@@ -696,12 +741,7 @@ describe(' ', () => {
};
test('should call each handler at expected lifecycle', async () => {
- const setup = registerTestBed(TestComp, {
- memoryRouter: { wrapComponent: false },
- defaultProps: { onForm: onFormHook },
- });
-
- const testBed = setup() as TestBed;
+ render( );
if (!formHook) {
throw new Error(
@@ -709,8 +749,6 @@ describe(' ', () => {
);
}
- const { form } = testBed;
-
expect(deserializer).toBeCalled();
expect(serializer).not.toBeCalled();
expect(formatter).not.toBeCalled();
@@ -718,9 +756,9 @@ describe(' ', () => {
const internalFormData = formHook.__getFormData$().value;
expect(internalFormData.name).toEqual('John-deserialized');
- await act(async () => {
- form.setInputValue('myField', 'Mike');
- });
+ const myField = screen.getByTestId('myField');
+ await user.clear(myField);
+ await user.type(myField, 'Mike');
expect(formatter).toBeCalled(); // Formatters are executed on each value change
expect(serializer).not.toBeCalled(); // Serializer are executed *only** when outputting the form data
@@ -732,9 +770,15 @@ describe(' ', () => {
// Make sure that when we reset the form values, we don't serialize the fields
serializer.mockReset();
- await act(async () => {
+ act(() => {
formHook!.reset();
});
+
+ await act(async () => {
+ // Wait for the form to reset
+ await jest.runAllTimersAsync();
+ });
+
expect(serializer).not.toBeCalled();
});
});
@@ -776,25 +820,17 @@ describe(' ', () => {
it('allows function components', () => {
const Component = () => ;
- const setup = registerTestBed(TestComp, {
- defaultProps: { onForm: onFormHook, component: Component },
- memoryRouter: { wrapComponent: false },
- });
- const testBed = setup() as TestBed;
+ render( );
- expect(testBed.exists('function-component')).toEqual(true);
+ expect(screen.getByTestId('function-component')).toBeInTheDocument();
expect(formHook?.getFormData()).toEqual({ name: 'myName' });
});
it('allows memoized function components', () => {
const Component = React.memo(() => );
- const setup = registerTestBed(TestComp, {
- defaultProps: { onForm: onFormHook, component: Component },
- memoryRouter: { wrapComponent: false },
- });
- const testBed = setup() as TestBed;
+ render( );
- expect(testBed.exists('memoized-component')).toEqual(true);
+ expect(screen.getByTestId('memoized-component')).toBeInTheDocument();
expect(formHook?.getFormData()).toEqual({ name: 'myName' });
});
});
@@ -827,9 +863,20 @@ describe(' ', () => {
};
const setup = (fieldConfig?: FieldConfig) => {
- return registerTestBed(getTestComp(fieldConfig), {
- memoryRouter: { wrapComponent: false },
- })() as TestBed;
+ const TestComp = getTestComp(fieldConfig);
+ render( );
+ return {
+ user,
+ form: {
+ setInputValue: async (testId: string, value: string) => {
+ const input = screen.getByTestId(testId) as HTMLInputElement;
+ // With legacyFakeTimers, user.clear() hangs, so set value directly
+ await act(async () => {
+ fireEvent.change(input, { target: { value } });
+ });
+ },
+ },
+ };
};
test('calls onChange() prop when value state changes', async () => {
@@ -840,11 +887,14 @@ describe(' ', () => {
expect(onChange).toBeCalledTimes(0);
await act(async () => {
- setInputValue('myField', 'foo');
+ const setInputValuePromise = setInputValue('myField', 'foo');
+ await jest.runAllTimersAsync();
+ await setInputValuePromise;
});
- expect(onChange).toBeCalledTimes(1);
- expect(onChange).toBeCalledWith('foo');
+ // userEvent.type triggers onChange for each character
+ expect(onChange).toHaveBeenCalled();
+ expect(onChange.mock.calls[onChange.mock.calls.length - 1][0]).toBe('foo');
});
test('calls onError() prop when validation state changes', async () => {
@@ -859,16 +909,24 @@ describe(' ', () => {
});
expect(onError).toBeCalledTimes(0);
- await act(async () => {
- setInputValue('myField', '0');
+ await setInputValue('myField', '0');
+ // Trigger validation
+ let field = screen.getByTestId('myField');
+ act(() => {
+ field.blur();
});
- expect(onError).toBeCalledTimes(1);
- expect(onError).toBeCalledWith(['oops!']);
- await act(async () => {
- setInputValue('myField', '1');
+
+ expect(onError).toHaveBeenCalled();
+ expect(onError).toHaveBeenCalledWith(['oops!']);
+
+ await setInputValue('myField', '1');
+ // Trigger validation
+ field = screen.getByTestId('myField');
+
+ act(() => {
+ field.blur();
});
- expect(onError).toBeCalledTimes(2);
- expect(onError).toBeCalledWith(null);
+ expect(onError).toHaveBeenCalledWith(null);
});
});
});
diff --git a/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/components/use_multi_fields.test.tsx b/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/components/use_multi_fields.test.tsx
index 04560f7c10b6d..0caad0327cb47 100644
--- a/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/components/use_multi_fields.test.tsx
+++ b/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/components/use_multi_fields.test.tsx
@@ -8,69 +8,70 @@
*/
import React, { useState } from 'react';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
-import { registerTestBed } from '../shared_imports';
import type { FieldHook } from '../types';
import { useForm } from '../hooks/use_form';
import { Form } from './form';
import { UseMultiFields } from './use_multi_fields';
-describe(' ', () => {
- beforeAll(() => {
- jest.useFakeTimers({ legacyFakeTimers: true });
- });
-
- afterAll(() => {
- jest.useRealTimers();
- });
+const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
+const onFieldsMock = jest.fn();
- const fields = {
- foo: { path: 'foo' },
- bar: { path: 'bar' },
- };
+const fields = {
+ foo: { path: 'foo' },
+ bar: { path: 'bar' },
+};
- const TestComp = ({ onFields }: { onFields: (fields: { [x: string]: FieldHook }) => void }) => {
- const { form } = useForm();
- const [stateFields, setStateFields] = useState<{ [key: string]: any }>(fields);
+const TestComp = ({ onFields }: { onFields: (fields: { [x: string]: FieldHook }) => void }) => {
+ const { form } = useForm();
+ const [stateFields, setStateFields] = useState<{ [key: string]: any }>(fields);
- const changeStateFields = () => {
- // We'll make sure that if other fields are passed down after the initial
- // rendering of the change does not create new FieldHook as that
- // would break the **order** of hooks declared inside
+ const changeStateFields = () => {
+ // We'll make sure that if other fields are passed down after the initial
+ // rendering of the change does not create new FieldHook as that
+ // would break the **order** of hooks declared inside
- setStateFields({
- aaa: { path: 'aaa' }, // we add this field that will come first when sorting() A-Z
- ...fields,
- });
- };
-
- return (
-
- );
+ setStateFields({
+ aaa: { path: 'aaa' }, // we add this field that will come first when sorting() A-Z
+ ...fields,
+ });
};
- test('it should return 2 hook fields', () => {
- const onFields = jest.fn();
+ return (
+
+ );
+};
+
+beforeAll(() => {
+ jest.useFakeTimers();
+});
- const setup = registerTestBed(TestComp, {
- defaultProps: { onFields },
- memoryRouter: { wrapComponent: false },
- });
+afterAll(() => {
+ jest.useRealTimers();
+});
+
+beforeEach(() => {
+ jest.clearAllMocks();
+});
- setup();
+describe(' ', () => {
+ test('it should return 2 hook fields', () => {
+ render( );
- expect(onFields).toHaveBeenCalled();
- const fieldsReturned = onFields.mock.calls[0][0];
+ expect(onFieldsMock).toHaveBeenCalled();
+ const fieldsReturned = onFieldsMock.mock.calls[0][0];
expect(fieldsReturned.foo.path).toBe(fields.foo.path);
expect(fieldsReturned.foo.isPristine).toBeDefined(); // It's a FieldHook!
@@ -78,25 +79,23 @@ describe(' ', () => {
expect(fieldsReturned.bar.isPristine).toBeDefined();
});
- test('it should keep a stable ref of initial fields passed', () => {
- const onFields = jest.fn();
+ test('it should keep a stable ref of initial fields passed', async () => {
+ render( );
- const setup = registerTestBed(TestComp, {
- defaultProps: { onFields },
- memoryRouter: { wrapComponent: false },
- });
-
- const { find } = setup();
-
- expect(onFields).toBeCalledTimes(1);
- let fieldsReturned = onFields.mock.calls[0][0] as { [key: string]: FieldHook };
+ expect(onFieldsMock).toBeCalledTimes(1);
+ let fieldsReturned = onFieldsMock.mock.calls[0][0] as { [key: string]: FieldHook };
let paths = Object.values(fieldsReturned).map(({ path }) => path);
expect(paths).toEqual(['bar', 'foo']);
// We change the fields passed down to
- find('changeFields').simulate('click');
- expect(onFields).toBeCalledTimes(2);
- fieldsReturned = onFields.mock.calls[1][0] as { [key: string]: FieldHook };
+ const button = await screen.findByTestId('changeFields');
+ await user.click(button);
+
+ // Check that onFields was called again (button click may trigger multiple re-renders)
+ expect(onFieldsMock.mock.calls.length).toBeGreaterThan(1);
+ fieldsReturned = onFieldsMock.mock.calls[onFieldsMock.mock.calls.length - 1][0] as {
+ [key: string]: FieldHook;
+ };
paths = Object.values(fieldsReturned).map(({ path }) => path);
// We still get the same 2 fields originally passed
diff --git a/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.test.tsx b/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.test.tsx
index 6d21956a70c87..8b5a02fd1743c 100644
--- a/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.test.tsx
+++ b/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.test.tsx
@@ -7,8 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
-import { act } from 'react-dom/test-utils';
-import { registerTestBed } from '../shared_imports';
+import { render, act } from '@testing-library/react';
import { Form, UseField } from '../components';
import React from 'react';
@@ -19,7 +18,7 @@ import { VALIDATION_TYPES } from '..';
describe('useField() hook', () => {
beforeAll(() => {
- jest.useFakeTimers({ legacyFakeTimers: true });
+ jest.useFakeTimers();
});
afterAll(() => {
@@ -56,15 +55,19 @@ describe('useField() hook', () => {
],
});
- registerTestBed(TestForm)();
+ render( );
let validateResponse: FieldValidateResponse;
await act(async () => {
- validateResponse = await fieldHook!.validate({
+ const validatePromise = fieldHook!.validate({
value: EMPTY_VALUE,
validationType: VALIDATION_TYPES.ARRAY_ITEM,
});
+
+ await jest.runAllTimersAsync();
+
+ validateResponse = await validatePromise;
});
// validation fails for ARRAY_ITEM with a non-blocking validation error
@@ -96,15 +99,19 @@ describe('useField() hook', () => {
],
});
- registerTestBed(TestForm)();
+ render( );
let validateResponse: FieldValidateResponse;
await act(async () => {
- validateResponse = await fieldHook!.validate({
+ const validatePromise = fieldHook!.validate({
value: EMPTY_VALUE,
validationType: VALIDATION_TYPES.ARRAY_ITEM,
});
+
+ await jest.runAllTimersAsync();
+
+ validateResponse = await validatePromise;
});
// validation fails for ARRAY_ITEM with a blocking validation error
@@ -136,15 +143,19 @@ describe('useField() hook', () => {
],
});
- registerTestBed(TestForm)();
+ render( );
- act(() => {
+ await act(async () => {
// This should **not** call our validator as it is of type ARRAY_ITEM
// and here, as we don't specify the validation type, we validate the default "FIELD" type.
- fieldHook!.validate({
+ const validatePromise = fieldHook!.validate({
value: 'foo',
validationType: undefined, // Although not necessary adding it to be explicit
});
+
+ await jest.runAllTimersAsync();
+
+ await validatePromise;
});
expect(validatorFn).toBeCalledTimes(0);
@@ -180,14 +191,15 @@ describe('useField() hook', () => {
}
);
- const wrapper = registerTestBed(TestForm, {
- memoryRouter: { wrapComponent: false },
- })({ showField1: true, showField2: true });
+ const { rerender } = render( );
expect(field2ValidatorFn).toBeCalledTimes(0);
+ rerender( );
+
await act(async () => {
- wrapper.setProps({ showField1: false });
+ await jest.runAllTimersAsync();
});
+
expect(field2ValidatorFn).toBeCalledTimes(1);
});
});
diff --git a/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.test.tsx b/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.test.tsx
index 882fe1d1f4cdd..22bc6444484e9 100644
--- a/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.test.tsx
+++ b/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/hooks/use_form.test.tsx
@@ -8,10 +8,10 @@
*/
import React, { useEffect, useState } from 'react';
-import { act } from 'react-dom/test-utils';
+import { render, screen, act } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
-import type { TestBed } from '../shared_imports';
-import { registerTestBed, getRandomString } from '../shared_imports';
+import { getRandomString } from '../shared_imports';
import { emptyField } from '../../helpers/field_validators';
import { ComboBoxField } from '../../components';
import { Form, UseField, UseArray } from '../components';
@@ -40,26 +40,25 @@ const onFormHook = (_form: FormHook) => {
formHook = _form;
};
-describe('useForm() hook', () => {
- beforeAll(() => {
- jest.useFakeTimers({ legacyFakeTimers: true });
- });
+beforeAll(() => {
+ jest.useFakeTimers();
+});
- afterAll(() => {
- jest.useRealTimers();
- });
+afterAll(() => {
+ jest.useRealTimers();
+});
- beforeEach(() => {
- formHook = null;
- });
+beforeEach(() => {
+ jest.clearAllMocks();
+ formHook = null;
+});
- describe('form.submit() & config.onSubmit()', () => {
- const onFormData = jest.fn();
+const user = userEvent.setup({ advanceTimers: jest.advanceTimersByTime });
- afterEach(() => {
- onFormData.mockReset();
- });
+describe('useForm() hook', () => {
+ const onFormData = jest.fn();
+ describe('form.submit() & config.onSubmit()', () => {
test('should receive the form data and the validity of the form', async () => {
const TestComp = ({ onData }: Props) => {
const { form } = useForm({ onSubmit: onData });
@@ -72,21 +71,16 @@ describe('useForm() hook', () => {
);
};
- const setup = registerTestBed(TestComp, {
- defaultProps: { onData: onFormData },
- memoryRouter: { wrapComponent: false },
- });
+ render( );
- const {
- component,
- form: { setInputValue },
- } = setup() as TestBed;
+ const usernameField = screen.getByTestId('usernameField');
+ await user.clear(usernameField);
+ await user.type(usernameField, 'John');
- await act(async () => {
- setInputValue('usernameField', 'John');
- component.find('button').simulate('click');
- jest.advanceTimersByTime(0);
- });
+ const button = screen.getByRole('button');
+ await user.click(button);
+
+ expect(onFormData).toHaveBeenCalled();
const [formData, isValid] = onFormData.mock.calls[onFormData.mock.calls.length - 1];
@@ -110,15 +104,7 @@ describe('useForm() hook', () => {
);
};
- const setup = registerTestBed(TestComp, {
- defaultProps: { onData: onFormData },
- memoryRouter: { wrapComponent: false },
- });
-
- const {
- component,
- form: { setInputValue },
- } = setup() as TestBed;
+ render( );
const expectedData = {
address: {
@@ -130,15 +116,20 @@ describe('useForm() hook', () => {
tags: ['Belgium', 'Europe'],
};
- await act(async () => {
- setInputValue('countryCodeField', expectedData.address.country.code);
- setInputValue('addressNote1Field', expectedData.address.notes[0]);
- setInputValue('tagField1', expectedData.tags[0]);
- setInputValue('tagField2', expectedData.tags[1]);
+ const countryCodeField = screen.getByTestId('countryCodeField');
+ const addressNote1Field = screen.getByTestId('addressNote1Field');
+ const tagField1 = screen.getByTestId('tagField1');
+ const tagField2 = screen.getByTestId('tagField2');
- component.find('button').simulate('click');
- jest.advanceTimersByTime(0);
- });
+ await user.type(countryCodeField, expectedData.address.country.code);
+ await user.type(addressNote1Field, expectedData.address.notes[0]);
+ await user.type(tagField1, expectedData.tags[0]);
+ await user.type(tagField2, expectedData.tags[1]);
+
+ const button = screen.getByRole('button');
+ await user.click(button);
+
+ expect(onFormData).toHaveBeenCalled();
const [formData] = onFormData.mock.calls[onFormData.mock.calls.length - 1];
@@ -169,14 +160,7 @@ describe('useForm() hook', () => {
);
};
- const setup = registerTestBed(TestComp, {
- defaultProps: { onForm: onFormHook },
- memoryRouter: { wrapComponent: false },
- });
-
- const {
- form: { setInputValue },
- } = setup() as TestBed;
+ render( );
if (!formHook) {
throw new Error(
@@ -189,18 +173,20 @@ describe('useForm() hook', () => {
await act(async () => {
const submitPromise = formHook!.submit();
- jest.advanceTimersByTime(0);
+ await jest.runAllTimersAsync();
({ data, isValid } = await submitPromise);
});
expect(isValid).toBe(true);
expect(data).toEqual({ username: 'initialValue' });
- await act(async () => {
- setInputValue('myField', 'wrongValue'); // Validation will fail
+ const myField = screen.getByTestId('myField');
+ await user.clear(myField);
+ await user.type(myField, 'wrongValue'); // Validation will fail
+ await act(async () => {
const submitPromise = formHook!.submit();
- jest.advanceTimersByTime(0);
+ await jest.runAllTimersAsync();
({ data, isValid } = await submitPromise);
});
@@ -212,12 +198,6 @@ describe('useForm() hook', () => {
});
describe('form.subscribe()', () => {
- const onFormData = jest.fn();
-
- afterEach(() => {
- onFormData.mockReset();
- });
-
test('should allow subscribing to the form data changes and provide a handler to build the form data', async () => {
const TestComp = ({ onData }: { onData: OnUpdateHandler }) => {
const { form } = useForm({
@@ -242,14 +222,7 @@ describe('useForm() hook', () => {
);
};
- const setup = registerTestBed(TestComp, {
- defaultProps: { onData: onFormData },
- memoryRouter: { wrapComponent: false },
- });
-
- const {
- form: { setInputValue },
- } = setup() as TestBed;
+ render( );
let [{ data, isValid }] = onFormData.mock.calls[
onFormData.mock.calls.length - 1
@@ -260,10 +233,9 @@ describe('useForm() hook', () => {
expect(isValid).toBeUndefined();
// Make some changes to the form fields
- await act(async () => {
- setInputValue('usernameField', 'John');
- jest.advanceTimersByTime(0);
- });
+ const usernameField = screen.getByTestId('usernameField');
+ await user.clear(usernameField);
+ await user.type(usernameField, 'John');
[{ data, isValid }] = onFormData.mock.calls[
onFormData.mock.calls.length - 1
@@ -278,12 +250,6 @@ describe('useForm() hook', () => {
});
describe('config.defaultValue', () => {
- const onFormData = jest.fn();
-
- afterEach(() => {
- onFormData.mockReset();
- });
-
test('should set the default value of a field ', () => {
const defaultValue = {
title: getRandomString(),
@@ -309,10 +275,7 @@ describe('useForm() hook', () => {
);
};
- registerTestBed(TestComp, {
- defaultProps: { onData: onFormData },
- memoryRouter: { wrapComponent: false },
- })();
+ render( );
expect(onFormData.mock.calls.length).toBe(1);
@@ -334,7 +297,7 @@ describe('useForm() hook', () => {
});
});
- test('should be updated with the UseField "defaultValue" prop', () => {
+ test('should be updated with the UseField "defaultValue" prop', async () => {
const TestComp = () => {
const { form } = useForm({ defaultValue: { name: 'Mike' } });
const [_, setDate] = useState(new Date());
@@ -351,14 +314,13 @@ describe('useForm() hook', () => {
);
};
- const { find } = registerTestBed(TestComp, { memoryRouter: { wrapComponent: false } })();
+ render( );
expect(formHook?.__getFormDefaultValue()).toEqual({ name: 'John' });
// Make sure a re-render of the component does not re-update the defaultValue
- act(() => {
- find('forceUpdateBtn').simulate('click');
- });
+ const forceUpdateBtn = screen.getByTestId('forceUpdateBtn');
+ await user.click(forceUpdateBtn);
expect(formHook?.__getFormDefaultValue()).toEqual({ name: 'John' });
});
@@ -400,15 +362,8 @@ describe('useForm() hook', () => {
);
};
- const setup = registerTestBed(TestComp, {
- defaultProps: { onForm: onFormHook },
- memoryRouter: { wrapComponent: false },
- });
-
test('should put back the defaultValue for each field', async () => {
- const {
- form: { setInputValue },
- } = setup() as TestBed;
+ render( );
if (!formHook) {
throw new Error(
@@ -418,35 +373,36 @@ describe('useForm() hook', () => {
let formData: Partial = {};
- await act(async () => {
- formData = formHook!.getFormData();
- });
+ formData = formHook!.getFormData();
expect(formData).toEqual({
username: 'defaultValue',
city: 'inlineDefaultValue',
deeply: { nested: { value: 'defaultValue' } },
});
- setInputValue('userNameField', 'changedValue');
- setInputValue('cityField', 'changedValue');
- setInputValue('deeplyNestedField', 'changedValue');
+ const userNameField = screen.getByTestId('userNameField');
+ const cityField = screen.getByTestId('cityField');
+ const deeplyNestedField = screen.getByTestId('deeplyNestedField');
- await act(async () => {
- formData = formHook!.getFormData();
- });
+ await user.clear(userNameField);
+ await user.type(userNameField, 'changedValue');
+ await user.clear(cityField);
+ await user.type(cityField, 'changedValue');
+ await user.clear(deeplyNestedField);
+ await user.type(deeplyNestedField, 'changedValue');
+
+ formData = formHook!.getFormData();
expect(formData).toEqual({
username: 'changedValue',
city: 'changedValue',
deeply: { nested: { value: 'changedValue' } },
});
- await act(async () => {
+ act(() => {
formHook!.reset();
});
- await act(async () => {
- formData = formHook!.getFormData();
- });
+ formData = formHook!.getFormData();
expect(formData).toEqual({
username: 'defaultValue',
city: 'inlineDefaultValue', // Inline default value is correctly kept after resetting
@@ -455,9 +411,7 @@ describe('useForm() hook', () => {
});
test('should allow to pass a new "defaultValue" object for the fields', async () => {
- const {
- form: { setInputValue },
- } = setup() as TestBed;
+ render( );
if (!formHook) {
throw new Error(
@@ -465,13 +419,20 @@ describe('useForm() hook', () => {
);
}
- setInputValue('userNameField', 'changedValue');
- setInputValue('cityField', 'changedValue');
- setInputValue('deeplyNestedField', 'changedValue');
+ const userNameField = screen.getByTestId('userNameField');
+ const cityField = screen.getByTestId('cityField');
+ const deeplyNestedField = screen.getByTestId('deeplyNestedField');
+
+ await user.clear(userNameField);
+ await user.type(userNameField, 'changedValue');
+ await user.clear(cityField);
+ await user.type(cityField, 'changedValue');
+ await user.clear(deeplyNestedField);
+ await user.type(deeplyNestedField, 'changedValue');
let formData: Partial = {};
- await act(async () => {
+ act(() => {
formHook!.reset({
defaultValue: {
city: () => 'newDefaultValue', // A function can also be passed
@@ -479,9 +440,7 @@ describe('useForm() hook', () => {
},
});
});
- await act(async () => {
- formData = formHook!.getFormData();
- });
+ formData = formHook!.getFormData();
expect(formData).toEqual({
username: 'configDefaultValue', // Back to the config defaultValue as no value was provided when resetting
city: 'newDefaultValue',
@@ -489,14 +448,12 @@ describe('useForm() hook', () => {
});
// Make sure all field are back to the config defautlValue, even when we have a UseField with inline prop "defaultValue"
- await act(async () => {
+ act(() => {
formHook!.reset({
defaultValue: {},
});
});
- await act(async () => {
- formData = formHook!.getFormData();
- });
+ formData = formHook!.getFormData();
expect(formData).toEqual({
username: 'configDefaultValue',
city: 'configDefaultValue', // Inline default value **is not** kept after resetting with undefined "city" value
@@ -532,23 +489,21 @@ describe('useForm() hook', () => {
);
};
- const {
- form: { setInputValue },
- } = registerTestBed(TestResetComp, {
- memoryRouter: { wrapComponent: false },
- })() as TestBed;
+ render( );
let { isValid } = formHook!;
expect(isValid).toBeUndefined();
- await act(async () => {
- setInputValue('myField', 'changedValue');
- });
+ const myField = screen.getByTestId('myField');
+ await user.clear(myField);
+ await user.type(myField, 'changedValue');
+ myField.blur();
+
({ isValid } = formHook!);
expect(isValid).toBe(true);
- await act(async () => {
- // When we reset the form, value is back to "", which is invalid for the field
+ // When we reset the form, value is back to "", which is invalid for the field
+ act(() => {
formHook!.reset();
});
@@ -581,13 +536,13 @@ describe('useForm() hook', () => {
);
};
- registerTestBed(TestComp)();
+ render( );
let isValid: boolean = false;
await act(async () => {
const validatePromise = formHook!.validate();
- jest.advanceTimersByTime(0);
+ await jest.runAllTimersAsync();
isValid = await validatePromise;
});
@@ -624,19 +579,21 @@ describe('useForm() hook', () => {
);
};
- registerTestBed(TestComp)();
+ render( );
let isValid: boolean = false;
- act(() => {
- // We need to call the field validation to mark this field as invalid.
- // This will then mark the form as invalid when calling formHook.validate() below
- fieldHook.validate({ validationType: VALIDATION_TYPES.ARRAY_ITEM });
+ // We need to call the field validation to mark this field as invalid.
+ // This will then mark the form as invalid when calling formHook.validate() below
+ await act(async () => {
+ const validatePromise = fieldHook.validate({ validationType: VALIDATION_TYPES.ARRAY_ITEM });
+ await jest.runAllTimersAsync();
+ await validatePromise;
});
await act(async () => {
const validatePromise = formHook!.validate();
- jest.advanceTimersByTime(0);
+ await jest.runAllTimersAsync();
isValid = await validatePromise;
});
@@ -672,23 +629,29 @@ describe('useForm() hook', () => {
);
};
- registerTestBed(TestComp)();
+ render( );
let isValid: boolean = false;
- await act(async () => {
+ act(() => {
fieldHook.setValue([]);
+ });
+
+ await act(async () => {
const validatePromise = formHook!.validate();
- jest.advanceTimersByTime(0);
+ await jest.runAllTimersAsync();
isValid = await validatePromise;
});
expect(isValid).toBe(false);
- await act(async () => {
+ act(() => {
fieldHook.setValue(['bar']);
+ });
+
+ await act(async () => {
const validatePromise = formHook!.validate();
- jest.advanceTimersByTime(0);
+ await jest.runAllTimersAsync();
isValid = await validatePromise;
});
@@ -736,22 +699,24 @@ describe('useForm() hook', () => {
);
};
- const {
- form: { setInputValue },
- } = registerTestBed(TestComp)() as TestBed;
+ render( );
let errors: string[] = formHook!.getErrors();
expect(errors).toEqual([]);
await act(async () => {
const submitPromise = formHook!.submit();
- jest.advanceTimersByTime(0);
+ await jest.runAllTimersAsync();
await submitPromise;
});
+
errors = formHook!.getErrors();
expect(errors).toEqual(['Field1 can not be empty']);
- await setInputValue('field2', 'bad');
+ const field2 = screen.getByTestId('field2');
+ await user.type(field2, 'bad');
+ field2.blur();
+
errors = formHook!.getErrors();
expect(errors).toEqual(['Field1 can not be empty', 'Field2 is invalid']);
});
@@ -772,7 +737,7 @@ describe('useForm() hook', () => {
);
};
- registerTestBed(TestComp)();
+ render( );
expect(formHook!.getFormData()).toEqual({
field1: 'field1_defaultValue',
@@ -782,7 +747,7 @@ describe('useForm() hook', () => {
},
});
- await act(async () => {
+ act(() => {
formHook!.updateFieldValues({
field1: 'field1_updated',
field2: {
@@ -793,6 +758,10 @@ describe('useForm() hook', () => {
});
});
+ await act(async () => {
+ await jest.runAllTimersAsync();
+ });
+
expect(formHook!.getFormData()).toEqual({
field1: 'field1_updated',
field2: {
@@ -833,13 +802,9 @@ describe('useForm() hook', () => {
);
};
- registerTestBed(TestComp)();
-
- if (formHook === null) {
- throw new Error('Formhook has not been set.');
- }
+ render( );
- expect(formHook.getFormData()).toEqual({
+ expect(formHook!.getFormData()).toEqual({
users: [
{
name: 'John',
@@ -861,11 +826,15 @@ describe('useForm() hook', () => {
],
};
- await act(async () => {
+ act(() => {
formHook!.updateFieldValues(newFormData);
});
- expect(formHook.getFormData()).toEqual(newFormData);
+ await act(async () => {
+ await jest.runAllTimersAsync();
+ });
+
+ expect(formHook!.getFormData()).toEqual(newFormData);
});
test('should update an array of string fields (ComboBox)', async () => {
@@ -880,13 +849,9 @@ describe('useForm() hook', () => {
);
};
- registerTestBed(TestComp)();
-
- if (formHook === null) {
- throw new Error('Formhook has not been set.');
- }
+ render( );
- expect(formHook.getFormData()).toEqual({
+ expect(formHook!.getFormData()).toEqual({
tags: ['foo', 'bar'],
});
@@ -894,11 +859,15 @@ describe('useForm() hook', () => {
tags: ['updated', 'array'],
};
- await act(async () => {
+ act(() => {
formHook!.updateFieldValues(newFormData);
});
- expect(formHook.getFormData()).toEqual(newFormData);
+ await act(async () => {
+ await jest.runAllTimersAsync();
+ });
+
+ expect(formHook!.getFormData()).toEqual(newFormData);
});
test('should update recursively an array of object fields', async () => {
@@ -950,13 +919,9 @@ describe('useForm() hook', () => {
);
};
- registerTestBed(TestComp)();
+ render( );
- if (formHook === null) {
- throw new Error('Formhook has not been set.');
- }
-
- expect(formHook.getFormData()).toEqual({
+ expect(formHook!.getFormData()).toEqual({
users: [
{
name: 'John',
@@ -1000,11 +965,15 @@ describe('useForm() hook', () => {
],
};
- await act(async () => {
+ act(() => {
formHook!.updateFieldValues(newFormData);
});
- expect(formHook.getFormData()).toEqual(newFormData);
+ await act(async () => {
+ await jest.runAllTimersAsync();
+ });
+
+ expect(formHook!.getFormData()).toEqual(newFormData);
});
describe('deserializer', () => {
@@ -1025,13 +994,9 @@ describe('useForm() hook', () => {
};
test('should run deserializer on the new form data provided', async () => {
- registerTestBed(TestComp)();
+ render( );
- if (formHook === null) {
- throw new Error('Formhook has not been set.');
- }
-
- expect(formHook.getFormData()).toEqual({
+ expect(formHook!.getFormData()).toEqual({
foo: { label: 'INITIAL', value: 'initial' },
});
@@ -1039,23 +1004,23 @@ describe('useForm() hook', () => {
foo: 'updated',
};
- await act(async () => {
+ act(() => {
formHook!.updateFieldValues(newFormData);
});
- expect(formHook.getFormData()).toEqual({
+ await act(async () => {
+ await jest.runAllTimersAsync();
+ });
+
+ expect(formHook!.getFormData()).toEqual({
foo: { label: 'UPDATED', value: 'updated' },
});
});
test('should not run deserializer on the new form data provided', async () => {
- registerTestBed(TestComp)();
+ render( );
- if (formHook === null) {
- throw new Error('Formhook has not been set.');
- }
-
- expect(formHook.getFormData()).toEqual({
+ expect(formHook!.getFormData()).toEqual({
foo: { label: 'INITIAL', value: 'initial' },
});
@@ -1063,11 +1028,15 @@ describe('useForm() hook', () => {
foo: 'updated',
};
- await act(async () => {
+ act(() => {
formHook!.updateFieldValues(newFormData, { runDeserializer: false });
});
- expect(formHook.getFormData()).toEqual({
+ await act(async () => {
+ await jest.runAllTimersAsync();
+ });
+
+ expect(formHook!.getFormData()).toEqual({
foo: 'updated',
});
});
diff --git a/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_data.test.tsx b/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_data.test.tsx
index ef77698c3505d..a33c2b17f8003 100644
--- a/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_data.test.tsx
+++ b/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_data.test.tsx
@@ -8,10 +8,9 @@
*/
import React, { useEffect, useRef } from 'react';
-import { act } from 'react-dom/test-utils';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
-import type { TestBed } from '../shared_imports';
-import { registerTestBed } from '../shared_imports';
import { Form, UseField } from '../components';
import { useForm } from './use_form';
import type { HookReturn } from './use_form_data';
@@ -56,7 +55,6 @@ describe('useFormData() hook', () => {
const HookListener = React.memo(HookListenerComp);
describe('form data updates', () => {
- let testBed: TestBed;
let onChangeSpy: jest.Mock;
const getLastMockValue = () => {
@@ -74,31 +72,28 @@ describe('useFormData() hook', () => {
);
};
- const setup = registerTestBed(TestComp, {
- memoryRouter: { wrapComponent: false },
- });
-
beforeEach(() => {
onChangeSpy = jest.fn();
- testBed = setup({ onHookValueChange: onChangeSpy }) as TestBed;
});
test('should return the form data', () => {
+ render( );
+
expect(onChangeSpy).toBeCalledTimes(1);
const [data] = getLastMockValue();
expect(data).toEqual({ title: 'titleInitialValue' });
});
test('should listen to field changes', async () => {
- const {
- form: { setInputValue },
- } = testBed;
+ const user = userEvent.setup();
+ render( );
- await act(async () => {
- setInputValue('titleField', 'titleChanged');
- });
+ const titleField = screen.getByTestId('titleField');
+ await user.clear(titleField);
+ await user.type(titleField, 'titleChanged');
- expect(onChangeSpy).toBeCalledTimes(2);
+ // userEvent.type() triggers onChange for each character
+ expect(onChangeSpy).toHaveBeenCalled();
const [data] = getLastMockValue();
expect(data).toEqual({ title: 'titleChanged' });
});
@@ -123,16 +118,13 @@ describe('useFormData() hook', () => {
);
};
- const setup = registerTestBed(TestComp, {
- memoryRouter: { wrapComponent: false },
- });
-
beforeEach(() => {
onChangeSpy = jest.fn();
- setup({ onHookValueChange: onChangeSpy });
});
test('should expose a handler to build the form data', () => {
+ render( );
+
const [formData] = getLastMockValue();
expect(formData).toEqual({
user: {
@@ -145,7 +137,6 @@ describe('useFormData() hook', () => {
describe('options', () => {
describe('watch', () => {
- let testBed: TestBed;
let onChangeSpy: jest.Mock;
const getLastMockValue = () => {
@@ -168,34 +159,40 @@ describe('useFormData() hook', () => {
);
};
- const setup = registerTestBed(TestComp, {
- memoryRouter: { wrapComponent: false },
- });
-
beforeEach(() => {
onChangeSpy = jest.fn();
- testBed = setup({ watch: 'title', onHookValueChange: onChangeSpy }) as TestBed;
});
test('should not listen to changes on fields we are not interested in', async () => {
- const {
- form: { setInputValue },
- } = testBed;
+ const user = userEvent.setup();
+ render( );
- await act(async () => {
- // Changing a field we are **not** interested in
- setInputValue('subTitleField', 'subTitleChanged');
- // Changing a field we **are** interested in
- setInputValue('titleField', 'titleChanged');
- });
+ const subTitleField = screen.getByTestId('subTitleField');
+ const titleField = screen.getByTestId('titleField');
+
+ const initialCallCount = onChangeSpy.mock.calls.length;
+
+ // Changing a field we are **not** interested in
+ await user.clear(subTitleField);
+ await user.type(subTitleField, 'subTitleChanged');
+
+ // Callback should not have been triggered by subtitle changes
+ expect(onChangeSpy.mock.calls.length).toBe(initialCallCount);
+
+ // Changing a field we **are** interested in
+ await user.clear(titleField);
+ await user.type(titleField, 'titleChanged');
+
+ // Now the callback should have been triggered
+ expect(onChangeSpy.mock.calls.length).toBeGreaterThan(initialCallCount);
const [data] = getLastMockValue();
- expect(data).toEqual({ title: 'titleChanged', subTitle: 'subTitleInitialValue' });
+ // The data includes all current form values (both title and subtitle have changed)
+ expect(data).toEqual({ title: 'titleChanged', subTitle: 'subTitleChanged' });
});
});
describe('form', () => {
- let testBed: TestBed;
let onChangeSpy: jest.Mock;
const getLastMockValue = () => {
@@ -217,26 +214,20 @@ describe('useFormData() hook', () => {
);
};
- const setup = registerTestBed(TestComp, {
- memoryRouter: { wrapComponent: false },
- });
-
beforeEach(() => {
onChangeSpy = jest.fn();
- testBed = setup({ onHookValueChange: onChangeSpy }) as TestBed;
});
test('should allow a form to be provided when the hook is called outside of the FormDataContext', async () => {
- const {
- form: { setInputValue },
- } = testBed;
+ const user = userEvent.setup();
+ render( );
const [initialData] = getLastMockValue();
expect(initialData).toEqual({ title: 'titleInitialValue' });
- await act(async () => {
- setInputValue('titleField', 'titleChanged');
- });
+ const titleField = screen.getByTestId('titleField');
+ await user.clear(titleField);
+ await user.type(titleField, 'titleChanged');
const [updatedData] = getLastMockValue();
expect(updatedData).toEqual({ title: 'titleChanged' });
@@ -244,7 +235,6 @@ describe('useFormData() hook', () => {
});
describe('onChange', () => {
- let testBed: TestBed;
let onChangeSpy: jest.Mock;
let validationSpy: jest.Mock;
@@ -273,20 +263,14 @@ describe('useFormData() hook', () => {
);
};
- const setup = registerTestBed(TestComp, {
- memoryRouter: { wrapComponent: false },
- });
-
beforeEach(() => {
onChangeSpy = jest.fn();
validationSpy = jest.fn();
- testBed = setup({ watch: 'title' }) as TestBed;
});
test('should call onChange handler _before_ running the validations', async () => {
- const {
- form: { setInputValue },
- } = testBed;
+ const user = userEvent.setup();
+ render( );
onChangeSpy.mockReset(); // Reset our counters
validationSpy.mockReset();
@@ -294,9 +278,9 @@ describe('useFormData() hook', () => {
expect(onChangeSpy).not.toHaveBeenCalled();
expect(validationSpy).not.toHaveBeenCalled();
- await act(async () => {
- setInputValue('titleField', 'titleChanged');
- });
+ const titleField = screen.getByTestId('titleField');
+ await user.clear(titleField);
+ await user.type(titleField, 'titleChanged');
expect(onChangeSpy).toHaveBeenCalled();
expect(validationSpy).toHaveBeenCalled();
diff --git a/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_is_modified.test.tsx b/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_is_modified.test.tsx
index 44a5fa73c9ed1..b5ae4916265e7 100644
--- a/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_is_modified.test.tsx
+++ b/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/hooks/use_form_is_modified.test.tsx
@@ -8,9 +8,9 @@
*/
import React, { useState, useEffect } from 'react';
-import { act } from 'react-dom/test-utils';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
-import { registerTestBed } from '../shared_imports';
import { useForm } from './use_form';
import { useFormIsModified } from './use_form_is_modified';
import { Form } from '../components/form';
@@ -67,63 +67,60 @@ describe('useFormIsModified()', () => {
const isFormModified = () =>
onIsModifiedChange.mock.calls[onIsModifiedChange.mock.calls.length - 1][0];
- const setup = registerTestBed(TestComp, {
- defaultProps: { onIsModifiedChange },
- memoryRouter: { wrapComponent: false },
- });
-
test('should return true **only** when the field value differs from its initial value', async () => {
- const { form } = await setup();
+ const user = userEvent.setup();
+ render( );
expect(isFormModified()).toBe(false);
- await act(async () => {
- form.setInputValue('nameField', 'changed');
- });
+ const nameField = screen.getByTestId('nameField');
+ await user.clear(nameField);
+ await user.type(nameField, 'changed');
expect(isFormModified()).toBe(true);
// Put back to the initial value --> isModified should be false
- await act(async () => {
- form.setInputValue('nameField', 'initialValue');
- });
+ await user.clear(nameField);
+ await user.type(nameField, 'initialValue');
+
expect(isFormModified()).toBe(false);
});
test('should accepts a list of field to discard', async () => {
- const { form } = await setup({ discard: ['toDiscard'] });
+ const user = userEvent.setup();
+ render( );
expect(isFormModified()).toBe(false);
- await act(async () => {
- form.setInputValue('toDiscardField', 'changed');
- });
+ const toDiscardField = screen.getByTestId('toDiscardField');
+ await user.clear(toDiscardField);
+ await user.type(toDiscardField, 'changed');
// It should still not be modififed
expect(isFormModified()).toBe(false);
});
test('should take into account if a field is removed from the DOM **and** it existed on the form "defaultValue"', async () => {
- const { find } = await setup();
+ const user = userEvent.setup();
+ render( );
expect(isFormModified()).toBe(false);
- await act(async () => {
- find('hideNameButton').simulate('click');
- });
+ const hideNameButton = screen.getByTestId('hideNameButton');
+ await user.click(hideNameButton);
+
expect(isFormModified()).toBe(true);
// Put back the name
- await act(async () => {
- find('hideNameButton').simulate('click');
- });
+ await user.click(hideNameButton);
+
expect(isFormModified()).toBe(false);
// Hide the lastname which is **not** in the form defaultValue
// this it won't set the form isModified to true
- await act(async () => {
- find('hideLastNameButton').simulate('click');
- });
+ const hideLastNameButton = screen.getByTestId('hideLastNameButton');
+ await user.click(hideLastNameButton);
+
expect(isFormModified()).toBe(false);
});
});
diff --git a/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/shared_imports.ts b/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/shared_imports.ts
index c18ef91e63138..1d1da64dedef4 100644
--- a/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/shared_imports.ts
+++ b/src/platform/plugins/shared/es_ui_shared/static/forms/hook_form_lib/shared_imports.ts
@@ -7,6 +7,5 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
-export type { TestBed } from '@kbn/test-jest-helpers';
// eslint-disable-next-line @kbn/imports/no_boundary_crossing
-export { registerTestBed, getRandomString } from '@kbn/test-jest-helpers';
+export { getRandomString } from '@kbn/test-jest-helpers';
diff --git a/src/platform/plugins/shared/management/public/components/landing/landing.test.tsx b/src/platform/plugins/shared/management/public/components/landing/landing.test.tsx
index 9e215a8026d55..784c97ec3ce15 100644
--- a/src/platform/plugins/shared/management/public/components/landing/landing.test.tsx
+++ b/src/platform/plugins/shared/management/public/components/landing/landing.test.tsx
@@ -8,13 +8,22 @@
*/
import React from 'react';
-import { merge } from 'lodash';
+import { render, screen, act } from '@testing-library/react';
+import { MemoryRouter } from '@kbn/shared-ux-router';
+import { I18nProvider } from '@kbn/i18n-react';
import { coreMock } from '@kbn/core/public/mocks';
-import type { AsyncTestBedConfig, TestBed } from '@kbn/test-jest-helpers';
-import { registerTestBed } from '@kbn/test-jest-helpers';
import { AppContextProvider } from '../management_app/management_context';
import { ManagementLandingPage } from './landing';
+import type { AppDependencies } from '../../types';
+
+beforeAll(() => {
+ jest.useFakeTimers();
+});
+
+afterAll(() => {
+ jest.useRealTimers();
+});
const sectionsMock = [
{
@@ -29,161 +38,135 @@ const sectionsMock = [
},
],
},
-];
-
-const testBedConfig: AsyncTestBedConfig = {
- memoryRouter: {
- initialEntries: [`/management_landing`],
- componentRoutePath: '/management_landing',
- },
- doMountAsync: true,
-};
-
-export const WithAppDependencies =
- (Comp: any, overrides: Record = {}) =>
- (props: Record) => {
- const contextDependencies = {
- appBasePath: 'http://localhost:9001',
- kibanaVersion: '8.10.0',
- cardsNavigationConfig: { enabled: true },
- sections: sectionsMock,
- chromeStyle: 'classic',
- };
-
- return (
- // @ts-ignore
-
-
-
- );
+] as AppDependencies['sections'];
+
+const renderLandingPage = async (overrides: Partial = {}) => {
+ const coreStart = coreMock.createStart();
+ const contextDependencies: AppDependencies = {
+ appBasePath: 'http://localhost:9001',
+ kibanaVersion: '8.10.0',
+ cardsNavigationConfig: { enabled: true },
+ sections: sectionsMock,
+ chromeStyle: 'classic',
+ coreStart,
+ hasEnterpriseLicense: false,
+ ...overrides,
};
-export const setupLandingPage = async (overrides?: Record): Promise => {
- const initTestBed = registerTestBed(
- WithAppDependencies(ManagementLandingPage, overrides),
- testBedConfig
+ const result = render(
+
+
+
+
+
+
+
);
- const testBed = await initTestBed();
- return {
- ...testBed,
- };
+ // Wait for async rendering
+ await act(async () => {
+ await jest.runAllTimersAsync();
+ });
+
+ // Component is rendered when either cards-navigation-page, managementHome (classic) or managementHomeSolution (project) is present
+ const cardsNav = screen.queryByTestId('cards-navigation-page');
+ const managementHome = screen.queryByTestId('managementHome');
+ const managementHomeSolution = screen.queryByTestId('managementHomeSolution');
+ expect(cardsNav || managementHome || managementHomeSolution).toBeTruthy();
+
+ return result;
};
describe('Landing Page', () => {
- let testBed: TestBed;
-
describe('Can be configured through cardsNavigationConfig', () => {
- beforeEach(async () => {
- testBed = await setupLandingPage();
- });
-
test('Shows cards navigation when feature is enabled', async () => {
- const { exists } = testBed;
- expect(exists('cards-navigation-page')).toBe(true);
+ await renderLandingPage();
+ expect(screen.getByTestId('cards-navigation-page')).toBeInTheDocument();
});
test('Hide cards navigation when feature is disabled', async () => {
- testBed = await setupLandingPage({ cardsNavigationConfig: { enabled: false } });
- const { exists } = testBed;
+ await renderLandingPage({ cardsNavigationConfig: { enabled: false } });
- expect(exists('cards-navigation-page')).toBe(false);
- expect(exists('managementHome')).toBe(true);
+ expect(screen.queryByTestId('cards-navigation-page')).not.toBeInTheDocument();
+ expect(screen.getByTestId('managementHome')).toBeInTheDocument();
});
});
describe('Empty prompt', () => {
test('Renders the default empty prompt when chromeStyle is "classic"', async () => {
- testBed = await setupLandingPage({
+ await renderLandingPage({
chromeStyle: 'classic',
cardsNavigationConfig: { enabled: false },
});
- const { exists } = testBed;
-
- expect(exists('managementHome')).toBe(true);
+ expect(screen.getByTestId('managementHome')).toBeInTheDocument();
});
test('Renders the solution empty prompt when chromeStyle is "project"', async () => {
- const coreStart = coreMock.createStart();
-
- testBed = await setupLandingPage({
+ await renderLandingPage({
chromeStyle: 'project',
cardsNavigationConfig: { enabled: false },
- coreStart,
});
- const { exists } = testBed;
-
- expect(exists('managementHome')).toBe(false);
- expect(exists('managementHomeSolution')).toBe(true);
+ expect(screen.queryByTestId('managementHome')).not.toBeInTheDocument();
+ expect(screen.getByTestId('managementHomeSolution')).toBeInTheDocument();
});
});
describe('AutoOps Promotion Callout', () => {
test('Shows AutoOps callout when not in cloud and has enterprise license', async () => {
- testBed = await setupLandingPage({
+ await renderLandingPage({
chromeStyle: 'classic',
cardsNavigationConfig: { enabled: false },
cloud: { isCloudEnabled: false },
hasEnterpriseLicense: true,
});
- const { exists } = testBed;
-
- expect(exists('autoOpsPromotionCallout')).toBe(true);
+ expect(screen.getByTestId('autoOpsPromotionCallout')).toBeInTheDocument();
});
test('Hides AutoOps callout when in cloud environment', async () => {
- testBed = await setupLandingPage({
+ await renderLandingPage({
chromeStyle: 'classic',
cardsNavigationConfig: { enabled: false },
cloud: { isCloudEnabled: true },
hasEnterpriseLicense: true,
});
- const { exists } = testBed;
-
- expect(exists('autoOpsPromotionCallout')).toBe(false);
+ expect(screen.queryByTestId('autoOpsPromotionCallout')).not.toBeInTheDocument();
});
test('Hides AutoOps callout when not having enterprise license', async () => {
- testBed = await setupLandingPage({
+ await renderLandingPage({
chromeStyle: 'classic',
cardsNavigationConfig: { enabled: false },
cloud: { isCloudEnabled: false },
hasEnterpriseLicense: false,
});
- const { exists } = testBed;
-
- expect(exists('autoOpsPromotionCallout')).toBe(false);
+ expect(screen.queryByTestId('autoOpsPromotionCallout')).not.toBeInTheDocument();
});
test('Hides AutoOps callout when in cloud and without enterprise license', async () => {
- testBed = await setupLandingPage({
+ await renderLandingPage({
chromeStyle: 'classic',
cardsNavigationConfig: { enabled: false },
cloud: { isCloudEnabled: true },
hasEnterpriseLicense: false,
});
- const { exists } = testBed;
-
- expect(exists('autoOpsPromotionCallout')).toBe(false);
+ expect(screen.queryByTestId('autoOpsPromotionCallout')).not.toBeInTheDocument();
});
test('Shows AutoOps callout when cloud service is not available but has enterprise license', async () => {
- testBed = await setupLandingPage({
+ await renderLandingPage({
chromeStyle: 'classic',
cardsNavigationConfig: { enabled: false },
hasEnterpriseLicense: true,
// cloud service not provided - defaults to isCloudEnabled: false
});
- const { exists } = testBed;
-
- expect(exists('autoOpsPromotionCallout')).toBe(true);
+ expect(screen.getByTestId('autoOpsPromotionCallout')).toBeInTheDocument();
});
});
});
diff --git a/src/platform/plugins/shared/management/tsconfig.json b/src/platform/plugins/shared/management/tsconfig.json
index 468305cca9f19..7306d7fc146bb 100644
--- a/src/platform/plugins/shared/management/tsconfig.json
+++ b/src/platform/plugins/shared/management/tsconfig.json
@@ -22,7 +22,6 @@
"@kbn/shared-ux-router",
"@kbn/management-cards-navigation",
"@kbn/shared-ux-link-redirect-app",
- "@kbn/test-jest-helpers",
"@kbn/config-schema",
"@kbn/serverless",
"@kbn/shared-ux-error-boundary",