Improve test reliability in preparation of React 18. (#17990)

* Improve test reliability in preparation of React 18.

* Improving more tests.

* Updating `react-select-event`.

* Improving more tests.

* Improving test.

* Using `act` when needed.

* Migrate `connect` test to testing-library.

* Make sure enzyme types are present.
This commit is contained in:
Dennis Oelkers
2024-01-19 14:01:05 +01:00
committed by GitHub
parent 75ce9aca9e
commit c8d84e8e96
18 changed files with 192 additions and 149 deletions

View File

@@ -28,7 +28,7 @@
"jest-environment-jsdom": "29.6.2",
"jest-enzyme": "^7.1.2",
"jest-styled-components": "7.2.0",
"react-select-event": "^5.0.0",
"react-select-event": "5.5.1",
"resize-observer-polyfill": "^1.5.1"
}
}

View File

@@ -19,6 +19,7 @@ import { render, screen, act, fireEvent, waitFor } from 'wrappedTestingLibrary';
import { HTTPHeaderAuthConfigActions } from 'stores/authentication/HTTPHeaderAuthConfigStore';
import HTTPHeaderAuthConfig from 'logic/authentication/HTTPHeaderAuthConfig';
import asMock from 'helpers/mocking/AsMock';
import HTTPHeaderAuthConfigSection from './HTTPHeaderAuthConfigSection';
@@ -39,6 +40,7 @@ describe('<HTTPHeaderAuthConfigSection />', () => {
it('should display loading indicator while loading', async () => {
jest.useFakeTimers();
asMock(HTTPHeaderAuthConfigActions.load).mockImplementationOnce(() => new Promise(() => {}));
render(<HTTPHeaderAuthConfigSection />);

View File

@@ -16,6 +16,7 @@
*/
import * as React from 'react';
import { render, screen, waitFor } from 'wrappedTestingLibrary';
import userEvent from '@testing-library/user-event';
import MenuItem from 'components/bootstrap/MenuItem';
@@ -30,7 +31,7 @@ describe('DropdownButton', () => {
));
const button = await screen.findByRole('button', { name: 'Click me!' });
button.click();
await userEvent.click(button);
await screen.findByRole('menuitem', { name: 'Hey there!' });
});
@@ -45,10 +46,10 @@ describe('DropdownButton', () => {
));
const button = await screen.findByRole('button', { name: 'Click me!' });
button.click();
await userEvent.click(button);
const menuitem = await screen.findByRole('menuitem', { name: 'Hey there!' });
menuitem.click();
await userEvent.click(menuitem);
await waitFor(() => {
expect(onClick).toHaveBeenCalled();

View File

@@ -19,6 +19,7 @@ import React from 'react';
import { screen, render } from 'wrappedTestingLibrary';
import Immutable from 'immutable';
import { defaultUser } from 'defaultMockValues';
import userEvent from '@testing-library/user-event';
import { adminUser } from 'fixtures/users';
import MockAction from 'helpers/mocking/MockAction';
@@ -61,7 +62,7 @@ describe('<URLWhiteListFormModal>', () => {
expect(addButton).toBeInTheDocument();
addButton.click();
await userEvent.click(addButton);
expect(await screen.findByText('Whitelist URLs')).toBeInTheDocument();
expect(screen.getByDisplayValue('http://graylog.com')).toBeInTheDocument();
@@ -110,7 +111,7 @@ describe('<URLWhiteListFormModal>', () => {
expect(addButton).toBeInTheDocument();
addButton.click();
await userEvent.click(addButton);
expect(await screen.findByText('Whitelist URLs')).toBeInTheDocument();
expect(screen.getByDisplayValue('http://localhost(:\\d+)?')).toBeInTheDocument();

View File

@@ -64,14 +64,14 @@ describe('UrlWhitelistForm', () => {
jest.useRealTimers();
});
it('should show allow list toggle and url table', () => {
it('should show allow list toggle and url table', async () => {
const onUpdate = jest.fn();
render(<UrlWhiteListForm urls={config.entries}
disabled={config.disabled}
onUpdate={onUpdate} />);
expect(screen.getByRole('checkbox', { name: /disable whitelist/i })).toBeInTheDocument();
await screen.findByRole('checkbox', { name: /disable whitelist/i });
config.entries.forEach(({ title }) => {
expect(screen.getByDisplayValue(title)).toBeInTheDocument();
@@ -117,8 +117,8 @@ describe('UrlWhitelistForm', () => {
disabled={config.disabled}
onUpdate={onUpdate} />);
const row = screen.getByRole('row', { name: /3/i, exact: true });
const select = within(row).getByText(/exact match/i);
const row = await screen.findByRole('row', { name: /3/i });
const select = await within(row).findByText(/exact match/i);
await selectEvent.openMenu(select);
await selectEvent.select(select, 'Regex');
@@ -138,16 +138,16 @@ describe('UrlWhitelistForm', () => {
);
});
it('should add a new row to the form', () => {
it('should add a new row to the form', async () => {
const onUpdate = jest.fn();
render(<UrlWhiteListForm urls={config.entries}
disabled={config.disabled}
onUpdate={onUpdate} />);
expect(screen.queryByRole('cell', { name: String(config.entries.length + 1) })).not.toBeInTheDocument();
const button = (await screen.findAllByRole('button', { name: /add url/i }))[0];
const button = screen.getAllByRole('button', { name: /add url/i })[0];
expect(screen.queryByRole('cell', { name: String(config.entries.length + 1) })).not.toBeInTheDocument();
expect(button).toBeInTheDocument();
@@ -155,7 +155,9 @@ describe('UrlWhitelistForm', () => {
expect(screen.getByRole('cell', { name: String(config.entries.length + 1) })).toBeInTheDocument();
expect(onUpdate).toHaveBeenCalledTimes(1);
await waitFor(() => {
expect(onUpdate).toHaveBeenCalled();
});
});
it('should delete a row', async () => {
@@ -167,7 +169,7 @@ describe('UrlWhitelistForm', () => {
expect(screen.getByDisplayValue(config.entries[0].title)).toBeInTheDocument();
const row = screen.getByRole('row', { name: /1/i, exact: true });
const row = screen.getByRole('row', { name: /1/i });
const deleteButton = within(row).getByRole('button', { name: /delete entry/i });
expect(deleteButton).toBeInTheDocument();
@@ -244,10 +246,11 @@ describe('UrlWhitelistForm', () => {
disabled={config.disabled}
onUpdate={onUpdate} />);
const row = screen.getByRole('row', { name: /3/i, exact: true });
const row = screen.getByRole('row', { name: /3/i });
const select = within(row).getByText(/exact match/i);
await selectEvent.openMenu(select);
await selectEvent.select(select, 'Regex');
await screen.findByText(/not a valid java regular expression/i);

View File

@@ -15,7 +15,7 @@
* <http://www.mongodb.com/licensing/server-side-public-license>.
*/
import * as React from 'react';
import { render, fireEvent, waitFor, screen } from 'wrappedTestingLibrary';
import { act, render, fireEvent, waitFor, screen } from 'wrappedTestingLibrary';
import selectEvent from 'react-select-event';
import { List } from 'immutable';
@@ -79,7 +79,11 @@ describe('<SettingsSection />', () => {
await screen.findByText('Hours');
fireEvent.change(timeoutAmountInput, { target: { value: '40' } });
await selectEvent.select(timeoutUnitSelect, 'Days');
await act(async () => {
await selectEvent.select(timeoutUnitSelect, 'Days');
});
await selectEvent.select(timezoneSelect, 'Vancouver');
fireEvent.click(submitButton);

View File

@@ -19,9 +19,11 @@ import * as React from 'react';
import { render, screen } from 'wrappedTestingLibrary';
import { createMemoryRouter, RouterProvider } from 'react-router-dom';
import DefaultProviders from 'DefaultProviders';
import userEvent from '@testing-library/user-event';
import Routes from 'routing/Routes';
import { usePluginExports } from 'views/test/testPlugins';
import suppressConsole from 'helpers/suppressConsole';
import useLogout from './useLogout';
@@ -48,7 +50,7 @@ describe('useLogout', () => {
await screen.findByText('Logged in');
const logoutButton = await screen.findByRole('button', { name: 'logout' });
logoutButton.click();
userEvent.click(logoutButton);
await screen.findByText('Logged out');
});
@@ -63,7 +65,7 @@ describe('useLogout', () => {
await screen.findByText('Logged in');
const logoutButton = await screen.findByRole('button', { name: 'logout' });
logoutButton.click();
userEvent.click(logoutButton);
await screen.findByText('Logged out');
@@ -81,7 +83,10 @@ describe('useLogout', () => {
await screen.findByText('Logged in');
const logoutButton = await screen.findByRole('button', { name: 'logout' });
logoutButton.click();
suppressConsole(() => {
userEvent.click(logoutButton);
});
await screen.findByText('Logged out');

View File

@@ -14,16 +14,13 @@
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*/
/// <reference types="jest-enzyme" />
import React from 'react';
import { act } from 'react-dom/test-utils';
import { mount } from 'wrappedEnzyme';
import { act, render, screen } from 'wrappedTestingLibrary';
import Reflux from 'reflux';
import PropTypes from 'prop-types';
import { Map, List } from 'immutable';
import { asMock } from 'helpers/mocking/index';
import type { Store } from 'stores/StoreTypes';
import {
arrayOfMaps,
@@ -39,12 +36,11 @@ import {
import connect, { useStore } from '../connect';
const SimpleComponentWithoutStores = () => <span>Hello World!</span>;
const SimpleStore = Reflux.createStore<{ value: number }>({
const createSimpleStore = () => Reflux.createStore<{ value: number }>({
getInitialState() {
return this.state;
},
setValue(value) {
setValue(value: number) {
this.state = { value };
this.trigger(this.state);
},
@@ -55,6 +51,8 @@ const SimpleStore = Reflux.createStore<{ value: number }>({
},
});
const SimpleStore = createSimpleStore();
const SimpleComponentWithDummyStore = ({ simpleStore }) => {
if (simpleStore && simpleStore.value) {
return <span>Value is: {simpleStore.value}</span>;
@@ -75,61 +73,72 @@ SimpleComponentWithDummyStore.defaultProps = {
describe('connect()', () => {
beforeEach(() => {
SimpleStore.reset();
act(() => {
SimpleStore.reset();
});
});
it('does not do anything if no stores are provided', () => {
it('does not do anything if no stores are provided', async () => {
const Component = connect(SimpleComponentWithoutStores, {});
const wrapper = mount(<Component />);
render(<Component />);
expect(wrapper).toHaveHTML('<span>Hello World!</span>');
await screen.findByText('Hello World!');
});
it('connects component to store without state', () => {
it('connects component to store without state', async () => {
const Component = connect(SimpleComponentWithDummyStore, { simpleStore: SimpleStore });
const wrapper = mount(<Component />);
render(<Component />);
expect(wrapper).toHaveText('No value.');
await screen.findByText('No value.');
});
it('connects component to store with state', () => {
SimpleStore.setValue(42);
const Component = connect(SimpleComponentWithDummyStore, { simpleStore: SimpleStore });
const wrapper = mount(<Component />);
it('connects component to store with state', async () => {
act(() => {
SimpleStore.setValue(42);
});
expect(wrapper).toHaveText('Value is: 42');
const Component = connect(SimpleComponentWithDummyStore, { simpleStore: SimpleStore });
render(<Component />);
await screen.findByText('Value is: 42');
});
it('reflects state changes in store', () => {
it('reflects state changes in store', async () => {
const Component = connect(SimpleComponentWithDummyStore, { simpleStore: SimpleStore });
const wrapper = mount(<Component />);
render(<Component />);
expect(wrapper).toHaveText('No value.');
await screen.findByText('No value.');
SimpleStore.setValue(42);
act(() => {
SimpleStore.setValue(42);
});
expect(wrapper).toHaveText('Value is: 42');
await screen.findByText('Value is: 42');
SimpleStore.noop();
expect(wrapper).toHaveText('Value is: 42');
await screen.findByText('Value is: 42');
SimpleStore.reset();
act(() => {
SimpleStore.reset();
});
expect(wrapper).toHaveText('No value.');
await screen.findByText('No value.');
});
it('allows mangling of props before passing them', () => {
it('allows mangling of props before passing them', async () => {
const Component = connect(
SimpleComponentWithDummyStore,
{ simpleStore: SimpleStore },
({ simpleStore }) => (simpleStore && { simpleStore: { value: simpleStore.value * 2 } }),
);
const wrapper = mount(<Component />);
render(<Component />);
SimpleStore.setValue(42);
act(() => {
SimpleStore.setValue(42);
});
expect(wrapper).toHaveText('Value is: 84');
await screen.findByText('Value is: 84');
});
it('adds meaningful name to wrapper component', () => {
@@ -144,22 +153,26 @@ describe('connect()', () => {
expect(Component.displayName).toEqual('ConnectStoresWrapper[Unknown/Anonymous] stores=simpleStore');
});
it('types store props as optional', () => {
it('types store props as optional', async () => {
const Component = connect(() => <span>hello!</span>, { simpleStore: SimpleStore });
mount(<Component />);
render(<Component />);
await screen.findByText('hello!');
});
it('types mapped props as optional', () => {
it('types mapped props as optional', async () => {
const Component = connect(
() => <span>hello!</span>,
{ simpleStore: SimpleStore },
({ simpleStore }) => (simpleStore && { storeValue: simpleStore.value }),
);
mount(<Component />);
render(<Component />);
await screen.findByText('hello!');
});
it('types props which have a default value (defaultProps) as optional', () => {
const BaseComponent = ({ exampleProp }: { exampleProp: string }) => <span>{exampleProp}</span>;
it('types props which have a default value (defaultProps) as optional', async () => {
const BaseComponent = ({ exampleProp }: {
exampleProp: string
}) => <span>{exampleProp}</span>;
BaseComponent.defaultProps = {
exampleProp: 'hello!',
@@ -170,50 +183,38 @@ describe('connect()', () => {
};
const Component = connect(BaseComponent, { simpleStore: SimpleStore });
mount(<Component />);
render(<Component />);
await screen.findByText('hello!');
});
describe('generates `shouldComponentUpdate`', () => {
const Component: React.ComponentType<{ someProp?: any, foo: number }> = jest.fn(() => <span>Hello!</span>);
const SimplestStore: Store<number> = ({
getInitialState: jest.fn(() => 42),
listen: jest.fn(() => () => {}),
});
afterEach(() => { jest.clearAllMocks(); });
it('comparing empty values properly', () => {
it('comparing empty values properly', async () => {
const ComponentClass = connect(Component, {});
const wrapper = mount(<ComponentClass foo={42} />);
const { rerender } = render(<ComponentClass foo={42} />);
wrapper.setProps({});
// @ts-expect-error
rerender(<ComponentClass />);
expect(Component).toHaveBeenCalledTimes(1);
await screen.findByText('Hello!');
});
const verifyShouldComponentUpdate = ({ initial, next, result }) => {
asMock(SimplestStore.getInitialState).mockReturnValue(initial);
const ComponentClass = connect(Component, { foo: SimplestStore });
SimpleStore.setValue(initial);
const ComponentClass = connect(Component, { foo: SimpleStore });
mount(<ComponentClass />);
const update = asMock(SimplestStore.listen).mock.calls[0][0];
render(<ComponentClass />);
asMock(Component).mockClear();
update(next);
if (result) {
expect(Component).toHaveBeenCalled();
} else {
expect(Component).not.toHaveBeenCalled();
}
const wrapper = mount(<ComponentClass someProp={initial} />);
asMock(Component).mockClear();
wrapper.setProps({ someProp: next });
act(() => {
SimpleStore.setValue(next);
});
if (result) {
expect(Component).toHaveBeenCalled();
@@ -222,6 +223,7 @@ describe('connect()', () => {
}
};
// eslint-disable-next-line jest/expect-expect
it.each`
initial | next | result | description
${undefined} | ${undefined} | ${false} | ${'equal undefined values'}
@@ -268,51 +270,51 @@ describe('useStore', () => {
act(() => SimpleStore.reset());
});
it('renders state from store', () => {
const wrapper = mount(<SimpleComponent />);
it('renders state from store', async () => {
render(<SimpleComponent />);
expect(wrapper).toHaveText('No value.');
await screen.findByText('No value.');
});
it('connects component to store with state', () => {
it('connects component to store with state', async () => {
act(() => SimpleStore.setValue(42));
const wrapper = mount(<SimpleComponent />);
render(<SimpleComponent />);
expect(wrapper).toHaveText('Value is: 42');
await screen.findByText('Value is: 42');
});
it('reflects state changes from store', () => {
const wrapper = mount(<SimpleComponent />);
it('reflects state changes from store', async () => {
render(<SimpleComponent />);
expect(wrapper).toHaveText('No value.');
await screen.findByText('No value.');
act(() => SimpleStore.setValue(42));
expect(wrapper).toHaveText('Value is: 42');
await screen.findByText('Value is: 42');
act(() => SimpleStore.noop());
expect(wrapper).toHaveText('Value is: 42');
await screen.findByText('Value is: 42');
act(() => SimpleStore.reset());
expect(wrapper).toHaveText('No value.');
await screen.findByText('No value.');
});
it('allows mangling of props before passing them', () => {
it('allows mangling of props before passing them', async () => {
const Component = () => {
const { value } = useStore(SimpleStore, ({ value: v } = { value: 0 }) => ({ value: v * 2 })) || {};
return <span>{value ? `Value is: ${value}` : 'No value.'}</span>;
};
const wrapper = mount(<Component />);
render(<Component />);
expect(wrapper).toHaveText('No value.');
await screen.findByText('No value.');
act(() => SimpleStore.setValue(42));
expect(wrapper).toHaveText('Value is: 84');
await screen.findByText('Value is: 84');
});
it('does not rerender component if state does not change', () => {
@@ -325,7 +327,7 @@ describe('useStore', () => {
return <span>{value ? `Value is: ${value}` : 'No value.'}</span>;
};
mount(<SimpleComponentWithRenderCounter />);
render(<SimpleComponentWithRenderCounter />);
const beforeFirstSet = renderCount;
@@ -340,7 +342,7 @@ describe('useStore', () => {
expect(renderCount).toEqual(beforeSecondSet);
});
it('does not reregister if props mapper is provided as arrow function', () => {
it('does not reregister if props mapper is provided as arrow function', async () => {
const listenSpy = jest.spyOn(SimpleStore, 'listen');
const ComponentWithPropsMapper = ({ propsMapper }: { propsMapper: (state: { value: number }) => ({ value: number }) }) => {
@@ -349,14 +351,14 @@ describe('useStore', () => {
return <span>{value ? `Value is: ${value}` : 'No value.'}</span>;
};
const wrapper = mount(<ComponentWithPropsMapper propsMapper={(x) => x} />);
const { rerender } = render(<ComponentWithPropsMapper propsMapper={(x) => x} />);
expect(wrapper).toHaveText('No value.');
await screen.findByText('No value.');
wrapper.setProps({ propsMapper: ({ value: v } = { value: 0 }) => ({ value: v * 2 }) });
rerender(<ComponentWithPropsMapper propsMapper={({ value: v } = { value: 0 }) => ({ value: v * 2 })} />);
act(() => SimpleStore.setValue(42));
expect(wrapper).toHaveText('Value is: 84');
await screen.findByText('Value is: 84');
expect(listenSpy).toHaveBeenCalledTimes(1);
});

View File

@@ -15,7 +15,7 @@
* <http://www.mongodb.com/licensing/server-side-public-license>.
*/
import * as React from 'react';
import { render, screen, waitFor } from 'wrappedTestingLibrary';
import { act, render, screen, waitFor } from 'wrappedTestingLibrary';
import * as Immutable from 'immutable';
import selectEvent from 'react-select-event';
import userEvent from '@testing-library/user-event';
@@ -84,7 +84,10 @@ const visualizationSelect = async () => screen.findByLabelText('Select visualiza
const selectOption = async (ariaLabel: string, option: string) => {
const select = await screen.findByLabelText(ariaLabel);
await selectEvent.openMenu(select);
await selectEvent.select(select, option, selectEventConfig);
await act(async () => {
await selectEvent.select(select, option, selectEventConfig);
});
};
describe('AggregationWizard/Core Visualizations', () => {

View File

@@ -16,6 +16,7 @@
*/
import * as React from 'react';
import { render, screen, waitFor } from 'wrappedTestingLibrary';
import userEvent from '@testing-library/user-event';
import { MenuItem } from 'components/bootstrap';
@@ -33,7 +34,7 @@ describe('ActionDropdown', () => {
expect(screen.queryByText('Foo')).not.toBeInTheDocument();
triggerButton.click();
await userEvent.click(triggerButton);
await screen.findByRole('menuitem', { name: 'Foo' });
});
@@ -53,13 +54,11 @@ describe('ActionDropdown', () => {
expect(screen.queryByText('Foo')).not.toBeInTheDocument();
triggerButton.click();
await userEvent.click(triggerButton);
await screen.findByRole('menuitem', { name: 'Foo' });
expect(onClick).not.toHaveBeenCalled();
await waitFor(() => {
expect(screen.queryByRole('menuitem', { name: 'Foo' })).not.toBeInTheDocument();
});
});
it('closes menu when MenuItem is clicked', async () => {
@@ -73,10 +72,10 @@ describe('ActionDropdown', () => {
const triggerButton = await screen.findByText('Trigger!');
triggerButton.click();
await userEvent.click(triggerButton);
const menuItem = await screen.findByRole('menuitem', { name: 'Foo' });
menuItem.click();
await userEvent.click(menuItem);
await waitFor(() => {
expect(onSelect).toHaveBeenCalled();
@@ -100,10 +99,10 @@ describe('ActionDropdown', () => {
const triggerButton = await screen.findByText('Trigger!');
triggerButton.click();
await userEvent.click(triggerButton);
const menuItem = await screen.findByRole('menuitem', { name: 'Foo' });
menuItem.click();
await userEvent.click(menuItem);
await waitFor(() => {
expect(onSelect).toHaveBeenCalled();
@@ -128,10 +127,10 @@ describe('ActionDropdown', () => {
const triggerButton = await screen.findByText('Trigger!');
triggerButton.click();
await userEvent.click(triggerButton);
const menuItem = await screen.findByRole('menuitem', { name: 'Foo' });
menuItem.click();
await userEvent.click(menuItem);
await waitFor(() => {
expect(onSelect).toHaveBeenCalled();

View File

@@ -69,11 +69,11 @@ describe('BigDisplayModeConfiguration', () => {
<BigDisplayModeConfiguration view={view} {...props} />
));
it('disables menu item if `disabled` prop is `true`', () => {
const { getByText, queryByText } = render(<SUT disabled />);
const menuItem = getByText('Full Screen');
it('disables menu item if `disabled` prop is `true`', async () => {
const { queryByText, findByText } = render(<SUT disabled />);
const menuItem = await findByText('Full Screen');
fireEvent.submit(menuItem);
fireEvent.click(menuItem);
expect(queryByText('Configuring Full Screen')).toBeNull();
});

View File

@@ -16,7 +16,7 @@
*/
import * as React from 'react';
import * as Immutable from 'immutable';
import { render, fireEvent, waitFor, screen } from 'wrappedTestingLibrary';
import { act, render, fireEvent, waitFor, screen } from 'wrappedTestingLibrary';
import QueryTitleEditModal from './QueryTitleEditModal';
@@ -25,7 +25,9 @@ describe('QueryTitleEditModal', () => {
const openModal = (modalRef, currentTitle = 'CurrentTitle') => {
if (modalRef) {
modalRef.open(currentTitle);
act(() => {
modalRef.open(currentTitle);
});
}
};

View File

@@ -35,9 +35,11 @@ const defaultProps = {
describe('AbsoluteDateInput', () => {
beforeAll(() => { jest.clearAllMocks(); });
it('renders with minimal props', () => {
it('renders with minimal props', async () => {
render(<AbsoluteDateInput {...defaultProps} />);
await screen.findByRole('button', { name: /insert current date/i });
expect(screen).not.toBeNull();
});

View File

@@ -15,8 +15,9 @@
* <http://www.mongodb.com/licensing/server-side-public-license>.
*/
import * as React from 'react';
import { fireEvent, render, screen } from 'wrappedTestingLibrary';
import { render, screen } from 'wrappedTestingLibrary';
import { Formik, Form } from 'formik';
import userEvent from '@testing-library/user-event';
import TabAbsoluteTimeRange from './TabAbsoluteTimeRange';
@@ -39,7 +40,7 @@ const renderWithForm = (element) => render((
));
describe('TabAbsoluteTimeRange', () => {
it('renders Accordions that work', () => {
it('renders Accordions that work', async () => {
renderWithForm((
<TabAbsoluteTimeRange {...defaultProps} />
));
@@ -52,9 +53,8 @@ describe('TabAbsoluteTimeRange', () => {
expect(accordionItemCal.getAttribute('aria-expanded')).toEqual('true');
expect(accordionItemTime.getAttribute('aria-expanded')).toEqual('false');
fireEvent.click(accordionItemTime);
userEvent.click(accordionItemTime);
expect(accordionItemCal.getAttribute('aria-expanded')).toEqual('false');
expect(accordionItemTime.getAttribute('aria-expanded')).toEqual('true');
await screen.findByText(/Date should be formatted as/i);
});
});

View File

@@ -74,7 +74,7 @@ describe('CopyToDashboardForm', () => {
fireEvent.click(submitButton);
};
it('should render the modal minimal', () => {
it('should render the modal minimal', async () => {
asMock(useDashboards).mockReturnValue({
data: {
list: [],
@@ -96,33 +96,40 @@ describe('CopyToDashboardForm', () => {
refetch: () => {},
});
const { baseElement } = render(<SUT />);
render(<SUT />);
expect(baseElement).not.toBeNull();
await screen.findByText(/no dashboards found/i);
});
it('should render the modal with entries', () => {
const { baseElement } = render(<SUT />);
it('should render the modal with entries', async () => {
render(<SUT />);
expect(baseElement).not.toBeNull();
await screen.findByText('view 1');
await screen.findByText('view 2');
});
it('should handle onCancel', () => {
it('should handle onCancel', async () => {
const onCancel = jest.fn();
const { getByText } = render(<SUT onCancel={onCancel} />);
const cancelButton = getByText('Cancel');
fireEvent.click(cancelButton);
expect(onCancel).toHaveBeenCalledTimes(1);
await waitFor(() => {
expect(onCancel).toHaveBeenCalledTimes(1);
});
});
it('should not handle onSubmit without selection', () => {
it('should not handle onSubmit without selection', async () => {
const onSubmit = jest.fn(() => Promise.resolve());
render(<SUT />);
submitModal();
const submitButton = await screen.findByRole('button', { name: /submit/i, hidden: true });
await waitFor(() => {
expect(submitButton).toBeDisabled();
});
expect(onSubmit).not.toHaveBeenCalled();
});

View File

@@ -27,9 +27,10 @@ describe('FieldSortIcon', () => {
const currentSort = new SortConfig(SortConfig.PIVOT_TYPE, 'timestamp', Direction.Descending);
const config = new MessagesWidgetConfig(['timestamp', 'source'], true, true, [], [currentSort]);
it('should set descending sort on click, if field sort is not defined', () => {
it('should set descending sort on click, if field sort is not defined', async () => {
const onSortChangeStub = jest.fn(() => Promise.resolve());
const { getByTitle } = render(<FieldSortIcon config={config} fieldName="source" onSortChange={onSortChangeStub} setLoadingState={() => {}} />);
const setLoadingState = jest.fn();
const { getByTitle } = render(<FieldSortIcon config={config} fieldName="source" onSortChange={onSortChangeStub} setLoadingState={setLoadingState} />);
const sortIcon = getByTitle('Sort source Descending');
@@ -39,6 +40,10 @@ describe('FieldSortIcon', () => {
expect(onSortChangeStub).toHaveBeenCalledTimes(1);
expect(onSortChangeStub).toHaveBeenCalledWith(expectedSort);
await waitFor(() => {
expect(setLoadingState).toHaveBeenCalledWith(false);
});
});
it('should set ascending sort on click, if field sort is descending', () => {
@@ -55,12 +60,13 @@ describe('FieldSortIcon', () => {
expect(onSortChangeStub).toHaveBeenCalledWith(expectedSort);
});
it('should set descending sort on click, if field sort is descending', () => {
it('should set descending sort on click, if field sort is descending', async () => {
const initialSort = new SortConfig(SortConfig.PIVOT_TYPE, 'source', Direction.Ascending);
const initialConfig = new MessagesWidgetConfig(['timestamp', 'source'], true, true, [], [initialSort]);
const onSortChangeStub = jest.fn(() => Promise.resolve());
const setLoadingState = jest.fn();
const { getByTitle } = render(<FieldSortIcon config={initialConfig} fieldName="source" onSortChange={onSortChangeStub} setLoadingState={() => {}} />);
const { getByTitle } = render(<FieldSortIcon config={initialConfig} fieldName="source" onSortChange={onSortChangeStub} setLoadingState={setLoadingState} />);
const sortIcon = getByTitle('Sort source Descending');
@@ -70,6 +76,10 @@ describe('FieldSortIcon', () => {
expect(onSortChangeStub).toHaveBeenCalledTimes(1);
expect(onSortChangeStub).toHaveBeenCalledWith(expectedSort);
await waitFor(() => {
expect(setLoadingState).toHaveBeenCalledWith(false);
});
});
it('should set loading state while changing sort', async () => {

View File

@@ -16,6 +16,7 @@
*/
import * as React from 'react';
import { render, screen, waitFor } from 'wrappedTestingLibrary';
import userEvent from '@testing-library/user-event';
import { MenuItem } from 'components/bootstrap';
@@ -33,7 +34,7 @@ describe('WidgetActionDropdown', () => {
expect(screen.queryByText('Foo')).not.toBeInTheDocument();
menuButton.click();
await userEvent.click(menuButton);
await screen.findByRole('menuitem', { name: 'Foo' });
});
@@ -48,10 +49,10 @@ describe('WidgetActionDropdown', () => {
));
const menuButton = await screen.findByRole('button', { name: /open actions dropdown/i });
menuButton.click();
await userEvent.click(menuButton);
const fooAction = await screen.findByRole('menuitem', { name: 'Foo' });
fooAction.click();
await userEvent.click(fooAction);
await waitFor(() => {
expect(screen.queryByText('Foo')).not.toBeInTheDocument();

View File

@@ -14,6 +14,7 @@
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*/
/// <reference types="jest-enzyme" />
import type * as React from 'react';
import type { ReactWrapper, ShallowWrapper } from 'enzyme';
import { configure, mount, shallow } from 'enzyme';