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
This commit is contained in:
parent
002e4db355
commit
7845476af1
10 changed files with 56 additions and 26 deletions
|
@ -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>
|
||||
|
|
|
@ -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'));
|
||||
});
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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, {}),
|
||||
},
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue