瀏覽代碼

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
Mgrdich 3 年之前
父節點
當前提交
e597f14b8d
共有 25 個文件被更改,包括 1438 次插入677 次删除
  1. 0 168
      kafka-ui-react-app/src/components/Topics/List/ListItem.tsx
  2. 251 180
      kafka-ui-react-app/src/components/Topics/List/__tests__/List.spec.tsx
  3. 0 74
      kafka-ui-react-app/src/components/Topics/List/__tests__/ListItem.spec.tsx
  4. 149 0
      kafka-ui-react-app/src/components/Topics/List/__tests__/TopicsTableCells.spec.tsx
  5. 25 0
      kafka-ui-react-app/src/components/Topics/New/__test__/New.spec.tsx
  6. 3 1
      kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/Filters.styled.ts
  7. 27 0
      kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/__tests__/Filters.styled.spec.tsx
  8. 5 1
      kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Message.tsx
  9. 80 17
      kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/__test__/Message.spec.tsx
  10. 19 8
      kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/__test__/Messages.spec.tsx
  11. 81 11
      kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/__test__/MessagesTable.spec.tsx
  12. 52 2
      kafka-ui-react-app/src/components/Topics/Topic/Details/Overview/__test__/Overview.spec.tsx
  13. 41 0
      kafka-ui-react-app/src/components/Topics/Topic/Details/Settings/__test__/Settings.styled.spec.tsx
  14. 287 0
      kafka-ui-react-app/src/components/Topics/Topic/Edit/DangerZone/__test__/DangerZone.spec.tsx
  15. 1 1
      kafka-ui-react-app/src/components/Topics/Topic/Edit/Edit.tsx
  16. 0 116
      kafka-ui-react-app/src/components/Topics/Topic/Edit/__test__/DangerZone.spec.tsx
  17. 136 16
      kafka-ui-react-app/src/components/Topics/Topic/Edit/__test__/Edit.spec.tsx
  18. 61 14
      kafka-ui-react-app/src/components/Topics/Topic/SendMessage/__test__/SendMessage.spec.tsx
  19. 52 2
      kafka-ui-react-app/src/components/Topics/Topic/SendMessage/__test__/validateMessage.spec.ts
  20. 58 44
      kafka-ui-react-app/src/components/Topics/Topic/__tests__/Topic.spec.tsx
  21. 61 0
      kafka-ui-react-app/src/components/Topics/__tests__/Topics.spec.tsx
  22. 12 6
      kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/__test__/CustomParamField.spec.tsx
  23. 4 15
      kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/__test__/CustomParams.spec.tsx
  24. 27 0
      kafka-ui-react-app/src/components/Topics/shared/Form/__tests__/TopicForm.styled.tsx
  25. 6 1
      kafka-ui-react-app/src/redux/reducers/topics/__test__/fixtures.ts

+ 0 - 168
kafka-ui-react-app/src/components/Topics/List/ListItem.tsx

@@ -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;

+ 251 - 180
kafka-ui-react-app/src/components/Topics/List/__tests__/List.spec.tsx

@@ -1,56 +1,45 @@
 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={[]}
-        totalPages={1}
-        fetchTopicsList={jest.fn()}
-        deleteTopic={jest.fn()}
-        deleteTopics={jest.fn()}
-        clearTopicsMessages={jest.fn()}
-        clearTopicMessages={jest.fn()}
-        recreateTopic={jest.fn()}
-        search=""
-        orderBy={null}
-        sortOrder={SortOrder.ASC}
-        setTopicsSearch={jest.fn()}
-        setTopicsOrderBy={jest.fn()}
-        {...props}
-      />
-    </ThemeProvider>
+    <List
+      areTopicsFetching={false}
+      topics={[]}
+      totalPages={1}
+      fetchTopicsList={jest.fn()}
+      deleteTopic={jest.fn()}
+      deleteTopics={jest.fn()}
+      clearTopicsMessages={jest.fn()}
+      clearTopicMessages={jest.fn()}
+      recreateTopic={jest.fn()}
+      search=""
+      orderBy={null}
+      sortOrder={SortOrder.ASC}
+      setTopicsSearch={jest.fn()}
+      setTopicsOrderBy={jest.fn()}
+      {...props}
+    />
   );
 
   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);
-      expect(setTopicsSearch).toHaveBeenCalledWith(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');
 
-      expect(fetchTopicsList).toHaveBeenLastCalledWith({
-        search: '',
-        showInternal: !checked,
-        sortOrder: SortOrder.ASC,
+      userEvent.click(internalCheckBox);
+      const { value } = internalCheckBox;
+
+      await waitFor(() => {
+        expect(fetchTopicsList).toHaveBeenLastCalledWith({
+          search: '',
+          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,127 +137,158 @@ 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;
+      const searchInput = screen.getByPlaceholderText('Search by Topic Name');
+      userEvent.type(searchInput, 'nonEmptyString');
 
-      mockedHistory.push(`/?page=${cachedPage}&perPage=25`);
-
-      const input = component.find(Search);
-      input.props().handleSearch('nonEmptyString');
-
-      expect(mockedHistory.push).toHaveBeenCalledWith('/?page=1&perPage=25');
+      await waitFor(() => {
+        expect(mockedHistory.push).toHaveBeenCalledWith('/?page=1&perPage=25');
+      });
 
-      input.props().handleSearch('');
+      userEvent.clear(searchInput);
 
-      expect(mockedHistory.push).toHaveBeenCalledWith(
-        `/?page=${cachedPage}&perPage=25`
-      );
+      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(
-      <StaticRouter location={{ pathname }}>
-        <Route path="/ui/clusters/:clusterName">
-          <ClusterContext.Provider
-            value={{
-              isReadOnly: false,
-              hasKafkaConnectConfigured: true,
-              hasSchemaRegistryConfigured: true,
-              isTopicDeletionAllowed: true,
-            }}
-          >
-            {setupComponent({
-              topics: [
-                externalTopicPayload,
-                { ...externalTopicPayload, name: 'external.topic2' },
-              ],
-              deleteTopics: mockDeleteTopics,
-              clearTopicsMessages: mockClearTopicsMessages,
-            })}
-          </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);
+    beforeEach(() => {
+      render(
+        <StaticRouter location={{ pathname }}>
+          <Route path="/ui/clusters/:clusterName">
+            <ClusterContext.Provider
+              value={{
+                isReadOnly: false,
+                hasKafkaConnectConfigured: true,
+                hasSchemaRegistryConfigured: true,
+                isTopicDeletionAllowed: true,
+              }}
+            >
+              {setupComponent({
+                topics: [
+                  {
+                    ...externalTopicPayload,
+                    cleanUpPolicy: CleanUpPolicy.DELETE,
+                  },
+                  { ...externalTopicPayload, name: 'external.topic2' },
+                ],
+                deleteTopics: mockDeleteTopics,
+                clearTopicsMessages: mockClearTopicsMessages,
+                recreateTopic: mockRecreate,
+                deleteTopic: mockDeleteTopic,
+                clearTopicMessages: mockClearTopic,
+                fetchTopicsList,
+              })}
+            </ClusterContext.Provider>
+          </Route>
+        </StaticRouter>
+      );
+    });
+
+    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', () => {
-      expect(getCheckboxInput(0).props().checked).toBeFalsy();
-      expect(getCheckboxInput(1).props().checked).toBeFalsy();
-      expect(component.find('.buttons').length).toEqual(0);
+      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
-      getCheckboxInput(0).simulate('change', { target: { checked: true } });
-      expect(getCheckboxInput(0).props().checked).toBeTruthy();
-      expect(getCheckboxInput(1).props().checked).toBeFalsy();
+      userEvent.click(firstCheckbox);
+      expect(firstCheckbox).toBeChecked();
+      expect(secondCheckbox).not.toBeChecked();
+
+      expect(screen.getByTestId('delete-buttons')).toBeInTheDocument();
 
       // 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);
+      userEvent.click(secondCheckbox);
+      expect(firstCheckbox).toBeChecked();
+      expect(secondCheckbox).toBeChecked();
+
+      expect(screen.getByTestId('delete-buttons')).toBeInTheDocument();
 
       // 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);
+      userEvent.click(secondCheckbox);
+      expect(firstCheckbox).toBeChecked();
+      expect(secondCheckbox).not.toBeChecked();
+
+      expect(screen.getByTestId('delete-buttons')).toBeInTheDocument();
 
       // 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);
+      userEvent.click(firstCheckbox);
+      expect(firstCheckbox).not.toBeChecked();
+      expect(secondCheckbox).not.toBeChecked();
+
+      expect(screen.queryByTestId('delete-buttons')).not.toBeInTheDocument();
     });
 
-    const checkActionButtonClick = async (action: string) => {
+    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();
+    });
+
+    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();
+        }
       });
-      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);
+
+      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');
     });
   });
 });

+ 0 - 74
kafka-ui-react-app/src/components/Topics/List/__tests__/ListItem.spec.tsx

@@ -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();
-  });
-});

+ 149 - 0
kafka-ui-react-app/src/components/Topics/List/__tests__/TopicsTableCells.spec.tsx

@@ -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();
+    });
+  });
+});

+ 25 - 0
kafka-ui-react-app/src/components/Topics/New/__test__/New.spec.tsx

@@ -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);
+  });
 });

+ 3 - 1
kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/Filters.styled.ts

@@ -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')};

+ 27 - 0
kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/__tests__/Filters.styled.spec.tsx

@@ -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',
+      });
+    });
+  });
+});

+ 5 - 1
kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Message.tsx

@@ -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,

+ 80 - 17
kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/__test__/Message.spec.tsx

@@ -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(
+  const mockMessage: TopicMessage = {
+    timestamp: new Date(),
+    timestampType: TopicMessageTimestampTypeEnum.CREATE_TIME,
+    offset: 0,
+    key: 'test-key',
+    partition: 6,
+    content: '{"data": "test"}',
+    headers: { header: 'test' },
+  };
+
+  const renderComponent = (
+    props: Partial<Props> = {
+      message: mockMessage,
+    }
+  ) => {
+    return render(
       <table>
         <tbody>
-          <Message
-            message={{
-              timestamp: new Date(0),
-              timestampType: TopicMessageTimestampTypeEnum.CREATE_TIME,
-              offset: 0,
-              key: 'test-key',
-              partition: 0,
-              content: '{"data": "test"}',
-              headers: { header: 'test' },
-            }}
-          />
+          <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();
   });
 });

+ 19 - 8
kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/__test__/Messages.spec.tsx

@@ -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();
+      });
     });
   });
 

+ 81 - 11
kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/__test__/MessagesTable.spec.tsx

@@ -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();
+    });
   });
 });

+ 52 - 2
kafka-ui-react-app/src/components/Topics/Topic/Details/Overview/__test__/Overview.spec.tsx

@@ -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();
+    });
+  });
 });

+ 41 - 0
kafka-ui-react-app/src/components/Topics/Topic/Details/Settings/__test__/Settings.styled.spec.tsx

@@ -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'
+      );
+    });
+  });
+});

+ 287 - 0
kafka-ui-react-app/src/components/Topics/Topic/Edit/DangerZone/__test__/DangerZone.spec.tsx

@@ -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();
+  });
+});

+ 1 - 1
kafka-ui-react-app/src/components/Topics/Topic/Edit/Edit.tsx

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

+ 0 - 116
kafka-ui-react-app/src/components/Topics/Topic/Edit/__test__/DangerZone.spec.tsx

@@ -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);
-    });
-  });
-});

+ 136 - 16
kafka-ui-react-app/src/components/Topics/Topic/Edit/__test__/Edit.spec.tsx

@@ -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(
-    <Edit
-      clusterName={clusterName}
-      topicName={topicName}
-      topic={topicWithInfo}
-      isFetched
-      isTopicUpdated={false}
-      fetchTopicConfig={jest.fn()}
-      updateTopic={jest.fn()}
-      updateTopicPartitionsCount={jest.fn()}
-      {...props}
-    />
+    <Router history={history}>
+      <Edit
+        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={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)
+        );
+      });
+    });
+  });
 });

+ 61 - 14
kafka-ui-react-app/src/components/Topics/Topic/SendMessage/__test__/SendMessage.spec.tsx

@@ -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 history={history}>
+        <Route path={clusterTopicSendMessagePath(':clusterName', ':topicName')}>
+          <SendMessage />
+        </Route>
+      </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
+      const sendTopicMessageMock = fetchMock.postOnce(url, 200);
+      await renderAndSubmitData();
+
+      await waitFor(() =>
+        expect(sendTopicMessageMock.called(url)).toBeTruthy()
+      );
+      expect(history.location.pathname).toEqual(
+        clusterTopicMessagesPath(clusterName, topicName)
       );
-      renderComponent();
-      await waitForElementToBeRemoved(() => screen.getByRole('progressbar'));
+    });
 
-      userEvent.selectOptions(screen.getByLabelText('Partition'), '0');
-      await screen.findByText('Send');
-      userEvent.click(screen.getByText('Send'));
-      await waitFor(() => expect(sendTopicMessageMock.called()).toBeTruthy());
+    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)
+      );
+    });
   });
 });

+ 52 - 2
kafka-ui-react-app/src/components/Topics/Topic/SendMessage/__test__/validateMessage.spec.ts

@@ -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', () => {

+ 58 - 44
kafka-ui-react-app/src/components/Topics/Topic/__tests__/Topic.spec.tsx

@@ -9,64 +9,78 @@ 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) =>
-  render(
-    <Route path={clusterTopicPath(':clusterName', ':topicName')}>
-      <Topic
-        isTopicFetching={topicFetching}
-        resetTopicMessages={resetTopicMessages}
-        fetchTopicDetails={fetchTopicDetailsMock}
-      />
-    </Route>,
-    { pathname }
-  );
+  const renderComponent = (pathname: string, topicFetching: boolean) =>
+    render(
+      <Route path={clusterTopicPath(':clusterName', ':topicName')}>
+        <Topic
+          isTopicFetching={topicFetching}
+          resetTopicMessages={resetTopicMessages}
+          fetchTopicDetails={fetchTopicDetailsMock}
+        />
+      </Route>,
+      { pathname }
+    );
 
-it('renders Edit page', () => {
-  renderComponent(clusterTopicEditPath('local', 'myTopicName'), false);
-  expect(screen.getByText('Edit Container')).toBeInTheDocument();
-});
+  afterEach(() => {
+    resetTopicMessages.mockClear();
+    fetchTopicDetailsMock.mockClear();
+  });
 
-it('renders Send Message page', () => {
-  renderComponent(clusterTopicSendMessagePath('local', 'myTopicName'), false);
-  expect(screen.getByText('Send Message')).toBeInTheDocument();
-});
+  it('renders Edit page', () => {
+    renderComponent(clusterTopicEditPath('local', 'myTopicName'), false);
+    expect(screen.getByText(topicText.edit)).toBeInTheDocument();
+  });
 
-it('renders Details Container page', () => {
-  renderComponent(clusterTopicPath('local', 'myTopicName'), false);
-  expect(screen.getByText('Details Container')).toBeInTheDocument();
-});
+  it('renders Send Message page', () => {
+    renderComponent(clusterTopicSendMessagePath('local', 'myTopicName'), false);
+    expect(screen.getByText(topicText.send)).toBeInTheDocument();
+  });
 
-it('renders Page loader', () => {
-  renderComponent(clusterTopicPath('local', 'myTopicName'), true);
-  expect(screen.getByText('Loading')).toBeInTheDocument();
-});
+  it('renders Details Container page', () => {
+    renderComponent(clusterTopicPath('local', 'myTopicName'), false);
+    expect(screen.getByText(topicText.detail)).toBeInTheDocument();
+  });
 
-it('fetches topicDetails', () => {
-  renderComponent(clusterTopicPath('local', 'myTopicName'), false);
-  expect(fetchTopicDetailsMock).toHaveBeenCalledTimes(1);
-});
+  it('renders Page loader', () => {
+    renderComponent(clusterTopicPath('local', 'myTopicName'), true);
+    expect(screen.getByText(topicText.loading)).toBeInTheDocument();
+  });
+
+  it('fetches topicDetails', () => {
+    renderComponent(clusterTopicPath('local', 'myTopicName'), false);
+    expect(fetchTopicDetailsMock).toHaveBeenCalledTimes(1);
+  });
 
-it('resets topic messages after unmount', () => {
-  const component = renderComponent(
-    clusterTopicPath('local', 'myTopicName'),
-    false
-  );
-  component.unmount();
-  expect(resetTopicMessages).toHaveBeenCalledTimes(1);
+  it('resets topic messages after unmount', () => {
+    const component = renderComponent(
+      clusterTopicPath('local', 'myTopicName'),
+      false
+    );
+    component.unmount();
+    expect(resetTopicMessages).toHaveBeenCalledTimes(1);
+  });
 });

+ 61 - 0
kafka-ui-react-app/src/components/Topics/__tests__/Topics.spec.tsx

@@ -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();
+  });
+});

+ 12 - 6
kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/__test__/CustomParamField.spec.tsx

@@ -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);
     });
   });
 });

+ 4 - 15
kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/__test__/CustomParams.spec.tsx

@@ -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));
 

+ 27 - 0
kafka-ui-react-app/src/components/Topics/shared/Form/__tests__/TopicForm.styled.tsx

@@ -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,
+      });
+    });
+  });
+});

+ 6 - 1
kafka-ui-react-app/src/redux/reducers/topics/__test__/fixtures.ts

@@ -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,
+];