diff --git a/graylog2-web-interface/packages/jest-preset-graylog/package.json b/graylog2-web-interface/packages/jest-preset-graylog/package.json index 81c69ef7c6ae..2536b7553b21 100644 --- a/graylog2-web-interface/packages/jest-preset-graylog/package.json +++ b/graylog2-web-interface/packages/jest-preset-graylog/package.json @@ -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" } } diff --git a/graylog2-web-interface/src/components/authentication/AuthenticatorsEdit/HTTPHeaderAuthConfigSection.test.tsx b/graylog2-web-interface/src/components/authentication/AuthenticatorsEdit/HTTPHeaderAuthConfigSection.test.tsx index dc46330274d7..0abbf3941543 100644 --- a/graylog2-web-interface/src/components/authentication/AuthenticatorsEdit/HTTPHeaderAuthConfigSection.test.tsx +++ b/graylog2-web-interface/src/components/authentication/AuthenticatorsEdit/HTTPHeaderAuthConfigSection.test.tsx @@ -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('', () => { it('should display loading indicator while loading', async () => { jest.useFakeTimers(); + asMock(HTTPHeaderAuthConfigActions.load).mockImplementationOnce(() => new Promise(() => {})); render(); diff --git a/graylog2-web-interface/src/components/bootstrap/DropdownButton.test.tsx b/graylog2-web-interface/src/components/bootstrap/DropdownButton.test.tsx index aef6bdc92125..34b0daeb3a74 100644 --- a/graylog2-web-interface/src/components/bootstrap/DropdownButton.test.tsx +++ b/graylog2-web-interface/src/components/bootstrap/DropdownButton.test.tsx @@ -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(); diff --git a/graylog2-web-interface/src/components/common/URLWhiteListFormModal.test.tsx b/graylog2-web-interface/src/components/common/URLWhiteListFormModal.test.tsx index c531ae93a8eb..b5566eb31944 100644 --- a/graylog2-web-interface/src/components/common/URLWhiteListFormModal.test.tsx +++ b/graylog2-web-interface/src/components/common/URLWhiteListFormModal.test.tsx @@ -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('', () => { 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('', () => { expect(addButton).toBeInTheDocument(); - addButton.click(); + await userEvent.click(addButton); expect(await screen.findByText('Whitelist URLs')).toBeInTheDocument(); expect(screen.getByDisplayValue('http://localhost(:\\d+)?')).toBeInTheDocument(); diff --git a/graylog2-web-interface/src/components/configurations/UrlWhiteListForm.test.tsx b/graylog2-web-interface/src/components/configurations/UrlWhiteListForm.test.tsx index e8adb97bc2ea..38e93daac8c9 100644 --- a/graylog2-web-interface/src/components/configurations/UrlWhiteListForm.test.tsx +++ b/graylog2-web-interface/src/components/configurations/UrlWhiteListForm.test.tsx @@ -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(); - 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(); - 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); diff --git a/graylog2-web-interface/src/components/users/UserEdit/SettingsSection.test.tsx b/graylog2-web-interface/src/components/users/UserEdit/SettingsSection.test.tsx index 2e6b823ab9e1..0a74d24db86b 100644 --- a/graylog2-web-interface/src/components/users/UserEdit/SettingsSection.test.tsx +++ b/graylog2-web-interface/src/components/users/UserEdit/SettingsSection.test.tsx @@ -15,7 +15,7 @@ * . */ 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('', () => { 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); diff --git a/graylog2-web-interface/src/hooks/useLogout.test.tsx b/graylog2-web-interface/src/hooks/useLogout.test.tsx index 7a22c420d0f0..81394e6cb49f 100644 --- a/graylog2-web-interface/src/hooks/useLogout.test.tsx +++ b/graylog2-web-interface/src/hooks/useLogout.test.tsx @@ -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'); diff --git a/graylog2-web-interface/src/stores/__tests__/connect.test.tsx b/graylog2-web-interface/src/stores/__tests__/connect.test.tsx index f528d1ee950c..0ce36f14fbb8 100644 --- a/graylog2-web-interface/src/stores/__tests__/connect.test.tsx +++ b/graylog2-web-interface/src/stores/__tests__/connect.test.tsx @@ -14,16 +14,13 @@ * along with this program. If not, see * . */ -/// 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 = () => Hello World!; - -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 Value is: {simpleStore.value}; @@ -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(); + render(); - expect(wrapper).toHaveHTML('Hello World!'); + 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(); + render(); - expect(wrapper).toHaveText('No value.'); + await screen.findByText('No value.'); }); - it('connects component to store with state', () => { - SimpleStore.setValue(42); + it('connects component to store with state', async () => { + act(() => { + SimpleStore.setValue(42); + }); + const Component = connect(SimpleComponentWithDummyStore, { simpleStore: SimpleStore }); - const wrapper = mount(); + render(); - expect(wrapper).toHaveText('Value is: 42'); + 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(); + render(); - 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(); + render(); - 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(() => hello!, { simpleStore: SimpleStore }); - mount(); + render(); + await screen.findByText('hello!'); }); - it('types mapped props as optional', () => { + it('types mapped props as optional', async () => { const Component = connect( () => hello!, { simpleStore: SimpleStore }, ({ simpleStore }) => (simpleStore && { storeValue: simpleStore.value }), ); - mount(); + render(); + await screen.findByText('hello!'); }); - it('types props which have a default value (defaultProps) as optional', () => { - const BaseComponent = ({ exampleProp }: { exampleProp: string }) => {exampleProp}; + it('types props which have a default value (defaultProps) as optional', async () => { + const BaseComponent = ({ exampleProp }: { + exampleProp: string + }) => {exampleProp}; BaseComponent.defaultProps = { exampleProp: 'hello!', @@ -170,50 +183,38 @@ describe('connect()', () => { }; const Component = connect(BaseComponent, { simpleStore: SimpleStore }); - mount(); + render(); + + await screen.findByText('hello!'); }); describe('generates `shouldComponentUpdate`', () => { const Component: React.ComponentType<{ someProp?: any, foo: number }> = jest.fn(() => Hello!); - const SimplestStore: Store = ({ - 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(); + const { rerender } = render(); - wrapper.setProps({}); + // @ts-expect-error + rerender(); - expect(Component).toHaveBeenCalledTimes(1); + await screen.findByText('Hello!'); }); const verifyShouldComponentUpdate = ({ initial, next, result }) => { - asMock(SimplestStore.getInitialState).mockReturnValue(initial); - const ComponentClass = connect(Component, { foo: SimplestStore }); - - mount(); - const update = asMock(SimplestStore.listen).mock.calls[0][0]; - - asMock(Component).mockClear(); - - update(next); - - if (result) { - expect(Component).toHaveBeenCalled(); - } else { - expect(Component).not.toHaveBeenCalled(); - } + SimpleStore.setValue(initial); + const ComponentClass = connect(Component, { foo: SimpleStore }); - const wrapper = mount(); + render(); 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(); + it('renders state from store', async () => { + render(); - 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(); + render(); - expect(wrapper).toHaveText('Value is: 42'); + await screen.findByText('Value is: 42'); }); - it('reflects state changes from store', () => { - const wrapper = mount(); + it('reflects state changes from store', async () => { + render(); - 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 {value ? `Value is: ${value}` : 'No value.'}; }; - const wrapper = mount(); + render(); - 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 {value ? `Value is: ${value}` : 'No value.'}; }; - mount(); + render(); 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 {value ? `Value is: ${value}` : 'No value.'}; }; - const wrapper = mount( x} />); + const { rerender } = render( x} />); - expect(wrapper).toHaveText('No value.'); + await screen.findByText('No value.'); - wrapper.setProps({ propsMapper: ({ value: v } = { value: 0 }) => ({ value: v * 2 }) }); + rerender( ({ value: v * 2 })} />); act(() => SimpleStore.setValue(42)); - expect(wrapper).toHaveText('Value is: 84'); + await screen.findByText('Value is: 84'); expect(listenSpy).toHaveBeenCalledTimes(1); }); diff --git a/graylog2-web-interface/src/views/components/aggregationwizard/__tests__/AggregationWizard.corevisualizations.test.tsx b/graylog2-web-interface/src/views/components/aggregationwizard/__tests__/AggregationWizard.corevisualizations.test.tsx index 10c88dc969ae..e8a13507d3a2 100644 --- a/graylog2-web-interface/src/views/components/aggregationwizard/__tests__/AggregationWizard.corevisualizations.test.tsx +++ b/graylog2-web-interface/src/views/components/aggregationwizard/__tests__/AggregationWizard.corevisualizations.test.tsx @@ -15,7 +15,7 @@ * . */ 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', () => { diff --git a/graylog2-web-interface/src/views/components/common/ActionDropdown.test.tsx b/graylog2-web-interface/src/views/components/common/ActionDropdown.test.tsx index c2df12dd7d04..1eff3b5e20b8 100644 --- a/graylog2-web-interface/src/views/components/common/ActionDropdown.test.tsx +++ b/graylog2-web-interface/src/views/components/common/ActionDropdown.test.tsx @@ -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); - expect(onClick).not.toHaveBeenCalled(); + await screen.findByRole('menuitem', { name: 'Foo' }); - await waitFor(() => { - expect(screen.queryByRole('menuitem', { name: 'Foo' })).not.toBeInTheDocument(); - }); + expect(onClick).not.toHaveBeenCalled(); }); 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(); diff --git a/graylog2-web-interface/src/views/components/dashboard/BigDisplayModeConfiguration.test.tsx b/graylog2-web-interface/src/views/components/dashboard/BigDisplayModeConfiguration.test.tsx index 12683afb2cc6..76fb9d93ff67 100644 --- a/graylog2-web-interface/src/views/components/dashboard/BigDisplayModeConfiguration.test.tsx +++ b/graylog2-web-interface/src/views/components/dashboard/BigDisplayModeConfiguration.test.tsx @@ -69,11 +69,11 @@ describe('BigDisplayModeConfiguration', () => { )); - it('disables menu item if `disabled` prop is `true`', () => { - const { getByText, queryByText } = render(); - const menuItem = getByText('Full Screen'); + it('disables menu item if `disabled` prop is `true`', async () => { + const { queryByText, findByText } = render(); + const menuItem = await findByText('Full Screen'); - fireEvent.submit(menuItem); + fireEvent.click(menuItem); expect(queryByText('Configuring Full Screen')).toBeNull(); }); diff --git a/graylog2-web-interface/src/views/components/queries/QueryTitleEditModal.test.tsx b/graylog2-web-interface/src/views/components/queries/QueryTitleEditModal.test.tsx index 4b495364aed7..4a7d243ebed8 100644 --- a/graylog2-web-interface/src/views/components/queries/QueryTitleEditModal.test.tsx +++ b/graylog2-web-interface/src/views/components/queries/QueryTitleEditModal.test.tsx @@ -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); + }); } }; diff --git a/graylog2-web-interface/src/views/components/searchbar/time-range-filter/time-range-picker/AbsoluteDateInput.test.tsx b/graylog2-web-interface/src/views/components/searchbar/time-range-filter/time-range-picker/AbsoluteDateInput.test.tsx index 3adcd8ebe622..815547fa61c2 100644 --- a/graylog2-web-interface/src/views/components/searchbar/time-range-filter/time-range-picker/AbsoluteDateInput.test.tsx +++ b/graylog2-web-interface/src/views/components/searchbar/time-range-filter/time-range-picker/AbsoluteDateInput.test.tsx @@ -35,9 +35,11 @@ const defaultProps = { describe('AbsoluteDateInput', () => { beforeAll(() => { jest.clearAllMocks(); }); - it('renders with minimal props', () => { + it('renders with minimal props', async () => { render(); + await screen.findByRole('button', { name: /insert current date/i }); + expect(screen).not.toBeNull(); }); diff --git a/graylog2-web-interface/src/views/components/searchbar/time-range-filter/time-range-picker/TabAbsoluteTimeRange.test.tsx b/graylog2-web-interface/src/views/components/searchbar/time-range-filter/time-range-picker/TabAbsoluteTimeRange.test.tsx index 28836e5d7d6f..b00c57627df2 100644 --- a/graylog2-web-interface/src/views/components/searchbar/time-range-filter/time-range-picker/TabAbsoluteTimeRange.test.tsx +++ b/graylog2-web-interface/src/views/components/searchbar/time-range-filter/time-range-picker/TabAbsoluteTimeRange.test.tsx @@ -15,8 +15,9 @@ * . */ 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(( )); @@ -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); }); }); diff --git a/graylog2-web-interface/src/views/components/widgets/CopyToDashboardForm.test.tsx b/graylog2-web-interface/src/views/components/widgets/CopyToDashboardForm.test.tsx index 03222db4f6da..55a81341bb1d 100644 --- a/graylog2-web-interface/src/views/components/widgets/CopyToDashboardForm.test.tsx +++ b/graylog2-web-interface/src/views/components/widgets/CopyToDashboardForm.test.tsx @@ -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(); + render(); - expect(baseElement).not.toBeNull(); + await screen.findByText(/no dashboards found/i); }); - it('should render the modal with entries', () => { - const { baseElement } = render(); + it('should render the modal with entries', async () => { + render(); - 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(); 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(); - submitModal(); + const submitButton = await screen.findByRole('button', { name: /submit/i, hidden: true }); + + await waitFor(() => { + expect(submitButton).toBeDisabled(); + }); expect(onSubmit).not.toHaveBeenCalled(); }); diff --git a/graylog2-web-interface/src/views/components/widgets/FieldSortIcon.test.tsx b/graylog2-web-interface/src/views/components/widgets/FieldSortIcon.test.tsx index 215b5c08dc6d..6d2eeca2be62 100644 --- a/graylog2-web-interface/src/views/components/widgets/FieldSortIcon.test.tsx +++ b/graylog2-web-interface/src/views/components/widgets/FieldSortIcon.test.tsx @@ -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( {}} />); + const setLoadingState = jest.fn(); + const { getByTitle } = render(); 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( {}} />); + const { getByTitle } = render(); 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 () => { diff --git a/graylog2-web-interface/src/views/components/widgets/WidgetActionDropdown.test.tsx b/graylog2-web-interface/src/views/components/widgets/WidgetActionDropdown.test.tsx index cf49fddda64f..241578bf710d 100644 --- a/graylog2-web-interface/src/views/components/widgets/WidgetActionDropdown.test.tsx +++ b/graylog2-web-interface/src/views/components/widgets/WidgetActionDropdown.test.tsx @@ -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(); diff --git a/graylog2-web-interface/test/wrappedEnzyme.tsx b/graylog2-web-interface/test/wrappedEnzyme.tsx index 8aed8647d42b..ae762dac0c43 100644 --- a/graylog2-web-interface/test/wrappedEnzyme.tsx +++ b/graylog2-web-interface/test/wrappedEnzyme.tsx @@ -14,6 +14,7 @@ * along with this program. If not, see * . */ +/// import type * as React from 'react'; import type { ReactWrapper, ShallowWrapper } from 'enzyme'; import { configure, mount, shallow } from 'enzyme';