瀏覽代碼

fixing forms not no be able to submit until all required fields are f… (#2221)

* fixing forms not no be able to submit until all required fields are filled

* fixing topic form validation issue
Robert Azizbekyan 2 年之前
父節點
當前提交
7845476af1

+ 6 - 6
kafka-ui-react-app/src/components/Schemas/New/New.tsx

@@ -32,12 +32,12 @@ const New: React.FC = () => {
   const { clusterName } = useAppParams<ClusterNameRoute>();
   const navigate = useNavigate();
   const dispatch = useAppDispatch();
-  const methods = useForm<NewSchemaSubjectRaw>();
+  const methods = useForm<NewSchemaSubjectRaw>({ mode: 'onChange' });
   const {
     register,
     handleSubmit,
     control,
-    formState: { isDirty, isSubmitting, errors },
+    formState: { isDirty, isSubmitting, errors, isValid },
   } = methods;
 
   const onSubmit = async ({
@@ -99,15 +99,15 @@ const New: React.FC = () => {
         <div>
           <InputLabel>Schema Type *</InputLabel>
           <Controller
-            defaultValue={SchemaTypeOptions[0].value as SchemaType}
             control={control}
             rules={{ required: 'Schema Type is required.' }}
             name="schemaType"
-            render={({ field: { name, onChange } }) => (
+            render={({ field: { name, onChange, value } }) => (
               <Select
                 selectSize="M"
                 name={name}
-                value={SchemaTypeOptions[0].value}
+                defaultValue={SchemaTypeOptions[0].value}
+                value={value}
                 onChange={onChange}
                 minWidth="50%"
                 disabled={isSubmitting}
@@ -124,7 +124,7 @@ const New: React.FC = () => {
           buttonSize="M"
           buttonType="primary"
           type="submit"
-          disabled={isSubmitting || !isDirty}
+          disabled={!isValid || isSubmitting || !isDirty}
         >
           Submit
         </Button>

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

@@ -80,7 +80,12 @@ describe('New', () => {
 
   it('validates form', async () => {
     await act(() => renderComponent(clusterTopicNewPath(clusterName)));
-    userEvent.click(screen.getByText('Create topic'));
+    await waitFor(() => {
+      userEvent.type(screen.getByPlaceholderText('Topic Name'), topicName);
+    });
+    await waitFor(() => {
+      userEvent.clear(screen.getByPlaceholderText('Topic Name'));
+    });
     await waitFor(() => {
       expect(screen.getByText('name is a required field')).toBeInTheDocument();
     });
@@ -96,8 +101,13 @@ describe('New', () => {
 
     await act(() => renderComponent(clusterTopicNewPath(clusterName)));
 
-    userEvent.type(screen.getByPlaceholderText('Topic Name'), topicName);
-    userEvent.click(screen.getByText('Create topic'));
+    await act(() => {
+      userEvent.type(screen.getByPlaceholderText('Topic Name'), topicName);
+    });
+
+    await act(() => {
+      userEvent.click(screen.getByText('Create topic'));
+    });
 
     await waitFor(() => expect(mockNavigate).toBeCalledTimes(1));
     expect(mockNavigate).toHaveBeenLastCalledWith(`../${topicName}`);
@@ -127,6 +137,8 @@ describe('New', () => {
     await act(() => renderComponent(clusterTopicNewPath(clusterName)));
     await act(() => {
       userEvent.type(screen.getByPlaceholderText('Topic Name'), topicName);
+    });
+    await act(() => {
       userEvent.click(screen.getByText('Create topic'));
     });
 

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

@@ -92,6 +92,7 @@ const Edit: React.FC<Props> = ({
   const methods = useForm<TopicFormData>({
     defaultValues,
     resolver: yupResolver(topicFormValidationSchema),
+    mode: 'onChange',
   });
 
   const [isSubmitting, setIsSubmitting] = React.useState<boolean>(false);

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

@@ -117,13 +117,15 @@ describe('Edit Component', () => {
       renderComponent({ updateTopic: updateTopicMock }, undefined);
 
       const btn = screen.getAllByText(/Save/i)[0];
-      expect(btn).toBeEnabled();
 
       await act(() => {
         userEvent.type(
           screen.getByPlaceholderText('Min In Sync Replicas'),
           '1'
         );
+      });
+
+      await act(() => {
         userEvent.click(btn);
       });
       expect(updateTopicMock).toHaveBeenCalledTimes(1);
@@ -145,6 +147,8 @@ describe('Edit Component', () => {
           screen.getByPlaceholderText('Min In Sync Replicas'),
           '1'
         );
+      });
+      await act(() => {
         userEvent.click(btn);
       });
       expect(updateTopicMock).toHaveBeenCalledTimes(1);

+ 10 - 5
kafka-ui-react-app/src/components/Topics/shared/Form/TopicForm.tsx

@@ -51,14 +51,14 @@ const TopicForm: React.FC<Props> = ({
 }) => {
   const {
     control,
-    formState: { errors },
+    formState: { errors, isDirty, isValid },
   } = useFormContext();
   const getCleanUpPolicy =
     CleanupPolicyOptions.find((option: SelectOption) => {
       return option.value === cleanUpPolicy?.toLowerCase();
     })?.value || CleanupPolicyOptions[0].value;
   return (
-    <StyledForm onSubmit={onSubmit}>
+    <StyledForm onSubmit={onSubmit} aria-label="topic form">
       <fieldset disabled={isSubmitting}>
         <fieldset disabled={isEditing}>
           <S.Column>
@@ -125,10 +125,10 @@ const TopicForm: React.FC<Props> = ({
               placeholder="Min In Sync Replicas"
               min="1"
               defaultValue={inSyncReplicas}
-              name="minInsyncReplicas"
+              name="minInSyncReplicas"
             />
             <FormError>
-              <ErrorMessage errors={errors} name="minInsyncReplicas" />
+              <ErrorMessage errors={errors} name="minInSyncReplicas" />
             </FormError>
           </div>
           <div>
@@ -209,7 +209,12 @@ const TopicForm: React.FC<Props> = ({
         <S.CustomParamsHeading>Custom parameters</S.CustomParamsHeading>
         <CustomParams isSubmitting={isSubmitting} />
         <S.ButtonWrapper>
-          <Button type="submit" buttonType="primary" buttonSize="L">
+          <Button
+            type="submit"
+            buttonType="primary"
+            buttonSize="L"
+            disabled={!isValid || isSubmitting || !isDirty}
+          >
             {isEditing ? 'Save' : 'Create topic'}
           </Button>
           <Button type="button" buttonType="primary" buttonSize="L">

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

@@ -1,9 +1,10 @@
 import React, { PropsWithChildren } from 'react';
 import { render } from 'lib/testHelpers';
-import { screen } from '@testing-library/dom';
+import { fireEvent, screen } from '@testing-library/dom';
 import { FormProvider, useForm } from 'react-hook-form';
 import TopicForm, { Props } from 'components/Topics/shared/Form/TopicForm';
 import userEvent from '@testing-library/user-event';
+import { act } from 'react-dom/test-utils';
 
 const isSubmitting = false;
 const onSubmit = jest.fn();
@@ -60,12 +61,19 @@ describe('TopicForm', () => {
     expectByRoleAndNameToBeInDocument('button', 'Create topic');
   });
 
-  it('submits', () => {
+  it('submits', async () => {
     renderComponent({
       isSubmitting,
       onSubmit: onSubmit.mockImplementation((e) => e.preventDefault()),
     });
 
+    await act(() => {
+      userEvent.type(screen.getByPlaceholderText('Topic Name'), 'topicName');
+    });
+    await act(() => {
+      fireEvent.submit(screen.getByLabelText('topic form'));
+    });
+
     userEvent.click(screen.getByRole('button', { name: 'Create topic' }));
     expect(onSubmit).toBeCalledTimes(1);
   });

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

@@ -62,7 +62,7 @@ export const topicFormValidationSchema = yup.object().shape({
     .min(1)
     .required()
     .typeError('Replication factor is required and must be a number'),
-  minInsyncReplicas: yup
+  minInSyncReplicas: yup
     .number()
     .min(1)
     .required()

+ 2 - 2
kafka-ui-react-app/src/redux/interfaces/topic.ts

@@ -64,7 +64,7 @@ export interface TopicFormDataRaw {
   name: string;
   partitions: number;
   replicationFactor: number;
-  minInsyncReplicas: number;
+  minInSyncReplicas: number;
   cleanupPolicy: string;
   retentionMs: number;
   retentionBytes: number;
@@ -76,7 +76,7 @@ export interface TopicFormData {
   name: string;
   partitions: number;
   replicationFactor: number;
-  minInsyncReplicas: number;
+  minInSyncReplicas: number;
   cleanupPolicy: string;
   retentionMs: number;
   retentionBytes: number;

+ 2 - 2
kafka-ui-react-app/src/redux/reducers/topics/__test__/reducer.spec.ts

@@ -806,7 +806,7 @@ describe('topics Slice', () => {
         name: 'newTopic',
         partitions: 0,
         replicationFactor: 0,
-        minInsyncReplicas: 0,
+        minInSyncReplicas: 0,
         cleanupPolicy: 'DELETE',
         retentionMs: 1,
         retentionBytes: 1,
@@ -864,7 +864,7 @@ describe('topics Slice', () => {
         name: topicName,
         partitions: 0,
         replicationFactor: 0,
-        minInsyncReplicas: 0,
+        minInSyncReplicas: 0,
         cleanupPolicy: 'DELETE',
         retentionMs: 0,
         retentionBytes: 0,

+ 4 - 4
kafka-ui-react-app/src/redux/reducers/topics/topicsSlice.ts

@@ -92,7 +92,7 @@ export const formatTopicCreation = (form: TopicFormData): TopicCreation => {
     retentionBytes,
     retentionMs,
     maxMessageBytes,
-    minInsyncReplicas,
+    minInSyncReplicas,
     customParams,
   } = form;
 
@@ -105,7 +105,7 @@ export const formatTopicCreation = (form: TopicFormData): TopicCreation => {
       'retention.ms': retentionMs.toString(),
       'retention.bytes': retentionBytes.toString(),
       'max.message.bytes': maxMessageBytes.toString(),
-      'min.insync.replicas': minInsyncReplicas.toString(),
+      'min.insync.replicas': minInSyncReplicas.toString(),
       ...Object.values(customParams || {}).reduce(topicReducer, {}),
     },
   };
@@ -153,7 +153,7 @@ const formatTopicUpdate = (form: TopicFormDataRaw): TopicUpdate => {
     retentionBytes,
     retentionMs,
     maxMessageBytes,
-    minInsyncReplicas,
+    minInSyncReplicas,
     customParams,
   } = form;
 
@@ -163,7 +163,7 @@ const formatTopicUpdate = (form: TopicFormDataRaw): TopicUpdate => {
       'retention.ms': retentionMs,
       'retention.bytes': retentionBytes,
       'max.message.bytes': maxMessageBytes,
-      'min.insync.replicas': minInsyncReplicas,
+      'min.insync.replicas': minInSyncReplicas,
       ...Object.values(customParams || {}).reduce(topicReducer, {}),
     },
   };