فهرست منبع

Space sensitive syntax for Create Connector #1217

* added trim to json config

* refactored new tests

* refactored edit tests

* fix new test act warning

* first working tests

* fixed textarea props

* resolved discussions

Co-authored-by: Ekaterina Petrova <epetrova@provectus.com>
Co-authored-by: Roman Zabaluev <rzabaluev@provectus.com>
Ekaterina Petrova 3 سال پیش
والد
کامیت
2b79fee1e4

+ 2 - 2
kafka-ui-react-app/src/components/Connect/Edit/Edit.tsx

@@ -88,7 +88,7 @@ const Edit: React.FC<EditProps> = ({
         clusterName,
         connectName,
         connectorName,
-        JSON.parse(values.config)
+        JSON.parse(values.config.trim())
       );
       if (connector) {
         history.push(
@@ -116,7 +116,7 @@ const Edit: React.FC<EditProps> = ({
           accidentally breaking your connector config!
         </ConnectEditWarningMessageStyled>
       )}
-      <form onSubmit={handleSubmit(onSubmit)}>
+      <form onSubmit={handleSubmit(onSubmit)} aria-label="Edit connect form">
         <div>
           <Controller
             control={control}

+ 22 - 44
kafka-ui-react-app/src/components/Connect/Edit/__tests__/Edit.spec.tsx

@@ -1,8 +1,5 @@
 import React from 'react';
-import { create } from 'react-test-renderer';
-import { mount } from 'enzyme';
-import { act } from 'react-dom/test-utils';
-import { containerRendersView, TestRouterWrapper } from 'lib/testHelpers';
+import { containerRendersView, render } from 'lib/testHelpers';
 import {
   clusterConnectConnectorConfigPath,
   clusterConnectConnectorEditPath,
@@ -10,8 +7,9 @@ import {
 import EditContainer from 'components/Connect/Edit/EditContainer';
 import Edit, { EditProps } from 'components/Connect/Edit/Edit';
 import { connector } from 'redux/reducers/connect/__test__/fixtures';
-import { ThemeProvider } from 'styled-components';
-import theme from 'theme/theme';
+import { Route } from 'react-router';
+import { waitFor } from '@testing-library/dom';
+import { fireEvent, screen } from '@testing-library/react';
 
 jest.mock('components/common/PageLoader/PageLoader', () => 'mock-PageLoader');
 
@@ -38,12 +36,9 @@ describe('Edit', () => {
     const connectName = 'my-connect';
     const connectorName = 'my-connector';
 
-    const setupWrapper = (props: Partial<EditProps> = {}) => (
-      <ThemeProvider theme={theme}>
-        <TestRouterWrapper
-          pathname={pathname}
-          urlParams={{ clusterName, connectName, connectorName }}
-        >
+    const renderComponent = (props: Partial<EditProps> = {}) =>
+      render(
+        <Route path={pathname}>
           <Edit
             fetchConfig={jest.fn()}
             isConfigFetching={false}
@@ -51,30 +46,19 @@ describe('Edit', () => {
             updateConfig={jest.fn()}
             {...props}
           />
-        </TestRouterWrapper>
-      </ThemeProvider>
-    );
-
-    it('matches snapshot', () => {
-      const wrapper = create(setupWrapper());
-      expect(wrapper.toJSON()).toMatchSnapshot();
-    });
-
-    it('matches snapshot when fetching config', () => {
-      const wrapper = create(setupWrapper({ isConfigFetching: true }));
-      expect(wrapper.toJSON()).toMatchSnapshot();
-    });
-
-    it('matches snapshot when config has credentials', () => {
-      const wrapper = create(
-        setupWrapper({ config: { ...connector.config, password: '******' } })
+        </Route>,
+        {
+          pathname: clusterConnectConnectorEditPath(
+            clusterName,
+            connectName,
+            connectorName
+          ),
+        }
       );
-      expect(wrapper.toJSON()).toMatchSnapshot();
-    });
 
     it('fetches config on mount', () => {
       const fetchConfig = jest.fn();
-      mount(setupWrapper({ fetchConfig }));
+      renderComponent({ fetchConfig });
       expect(fetchConfig).toHaveBeenCalledTimes(1);
       expect(fetchConfig).toHaveBeenCalledWith(
         clusterName,
@@ -85,10 +69,8 @@ describe('Edit', () => {
 
     it('calls updateConfig on form submit', async () => {
       const updateConfig = jest.fn();
-      const wrapper = mount(setupWrapper({ updateConfig }));
-      await act(async () => {
-        wrapper.find('form').simulate('submit');
-      });
+      renderComponent({ updateConfig });
+      await waitFor(() => fireEvent.submit(screen.getByRole('form')));
       expect(updateConfig).toHaveBeenCalledTimes(1);
       expect(updateConfig).toHaveBeenCalledWith(
         clusterName,
@@ -100,10 +82,8 @@ describe('Edit', () => {
 
     it('redirects to connector config view on successful submit', async () => {
       const updateConfig = jest.fn().mockResolvedValueOnce(connector);
-      const wrapper = mount(setupWrapper({ updateConfig }));
-      await act(async () => {
-        wrapper.find('form').simulate('submit');
-      });
+      renderComponent({ updateConfig });
+      await waitFor(() => fireEvent.submit(screen.getByRole('form')));
       expect(mockHistoryPush).toHaveBeenCalledTimes(1);
       expect(mockHistoryPush).toHaveBeenCalledWith(
         clusterConnectConnectorConfigPath(
@@ -116,10 +96,8 @@ describe('Edit', () => {
 
     it('does not redirect to connector config view on unsuccessful submit', async () => {
       const updateConfig = jest.fn().mockResolvedValueOnce(undefined);
-      const wrapper = mount(setupWrapper({ updateConfig }));
-      await act(async () => {
-        wrapper.find('form').simulate('submit');
-      });
+      renderComponent({ updateConfig });
+      await waitFor(() => fireEvent.submit(screen.getByRole('form')));
       expect(mockHistoryPush).not.toHaveBeenCalled();
     });
   });

+ 0 - 209
kafka-ui-react-app/src/components/Connect/Edit/__tests__/__snapshots__/Edit.spec.tsx.snap

@@ -1,209 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`Edit view matches snapshot 1`] = `
-.c1 {
-  display: -webkit-box;
-  display: -webkit-flex;
-  display: -ms-flexbox;
-  display: flex;
-  -webkit-flex-direction: row;
-  -ms-flex-direction: row;
-  flex-direction: row;
-  -webkit-align-items: center;
-  -webkit-box-align: center;
-  -ms-flex-align: center;
-  align-items: center;
-  -webkit-box-pack: center;
-  -webkit-justify-content: center;
-  -ms-flex-pack: center;
-  justify-content: center;
-  padding: 0px 12px;
-  border: none;
-  border-radius: 4px;
-  white-space: nowrap;
-  background: #4F4FFF;
-  color: #FFFFFF;
-  font-size: 14px;
-  font-weight: 500;
-  height: 32px;
-}
-
-.c1:hover:enabled {
-  background: #1717CF;
-  color: #FFFFFF;
-  cursor: pointer;
-}
-
-.c1:active:enabled {
-  background: #1414B8;
-  color: #FFFFFF;
-}
-
-.c1:disabled {
-  opacity: 0.5;
-  cursor: not-allowed;
-}
-
-.c1 a {
-  color: white;
-}
-
-.c1 i {
-  margin-right: 7px;
-}
-
-.c0 {
-  margin: 16px;
-}
-
-.c0 form > *:last-child {
-  margin-top: 16px;
-}
-
-<div
-  className="c0"
->
-  <form
-    onSubmit={[Function]}
-  >
-    <div>
-      <mock-Editor
-        name="config"
-        onBlur={[Function]}
-        onChange={[Function]}
-        readOnly={false}
-        value="{
-	\\"connector.class\\": \\"FileStreamSource\\",
-	\\"tasks.max\\": \\"10\\",
-	\\"topic\\": \\"test-topic\\",
-	\\"file\\": \\"/some/file\\"
-}"
-      />
-    </div>
-    <div />
-    <button
-      className="c1"
-      disabled={true}
-      type="submit"
-    >
-      Submit
-    </button>
-  </form>
-</div>
-`;
-
-exports[`Edit view matches snapshot when config has credentials 1`] = `
-.c2 {
-  display: -webkit-box;
-  display: -webkit-flex;
-  display: -ms-flexbox;
-  display: flex;
-  -webkit-flex-direction: row;
-  -ms-flex-direction: row;
-  flex-direction: row;
-  -webkit-align-items: center;
-  -webkit-box-align: center;
-  -ms-flex-align: center;
-  align-items: center;
-  -webkit-box-pack: center;
-  -webkit-justify-content: center;
-  -ms-flex-pack: center;
-  justify-content: center;
-  padding: 0px 12px;
-  border: none;
-  border-radius: 4px;
-  white-space: nowrap;
-  background: #4F4FFF;
-  color: #FFFFFF;
-  font-size: 14px;
-  font-weight: 500;
-  height: 32px;
-}
-
-.c2:hover:enabled {
-  background: #1717CF;
-  color: #FFFFFF;
-  cursor: pointer;
-}
-
-.c2:active:enabled {
-  background: #1414B8;
-  color: #FFFFFF;
-}
-
-.c2:disabled {
-  opacity: 0.5;
-  cursor: not-allowed;
-}
-
-.c2 a {
-  color: white;
-}
-
-.c2 i {
-  margin-right: 7px;
-}
-
-.c0 {
-  margin: 16px;
-}
-
-.c0 form > *:last-child {
-  margin-top: 16px;
-}
-
-.c1 {
-  height: 48px;
-  display: -webkit-box;
-  display: -webkit-flex;
-  display: -ms-flexbox;
-  display: flex;
-  -webkit-align-items: center;
-  -webkit-box-align: center;
-  -ms-flex-align: center;
-  align-items: center;
-  background-color: #FFEECC;
-  border-radius: 8px;
-  padding: 8px;
-  margin-bottom: 16px;
-}
-
-<div
-  className="c0"
->
-  <div
-    className="c1"
-  >
-    Please replace ****** with the real credential values to avoid accidentally breaking your connector config!
-  </div>
-  <form
-    onSubmit={[Function]}
-  >
-    <div>
-      <mock-Editor
-        name="config"
-        onBlur={[Function]}
-        onChange={[Function]}
-        readOnly={false}
-        value="{
-	\\"connector.class\\": \\"FileStreamSource\\",
-	\\"tasks.max\\": \\"10\\",
-	\\"topic\\": \\"test-topic\\",
-	\\"file\\": \\"/some/file\\",
-	\\"password\\": \\"******\\"
-}"
-      />
-    </div>
-    <div />
-    <button
-      className="c2"
-      disabled={true}
-      type="submit"
-    >
-      Submit
-    </button>
-  </form>
-</div>
-`;
-
-exports[`Edit view matches snapshot when fetching config 1`] = `<mock-PageLoader />`;

+ 12 - 0
kafka-ui-react-app/src/components/Connect/New/New.styled.ts

@@ -0,0 +1,12 @@
+import styled from 'styled-components';
+
+export const NewConnectFormStyled = styled.form`
+  padding: 0 16px 16px;
+  display: flex;
+  flex-direction: column;
+  gap: 16px;
+
+  & > button:last-child {
+    align-self: flex-start;
+  }
+`;

+ 8 - 15
kafka-ui-react-app/src/components/Connect/New/New.tsx

@@ -14,9 +14,10 @@ import Select from 'components/common/Select/Select';
 import { FormError } from 'components/common/Input/Input.styled';
 import Input from 'components/common/Input/Input';
 import { Button } from 'components/common/Button/Button';
-import styled from 'styled-components';
 import PageHeading from 'components/common/PageHeading/PageHeading';
 
+import * as S from './New.styled';
+
 const validationSchema = yup.object().shape({
   name: yup.string().required(),
   config: yup.string().required().isJsonObject(),
@@ -26,17 +27,6 @@ interface RouterParams {
   clusterName: ClusterName;
 }
 
-const NewConnectFormStyled = styled.form`
-  padding: 16px;
-  padding-top: 0;
-  display: flex;
-  flex-direction: column;
-  gap: 16px;
-  & > button:last-child {
-    align-self: flex-start;
-  }
-`;
-
 export interface NewProps {
   fetchConnects(clusterName: ClusterName): void;
   areConnectsFetching: boolean;
@@ -99,7 +89,7 @@ const New: React.FC<NewProps> = ({
     async (values: FormValues) => {
       const connector = await createConnector(clusterName, values.connectName, {
         name: values.name,
-        config: JSON.parse(values.config),
+        config: JSON.parse(values.config.trim()),
       });
       if (connector) {
         history.push(
@@ -125,7 +115,10 @@ const New: React.FC<NewProps> = ({
   return (
     <FormProvider {...methods}>
       <PageHeading text="Create new connector" />
-      <NewConnectFormStyled onSubmit={handleSubmit(onSubmit)}>
+      <S.NewConnectFormStyled
+        onSubmit={handleSubmit(onSubmit)}
+        aria-label="Create connect form"
+      >
         <div className={['field', connectNameFieldClassName].join(' ')}>
           <InputLabel>Connect *</InputLabel>
           <Select selectSize="M" name="connectName" disabled={isSubmitting}>
@@ -175,7 +168,7 @@ const New: React.FC<NewProps> = ({
         >
           Submit
         </Button>
-      </NewConnectFormStyled>
+      </S.NewConnectFormStyled>
     </FormProvider>
   );
 };

+ 40 - 47
kafka-ui-react-app/src/components/Connect/New/__tests__/New.spec.tsx

@@ -1,8 +1,5 @@
 import React from 'react';
-import { create, act as rendererAct } from 'react-test-renderer';
-import { mount, ReactWrapper } from 'enzyme';
-import { act } from 'react-dom/test-utils';
-import { containerRendersView, TestRouterWrapper } from 'lib/testHelpers';
+import { containerRendersView, render } from 'lib/testHelpers';
 import {
   clusterConnectConnectorPath,
   clusterConnectorNewPath,
@@ -10,12 +7,19 @@ import {
 import NewContainer from 'components/Connect/New/NewContainer';
 import New, { NewProps } from 'components/Connect/New/New';
 import { connects, connector } from 'redux/reducers/connect/__test__/fixtures';
-import { ThemeProvider } from 'styled-components';
-import theme from 'theme/theme';
+import { Route } from 'react-router';
+import { act, fireEvent, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { waitFor } from '@testing-library/dom';
+import { ControllerRenderProps } from 'react-hook-form';
 
 jest.mock('components/common/PageLoader/PageLoader', () => 'mock-PageLoader');
-
-jest.mock('components/common/Editor/Editor', () => 'mock-Editor');
+jest.mock(
+  'components/common/Editor/Editor',
+  () => (props: ControllerRenderProps) => {
+    return <textarea {...props} placeholder="json" />;
+  }
+);
 
 const mockHistoryPush = jest.fn();
 jest.mock('react-router-dom', () => ({
@@ -29,22 +33,27 @@ describe('New', () => {
   containerRendersView(<NewContainer />, New);
 
   describe('view', () => {
-    const pathname = clusterConnectorNewPath(':clusterName');
     const clusterName = 'my-cluster';
-    const simulateFormSubmit = (wrapper: ReactWrapper) =>
-      act(async () => {
-        wrapper.find('input[name="name"]').simulate('change', {
-          target: { name: 'name', value: 'my-connector' },
-        });
-        wrapper
-          .find('mock-Editor')
-          .simulate('change', { target: { value: '{"class":"MyClass"}' } });
-        wrapper.find('button[type="submit"]').simulate('submit');
+    const simulateFormSubmit = async () => {
+      userEvent.type(
+        screen.getByPlaceholderText('Connector Name'),
+        'my-connector'
+      );
+      userEvent.type(
+        screen.getByPlaceholderText('json'),
+        '{"class":"MyClass"}'.replace(/[{[]/g, '$&$&')
+      );
+      expect(screen.getByPlaceholderText('json')).toHaveValue(
+        '{"class":"MyClass"}'
+      );
+      await waitFor(() => {
+        fireEvent.submit(screen.getByRole('form'));
       });
+    };
 
-    const setupWrapper = (props: Partial<NewProps> = {}) => (
-      <ThemeProvider theme={theme}>
-        <TestRouterWrapper pathname={pathname} urlParams={{ clusterName }}>
+    const renderComponent = (props: Partial<NewProps> = {}) =>
+      render(
+        <Route path={clusterConnectorNewPath(':clusterName')}>
           <New
             fetchConnects={jest.fn()}
             areConnectsFetching={false}
@@ -52,30 +61,14 @@ describe('New', () => {
             createConnector={jest.fn()}
             {...props}
           />
-        </TestRouterWrapper>
-      </ThemeProvider>
-    );
-
-    it('matches snapshot', async () => {
-      let wrapper = create(<div />);
-      await rendererAct(async () => {
-        wrapper = create(setupWrapper());
-      });
-      expect(wrapper.toJSON()).toMatchSnapshot();
-    });
-
-    it('matches snapshot when fetching connects', async () => {
-      let wrapper = create(<div />);
-      await rendererAct(async () => {
-        wrapper = create(setupWrapper({ areConnectsFetching: true }));
-      });
-      expect(wrapper.toJSON()).toMatchSnapshot();
-    });
+        </Route>,
+        { pathname: clusterConnectorNewPath(clusterName) }
+      );
 
     it('fetches connects on mount', async () => {
       const fetchConnects = jest.fn();
       await act(async () => {
-        mount(setupWrapper({ fetchConnects }));
+        renderComponent({ fetchConnects });
       });
       expect(fetchConnects).toHaveBeenCalledTimes(1);
       expect(fetchConnects).toHaveBeenCalledWith(clusterName);
@@ -83,8 +76,8 @@ describe('New', () => {
 
     it('calls createConnector on form submit', async () => {
       const createConnector = jest.fn();
-      const wrapper = mount(setupWrapper({ createConnector }));
-      await simulateFormSubmit(wrapper);
+      renderComponent({ createConnector });
+      await simulateFormSubmit();
       expect(createConnector).toHaveBeenCalledTimes(1);
       expect(createConnector).toHaveBeenCalledWith(
         clusterName,
@@ -98,8 +91,8 @@ describe('New', () => {
 
     it('redirects to connector details view on successful submit', async () => {
       const createConnector = jest.fn().mockResolvedValue(connector);
-      const wrapper = mount(setupWrapper({ createConnector }));
-      await simulateFormSubmit(wrapper);
+      renderComponent({ createConnector });
+      await simulateFormSubmit();
       expect(mockHistoryPush).toHaveBeenCalledTimes(1);
       expect(mockHistoryPush).toHaveBeenCalledWith(
         clusterConnectConnectorPath(
@@ -112,8 +105,8 @@ describe('New', () => {
 
     it('does not redirect to connector details view on unsuccessful submit', async () => {
       const createConnector = jest.fn().mockResolvedValueOnce(undefined);
-      const wrapper = mount(setupWrapper({ createConnector }));
-      await simulateFormSubmit(wrapper);
+      renderComponent({ createConnector });
+      await simulateFormSubmit();
       expect(mockHistoryPush).not.toHaveBeenCalled();
     });
   });

+ 0 - 336
kafka-ui-react-app/src/components/Connect/New/__tests__/__snapshots__/New.spec.tsx.snap

@@ -1,336 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`New view matches snapshot 1`] = `
-Array [
-  .c0 {
-  height: 56px;
-  display: -webkit-box;
-  display: -webkit-flex;
-  display: -ms-flexbox;
-  display: flex;
-  -webkit-box-pack: justify;
-  -webkit-justify-content: space-between;
-  -ms-flex-pack: justify;
-  justify-content: space-between;
-  -webkit-align-items: center;
-  -webkit-box-align: center;
-  -ms-flex-align: center;
-  align-items: center;
-  padding: 0px 16px;
-}
-
-.c0 h1 {
-  font-size: 24px;
-  font-weight: 500;
-  line-height: 32px;
-  color: #000;
-}
-
-.c0 > div {
-  display: -webkit-box;
-  display: -webkit-flex;
-  display: -ms-flexbox;
-  display: flex;
-  gap: 16px;
-}
-
-<div
-    className="c0"
-  >
-    <h1>
-      Create new connector
-    </h1>
-    <div />
-  </div>,
-  .c1 {
-  font-weight: 500;
-  font-size: 12px;
-  line-height: 20px;
-  color: #454F54;
-}
-
-.c3 {
-  height: 32px;
-  border: 1px #ABB5BA solid;
-  border-radius: 4px;
-  font-size: 14px;
-  width: 100%;
-  padding-left: 12px;
-  padding-right: 16px;
-  color: #171A1C;
-  min-width: auto;
-  background-image: url('data:image/svg+xml,%3Csvg width="10" height="6" viewBox="0 0 10 6" fill="none" xmlns="http://www.w3.org/2000/svg"%3E%3Cpath d="M1 1L5 5L9 1" stroke="%23454F54"/%3E%3C/svg%3E%0A') !important;
-  background-repeat: no-repeat !important;
-  background-position-x: calc(100% - 8px) !important;
-  background-position-y: 55% !important;
-  -webkit-appearance: none !important;
-  -moz-appearance: none !important;
-  appearance: none !important;
-}
-
-.c3:hover {
-  color: #171A1C;
-  border-color: #73848C;
-}
-
-.c3:focus {
-  outline: none;
-  color: #171A1C;
-  border-color: #454F54;
-}
-
-.c3:disabled {
-  color: #ABB5BA;
-  border-color: #E3E6E8;
-  cursor: not-allowed;
-}
-
-.c2 {
-  position: relative;
-}
-
-.c6 {
-  border: 1px #ABB5BA solid;
-  border-radius: 4px;
-  height: 32px;
-  width: 100%;
-  padding-left: 12px;
-  font-size: 14px;
-}
-
-.c6::-webkit-input-placeholder {
-  color: #ABB5BA;
-  font-size: 14px;
-}
-
-.c6::-moz-placeholder {
-  color: #ABB5BA;
-  font-size: 14px;
-}
-
-.c6:-ms-input-placeholder {
-  color: #ABB5BA;
-  font-size: 14px;
-}
-
-.c6::placeholder {
-  color: #ABB5BA;
-  font-size: 14px;
-}
-
-.c6:hover {
-  border-color: #73848C;
-}
-
-.c6:focus {
-  outline: none;
-  border-color: #454F54;
-}
-
-.c6:focus::-webkit-input-placeholder {
-  color: transparent;
-}
-
-.c6:focus::-moz-placeholder {
-  color: transparent;
-}
-
-.c6:focus:-ms-input-placeholder {
-  color: transparent;
-}
-
-.c6:focus::placeholder {
-  color: transparent;
-}
-
-.c6:disabled {
-  color: #ABB5BA;
-  border-color: #E3E6E8;
-  cursor: not-allowed;
-}
-
-.c6:read-only {
-  color: #171A1C;
-  border: none;
-  background-color: #F1F2F3;
-  cursor: not-allowed;
-}
-
-.c6:-moz-read-only:focus::placeholder {
-  color: #ABB5BA;
-}
-
-.c6:read-only:focus::placeholder {
-  color: #ABB5BA;
-}
-
-.c4 {
-  color: #E51A1A;
-  font-size: 12px;
-}
-
-.c5 {
-  position: relative;
-}
-
-.c7 {
-  display: -webkit-box;
-  display: -webkit-flex;
-  display: -ms-flexbox;
-  display: flex;
-  -webkit-flex-direction: row;
-  -ms-flex-direction: row;
-  flex-direction: row;
-  -webkit-align-items: center;
-  -webkit-box-align: center;
-  -ms-flex-align: center;
-  align-items: center;
-  -webkit-box-pack: center;
-  -webkit-justify-content: center;
-  -ms-flex-pack: center;
-  justify-content: center;
-  padding: 0px 12px;
-  border: none;
-  border-radius: 4px;
-  white-space: nowrap;
-  background: #4F4FFF;
-  color: #FFFFFF;
-  font-size: 14px;
-  font-weight: 500;
-  height: 32px;
-}
-
-.c7:hover:enabled {
-  background: #1717CF;
-  color: #FFFFFF;
-  cursor: pointer;
-}
-
-.c7:active:enabled {
-  background: #1414B8;
-  color: #FFFFFF;
-}
-
-.c7:disabled {
-  opacity: 0.5;
-  cursor: not-allowed;
-}
-
-.c7 a {
-  color: white;
-}
-
-.c7 i {
-  margin-right: 7px;
-}
-
-.c0 {
-  padding: 16px;
-  padding-top: 0;
-  display: -webkit-box;
-  display: -webkit-flex;
-  display: -ms-flexbox;
-  display: flex;
-  -webkit-flex-direction: column;
-  -ms-flex-direction: column;
-  flex-direction: column;
-  gap: 16px;
-}
-
-.c0 > button:last-child {
-  -webkit-align-self: flex-start;
-  -ms-flex-item-align: start;
-  align-self: flex-start;
-}
-
-<form
-    className="c0"
-    onSubmit={[Function]}
-  >
-    <div
-      className="field "
-    >
-      <label
-        className="c1"
-      >
-        Connect *
-      </label>
-      <div
-        className="select-wrapper c2"
-      >
-        <select
-          className="c3"
-          disabled={false}
-          name="connectName"
-          onBlur={[Function]}
-          onChange={[Function]}
-          role="listbox"
-        >
-          <option
-            value="first"
-          >
-            first
-          </option>
-          <option
-            value="second"
-          >
-            second
-          </option>
-        </select>
-      </div>
-      <p
-        className="c4"
-      />
-    </div>
-    <div>
-      <label
-        className="c1"
-      >
-        Name *
-      </label>
-      <div
-        className="c5"
-      >
-        <input
-          autoComplete="off"
-          className="c6 c5"
-          disabled={false}
-          name="name"
-          onBlur={[Function]}
-          onChange={[Function]}
-          placeholder="Connector Name"
-        />
-      </div>
-      <p
-        className="c4"
-      />
-    </div>
-    <div>
-      <label
-        className="c1"
-      >
-        Config *
-      </label>
-      <mock-Editor
-        name="config"
-        onBlur={[Function]}
-        onChange={[Function]}
-        readOnly={false}
-        value=""
-      />
-      <p
-        className="c4"
-      />
-    </div>
-    <button
-      className="c7"
-      disabled={true}
-      type="submit"
-    >
-      Submit
-    </button>
-  </form>,
-]
-`;
-
-exports[`New view matches snapshot when fetching connects 1`] = `<mock-PageLoader />`;

+ 5 - 3
kafka-ui-react-app/src/lib/yupExtended.ts

@@ -16,11 +16,13 @@ declare module 'yup' {
 export const isValidJsonObject = (value?: string) => {
   try {
     if (!value) return false;
+
+    const trimmedValue = value.trim();
     if (
-      value.indexOf('{') === 0 &&
-      value.lastIndexOf('}') === value.length - 1
+      trimmedValue.indexOf('{') === 0 &&
+      trimmedValue.lastIndexOf('}') === trimmedValue.length - 1
     ) {
-      JSON.parse(value);
+      JSON.parse(trimmedValue);
       return true;
     }
   } catch {