Repository: provectus/kafka-ui
Issue: Cover common components with tests
Labels: good first issue
scope/frontend

As an engineer using kafka-ui in production, I would like to have 100% test coverage.

Please add specs to all common components (src/components/common)

Issue comments:

Pull request review comments:
I would suggest to just check that last Li contains `child`
Please add specs to test updated code
I would also suggest to wrap all this code into `try catch`
Please add examples with incorrect input props
I think that adding a snapshot will allow you to cover the part of the code that you are testing here 
👍 
It should return value not an error. Lets return "-"
I prefer to avoid random data in tests. Lets use const value like 533. and check it like
```
expect(component.text()).toEqual('533 Bytes');
```
From the interface `value: string | number | undefined`. Please add specs for all available cases
please use 
```suggestion
    expect(component.text()).toEqual('10Blah');
```

Patch that resolved the issue:
diff --git a/kafka-ui-react-app/src/components/common/Breadcrumb/Breadcrumb.tsx b/kafka-ui-react-app/src/components/common/Breadcrumb/Breadcrumb.tsx
index b3b7a2c5c08..2e93e2a6855 100644
--- a/kafka-ui-react-app/src/components/common/Breadcrumb/Breadcrumb.tsx
+++ b/kafka-ui-react-app/src/components/common/Breadcrumb/Breadcrumb.tsx
@@ -1,7 +1,7 @@
 import React from 'react';
 import { NavLink } from 'react-router-dom';
 
-interface Link {
+export interface Link {
   label: string;
   href: string;
 }
diff --git a/kafka-ui-react-app/src/components/common/Breadcrumb/__tests__/Breadcrumb.spec.tsx b/kafka-ui-react-app/src/components/common/Breadcrumb/__tests__/Breadcrumb.spec.tsx
new file mode 100644
index 00000000000..c8b3a581568
--- /dev/null
+++ b/kafka-ui-react-app/src/components/common/Breadcrumb/__tests__/Breadcrumb.spec.tsx
@@ -0,0 +1,48 @@
+import { mount, shallow } from 'enzyme';
+import React from 'react';
+import { StaticRouter } from 'react-router-dom';
+import Breadcrumb, { Link } from '../Breadcrumb';
+
+describe('Breadcrumb component', () => {
+  const links: Link[] = [
+    {
+      label: 'link1',
+      href: 'link1href',
+    },
+    {
+      label: 'link2',
+      href: 'link2href',
+    },
+    {
+      label: 'link3',
+      href: 'link3href',
+    },
+  ];
+
+  const child = <div className="child" />;
+
+  const component = mount(
+    <StaticRouter>
+      <Breadcrumb links={links}>{child}</Breadcrumb>
+    </StaticRouter>
+  );
+
+  it('renders the list of links', () => {
+    component.find(`NavLink`).forEach((link, idx) => {
+      expect(link.prop('to')).toEqual(links[idx].href);
+      expect(link.contains(links[idx].label)).toBeTruthy();
+    });
+  });
+  it('renders the children', () => {
+    const list = component.find('ul').children();
+    expect(list.last().containsMatchingElement(child)).toBeTruthy();
+  });
+  it('matches the snapshot', () => {
+    const shallowComponent = shallow(
+      <StaticRouter>
+        <Breadcrumb links={links}>{child}</Breadcrumb>
+      </StaticRouter>
+    );
+    expect(shallowComponent).toMatchSnapshot();
+  });
+});
diff --git a/kafka-ui-react-app/src/components/common/Breadcrumb/__tests__/__snapshots__/Breadcrumb.spec.tsx.snap b/kafka-ui-react-app/src/components/common/Breadcrumb/__tests__/__snapshots__/Breadcrumb.spec.tsx.snap
new file mode 100644
index 00000000000..b7696f3bb63
--- /dev/null
+++ b/kafka-ui-react-app/src/components/common/Breadcrumb/__tests__/__snapshots__/Breadcrumb.spec.tsx.snap
@@ -0,0 +1,49 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Breadcrumb component matches the snapshot 1`] = `
+<Router
+  history={
+    Object {
+      "action": "POP",
+      "block": [Function],
+      "createHref": [Function],
+      "go": [Function],
+      "goBack": [Function],
+      "goForward": [Function],
+      "listen": [Function],
+      "location": Object {
+        "hash": "",
+        "pathname": "/",
+        "search": "",
+        "state": undefined,
+      },
+      "push": [Function],
+      "replace": [Function],
+    }
+  }
+  staticContext={Object {}}
+>
+  <Breadcrumb
+    links={
+      Array [
+        Object {
+          "href": "link1href",
+          "label": "link1",
+        },
+        Object {
+          "href": "link2href",
+          "label": "link2",
+        },
+        Object {
+          "href": "link3href",
+          "label": "link3",
+        },
+      ]
+    }
+  >
+    <div
+      className="child"
+    />
+  </Breadcrumb>
+</Router>
+`;
diff --git a/kafka-ui-react-app/src/components/common/BytesFormatted/BytesFormatted.tsx b/kafka-ui-react-app/src/components/common/BytesFormatted/BytesFormatted.tsx
index 2334c8067ab..9447f724025 100644
--- a/kafka-ui-react-app/src/components/common/BytesFormatted/BytesFormatted.tsx
+++ b/kafka-ui-react-app/src/components/common/BytesFormatted/BytesFormatted.tsx
@@ -5,20 +5,22 @@ interface Props {
   precision?: number;
 }
 
-const BytesFormatted: React.FC<Props> = ({ value, precision = 0 }) => {
-  const formatedValue = React.useMemo(() => {
-    const bytes = typeof value === 'string' ? parseInt(value, 10) : value;
-
-    const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
-    if (!bytes || bytes === 0) return [0, sizes[0]];
+export const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
 
-    if (bytes < 1024) return [Math.ceil(bytes), sizes[0]];
-
-    const pow = Math.floor(Math.log2(bytes) / 10);
-    const multiplier = 10 ** (precision || 2);
-    return (
-      Math.round((bytes * multiplier) / 1024 ** pow) / multiplier + sizes[pow]
-    );
+const BytesFormatted: React.FC<Props> = ({ value, precision = 0 }) => {
+  const formatedValue = React.useMemo((): string => {
+    try {
+      const bytes = typeof value === 'string' ? parseInt(value, 10) : value;
+      if (Number.isNaN(bytes)) return `-Bytes`;
+      if (!bytes || bytes < 1024) return `${Math.ceil(bytes || 0)}${sizes[0]}`;
+      const pow = Math.floor(Math.log2(bytes) / 10);
+      const multiplier = 10 ** (precision < 0 ? 0 : precision);
+      return (
+        Math.round((bytes * multiplier) / 1024 ** pow) / multiplier + sizes[pow]
+      );
+    } catch (e) {
+      return `-Bytes`;
+    }
   }, [value]);
 
   return <span>{formatedValue}</span>;
diff --git a/kafka-ui-react-app/src/components/common/BytesFormatted/__tests__/BytesFormatted.spec.tsx b/kafka-ui-react-app/src/components/common/BytesFormatted/__tests__/BytesFormatted.spec.tsx
new file mode 100644
index 00000000000..85ed10877b3
--- /dev/null
+++ b/kafka-ui-react-app/src/components/common/BytesFormatted/__tests__/BytesFormatted.spec.tsx
@@ -0,0 +1,38 @@
+import { shallow } from 'enzyme';
+import React from 'react';
+import BytesFormatted, { sizes } from '../BytesFormatted';
+
+describe('BytesFormatted', () => {
+  it('renders Bytes correctly', () => {
+    const component = shallow(<BytesFormatted value={666} />);
+    expect(component.text()).toEqual('666Bytes');
+  });
+
+  it('renders correct units', () => {
+    let value = 1;
+    sizes.forEach((unit) => {
+      const component = shallow(<BytesFormatted value={value} />);
+      expect(component.text()).toEqual(`1${unit}`);
+      value *= 1024;
+    });
+  });
+
+  it('renders correct precision', () => {
+    let component = shallow(<BytesFormatted value={2000} precision={100} />);
+    expect(component.text()).toEqual(`1.953125${sizes[1]}`);
+
+    component = shallow(<BytesFormatted value={10000} precision={5} />);
+    expect(component.text()).toEqual(`9.76563${sizes[1]}`);
+  });
+
+  it('correctly handles invalid props', () => {
+    let component = shallow(<BytesFormatted value={10000} precision={-1} />);
+    expect(component.text()).toEqual(`10${sizes[1]}`);
+
+    component = shallow(<BytesFormatted value="some string" />);
+    expect(component.text()).toEqual(`-${sizes[0]}`);
+
+    component = shallow(<BytesFormatted value={undefined} />);
+    expect(component.text()).toEqual(`0${sizes[0]}`);
+  });
+});
diff --git a/kafka-ui-react-app/src/components/common/Dashboard/__tests__/Indicator.spec.tsx b/kafka-ui-react-app/src/components/common/Dashboard/__tests__/Indicator.spec.tsx
new file mode 100644
index 00000000000..3709f9add18
--- /dev/null
+++ b/kafka-ui-react-app/src/components/common/Dashboard/__tests__/Indicator.spec.tsx
@@ -0,0 +1,15 @@
+import { mount } from 'enzyme';
+import React from 'react';
+import Indicator from '../Indicator';
+
+describe('Indicator', () => {
+  it('matches the snapshot', () => {
+    const child = 'Child';
+    const component = mount(
+      <Indicator title="title" label="label">
+        {child}
+      </Indicator>
+    );
+    expect(component).toMatchSnapshot();
+  });
+});
diff --git a/kafka-ui-react-app/src/components/common/Dashboard/__tests__/MetricsWrapper.spec.tsx b/kafka-ui-react-app/src/components/common/Dashboard/__tests__/MetricsWrapper.spec.tsx
new file mode 100644
index 00000000000..73864385ffd
--- /dev/null
+++ b/kafka-ui-react-app/src/components/common/Dashboard/__tests__/MetricsWrapper.spec.tsx
@@ -0,0 +1,24 @@
+import { shallow } from 'enzyme';
+import React from 'react';
+import MetricsWrapper from '../MetricsWrapper';
+
+describe('MetricsWrapper', () => {
+  it('correctly adds classes', () => {
+    const className = 'className';
+    const component = shallow(
+      <MetricsWrapper wrapperClassName={className} multiline />
+    );
+    expect(component.find(`.${className}`).exists()).toBeTruthy();
+    expect(component.find('.level-multiline').exists()).toBeTruthy();
+  });
+
+  it('correctly renders children', () => {
+    let component = shallow(<MetricsWrapper />);
+    expect(component.find('.subtitle').exists()).toBeFalsy();
+
+    const title = 'title';
+    component = shallow(<MetricsWrapper title={title} />);
+    expect(component.find('.subtitle').exists()).toBeTruthy();
+    expect(component.text()).toEqual(title);
+  });
+});
diff --git a/kafka-ui-react-app/src/components/common/Dashboard/__tests__/__snapshots__/Indicator.spec.tsx.snap b/kafka-ui-react-app/src/components/common/Dashboard/__tests__/__snapshots__/Indicator.spec.tsx.snap
new file mode 100644
index 00000000000..7cabd55e4f5
--- /dev/null
+++ b/kafka-ui-react-app/src/components/common/Dashboard/__tests__/__snapshots__/Indicator.spec.tsx.snap
@@ -0,0 +1,27 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Indicator matches the snapshot 1`] = `
+<Indicator
+  label="label"
+  title="title"
+>
+  <div
+    className="level-item level-left"
+  >
+    <div
+      title="title"
+    >
+      <p
+        className="heading"
+      >
+        label
+      </p>
+      <p
+        className="title"
+      >
+        Child
+      </p>
+    </div>
+  </div>
+</Indicator>
+`;
diff --git a/kafka-ui-react-app/src/components/common/PageLoader/__tests__/PageLoader.spec.tsx b/kafka-ui-react-app/src/components/common/PageLoader/__tests__/PageLoader.spec.tsx
new file mode 100644
index 00000000000..ea34bb19420
--- /dev/null
+++ b/kafka-ui-react-app/src/components/common/PageLoader/__tests__/PageLoader.spec.tsx
@@ -0,0 +1,10 @@
+import { mount } from 'enzyme';
+import React from 'react';
+import PageLoader from '../PageLoader';
+
+describe('PageLoader', () => {
+  it('matches the snapshot', () => {
+    const component = mount(<PageLoader />);
+    expect(component).toMatchSnapshot();
+  });
+});
diff --git a/kafka-ui-react-app/src/components/common/PageLoader/__tests__/__snapshots__/PageLoader.spec.tsx.snap b/kafka-ui-react-app/src/components/common/PageLoader/__tests__/__snapshots__/PageLoader.spec.tsx.snap
new file mode 100644
index 00000000000..10a9cf06313
--- /dev/null
+++ b/kafka-ui-react-app/src/components/common/PageLoader/__tests__/__snapshots__/PageLoader.spec.tsx.snap
@@ -0,0 +1,36 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`PageLoader matches the snapshot 1`] = `
+<PageLoader>
+  <section
+    className="hero is-fullheight-with-navbar"
+  >
+    <div
+      className="hero-body has-text-centered"
+      style={
+        Object {
+          "justifyContent": "center",
+        }
+      }
+    >
+      <div
+        style={
+          Object {
+            "width": 300,
+          }
+        }
+      >
+        <div
+          className="subtitle"
+        >
+          Loading...
+        </div>
+        <progress
+          className="progress is-small is-primary is-inline-block"
+          max="100"
+        />
+      </div>
+    </div>
+  </section>
+</PageLoader>
+`;


Test patch: