Issues/215 topics page tests (#1948)

* Add Tests to Topics Lists Page for TopicsTableCells

* Delete obsolete ListItems and its test suites from topics.

* Add tests suites for the new Topic creation

* Add tests suites for TopicForm styled components

* Minor code modifications in the CustomParamField test file

* Minor code modifications in the CustomParams test file

* Minor code modifications in the Topic folder

* Add test suites for Topic Edit Page + minor modifications in the DangerZone test suite

* Add tests suites for Topic Edit page

* Add tests suites for Topic Edit page

* Add tests suites for Topic Edit page

* Add tests suites for DangerZone and validation

* Add tests suites for DangerZone

* Add tests suites for DangerZone

* Add tests suites to SendMessage

* increase the tests coverage for validateMessage

* minor changes in the SendMessage and validateMessage function

* add alert message suggestion in the SendMessage

* add alert message suggestion in the SendMessage

* Total Coverage of Overview test suites

* increase tests suite coverage in the Filters styles

* increase tests suite coverage in the Filters styles + Messages Page

* improve the test coverage of the Message Component

* improve the test coverage of the Message Component

* improve the test coverage of the MessagesTable Component

* improve the test coverage of the MessagesTable Component

* improve the test coverage of the MessagesTable Component

* Add Tests for Topic Page

* Change to react testing library from enzyme Topics list

* optimizing List elements Tests suites

* delete necessary file

* minor bug fix in messages due to the rebase

* minor semantic changes in the Test suites
This commit is contained in:
Mgrdich 2022-05-11 12:27:22 +04:00 committed by GitHub
parent a94697c6af
commit e597f14b8d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 1454 additions and 693 deletions

View file

@ -1,168 +0,0 @@
import React from 'react';
import {
ClusterName,
TopicName,
TopicWithDetailedInfo,
} from 'redux/interfaces';
import DropdownItem from 'components/common/Dropdown/DropdownItem';
import Dropdown from 'components/common/Dropdown/Dropdown';
import ConfirmationModal from 'components/common/ConfirmationModal/ConfirmationModal';
import ClusterContext from 'components/contexts/ClusterContext';
import BytesFormatted from 'components/common/BytesFormatted/BytesFormatted';
import { Tag } from 'components/common/Tag/Tag.styled';
import VerticalElipsisIcon from 'components/common/Icons/VerticalElipsisIcon';
import { TableKeyLink } from 'components/common/table/Table/TableKeyLink.styled';
import * as S from './List.styled';
export interface ListItemProps {
topic: TopicWithDetailedInfo;
selected: boolean;
toggleTopicSelected(topicName: TopicName): void;
deleteTopic: (clusterName: ClusterName, topicName: TopicName) => void;
recreateTopic: (clusterName: ClusterName, topicName: TopicName) => void;
clusterName: ClusterName;
clearTopicMessages(topicName: TopicName, clusterName: ClusterName): void;
}
const ListItem: React.FC<ListItemProps> = ({
topic: {
name,
internal,
partitions,
segmentSize,
replicationFactor,
cleanUpPolicy,
},
selected,
toggleTopicSelected,
deleteTopic,
recreateTopic,
clusterName,
clearTopicMessages,
}) => {
const { isReadOnly, isTopicDeletionAllowed } =
React.useContext(ClusterContext);
const [isDeleteTopicConfirmationVisible, setDeleteTopicConfirmationVisible] =
React.useState(false);
const [
isRecreateTopicConfirmationVisible,
setRecreateTopicConfirmationVisible,
] = React.useState(false);
const { outOfSyncReplicas, numberOfMessages } = React.useMemo(() => {
if (partitions === undefined || partitions.length === 0) {
return {
outOfSyncReplicas: 0,
numberOfMessages: 0,
};
}
return partitions.reduce(
(memo, { replicas, offsetMax, offsetMin }) => {
const outOfSync = replicas?.filter(({ inSync }) => !inSync);
return {
outOfSyncReplicas: memo.outOfSyncReplicas + (outOfSync?.length || 0),
numberOfMessages: memo.numberOfMessages + (offsetMax - offsetMin),
};
},
{
outOfSyncReplicas: 0,
numberOfMessages: 0,
}
);
}, [partitions]);
const deleteTopicHandler = React.useCallback(() => {
deleteTopic(clusterName, name);
}, [clusterName, deleteTopic, name]);
const recreateTopicHandler = React.useCallback(() => {
recreateTopic(clusterName, name);
setRecreateTopicConfirmationVisible(false);
}, [recreateTopic, clusterName, name]);
const clearTopicMessagesHandler = React.useCallback(() => {
clearTopicMessages(clusterName, name);
}, [clearTopicMessages, clusterName, name]);
const [vElipsisVisble, setVElipsisVisble] = React.useState(false);
return (
<tr
onMouseEnter={() => setVElipsisVisble(true)}
onMouseLeave={() => setVElipsisVisble(false)}
>
{!isReadOnly && (
<td>
{!internal && (
<input
type="checkbox"
checked={selected}
onChange={() => {
toggleTopicSelected(name);
}}
/>
)}
</td>
)}
<TableKeyLink style={{ width: '44%' }}>
{internal && <Tag color="gray">IN</Tag>}
<S.Link exact to={`topics/${name}`} $isInternal={internal}>
{name}
</S.Link>
</TableKeyLink>
<td>{partitions?.length}</td>
<td>{outOfSyncReplicas}</td>
<td>{replicationFactor}</td>
<td>{numberOfMessages}</td>
<td>
<BytesFormatted value={segmentSize} />
</td>
<td className="topic-action-block" style={{ width: '4%' }}>
{!internal && !isReadOnly && vElipsisVisble ? (
<div className="has-text-right">
<Dropdown label={<VerticalElipsisIcon />} right>
{cleanUpPolicy === 'DELETE' && (
<DropdownItem onClick={clearTopicMessagesHandler} danger>
Clear Messages
</DropdownItem>
)}
{isTopicDeletionAllowed && (
<DropdownItem
onClick={() => setDeleteTopicConfirmationVisible(true)}
danger
>
Remove Topic
</DropdownItem>
)}
<DropdownItem
onClick={() => setRecreateTopicConfirmationVisible(true)}
danger
>
Recreate Topic
</DropdownItem>
</Dropdown>
</div>
) : null}
<ConfirmationModal
isOpen={isDeleteTopicConfirmationVisible}
onCancel={() => setDeleteTopicConfirmationVisible(false)}
onConfirm={deleteTopicHandler}
>
Are you sure want to remove <b>{name}</b> topic?
</ConfirmationModal>
<ConfirmationModal
isOpen={isRecreateTopicConfirmationVisible}
onCancel={() => setRecreateTopicConfirmationVisible(false)}
onConfirm={recreateTopicHandler}
>
Are you sure to recreate <b>{name}</b> topic?
</ConfirmationModal>
</td>
</tr>
);
};
export default ListItem;

View file

@ -1,28 +1,18 @@
import React from 'react';
import { mount, ReactWrapper } from 'enzyme';
import { Route, Router } from 'react-router-dom';
import { act } from 'react-dom/test-utils';
import { render } from 'lib/testHelpers';
import { screen, waitFor, within } from '@testing-library/react';
import { Route, Router, StaticRouter } from 'react-router';
import ClusterContext, {
ContextProps,
} from 'components/contexts/ClusterContext';
import List, { TopicsListProps } from 'components/Topics/List/List';
import { createMemoryHistory } from 'history';
import { StaticRouter } from 'react-router';
import Search from 'components/common/Search/Search';
import { externalTopicPayload } from 'redux/reducers/topics/__test__/fixtures';
import { ConfirmationModalProps } from 'components/common/ConfirmationModal/ConfirmationModal';
import theme from 'theme/theme';
import { ThemeProvider } from 'styled-components';
import { SortOrder } from 'generated-sources';
jest.mock(
'components/common/ConfirmationModal/ConfirmationModal',
() => 'mock-ConfirmationModal'
);
import { CleanUpPolicy, SortOrder } from 'generated-sources';
import userEvent from '@testing-library/user-event';
describe('List', () => {
const setupComponent = (props: Partial<TopicsListProps> = {}) => (
<ThemeProvider theme={theme}>
<List
areTopicsFetching={false}
topics={[]}
@ -40,17 +30,16 @@ describe('List', () => {
setTopicsOrderBy={jest.fn()}
{...props}
/>
</ThemeProvider>
);
const historyMock = createMemoryHistory();
const mountComponentWithProviders = (
const renderComponentWithProviders = (
contextProps: Partial<ContextProps> = {},
props: Partial<TopicsListProps> = {},
history = historyMock
) =>
mount(
render(
<Router history={history}>
<ClusterContext.Provider
value={{
@ -68,84 +57,79 @@ describe('List', () => {
describe('when it has readonly flag', () => {
it('does not render the Add a Topic button', () => {
const component = mountComponentWithProviders();
expect(component.exists('Link')).toBeFalsy();
renderComponentWithProviders();
expect(screen.queryByText(/add a topic/)).not.toBeInTheDocument();
});
});
describe('when it does not have readonly flag', () => {
let fetchTopicsList = jest.fn();
let component: ReactWrapper;
const internalTopicsSwitchName = 'input[name="ShowInternalTopics"]';
const fetchTopicsList = jest.fn();
jest.useFakeTimers();
beforeEach(() => {
fetchTopicsList = jest.fn();
component = mountComponentWithProviders(
{ isReadOnly: false },
{ fetchTopicsList }
);
afterEach(() => {
fetchTopicsList.mockClear();
});
it('renders the Add a Topic button', () => {
expect(component.exists('Link')).toBeTruthy();
renderComponentWithProviders({ isReadOnly: false }, { fetchTopicsList });
expect(screen.getByText(/add a topic/i)).toBeInTheDocument();
});
it('calls setTopicsSearch on input', () => {
it('calls setTopicsSearch on input', async () => {
const setTopicsSearch = jest.fn();
component = mountComponentWithProviders({}, { setTopicsSearch });
renderComponentWithProviders({}, { setTopicsSearch });
const query = 'topic';
const input = component.find(Search);
input.props().handleSearch(query);
const searchElement = screen.getByPlaceholderText('Search by Topic Name');
userEvent.type(searchElement, query);
await waitFor(() => {
expect(setTopicsSearch).toHaveBeenCalledWith(query);
});
});
it('show internal toggle state should be true if user has not used it yet', () => {
const toggle = component.find(internalTopicsSwitchName);
const { checked } = toggle.props();
renderComponentWithProviders({ isReadOnly: false }, { fetchTopicsList });
const internalCheckBox = screen.getByRole('checkbox');
expect(checked).toEqual(true);
expect(internalCheckBox).toBeChecked();
});
it('show internal toggle state should match user preference', () => {
localStorage.setItem('hideInternalTopics', 'true');
component = mountComponentWithProviders(
{ isReadOnly: false },
{ fetchTopicsList }
);
renderComponentWithProviders({ isReadOnly: false }, { fetchTopicsList });
const toggle = component.find(internalTopicsSwitchName);
const { checked } = toggle.props();
const internalCheckBox = screen.getByRole('checkbox');
expect(checked).toEqual(false);
expect(internalCheckBox).not.toBeChecked();
});
it('should refetch topics on show internal toggle change', () => {
jest.clearAllMocks();
const toggle = component.find(internalTopicsSwitchName);
const { checked } = toggle.props();
toggle.simulate('change');
it('should re-fetch topics on show internal toggle change', async () => {
renderComponentWithProviders({ isReadOnly: false }, { fetchTopicsList });
const internalCheckBox: HTMLInputElement = screen.getByRole('checkbox');
userEvent.click(internalCheckBox);
const { value } = internalCheckBox;
await waitFor(() => {
expect(fetchTopicsList).toHaveBeenLastCalledWith({
search: '',
showInternal: !checked,
showInternal: value === 'on',
sortOrder: SortOrder.ASC,
});
});
});
it('should reset page query param on show internal toggle change', () => {
const mockedHistory = createMemoryHistory();
jest.spyOn(mockedHistory, 'push');
component = mountComponentWithProviders(
renderComponentWithProviders(
{ isReadOnly: false },
{ fetchTopicsList },
mockedHistory
);
const toggle = component.find(internalTopicsSwitchName);
toggle.simulate('change');
const internalCheckBox: HTMLInputElement = screen.getByRole('checkbox');
userEvent.click(internalCheckBox);
expect(mockedHistory.push).toHaveBeenCalledWith('/?page=1&perPage=25');
});
@ -153,35 +137,46 @@ describe('List', () => {
it('should set cached page query param on show internal toggle change', async () => {
const mockedHistory = createMemoryHistory();
jest.spyOn(mockedHistory, 'push');
component = mountComponentWithProviders(
const cachedPage = 5;
mockedHistory.push(`/?page=${cachedPage}&perPage=25`);
renderComponentWithProviders(
{ isReadOnly: false },
{ fetchTopicsList, totalPages: 10 },
mockedHistory
);
const cachedPage = 5;
mockedHistory.push(`/?page=${cachedPage}&perPage=25`);
const input = component.find(Search);
input.props().handleSearch('nonEmptyString');
const searchInput = screen.getByPlaceholderText('Search by Topic Name');
userEvent.type(searchInput, 'nonEmptyString');
await waitFor(() => {
expect(mockedHistory.push).toHaveBeenCalledWith('/?page=1&perPage=25');
});
input.props().handleSearch('');
userEvent.clear(searchInput);
await waitFor(() => {
expect(mockedHistory.push).toHaveBeenCalledWith(
`/?page=${cachedPage}&perPage=25`
);
});
});
});
describe('when some list items are selected', () => {
const mockDeleteTopics = jest.fn();
const mockDeleteTopic = jest.fn();
const mockClearTopic = jest.fn();
const mockClearTopicsMessages = jest.fn();
const mockRecreate = jest.fn();
const fetchTopicsList = jest.fn();
jest.useFakeTimers();
const pathname = '/ui/clusters/local/topics';
const component = mount(
beforeEach(() => {
render(
<StaticRouter location={{ pathname }}>
<Route path="/ui/clusters/:clusterName">
<ClusterContext.Provider
@ -194,86 +189,106 @@ describe('List', () => {
>
{setupComponent({
topics: [
externalTopicPayload,
{
...externalTopicPayload,
cleanUpPolicy: CleanUpPolicy.DELETE,
},
{ ...externalTopicPayload, name: 'external.topic2' },
],
deleteTopics: mockDeleteTopics,
clearTopicsMessages: mockClearTopicsMessages,
recreateTopic: mockRecreate,
deleteTopic: mockDeleteTopic,
clearTopicMessages: mockClearTopic,
fetchTopicsList,
})}
</ClusterContext.Provider>
</Route>
</StaticRouter>
);
const getCheckboxInput = (at: number) =>
component.find('TableRow').at(at).find('input[type="checkbox"]').at(0);
const getConfirmationModal = () =>
component.find('mock-ConfirmationModal').at(0);
it('renders delete/purge buttons', () => {
expect(getCheckboxInput(0).props().checked).toBeFalsy();
expect(getCheckboxInput(1).props().checked).toBeFalsy();
expect(component.find('.buttons').length).toEqual(0);
// check first item
getCheckboxInput(0).simulate('change', { target: { checked: true } });
expect(getCheckboxInput(0).props().checked).toBeTruthy();
expect(getCheckboxInput(1).props().checked).toBeFalsy();
// check second item
getCheckboxInput(1).simulate('change', { target: { checked: true } });
expect(getCheckboxInput(0).props().checked).toBeTruthy();
expect(getCheckboxInput(1).props().checked).toBeTruthy();
expect(
component.find('div[data-testid="delete-buttons"]').length
).toEqual(1);
// uncheck second item
getCheckboxInput(1).simulate('change', { target: { checked: false } });
expect(getCheckboxInput(0).props().checked).toBeTruthy();
expect(getCheckboxInput(1).props().checked).toBeFalsy();
expect(
component.find('div[data-testid="delete-buttons"]').length
).toEqual(1);
// uncheck first item
getCheckboxInput(0).simulate('change', { target: { checked: false } });
expect(getCheckboxInput(0).props().checked).toBeFalsy();
expect(getCheckboxInput(1).props().checked).toBeFalsy();
expect(
component.find('div[data-testid="delete-buttons"]').length
).toEqual(0);
});
const checkActionButtonClick = async (action: string) => {
afterEach(() => {
mockDeleteTopics.mockClear();
mockClearTopicsMessages.mockClear();
mockRecreate.mockClear();
mockDeleteTopic.mockClear();
});
const getCheckboxInput = (at: number) => {
const rows = screen.getAllByRole('row');
return within(rows[at + 1]).getByRole('checkbox');
};
it('renders delete/purge buttons', () => {
const firstCheckbox = getCheckboxInput(0);
const secondCheckbox = getCheckboxInput(1);
expect(firstCheckbox).not.toBeChecked();
expect(secondCheckbox).not.toBeChecked();
// expect(component.find('.buttons').length).toEqual(0);
// check first item
userEvent.click(firstCheckbox);
expect(firstCheckbox).toBeChecked();
expect(secondCheckbox).not.toBeChecked();
expect(screen.getByTestId('delete-buttons')).toBeInTheDocument();
// check second item
userEvent.click(secondCheckbox);
expect(firstCheckbox).toBeChecked();
expect(secondCheckbox).toBeChecked();
expect(screen.getByTestId('delete-buttons')).toBeInTheDocument();
// uncheck second item
userEvent.click(secondCheckbox);
expect(firstCheckbox).toBeChecked();
expect(secondCheckbox).not.toBeChecked();
expect(screen.getByTestId('delete-buttons')).toBeInTheDocument();
// uncheck first item
userEvent.click(firstCheckbox);
expect(firstCheckbox).not.toBeChecked();
expect(secondCheckbox).not.toBeChecked();
expect(screen.queryByTestId('delete-buttons')).not.toBeInTheDocument();
});
const checkActionButtonClick = async (
action: 'deleteTopics' | 'clearTopicsMessages'
) => {
const buttonIndex = action === 'deleteTopics' ? 0 : 1;
const confirmationText =
action === 'deleteTopics'
? 'Are you sure you want to remove selected topics?'
: 'Are you sure you want to purge messages of selected topics?';
const mockFn =
action === 'deleteTopics' ? mockDeleteTopics : mockClearTopicsMessages;
getCheckboxInput(0).simulate('change', { target: { checked: true } });
getCheckboxInput(1).simulate('change', { target: { checked: true } });
let modal = getConfirmationModal();
expect(modal.prop('isOpen')).toBeFalsy();
component
.find('div[data-testid="delete-buttons"]')
.find('button')
.at(buttonIndex)
.simulate('click');
expect(modal.text()).toEqual(confirmationText);
modal = getConfirmationModal();
expect(modal.prop('isOpen')).toBeTruthy();
await act(async () => {
(modal.props() as ConfirmationModalProps).onConfirm();
const firstCheckbox = getCheckboxInput(0);
const secondCheckbox = getCheckboxInput(1);
userEvent.click(firstCheckbox);
userEvent.click(secondCheckbox);
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
const deleteButtonContainer = screen.getByTestId('delete-buttons');
const buttonClickedElement = within(deleteButtonContainer).getAllByRole(
'button'
)[buttonIndex];
userEvent.click(buttonClickedElement);
const modal = screen.getByRole('dialog');
expect(within(modal).getByText(confirmationText)).toBeInTheDocument();
userEvent.click(within(modal).getByRole('button', { name: 'Submit' }));
await waitFor(() => {
expect(screen.queryByTestId('delete-buttons')).not.toBeInTheDocument();
});
component.update();
expect(getCheckboxInput(0).props().checked).toBeFalsy();
expect(getCheckboxInput(1).props().checked).toBeFalsy();
expect(
component.find('div[data-testid="delete-buttons"]').length
).toEqual(0);
expect(mockFn).toBeCalledTimes(1);
expect(mockFn).toBeCalledWith('local', [
externalTopicPayload.name,
@ -290,28 +305,84 @@ describe('List', () => {
});
it('closes ConfirmationModal when clicked on the cancel button', async () => {
getCheckboxInput(0).simulate('change', { target: { checked: true } });
getCheckboxInput(1).simulate('change', { target: { checked: true } });
let modal = getConfirmationModal();
expect(modal.prop('isOpen')).toBeFalsy();
component
.find('div[data-testid="delete-buttons"]')
.find('button')
.at(0)
.simulate('click');
modal = getConfirmationModal();
expect(modal.prop('isOpen')).toBeTruthy();
await act(async () => {
(modal.props() as ConfirmationModalProps).onCancel();
const firstCheckbox = getCheckboxInput(0);
const secondCheckbox = getCheckboxInput(1);
userEvent.click(firstCheckbox);
userEvent.click(secondCheckbox);
const deleteButton = screen.getByText('Delete selected topics');
userEvent.click(deleteButton);
const modal = screen.getByRole('dialog');
userEvent.click(within(modal).getByText('Cancel'));
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
expect(firstCheckbox).toBeChecked();
expect(secondCheckbox).toBeChecked();
expect(screen.getByTestId('delete-buttons')).toBeInTheDocument();
expect(mockDeleteTopics).not.toHaveBeenCalled();
});
component.update();
expect(getConfirmationModal().prop('isOpen')).toBeFalsy();
expect(getCheckboxInput(0).props().checked).toBeTruthy();
expect(getCheckboxInput(1).props().checked).toBeTruthy();
expect(
component.find('div[data-testid="delete-buttons"]').length
).toEqual(1);
expect(mockDeleteTopics).toBeCalledTimes(0);
const tableRowActionClickAndCheck = async (
action: 'deleteTopics' | 'clearTopicsMessages' | 'recreate'
) => {
const row = screen.getAllByRole('row')[1];
userEvent.hover(row);
const actionBtn = within(row).getByRole('menu', { hidden: true });
userEvent.click(actionBtn);
let textBtn;
let mock: jest.Mock;
if (action === 'clearTopicsMessages') {
textBtn = 'Clear Messages';
mock = mockClearTopic;
} else if (action === 'deleteTopics') {
textBtn = 'Remove Topic';
mock = mockDeleteTopic;
} else {
textBtn = 'Recreate Topic';
mock = mockRecreate;
}
const ourAction = screen.getByText(textBtn);
userEvent.click(ourAction);
let dialog = screen.getByRole('dialog');
expect(dialog).toBeInTheDocument();
userEvent.click(within(dialog).getByRole('button', { name: 'Submit' }));
await waitFor(() => {
expect(mock).toHaveBeenCalled();
if (action === 'clearTopicsMessages') {
expect(fetchTopicsList).toHaveBeenCalled();
}
});
userEvent.click(ourAction);
dialog = screen.getByRole('dialog');
expect(dialog).toBeInTheDocument();
userEvent.click(within(dialog).getByRole('button', { name: 'Cancel' }));
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
expect(mock).toHaveBeenCalledTimes(1);
};
it('should test the actions of the row and their modal and fetching for removing', async () => {
await tableRowActionClickAndCheck('deleteTopics');
});
it('should test the actions of the row and their modal and fetching for clear', async () => {
await tableRowActionClickAndCheck('clearTopicsMessages');
});
it('should test the actions of the row and their modal and fetching for recreate', async () => {
await tableRowActionClickAndCheck('recreate');
});
});
});

View file

@ -1,74 +0,0 @@
import React from 'react';
import {
externalTopicPayload,
internalTopicPayload,
} from 'redux/reducers/topics/__test__/fixtures';
import ListItem, { ListItemProps } from 'components/Topics/List/ListItem';
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { render } from 'lib/testHelpers';
const mockDelete = jest.fn();
const clusterName = 'local';
const mockDeleteMessages = jest.fn();
const mockToggleTopicSelected = jest.fn();
const mockRecreateTopic = jest.fn();
jest.mock(
'components/common/ConfirmationModal/ConfirmationModal',
() => 'mock-ConfirmationModal'
);
jest.mock('react-redux', () => ({
...jest.requireActual('react-redux'),
useSelector: () => ['TOPIC_DELETION'],
}));
describe('ListItem', () => {
const setupComponent = (props: Partial<ListItemProps> = {}) => (
<table>
<tbody>
<ListItem
topic={internalTopicPayload}
deleteTopic={mockDelete}
clusterName={clusterName}
clearTopicMessages={mockDeleteMessages}
recreateTopic={mockRecreateTopic}
selected={false}
toggleTopicSelected={mockToggleTopicSelected}
{...props}
/>
</tbody>
</table>
);
const getCheckbox = () => screen.getByRole('checkbox');
it('renders without checkbox for internal topic', () => {
render(setupComponent({ topic: internalTopicPayload }));
expect(screen.queryByRole('checkbox')).not.toBeInTheDocument();
});
it('renders with checkbox for external topic', () => {
render(setupComponent({ topic: externalTopicPayload }));
expect(getCheckbox()).toBeInTheDocument();
});
it('triggers the toggleTopicSelected when clicked on the checkbox input', () => {
render(setupComponent({ topic: externalTopicPayload }));
expect(getCheckbox()).toBeInTheDocument();
userEvent.click(getCheckbox());
expect(mockToggleTopicSelected).toBeCalledTimes(1);
expect(mockToggleTopicSelected).toBeCalledWith(externalTopicPayload.name);
});
it('renders correct out of sync replicas number', () => {
render(
setupComponent({
topic: { ...externalTopicPayload, partitions: undefined },
})
);
expect(screen.getAllByRole('cell', { name: '0' }).length).toBeTruthy();
});
});

View file

@ -0,0 +1,149 @@
import React from 'react';
import { render } from 'lib/testHelpers';
import {
MessagesCell,
OutOfSyncReplicasCell,
TitleCell,
} from 'components/Topics/List/TopicsTableCells';
import { TableState } from 'lib/hooks/useTableState';
import { screen } from '@testing-library/react';
import { Topic } from 'generated-sources';
import { topicsPayload } from 'redux/reducers/topics/__test__/fixtures';
describe('TopicsTableCells Components', () => {
const mockTableState: TableState<Topic, string, never> = {
data: topicsPayload,
selectedIds: new Set([]),
idSelector: jest.fn(),
isRowSelectable: jest.fn(),
selectedCount: 0,
setRowsSelection: jest.fn(),
toggleSelection: jest.fn(),
};
describe('TitleCell Component', () => {
it('should check the TitleCell component Render without the internal option', () => {
const currentData = topicsPayload[1];
render(
<TitleCell
rowIndex={1}
dataItem={currentData}
tableState={mockTableState}
/>
);
expect(screen.queryByText('IN')).not.toBeInTheDocument();
expect(screen.getByText(currentData.name)).toBeInTheDocument();
});
it('should check the TitleCell component Render without the internal option', () => {
const currentData = topicsPayload[0];
render(
<TitleCell
rowIndex={1}
dataItem={currentData}
tableState={mockTableState}
/>
);
expect(screen.getByText('IN')).toBeInTheDocument();
expect(screen.getByText(currentData.name)).toBeInTheDocument();
});
});
describe('OutOfSyncReplicasCell Component', () => {
it('should check the content of the OutOfSyncReplicasCell to return 0 if no partition is empty array', () => {
const currentData = topicsPayload[0];
currentData.partitions = [];
render(
<OutOfSyncReplicasCell
rowIndex={1}
dataItem={currentData}
tableState={mockTableState}
/>
);
expect(screen.getByText('0')).toBeInTheDocument();
});
it('should check the content of the OutOfSyncReplicasCell to return 0 if no partition is found', () => {
const currentData = topicsPayload[1];
currentData.partitions = undefined;
render(
<OutOfSyncReplicasCell
rowIndex={1}
dataItem={currentData}
tableState={mockTableState}
/>
);
expect(screen.getByText('0')).toBeInTheDocument();
});
it('should check the content of the OutOfSyncReplicasCell with the correct partition number', () => {
const currentData = topicsPayload[0];
const partitionNumber = currentData.partitions?.reduce(
(memo, { replicas }) => {
const outOfSync = replicas?.filter(({ inSync }) => !inSync);
return memo + (outOfSync?.length || 0);
},
0
);
render(
<OutOfSyncReplicasCell
rowIndex={1}
dataItem={currentData}
tableState={mockTableState}
/>
);
expect(
screen.getByText(partitionNumber ? partitionNumber.toString() : '0')
).toBeInTheDocument();
});
});
describe('MessagesCell Component', () => {
it('should check the content of the MessagesCell to return 0 if no partition is empty array ', () => {
const currentData = topicsPayload[0];
currentData.partitions = [];
render(
<MessagesCell
rowIndex={1}
dataItem={topicsPayload[0]}
tableState={mockTableState}
/>
);
expect(screen.getByText('0')).toBeInTheDocument();
});
it('should check the content of the MessagesCell to return 0 if no partition is found', () => {
const currentData = topicsPayload[0];
currentData.partitions = undefined;
render(
<MessagesCell
rowIndex={1}
dataItem={topicsPayload[0]}
tableState={mockTableState}
/>
);
expect(screen.getByText('0')).toBeInTheDocument();
});
it('should check the content of the MessagesCell with the correct partition number', () => {
const currentData = topicsPayload[0];
const partitionNumber = currentData.partitions?.reduce(
(memo, { offsetMax, offsetMin }) => {
return memo + (offsetMax - offsetMin);
},
0
);
render(
<MessagesCell
rowIndex={1}
dataItem={topicsPayload[0]}
tableState={mockTableState}
/>
);
expect(
screen.getByText(partitionNumber ? partitionNumber.toString() : '0')
).toBeInTheDocument();
});
});
});

View file

@ -122,4 +122,29 @@ describe('New', () => {
expect(mockedHistory.push).toBeCalledTimes(1);
expect(createTopicAPIPathMock.called()).toBeTruthy();
});
it('submits valid form that result in an error', async () => {
const createTopicAPIPathMock = fetchMock.postOnce(
createTopicAPIPath,
{ throws: new Error('Something went wrong') },
{
body: createTopicPayload,
}
);
const mocked = createMemoryHistory({
initialEntries: [clusterTopicNewPath(clusterName)],
});
jest.spyOn(mocked, 'push');
renderComponent(mocked);
await waitFor(() => {
userEvent.type(screen.getByPlaceholderText('Topic Name'), topicName);
userEvent.click(screen.getByText(/submit/i));
});
expect(createTopicAPIPathMock.called()).toBeTruthy();
expect(mocked.push).toBeCalledTimes(0);
});
});

View file

@ -257,7 +257,9 @@ export const ConfirmDeletionText = styled.h3`
padding: 16px 0;
`;
export const MessageLoading = styled.div<MessageLoadingProps>`
export const MessageLoading = styled.div.attrs({
role: 'contentLoader',
})<MessageLoadingProps>`
color: ${({ theme }) => theme.heading.h3.color};
font-size: ${({ theme }) => theme.heading.h3.fontSize};
display: ${(props) => (props.isLive ? 'flex' : 'none')};

View file

@ -0,0 +1,27 @@
import React from 'react';
import { render } from 'lib/testHelpers';
import * as S from 'components/Topics/Topic/Details/Messages/Filters/Filters.styled';
import { screen } from '@testing-library/react';
import theme from 'theme/theme';
describe('Filters Styled components', () => {
describe('MessageLoading component', () => {
it('should check the styling during live', () => {
render(<S.MessageLoading isLive />);
expect(screen.getByRole('contentLoader')).toHaveStyle({
color: theme.heading.h3.color,
'font-size': theme.heading.h3.fontSize,
display: 'flex',
});
});
it('should check the styling during not live', () => {
render(<S.MessageLoading isLive={false} />);
expect(screen.getByRole('contentLoader', { hidden: true })).toHaveStyle({
color: theme.heading.h3.color,
'font-size': theme.heading.h3.fontSize,
display: 'none',
});
});
});
});

View file

@ -20,7 +20,11 @@ const StyledDataCell = styled.td`
min-width: 350px;
`;
const Message: React.FC<{ message: TopicMessage }> = ({
export interface Props {
message: TopicMessage;
}
const Message: React.FC<Props> = ({
message: {
timestamp,
timestampType,

View file

@ -1,29 +1,92 @@
import React from 'react';
import { TopicMessageTimestampTypeEnum } from 'generated-sources';
import Message from 'components/Topics/Topic/Details/Messages/Message';
import { TopicMessage, TopicMessageTimestampTypeEnum } from 'generated-sources';
import Message, {
Props,
} from 'components/Topics/Topic/Details/Messages/Message';
import { screen } from '@testing-library/react';
import { render } from 'lib/testHelpers';
import dayjs from 'dayjs';
import userEvent from '@testing-library/user-event';
const messageContentText = 'messageContentText';
jest.mock(
'components/Topics/Topic/Details/Messages/MessageContent/MessageContent',
() => () =>
(
<tr>
<td>{messageContentText}</td>
</tr>
)
);
describe('Message component', () => {
it('shows the data in the table row', async () => {
render(
<table>
<tbody>
<Message
message={{
timestamp: new Date(0),
const mockMessage: TopicMessage = {
timestamp: new Date(),
timestampType: TopicMessageTimestampTypeEnum.CREATE_TIME,
offset: 0,
key: 'test-key',
partition: 0,
partition: 6,
content: '{"data": "test"}',
headers: { header: 'test' },
}}
/>
};
const renderComponent = (
props: Partial<Props> = {
message: mockMessage,
}
) => {
return render(
<table>
<tbody>
<Message message={props.message || mockMessage} />
</tbody>
</table>
);
expect(screen.getByText('{"data": "test"}')).toBeInTheDocument();
expect(screen.getByText('test-key')).toBeInTheDocument();
};
it('shows the data in the table row', () => {
renderComponent();
expect(screen.getByText(mockMessage.content as string)).toBeInTheDocument();
expect(screen.getByText(mockMessage.key as string)).toBeInTheDocument();
expect(
screen.getByText(
dayjs(mockMessage.timestamp).format('MM.DD.YYYY HH:mm:ss')
)
).toBeInTheDocument();
expect(screen.getByText(mockMessage.offset.toString())).toBeInTheDocument();
expect(
screen.getByText(mockMessage.partition.toString())
).toBeInTheDocument();
});
it('check the useDataSaver functionality', () => {
const props = { message: { ...mockMessage } };
delete props.message.content;
renderComponent(props);
expect(
screen.queryByText(mockMessage.content as string)
).not.toBeInTheDocument();
});
it('should check the dropdown being visible during hover', () => {
renderComponent();
const text = 'Save as a file';
const trElement = screen.getByRole('row');
expect(screen.queryByText(text)).not.toBeInTheDocument();
userEvent.hover(trElement);
expect(screen.getByText(text)).toBeInTheDocument();
userEvent.unhover(trElement);
expect(screen.queryByText(text)).not.toBeInTheDocument();
});
it('should check open Message Content functionality', () => {
renderComponent();
const messageToggleIcon = screen.getByRole('button', { hidden: true });
expect(screen.queryByText(messageContentText)).not.toBeInTheDocument();
userEvent.click(messageToggleIcon);
expect(screen.getByText(messageContentText)).toBeInTheDocument();
});
});

View file

@ -1,5 +1,5 @@
import React from 'react';
import { screen } from '@testing-library/react';
import { screen, waitFor } from '@testing-library/react';
import { render, EventSourceMock } from 'lib/testHelpers';
import Messages, {
SeekDirectionOptions,
@ -40,7 +40,7 @@ describe('Messages', () => {
);
});
it('should check the SeekDirection select changes', () => {
it('should check the SeekDirection select changes with live option', async () => {
const seekDirectionSelect = screen.getAllByRole('listbox')[1];
const seekDirectionOption = screen.getAllByRole('option')[1];
@ -50,17 +50,28 @@ describe('Messages', () => {
const labelValue1 = SeekDirectionOptions[1].label;
userEvent.click(seekDirectionSelect);
userEvent.selectOptions(seekDirectionSelect, [
SeekDirectionOptions[1].label,
]);
userEvent.selectOptions(seekDirectionSelect, [labelValue1]);
expect(seekDirectionOption).toHaveTextContent(labelValue1);
const labelValue0 = SeekDirectionOptions[0].label;
userEvent.click(seekDirectionSelect);
userEvent.selectOptions(seekDirectionSelect, [
SeekDirectionOptions[0].label,
]);
userEvent.selectOptions(seekDirectionSelect, [labelValue0]);
expect(seekDirectionOption).toHaveTextContent(labelValue0);
const liveOptionConf = SeekDirectionOptions[2];
const labelValue2 = liveOptionConf.label;
userEvent.click(seekDirectionSelect);
const liveModeLi = screen.getByRole(
(role, element) =>
role === 'option' &&
element?.getAttribute('value') === liveOptionConf.value
);
userEvent.selectOptions(seekDirectionSelect, [liveModeLi]);
expect(seekDirectionOption).toHaveTextContent(labelValue2);
await waitFor(() => {
expect(screen.getByRole('contentLoader')).toBeInTheDocument();
});
});
});

View file

@ -3,17 +3,23 @@ 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 { createMemoryHistory } from 'history';
import { SeekDirection, SeekType } from 'generated-sources';
import { createMemoryHistory, MemoryHistory } from 'history';
import { SeekDirection, SeekType, TopicMessage } from 'generated-sources';
import userEvent from '@testing-library/user-event';
import TopicMessagesContext, {
ContextProps,
} from 'components/contexts/TopicMessagesContext';
import {
topicMessagePayload,
topicMessagesMetaPayload,
} from 'redux/reducers/topicMessages/__test__/fixtures';
const mockTopicsMessages: TopicMessage[] = [{ ...topicMessagePayload }];
describe('MessagesTable', () => {
const searchParams = new URLSearchParams(
`?filterQueryType=STRING_CONTAINS&attempt=0&limit=100&seekDirection=${SeekDirection.FORWARD}&seekType=${SeekType.OFFSET}&seekTo=0::9`
);
const seekToResult = '&seekTo=0::9';
const searchParamsValue = `?filterQueryType=STRING_CONTAINS&attempt=0&limit=100&seekDirection=${SeekDirection.FORWARD}&seekType=${SeekType.OFFSET}${seekToResult}`;
const searchParams = new URLSearchParams(searchParamsValue);
const contextValue: ContextProps = {
isLive: false,
seekDirection: SeekDirection.FORWARD,
@ -23,18 +29,32 @@ describe('MessagesTable', () => {
const setUpComponent = (
params: URLSearchParams = searchParams,
ctx: ContextProps = contextValue
ctx: ContextProps = contextValue,
messages: TopicMessage[] = [],
customHistory?: MemoryHistory
) => {
const history = createMemoryHistory();
history.push({
search: params.toString(),
const history =
customHistory ||
createMemoryHistory({
initialEntries: [params.toString()],
});
return render(
<Router history={history}>
<TopicMessagesContext.Provider value={ctx}>
<MessagesTable />
</TopicMessagesContext.Provider>
</Router>
</Router>,
{
preloadedState: {
topicMessages: {
messages,
meta: {
...topicMessagesMetaPayload,
},
isFetching: false,
},
},
}
);
};
@ -69,5 +89,55 @@ describe('MessagesTable', () => {
setUpComponent(searchParams, { ...contextValue, isLive: true });
expect(screen.getByRole('progressbar')).toBeInTheDocument();
});
it('should check the seekTo parameter in the url if no seekTo is found should noy change the history', () => {
const customSearchParam = new URLSearchParams(searchParamsValue);
const mockedHistory = createMemoryHistory({
initialEntries: [customSearchParam.toString()],
});
jest.spyOn(mockedHistory, 'push');
setUpComponent(customSearchParam, contextValue, [], mockedHistory);
userEvent.click(screen.getByRole('button', { name: 'Next' }));
expect(mockedHistory.push).toHaveBeenCalledWith({
search: searchParamsValue.replace(seekToResult, '&seekTo=0%3A%3A1'),
});
});
it('should check the seekTo parameter in the url if no seekTo is found should change the history', () => {
const customSearchParam = new URLSearchParams(
searchParamsValue.replace(seekToResult, '')
);
const mockedHistory = createMemoryHistory({
initialEntries: [customSearchParam.toString()],
});
jest.spyOn(mockedHistory, 'push');
setUpComponent(
customSearchParam,
{ ...contextValue, searchParams: customSearchParam },
[],
mockedHistory
);
userEvent.click(screen.getByRole('button', { name: 'Next' }));
expect(mockedHistory.push).not.toHaveBeenCalled();
});
});
describe('should render Messages table with data', () => {
beforeEach(() => {
setUpComponent(searchParams, { ...contextValue }, mockTopicsMessages);
});
it('should check the rendering of the messages', () => {
expect(screen.queryByText(/No messages found/i)).not.toBeInTheDocument();
expect(
screen.getByText(mockTopicsMessages[0].content as string)
).toBeInTheDocument();
});
});
});

View file

@ -49,8 +49,8 @@ describe('Overview', () => {
underReplicatedPartitions?: number,
inSyncReplicas?: number,
replicas?: number
) =>
render(
) => {
return render(
<ClusterContext.Provider value={contextValues}>
<Overview
underReplicatedPartitions={underReplicatedPartitions}
@ -60,6 +60,11 @@ describe('Overview', () => {
/>
</ClusterContext.Provider>
);
};
afterEach(() => {
mockClearTopicMessages.mockClear();
});
describe('when it has internal flag', () => {
it('does not render the Action button a Topic', () => {
@ -116,4 +121,49 @@ describe('Overview', () => {
expect(mockClearTopicMessages).toHaveBeenCalledTimes(1);
});
describe('when the table partition dropdown appearance', () => {
it('should check if the dropdown is not present when it is readOnly', () => {
setupComponent(
{
...defaultProps,
partitions: mockPartitions,
internal: true,
cleanUpPolicy: CleanUpPolicy.DELETE,
},
{ ...defaultContextValues, isReadOnly: true }
);
expect(screen.queryByText('Clear Messages')).not.toBeInTheDocument();
});
it('should check if the dropdown is not present when it is internal', () => {
setupComponent({
...defaultProps,
partitions: mockPartitions,
internal: true,
cleanUpPolicy: CleanUpPolicy.DELETE,
});
expect(screen.queryByText('Clear Messages')).not.toBeInTheDocument();
});
it('should check if the dropdown is not present when cleanUpPolicy is not DELETE', () => {
setupComponent({
...defaultProps,
partitions: mockPartitions,
internal: false,
cleanUpPolicy: CleanUpPolicy.COMPACT,
});
expect(screen.queryByText('Clear Messages')).not.toBeInTheDocument();
});
it('should check if the dropdown action to be in visible', () => {
setupComponent({
...defaultProps,
partitions: mockPartitions,
internal: false,
cleanUpPolicy: CleanUpPolicy.DELETE,
});
expect(screen.getByText('Clear Messages')).toBeInTheDocument();
});
});
});

View file

@ -0,0 +1,41 @@
import React from 'react';
import { screen } from '@testing-library/react';
import { render } from 'lib/testHelpers';
import { ConfigItemCell } from 'components/Topics/Topic/Details/Settings/Settings.styled';
describe('Settings styled Components', () => {
describe('ConfigItemCell Component', () => {
const renderComponent = (
props: Partial<{ $hasCustomValue: boolean }> = {}
) => {
return render(
<table>
<tbody>
<tr>
<ConfigItemCell
$hasCustomValue={
'$hasCustomValue' in props ? !!props.$hasCustomValue : false
}
/>
</tr>
</tbody>
</table>
);
};
it('should check the true rendering ConfigItemList', () => {
renderComponent({ $hasCustomValue: true });
expect(screen.getByRole('cell')).toHaveStyleRule(
'font-weight',
'500 !important'
);
});
it('should check the true rendering ConfigItemList', () => {
renderComponent();
expect(screen.getByRole('cell')).toHaveStyleRule(
'font-weight',
'400 !important'
);
});
});
});

View file

@ -0,0 +1,287 @@
import React from 'react';
import DangerZone, {
Props,
} from 'components/Topics/Topic/Edit/DangerZone/DangerZone';
import { screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { render } from 'lib/testHelpers';
import {
topicName,
clusterName,
} from 'components/Topics/Topic/Edit/__test__/fixtures';
const defaultPartitions = 3;
const defaultReplicationFactor = 3;
const renderComponent = (props?: Partial<Props>) => {
return render(
<DangerZone
clusterName={clusterName}
topicName={topicName}
defaultPartitions={defaultPartitions}
defaultReplicationFactor={defaultReplicationFactor}
partitionsCountIncreased={false}
replicationFactorUpdated={false}
updateTopicPartitionsCount={jest.fn()}
updateTopicReplicationFactor={jest.fn()}
{...props}
/>
);
};
const clickOnDialogSubmitButton = () => {
userEvent.click(
within(screen.getByRole('dialog')).getByRole('button', {
name: 'Submit',
})
);
};
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', () => {
renderComponent();
const numberOfPartitionsEditForm = screen.getByRole('form', {
name: 'Edit number of partitions',
});
expect(numberOfPartitionsEditForm).toBeInTheDocument();
expect(
within(numberOfPartitionsEditForm).getByRole('spinbutton', {
name: 'Number of partitions *',
})
).toBeInTheDocument();
expect(
within(numberOfPartitionsEditForm).getByRole('button', { name: 'Submit' })
).toBeInTheDocument();
const replicationFactorEditForm = screen.getByRole('form', {
name: 'Edit replication factor',
});
expect(replicationFactorEditForm).toBeInTheDocument();
expect(
within(replicationFactorEditForm).getByRole('spinbutton', {
name: 'Replication Factor *',
})
).toBeInTheDocument();
expect(
within(replicationFactorEditForm).getByRole('button', { name: 'Submit' })
).toBeInTheDocument();
});
it('calls updateTopicPartitionsCount', async () => {
const mockUpdateTopicPartitionsCount = jest.fn();
renderComponent({
updateTopicPartitionsCount: mockUpdateTopicPartitionsCount,
});
const numberOfPartitionsEditForm = screen.getByRole('form', {
name: 'Edit number of partitions',
});
userEvent.type(
within(numberOfPartitionsEditForm).getByRole('spinbutton'),
'4'
);
userEvent.click(within(numberOfPartitionsEditForm).getByRole('button'));
await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument());
await waitFor(() => clickOnDialogSubmitButton());
expect(mockUpdateTopicPartitionsCount).toHaveBeenCalledTimes(1);
});
it('calls updateTopicReplicationFactor', async () => {
const mockUpdateTopicReplicationFactor = jest.fn();
renderComponent({
updateTopicReplicationFactor: mockUpdateTopicReplicationFactor,
});
const replicationFactorEditForm = screen.getByRole('form', {
name: 'Edit replication factor',
});
expect(
within(replicationFactorEditForm).getByRole('spinbutton', {
name: 'Replication Factor *',
})
).toBeInTheDocument();
expect(
within(replicationFactorEditForm).getByRole('button', { name: 'Submit' })
).toBeInTheDocument();
userEvent.type(
within(replicationFactorEditForm).getByRole('spinbutton'),
'4'
);
userEvent.click(within(replicationFactorEditForm).getByRole('button'));
await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument());
await waitFor(() => clickOnDialogSubmitButton());
await waitFor(() => {
expect(mockUpdateTopicReplicationFactor).toHaveBeenCalledTimes(1);
});
});
it('should view the validation error when partition value is lower than the default passed or empty', async () => {
renderComponent();
const partitionInput = screen.getByPlaceholderText('Number of partitions');
const partitionInputSubmitBtn = screen.getAllByText(/submit/i)[0];
const value = (defaultPartitions - 4).toString();
expect(partitionInputSubmitBtn).toBeDisabled();
await waitFor(() => {
userEvent.clear(partitionInput);
userEvent.type(partitionInput, value);
});
expect(partitionInput).toHaveValue(+value);
expect(partitionInputSubmitBtn).toBeEnabled();
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();
});
it('should view the validation error when Replication Facto value is lower than the default passed or empty', async () => {
renderComponent();
const replicatorFactorInput =
screen.getByPlaceholderText('Replication Factor');
const replicatorFactorInputSubmitBtn = screen.getAllByText(/submit/i)[1];
await waitFor(() => {
userEvent.clear(replicatorFactorInput);
});
expect(replicatorFactorInputSubmitBtn).toBeEnabled();
await waitFor(() => {
userEvent.click(replicatorFactorInputSubmitBtn);
});
expect(screen.getByText(/are required/i)).toBeInTheDocument();
await waitFor(() => {
userEvent.type(replicatorFactorInput, '1');
});
expect(screen.queryByText(/are required/i)).not.toBeInTheDocument();
});
it('should close any popup if the partitionsCount is Increased ', () => {
renderComponent({ partitionsCountIncreased: true });
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
});
it('should close any popup if the replicationFactor is Updated', () => {
renderComponent({ replicationFactorUpdated: true });
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
});
it('should already opened Confirmation popup if partitionsCount is Increased', async () => {
const { rerender } = renderComponent();
const partitionInput = screen.getByPlaceholderText('Number of partitions');
const partitionInputSubmitBtn = screen.getAllByText(/submit/i)[0];
await waitFor(() => {
userEvent.type(partitionInput, '5');
});
userEvent.click(partitionInputSubmitBtn);
await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument());
rerender(
<DangerZone
clusterName={clusterName}
topicName={topicName}
defaultPartitions={defaultPartitions}
defaultReplicationFactor={defaultReplicationFactor}
partitionsCountIncreased
replicationFactorUpdated={false}
updateTopicPartitionsCount={jest.fn()}
updateTopicReplicationFactor={jest.fn()}
/>
);
await waitFor(() =>
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
);
});
it('should already opened Confirmation popup if replicationFactor is Increased', async () => {
const { rerender } = renderComponent();
const replicatorFactorInput =
screen.getByPlaceholderText('Replication Factor');
const replicatorFactorInputSubmitBtn = screen.getAllByText(/submit/i)[1];
await waitFor(() => {
userEvent.type(replicatorFactorInput, '5');
});
userEvent.click(replicatorFactorInputSubmitBtn);
await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument());
rerender(
<DangerZone
clusterName={clusterName}
topicName={topicName}
defaultPartitions={defaultPartitions}
defaultReplicationFactor={defaultReplicationFactor}
partitionsCountIncreased={false}
replicationFactorUpdated
updateTopicPartitionsCount={jest.fn()}
updateTopicReplicationFactor={jest.fn()}
/>
);
await waitFor(() =>
expect(screen.queryByRole('dialog')).not.toBeInTheDocument()
);
});
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(() => {
userEvent.type(partitionInput, '5');
});
await waitFor(() => {
userEvent.click(partitionInputSubmitBtn);
});
await checkDialogThenPressCancel();
});
it('should close the replicator dialog if he cancel button is pressed', async () => {
renderComponent();
const replicatorFactorInput =
screen.getByPlaceholderText('Replication Factor');
const replicatorFactorInputSubmitBtn = screen.getAllByText(/submit/i)[1];
await waitFor(() => {
userEvent.type(replicatorFactorInput, '5');
});
await waitFor(() => {
userEvent.click(replicatorFactorInputSubmitBtn);
});
await checkDialogThenPressCancel();
});
});

View file

@ -46,7 +46,7 @@ const EditWrapperStyled = styled.div`
}
`;
const DEFAULTS = {
export const DEFAULTS = {
partitions: 1,
replicationFactor: 1,
minInSyncReplicas: 1,

View file

@ -1,116 +0,0 @@
import React from 'react';
import DangerZone, {
Props,
} from 'components/Topics/Topic/Edit/DangerZone/DangerZone';
import { screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { render } from 'lib/testHelpers';
import { topicName, clusterName } from './fixtures';
const renderComponent = (props?: Partial<Props>) =>
render(
<DangerZone
clusterName={clusterName}
topicName={topicName}
defaultPartitions={3}
defaultReplicationFactor={3}
partitionsCountIncreased={false}
replicationFactorUpdated={false}
updateTopicPartitionsCount={jest.fn()}
updateTopicReplicationFactor={jest.fn()}
{...props}
/>
);
const clickOnDialogSubmitButton = () => {
userEvent.click(
within(screen.getByRole('dialog')).getByRole('button', {
name: 'Submit',
})
);
};
describe('DangerZone', () => {
it('renders', () => {
renderComponent();
const numberOfPartitionsEditForm = screen.getByRole('form', {
name: 'Edit number of partitions',
});
expect(numberOfPartitionsEditForm).toBeInTheDocument();
expect(
within(numberOfPartitionsEditForm).getByRole('spinbutton', {
name: 'Number of partitions *',
})
).toBeInTheDocument();
expect(
within(numberOfPartitionsEditForm).getByRole('button', { name: 'Submit' })
).toBeInTheDocument();
const replicationFactorEditForm = screen.getByRole('form', {
name: 'Edit replication factor',
});
expect(replicationFactorEditForm).toBeInTheDocument();
expect(
within(replicationFactorEditForm).getByRole('spinbutton', {
name: 'Replication Factor *',
})
).toBeInTheDocument();
expect(
within(replicationFactorEditForm).getByRole('button', { name: 'Submit' })
).toBeInTheDocument();
});
it('calls updateTopicPartitionsCount', async () => {
const mockUpdateTopicPartitionsCount = jest.fn();
renderComponent({
updateTopicPartitionsCount: mockUpdateTopicPartitionsCount,
});
const numberOfPartitionsEditForm = screen.getByRole('form', {
name: 'Edit number of partitions',
});
userEvent.type(
within(numberOfPartitionsEditForm).getByRole('spinbutton'),
'4'
);
userEvent.click(within(numberOfPartitionsEditForm).getByRole('button'));
await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument());
await waitFor(() => clickOnDialogSubmitButton());
expect(mockUpdateTopicPartitionsCount).toHaveBeenCalledTimes(1);
});
it('calls updateTopicReplicationFactor', async () => {
const mockUpdateTopicReplicationFactor = jest.fn();
renderComponent({
updateTopicReplicationFactor: mockUpdateTopicReplicationFactor,
});
const replicationFactorEditForm = screen.getByRole('form', {
name: 'Edit replication factor',
});
expect(
within(replicationFactorEditForm).getByRole('spinbutton', {
name: 'Replication Factor *',
})
).toBeInTheDocument();
expect(
within(replicationFactorEditForm).getByRole('button', { name: 'Submit' })
).toBeInTheDocument();
userEvent.type(
within(replicationFactorEditForm).getByRole('spinbutton'),
'4'
);
userEvent.click(within(replicationFactorEditForm).getByRole('button'));
await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument());
await waitFor(() => clickOnDialogSubmitButton());
await waitFor(() => {
expect(mockUpdateTopicReplicationFactor).toHaveBeenCalledTimes(1);
});
});
});

View file

@ -1,27 +1,39 @@
import React from 'react';
import Edit, { Props } from 'components/Topics/Topic/Edit/Edit';
import { screen } from '@testing-library/react';
import Edit, { DEFAULTS, Props } from 'components/Topics/Topic/Edit/Edit';
import { screen, waitFor } from '@testing-library/react';
import { render } from 'lib/testHelpers';
import userEvent from '@testing-library/user-event';
import { Router } from 'react-router-dom';
import { createMemoryHistory } from 'history';
import { clusterTopicPath, clusterTopicsPath } from 'lib/paths';
import { topicName, clusterName, topicWithInfo } from './fixtures';
const renderComponent = (props?: Partial<Props>) =>
const historyMock = createMemoryHistory();
const renderComponent = (props: Partial<Props> = {}, history = historyMock) =>
render(
<Router history={history}>
<Edit
clusterName={clusterName}
topicName={topicName}
topic={topicWithInfo}
isFetched
isTopicUpdated={false}
clusterName={props.clusterName || clusterName}
topicName={props.topicName || topicName}
topic={'topic' in props ? props.topic : topicWithInfo}
isFetched={'isFetched' in props ? !!props.isFetched : true}
isTopicUpdated={
'isTopicUpdated' in props ? !!props.isTopicUpdated : false
}
fetchTopicConfig={jest.fn()}
updateTopic={jest.fn()}
updateTopicPartitionsCount={jest.fn()}
updateTopic={props.updateTopic || jest.fn()}
updateTopicPartitionsCount={
props.updateTopicPartitionsCount || jest.fn()
}
{...props}
/>
</Router>
);
describe('DangerZone', () => {
it('renders', () => {
describe('Edit Component', () => {
it('renders the Edit Component', () => {
renderComponent();
expect(
@ -31,4 +43,112 @@ describe('DangerZone', () => {
screen.getByRole('heading', { name: `Danger Zone` })
).toBeInTheDocument();
});
it('should check Edit component renders null is not rendered when topic is not passed', () => {
renderComponent({ topic: undefined });
expect(
screen.queryByRole('heading', { name: `Edit ${topicName}` })
).not.toBeInTheDocument();
expect(
screen.queryByRole('heading', { name: `Danger Zone` })
).not.toBeInTheDocument();
});
it('should check Edit component renders null is not isFetched is false', () => {
renderComponent({ isFetched: false });
expect(
screen.queryByRole('heading', { name: `Edit ${topicName}` })
).not.toBeInTheDocument();
expect(
screen.queryByRole('heading', { name: `Danger Zone` })
).not.toBeInTheDocument();
});
it('should check Edit component renders null is not topic config is not passed is false', () => {
const modifiedTopic = { ...topicWithInfo };
modifiedTopic.config = undefined;
renderComponent({ topic: modifiedTopic });
expect(
screen.queryByRole('heading', { name: `Edit ${topicName}` })
).not.toBeInTheDocument();
expect(
screen.queryByRole('heading', { name: `Danger Zone` })
).not.toBeInTheDocument();
});
describe('Edit Component with its topic default and modified values', () => {
it('should check the default partitions value in the DangerZone', () => {
renderComponent({
topic: { ...topicWithInfo, partitionCount: undefined },
});
expect(screen.getByPlaceholderText('Number of partitions')).toHaveValue(
DEFAULTS.partitions
);
});
it('should check the default partitions value in the DangerZone', () => {
renderComponent({
topic: { ...topicWithInfo, replicationFactor: undefined },
});
expect(screen.getByPlaceholderText('Replication Factor')).toHaveValue(
DEFAULTS.replicationFactor
);
});
});
describe('Submit Case of the Edit Component', () => {
it('should check the submit functionality when topic updated is false', async () => {
const updateTopicMock = jest.fn();
const mocked = createMemoryHistory({
initialEntries: [`${clusterTopicsPath(clusterName)}/${topicName}/edit`],
});
jest.spyOn(mocked, 'push');
renderComponent({ updateTopic: updateTopicMock }, mocked);
const btn = screen.getAllByText(/submit/i)[0];
expect(btn).toBeEnabled();
await waitFor(() => {
userEvent.type(
screen.getByPlaceholderText('Min In Sync Replicas'),
'1'
);
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();
const mocked = createMemoryHistory({
initialEntries: [`${clusterTopicsPath(clusterName)}/${topicName}/edit`],
});
jest.spyOn(mocked, 'push');
renderComponent(
{ updateTopic: updateTopicMock, isTopicUpdated: true },
mocked
);
const btn = screen.getAllByText(/submit/i)[0];
await waitFor(() => {
userEvent.type(
screen.getByPlaceholderText('Min In Sync Replicas'),
'1'
);
userEvent.click(btn);
});
expect(updateTopicMock).toHaveBeenCalledTimes(1);
await waitFor(() => {
expect(mocked.push).toHaveBeenCalled();
expect(mocked.location.pathname).toBe(
clusterTopicPath(clusterName, topicName)
);
});
});
});
});

View file

@ -18,9 +18,14 @@ import { store } from 'redux/store';
import { fetchTopicDetailsAction } from 'redux/actions';
import { initialState } from 'redux/reducers/topics/reducer';
import { externalTopicPayload } from 'redux/reducers/topics/__test__/fixtures';
import validateMessage from 'components/Topics/Topic/SendMessage/validateMessage';
import Alerts from 'components/Alerts/Alerts';
import * as S from 'components/App.styled';
import { testSchema } from './fixtures';
import Mock = jest.Mock;
jest.mock('json-schema-faker', () => ({
generate: () => ({
f1: -93251214,
@ -30,6 +35,10 @@ jest.mock('json-schema-faker', () => ({
option: jest.fn(),
}));
jest.mock('components/Topics/Topic/SendMessage/validateMessage', () =>
jest.fn()
);
const clusterName = 'testCluster';
const topicName = externalTopicPayload.name;
const history = createMemoryHistory();
@ -37,15 +46,30 @@ const history = createMemoryHistory();
const renderComponent = () => {
history.push(clusterTopicSendMessagePath(clusterName, topicName));
render(
<>
<Router history={history}>
<Route path={clusterTopicSendMessagePath(':clusterName', ':topicName')}>
<SendMessage />
</Route>
</Router>,
</Router>
<S.AlertsContainer role="toolbar">
<Alerts />
</S.AlertsContainer>
</>,
{ store }
);
};
const renderAndSubmitData = async (error: string[] = []) => {
renderComponent();
await waitForElementToBeRemoved(() => screen.getByRole('progressbar'));
userEvent.selectOptions(screen.getByLabelText('Partition'), '0');
const sendBtn = await screen.findByText('Send');
(validateMessage as Mock).mockImplementation(() => error);
userEvent.click(sendBtn);
};
describe('SendMessage', () => {
beforeAll(() => {
store.dispatch(
@ -71,6 +95,8 @@ describe('SendMessage', () => {
});
describe('when schema is fetched', () => {
const url = `/api/clusters/${clusterName}/topics/${topicName}/messages`;
beforeEach(() => {
fetchMock.getOnce(
`/api/clusters/${clusterName}/topics/${topicName}/messages/schema`,
@ -79,20 +105,41 @@ describe('SendMessage', () => {
});
it('calls sendTopicMessage on submit', async () => {
const sendTopicMessageMock = fetchMock.postOnce(
`/api/clusters/${clusterName}/topics/${topicName}/messages`,
200
);
renderComponent();
await waitForElementToBeRemoved(() => screen.getByRole('progressbar'));
const sendTopicMessageMock = fetchMock.postOnce(url, 200);
await renderAndSubmitData();
userEvent.selectOptions(screen.getByLabelText('Partition'), '0');
await screen.findByText('Send');
userEvent.click(screen.getByText('Send'));
await waitFor(() => expect(sendTopicMessageMock.called()).toBeTruthy());
await waitFor(() =>
expect(sendTopicMessageMock.called(url)).toBeTruthy()
);
expect(history.location.pathname).toEqual(
clusterTopicMessagesPath(clusterName, topicName)
);
});
it('should make the sendTopicMessage but most find an error within it', async () => {
const sendTopicMessageMock = fetchMock.postOnce(url, {
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)
);
});
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(history.location.pathname).not.toEqual(
clusterTopicMessagesPath(clusterName, topicName)
);
});
});
});

View file

@ -1,12 +1,62 @@
import validateMessage from 'components/Topics/Topic/SendMessage/validateMessage';
import cloneDeep from 'lodash/cloneDeep';
import { testSchema } from './fixtures';
describe('validateMessage', () => {
it('returns no errors on correct input data', () => {
const defaultValidKey = `{"f1": 32, "f2": "multi-state", "schema": "Bedfordshire violet SAS"}`;
const defaultValidContent = `{"f1": 21128, "f2": "Health Berkshire", "schema": "Dynamic"}`;
it('should return empty error data if value is empty', () => {
const key = ``;
const content = ``;
expect(validateMessage(key, content, testSchema)).toEqual([]);
});
it('should return empty error data if schema is empty', () => {
const key = `{"f1": 32, "f2": "multi-state", "schema": "Bedfordshire violet SAS"}`;
const content = `{"f1": 21128, "f2": "Health Berkshire", "schema": "Dynamic"}`;
expect(validateMessage(key, content, testSchema)).toEqual([]);
const schema = cloneDeep(testSchema);
schema.key.schema = '';
schema.value.schema = '';
expect(validateMessage(key, content, schema)).toEqual([]);
});
it('should return parsing error data if schema is not parsed with type of key', () => {
const schema = cloneDeep(testSchema);
schema.key.schema = '{invalid';
expect(
validateMessage(defaultValidKey, defaultValidContent, schema)
).toEqual([`Error in parsing the "key" field schema`]);
});
it('should return parsing error data if schema is not parsed with type of value', () => {
const schema = cloneDeep(testSchema);
schema.value.schema = '{invalid';
expect(
validateMessage(defaultValidKey, defaultValidContent, schema)
).toEqual([`Error in parsing the "content" field schema`]);
});
it('should return empty error data if schema type is string', () => {
const schema = cloneDeep(testSchema);
schema.key.schema = `{"type": "string"}`;
schema.value.schema = `{"type": "string"}`;
expect(
validateMessage(defaultValidKey, defaultValidContent, schema)
).toEqual([]);
});
it('should return error data if compile Ajv data throws an error', () => {
expect(
validateMessage(defaultValidKey, defaultValidContent, testSchema)
).toEqual([]);
});
it('returns no errors on correct input data', () => {
expect(
validateMessage(defaultValidContent, defaultValidContent, testSchema)
).toEqual([]);
});
it('returns errors on invalid input data', () => {

View file

@ -9,23 +9,31 @@ import {
clusterTopicSendMessagePath,
} from 'lib/paths';
const topicText = {
edit: 'Edit Container',
send: 'Send Message',
detail: 'Details Container',
loading: 'Loading',
};
jest.mock('components/Topics/Topic/Edit/EditContainer', () => () => (
<div>Edit Container</div>
<div>{topicText.edit}</div>
));
jest.mock('components/Topics/Topic/SendMessage/SendMessage', () => () => (
<div>Send Message</div>
<div>{topicText.send}</div>
));
jest.mock('components/Topics/Topic/Details/DetailsContainer', () => () => (
<div>Details Container</div>
<div>{topicText.detail}</div>
));
jest.mock('components/common/PageLoader/PageLoader', () => () => (
<div>Loading</div>
<div>{topicText.loading}</div>
));
const resetTopicMessages = jest.fn();
const fetchTopicDetailsMock = jest.fn();
describe('Topic Component', () => {
const resetTopicMessages = jest.fn();
const fetchTopicDetailsMock = jest.fn();
const renderComponent = (pathname: string, topicFetching: boolean) =>
const renderComponent = (pathname: string, topicFetching: boolean) =>
render(
<Route path={clusterTopicPath(':clusterName', ':topicName')}>
<Topic
@ -37,36 +45,42 @@ const renderComponent = (pathname: string, topicFetching: boolean) =>
{ pathname }
);
it('renders Edit page', () => {
afterEach(() => {
resetTopicMessages.mockClear();
fetchTopicDetailsMock.mockClear();
});
it('renders Edit page', () => {
renderComponent(clusterTopicEditPath('local', 'myTopicName'), false);
expect(screen.getByText('Edit Container')).toBeInTheDocument();
});
expect(screen.getByText(topicText.edit)).toBeInTheDocument();
});
it('renders Send Message page', () => {
it('renders Send Message page', () => {
renderComponent(clusterTopicSendMessagePath('local', 'myTopicName'), false);
expect(screen.getByText('Send Message')).toBeInTheDocument();
});
expect(screen.getByText(topicText.send)).toBeInTheDocument();
});
it('renders Details Container page', () => {
it('renders Details Container page', () => {
renderComponent(clusterTopicPath('local', 'myTopicName'), false);
expect(screen.getByText('Details Container')).toBeInTheDocument();
});
expect(screen.getByText(topicText.detail)).toBeInTheDocument();
});
it('renders Page loader', () => {
it('renders Page loader', () => {
renderComponent(clusterTopicPath('local', 'myTopicName'), true);
expect(screen.getByText('Loading')).toBeInTheDocument();
});
expect(screen.getByText(topicText.loading)).toBeInTheDocument();
});
it('fetches topicDetails', () => {
it('fetches topicDetails', () => {
renderComponent(clusterTopicPath('local', 'myTopicName'), false);
expect(fetchTopicDetailsMock).toHaveBeenCalledTimes(1);
});
});
it('resets topic messages after unmount', () => {
it('resets topic messages after unmount', () => {
const component = renderComponent(
clusterTopicPath('local', 'myTopicName'),
false
);
component.unmount();
expect(resetTopicMessages).toHaveBeenCalledTimes(1);
});
});

View file

@ -0,0 +1,61 @@
import React from 'react';
import { render } from 'lib/testHelpers';
import Topics from 'components/Topics/Topics';
import { Router } from 'react-router-dom';
import { createMemoryHistory } from 'history';
import { screen } from '@testing-library/react';
import {
clusterTopicCopyPath,
clusterTopicNewPath,
clusterTopicPath,
clusterTopicsPath,
} from 'lib/paths';
const listContainer = 'listContainer';
const topicContainer = 'topicContainer';
const newCopyContainer = 'newCopyContainer';
jest.mock('components/Topics/List/ListContainer', () => () => (
<div>{listContainer}</div>
));
jest.mock('components/Topics/Topic/TopicContainer', () => () => (
<div>{topicContainer}</div>
));
jest.mock('components/Topics/New/New', () => () => (
<div>{newCopyContainer}</div>
));
describe('Topics Component', () => {
const clusterName = 'clusterName';
const topicName = 'topicName';
const setUpComponent = (path: string) => {
const history = createMemoryHistory({
initialEntries: [path],
});
return render(
<Router history={history}>
<Topics />
</Router>
);
};
it('should check if the page is Topics List rendered', () => {
setUpComponent(clusterTopicsPath(clusterName));
expect(screen.getByText(listContainer)).toBeInTheDocument();
});
it('should check if the page is New Topic rendered', () => {
setUpComponent(clusterTopicNewPath(clusterName));
expect(screen.getByText(newCopyContainer)).toBeInTheDocument();
});
it('should check if the page is Copy Topic rendered', () => {
setUpComponent(clusterTopicCopyPath(clusterName));
expect(screen.getByText(newCopyContainer)).toBeInTheDocument();
});
it('should check if the page is Topic page rendered', () => {
setUpComponent(clusterTopicPath(clusterName, topicName));
expect(screen.getByText(topicContainer)).toBeInTheDocument();
});
});

View file

@ -12,8 +12,6 @@ const isDisabled = false;
const index = 0;
const existingFields: string[] = [];
const field = { name: 'name', value: 'value', id: 'id' };
const remove = jest.fn();
const setExistingFields = jest.fn();
const SPACE_KEY = ' ';
@ -23,6 +21,9 @@ const selectOption = async (listbox: HTMLElement, option: string) => {
};
describe('CustomParamsField', () => {
const remove = jest.fn();
const setExistingFields = jest.fn();
const setupComponent = (props: Props) => {
const Wrapper: React.FC = ({ children }) => {
const methods = useForm();
@ -36,7 +37,12 @@ describe('CustomParamsField', () => {
);
};
it('renders with props', () => {
afterEach(() => {
remove.mockClear();
setExistingFields.mockClear();
});
it('renders the component with its view correctly', () => {
setupComponent({
field,
isDisabled,
@ -61,7 +67,7 @@ describe('CustomParamsField', () => {
setExistingFields,
});
userEvent.click(screen.getByRole('button'));
expect(remove.mock.calls.length).toBe(1);
expect(remove).toHaveBeenCalledTimes(1);
});
it('pressing space on button triggers remove', () => {
@ -75,7 +81,7 @@ describe('CustomParamsField', () => {
});
userEvent.type(screen.getByRole('button'), SPACE_KEY);
// userEvent.type triggers remove two times as at first it clicks on element and then presses space
expect(remove.mock.calls.length).toBe(2);
expect(remove).toHaveBeenCalledTimes(2);
});
it('can select option', async () => {
@ -123,7 +129,7 @@ describe('CustomParamsField', () => {
const listbox = screen.getByRole('listbox');
await selectOption(listbox, 'compression.type');
expect(setExistingFields.mock.calls.length).toBe(1);
expect(setExistingFields).toHaveBeenCalledTimes(1);
});
});
});

View file

@ -80,14 +80,14 @@ describe('CustomParams', () => {
});
describe('works with user inputs correctly', () => {
beforeEach(() => {
let button: HTMLButtonElement;
beforeEach(async () => {
renderComponent({ isSubmitting: false });
button = screen.getByRole('button');
await waitFor(() => userEvent.click(button));
});
it('button click creates custom param fieldset', async () => {
const button = screen.getByRole('button');
await waitFor(() => userEvent.click(button));
const listbox = screen.getByRole('listbox');
expect(listbox).toBeInTheDocument();
@ -96,8 +96,6 @@ describe('CustomParams', () => {
});
it('can select option', async () => {
const button = screen.getByRole('button');
await waitFor(() => userEvent.click(button));
const listbox = screen.getByRole('listbox');
await selectOption(listbox, 'compression.type');
@ -109,9 +107,6 @@ describe('CustomParams', () => {
});
it('when selected option changes disabled options update correctly', async () => {
const button = screen.getByRole('button');
await waitFor(() => userEvent.click(button));
const listbox = screen.getByRole('listbox');
await selectOption(listbox, 'compression.type');
@ -124,8 +119,6 @@ describe('CustomParams', () => {
});
it('multiple button clicks create multiple fieldsets', async () => {
const button = screen.getByRole('button');
await waitFor(() => userEvent.click(button));
await waitFor(() => userEvent.click(button));
await waitFor(() => userEvent.click(button));
@ -137,8 +130,6 @@ describe('CustomParams', () => {
});
it("can't select already selected option", async () => {
const button = screen.getByRole('button');
await waitFor(() => userEvent.click(button));
await waitFor(() => userEvent.click(button));
const listboxes = screen.getAllByRole('listbox');
@ -152,8 +143,6 @@ describe('CustomParams', () => {
});
it('when fieldset with selected custom property type is deleted disabled options update correctly', async () => {
const button = screen.getByRole('button');
await waitFor(() => userEvent.click(button));
await waitFor(() => userEvent.click(button));
await waitFor(() => userEvent.click(button));

View file

@ -0,0 +1,27 @@
import React from 'react';
import { render } from 'lib/testHelpers';
import * as S from 'components/Topics/shared/Form/TopicForm.styled';
import { screen } from '@testing-library/react';
import theme from 'theme/theme';
describe('TopicForm styled components', () => {
describe('Button', () => {
it('should check the button styling in isActive state', () => {
render(<S.Button isActive />);
const button = screen.getByRole('button');
expect(button).toHaveStyle({
border: `1px solid ${theme.button.border.active}`,
backgroundColor: theme.button.primary.backgroundColor.active,
});
});
it('should check the button styling in non Active state', () => {
render(<S.Button isActive={false} />);
const button = screen.getByRole('button');
expect(button).toHaveStyle({
border: `1px solid ${theme.button.primary.color}`,
backgroundColor: theme.button.primary.backgroundColor.normal,
});
});
});
});

View file

@ -1,3 +1,5 @@
import { Topic } from 'generated-sources';
export const internalTopicPayload = {
name: '__internal.topic',
internal: true,
@ -40,4 +42,7 @@ export const externalTopicPayload = {
],
};
export const topicsPayload = [internalTopicPayload, externalTopicPayload];
export const topicsPayload: Topic[] = [
internalTopicPayload,
externalTopicPayload,
];