Repository: provectus/kafka-ui
Issue: Improve test coverage
Labels: good first issue
scope/frontend
type/chore

**Describe the bug**
Files are not completely covered with tests 
src/components/Topics/Topic/Details/Messages/Filters/utils.ts
src/components/Topics/Topic/Topic.tsx
src/components/Topics/Topic/Details/Settings/ConfigListItem.tsx


**Expected behavior**
100% covered 

Issue comments:

Pull request review comments:
```suggestion
    it('returns parsed partition from params when partition list includes selected partition', () => {
```
update description
returns what?
👍 
I'm ok with it
returns what?
pls check all the cases

```suggestion
    it('returns partitions when searchParams not defined', () => {
```

Patch that resolved the issue:
diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/utils.ts b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/utils.ts
index 6328d96ac9b..8b29b5b820f 100644
--- a/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/utils.ts
+++ b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/Filters/utils.ts
@@ -31,7 +31,6 @@ export const getTimestampFromSeekToParam = (params: URLSearchParams) => {
       .get('seekTo')
       ?.split(',')
       .map((item) => Number(item.split('::')[1]));
-
     return new Date(Math.max(...(offsets || []), 0));
   }
 
diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/__test__/utils.spec.ts b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/__test__/utils.spec.ts
index 526682ae56a..c58bc09b0ea 100644
--- a/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/__test__/utils.spec.ts
+++ b/kafka-ui-react-app/src/components/Topics/Topic/Details/Messages/__test__/utils.spec.ts
@@ -1,5 +1,11 @@
 import { Option } from 'react-multi-select-component/dist/lib/interfaces';
-import { filterOptions } from 'components/Topics/Topic/Details/Messages/Filters/utils';
+import {
+  filterOptions,
+  getOffsetFromSeekToParam,
+  getTimestampFromSeekToParam,
+  getSelectedPartitionsFromSeekToParam,
+} from 'components/Topics/Topic/Details/Messages/Filters/utils';
+import { SeekType, Partition } from 'generated-sources';
 
 const options: Option[] = [
   {
@@ -20,6 +26,9 @@ const options: Option[] = [
   },
 ];
 
+let paramsString;
+let searchParams = new URLSearchParams(paramsString);
+
 describe('utils', () => {
   describe('filterOptions', () => {
     it('returns options if no filter is defined', () => {
@@ -30,4 +39,82 @@ describe('utils', () => {
       expect(filterOptions(options, '11')).toEqual([options[2]]);
     });
   });
+
+  describe('getOffsetFromSeekToParam', () => {
+    beforeEach(() => {
+      paramsString = 'seekTo=0::123,1::123,2::0';
+      searchParams = new URLSearchParams(paramsString);
+    });
+
+    it('returns nothing when param "seekType" is equal BEGGINING', () => {
+      searchParams.set('seekType', SeekType.BEGINNING);
+      expect(getOffsetFromSeekToParam(searchParams)).toEqual('');
+    });
+
+    it('returns nothing when param "seekType" is equal TIMESTAMP', () => {
+      searchParams.set('seekType', SeekType.TIMESTAMP);
+      expect(getOffsetFromSeekToParam(searchParams)).toEqual('');
+    });
+
+    it('returns correct messages list when param "seekType" is equal OFFSET', () => {
+      searchParams.set('seekType', SeekType.OFFSET);
+      expect(getOffsetFromSeekToParam(searchParams)).toEqual('123');
+    });
+
+    it('returns 0 when param "seekTo" is not defined and param "seekType" is equal OFFSET', () => {
+      searchParams.set('seekType', SeekType.OFFSET);
+      searchParams.delete('seekTo');
+      expect(getOffsetFromSeekToParam(searchParams)).toEqual('0');
+    });
+  });
+
+  describe('getTimestampFromSeekToParam', () => {
+    beforeEach(() => {
+      paramsString = `seekTo=0::1627333200000,1::1627333200000`;
+      searchParams = new URLSearchParams(paramsString);
+    });
+
+    it('returns null when param "seekType" is equal BEGGINING', () => {
+      searchParams.set('seekType', SeekType.BEGINNING);
+      expect(getTimestampFromSeekToParam(searchParams)).toEqual(null);
+    });
+    it('returns null when param "seekType" is equal OFFSET', () => {
+      searchParams.set('seekType', SeekType.OFFSET);
+      expect(getTimestampFromSeekToParam(searchParams)).toEqual(null);
+    });
+    it('returns correct messages list when param "seekType" is equal TIMESTAMP', () => {
+      searchParams.set('seekType', SeekType.TIMESTAMP);
+      expect(getTimestampFromSeekToParam(searchParams)).toEqual(
+        new Date(1627333200000)
+      );
+    });
+    it('returns default timestamp when param "seekTo" is empty and param "seekType" is equal TIMESTAMP', () => {
+      searchParams.set('seekType', SeekType.TIMESTAMP);
+      searchParams.delete('seekTo');
+      expect(getTimestampFromSeekToParam(searchParams)).toEqual(new Date(0));
+    });
+  });
+
+  describe('getSelectedPartitionsFromSeekToParam', () => {
+    const part: Partition[] = [{ partition: 42, offsetMin: 0, offsetMax: 100 }];
+
+    it('returns parsed partition from params when partition list includes selected partition', () => {
+      searchParams.set('seekTo', '42::0');
+      expect(getSelectedPartitionsFromSeekToParam(searchParams, part)).toEqual([
+        { label: '42', value: 42 },
+      ]);
+    });
+    it('returns parsed partition from params when partition list NOT includes selected partition', () => {
+      searchParams.set('seekTo', '24::0');
+      expect(getSelectedPartitionsFromSeekToParam(searchParams, part)).toEqual(
+        []
+      );
+    });
+    it('returns partitions when param "seekTo" is not defined', () => {
+      searchParams.delete('seekTo');
+      expect(getSelectedPartitionsFromSeekToParam(searchParams, part)).toEqual([
+        { label: '42', value: 42 },
+      ]);
+    });
+  });
 });
diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Details/Settings/ConfigListItem.tsx b/kafka-ui-react-app/src/components/Topics/Topic/Details/Settings/ConfigListItem.tsx
index 087db1716da..121ab456848 100644
--- a/kafka-ui-react-app/src/components/Topics/Topic/Details/Settings/ConfigListItem.tsx
+++ b/kafka-ui-react-app/src/components/Topics/Topic/Details/Settings/ConfigListItem.tsx
@@ -3,19 +3,23 @@ import React from 'react';
 
 import * as S from './Settings.styled';
 
-interface ListItemProps {
+export interface ListItemProps {
   config: TopicConfig;
 }
 
 const ConfigListItem: React.FC<ListItemProps> = ({
   config: { name, value, defaultValue },
 }) => {
-  const hasCustomValue = value !== defaultValue;
+  const hasCustomValue = !!defaultValue && value !== defaultValue;
 
   return (
     <S.ConfigList>
-      <S.ConfigItemCell $hasCustomValue>{name}</S.ConfigItemCell>
-      <S.ConfigItemCell $hasCustomValue>{value}</S.ConfigItemCell>
+      <S.ConfigItemCell $hasCustomValue={hasCustomValue}>
+        {name}
+      </S.ConfigItemCell>
+      <S.ConfigItemCell $hasCustomValue={hasCustomValue}>
+        {value}
+      </S.ConfigItemCell>
       <td className="has-text-grey" title="Default Value">
         {hasCustomValue && defaultValue}
       </td>
diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Details/Settings/Settings.styled.ts b/kafka-ui-react-app/src/components/Topics/Topic/Details/Settings/Settings.styled.ts
index 7fad7166096..bc15ab49a9d 100644
--- a/kafka-ui-react-app/src/components/Topics/Topic/Details/Settings/Settings.styled.ts
+++ b/kafka-ui-react-app/src/components/Topics/Topic/Details/Settings/Settings.styled.ts
@@ -6,5 +6,5 @@ export const ConfigList = styled.tr`
   }
 `;
 export const ConfigItemCell = styled.td<{ $hasCustomValue: boolean }>`
-  font-weight: ${(props) => (props.$hasCustomValue ? 500 : 400)};
+  font-weight: ${(props) => (props.$hasCustomValue ? 500 : 400)} !important;
 `;
diff --git a/kafka-ui-react-app/src/components/Topics/Topic/Details/Settings/__test__/ConfigListItem.spec.tsx b/kafka-ui-react-app/src/components/Topics/Topic/Details/Settings/__test__/ConfigListItem.spec.tsx
new file mode 100644
index 00000000000..5aa8794fa35
--- /dev/null
+++ b/kafka-ui-react-app/src/components/Topics/Topic/Details/Settings/__test__/ConfigListItem.spec.tsx
@@ -0,0 +1,42 @@
+import React from 'react';
+import { render } from 'lib/testHelpers';
+import { screen } from '@testing-library/react';
+import ConfigListItem, {
+  ListItemProps,
+} from 'components/Topics/Topic/Details/Settings/ConfigListItem';
+
+const setupComponent = (props: ListItemProps) => {
+  render(
+    <table>
+      <tbody>
+        <ConfigListItem {...props} />
+      </tbody>
+    </table>
+  );
+};
+
+it('renders with CustomValue', () => {
+  setupComponent({
+    config: {
+      name: 'someName',
+      value: 'someValue',
+      defaultValue: 'someDefaultValue',
+    },
+  });
+  expect(screen.getByText('someName')).toBeInTheDocument();
+  expect(screen.getByText('someName')).toHaveStyle('font-weight: 500');
+  expect(screen.getByText('someValue')).toBeInTheDocument();
+  expect(screen.getByText('someValue')).toHaveStyle('font-weight: 500');
+  expect(screen.getByText('someDefaultValue')).toBeInTheDocument();
+});
+
+it('renders without CustomValue', () => {
+  setupComponent({
+    config: { name: 'someName', value: 'someValue', defaultValue: 'someValue' },
+  });
+  expect(screen.getByText('someName')).toBeInTheDocument();
+  expect(screen.getByText('someName')).toHaveStyle('font-weight: 400');
+  expect(screen.getByText('someValue')).toBeInTheDocument();
+  expect(screen.getByText('someValue')).toHaveStyle('font-weight: 400');
+  expect(screen.getByTitle('Default Value')).toHaveTextContent('');
+});
diff --git a/kafka-ui-react-app/src/components/Topics/Topic/__tests__/Topic.spec.tsx b/kafka-ui-react-app/src/components/Topics/Topic/__tests__/Topic.spec.tsx
new file mode 100644
index 00000000000..d5ee996f749
--- /dev/null
+++ b/kafka-ui-react-app/src/components/Topics/Topic/__tests__/Topic.spec.tsx
@@ -0,0 +1,61 @@
+import React from 'react';
+import { Route } from 'react-router-dom';
+import { render } from 'lib/testHelpers';
+import { screen } from '@testing-library/react';
+import Topic from 'components/Topics/Topic/Topic';
+import {
+  clusterTopicPath,
+  clusterTopicEditPath,
+  clusterTopicSendMessagePath,
+} from 'lib/paths';
+
+jest.mock('components/Topics/Topic/Edit/EditContainer', () => () => (
+  <div>Edit Container</div>
+));
+jest.mock('components/Topics/Topic/SendMessage/SendMessage', () => () => (
+  <div>Send Message</div>
+));
+jest.mock('components/Topics/Topic/Details/DetailsContainer', () => () => (
+  <div>Details Container</div>
+));
+jest.mock('components/common/PageLoader/PageLoader', () => () => (
+  <div>Loading</div>
+));
+
+const fetchTopicDetailsMock = jest.fn();
+
+const renderComponent = (pathname: string, topicFetching: boolean) =>
+  render(
+    <Route path={clusterTopicPath(':clusterName', ':topicName')}>
+      <Topic
+        isTopicFetching={topicFetching}
+        fetchTopicDetails={fetchTopicDetailsMock}
+      />
+    </Route>,
+    { pathname }
+  );
+
+it('renders Edit page', () => {
+  renderComponent(clusterTopicEditPath('local', 'myTopicName'), false);
+  expect(screen.getByText('Edit Container')).toBeInTheDocument();
+});
+
+it('renders Send Message page', () => {
+  renderComponent(clusterTopicSendMessagePath('local', 'myTopicName'), false);
+  expect(screen.getByText('Send Message')).toBeInTheDocument();
+});
+
+it('renders Details Container page', () => {
+  renderComponent(clusterTopicPath('local', 'myTopicName'), false);
+  expect(screen.getByText('Details Container')).toBeInTheDocument();
+});
+
+it('renders Page loader', () => {
+  renderComponent(clusterTopicPath('local', 'myTopicName'), true);
+  expect(screen.getByText('Loading')).toBeInTheDocument();
+});
+
+it('fetches topicDetails', () => {
+  renderComponent(clusterTopicPath('local', 'myTopicName'), false);
+  expect(fetchTopicDetailsMock).toHaveBeenCalledTimes(1);
+});


Test patch: