浏览代码

[CHORE] Specs. Improvements (#202)

* [CHORE] Improve types

* [CHORE] Example of actions & thunks specs

* [CHORE] Example of reducer & selectors specs

* [CHORE] Update dependencies
Oleg Shur 4 年之前
父节点
当前提交
8f63cf775e

+ 321 - 177
kafka-ui-react-app/package-lock.json

@@ -1573,6 +1573,11 @@
         "@hapi/hoek": "^8.3.0"
       }
     },
+    "@hookform/error-message": {
+      "version": "0.0.5",
+      "resolved": "https://registry.npmjs.org/@hookform/error-message/-/error-message-0.0.5.tgz",
+      "integrity": "sha512-es7eLLFA3SXNYAT8aUjvf7Gok1eMHK+9DMILtJA7ZEwYZlCCCPifhpoZmY+5SOopEtF7e+qxFOjX+MJnI3jOcg=="
+    },
     "@istanbuljs/load-nyc-config": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
@@ -2718,10 +2723,30 @@
       "dev": true
     },
     "@types/node": {
-      "version": "12.19.15",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-12.19.15.tgz",
-      "integrity": "sha512-lowukE3GUI+VSYSu6VcBXl14d61Rp5hA1D+61r16qnwC0lYNSqdxcvRh0pswejorHfS+HgwBasM8jLXz0/aOsw==",
-      "dev": true
+      "version": "12.20.2",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.2.tgz",
+      "integrity": "sha512-djoyN0pvTje9Lpu25ZwZwlLQICPiuv42omiydLhl7om+og1RhQboGmar12KaKls8soTUQ893TuWCrlyt8B1pVg=="
+    },
+    "@types/node-fetch": {
+      "version": "2.5.8",
+      "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.8.tgz",
+      "integrity": "sha512-fbjI6ja0N5ZA8TV53RUqzsKNkl9fv8Oj3T7zxW7FGv1GSH7gwJaNF8dzCjrqKaxKeUpTz4yT1DaJFq/omNpGfw==",
+      "requires": {
+        "@types/node": "*",
+        "form-data": "^3.0.0"
+      },
+      "dependencies": {
+        "form-data": {
+          "version": "3.0.1",
+          "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz",
+          "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==",
+          "requires": {
+            "asynckit": "^0.4.0",
+            "combined-stream": "^1.0.8",
+            "mime-types": "^2.1.12"
+          }
+        }
+      }
     },
     "@types/normalize-package-data": {
       "version": "2.4.0",
@@ -2753,9 +2778,9 @@
       "dev": true
     },
     "@types/react": {
-      "version": "17.0.0",
-      "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.0.tgz",
-      "integrity": "sha512-aj/L7RIMsRlWML3YB6KZiXB3fV2t41+5RBGYF8z+tAKU43Px8C3cYUZsDvf1/+Bm4FK21QWBrDutu8ZJ/70qOw==",
+      "version": "17.0.2",
+      "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.2.tgz",
+      "integrity": "sha512-Xt40xQsrkdvjn1EyWe1Bc0dJLcil/9x2vAuW7ya+PuQip4UYUaXyhzWmAbwRsdMgwOFHpfp7/FFZebDU6Y8VHA==",
       "dev": true,
       "requires": {
         "@types/prop-types": "*",
@@ -2774,9 +2799,9 @@
       }
     },
     "@types/react-dom": {
-      "version": "17.0.0",
-      "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.0.tgz",
-      "integrity": "sha512-lUqY7OlkF/RbNtD5nIq7ot8NquXrdFrjSOR6+w9a9RFQevGi1oZO1dcJbXMeONAPKtZ2UrZOEJ5UOCVsxbLk/g==",
+      "version": "17.0.1",
+      "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.1.tgz",
+      "integrity": "sha512-yIVyopxQb8IDZ7SOHeTovurFq+fXiPICa+GV3gp0Xedsl+MwQlMLKmvrnEjFbQxjliH5YVAEWFh975eVNmKj7Q==",
       "dev": true,
       "requires": {
         "@types/react": "*"
@@ -2824,6 +2849,15 @@
         "redux": "*"
       }
     },
+    "@types/redux-mock-store": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/@types/redux-mock-store/-/redux-mock-store-1.0.2.tgz",
+      "integrity": "sha512-6LBtAQBN34i7SI5X+Qs4zpTEZO1tTDZ6sZ9fzFjYwTl3nLQXaBtwYdoV44CzNnyKu438xJ1lSIYyw0YMvunESw==",
+      "dev": true,
+      "requires": {
+        "redux": "^4.0.5"
+      }
+    },
     "@types/redux-thunk": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/@types/redux-thunk/-/redux-thunk-2.1.0.tgz",
@@ -3101,13 +3135,13 @@
       "dev": true
     },
     "@typescript-eslint/eslint-plugin": {
-      "version": "4.14.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.14.1.tgz",
-      "integrity": "sha512-5JriGbYhtqMS1kRcZTQxndz1lKMwwEXKbwZbkUZNnp6MJX0+OVXnG0kOlBZP4LUAxEyzu3cs+EXd/97MJXsGfw==",
+      "version": "4.15.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.15.1.tgz",
+      "integrity": "sha512-yW2epMYZSpNJXZy22Biu+fLdTG8Mn6b22kR3TqblVk50HGNV8Zya15WAXuQCr8tKw4Qf1BL4QtI6kv6PCkLoJw==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/experimental-utils": "4.14.1",
-        "@typescript-eslint/scope-manager": "4.14.1",
+        "@typescript-eslint/experimental-utils": "4.15.1",
+        "@typescript-eslint/scope-manager": "4.15.1",
         "debug": "^4.1.1",
         "functional-red-black-tree": "^1.0.1",
         "lodash": "^4.17.15",
@@ -3116,6 +3150,61 @@
         "tsutils": "^3.17.1"
       },
       "dependencies": {
+        "@typescript-eslint/experimental-utils": {
+          "version": "4.15.1",
+          "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.15.1.tgz",
+          "integrity": "sha512-9LQRmOzBRI1iOdJorr4jEnQhadxK4c9R2aEAsm7WE/7dq8wkKD1suaV0S/JucTL8QlYUPU1y2yjqg+aGC0IQBQ==",
+          "dev": true,
+          "requires": {
+            "@types/json-schema": "^7.0.3",
+            "@typescript-eslint/scope-manager": "4.15.1",
+            "@typescript-eslint/types": "4.15.1",
+            "@typescript-eslint/typescript-estree": "4.15.1",
+            "eslint-scope": "^5.0.0",
+            "eslint-utils": "^2.0.0"
+          }
+        },
+        "@typescript-eslint/scope-manager": {
+          "version": "4.15.1",
+          "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.15.1.tgz",
+          "integrity": "sha512-ibQrTFcAm7yG4C1iwpIYK7vDnFg+fKaZVfvyOm3sNsGAerKfwPVFtYft5EbjzByDJ4dj1WD8/34REJfw/9wdVA==",
+          "dev": true,
+          "requires": {
+            "@typescript-eslint/types": "4.15.1",
+            "@typescript-eslint/visitor-keys": "4.15.1"
+          }
+        },
+        "@typescript-eslint/types": {
+          "version": "4.15.1",
+          "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.15.1.tgz",
+          "integrity": "sha512-iGsaUyWFyLz0mHfXhX4zO6P7O3sExQpBJ2dgXB0G5g/8PRVfBBsmQIc3r83ranEQTALLR3Vko/fnCIVqmH+mPw==",
+          "dev": true
+        },
+        "@typescript-eslint/typescript-estree": {
+          "version": "4.15.1",
+          "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.15.1.tgz",
+          "integrity": "sha512-z8MN3CicTEumrWAEB2e2CcoZa3KP9+SMYLIA2aM49XW3cWIaiVSOAGq30ffR5XHxRirqE90fgLw3e6WmNx5uNw==",
+          "dev": true,
+          "requires": {
+            "@typescript-eslint/types": "4.15.1",
+            "@typescript-eslint/visitor-keys": "4.15.1",
+            "debug": "^4.1.1",
+            "globby": "^11.0.1",
+            "is-glob": "^4.0.1",
+            "semver": "^7.3.2",
+            "tsutils": "^3.17.1"
+          }
+        },
+        "@typescript-eslint/visitor-keys": {
+          "version": "4.15.1",
+          "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.15.1.tgz",
+          "integrity": "sha512-tYzaTP9plooRJY8eNlpAewTOqtWW/4ff/5wBjNVaJ0S0wC4Gpq/zDVRTJa5bq2v1pCNQ08xxMCndcvR+h7lMww==",
+          "dev": true,
+          "requires": {
+            "@typescript-eslint/types": "4.15.1",
+            "eslint-visitor-keys": "^2.0.0"
+          }
+        },
         "debug": {
           "version": "4.3.1",
           "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
@@ -3148,17 +3237,58 @@
       }
     },
     "@typescript-eslint/parser": {
-      "version": "4.14.1",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.14.1.tgz",
-      "integrity": "sha512-mL3+gU18g9JPsHZuKMZ8Z0Ss9YP1S5xYZ7n68Z98GnPq02pYNQuRXL85b9GYhl6jpdvUc45Km7hAl71vybjUmw==",
+      "version": "4.15.1",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.15.1.tgz",
+      "integrity": "sha512-V8eXYxNJ9QmXi5ETDguB7O9diAXlIyS+e3xzLoP/oVE4WCAjssxLIa0mqCLsCGXulYJUfT+GV70Jv1vHsdKwtA==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/scope-manager": "4.14.1",
-        "@typescript-eslint/types": "4.14.1",
-        "@typescript-eslint/typescript-estree": "4.14.1",
+        "@typescript-eslint/scope-manager": "4.15.1",
+        "@typescript-eslint/types": "4.15.1",
+        "@typescript-eslint/typescript-estree": "4.15.1",
         "debug": "^4.1.1"
       },
       "dependencies": {
+        "@typescript-eslint/scope-manager": {
+          "version": "4.15.1",
+          "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.15.1.tgz",
+          "integrity": "sha512-ibQrTFcAm7yG4C1iwpIYK7vDnFg+fKaZVfvyOm3sNsGAerKfwPVFtYft5EbjzByDJ4dj1WD8/34REJfw/9wdVA==",
+          "dev": true,
+          "requires": {
+            "@typescript-eslint/types": "4.15.1",
+            "@typescript-eslint/visitor-keys": "4.15.1"
+          }
+        },
+        "@typescript-eslint/types": {
+          "version": "4.15.1",
+          "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.15.1.tgz",
+          "integrity": "sha512-iGsaUyWFyLz0mHfXhX4zO6P7O3sExQpBJ2dgXB0G5g/8PRVfBBsmQIc3r83ranEQTALLR3Vko/fnCIVqmH+mPw==",
+          "dev": true
+        },
+        "@typescript-eslint/typescript-estree": {
+          "version": "4.15.1",
+          "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.15.1.tgz",
+          "integrity": "sha512-z8MN3CicTEumrWAEB2e2CcoZa3KP9+SMYLIA2aM49XW3cWIaiVSOAGq30ffR5XHxRirqE90fgLw3e6WmNx5uNw==",
+          "dev": true,
+          "requires": {
+            "@typescript-eslint/types": "4.15.1",
+            "@typescript-eslint/visitor-keys": "4.15.1",
+            "debug": "^4.1.1",
+            "globby": "^11.0.1",
+            "is-glob": "^4.0.1",
+            "semver": "^7.3.2",
+            "tsutils": "^3.17.1"
+          }
+        },
+        "@typescript-eslint/visitor-keys": {
+          "version": "4.15.1",
+          "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.15.1.tgz",
+          "integrity": "sha512-tYzaTP9plooRJY8eNlpAewTOqtWW/4ff/5wBjNVaJ0S0wC4Gpq/zDVRTJa5bq2v1pCNQ08xxMCndcvR+h7lMww==",
+          "dev": true,
+          "requires": {
+            "@typescript-eslint/types": "4.15.1",
+            "eslint-visitor-keys": "^2.0.0"
+          }
+        },
         "debug": {
           "version": "4.3.1",
           "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
@@ -4084,8 +4214,7 @@
     "asynckit": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
-      "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
-      "dev": true
+      "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
     },
     "at-least-node": {
       "version": "1.0.0",
@@ -5039,6 +5168,7 @@
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
       "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+      "dev": true,
       "requires": {
         "function-bind": "^1.1.1",
         "get-intrinsic": "^1.0.2"
@@ -5563,7 +5693,6 @@
       "version": "1.0.8",
       "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
       "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
-      "dev": true,
       "requires": {
         "delayed-stream": "~1.0.0"
       }
@@ -6321,9 +6450,9 @@
       }
     },
     "date-fns": {
-      "version": "2.16.1",
-      "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.16.1.tgz",
-      "integrity": "sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ=="
+      "version": "2.17.0",
+      "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.17.0.tgz",
+      "integrity": "sha512-ZEhqxUtEZeGgg9eHNSOAJ8O9xqSgiJdrL0lzSSfMF54x6KXWJiOH/xntSJ9YomJPrYH/p08t6gWjGWq1SDJlSA=="
     },
     "debug": {
       "version": "2.6.9",
@@ -6361,6 +6490,7 @@
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz",
       "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==",
+      "dev": true,
       "requires": {
         "is-arguments": "^1.0.4",
         "is-date-object": "^1.0.1",
@@ -6396,6 +6526,7 @@
       "version": "1.1.3",
       "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
       "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
+      "dev": true,
       "requires": {
         "object-keys": "^1.0.12"
       }
@@ -6512,8 +6643,7 @@
     "delayed-stream": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
-      "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
-      "dev": true
+      "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
     },
     "delegates": {
       "version": "1.0.0",
@@ -7186,12 +7316,12 @@
       }
     },
     "eslint": {
-      "version": "7.18.0",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.18.0.tgz",
-      "integrity": "sha512-fbgTiE8BfUJZuBeq2Yi7J3RB3WGUQ9PNuNbmgi6jt9Iv8qrkxfy19Ds3OpL1Pm7zg3BtTVhvcUZbIRQ0wmSjAQ==",
+      "version": "7.20.0",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.20.0.tgz",
+      "integrity": "sha512-qGi0CTcOGP2OtCQBgWZlQjcTuP0XkIpYFj25XtRTQSHC+umNnp7UMshr2G8SLsRFYDdAPFeHOsiteadmMH02Yw==",
       "dev": true,
       "requires": {
-        "@babel/code-frame": "^7.0.0",
+        "@babel/code-frame": "7.12.11",
         "@eslint/eslintrc": "^0.3.0",
         "ajv": "^6.10.0",
         "chalk": "^4.0.0",
@@ -7203,7 +7333,7 @@
         "eslint-utils": "^2.1.0",
         "eslint-visitor-keys": "^2.0.0",
         "espree": "^7.3.1",
-        "esquery": "^1.2.0",
+        "esquery": "^1.4.0",
         "esutils": "^2.0.2",
         "file-entry-cache": "^6.0.0",
         "functional-red-black-tree": "^1.0.1",
@@ -7276,107 +7406,14 @@
       }
     },
     "eslint-config-airbnb-typescript": {
-      "version": "12.0.0",
-      "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-12.0.0.tgz",
-      "integrity": "sha512-TUCVru1Z09eKnVAX5i3XoNzjcCOU3nDQz2/jQGkg1jVYm+25fKClveziSl16celfCq+npU0MBPW/ZnXdGFZ9lw==",
+      "version": "12.3.1",
+      "resolved": "https://registry.npmjs.org/eslint-config-airbnb-typescript/-/eslint-config-airbnb-typescript-12.3.1.tgz",
+      "integrity": "sha512-ql/Pe6/hppYuRp4m3iPaHJqkBB7dgeEmGPQ6X0UNmrQOfTF+dXw29/ZjU2kQ6RDoLxaxOA+Xqv07Vbef6oVTWw==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/parser": "4.4.1",
-        "eslint-config-airbnb": "18.2.0",
-        "eslint-config-airbnb-base": "14.2.0"
-      },
-      "dependencies": {
-        "@typescript-eslint/parser": {
-          "version": "4.4.1",
-          "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.4.1.tgz",
-          "integrity": "sha512-S0fuX5lDku28Au9REYUsV+hdJpW/rNW0gWlc4SXzF/kdrRaAVX9YCxKpziH7djeWT/HFAjLZcnY7NJD8xTeUEg==",
-          "dev": true,
-          "requires": {
-            "@typescript-eslint/scope-manager": "4.4.1",
-            "@typescript-eslint/types": "4.4.1",
-            "@typescript-eslint/typescript-estree": "4.4.1",
-            "debug": "^4.1.1"
-          }
-        },
-        "@typescript-eslint/scope-manager": {
-          "version": "4.4.1",
-          "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.4.1.tgz",
-          "integrity": "sha512-2oD/ZqD4Gj41UdFeWZxegH3cVEEH/Z6Bhr/XvwTtGv66737XkR4C9IqEkebCuqArqBJQSj4AgNHHiN1okzD/wQ==",
-          "dev": true,
-          "requires": {
-            "@typescript-eslint/types": "4.4.1",
-            "@typescript-eslint/visitor-keys": "4.4.1"
-          }
-        },
-        "@typescript-eslint/types": {
-          "version": "4.4.1",
-          "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.4.1.tgz",
-          "integrity": "sha512-KNDfH2bCyax5db+KKIZT4rfA8rEk5N0EJ8P0T5AJjo5xrV26UAzaiqoJCxeaibqc0c/IvZxp7v2g3difn2Pn3w==",
-          "dev": true
-        },
-        "@typescript-eslint/typescript-estree": {
-          "version": "4.4.1",
-          "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.4.1.tgz",
-          "integrity": "sha512-wP/V7ScKzgSdtcY1a0pZYBoCxrCstLrgRQ2O9MmCUZDtmgxCO/TCqOTGRVwpP4/2hVfqMz/Vw1ZYrG8cVxvN3g==",
-          "dev": true,
-          "requires": {
-            "@typescript-eslint/types": "4.4.1",
-            "@typescript-eslint/visitor-keys": "4.4.1",
-            "debug": "^4.1.1",
-            "globby": "^11.0.1",
-            "is-glob": "^4.0.1",
-            "lodash": "^4.17.15",
-            "semver": "^7.3.2",
-            "tsutils": "^3.17.1"
-          }
-        },
-        "@typescript-eslint/visitor-keys": {
-          "version": "4.4.1",
-          "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.4.1.tgz",
-          "integrity": "sha512-H2JMWhLaJNeaylSnMSQFEhT/S/FsJbebQALmoJxMPMxLtlVAMy2uJP/Z543n9IizhjRayLSqoInehCeNW9rWcw==",
-          "dev": true,
-          "requires": {
-            "@typescript-eslint/types": "4.4.1",
-            "eslint-visitor-keys": "^2.0.0"
-          }
-        },
-        "debug": {
-          "version": "4.3.1",
-          "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
-          "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
-          "dev": true,
-          "requires": {
-            "ms": "2.1.2"
-          }
-        },
-        "eslint-config-airbnb": {
-          "version": "18.2.0",
-          "resolved": "https://registry.npmjs.org/eslint-config-airbnb/-/eslint-config-airbnb-18.2.0.tgz",
-          "integrity": "sha512-Fz4JIUKkrhO0du2cg5opdyPKQXOI2MvF8KUvN2710nJMT6jaRUpRE2swrJftAjVGL7T1otLM5ieo5RqS1v9Udg==",
-          "dev": true,
-          "requires": {
-            "eslint-config-airbnb-base": "^14.2.0",
-            "object.assign": "^4.1.0",
-            "object.entries": "^1.1.2"
-          }
-        },
-        "eslint-config-airbnb-base": {
-          "version": "14.2.0",
-          "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.0.tgz",
-          "integrity": "sha512-Snswd5oC6nJaevs3nZoLSTvGJBvzTfnBqOIArkf3cbyTyq9UD79wOk8s+RiL6bhca0p/eRO6veczhf6A/7Jy8Q==",
-          "dev": true,
-          "requires": {
-            "confusing-browser-globals": "^1.0.9",
-            "object.assign": "^4.1.0",
-            "object.entries": "^1.1.2"
-          }
-        },
-        "ms": {
-          "version": "2.1.2",
-          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
-          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
-          "dev": true
-        }
+        "@typescript-eslint/parser": "^4.4.1",
+        "eslint-config-airbnb": "^18.2.0",
+        "eslint-config-airbnb-base": "^14.2.0"
       }
     },
     "eslint-config-prettier": {
@@ -7407,9 +7444,9 @@
       }
     },
     "eslint-import-resolver-typescript": {
-      "version": "2.3.0",
-      "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.3.0.tgz",
-      "integrity": "sha512-MHSXvmj5e0SGOOBhBbt7C+fWj1bJbtSYFAD85Xeg8nvUtuooTod2HQb8bfhE9f5QyyNxEfgzqOYFCvmdDIcCuw==",
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-2.4.0.tgz",
+      "integrity": "sha512-useJKURidCcldRLCNKWemr1fFQL1SzB3G4a0li6lFGvlc5xGe1hY343bvG07cbpCzPuM/lK19FIJB3XGFSkplA==",
       "requires": {
         "debug": "^4.1.1",
         "glob": "^7.1.6",
@@ -7748,9 +7785,9 @@
       }
     },
     "esquery": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz",
-      "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==",
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
+      "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
       "dev": true,
       "requires": {
         "estraverse": "^5.1.0"
@@ -8201,6 +8238,82 @@
         "bser": "2.1.1"
       }
     },
+    "fetch-mock": {
+      "version": "9.11.0",
+      "resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-9.11.0.tgz",
+      "integrity": "sha512-PG1XUv+x7iag5p/iNHD4/jdpxL9FtVSqRMUQhPab4hVDt80T1MH5ehzVrL2IdXO9Q2iBggArFvPqjUbHFuI58Q==",
+      "dev": true,
+      "requires": {
+        "@babel/core": "^7.0.0",
+        "@babel/runtime": "^7.0.0",
+        "core-js": "^3.0.0",
+        "debug": "^4.1.1",
+        "glob-to-regexp": "^0.4.0",
+        "is-subset": "^0.1.1",
+        "lodash.isequal": "^4.5.0",
+        "path-to-regexp": "^2.2.1",
+        "querystring": "^0.2.0",
+        "whatwg-url": "^6.5.0"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "4.3.1",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
+          "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
+          "dev": true,
+          "requires": {
+            "ms": "2.1.2"
+          }
+        },
+        "ms": {
+          "version": "2.1.2",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+          "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+          "dev": true
+        },
+        "path-to-regexp": {
+          "version": "2.4.0",
+          "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.4.0.tgz",
+          "integrity": "sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w==",
+          "dev": true
+        },
+        "tr46": {
+          "version": "1.0.1",
+          "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
+          "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=",
+          "dev": true,
+          "requires": {
+            "punycode": "^2.1.0"
+          }
+        },
+        "webidl-conversions": {
+          "version": "4.0.2",
+          "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
+          "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==",
+          "dev": true
+        },
+        "whatwg-url": {
+          "version": "6.5.0",
+          "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz",
+          "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==",
+          "dev": true,
+          "requires": {
+            "lodash.sortby": "^4.7.0",
+            "tr46": "^1.0.1",
+            "webidl-conversions": "^4.0.2"
+          }
+        }
+      }
+    },
+    "fetch-mock-jest": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/fetch-mock-jest/-/fetch-mock-jest-1.5.1.tgz",
+      "integrity": "sha512-+utwzP8C+Pax1GSka3nFXILWMY3Er2L+s090FOgqVNrNCPp0fDqgXnAHAJf12PLHi0z4PhcTaZNTz8e7K3fjqQ==",
+      "dev": true,
+      "requires": {
+        "fetch-mock": "^9.11.0"
+      }
+    },
     "figgy-pudding": {
       "version": "3.5.2",
       "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz",
@@ -8844,6 +8957,7 @@
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.0.tgz",
       "integrity": "sha512-M11rgtQp5GZMZzDL7jLTNxbDfurpzuau5uqRWDPvlHjfvg3TdScAZo96GLvhMjImrmR8uAt0FS2RLoMrfWGKlg==",
+      "dev": true,
       "requires": {
         "function-bind": "^1.1.1",
         "has": "^1.0.3",
@@ -8931,6 +9045,12 @@
         "is-glob": "^4.0.1"
       }
     },
+    "glob-to-regexp": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
+      "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
+      "dev": true
+    },
     "global-modules": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz",
@@ -9099,7 +9219,8 @@
     "has-symbols": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz",
-      "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg=="
+      "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==",
+      "dev": true
     },
     "has-unicode": {
       "version": "2.0.1",
@@ -9923,6 +10044,7 @@
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz",
       "integrity": "sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==",
+      "dev": true,
       "requires": {
         "call-bind": "^1.0.0"
       }
@@ -10017,7 +10139,8 @@
     "is-date-object": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz",
-      "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g=="
+      "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==",
+      "dev": true
     },
     "is-descriptor": {
       "version": "0.1.6",
@@ -10166,6 +10289,7 @@
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz",
       "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==",
+      "dev": true,
       "requires": {
         "has-symbols": "^1.0.1"
       }
@@ -11857,9 +11981,9 @@
       "dev": true
     },
     "lint-staged": {
-      "version": "10.5.3",
-      "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.5.3.tgz",
-      "integrity": "sha512-TanwFfuqUBLufxCc3RUtFEkFraSPNR3WzWcGF39R3f2J7S9+iF9W0KTVLfSy09lYGmZS5NDCxjNvhGMSJyFCWg==",
+      "version": "10.5.4",
+      "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-10.5.4.tgz",
+      "integrity": "sha512-EechC3DdFic/TdOPgj/RB3FicqE6932LTHCUm0Y2fsD9KGlLB+RwJl2q1IYBIvEsKzDOgn0D4gll+YxG5RsrKg==",
       "dev": true,
       "requires": {
         "chalk": "^4.1.0",
@@ -11950,9 +12074,9 @@
       }
     },
     "listr2": {
-      "version": "3.3.0",
-      "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.3.0.tgz",
-      "integrity": "sha512-G9IFI/m65icgVlifS0wMQnvn35/8VJGzEb3crpE4NnaegQYQOn/wP7yqi9TTJQ/eoxme4UaPbffBK1XqKP/DOg==",
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.3.1.tgz",
+      "integrity": "sha512-8Zoxe7s/8nNr4bJ8bdAduHD8uJce+exmMmUWTXlq0WuUdffnH3muisHPHPFtW2vvOfohIsq7FGCaguUxN/h3Iw==",
       "dev": true,
       "requires": {
         "chalk": "^4.1.0",
@@ -12063,6 +12187,12 @@
       "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=",
       "dev": true
     },
+    "lodash.isplainobject": {
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+      "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=",
+      "dev": true
+    },
     "lodash.memoize": {
       "version": "4.1.2",
       "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@@ -12446,14 +12576,12 @@
     "mime-db": {
       "version": "1.45.0",
       "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz",
-      "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==",
-      "dev": true
+      "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w=="
     },
     "mime-types": {
       "version": "2.1.28",
       "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz",
       "integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==",
-      "dev": true,
       "requires": {
         "mime-db": "1.45.0"
       }
@@ -12799,6 +12927,11 @@
       "dev": true,
       "optional": true
     },
+    "node-fetch": {
+      "version": "2.6.1",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
+      "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
+    },
     "node-forge": {
       "version": "0.10.0",
       "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
@@ -13213,6 +13346,7 @@
       "version": "1.1.4",
       "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.4.tgz",
       "integrity": "sha512-1ZvAZ4wlF7IyPVOcE1Omikt7UpaFlOQq0HlSti+ZvDH3UiD2brwGMwDbyV43jao2bKJ+4+WdPJHSd7kgzKYVqg==",
+      "dev": true,
       "requires": {
         "call-bind": "^1.0.0",
         "define-properties": "^1.1.3"
@@ -13221,7 +13355,8 @@
     "object-keys": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
-      "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
+      "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+      "dev": true
     },
     "object-visit": {
       "version": "1.0.1",
@@ -15454,9 +15589,9 @@
       }
     },
     "react-datepicker": {
-      "version": "3.4.1",
-      "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-3.4.1.tgz",
-      "integrity": "sha512-ASyVb7UmVx1vzeITidD7Cr/EXRXhKyjjbSkBndPc1MipYq4rqQ3eMFgvRQzpsXc3JmIMFgICm7nmN6Otc1GE/Q==",
+      "version": "3.5.0",
+      "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-3.5.0.tgz",
+      "integrity": "sha512-iTRz15aG9lEF7tJVjPcGWvfALb2/RjqHVTvQO7J0DEvGSGZnTJpHlkTrBGBjJ9+i5zM4zcRGoStpjhaezS9p1g==",
       "requires": {
         "classnames": "^2.2.6",
         "date-fns": "^2.0.1",
@@ -15670,9 +15805,9 @@
       "dev": true
     },
     "react-hook-form": {
-      "version": "4.10.2",
-      "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-4.10.2.tgz",
-      "integrity": "sha512-Ule/KqHBwUvuubqGC4WDvOARS6VjlULSS+WHspgQ5FhFKR4ytHDc4AMpjVfnv+Wbz2TEbMp9/ZHmuZsUksPCiA=="
+      "version": "6.15.1",
+      "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-6.15.1.tgz",
+      "integrity": "sha512-bL0LQuQ3OlM3JYfbacKtBPLOHhmgYz8Lj6ivMrvu2M6e1wnt4sbGRtPEPYCc/8z3WDbjrMwfAfLX92OsB65pFA=="
     },
     "react-is": {
       "version": "17.0.1",
@@ -15705,13 +15840,12 @@
       "integrity": "sha512-7i2L3ef+0ILXpL6P+Hg304eCQswh4jl3ynwR71BSlMU49PE2uk31k8B2GkP6yE9s2D4jTGKnzuSpzWxu4YxfQQ=="
     },
     "react-popper": {
-      "version": "1.3.7",
-      "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.7.tgz",
-      "integrity": "sha512-nmqYTx7QVjCm3WUZLeuOomna138R1luC4EqkW3hxJUrAe+3eNz3oFCLYdnPwILfn0mX1Ew2c3wctrjlUMYYUww==",
+      "version": "1.3.9",
+      "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.9.tgz",
+      "integrity": "sha512-xKQMQmUMOHAtURz2hj1a0f+M2hyMVWC20hJ09l8UNO2aSCK48kWfdwAuwdCauER0Vo8gV4Qr9G0GobP6KG9AIQ==",
       "requires": {
         "@babel/runtime": "^7.1.2",
         "create-react-context": "^0.3.0",
-        "deep-equal": "^1.1.1",
         "popper.js": "^1.14.4",
         "prop-types": "^15.6.1",
         "typed-styles": "^0.0.7",
@@ -16004,6 +16138,15 @@
         "symbol-observable": "^1.2.0"
       }
     },
+    "redux-mock-store": {
+      "version": "1.5.4",
+      "resolved": "https://registry.npmjs.org/redux-mock-store/-/redux-mock-store-1.5.4.tgz",
+      "integrity": "sha512-xmcA0O/tjCLXhh9Fuiq6pMrJCwFRaouA8436zcikdIpYWWCjU76CRk+i2bHx8EeiSiMGnB85/lZdU3wIJVXHTA==",
+      "dev": true,
+      "requires": {
+        "lodash.isplainobject": "^4.0.6"
+      }
+    },
     "redux-thunk": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.3.0.tgz",
@@ -16064,6 +16207,7 @@
       "version": "1.3.1",
       "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz",
       "integrity": "sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==",
+      "dev": true,
       "requires": {
         "call-bind": "^1.0.2",
         "define-properties": "^1.1.3"
@@ -18273,9 +18417,9 @@
       },
       "dependencies": {
         "ajv": {
-          "version": "7.0.3",
-          "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.0.3.tgz",
-          "integrity": "sha512-R50QRlXSxqXcQP5SvKUrw8VZeypvo12i2IX0EeR5PiZ7bEKeHWgzgo264LDadUsCU42lTJVhFikTqJwNeH34gQ==",
+          "version": "7.1.1",
+          "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.1.1.tgz",
+          "integrity": "sha512-ga/aqDYnUy/o7vbsRTFhhTsNeXiYb5JWDIcRIeZfwRNCefwjNTVYCGdGSUrEmiu3yDK3vFvNbgJxvrQW4JXrYQ==",
           "dev": true,
           "requires": {
             "fast-deep-equal": "^3.1.1",
@@ -18695,9 +18839,9 @@
       "dev": true
     },
     "ts-jest": {
-      "version": "26.4.4",
-      "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.4.4.tgz",
-      "integrity": "sha512-3lFWKbLxJm34QxyVNNCgXX1u4o/RV0myvA2y2Bxm46iGIjKlaY0own9gIckbjZJPn+WaJEnfPPJ20HHGpoq4yg==",
+      "version": "26.5.1",
+      "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.1.tgz",
+      "integrity": "sha512-G7Rmo3OJMvlqE79amJX8VJKDiRcd7/r61wh9fnvvG8cAjhA9edklGw/dCxRSQmfZ/z8NDums5srSVgwZos1qfg==",
       "dev": true,
       "requires": {
         "@types/jest": "26.x",
@@ -18706,7 +18850,7 @@
         "fast-json-stable-stringify": "2.x",
         "jest-util": "^26.1.0",
         "json5": "2.x",
-        "lodash.memoize": "4.x",
+        "lodash": "4.x",
         "make-error": "1.x",
         "mkdirp": "1.x",
         "semver": "7.x",
@@ -18714,9 +18858,9 @@
       },
       "dependencies": {
         "json5": {
-          "version": "2.1.3",
-          "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz",
-          "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==",
+          "version": "2.2.0",
+          "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz",
+          "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==",
           "dev": true,
           "requires": {
             "minimist": "^1.2.5"
@@ -18854,9 +18998,9 @@
       "integrity": "sha512-bna6Yi1pRznoo6Bz1cE6btB/Yy8Xywytyfrzu/wc+NFW3ZF0I+2iCGImhBsoYYCOWuICtRO4yHcnDlzgo1AdNg=="
     },
     "typescript": {
-      "version": "4.1.3",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.3.tgz",
-      "integrity": "sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==",
+      "version": "4.1.5",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.5.tgz",
+      "integrity": "sha512-6OSu9PTIzmn9TCDiovULTnET6BgXtDYL4Gg4szY+cGsc3JP1dQL8qvE8kShTRx1NIw4Q9IBHlwODjkjWEtMUyA==",
       "dev": true
     },
     "unicode-canonical-property-names-ecmascript": {
@@ -20972,9 +21116,9 @@
       }
     },
     "yargs-parser": {
-      "version": "20.2.4",
-      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
-      "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==",
+      "version": "20.2.5",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.5.tgz",
+      "integrity": "sha512-jYRGS3zWy20NtDtK2kBgo/TlAoy5YUuhD9/LZ7z7W4j1Fdw2cqD0xEEclf8fxc8xjD6X5Qr+qQQwCEsP8iRiYg==",
       "dev": true
     },
     "yn": {

+ 20 - 14
kafka-ui-react-app/package.json

@@ -3,19 +3,22 @@
   "version": "0.1.0",
   "private": true,
   "dependencies": {
+    "@hookform/error-message": "0.0.5",
+    "@types/node-fetch": "^2.5.8",
     "bulma": "^0.9.2",
     "bulma-switch": "^2.0.0",
     "classnames": "^2.2.6",
-    "date-fns": "^2.16.1",
+    "date-fns": "^2.17.0",
     "eslint-import-resolver-node": "^0.3.4",
-    "eslint-import-resolver-typescript": "^2.3.0",
+    "eslint-import-resolver-typescript": "^2.4.0",
     "immer": "^8.0.1",
     "lodash": "^4.17.20",
+    "node-fetch": "^2.6.1",
     "pretty-ms": "^6.0.1",
     "react": "^17.0.1",
-    "react-datepicker": "^3.3.0",
+    "react-datepicker": "^3.5.0",
     "react-dom": "^17.0.1",
-    "react-hook-form": "^4.10.2",
+    "react-hook-form": "^6.15.1",
     "react-json-tree": "^0.13.0",
     "react-multi-select-component": "^2.0.14",
     "react-redux": "^7.2.2",
@@ -73,24 +76,25 @@
     "@types/enzyme": "^3.10.8",
     "@types/jest": "^26.0.20",
     "@types/lodash": "^4.14.165",
-    "@types/node": "^12.19.8",
-    "@types/react": "^17.0.0",
+    "@types/node": "^12.20.2",
+    "@types/react": "^17.0.2",
     "@types/react-datepicker": "^3.1.1",
-    "@types/react-dom": "^17.0.0",
+    "@types/react-dom": "^17.0.1",
     "@types/react-redux": "^7.1.11",
     "@types/react-router-dom": "^5.1.6",
     "@types/redux": "^3.6.0",
+    "@types/redux-mock-store": "^1.0.2",
     "@types/redux-thunk": "^2.1.0",
     "@types/uuid": "^8.3.0",
-    "@typescript-eslint/eslint-plugin": "^4.9.0",
-    "@typescript-eslint/parser": "^4.9.0",
+    "@typescript-eslint/eslint-plugin": "^4.15.1",
+    "@typescript-eslint/parser": "^4.15.1",
     "@wojtekmaj/enzyme-adapter-react-17": "^0.4.1",
     "dotenv": "^8.2.0",
     "enzyme": "^3.11.0",
     "enzyme-to-json": "^3.6.1",
-    "eslint": "^7.14.0",
+    "eslint": "^7.20.0",
     "eslint-config-airbnb": "^18.2.1",
-    "eslint-config-airbnb-typescript": "^12.0.0",
+    "eslint-config-airbnb-typescript": "^12.3.1",
     "eslint-config-prettier": "^6.15.0",
     "eslint-plugin-import": "^2.22.1",
     "eslint-plugin-jsx-a11y": "^6.4.1",
@@ -98,14 +102,16 @@
     "eslint-plugin-react": "^7.21.5",
     "eslint-plugin-react-hooks": "^2.5.1",
     "esprint": "^0.6.0",
+    "fetch-mock-jest": "^1.5.1",
     "husky": "^4.3.0",
-    "lint-staged": "^10.5.2",
+    "lint-staged": "^10.5.4",
     "node-sass": "^4.14.1",
     "prettier": "^2.2.1",
     "react-scripts": "4.0.2",
-    "ts-jest": "^26.4.4",
+    "redux-mock-store": "^1.5.4",
+    "ts-jest": "^26.5.1",
     "ts-node": "^9.1.1",
-    "typescript": "~4.1.2"
+    "typescript": "^4.1.5"
   },
   "engines": {
     "node": ">=14.15.4"

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

@@ -8,7 +8,7 @@ import {
   CleanupPolicy,
 } from 'redux/interfaces';
 import { TopicConfig } from 'generated-sources';
-import { useForm, FormContext } from 'react-hook-form';
+import { useForm, FormProvider } from 'react-hook-form';
 import { camelCase } from 'lodash';
 
 import TopicForm from '../shared/Form/TopicForm';
@@ -127,7 +127,7 @@ const Edit: React.FC<Props> = ({
 
       <div className="box">
         {/* eslint-disable-next-line react/jsx-props-no-spreading */}
-        <FormContext {...methods}>
+        <FormProvider {...methods}>
           <TopicForm
             topicName={topicName}
             config={config}
@@ -135,7 +135,7 @@ const Edit: React.FC<Props> = ({
             isEditing
             onSubmit={methods.handleSubmit(onSubmit)}
           />
-        </FormContext>
+        </FormProvider>
       </div>
     </div>
   );

+ 3 - 3
kafka-ui-react-app/src/components/Topics/New/New.tsx

@@ -1,6 +1,6 @@
 import React from 'react';
 import { ClusterName, TopicName, TopicFormDataRaw } from 'redux/interfaces';
-import { useForm, FormContext } from 'react-hook-form';
+import { useForm, FormProvider } from 'react-hook-form';
 
 import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
 import { clusterTopicsPath } from 'lib/paths';
@@ -54,12 +54,12 @@ const New: React.FC<Props> = ({
 
       <div className="box">
         {/* eslint-disable react/jsx-props-no-spreading */}
-        <FormContext {...methods}>
+        <FormProvider {...methods}>
           <TopicForm
             isSubmitting={isSubmitting}
             onSubmit={methods.handleSubmit(onSubmit)}
           />
-        </FormContext>
+        </FormProvider>
       </div>
     </div>
   );

+ 5 - 4
kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/CustomParamSelect.tsx

@@ -1,6 +1,7 @@
 import React from 'react';
-import { useFormContext, ErrorMessage } from 'react-hook-form';
+import { useFormContext } from 'react-hook-form';
 import { TopicConfigValue } from 'redux/interfaces';
+import { ErrorMessage } from '@hookform/error-message';
 import CustomParamOptions from './CustomParamOptions';
 import { INDEX_PREFIX } from './CustomParams';
 
@@ -19,11 +20,11 @@ const CustomParamSelect: React.FC<Props> = ({
   existingFields,
   onNameChange,
 }) => {
-  const { register, errors, getValues, triggerValidation } = useFormContext();
+  const { register, errors, getValues, trigger } = useFormContext();
   const optInputName = `${index}[name]`;
 
   const selectedMustBeUniq = (selected: string) => {
-    const values = getValues({ nest: true });
+    const values = getValues();
     const customParamsValues: TopicConfigValue = values.customParams;
 
     const valid = !Object.entries(customParamsValues).some(
@@ -40,7 +41,7 @@ const CustomParamSelect: React.FC<Props> = ({
   const onChange = (inputName: string) => (
     event: React.ChangeEvent<HTMLSelectElement>
   ) => {
-    triggerValidation(inputName);
+    trigger(inputName);
     onNameChange(index, event.target.value);
   };
 

+ 3 - 3
kafka-ui-react-app/src/components/Topics/shared/Form/CustomParams/CustomParamValue.tsx

@@ -1,6 +1,7 @@
 import React from 'react';
-import { useFormContext, ErrorMessage } from 'react-hook-form';
+import { useFormContext } from 'react-hook-form';
 import { TopicConfig } from 'generated-sources';
+import { ErrorMessage } from '@hookform/error-message';
 import CUSTOM_PARAMS_OPTIONS from './customParamsOptions';
 
 interface Props {
@@ -25,8 +26,7 @@ const CustomParamValue: React.FC<Props> = ({
     if (selectedParamName && !defaultValue) {
       setValue(
         valInputName,
-        CUSTOM_PARAMS_OPTIONS[selectedParamName].defaultValue,
-        true
+        CUSTOM_PARAMS_OPTIONS[selectedParamName].defaultValue
       );
     }
   }, [selectedParamName, setValue, valInputName]);

+ 3 - 1
kafka-ui-react-app/src/components/Topics/shared/Form/TimeToRetain.tsx

@@ -1,6 +1,8 @@
 import React from 'react';
 import prettyMilliseconds from 'pretty-ms';
-import { useFormContext, ErrorMessage } from 'react-hook-form';
+import { useFormContext } from 'react-hook-form';
+import { ErrorMessage } from '@hookform/error-message';
+
 import { MILLISECONDS_IN_WEEK, MILLISECONDS_IN_SECOND } from 'lib/constants';
 import TimeToRetainBtns from './TimeToRetainBtns';
 

+ 2 - 1
kafka-ui-react-app/src/components/Topics/shared/Form/TopicForm.tsx

@@ -1,7 +1,8 @@
 import React from 'react';
-import { useFormContext, ErrorMessage } from 'react-hook-form';
+import { useFormContext } from 'react-hook-form';
 import { TOPIC_NAME_VALIDATION_PATTERN, BYTES_IN_GB } from 'lib/constants';
 import { CleanupPolicy, TopicName, TopicConfigByName } from 'redux/interfaces';
+import { ErrorMessage } from '@hookform/error-message';
 import CustomParamsContainer from './CustomParams/CustomParamsContainer';
 import TimeToRetain from './TimeToRetain';
 

+ 28 - 0
kafka-ui-react-app/src/redux/actions/__test__/actions.spec.ts

@@ -0,0 +1,28 @@
+import * as actions from '../actions';
+
+describe('Actions', () => {
+  describe('fetchClusterStatsAction', () => {
+    it('creates an REQUEST action', () => {
+      expect(actions.fetchClusterStatsAction.request()).toEqual({
+        type: 'GET_CLUSTER_STATUS__REQUEST',
+      });
+    });
+
+    it('creates an SUCCESS action', () => {
+      expect(
+        actions.fetchClusterStatsAction.success({ brokerCount: 1 })
+      ).toEqual({
+        type: 'GET_CLUSTER_STATUS__SUCCESS',
+        payload: {
+          brokerCount: 1,
+        },
+      });
+    });
+
+    it('creates an FAILURE action', () => {
+      expect(actions.fetchClusterStatsAction.failure()).toEqual({
+        type: 'GET_CLUSTER_STATUS__FAILURE',
+      });
+    });
+  });
+});

+ 13 - 0
kafka-ui-react-app/src/redux/actions/__test__/fixtures.ts

@@ -0,0 +1,13 @@
+import { ClusterStats } from 'generated-sources';
+
+export const clusterStats: ClusterStats = {
+  brokerCount: 1,
+  zooKeeperStatus: 1,
+  activeControllers: 1,
+  onlinePartitionCount: 6,
+  offlinePartitionCount: 0,
+  inSyncReplicasCount: 6,
+  outOfSyncReplicasCount: 0,
+  underReplicatedPartitionCount: 0,
+  diskUsage: [{ brokerId: 1, segmentSize: 6538, segmentCount: 6 }],
+};

+ 52 - 0
kafka-ui-react-app/src/redux/actions/__test__/thunks.spec.ts

@@ -0,0 +1,52 @@
+import configureMockStore, {
+  MockStoreCreator,
+  MockStoreEnhanced,
+} from 'redux-mock-store';
+import thunk, { ThunkDispatch } from 'redux-thunk';
+import fetchMock from 'fetch-mock-jest';
+import { Middleware } from 'redux';
+import { RootState, Action } from 'redux/interfaces';
+import * as actions from 'redux/actions/actions';
+import * as thunks from 'redux/actions/thunks';
+import * as fixtures from './fixtures';
+
+const middlewares: Array<Middleware> = [thunk];
+type DispatchExts = ThunkDispatch<RootState, undefined, Action>;
+
+const mockStoreCreator: MockStoreCreator<
+  RootState,
+  DispatchExts
+> = configureMockStore<RootState, DispatchExts>(middlewares);
+
+const store: MockStoreEnhanced<RootState, DispatchExts> = mockStoreCreator();
+
+const clusterName = 'local';
+
+describe('Thunks', () => {
+  afterEach(() => {
+    fetchMock.restore();
+    store.clearActions();
+  });
+
+  describe('fetchClusterStats', () => {
+    it('creates GET_CLUSTER_STATUS__SUCCESS when fetching cluster stats', async () => {
+      fetchMock.getOnce(`/api/clusters/${clusterName}/stats`, {
+        body: fixtures.clusterStats,
+      });
+      await store.dispatch(thunks.fetchClusterStats(clusterName));
+      expect(store.getActions()).toEqual([
+        actions.fetchClusterStatsAction.request(),
+        actions.fetchClusterStatsAction.success(fixtures.clusterStats),
+      ]);
+    });
+
+    it('creates GET_CLUSTER_STATUS__FAILURE when fetching cluster stats', async () => {
+      fetchMock.getOnce(`/api/clusters/${clusterName}/stats`, 404);
+      await store.dispatch(thunks.fetchClusterStats(clusterName));
+      expect(store.getActions()).toEqual([
+        actions.fetchClusterStatsAction.request(),
+        actions.fetchClusterStatsAction.failure(),
+      ]);
+    });
+  });
+});

+ 14 - 14
kafka-ui-react-app/src/redux/actions/thunks.ts

@@ -8,7 +8,7 @@ import {
 } from 'generated-sources';
 import {
   ConsumerGroupID,
-  PromiseThunk,
+  PromiseThunkResult,
   ClusterName,
   BrokerId,
   TopicName,
@@ -23,7 +23,7 @@ import * as actions from './actions';
 const apiClientConf = new Configuration(BASE_PARAMS);
 const apiClient = new ApiClustersApi(apiClientConf);
 
-export const fetchClustersList = (): PromiseThunk<void> => async (dispatch) => {
+export const fetchClustersList = (): PromiseThunkResult => async (dispatch) => {
   dispatch(actions.fetchClusterListAction.request());
   try {
     const clusters: Cluster[] = await apiClient.getClusters();
@@ -35,7 +35,7 @@ export const fetchClustersList = (): PromiseThunk<void> => async (dispatch) => {
 
 export const fetchClusterStats = (
   clusterName: ClusterName
-): PromiseThunk<void> => async (dispatch) => {
+): PromiseThunkResult => async (dispatch) => {
   dispatch(actions.fetchClusterStatsAction.request());
   try {
     const payload = await apiClient.getClusterStats({ clusterName });
@@ -47,7 +47,7 @@ export const fetchClusterStats = (
 
 export const fetchClusterMetrics = (
   clusterName: ClusterName
-): PromiseThunk<void> => async (dispatch) => {
+): PromiseThunkResult => async (dispatch) => {
   dispatch(actions.fetchClusterMetricsAction.request());
   try {
     const payload = await apiClient.getClusterMetrics({ clusterName });
@@ -59,7 +59,7 @@ export const fetchClusterMetrics = (
 
 export const fetchBrokers = (
   clusterName: ClusterName
-): PromiseThunk<void> => async (dispatch) => {
+): PromiseThunkResult => async (dispatch) => {
   dispatch(actions.fetchBrokersAction.request());
   try {
     const payload = await apiClient.getBrokers({ clusterName });
@@ -72,7 +72,7 @@ export const fetchBrokers = (
 export const fetchBrokerMetrics = (
   clusterName: ClusterName,
   brokerId: BrokerId
-): PromiseThunk<void> => async (dispatch) => {
+): PromiseThunkResult => async (dispatch) => {
   dispatch(actions.fetchBrokerMetricsAction.request());
   try {
     const payload = await apiClient.getBrokersMetrics({
@@ -87,7 +87,7 @@ export const fetchBrokerMetrics = (
 
 export const fetchTopicsList = (
   clusterName: ClusterName
-): PromiseThunk<void> => async (dispatch) => {
+): PromiseThunkResult => async (dispatch) => {
   dispatch(actions.fetchTopicsListAction.request());
   try {
     const topics = await apiClient.getTopics({ clusterName });
@@ -101,7 +101,7 @@ export const fetchTopicMessages = (
   clusterName: ClusterName,
   topicName: TopicName,
   queryParams: Partial<TopicMessageQueryParams>
-): PromiseThunk<void> => async (dispatch) => {
+): PromiseThunkResult => async (dispatch) => {
   dispatch(actions.fetchTopicMessagesAction.request());
   try {
     const messages = await apiClient.getTopicMessages({
@@ -118,7 +118,7 @@ export const fetchTopicMessages = (
 export const fetchTopicDetails = (
   clusterName: ClusterName,
   topicName: TopicName
-): PromiseThunk<void> => async (dispatch) => {
+): PromiseThunkResult => async (dispatch) => {
   dispatch(actions.fetchTopicDetailsAction.request());
   try {
     const topicDetails = await apiClient.getTopicDetails({
@@ -139,7 +139,7 @@ export const fetchTopicDetails = (
 export const fetchTopicConfig = (
   clusterName: ClusterName,
   topicName: TopicName
-): PromiseThunk<void> => async (dispatch) => {
+): PromiseThunkResult => async (dispatch) => {
   dispatch(actions.fetchTopicConfigAction.request());
   try {
     const config = await apiClient.getTopicConfigs({ clusterName, topicName });
@@ -188,7 +188,7 @@ const formatTopicFormData = (form: TopicFormDataRaw): TopicFormData => {
 export const createTopic = (
   clusterName: ClusterName,
   form: TopicFormDataRaw
-): PromiseThunk<void> => async (dispatch) => {
+): PromiseThunkResult => async (dispatch) => {
   dispatch(actions.createTopicAction.request());
   try {
     const topic: Topic = await apiClient.createTopic({
@@ -204,7 +204,7 @@ export const createTopic = (
 export const updateTopic = (
   clusterName: ClusterName,
   form: TopicFormDataRaw
-): PromiseThunk<void> => async (dispatch) => {
+): PromiseThunkResult => async (dispatch) => {
   dispatch(actions.updateTopicAction.request());
   try {
     const topic: Topic = await apiClient.updateTopic({
@@ -220,7 +220,7 @@ export const updateTopic = (
 
 export const fetchConsumerGroupsList = (
   clusterName: ClusterName
-): PromiseThunk<void> => async (dispatch) => {
+): PromiseThunkResult => async (dispatch) => {
   dispatch(actions.fetchConsumerGroupsAction.request());
   try {
     const consumerGroups = await apiClient.getConsumerGroups({ clusterName });
@@ -233,7 +233,7 @@ export const fetchConsumerGroupsList = (
 export const fetchConsumerGroupDetails = (
   clusterName: ClusterName,
   consumerGroupID: ConsumerGroupID
-): PromiseThunk<void> => async (dispatch) => {
+): PromiseThunkResult => async (dispatch) => {
   dispatch(actions.fetchConsumerGroupDetailsAction.request());
   try {
     const consumerGroupDetails = await apiClient.getConsumerGroup({

+ 7 - 4
kafka-ui-react-app/src/redux/interfaces/index.ts

@@ -1,4 +1,3 @@
-import { AnyAction } from 'redux';
 import { ActionType } from 'typesafe-actions';
 import { ThunkAction } from 'redux-thunk';
 
@@ -26,9 +25,13 @@ export interface RootState {
 
 export type Action = ActionType<typeof actions>;
 
-export type PromiseThunk<T> = ThunkAction<
-  Promise<T>,
+export type ThunkResult<ReturnType = void> = ThunkAction<
+  ReturnType,
   RootState,
   undefined,
-  AnyAction
+  Action
+>;
+
+export type PromiseThunkResult<ReturnType = void> = ThunkResult<
+  Promise<ReturnType>
 >;

+ 27 - 0
kafka-ui-react-app/src/redux/reducers/clusters/__test__/fixtures.ts

@@ -0,0 +1,27 @@
+import { Cluster, ServerStatus } from 'generated-sources';
+
+export const onlineClusterPayload: Cluster = {
+  name: 'secondLocal',
+  defaultCluster: true,
+  status: ServerStatus.Online,
+  brokerCount: 1,
+  onlinePartitionCount: 6,
+  topicCount: 3,
+  bytesInPerSec: 1.55,
+  bytesOutPerSec: 9.314,
+};
+export const offlineClusterPayload: Cluster = {
+  name: 'local',
+  defaultCluster: false,
+  status: ServerStatus.Offline,
+  brokerCount: 1,
+  onlinePartitionCount: 2,
+  topicCount: 2,
+  bytesInPerSec: 3.42,
+  bytesOutPerSec: 4.14,
+};
+
+export const clustersPayload: Cluster[] = [
+  onlineClusterPayload,
+  offlineClusterPayload,
+];

+ 15 - 0
kafka-ui-react-app/src/redux/reducers/clusters/__test__/reducer.spec.ts

@@ -0,0 +1,15 @@
+import { fetchClusterListAction } from 'redux/actions';
+import reducer from 'redux/reducers/clusters/reducer';
+import { clustersPayload } from './fixtures';
+
+describe('Clusters reducer', () => {
+  it('returns the initial state', () => {
+    expect(reducer(undefined, fetchClusterListAction.request())).toEqual([]);
+  });
+
+  it('reacts on GET_CLUSTERS__SUCCESS and returns payload', () => {
+    expect(
+      reducer(undefined, fetchClusterListAction.success(clustersPayload))
+    ).toEqual(clustersPayload);
+  });
+});

+ 58 - 0
kafka-ui-react-app/src/redux/reducers/clusters/__test__/selectors.spec.ts

@@ -0,0 +1,58 @@
+import { fetchClusterListAction } from 'redux/actions';
+import configureStore from 'redux/store/configureStore';
+import * as selectors from '../selectors';
+import {
+  clustersPayload,
+  offlineClusterPayload,
+  onlineClusterPayload,
+} from './fixtures';
+
+const store = configureStore();
+
+describe('Clusters selectors', () => {
+  describe('Initial State', () => {
+    it('returns fetch status', () => {
+      expect(selectors.getIsClusterListFetched(store.getState())).toBeFalsy();
+    });
+
+    it('returns cluster list', () => {
+      expect(selectors.getClusterList(store.getState())).toEqual([]);
+    });
+
+    it('returns online cluster list', () => {
+      expect(selectors.getOnlineClusters(store.getState())).toEqual([]);
+    });
+
+    it('returns offline cluster list', () => {
+      expect(selectors.getOfflineClusters(store.getState())).toEqual([]);
+    });
+  });
+
+  describe('state', () => {
+    beforeAll(() => {
+      store.dispatch(fetchClusterListAction.success(clustersPayload));
+    });
+
+    it('returns fetch status', () => {
+      expect(selectors.getIsClusterListFetched(store.getState())).toBeTruthy();
+    });
+
+    it('returns cluster list', () => {
+      expect(selectors.getClusterList(store.getState())).toEqual(
+        clustersPayload
+      );
+    });
+
+    it('returns online cluster list', () => {
+      expect(selectors.getOnlineClusters(store.getState())).toEqual([
+        onlineClusterPayload,
+      ]);
+    });
+
+    it('returns offline cluster list', () => {
+      expect(selectors.getOfflineClusters(store.getState())).toEqual([
+        offlineClusterPayload,
+      ]);
+    });
+  });
+});

+ 5 - 3
kafka-ui-react-app/src/redux/store/configureStore/dev.ts

@@ -1,6 +1,8 @@
 import { createStore, applyMiddleware, compose } from 'redux';
-import thunk from 'redux-thunk';
-import rootReducer from '../../reducers';
+import thunk, { ThunkMiddleware } from 'redux-thunk';
+import { RootState } from 'redux/interfaces';
+import { Action } from 'typesafe-actions';
+import rootReducer from 'redux/reducers';
 
 declare global {
   interface Window {
@@ -9,7 +11,7 @@ declare global {
 }
 
 export default () => {
-  const middlewares = [thunk];
+  const middlewares = [thunk as ThunkMiddleware<RootState, Action>];
 
   const composeEnhancers =
     window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;

+ 1 - 1
kafka-ui-react-app/src/redux/store/configureStore/prod.ts

@@ -1,6 +1,6 @@
 import { createStore, applyMiddleware } from 'redux';
 import thunk from 'redux-thunk';
-import rootReducer from '../../reducers';
+import rootReducer from 'redux/reducers';
 
 export default () => {
   const middlewares = [thunk];