Upgrade to React 18 (#1955)

* Upgrade deps

* migration

* Fix specs

* exclude index.tsx from sonar metrics

* Update deps
This commit is contained in:
Oleg Shur 2022-05-12 01:03:08 +03:00 committed by GitHub
parent 1e3561ea28
commit eb47ec012d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
76 changed files with 6635 additions and 5786 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,15 +1,15 @@
{
"name": "kafka-ui",
"version": "0.1.0",
"version": "0.4.0",
"homepage": "./",
"private": true,
"dependencies": {
"@fortawesome/fontawesome-free": "^5.15.4",
"@hookform/error-message": "^2.0.0",
"@hookform/resolvers": "^2.7.1",
"@reduxjs/toolkit": "^1.7.1",
"@reduxjs/toolkit": "^1.8.1",
"@rooks/use-outside-click-ref": "^4.10.1",
"@testing-library/react": "^12.0.0",
"@testing-library/react": "^13.2.0",
"@types/eventsource": "^1.1.6",
"@types/yup": "^0.29.13",
"ace-builds": "^1.4.12",
@ -17,22 +17,22 @@
"bulma": "^0.9.3",
"classnames": "^2.2.6",
"dayjs": "^1.10.6",
"eslint-import-resolver-node": "^0.3.5",
"eslint-import-resolver-typescript": "^2.4.0",
"eslint-import-resolver-node": "^0.3.6",
"eslint-import-resolver-typescript": "^2.7.1",
"fetch-mock": "^9.11.0",
"json-schema-faker": "^0.5.0-rcv.39",
"lodash": "^4.17.21",
"node-fetch": "^2.6.1",
"pretty-ms": "^7.0.1",
"react": "^17.0.1",
"react": "^18.1.0",
"react-ace": "^9.4.3",
"react-datepicker": "^4.2.0",
"react-dom": "^17.0.1",
"react-dom": "^18.1.0",
"react-hook-form": "7.6.9",
"react-multi-select-component": "^4.0.6",
"react-redux": "^7.2.6",
"react-router": "^5.2.0",
"react-router-dom": "^5.2.0",
"react-router-dom": "^5.3.1",
"redux": "^4.1.1",
"redux-thunk": "^2.3.0",
"sass": "^1.43.4",
@ -79,39 +79,36 @@
},
"devDependencies": {
"@jest/types": "^27.0.6",
"@openapitools/openapi-generator-cli": "^2.4.15",
"@openapitools/openapi-generator-cli": "^2.5.1",
"@testing-library/dom": "^8.11.1",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react-hooks": "^7.0.2",
"@testing-library/user-event": "^13.5.0",
"@types/classnames": "^2.2.11",
"@types/jest": "^27.0.2",
"@types/lodash": "^4.14.172",
"@types/node": "^16.4.13",
"@types/node-fetch": "^3.0.3",
"@types/react": "^17.0.16",
"@types/react": "^18.0.9",
"@types/react-datepicker": "^4.1.4",
"@types/react-dom": "^17.0.9",
"@types/react-dom": "^18.0.3",
"@types/react-redux": "^7.1.18",
"@types/react-router-dom": "^5.1.8",
"@types/react-test-renderer": "^17.0.1",
"@types/react-router-dom": "^5.3.3",
"@types/redux-mock-store": "^1.0.3",
"@types/styled-components": "^5.1.13",
"@types/uuid": "^8.3.1",
"@typescript-eslint/eslint-plugin": "^5.10.0",
"@typescript-eslint/parser": "^5.10.0",
"dotenv": "^15.0.0",
"eslint": "^8.7.0",
"eslint-config-airbnb": "^19.0.0",
"eslint-config-airbnb-typescript": "^16.1.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.25.4",
"dotenv": "^16.0.1",
"eslint": "^8.15.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^17.0.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jest-dom": "^4.0.1",
"eslint-plugin-jsx-a11y": "^6.5.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-react": "^7.28.0",
"eslint-plugin-react-hooks": "^4.3.0",
"esprint": "^3.1.0",
"eslint-plugin-react": "^7.29.4",
"eslint-plugin-react-hooks": "^4.5.0",
"fetch-mock-jest": "^1.5.1",
"history": "^5.0.0",
"http-proxy-middleware": "^2.0.1",
@ -120,8 +117,7 @@
"jest-styled-components": "^7.0.6",
"lint-staged": "^12.1.2",
"prettier": "^2.3.1",
"react-scripts": "5.0.0",
"react-test-renderer": "^17.0.2",
"react-scripts": "5.0.1",
"redux-mock-store": "^1.5.4",
"rimraf": "^3.0.2",
"ts-jest": "^26.5.4",

View file

@ -2,7 +2,7 @@ sonar.projectKey=com.provectus:kafka-ui_frontend
sonar.organization=provectus
sonar.sources=.
sonar.exclusions=**/__tests__/**,**/__test__/**,src/serviceWorker.ts,src/setupTests.ts,src/setupProxy.js,**/fixtures.ts,src/lib/testHelpers.tsx
sonar.exclusions=**/__tests__/**,**/__test__/**,src/serviceWorker.ts,src/setupTests.ts,src/setupProxy.js,**/fixtures.ts,src/lib/testHelpers.tsx,src/index.tsx
sonar.typescript.lcov.reportPaths=./coverage/lcov.info
sonar.testExecutionReportPaths=./test-report.xml

View file

@ -1,23 +1,19 @@
import React from 'react';
import { Action, FailurePayload, ServerResponse } from 'redux/interfaces';
import { screen } from '@testing-library/react';
import { act, screen } from '@testing-library/react';
import Alerts from 'components/Alerts/Alerts';
import { render } from 'lib/testHelpers';
import { store } from 'redux/store';
import { UnknownAsyncThunkRejectedWithValueAction } from '@reduxjs/toolkit/dist/matchers';
import userEvent from '@testing-library/user-event';
describe('Alerts', () => {
beforeEach(() => render(<Alerts />, { store }));
it('renders alerts', async () => {
const payload: ServerResponse = {
const payload: ServerResponse = {
status: 422,
statusText: 'Unprocessable Entity',
message: 'Unprocessable Entity',
url: 'https://test.com/clusters',
};
const action: UnknownAsyncThunkRejectedWithValueAction = {
};
const action: UnknownAsyncThunkRejectedWithValueAction = {
type: 'any/action/rejected',
payload,
meta: {
@ -29,20 +25,26 @@ describe('Alerts', () => {
rejectedWithValue: true,
},
error: { message: 'Rejected' },
};
store.dispatch(action);
const alert: FailurePayload = {
};
const alert: FailurePayload = {
title: '404 - Not Found',
message: 'Item is not found',
subject: 'subject',
};
const legacyAction: Action = {
};
const legacyAction: Action = {
type: 'CLEAR_TOPIC_MESSAGES__FAILURE',
payload: { alert },
};
};
describe('Alerts', () => {
it('renders alerts', async () => {
store.dispatch(action);
store.dispatch(legacyAction);
await act(() => {
render(<Alerts />, { store });
});
expect(screen.getAllByRole('alert').length).toEqual(2);
const dissmissAlertButtons = screen.getAllByRole('button');

View file

@ -2,7 +2,7 @@ import React from 'react';
import { ClusterName } from 'redux/interfaces';
import useInterval from 'lib/hooks/useInterval';
import BytesFormatted from 'components/common/BytesFormatted/BytesFormatted';
import { useParams } from 'react-router';
import { useParams } from 'react-router-dom';
import TableHeaderCell from 'components/common/table/TableHeaderCell/TableHeaderCell';
import { Table } from 'components/common/table/Table/Table.styled';
import PageHeading from 'components/common/PageHeading/PageHeading';

View file

@ -2,10 +2,11 @@ import React from 'react';
import Brokers from 'components/Brokers/Brokers';
import { render } from 'lib/testHelpers';
import { screen, waitFor } from '@testing-library/dom';
import { Route } from 'react-router';
import { Route } from 'react-router-dom';
import { clusterBrokersPath } from 'lib/paths';
import fetchMock from 'fetch-mock';
import { clusterStatsPayload } from 'redux/reducers/brokers/__test__/fixtures';
import { act } from '@testing-library/react';
describe('Brokers Component', () => {
afterEach(() => fetchMock.reset());
@ -41,23 +42,26 @@ describe('Brokers Component', () => {
fetchStatsUrl,
clusterStatsPayload
);
await act(() => {
renderComponent();
await waitFor(() => {
expect(fetchStatsMock.called()).toBeTruthy();
});
await waitFor(() => {
expect(fetchBrokersMock.called()).toBeTruthy();
});
await waitFor(() => expect(fetchStatsMock.called()).toBeTruthy());
await waitFor(() => expect(fetchBrokersMock.called()).toBeTruthy());
expect(screen.getByRole('table')).toBeInTheDocument();
const rows = screen.getAllByRole('row');
expect(rows.length).toEqual(3);
});
it('shows warning when offlinePartitionCount > 0', async () => {
const fetchStatsMock = fetchMock.getOnce(fetchStatsUrl, {
...clusterStatsPayload,
offlinePartitionCount: 1345,
});
await act(() => {
renderComponent();
});
await waitFor(() => {
expect(fetchStatsMock.called()).toBeTruthy();
});
@ -76,7 +80,9 @@ describe('Brokers Component', () => {
inSyncReplicasCount: testInSyncReplicasCount,
outOfSyncReplicasCount: testOutOfSyncReplicasCount,
});
await act(() => {
renderComponent();
});
await waitFor(() => {
expect(fetchStatsMock.called()).toBeTruthy();
});
@ -94,7 +100,9 @@ describe('Brokers Component', () => {
inSyncReplicasCount: undefined,
outOfSyncReplicasCount: testOutOfSyncReplicasCount,
});
await act(() => {
renderComponent();
});
await waitFor(() => {
expect(fetchStatsMock.called()).toBeTruthy();
});
@ -108,7 +116,9 @@ describe('Brokers Component', () => {
inSyncReplicasCount: testInSyncReplicasCount,
outOfSyncReplicasCount: undefined,
});
await act(() => {
renderComponent();
});
await waitFor(() => {
expect(fetchStatsMock.called()).toBeTruthy();
});

View file

@ -85,7 +85,7 @@ const Actions: React.FC<ActionsProps> = ({
}, [restartConnector, clusterName, connectName, connectorName]);
const restartTasksHandler = React.useCallback(
(actionType) => {
(actionType: ConnectorAction) => {
restartTasks({
clusterName,
connectName,

View file

@ -1,5 +1,5 @@
import React from 'react';
import { useParams } from 'react-router';
import { useParams } from 'react-router-dom';
import {
ClusterName,
ConnectName,

View file

@ -6,9 +6,9 @@ import {
} from 'lib/paths';
import Edit, { EditProps } from 'components/Connect/Edit/Edit';
import { connector } from 'redux/reducers/connect/__test__/fixtures';
import { Route } from 'react-router';
import { Route } from 'react-router-dom';
import { waitFor } from '@testing-library/dom';
import { fireEvent, screen } from '@testing-library/react';
import { act, fireEvent, screen } from '@testing-library/react';
jest.mock('components/common/PageLoader/PageLoader', () => 'mock-PageLoader');
@ -52,9 +52,9 @@ describe('Edit', () => {
}
);
it('fetches config on mount', () => {
it('fetches config on mount', async () => {
const fetchConfig = jest.fn();
renderComponent({ fetchConfig });
await waitFor(() => renderComponent({ fetchConfig }));
expect(fetchConfig).toHaveBeenCalledTimes(1);
expect(fetchConfig).toHaveBeenCalledWith({
clusterName,
@ -65,9 +65,9 @@ describe('Edit', () => {
it('calls updateConfig on form submit', async () => {
const updateConfig = jest.fn();
renderComponent({ updateConfig });
await waitFor(() => fireEvent.submit(screen.getByRole('form')));
expect(updateConfig).toHaveBeenCalledTimes(1);
await waitFor(() => renderComponent({ updateConfig }));
fireEvent.submit(screen.getByRole('form'));
await waitFor(() => expect(updateConfig).toHaveBeenCalledTimes(1));
expect(updateConfig).toHaveBeenCalledWith({
clusterName,
connectName,
@ -78,9 +78,10 @@ describe('Edit', () => {
it('redirects to connector config view on successful submit', async () => {
const updateConfig = jest.fn().mockResolvedValueOnce(connector);
renderComponent({ updateConfig });
await waitFor(() => fireEvent.submit(screen.getByRole('form')));
expect(mockHistoryPush).toHaveBeenCalledTimes(1);
await waitFor(() => renderComponent({ updateConfig }));
fireEvent.submit(screen.getByRole('form'));
await waitFor(() => expect(mockHistoryPush).toHaveBeenCalledTimes(1));
expect(mockHistoryPush).toHaveBeenCalledWith(
clusterConnectConnectorConfigPath(clusterName, connectName, connectorName)
);
@ -88,8 +89,10 @@ describe('Edit', () => {
it('does not redirect to connector config view on unsuccessful submit', async () => {
const updateConfig = jest.fn().mockResolvedValueOnce(undefined);
renderComponent({ updateConfig });
await waitFor(() => fireEvent.submit(screen.getByRole('form')));
await waitFor(() => renderComponent({ updateConfig }));
await act(() => {
fireEvent.submit(screen.getByRole('form'));
});
expect(mockHistoryPush).not.toHaveBeenCalled();
});
});

View file

@ -6,13 +6,15 @@ import ClusterContext, {
} from 'components/contexts/ClusterContext';
import ListContainer from 'components/Connect/List/ListContainer';
import List, { ListProps } from 'components/Connect/List/List';
import { screen } from '@testing-library/react';
import { act, screen } from '@testing-library/react';
import { render } from 'lib/testHelpers';
describe('Connectors List', () => {
describe('Container', () => {
it('renders view with initial state of storage', () => {
it('renders view with initial state of storage', async () => {
await act(() => {
render(<ListContainer />);
});
expect(screen.getByRole('heading')).toHaveTextContent('Connectors');
});
});
@ -21,10 +23,11 @@ describe('Connectors List', () => {
const fetchConnects = jest.fn();
const fetchConnectors = jest.fn();
const setConnectorSearch = jest.fn();
const setupComponent = (
const renderComponent = (
props: Partial<ListProps> = {},
contextValue: ContextProps = initialValue
) => (
) => {
render(
<ClusterContext.Provider value={contextValue}>
<List
areConnectorsFetching
@ -39,45 +42,44 @@ describe('Connectors List', () => {
/>
</ClusterContext.Provider>
);
};
it('renders PageLoader', () => {
render(setupComponent({ areConnectorsFetching: true }));
it('renders PageLoader', async () => {
await act(() => renderComponent({ areConnectorsFetching: true }));
expect(screen.getByRole('progressbar')).toBeInTheDocument();
expect(screen.queryByRole('row')).not.toBeInTheDocument();
});
it('renders table', () => {
render(setupComponent({ areConnectorsFetching: false }));
renderComponent({ areConnectorsFetching: false });
expect(screen.queryByRole('progressbar')).not.toBeInTheDocument();
expect(screen.getByRole('table')).toBeInTheDocument();
});
it('renders connectors list', () => {
render(
setupComponent({
renderComponent({
areConnectorsFetching: false,
connectors,
})
);
});
expect(screen.queryByRole('progressbar')).not.toBeInTheDocument();
expect(screen.getByRole('table')).toBeInTheDocument();
expect(screen.getAllByRole('row').length).toEqual(3);
});
it('handles fetchConnects and fetchConnectors', () => {
render(setupComponent());
renderComponent();
expect(fetchConnects).toHaveBeenCalledTimes(1);
expect(fetchConnectors).toHaveBeenCalledTimes(1);
});
it('renders actions if cluster is not readonly', () => {
render(setupComponent({}, { ...initialValue, isReadOnly: false }));
renderComponent({}, { ...initialValue, isReadOnly: false });
expect(screen.getByRole('button')).toBeInTheDocument();
});
describe('readonly cluster', () => {
it('does not render actions if cluster is readonly', () => {
render(setupComponent({}, { ...initialValue, isReadOnly: true }));
renderComponent({}, { ...initialValue, isReadOnly: true });
expect(screen.queryByRole('button')).not.toBeInTheDocument();
});
});

View file

@ -6,8 +6,8 @@ import {
} from 'lib/paths';
import New, { NewProps } from 'components/Connect/New/New';
import { connects, connector } from 'redux/reducers/connect/__test__/fixtures';
import { Route } from 'react-router';
import { waitFor, fireEvent, screen } from '@testing-library/react';
import { Route } from 'react-router-dom';
import { fireEvent, screen, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { ControllerRenderProps } from 'react-hook-form';
@ -30,6 +30,7 @@ jest.mock('react-router-dom', () => ({
describe('New', () => {
const clusterName = 'my-cluster';
const simulateFormSubmit = async () => {
await act(() => {
userEvent.type(
screen.getByPlaceholderText('Connector Name'),
'my-connector'
@ -38,10 +39,12 @@ describe('New', () => {
screen.getByPlaceholderText('json'),
'{"class":"MyClass"}'.replace(/[{[]/g, '$&$&')
);
});
expect(screen.getByPlaceholderText('json')).toHaveValue(
'{"class":"MyClass"}'
);
await waitFor(() => {
await act(() => {
fireEvent.submit(screen.getByRole('form'));
});
};
@ -62,7 +65,9 @@ describe('New', () => {
it('fetches connects on mount', async () => {
const fetchConnects = jest.fn();
await waitFor(() => renderComponent({ fetchConnects }));
await act(() => {
renderComponent({ fetchConnects });
});
expect(fetchConnects).toHaveBeenCalledTimes(1);
expect(fetchConnects).toHaveBeenCalledWith(clusterName);
});
@ -71,6 +76,7 @@ describe('New', () => {
const createConnector = jest.fn();
renderComponent({ createConnector });
await simulateFormSubmit();
expect(createConnector).toHaveBeenCalledTimes(1);
expect(createConnector).toHaveBeenCalledWith({
clusterName,

View file

@ -7,7 +7,7 @@ import {
import { ConsumerGroupID } from 'redux/interfaces/consumerGroup';
import PageLoader from 'components/common/PageLoader/PageLoader';
import ConfirmationModal from 'components/common/ConfirmationModal/ConfirmationModal';
import { useHistory, useParams } from 'react-router';
import { useHistory, useParams } from 'react-router-dom';
import ClusterContext from 'components/contexts/ClusterContext';
import PageHeading from 'components/common/PageHeading/PageHeading';
import VerticalElipsisIcon from 'components/common/Icons/VerticalElipsisIcon';

View file

@ -15,7 +15,7 @@ import 'react-datepicker/dist/react-datepicker.css';
import { groupBy } from 'lodash';
import PageLoader from 'components/common/PageLoader/PageLoader';
import { ErrorMessage } from '@hookform/error-message';
import { useHistory, useParams } from 'react-router';
import { useHistory, useParams } from 'react-router-dom';
import Select from 'components/common/Select/Select';
import { InputLabel } from 'components/common/Input/InputLabel.styled';
import { Button } from 'components/common/Button/Button';

View file

@ -1,7 +1,7 @@
import React from 'react';
import fetchMock from 'fetch-mock';
import { Route } from 'react-router';
import { screen, waitFor } from '@testing-library/react';
import { Route } from 'react-router-dom';
import { act, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { render } from 'lib/testHelpers';
import { clusterConsumerGroupResetOffsetsPath } from 'lib/paths';
@ -77,12 +77,12 @@ describe('ResetOffsets', () => {
fetchMock.reset();
});
it('renders progress bar for initial state', () => {
it('renders progress bar for initial state', async () => {
fetchMock.getOnce(
`/api/clusters/${clusterName}/consumer-groups/${groupId}`,
404
);
renderComponent();
await waitFor(() => renderComponent());
expect(screen.getByRole('progressbar')).toBeInTheDocument();
});
@ -93,11 +93,10 @@ describe('ResetOffsets', () => {
`/api/clusters/${clusterName}/consumer-groups/${groupId}`,
consumerGroupPayload
);
await act(() => {
renderComponent();
await waitFor(() =>
expect(fetchConsumerGroupMock.called()).toBeTruthy()
);
await waitFor(() => screen.queryByRole('form'));
});
expect(fetchConsumerGroupMock.called()).toBeTruthy();
});
it('calls resetConsumerGroupOffsets with EARLIEST', async () => {

View file

@ -4,7 +4,7 @@ import { screen } from '@testing-library/react';
import TopicContents from 'components/ConsumerGroups/Details/TopicContents/TopicContents';
import { consumerGroupPayload } from 'redux/reducers/consumerGroups/__test__/fixtures';
import { render } from 'lib/testHelpers';
import { Route } from 'react-router';
import { Route } from 'react-router-dom';
import { ConsumerGroupTopicPartition } from 'generated-sources';
const clusterName = 'cluster1';

View file

@ -3,7 +3,7 @@ import React from 'react';
import fetchMock from 'fetch-mock';
import { createMemoryHistory } from 'history';
import { render } from 'lib/testHelpers';
import { Route, Router } from 'react-router';
import { Route, Router } from 'react-router-dom';
import {
clusterConsumerGroupDetailsPath,
clusterConsumerGroupResetOffsetsPath,
@ -16,6 +16,7 @@ import {
waitForElementToBeRemoved,
} from '@testing-library/dom';
import userEvent from '@testing-library/user-event';
import { act } from '@testing-library/react';
const clusterName = 'cluster1';
const { groupId } = consumerGroupPayload;
@ -92,20 +93,18 @@ describe('Details component', () => {
it('handles [Delete consumer group] click', async () => {
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
await act(() => {
userEvent.click(screen.getByText('Delete consumer group'));
await waitFor(() =>
expect(screen.queryByRole('dialog')).toBeInTheDocument()
);
});
expect(screen.queryByRole('dialog')).toBeInTheDocument();
const deleteConsumerGroupMock = fetchMock.deleteOnce(
`/api/clusters/${clusterName}/consumer-groups/${groupId}`,
200
);
await act(() => {
userEvent.click(screen.getByText('Submit'));
await waitFor(() =>
expect(deleteConsumerGroupMock.called()).toBeTruthy()
);
});
expect(deleteConsumerGroupMock.called()).toBeTruthy();
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
expect(history.location.pathname).toEqual(
clusterConsumerGroupsPath(clusterName)

View file

@ -5,7 +5,7 @@ import userEvent from '@testing-library/user-event';
import ListItem from 'components/ConsumerGroups/Details/ListItem';
import { consumerGroupPayload } from 'redux/reducers/consumerGroups/__test__/fixtures';
import { render } from 'lib/testHelpers';
import { Route } from 'react-router';
import { Route } from 'react-router-dom';
import { ConsumerGroupTopicPartition } from 'generated-sources';
const clusterName = 'cluster1';

View file

@ -1,6 +1,7 @@
import React from 'react';
import { clusterConsumerGroupsPath } from 'lib/paths';
import {
act,
screen,
waitFor,
waitForElementToBeRemoved,
@ -12,7 +13,7 @@ import {
} from 'redux/reducers/consumerGroups/__test__/fixtures';
import { render } from 'lib/testHelpers';
import fetchMock from 'fetch-mock';
import { Route, Router } from 'react-router';
import { Route, Router } from 'react-router-dom';
import { ConsumerGroupOrdering, SortOrder } from 'generated-sources';
import { createMemoryHistory } from 'history';
@ -37,7 +38,6 @@ const renderComponent = (history = historyMock) =>
describe('ConsumerGroups', () => {
it('renders with initial state', async () => {
renderComponent();
expect(screen.getByRole('progressbar')).toBeInTheDocument();
});
@ -54,10 +54,10 @@ describe('ConsumerGroups', () => {
sortOrder: SortOrder.ASC,
},
});
await act(() => {
renderComponent();
await waitFor(() => expect(fetchMock.calls().length).toBe(1));
});
expect(fetchMock.calls().length).toBe(1);
expect(screen.getByRole('table')).toBeInTheDocument();
expect(screen.getByText('No active consumer groups')).toBeInTheDocument();
});

View file

@ -3,7 +3,7 @@ import PageLoader from 'components/common/PageLoader/PageLoader';
import ListItem from 'components/KsqlDb/List/ListItem';
import React, { FC, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useParams } from 'react-router';
import { useParams } from 'react-router-dom';
import { fetchKsqlDbTables } from 'redux/reducers/ksqlDb/ksqlDbSlice';
import { getKsqlDbTables } from 'redux/reducers/ksqlDb/selectors';
import { clusterKsqlDbQueryPath } from 'lib/paths';

View file

@ -1,6 +1,6 @@
import React from 'react';
import List from 'components/KsqlDb/List/List';
import { Route, Router } from 'react-router';
import { Route, Router } from 'react-router-dom';
import { createMemoryHistory } from 'history';
import { clusterKsqlDbPath } from 'lib/paths';
import { render } from 'lib/testHelpers';

View file

@ -1,5 +1,5 @@
import React, { useCallback, useEffect, FC, useState } from 'react';
import { useParams } from 'react-router';
import { useParams } from 'react-router-dom';
import TableRenderer from 'components/KsqlDb/Query/renderer/TableRenderer/TableRenderer';
import {
executeKsql,

View file

@ -1,8 +1,9 @@
import { render } from 'lib/testHelpers';
import React from 'react';
import QueryForm, { Props } from 'components/KsqlDb/Query/QueryForm/QueryForm';
import { screen, waitFor, within } from '@testing-library/dom';
import { screen, within } from '@testing-library/dom';
import userEvent from '@testing-library/user-event';
import { act } from '@testing-library/react';
const renderComponent = (props: Props) => render(<QueryForm {...props} />);
@ -65,7 +66,7 @@ describe('QueryForm', () => {
submitHandler: submitFn,
});
await waitFor(() =>
await act(() =>
userEvent.click(screen.getByRole('button', { name: 'Execute' }))
);
expect(screen.getByText('ksql is a required field')).toBeInTheDocument();
@ -81,7 +82,7 @@ describe('QueryForm', () => {
submitHandler: jest.fn(),
});
await waitFor(() =>
await act(() => {
// the use of `paste` is a hack that i found somewhere,
// `type` won't work
userEvent.paste(
@ -89,12 +90,10 @@ describe('QueryForm', () => {
screen.getByLabelText('Stream properties (JSON format)')
).getByRole('textbox'),
'not-a-JSON-string'
)
);
await waitFor(() =>
userEvent.click(screen.getByRole('button', { name: 'Execute' }))
);
userEvent.click(screen.getByRole('button', { name: 'Execute' }));
});
expect(
screen.getByText('streamsProperties is not JSON object')
@ -110,19 +109,15 @@ describe('QueryForm', () => {
submitHandler: jest.fn(),
});
await waitFor(() =>
await act(() => {
userEvent.paste(
within(
screen.getByLabelText('Stream properties (JSON format)')
).getByRole('textbox'),
'{"totallyJSON": "string"}'
)
);
await waitFor(() =>
userEvent.click(screen.getByRole('button', { name: 'Execute' }))
);
userEvent.click(screen.getByRole('button', { name: 'Execute' }));
});
expect(
screen.queryByText('streamsProperties is not JSON object')
).not.toBeInTheDocument();
@ -138,25 +133,21 @@ describe('QueryForm', () => {
submitHandler: submitFn,
});
await waitFor(() =>
await act(() => {
userEvent.paste(
within(screen.getByLabelText('KSQL')).getByRole('textbox'),
'show tables;'
)
);
await waitFor(() =>
userEvent.paste(
within(
screen.getByLabelText('Stream properties (JSON format)')
).getByRole('textbox'),
'{"totallyJSON": "string"}'
)
);
await waitFor(() =>
userEvent.click(screen.getByRole('button', { name: 'Execute' }))
);
userEvent.click(screen.getByRole('button', { name: 'Execute' }));
});
expect(
screen.queryByText('ksql is a required field')
@ -181,7 +172,7 @@ describe('QueryForm', () => {
expect(screen.getByRole('button', { name: 'Clear results' })).toBeEnabled();
await waitFor(() =>
await act(() =>
userEvent.click(screen.getByRole('button', { name: 'Clear results' }))
);
@ -200,7 +191,7 @@ describe('QueryForm', () => {
expect(screen.getByRole('button', { name: 'Stop query' })).toBeEnabled();
await waitFor(() =>
await act(() =>
userEvent.click(screen.getByRole('button', { name: 'Stop query' }))
);
@ -217,19 +208,17 @@ describe('QueryForm', () => {
submitHandler: submitFn,
});
await waitFor(() =>
await act(() => {
userEvent.paste(
within(screen.getByLabelText('KSQL')).getByRole('textbox'),
'show tables;'
)
);
await waitFor(() =>
userEvent.type(
within(screen.getByLabelText('KSQL')).getByRole('textbox'),
'{ctrl}{enter}'
)
);
});
expect(submitFn.mock.calls.length).toBe(1);
});
@ -244,30 +233,26 @@ describe('QueryForm', () => {
submitHandler: submitFn,
});
await waitFor(() =>
await act(() => {
userEvent.paste(
within(screen.getByLabelText('KSQL')).getByRole('textbox'),
'show tables;'
)
);
await waitFor(() =>
userEvent.paste(
within(
screen.getByLabelText('Stream properties (JSON format)')
).getByRole('textbox'),
'{"some":"json"}'
)
);
await waitFor(() =>
userEvent.type(
within(
screen.getByLabelText('Stream properties (JSON format)')
).getByRole('textbox'),
'{ctrl}{enter}'
)
);
});
expect(submitFn.mock.calls.length).toBe(1);
});
@ -281,20 +266,17 @@ describe('QueryForm', () => {
submitHandler: jest.fn(),
});
await waitFor(() =>
await act(() => {
userEvent.paste(
within(screen.getByLabelText('KSQL')).getByRole('textbox'),
'show tables;'
)
);
await waitFor(() =>
userEvent.click(
within(screen.getByLabelText('KSQL')).getByRole('button', {
name: 'Clear',
})
)
);
});
expect(screen.queryByText('show tables;')).not.toBeInTheDocument();
});
@ -308,25 +290,21 @@ describe('QueryForm', () => {
submitHandler: jest.fn(),
});
await waitFor(() =>
await act(() => {
userEvent.paste(
within(
screen.getByLabelText('Stream properties (JSON format)')
).getByRole('textbox'),
'{"some":"json"}'
)
);
await waitFor(() =>
userEvent.click(
within(
screen.getByLabelText('Stream properties (JSON format)')
).getByRole('button', {
name: 'Clear',
})
)
);
});
expect(screen.queryByText('{"some":"json"}')).not.toBeInTheDocument();
});
});

View file

@ -3,11 +3,12 @@ import React from 'react';
import Query, {
getFormattedErrorFromTableData,
} from 'components/KsqlDb/Query/Query';
import { screen, waitFor, within } from '@testing-library/dom';
import { screen, within } from '@testing-library/dom';
import fetchMock from 'fetch-mock';
import userEvent from '@testing-library/user-event';
import { Route } from 'react-router-dom';
import { clusterKsqlDbQueryPath } from 'lib/paths';
import { act } from '@testing-library/react';
const clusterName = 'testLocal';
const renderComponent = () =>
@ -42,16 +43,14 @@ describe('Query', () => {
value: EventSourceMock,
});
await waitFor(() =>
await act(() => {
userEvent.paste(
within(screen.getByLabelText('KSQL')).getByRole('textbox'),
'show tables;'
)
);
userEvent.click(screen.getByRole('button', { name: 'Execute' }));
});
await waitFor(() =>
userEvent.click(screen.getByRole('button', { name: 'Execute' }))
);
expect(mock.calls().length).toBe(1);
});
@ -66,25 +65,19 @@ describe('Query', () => {
value: EventSourceMock,
});
await waitFor(() =>
await act(() => {
userEvent.paste(
within(screen.getByLabelText('KSQL')).getByRole('textbox'),
'show tables;'
)
);
await waitFor(() =>
userEvent.paste(
within(
screen.getByLabelText('Stream properties (JSON format)')
).getByRole('textbox'),
'{"some":"json"}'
)
);
await waitFor(() =>
userEvent.click(screen.getByRole('button', { name: 'Execute' }))
);
userEvent.click(screen.getByRole('button', { name: 'Execute' }));
});
expect(mock.calls().length).toBe(1);
});
@ -99,25 +92,19 @@ describe('Query', () => {
value: EventSourceMock,
});
await waitFor(() =>
await act(() => {
userEvent.paste(
within(screen.getByLabelText('KSQL')).getByRole('textbox'),
'show tables;'
)
);
await waitFor(() =>
userEvent.paste(
within(
screen.getByLabelText('Stream properties (JSON format)')
).getByRole('textbox'),
'{"some":"json"}'
)
);
await waitFor(() =>
userEvent.click(screen.getByRole('button', { name: 'Execute' }))
);
userEvent.click(screen.getByRole('button', { name: 'Execute' }));
});
expect(mock.calls().length).toBe(1);
});
});

View file

@ -24,7 +24,7 @@ const ClusterMenu: React.FC<Props> = ({
singleMode,
}) => {
const hasFeatureConfigured = React.useCallback(
(key) => features?.includes(key),
(key: ClusterFeaturesEnum) => features?.includes(key),
[features]
);
const [isOpen, setIsOpen] = React.useState(!!singleMode);

View file

@ -1,4 +1,4 @@
import React from 'react';
import React, { PropsWithChildren } from 'react';
import { NavLinkProps } from 'react-router-dom';
import * as S from './Nav.styled';
@ -11,7 +11,9 @@ export interface ClusterMenuItemProps {
isActive?: NavLinkProps['isActive'];
}
const ClusterMenuItem: React.FC<ClusterMenuItemProps> = (props) => {
const ClusterMenuItem: React.FC<PropsWithChildren<ClusterMenuItemProps>> = (
props
) => {
const { to, title, children, exact, isTopLevel, isActive } = props;
if (to) {

View file

@ -1,5 +1,5 @@
import React from 'react';
import { useHistory, useParams } from 'react-router';
import { useHistory, useParams } from 'react-router-dom';
import {
clusterSchemasPath,
clusterSchemaSchemaDiffPath,

View file

@ -1,7 +1,7 @@
import React from 'react';
import Details from 'components/Schemas/Details/Details';
import { render } from 'lib/testHelpers';
import { Route } from 'react-router';
import { Route } from 'react-router-dom';
import { clusterSchemaPath } from 'lib/paths';
import { screen, waitFor } from '@testing-library/dom';
import {
@ -14,6 +14,7 @@ import ClusterContext, {
initialValue as contextInitialValue,
} from 'components/contexts/ClusterContext';
import { RootState } from 'redux/interfaces';
import { act } from '@testing-library/react';
import { versionPayload, versionEmptyPayload } from './fixtures';
@ -24,8 +25,8 @@ const schemasAPIVersionsUrl = `/api/clusters/${clusterName}/schemas/${schemaVers
const renderComponent = (
initialState: RootState['schemas'] = schemasInitialState,
context: ContextProps = contextInitialValue
) => {
return render(
) =>
render(
<Route path={clusterSchemaPath(':clusterName', ':subject')}>
<ClusterContext.Provider value={context}>
<Details />
@ -38,7 +39,6 @@ const renderComponent = (
},
}
);
};
describe('Details', () => {
afterEach(() => fetchMock.reset());
@ -50,7 +50,10 @@ describe('Details', () => {
schemasAPIVersionsUrl,
404
);
await act(() => {
renderComponent();
});
await waitFor(() => {
expect(schemasAPILatestMock.called()).toBeTruthy();
});
@ -78,7 +81,9 @@ describe('Details', () => {
schemasAPIVersionsUrl,
versionPayload
);
await act(() => {
renderComponent();
});
await waitFor(() => {
expect(schemasAPILatestMock.called()).toBeTruthy();
});
@ -104,7 +109,9 @@ describe('Details', () => {
schemasAPIVersionsUrl,
versionEmptyPayload
);
await act(() => {
renderComponent();
});
await waitFor(() => {
expect(schemasAPILatestMock.called()).toBeTruthy();
});

View file

@ -3,7 +3,7 @@ import { SchemaSubject } from 'generated-sources';
import { clusterSchemaSchemaDiffPath } from 'lib/paths';
import PageLoader from 'components/common/PageLoader/PageLoader';
import DiffViewer from 'components/common/DiffViewer/DiffViewer';
import { useHistory, useParams, useLocation } from 'react-router';
import { useHistory, useParams, useLocation } from 'react-router-dom';
import {
fetchSchemaVersions,
SCHEMAS_VERSIONS_FETCH_ACTION,

View file

@ -1,5 +1,5 @@
import React from 'react';
import { useHistory, useParams } from 'react-router';
import { useHistory, useParams } from 'react-router-dom';
import { useForm, Controller, FormProvider } from 'react-hook-form';
import {
CompatibilityLevelCompatibilityEnum,

View file

@ -6,7 +6,7 @@ import {
schemasInitialState,
schemaVersion,
} from 'redux/reducers/schemas/__test__/fixtures';
import { Route } from 'react-router';
import { Route } from 'react-router-dom';
import { screen, waitFor } from '@testing-library/dom';
import ClusterContext, {
ContextProps,
@ -14,6 +14,7 @@ import ClusterContext, {
} from 'components/contexts/ClusterContext';
import { RootState } from 'redux/interfaces';
import fetchMock from 'fetch-mock';
import { act } from '@testing-library/react';
const clusterName = 'testClusterName';
const schemasAPILatestUrl = `/api/clusters/${clusterName}/schemas/${schemaVersion.subject}/latest`;
@ -21,8 +22,8 @@ const schemasAPILatestUrl = `/api/clusters/${clusterName}/schemas/${schemaVersio
const renderComponent = (
initialState: RootState['schemas'] = schemasInitialState,
context: ContextProps = contextInitialValue
) => {
return render(
) =>
render(
<Route path={clusterSchemaEditPath(':clusterName', ':subject')}>
<ClusterContext.Provider value={context}>
<Edit />
@ -35,21 +36,17 @@ const renderComponent = (
},
}
);
};
describe('Edit', () => {
afterEach(() => fetchMock.reset());
describe('fetch failed', () => {
beforeEach(async () => {
it('renders pageloader', async () => {
const schemasAPILatestMock = fetchMock.getOnce(schemasAPILatestUrl, 404);
await act(() => {
renderComponent();
await waitFor(() => {
expect(schemasAPILatestMock.called()).toBeTruthy();
});
});
it('renders pageloader', () => {
await waitFor(() => expect(schemasAPILatestMock.called()).toBeTruthy());
expect(screen.getByRole('progressbar')).toBeInTheDocument();
expect(screen.queryByText(schemaVersion.subject)).not.toBeInTheDocument();
expect(screen.queryByText('Submit')).not.toBeInTheDocument();
@ -58,19 +55,15 @@ describe('Edit', () => {
describe('fetch success', () => {
describe('has schema versions', () => {
beforeEach(async () => {
it('renders component with schema info', async () => {
const schemasAPILatestMock = fetchMock.getOnce(
schemasAPILatestUrl,
schemaVersion
);
await act(() => {
renderComponent();
await waitFor(() => {
expect(schemasAPILatestMock.called()).toBeTruthy();
});
});
it('renders component with schema info', () => {
await waitFor(() => expect(schemasAPILatestMock.called()).toBeTruthy());
expect(screen.getByText('Submit')).toBeInTheDocument();
expect(screen.queryByRole('progressbar')).not.toBeInTheDocument();
});

View file

@ -1,11 +1,11 @@
import React from 'react';
import { screen, waitFor, within } from '@testing-library/react';
import { act, screen, waitFor, within } from '@testing-library/react';
import { render } from 'lib/testHelpers';
import { CompatibilityLevelCompatibilityEnum } from 'generated-sources';
import GlobalSchemaSelector from 'components/Schemas/List/GlobalSchemaSelector/GlobalSchemaSelector';
import userEvent from '@testing-library/user-event';
import { clusterSchemasPath } from 'lib/paths';
import { Route } from 'react-router';
import { Route } from 'react-router-dom';
import fetchMock from 'fetch-mock';
const clusterName = 'testClusterName';
@ -42,7 +42,9 @@ describe('GlobalSchemaSelector', () => {
`api/clusters/${clusterName}/schemas/compatibility`,
{ compatibility: CompatibilityLevelCompatibilityEnum.FULL }
);
await act(() => {
renderComponent();
});
await waitFor(() =>
expect(fetchGlobalCompatibilityLevelMock.called()).toBeTruthy()
);
@ -89,7 +91,10 @@ describe('GlobalSchemaSelector', () => {
});
await waitFor(() => expect(putNewCompatibilityMock.called()).toBeTruthy());
await waitFor(() => expect(getSchemasMock.called()).toBeTruthy());
expect(screen.queryByText('Confirm the action')).not.toBeInTheDocument();
await waitFor(() =>
expect(screen.queryByText('Confirm the action')).not.toBeInTheDocument()
);
expectOptionIsSelected(CompatibilityLevelCompatibilityEnum.FORWARD);
});
});

View file

@ -1,9 +1,9 @@
import React from 'react';
import List from 'components/Schemas/List/List';
import { render } from 'lib/testHelpers';
import { Route } from 'react-router';
import { Route } from 'react-router-dom';
import { clusterSchemasPath } from 'lib/paths';
import { screen, waitFor } from '@testing-library/dom';
import { act, screen } from '@testing-library/react';
import {
schemasFulfilledState,
schemasInitialState,
@ -52,9 +52,11 @@ describe('List', () => {
schemasAPICompabilityUrl,
404
);
await act(() => {
renderComponent();
await waitFor(() => expect(fetchSchemasMock.called()).toBeTruthy());
await waitFor(() => expect(fetchCompabilityMock.called()).toBeTruthy());
});
expect(fetchSchemasMock.called()).toBeTruthy();
expect(fetchCompabilityMock.called()).toBeTruthy();
expect(screen.getByRole('progressbar')).toBeInTheDocument();
});
});
@ -70,9 +72,11 @@ describe('List', () => {
schemasAPICompabilityUrl,
200
);
await act(() => {
renderComponent();
await waitFor(() => expect(fetchSchemasMock.called()).toBeTruthy());
await waitFor(() => expect(fetchCompabilityMock.called()).toBeTruthy());
});
expect(fetchSchemasMock.called()).toBeTruthy();
expect(fetchCompabilityMock.called()).toBeTruthy();
});
it('renders empty table', () => {
expect(screen.getByText('No schemas found')).toBeInTheDocument();
@ -88,9 +92,11 @@ describe('List', () => {
schemasAPICompabilityUrl,
200
);
await act(() => {
renderComponent(schemasFulfilledState);
await waitFor(() => expect(fetchSchemasMock.called()).toBeTruthy());
await waitFor(() => expect(fetchCompabilityMock.called()).toBeTruthy());
});
expect(fetchSchemasMock.called()).toBeTruthy();
expect(fetchCompabilityMock.called()).toBeTruthy();
});
it('renders list', () => {
expect(screen.getByText(schemaVersion1.subject)).toBeInTheDocument();
@ -104,12 +110,13 @@ describe('List', () => {
schemasAPIUrl,
schemasPayload
);
await act(() => {
renderComponent(schemasFulfilledState, {
...contextInitialValue,
isReadOnly: true,
});
await waitFor(() => expect(fetchSchemasMock.called()).toBeTruthy());
});
expect(fetchSchemasMock.called()).toBeTruthy();
});
it('does not render Create Schema button', () => {
expect(screen.queryByText('Create Schema')).not.toBeInTheDocument();

View file

@ -5,7 +5,7 @@ import { ErrorMessage } from '@hookform/error-message';
import { clusterSchemaPath } from 'lib/paths';
import { SchemaType } from 'generated-sources';
import { SCHEMA_NAME_VALIDATION_PATTERN } from 'lib/constants';
import { useHistory, useParams } from 'react-router';
import { useHistory, useParams } from 'react-router-dom';
import { InputLabel } from 'components/common/Input/InputLabel.styled';
import Input from 'components/common/Input/Input';
import { FormError } from 'components/common/Input/Input.styled';

View file

@ -2,7 +2,7 @@ import React from 'react';
import New from 'components/Schemas/New/New';
import { render } from 'lib/testHelpers';
import { clusterSchemaNewPath } from 'lib/paths';
import { Route } from 'react-router';
import { Route } from 'react-router-dom';
import { screen } from '@testing-library/dom';
const clusterName = 'local';

View file

@ -9,7 +9,7 @@ import {
clusterSchemasPath,
} from 'lib/paths';
import { screen, waitFor } from '@testing-library/dom';
import { Route } from 'react-router';
import { Route } from 'react-router-dom';
import fetchMock from 'fetch-mock';
import { schemaVersion } from 'redux/reducers/schemas/__test__/fixtures';

View file

@ -1,11 +1,10 @@
import React from 'react';
import { useHistory } from 'react-router';
import { useHistory, useParams } from 'react-router-dom';
import {
TopicWithDetailedInfo,
ClusterName,
TopicName,
} from 'redux/interfaces';
import { useParams } from 'react-router-dom';
import { clusterTopicCopyPath, clusterTopicNewPath } from 'lib/paths';
import usePagination from 'lib/hooks/usePagination';
import useModal from 'lib/hooks/useModal';

View file

@ -1,7 +1,7 @@
import React from 'react';
import { render } from 'lib/testHelpers';
import { screen, waitFor, within } from '@testing-library/react';
import { Route, Router, StaticRouter } from 'react-router';
import { Route, Router, StaticRouter } from 'react-router-dom';
import ClusterContext, {
ContextProps,
} from 'components/contexts/ClusterContext';

View file

@ -10,7 +10,7 @@ import {
} from 'redux/actions';
import { useDispatch } from 'react-redux';
import { getResponse } from 'lib/errorHandling';
import { useHistory, useLocation, useParams } from 'react-router';
import { useHistory, useLocation, useParams } from 'react-router-dom';
import { yupResolver } from '@hookform/resolvers/yup';
import { topicFormValidationSchema } from 'lib/yupExtended';
import PageHeading from 'components/common/PageHeading/PageHeading';

View file

@ -1,10 +1,10 @@
import React from 'react';
import New from 'components/Topics/New/New';
import { Route, Router } from 'react-router';
import { Route, Router } from 'react-router-dom';
import configureStore from 'redux-mock-store';
import { RootState } from 'redux/interfaces';
import { Provider } from 'react-redux';
import { screen, waitFor } from '@testing-library/react';
import { act, screen, waitFor } from '@testing-library/react';
import { createMemoryHistory } from 'history';
import fetchMock from 'fetch-mock-jest';
import {
@ -139,7 +139,7 @@ describe('New', () => {
jest.spyOn(mocked, 'push');
renderComponent(mocked);
await waitFor(() => {
await act(() => {
userEvent.type(screen.getByPlaceholderText('Topic Name'), topicName);
userEvent.click(screen.getByText(/submit/i));
});

View file

@ -12,7 +12,7 @@ import {
} from 'generated-sources';
import React, { useContext } from 'react';
import { omitBy } from 'lodash';
import { useHistory, useLocation } from 'react-router';
import { useHistory, useLocation } from 'react-router-dom';
import DatePicker from 'react-datepicker';
import MultiSelect from 'components/common/MultiSelect/MultiSelect.styled';
import { Option } from 'react-multi-select-component/dist/lib/interfaces';

View file

@ -3,7 +3,7 @@ import AddEditFilterContainer, {
AddEditFilterContainerProps,
} from 'components/Topics/Topic/Details/Messages/Filters/AddEditFilterContainer';
import { render } from 'lib/testHelpers';
import { screen, waitFor } from '@testing-library/react';
import { act, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { MessageFilters } from 'components/Topics/Topic/Details/Messages/Filters/Filters';
@ -15,7 +15,9 @@ describe('AddEditFilterContainer component', () => {
code: 'mockCode',
};
const setupComponent = (props: Partial<AddEditFilterContainerProps> = {}) =>
const renderComponent = (
props: Partial<AddEditFilterContainerProps> = {}
) => {
render(
<AddEditFilterContainer
cancelBtnHandler={jest.fn()}
@ -23,10 +25,11 @@ describe('AddEditFilterContainer component', () => {
{...props}
/>
);
};
describe('default Component Parameters', () => {
beforeEach(async () => {
await waitFor(() => setupComponent());
await act(() => renderComponent());
});
it('should check the default Button text', () => {
@ -40,39 +43,43 @@ describe('AddEditFilterContainer component', () => {
const inputs = screen.getAllByRole('textbox');
const textAreaElement = inputs[0] as HTMLTextAreaElement;
userEvent.paste(textAreaElement, 'Hello World With TextArea');
await act(() =>
userEvent.paste(textAreaElement, 'Hello World With TextArea')
);
const inputNameElement = inputs[1];
userEvent.type(inputNameElement, 'Hello World!');
await act(() => userEvent.type(inputNameElement, 'Hello World!'));
await waitFor(() => expect(submitButtonElem).toBeEnabled());
expect(submitButtonElem).toBeEnabled();
userEvent.clear(inputNameElement);
await act(() => userEvent.clear(inputNameElement));
await waitFor(() => expect(submitButtonElem).toBeDisabled());
expect(submitButtonElem).toBeDisabled();
});
it('should view the error message after typing and clearing the input', async () => {
const inputs = screen.getAllByRole('textbox');
const textAreaElement = inputs[0] as HTMLTextAreaElement;
userEvent.paste(textAreaElement, 'Hello World With TextArea');
await act(() =>
userEvent.paste(textAreaElement, 'Hello World With TextArea')
);
const inputNameElement = inputs[1];
await act(() => {
userEvent.type(inputNameElement, 'Hello World!');
userEvent.clear(inputNameElement);
userEvent.clear(textAreaElement);
});
await waitFor(() =>
expect(screen.getByText(/required field/i)).toBeInTheDocument()
);
expect(screen.getByText(/required field/i)).toBeInTheDocument();
});
});
describe('Custom setup for the component', () => {
it('should render the input with default data if they are passed', async () => {
setupComponent({
renderComponent({
inputDisplayNameDefaultValue: mockData.name,
inputCodeDefaultValue: mockData.code,
});
@ -80,23 +87,24 @@ describe('AddEditFilterContainer component', () => {
const inputs = screen.getAllByRole('textbox');
const textAreaElement = inputs[0] as HTMLTextAreaElement;
const inputNameElement = inputs[1];
await waitFor(() => expect(inputNameElement).toHaveValue(mockData.name));
expect(inputNameElement).toHaveValue(mockData.name);
expect(textAreaElement.value).toEqual('');
});
it('should test whether the cancel callback is being called', async () => {
const cancelCallback = jest.fn();
setupComponent({
renderComponent({
cancelBtnHandler: cancelCallback,
});
const cancelBtnElement = screen.getByText(/cancel/i);
userEvent.click(cancelBtnElement);
await waitFor(() => expect(cancelCallback).toBeCalled());
await act(() => userEvent.click(cancelBtnElement));
expect(cancelCallback).toBeCalled();
});
it('should test whether the submit Callback is being called', async () => {
const submitCallback = jest.fn();
setupComponent({
renderComponent({
submitCallback,
});
@ -106,30 +114,30 @@ describe('AddEditFilterContainer component', () => {
userEvent.paste(textAreaElement, 'Hello World With TextArea');
const inputNameElement = inputs[1];
userEvent.type(inputNameElement, 'Hello World!');
await act(() => userEvent.type(inputNameElement, 'Hello World!'));
const submitBtnElement = screen.getByText(defaultSubmitBtn);
await waitFor(() => expect(submitBtnElement).toBeEnabled());
expect(submitBtnElement).toBeEnabled();
userEvent.click(submitBtnElement);
await act(() => userEvent.click(submitBtnElement));
await waitFor(() => expect(submitCallback).toBeCalled());
expect(submitCallback).toBeCalled();
});
it('should display the checkbox if the props is passed and initially check state', async () => {
setupComponent({ isAdd: true });
renderComponent({ isAdd: true });
const checkbox = screen.getByRole('checkbox');
expect(checkbox).toBeInTheDocument();
expect(checkbox).not.toBeChecked();
await waitFor(() => userEvent.click(checkbox));
await act(() => userEvent.click(checkbox));
expect(checkbox).toBeChecked();
});
it('should pass and render the correct button text', async () => {
const submitBtnText = 'submitBtnTextTest';
await waitFor(() =>
setupComponent({
await act(() =>
renderComponent({
submitBtnText,
})
);

View file

@ -4,7 +4,7 @@ import AddFilter, {
} from 'components/Topics/Topic/Details/Messages/Filters/AddFilter';
import { render } from 'lib/testHelpers';
import { MessageFilters } from 'components/Topics/Topic/Details/Messages/Filters/Filters';
import { screen, waitFor } from '@testing-library/react';
import { act, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
const filters: MessageFilters[] = [
@ -14,7 +14,7 @@ const filters: MessageFilters[] = [
const editFilterMock = jest.fn();
const setupComponent = (props: Partial<FilterModalProps> = {}) =>
const renderComponent = (props: Partial<FilterModalProps> = {}) =>
render(
<AddFilter
toggleIsOpen={jest.fn()}
@ -30,20 +30,21 @@ const setupComponent = (props: Partial<FilterModalProps> = {}) =>
describe('AddFilter component', () => {
it('should test click on Saved Filters redirects to Saved components', () => {
setupComponent();
renderComponent();
userEvent.click(screen.getByRole('savedFilterText'));
expect(screen.getByText('Saved filters')).toBeInTheDocument();
expect(screen.getAllByRole('savedFilter')).toHaveLength(2);
});
it('should test click on return to custom filter redirects to Add filters', async () => {
setupComponent();
renderComponent();
userEvent.click(screen.getByRole('savedFilterText'));
expect(screen.getByText('Saved filters')).toBeInTheDocument();
expect(screen.queryByRole('savedFilterText')).not.toBeInTheDocument();
expect(screen.getAllByRole('savedFilter')).toHaveLength(2);
await waitFor(() =>
await act(() =>
userEvent.click(screen.getByText(/back to custom filters/i))
);
expect(screen.queryByText('Saved filters')).not.toBeInTheDocument();
@ -52,7 +53,9 @@ describe('AddFilter component', () => {
describe('Add new filter', () => {
beforeEach(async () => {
await waitFor(() => setupComponent());
await act(() => {
renderComponent();
});
});
it('adding new filter', async () => {
@ -66,8 +69,12 @@ describe('AddFilter component', () => {
const addFilterBtn = screen.getByRole('button', { name: /Add filter/i });
expect(addFilterBtn).toBeDisabled();
expect(screen.getByPlaceholderText('Enter Name')).toBeInTheDocument();
await waitFor(() => userEvent.paste(codeTextBox, codeValue));
await waitFor(() => userEvent.type(nameTextBox, nameValue));
await act(() => {
userEvent.paste(codeTextBox, codeValue);
userEvent.type(nameTextBox, nameValue);
});
expect(addFilterBtn).toBeEnabled();
expect(codeTextBox.value).toEqual(`${codeValue}\n\n`);
expect(nameTextBox).toHaveValue(nameValue);
@ -81,7 +88,7 @@ describe('AddFilter component', () => {
const addFilterBtn = screen.getByRole('button', { name: /Add filter/i });
expect(addFilterBtn).toBeDisabled();
expect(screen.getByPlaceholderText('Enter Name')).toBeInTheDocument();
await waitFor(() => userEvent.paste(codeTextBox, code));
await act(() => userEvent.paste(codeTextBox, code));
expect(addFilterBtn).toBeEnabled();
expect(codeTextBox).toHaveValue(`${code}\n\n`);
});
@ -110,13 +117,13 @@ describe('AddFilter component', () => {
const nameValue = 'filter name';
beforeEach(async () => {
await waitFor(() =>
setupComponent({
await act(() => {
renderComponent({
addFilter: addFilterMock,
activeFilterHandler: activeFilterHandlerMock,
toggleIsOpen: toggleModelMock,
})
);
});
});
});
afterEach(() => {
@ -127,7 +134,7 @@ describe('AddFilter component', () => {
describe('OnSubmit conditions with codeValue and nameValue in fields', () => {
beforeEach(async () => {
await waitFor(() => {
await act(() => {
userEvent.paste(
screen.getAllByRole('textbox')[0] as HTMLTextAreaElement,
codeValue
@ -142,19 +149,20 @@ describe('AddFilter component', () => {
name: /Add filter/i,
});
expect(addFilterBtn).toBeEnabled();
userEvent.click(addFilterBtn);
await waitFor(() => expect(activeFilterHandlerMock).toHaveBeenCalled());
await act(() => userEvent.click(addFilterBtn));
expect(activeFilterHandlerMock).toHaveBeenCalled();
expect(addFilterMock).not.toHaveBeenCalled();
});
it('OnSubmit condition with checkbox on functionality', async () => {
await act(() => {
userEvent.click(screen.getByRole('checkbox'));
userEvent.click(screen.getAllByRole('button')[1]);
await waitFor(() =>
expect(activeFilterHandlerMock).not.toHaveBeenCalled()
);
});
expect(activeFilterHandlerMock).not.toHaveBeenCalled();
expect(addFilterMock).toHaveBeenCalled();
expect(toggleModelMock).not.toHaveBeenCalled();
});
@ -169,13 +177,12 @@ describe('AddFilter component', () => {
name: /Add filter/i,
});
userEvent.clear(nameTextBox);
await act(() => userEvent.clear(nameTextBox));
expect(nameTextBox).toHaveValue('');
userEvent.click(addFilterBtn);
await waitFor(() =>
expect(activeFilterHandlerMock).toHaveBeenCalledTimes(1)
);
await act(() => userEvent.click(addFilterBtn));
expect(activeFilterHandlerMock).toHaveBeenCalledTimes(1);
expect(activeFilterHandlerMock).toHaveBeenCalledWith(
{
@ -189,20 +196,18 @@ describe('AddFilter component', () => {
expect(codeTextBox).toHaveValue(``);
expect(toggleModelMock).toHaveBeenCalled();
userEvent.paste(codeTextBox, codeValue);
await act(() => userEvent.paste(codeTextBox, codeValue));
expect(codeTextBox).toHaveValue(`${codeValue}\n\n`);
userEvent.click(checkbox);
await act(() => userEvent.click(checkbox));
expect(addFilterBtn).toBeDisabled();
userEvent.type(nameTextBox, nameValue);
await act(() => userEvent.type(nameTextBox, nameValue));
expect(nameTextBox).toHaveValue(nameValue);
await waitFor(() => expect(addFilterBtn).toBeEnabled());
userEvent.click(addFilterBtn);
expect(addFilterBtn).toBeEnabled();
await act(() => userEvent.click(addFilterBtn));
await waitFor(() =>
expect(activeFilterHandlerMock).toHaveBeenCalledTimes(1)
);
expect(activeFilterHandlerMock).toHaveBeenCalledTimes(1);
expect(addFilterMock).toHaveBeenCalledWith({
name: nameValue,
code: codeValue,
@ -217,22 +222,19 @@ describe('AddFilter component', () => {
)[0] as HTMLTextAreaElement;
const nameTextBox = screen.getAllByRole('textbox')[1];
const addFilterBtn = screen.getByRole('button', { name: /Add filter/i });
await act(() => {
userEvent.clear(nameTextBox);
userEvent.clear(codeTextBox);
await waitFor(() => {
userEvent.paste(codeTextBox, longCodeValue);
});
expect(nameTextBox).toHaveValue('');
expect(codeTextBox).toHaveValue(`${longCodeValue}\n\n`);
userEvent.click(addFilterBtn);
await act(() => userEvent.click(addFilterBtn));
const filterName = `${longCodeValue.slice(0, 16)}...`;
await waitFor(() => {
expect(activeFilterHandlerMock).toHaveBeenCalledTimes(1);
expect(activeFilterHandlerMock).toHaveBeenCalledWith(
{
@ -242,9 +244,8 @@ describe('AddFilter component', () => {
},
-1
);
expect(codeTextBox.value).toEqual('');
expect(codeTextBox).toHaveValue('');
expect(toggleModelMock).toHaveBeenCalled();
});
});
});
});

View file

@ -3,7 +3,7 @@ import EditFilter, {
EditFilterProps,
} from 'components/Topics/Topic/Details/Messages/Filters/EditFilter';
import { render } from 'lib/testHelpers';
import { screen, waitFor, fireEvent, within } from '@testing-library/react';
import { screen, fireEvent, within, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { FilterEdit } from 'components/Topics/Topic/Details/Messages/Filters/FilterModal';
@ -12,7 +12,7 @@ const editFilter: FilterEdit = {
filter: { name: 'name', code: '' },
};
const setupComponent = (props?: Partial<EditFilterProps>) =>
const renderComponent = (props?: Partial<EditFilterProps>) =>
render(
<EditFilter
toggleEditModal={jest.fn()}
@ -24,13 +24,17 @@ const setupComponent = (props?: Partial<EditFilterProps>) =>
describe('EditFilter component', () => {
it('renders component', async () => {
await waitFor(() => setupComponent());
await act(() => {
renderComponent();
});
expect(screen.getByText(/edit saved filter/i)).toBeInTheDocument();
});
it('closes editFilter modal', async () => {
const toggleEditModal = jest.fn();
await waitFor(() => setupComponent({ toggleEditModal }));
await act(() => {
renderComponent({ toggleEditModal });
});
userEvent.click(screen.getByRole('button', { name: /Cancel/i }));
expect(toggleEditModal).toHaveBeenCalledTimes(1);
});
@ -38,19 +42,27 @@ describe('EditFilter component', () => {
it('save edited fields and close modal', async () => {
const toggleEditModal = jest.fn();
const editSavedFilter = jest.fn();
await waitFor(() => setupComponent({ toggleEditModal, editSavedFilter }));
await act(() => {
renderComponent({ toggleEditModal, editSavedFilter });
});
const inputs = screen.getAllByRole('textbox');
const textAreaElement = inputs[0] as HTMLTextAreaElement;
const inputNameElement = inputs[1];
await act(() => {
userEvent.paste(textAreaElement, 'edited code');
userEvent.type(inputNameElement, 'edited name');
await waitFor(() => fireEvent.submit(screen.getByRole('form')));
fireEvent.submit(screen.getByRole('form'));
});
expect(toggleEditModal).toHaveBeenCalledTimes(1);
expect(editSavedFilter).toHaveBeenCalledTimes(1);
});
it('checks input values to match', async () => {
await waitFor(() => setupComponent());
await act(() => {
renderComponent();
});
const inputs = screen.getAllByRole('textbox');
const textAreaElement = inputs[0] as HTMLTextAreaElement;
const inputNameElement = inputs[1];

View file

@ -4,12 +4,12 @@ import FilterModal, {
} from 'components/Topics/Topic/Details/Messages/Filters/FilterModal';
import { render } from 'lib/testHelpers';
import { MessageFilters } from 'components/Topics/Topic/Details/Messages/Filters/Filters';
import { screen, waitFor } from '@testing-library/react';
import { screen, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
const filters: MessageFilters[] = [{ name: 'name', code: 'code' }];
const setupWrapper = (props?: Partial<FilterModalProps>) =>
const renderComponent = (props?: Partial<FilterModalProps>) =>
render(
<FilterModal
toggleIsOpen={jest.fn()}
@ -23,7 +23,9 @@ const setupWrapper = (props?: Partial<FilterModalProps>) =>
);
describe('FilterModal component', () => {
beforeEach(async () => {
await waitFor(() => setupWrapper());
await act(() => {
renderComponent();
});
});
it('renders component with add filter modal', () => {
expect(

View file

@ -5,7 +5,7 @@ import Filters, {
SeekTypeOptions,
} from 'components/Topics/Topic/Details/Messages/Filters/Filters';
import { render } from 'lib/testHelpers';
import { screen, waitFor, within } from '@testing-library/react';
import { act, screen, within, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import TopicMessagesContext, {
ContextProps,
@ -19,7 +19,7 @@ const defaultContextValue: ContextProps = {
changeSeekDirection: jest.fn(),
};
const setupWrapper = (
const renderComponent = (
props: Partial<FiltersProps> = {},
ctx: ContextProps = defaultContextValue
) => {
@ -41,52 +41,58 @@ const setupWrapper = (
</TopicMessagesContext.Provider>
);
};
const getSubmit = () => screen.getByText('Submit');
describe('Filters component', () => {
it('shows cancel button while fetching', () => {
setupWrapper({ isFetching: true });
renderComponent({ isFetching: true });
expect(screen.getByText('Cancel')).toBeInTheDocument();
});
it('shows submit button while fetching is over', () => {
setupWrapper();
expect(getSubmit()).toBeInTheDocument();
renderComponent();
expect(screen.getByText('Submit')).toBeInTheDocument();
});
describe('Input elements', () => {
const inputValue = 'Hello World!';
beforeEach(() => setupWrapper());
beforeEach(async () => {
await act(() => {
renderComponent();
});
});
it('search input', () => {
const SearchInput = screen.getByPlaceholderText('Search');
expect(SearchInput).toBeInTheDocument();
expect(SearchInput).toHaveValue('');
userEvent.type(SearchInput, inputValue);
expect(SearchInput).toHaveValue(inputValue);
const searchInput = screen.getByPlaceholderText('Search');
expect(searchInput).toHaveValue('');
userEvent.type(searchInput, inputValue);
expect(searchInput).toHaveValue(inputValue);
});
it('offset input', () => {
const OffsetInput = screen.getByPlaceholderText('Offset');
expect(OffsetInput).toBeInTheDocument();
expect(OffsetInput).toHaveValue('');
userEvent.type(OffsetInput, inputValue);
expect(OffsetInput).toHaveValue(inputValue);
const offsetInput = screen.getByPlaceholderText('Offset');
expect(offsetInput).toHaveValue('');
userEvent.type(offsetInput, inputValue);
expect(offsetInput).toHaveValue(inputValue);
});
it('timestamp input', async () => {
const seekTypeSelect = screen.getAllByRole('listbox');
const option = screen.getAllByRole('option');
userEvent.click(seekTypeSelect[0]);
await act(() => userEvent.click(seekTypeSelect[0]));
await act(() => {
userEvent.selectOptions(seekTypeSelect[0], ['Timestamp']);
});
expect(option[0]).toHaveTextContent('Timestamp');
const timestampInput = screen.getByPlaceholderText('Select timestamp');
expect(timestampInput).toBeInTheDocument();
expect(timestampInput).toHaveValue('');
userEvent.type(timestampInput, inputValue);
await waitFor(() => expect(timestampInput).toHaveValue(inputValue));
await waitFor(() => userEvent.type(timestampInput, inputValue));
expect(timestampInput).toHaveValue(inputValue);
expect(screen.getByText('Submit')).toBeInTheDocument();
});
});
@ -101,7 +107,7 @@ describe('Filters component', () => {
const mockTypeOptionSelectLabel = selectTypeOptionValue.label;
beforeEach(() => {
setupWrapper();
renderComponent();
seekTypeSelects = screen.getAllByRole('listbox');
options = screen.getAllByRole('option');
});
@ -124,16 +130,16 @@ describe('Filters component', () => {
});
it('stop loading when live mode is active', () => {
setupWrapper();
renderComponent();
userEvent.click(screen.getByText('Stop loading'));
const option = screen.getAllByRole('option');
expect(option[1]).toHaveTextContent('Oldest First');
expect(getSubmit()).toBeInTheDocument();
expect(screen.getByText('Submit')).toBeInTheDocument();
});
it('renders addFilter modal', async () => {
setupWrapper();
await waitFor(() =>
renderComponent();
await act(() =>
userEvent.click(
screen.getByRole('button', {
name: /add filters/i,
@ -145,9 +151,9 @@ describe('Filters component', () => {
describe('when there is active smart filter', () => {
beforeEach(async () => {
setupWrapper();
renderComponent();
await waitFor(() =>
await act(() =>
userEvent.click(
screen.getByRole('button', {
name: /add filters/i,
@ -165,7 +171,7 @@ describe('Filters component', () => {
const textAreaElement = textBoxElements[0] as HTMLTextAreaElement;
const inputNameElement = textBoxElements[1];
await waitFor(() => {
await act(() => {
userEvent.paste(textAreaElement, filterName);
userEvent.type(inputNameElement, filterCode);
});
@ -173,7 +179,7 @@ describe('Filters component', () => {
expect(textAreaElement.value).toEqual(`${filterName}\n\n`);
expect(inputNameElement).toHaveValue(filterCode);
await waitFor(() =>
await act(() =>
userEvent.click(
within(messageFilterModal).getByRole('button', {
name: /add filter/i,
@ -191,7 +197,7 @@ describe('Filters component', () => {
const deleteIcon = within(smartFilterElement).getByTestId(
'activeSmartFilterCloseIcon'
);
await waitFor(() => userEvent.click(deleteIcon));
await act(() => userEvent.click(deleteIcon));
const anotherSmartFilterElement =
screen.queryByTestId('activeSmartFilter');

View file

@ -1,7 +1,7 @@
import React, { useCallback, useMemo, useState } from 'react';
import TopicMessagesContext from 'components/contexts/TopicMessagesContext';
import { SeekDirection } from 'generated-sources';
import { useLocation } from 'react-router';
import { useLocation } from 'react-router-dom';
import FiltersContainer from './Filters/FiltersContainer';
import MessagesTable from './MessagesTable';

View file

@ -6,7 +6,7 @@ import styled from 'styled-components';
import { compact, concat, groupBy, map, maxBy, minBy } from 'lodash';
import React, { useContext } from 'react';
import { useSelector } from 'react-redux';
import { useHistory } from 'react-router';
import { useHistory } from 'react-router-dom';
import {
getTopicMessges,
getIsTopicMessagesFetching,

View file

@ -2,7 +2,7 @@ import React from 'react';
import { screen } from '@testing-library/react';
import { render } from 'lib/testHelpers';
import MessagesTable from 'components/Topics/Topic/Details/Messages/MessagesTable';
import { Router } from 'react-router';
import { Router } from 'react-router-dom';
import { createMemoryHistory, MemoryHistory } from 'history';
import { SeekDirection, SeekType, TopicMessage } from 'generated-sources';
import userEvent from '@testing-library/user-event';

View file

@ -2,7 +2,7 @@ import React from 'react';
import DangerZone, {
Props,
} from 'components/Topics/Topic/Edit/DangerZone/DangerZone';
import { screen, waitFor, within } from '@testing-library/react';
import { act, screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { render } from 'lib/testHelpers';
import {
@ -13,8 +13,8 @@ import {
const defaultPartitions = 3;
const defaultReplicationFactor = 3;
const renderComponent = (props?: Partial<Props>) => {
return render(
const renderComponent = (props?: Partial<Props>) =>
render(
<DangerZone
clusterName={clusterName}
topicName={topicName}
@ -27,7 +27,6 @@ const renderComponent = (props?: Partial<Props>) => {
{...props}
/>
);
};
const clickOnDialogSubmitButton = () => {
userEvent.click(
@ -40,18 +39,14 @@ const clickOnDialogSubmitButton = () => {
const checkDialogThenPressCancel = async () => {
const dialog = screen.getByRole('dialog');
expect(screen.getByRole('dialog')).toBeInTheDocument();
await waitFor(() => {
userEvent.click(within(dialog).getByText(/cancel/i));
});
await waitFor(() =>
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
);
};
describe('DangerZone', () => {
it('renders the component', () => {
it('renders the component', async () => {
renderComponent();
const numberOfPartitionsEditForm = screen.getByRole('form', {
@ -139,27 +134,23 @@ describe('DangerZone', () => {
const partitionInput = screen.getByPlaceholderText('Number of partitions');
const partitionInputSubmitBtn = screen.getAllByText(/submit/i)[0];
const value = (defaultPartitions - 4).toString();
expect(partitionInputSubmitBtn).toBeDisabled();
await waitFor(() => {
await act(() => {
userEvent.clear(partitionInput);
userEvent.type(partitionInput, value);
});
expect(partitionInput).toHaveValue(+value);
expect(partitionInputSubmitBtn).toBeEnabled();
await act(() => {
userEvent.click(partitionInputSubmitBtn);
await waitFor(() => {
});
expect(
screen.getByText(/You can only increase the number of partitions!/i)
).toBeInTheDocument();
});
await waitFor(() => {
userEvent.clear(partitionInput);
});
expect(screen.getByText(/are required/i)).toBeInTheDocument();
await waitFor(() =>
expect(screen.getByText(/are required/i)).toBeInTheDocument()
);
});
it('should view the validation error when Replication Facto value is lower than the default passed or empty', async () => {
@ -168,31 +159,31 @@ describe('DangerZone', () => {
screen.getByPlaceholderText('Replication Factor');
const replicatorFactorInputSubmitBtn = screen.getAllByText(/submit/i)[1];
await waitFor(() => {
userEvent.clear(replicatorFactorInput);
});
await waitFor(() => userEvent.clear(replicatorFactorInput));
expect(replicatorFactorInputSubmitBtn).toBeEnabled();
await waitFor(() => {
userEvent.click(replicatorFactorInputSubmitBtn);
});
expect(screen.getByText(/are required/i)).toBeInTheDocument();
await waitFor(() => {
await waitFor(() =>
expect(screen.getByText(/are required/i)).toBeInTheDocument()
);
userEvent.type(replicatorFactorInput, '1');
});
expect(screen.queryByText(/are required/i)).not.toBeInTheDocument();
await waitFor(() =>
expect(screen.queryByText(/are required/i)).not.toBeInTheDocument()
);
});
it('should close any popup if the partitionsCount is Increased ', () => {
it('should close any popup if the partitionsCount is Increased ', async () => {
renderComponent({ partitionsCountIncreased: true });
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
await waitFor(() =>
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
);
});
it('should close any popup if the replicationFactor is Updated', () => {
it('should close any popup if the replicationFactor is Updated', async () => {
renderComponent({ replicationFactorUpdated: true });
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
await waitFor(() =>
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
);
});
it('should already opened Confirmation popup if partitionsCount is Increased', async () => {
@ -254,14 +245,12 @@ describe('DangerZone', () => {
it('should close the partitions dialog if he cancel button is pressed', async () => {
renderComponent();
const partitionInput = screen.getByPlaceholderText('Number of partitions');
const partitionInputSubmitBtn = screen.getAllByText(/submit/i)[0];
await waitFor(() => {
await act(() => {
userEvent.type(partitionInput, '5');
});
await waitFor(() => {
userEvent.click(partitionInputSubmitBtn);
});
@ -274,11 +263,8 @@ describe('DangerZone', () => {
screen.getByPlaceholderText('Replication Factor');
const replicatorFactorInputSubmitBtn = screen.getAllByText(/submit/i)[1];
await waitFor(() => {
await act(() => {
userEvent.type(replicatorFactorInput, '5');
});
await waitFor(() => {
userEvent.click(replicatorFactorInputSubmitBtn);
});

View file

@ -10,7 +10,7 @@ import {
import { useForm, FormProvider } from 'react-hook-form';
import TopicForm from 'components/Topics/shared/Form/TopicForm';
import { clusterTopicPath } from 'lib/paths';
import { useHistory } from 'react-router';
import { useHistory } from 'react-router-dom';
import { yupResolver } from '@hookform/resolvers/yup';
import { topicFormValidationSchema } from 'lib/yupExtended';
import { TOPIC_CUSTOM_PARAMS_PREFIX, TOPIC_CUSTOM_PARAMS } from 'lib/constants';

View file

@ -1,6 +1,6 @@
import React from 'react';
import Edit, { DEFAULTS, Props } from 'components/Topics/Topic/Edit/Edit';
import { screen, waitFor } from '@testing-library/react';
import { act, screen } from '@testing-library/react';
import { render } from 'lib/testHelpers';
import userEvent from '@testing-library/user-event';
import { Router } from 'react-router-dom';
@ -109,7 +109,7 @@ describe('Edit Component', () => {
const btn = screen.getAllByText(/submit/i)[0];
expect(btn).toBeEnabled();
await waitFor(() => {
await act(() => {
userEvent.type(
screen.getByPlaceholderText('Min In Sync Replicas'),
'1'
@ -117,10 +117,8 @@ describe('Edit Component', () => {
userEvent.click(btn);
});
expect(updateTopicMock).toHaveBeenCalledTimes(1);
await waitFor(() => {
expect(mocked.push).not.toHaveBeenCalled();
});
});
it('should check the submit functionality when topic updated is true', async () => {
const updateTopicMock = jest.fn();
@ -135,7 +133,7 @@ describe('Edit Component', () => {
const btn = screen.getAllByText(/submit/i)[0];
await waitFor(() => {
await act(() => {
userEvent.type(
screen.getByPlaceholderText('Min In Sync Replicas'),
'1'
@ -143,12 +141,10 @@ describe('Edit Component', () => {
userEvent.click(btn);
});
expect(updateTopicMock).toHaveBeenCalledTimes(1);
await waitFor(() => {
expect(mocked.push).toHaveBeenCalled();
expect(mocked.location.pathname).toBe(
clusterTopicPath(clusterName, topicName)
);
});
});
});
});

View file

@ -2,7 +2,7 @@ import Editor from 'components/common/Editor/Editor';
import PageLoader from 'components/common/PageLoader/PageLoader';
import React, { useEffect } from 'react';
import { useForm, Controller } from 'react-hook-form';
import { useHistory, useParams } from 'react-router';
import { useHistory, useParams } from 'react-router-dom';
import { clusterTopicMessagesPath } from 'lib/paths';
import jsf from 'json-schema-faker';
import { fetchTopicMessageSchema, messagesApiClient } from 'redux/actions';

View file

@ -1,15 +1,11 @@
import React from 'react';
import SendMessage from 'components/Topics/Topic/SendMessage/SendMessage';
import {
screen,
waitFor,
waitForElementToBeRemoved,
} from '@testing-library/react';
import { act, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import fetchMock from 'fetch-mock';
import { createMemoryHistory } from 'history';
import { render } from 'lib/testHelpers';
import { Route, Router } from 'react-router';
import { Route, Router } from 'react-router-dom';
import {
clusterTopicMessagesPath,
clusterTopicSendMessagePath,
@ -43,12 +39,15 @@ const clusterName = 'testCluster';
const topicName = externalTopicPayload.name;
const history = createMemoryHistory();
const renderComponent = () => {
const renderComponent = async () => {
history.push(clusterTopicSendMessagePath(clusterName, topicName));
await act(() => {
render(
<>
<Router history={history}>
<Route path={clusterTopicSendMessagePath(':clusterName', ':topicName')}>
<Route
path={clusterTopicSendMessagePath(':clusterName', ':topicName')}
>
<SendMessage />
</Route>
</Router>
@ -58,16 +57,17 @@ const renderComponent = () => {
</>,
{ store }
);
});
};
const renderAndSubmitData = async (error: string[] = []) => {
renderComponent();
await waitForElementToBeRemoved(() => screen.getByRole('progressbar'));
await renderComponent();
expect(screen.queryByRole('progressbar')).not.toBeInTheDocument();
await act(() => {
userEvent.selectOptions(screen.getByLabelText('Partition'), '0');
const sendBtn = await screen.findByText('Send');
(validateMessage as Mock).mockImplementation(() => error);
userEvent.click(sendBtn);
userEvent.click(screen.getByText('Send'));
});
};
describe('SendMessage', () => {
@ -85,12 +85,14 @@ describe('SendMessage', () => {
fetchMock.reset();
});
it('fetches schema on first render', () => {
it('fetches schema on first render', async () => {
const fetchTopicMessageSchemaMock = fetchMock.getOnce(
`/api/clusters/${clusterName}/topics/${topicName}/messages/schema`,
testSchema
);
await act(() => {
renderComponent();
});
expect(fetchTopicMessageSchemaMock.called()).toBeTruthy();
});
@ -107,9 +109,7 @@ describe('SendMessage', () => {
it('calls sendTopicMessage on submit', async () => {
const sendTopicMessageMock = fetchMock.postOnce(url, 200);
await renderAndSubmitData();
await waitFor(() =>
expect(sendTopicMessageMock.called(url)).toBeTruthy()
);
expect(sendTopicMessageMock.called(url)).toBeTruthy();
expect(history.location.pathname).toEqual(
clusterTopicMessagesPath(clusterName, topicName)
);
@ -120,12 +120,8 @@ describe('SendMessage', () => {
throws: 'Error',
});
await renderAndSubmitData();
await waitFor(() => {
expect(sendTopicMessageMock.called(url)).toBeTruthy();
});
await waitFor(() => {
expect(screen.getByRole('alert')).toBeInTheDocument();
});
expect(history.location.pathname).toEqual(
clusterTopicMessagesPath(clusterName, topicName)
);
@ -134,7 +130,7 @@ describe('SendMessage', () => {
it('should check and view validation error message when is not valid', async () => {
const sendTopicMessageMock = fetchMock.postOnce(url, 200);
await renderAndSubmitData(['error']);
await waitFor(() => expect(sendTopicMessageMock.called(url)).toBeFalsy());
expect(sendTopicMessageMock.called(url)).toBeFalsy();
expect(history.location.pathname).not.toEqual(
clusterTopicMessagesPath(clusterName, topicName)
);

View file

@ -1,5 +1,5 @@
import React from 'react';
import { screen, waitFor, within } from '@testing-library/react';
import React, { PropsWithChildren } from 'react';
import { act, screen, within } from '@testing-library/react';
import { render } from 'lib/testHelpers';
import CustomParamsField, {
Props,
@ -16,8 +16,8 @@ const field = { name: 'name', value: 'value', id: 'id' };
const SPACE_KEY = ' ';
const selectOption = async (listbox: HTMLElement, option: string) => {
await waitFor(() => userEvent.click(listbox));
await waitFor(() => userEvent.click(screen.getByText(option)));
await act(() => userEvent.click(listbox));
await act(() => userEvent.click(screen.getByText(option)));
};
describe('CustomParamsField', () => {
@ -25,7 +25,7 @@ describe('CustomParamsField', () => {
const setExistingFields = jest.fn();
const setupComponent = (props: Props) => {
const Wrapper: React.FC = ({ children }) => {
const Wrapper: React.FC<PropsWithChildren<unknown>> = ({ children }) => {
const methods = useForm();
return <FormProvider {...methods}>{children}</FormProvider>;
};

View file

@ -1,5 +1,5 @@
import React from 'react';
import { screen, waitFor, within } from '@testing-library/react';
import React, { PropsWithChildren } from 'react';
import { act, screen, within } from '@testing-library/react';
import { render } from 'lib/testHelpers';
import CustomParams, {
CustomParamsProps,
@ -11,10 +11,10 @@ import { TOPIC_CUSTOM_PARAMS } from 'lib/constants';
import { defaultValues } from './fixtures';
const selectOption = async (listbox: HTMLElement, option: string) => {
await waitFor(() => {
await act(() => {
userEvent.click(listbox);
userEvent.click(screen.getByText(option));
});
userEvent.click(screen.getByText(option));
};
const expectOptionIsSelected = (listbox: HTMLElement, option: string) => {
@ -28,7 +28,7 @@ const expectOptionAvailability = async (
option: string,
disabled: boolean
) => {
await waitFor(() => userEvent.click(listbox));
await act(() => userEvent.click(listbox));
const selectedOptions = within(listbox).getAllByText(option).reverse();
// its either two or one nodes, we only need last one
const selectedOption = selectedOptions[0];
@ -43,11 +43,11 @@ const expectOptionAvailability = async (
'cursor',
disabled ? 'not-allowed' : 'pointer'
);
await waitFor(() => userEvent.click(listbox));
await act(() => userEvent.click(listbox));
};
const renderComponent = (props: CustomParamsProps, defaults = {}) => {
const Wrapper: React.FC = ({ children }) => {
const Wrapper: React.FC<PropsWithChildren<unknown>> = ({ children }) => {
const methods = useForm({ defaultValues: defaults });
return <FormProvider {...methods}>{children}</FormProvider>;
};
@ -81,10 +81,11 @@ describe('CustomParams', () => {
describe('works with user inputs correctly', () => {
let button: HTMLButtonElement;
beforeEach(async () => {
renderComponent({ isSubmitting: false });
button = screen.getByRole('button');
await waitFor(() => userEvent.click(button));
await act(() => userEvent.click(button));
});
it('button click creates custom param fieldset', async () => {
@ -119,8 +120,8 @@ describe('CustomParams', () => {
});
it('multiple button clicks create multiple fieldsets', async () => {
await waitFor(() => userEvent.click(button));
await waitFor(() => userEvent.click(button));
await act(() => userEvent.click(button));
await act(() => userEvent.click(button));
const listboxes = screen.getAllByRole('listbox');
expect(listboxes.length).toBe(3);
@ -130,7 +131,7 @@ describe('CustomParams', () => {
});
it("can't select already selected option", async () => {
await waitFor(() => userEvent.click(button));
await act(() => userEvent.click(button));
const listboxes = screen.getAllByRole('listbox');
@ -143,8 +144,8 @@ describe('CustomParams', () => {
});
it('when fieldset with selected custom property type is deleted disabled options update correctly', async () => {
await waitFor(() => userEvent.click(button));
await waitFor(() => userEvent.click(button));
await act(() => userEvent.click(button));
await act(() => userEvent.click(button));
const listboxes = screen.getAllByRole('listbox');
@ -171,7 +172,7 @@ describe('CustomParams', () => {
const deleteSecondFieldsetButton = screen.getByTitle(
'Delete customParam field 1'
);
await waitFor(() => userEvent.click(deleteSecondFieldsetButton));
await act(() => userEvent.click(deleteSecondFieldsetButton));
expect(secondListbox).not.toBeInTheDocument();
await expectOptionAvailability(

View file

@ -1,4 +1,4 @@
import React from 'react';
import React, { PropsWithChildren } from 'react';
import { render } from 'lib/testHelpers';
import { screen } from '@testing-library/react';
import TimeToRetainBtn, {
@ -14,7 +14,7 @@ describe('TimeToRetainBtn', () => {
text: 'defaultPropsText',
value: 0,
};
const Wrapper: React.FC = ({ children }) => {
const Wrapper: React.FC<PropsWithChildren<unknown>> = ({ children }) => {
const methods = useForm();
return <FormProvider {...methods}>{children}</FormProvider>;
};

View file

@ -1,4 +1,4 @@
import React from 'react';
import React, { PropsWithChildren } from 'react';
import { render } from 'lib/testHelpers';
import { screen } from '@testing-library/react';
import TimeToRetainBtns, {
@ -11,7 +11,7 @@ describe('TimeToRetainBtns', () => {
name: 'defaultPropsTestingName',
value: 'defaultPropsValue',
};
const Wrapper: React.FC = ({ children }) => {
const Wrapper: React.FC<PropsWithChildren<unknown>> = ({ children }) => {
const methods = useForm();
return <FormProvider {...methods}>{children}</FormProvider>;
};

View file

@ -1,4 +1,4 @@
import React from 'react';
import React, { PropsWithChildren } from 'react';
import { render } from 'lib/testHelpers';
import { screen } from '@testing-library/dom';
import { FormProvider, useForm } from 'react-hook-form';
@ -9,7 +9,7 @@ const isSubmitting = false;
const onSubmit = jest.fn();
const renderComponent = (props: Props = { isSubmitting, onSubmit }) => {
const Wrapper: React.FC = ({ children }) => {
const Wrapper: React.FC<PropsWithChildren<unknown>> = ({ children }) => {
const methods = useForm();
return <FormProvider {...methods}>{children}</FormProvider>;
};

View file

@ -1,5 +1,5 @@
import React from 'react';
import { screen, within, waitFor } from '@testing-library/react';
import { screen, within, act } from '@testing-library/react';
import App from 'components/App';
import { render } from 'lib/testHelpers';
import { clustersPayload } from 'redux/reducers/clusters/__test__/fixtures';
@ -43,10 +43,14 @@ describe('App', () => {
describe('with clusters list fetched', () => {
it('shows Cluster list', async () => {
const mock = fetchMock.getOnce('/api/clusters', clustersPayload);
await act(() => {
render(<App />, {
pathname: '/',
});
await waitFor(() => expect(mock.called()).toBeTruthy());
});
expect(mock.called()).toBeTruthy();
const menuContainer = screen.getByLabelText('Sidebar Menu');
expect(menuContainer).toBeInTheDocument();
expect(within(menuContainer).getByText('Dashboard')).toBeInTheDocument();

View file

@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { PropsWithChildren, useState } from 'react';
import capitalize from 'lodash/capitalize';
import { BreadcrumbContext, BreadcrumbEntry } from './Breadcrumb.context';
@ -13,7 +13,9 @@ const mapLocationToPath = (
: item
);
export const BreadcrumbProvider: React.FC = ({ children }) => {
export const BreadcrumbProvider: React.FC<PropsWithChildren<unknown>> = ({
children,
}) => {
const [state, setState] = useState<BreadcrumbEntry>({
link: '',
path: [],

View file

@ -1,4 +1,4 @@
import React from 'react';
import React, { PropsWithChildren } from 'react';
import { Button } from 'components/common/Button/Button';
import { ConfirmationModalWrapper } from './ConfirmationModal.styled';
@ -12,7 +12,9 @@ export interface ConfirmationModalProps {
submitBtnText?: string;
}
const ConfirmationModal: React.FC<ConfirmationModalProps> = ({
const ConfirmationModal: React.FC<
PropsWithChildren<ConfirmationModalProps>
> = ({
isOpen,
children,
title = 'Confirm the action',

View file

@ -1,6 +1,11 @@
import useOutsideClickRef from '@rooks/use-outside-click-ref';
import cx from 'classnames';
import React, { useCallback, useMemo, useState } from 'react';
import React, {
PropsWithChildren,
useCallback,
useMemo,
useState,
} from 'react';
import * as S from './Dropdown.styled';
@ -10,7 +15,12 @@ export interface DropdownProps {
up?: boolean;
}
const Dropdown: React.FC<DropdownProps> = ({ label, right, up, children }) => {
const Dropdown: React.FC<PropsWithChildren<DropdownProps>> = ({
label,
right,
up,
children,
}) => {
const [active, setActive] = useState<boolean>(false);
const [wrapperRef] = useOutsideClickRef(() => setActive(false));
const onClick = useCallback(() => setActive(!active), [active]);

View file

@ -1,4 +1,4 @@
import React from 'react';
import React, { PropsWithChildren } from 'react';
import * as S from './Dropdown.styled';
@ -7,7 +7,7 @@ export interface DropdownItemProps {
danger?: boolean;
}
const DropdownItem: React.FC<DropdownItemProps> = ({
const DropdownItem: React.FC<PropsWithChildren<DropdownItemProps>> = ({
onClick,
danger,
children,

View file

@ -1,4 +1,4 @@
import React from 'react';
import React, { PropsWithChildren } from 'react';
import { AlertType } from 'redux/interfaces';
import * as S from './Metrics.styled';
@ -11,15 +11,14 @@ export interface Props {
alertType?: AlertType;
}
const Indicator: React.FC<Props> = ({
const Indicator: React.FC<PropsWithChildren<Props>> = ({
label,
title,
fetching,
isAlert,
alertType = 'error',
children,
}) => {
return (
}) => (
<S.IndicatorWrapper>
<div title={title}>
<S.IndicatorTitle>
@ -35,7 +34,6 @@ const Indicator: React.FC<Props> = ({
</span>
</div>
</S.IndicatorWrapper>
);
};
);
export default Indicator;

View file

@ -1,4 +1,4 @@
import React from 'react';
import React, { PropsWithChildren } from 'react';
import * as S from './Metrics.styled';
@ -6,13 +6,11 @@ interface Props {
title?: string;
}
const Section: React.FC<Props> = ({ title, children }) => {
return (
const Section: React.FC<PropsWithChildren<Props>> = ({ title, children }) => (
<div>
{title && <S.SectionTitle>{title}</S.SectionTitle>}
<S.IndicatorsWrapper>{children}</S.IndicatorsWrapper>
</div>
);
};
);
export default Section;

View file

@ -1,5 +1,5 @@
import styled from 'styled-components';
import React from 'react';
import React, { PropsWithChildren } from 'react';
import Heading from 'components/common/heading/Heading.styled';
interface Props {
@ -7,7 +7,11 @@ interface Props {
className?: string;
}
const PageHeading: React.FC<Props> = ({ text, className, children }) => {
const PageHeading: React.FC<PropsWithChildren<Props>> = ({
text,
className,
children,
}) => {
return (
<div className={className}>
<Heading>{text}</Heading>

View file

@ -1,5 +1,5 @@
import React from 'react';
import { StaticRouter } from 'react-router';
import { StaticRouter } from 'react-router-dom';
import Pagination, {
PaginationProps,
} from 'components/common/Pagination/Pagination';

View file

@ -67,9 +67,7 @@ export const TableRow = <T, TId extends IdType, OT = never>({
const Cell = cell as React.FC<TableCellProps<T, TId, OT>> | undefined;
const TdComponent = customTd || Td;
return (
<TdComponent maxWidth={maxWidth}>
{Cell ? (
const content = Cell ? (
<Cell
tableState={tableState}
hovered={hovered}
@ -78,7 +76,11 @@ export const TableRow = <T, TId extends IdType, OT = never>({
/>
) : (
field && propertyLookup(field, dataItem)
)}
);
return (
<TdComponent maxWidth={maxWidth}>
{content as React.ReactNode}
</TdComponent>
);
})}

View file

@ -1,5 +1,5 @@
/* eslint-disable jsx-a11y/anchor-is-valid */
import React from 'react';
import React, { PropsWithChildren } from 'react';
import classNames from 'classnames';
interface TabsProps {
@ -8,7 +8,7 @@ interface TabsProps {
onChange?(index: number): void;
}
const Tabs: React.FC<TabsProps> = ({
const Tabs: React.FC<PropsWithChildren<TabsProps>> = ({
tabs,
defaultSelectedIndex = 0,
onChange,

View file

@ -1,4 +1,4 @@
import React from 'react';
import React, { PropsWithChildren } from 'react';
import styled from 'styled-components';
type HeadingLevel = 1 | 2 | 3 | 4 | 5 | 6;
@ -13,7 +13,10 @@ const HeadingBase = styled.h1<HeadingBaseProps>`
export interface Props {
level?: HeadingLevel;
}
const Heading: React.FC<Props> = ({ level = 1, ...rest }) => {
const Heading: React.FC<PropsWithChildren<Props>> = ({
level = 1,
...rest
}) => {
return <HeadingBase as={`h${level}`} $level={level} {...rest} />;
};

View file

@ -1,4 +1,4 @@
import React from 'react';
import React, { PropsWithChildren } from 'react';
import { SortOrder, TopicColumnsToSort } from 'generated-sources';
import * as S from 'components/common/table/TableHeaderCell/TableHeaderCell.styled';
@ -12,7 +12,9 @@ export interface TableHeaderCellProps {
handleOrderBy?: (orderBy: TopicColumnsToSort | null) => void;
}
const TableHeaderCell: React.FC<TableHeaderCellProps> = (props) => {
const TableHeaderCell: React.FC<PropsWithChildren<TableHeaderCellProps>> = (
props
) => {
const {
title,
previewText,

View file

@ -1,5 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { createRoot } from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import * as serviceWorker from 'serviceWorker';
@ -8,13 +8,16 @@ import { store } from 'redux/store';
import 'theme/index.scss';
import 'lib/constants';
ReactDOM.render(
const container =
document.getElementById('root') || document.createElement('div');
const root = createRoot(container);
root.render(
<Provider store={store}>
<BrowserRouter basename={window.basePath || '/'}>
<App />
</BrowserRouter>
</Provider>,
document.getElementById('root')
</Provider>
);
// If you want your app to work offline and load faster, you can change

View file

@ -1,4 +1,4 @@
import { renderHook, act } from '@testing-library/react-hooks';
import { renderHook, act } from '@testing-library/react';
import useModal from 'lib/hooks/useModal';
describe('useModal CustomHook', () => {

View file

@ -1,4 +1,4 @@
import { useLocation } from 'react-router';
import { useLocation } from 'react-router-dom';
const usePagination = () => {
const { search, pathname } = useLocation();

View file

@ -1,5 +1,5 @@
import { useCallback, useEffect, useMemo } from 'react';
import { useHistory, useLocation } from 'react-router';
import { useHistory, useLocation } from 'react-router-dom';
const SEARCH_QUERY_ARG = 'q';

View file

@ -1,4 +1,4 @@
import React, { ReactElement } from 'react';
import React, { PropsWithChildren, ReactElement } from 'react';
import { StaticRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import { ThemeProvider } from 'styled-components';
@ -40,7 +40,9 @@ const customRender = (
}: CustomRenderOptions = {}
) => {
// overrides @testing-library/react render.
const AllTheProviders: React.FC = ({ children }) => {
const AllTheProviders: React.FC<PropsWithChildren<unknown>> = ({
children,
}) => {
return (
<ThemeProvider theme={theme}>
<Provider store={store}>