Skip to content

Commit

Permalink
Fix error when validating the form with non blocking validations
Browse files Browse the repository at this point in the history
issue: #102338
  • Loading branch information
machadoum committed Jul 6, 2021
1 parent dfc5dbb commit e8fb07d
Show file tree
Hide file tree
Showing 5 changed files with 228 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ export const ComboBoxField = ({ field, euiFieldProps = {}, idAria, ...rest }: Pr
const onCreateComboOption = (value: string) => {
// Note: for now, all validations for a comboBox array item have to be synchronous
// If there is a need to support asynchronous validation, we'll work on it (and will need to update the <EuiComboBox /> logic).
const { isValid } = field.validate({
const { errors } = field.validate({
value,
validationType: VALIDATION_TYPES.ARRAY_ITEM,
}) as FieldValidateResponse;

if (!isValid) {
if (errors.length > 0) {
// Return false to explicitly reject the user's input.
return false;
}
Expand All @@ -68,6 +68,12 @@ export const ComboBoxField = ({ field, euiFieldProps = {}, idAria, ...rest }: Pr
}
};

const onBlur = () => {
// Execute field validations. These validation run on the value of the entire field and not on every item.
// Ex: emptyField, validates that the field can't be empty.
field.validate({ value: field.value, validationType: VALIDATION_TYPES.FIELD });
};

return (
<EuiFormRow
label={field.label}
Expand All @@ -80,6 +86,7 @@ export const ComboBoxField = ({ field, euiFieldProps = {}, idAria, ...rest }: Pr
{...rest}
>
<EuiComboBox
onBlur={onBlur}
noSuggestions
placeholder={i18n.translate('esUi.forms.comboBoxField.placeHolderText', {
defaultMessage: 'Type and then hit "ENTER"',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/

import { act } from 'react-dom/test-utils';
import { registerTestBed } from '../shared_imports';

import { Form, UseField } from '../components';
import React from 'react';
import { useForm } from '.';
import { emptyField } from '../../helpers/field_validators';
import { FieldHook, FieldValidateResponse, VALIDATION_TYPES } from '..';

describe('useField() hook', () => {
describe('field.validate()', () => {
const EMPTY_VALUE = ' ';

test('It should not invalidate a field with arrayItem validation when isBlocking is false', async () => {
let fieldHook: FieldHook;

const TestField = ({ field }: { field: FieldHook }) => {
fieldHook = field;
return null;
};

const TestForm = () => {
const { form } = useForm();

return (
<Form form={form}>
<UseField
path="test-path"
component={TestField}
config={{
validations: [
{
validator: emptyField('error-message'),
type: VALIDATION_TYPES.ARRAY_ITEM,
isBlocking: false,
},
],
}}
/>
</Form>
);
};

registerTestBed(TestForm)();

let validateResponse: FieldValidateResponse;

await act(async () => {
validateResponse = await fieldHook!.validate({
value: EMPTY_VALUE,
validationType: VALIDATION_TYPES.ARRAY_ITEM,
});
});

// Expect a non-blocking validation error
expect(validateResponse!).toEqual({
isValid: true,
errors: [
{
code: 'ERR_FIELD_MISSING',
path: 'test-path',
message: 'error-message',
__isBlocking__: false,
validationType: 'arrayItem',
},
],
});

// expect the field to be valid because the validation error is non-blocking
expect(fieldHook!.isValid).toBe(true);
});

test('It should invalidate an arrayItem field when isBlocking is true', async () => {
let fieldHook: FieldHook;

const TestField = ({ field }: { field: FieldHook }) => {
fieldHook = field;
return null;
};

const TestForm = () => {
const { form } = useForm();

return (
<Form form={form}>
<UseField
path="test-path"
component={TestField}
config={{
validations: [
{
validator: emptyField('error-message'),
type: VALIDATION_TYPES.ARRAY_ITEM,
isBlocking: true,
},
],
}}
/>
</Form>
);
};

registerTestBed(TestForm)();

let validateResponse: FieldValidateResponse;

await act(async () => {
validateResponse = await fieldHook!.validate({
value: EMPTY_VALUE,
validationType: VALIDATION_TYPES.ARRAY_ITEM,
});
});

// Expect a blocking validation error
expect(validateResponse!).toEqual({
isValid: false,
errors: [
{
code: 'ERR_FIELD_MISSING',
path: 'test-path',
message: 'error-message',
__isBlocking__: true,
validationType: 'arrayItem',
},
],
});

// expect the field to be invalid because the validation error is blocking
expect(fieldHook!.isValid).toBe(false);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -365,9 +365,11 @@ export const useField = <T, FormType = FormData, I = T>(
return [...filteredErrors, ..._validationErrors];
});
}
// Don't take into account non blocker validation. Some are just warning (like trying to add a wrong ComboBox item)
const isValid = _validationErrors.filter((e) => e.__isBlocking__ !== false).length === 0;

return {
isValid: _validationErrors.length === 0,
isValid,
errors: _validationErrors,
};
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,16 @@ import React, { useEffect } from 'react';
import { act } from 'react-dom/test-utils';

import { registerTestBed, getRandomString, TestBed } from '../shared_imports';

import { emptyField } from '../../helpers/field_validators';
import { Form, UseField } from '../components';
import {
FormSubmitHandler,
OnUpdateHandler,
FormHook,
ValidationFunc,
FieldConfig,
} from '../types';
VALIDATION_TYPES,
} from '..';
import { useForm } from './use_form';

interface MyForm {
Expand Down Expand Up @@ -501,4 +502,74 @@ describe('useForm() hook', () => {
expect(isValid).toBeUndefined(); // Make sure it is "undefined" and not "false".
});
});

describe('form.validate()', () => {
test('should not invalidate a field with arrayItem validation when validating a form', async () => {
const TestComp = () => {
const { form } = useForm();
formHook = form;

return (
<Form form={form}>
<UseField
path="test-path"
config={{
validations: [
{
validator: emptyField('error-message'),
type: VALIDATION_TYPES.ARRAY_ITEM,
isBlocking: false,
},
],
}}
/>
</Form>
);
};

registerTestBed(TestComp)();

let isValid: boolean = false;

await act(async () => {
isValid = await formHook!.validate();
});

expect(isValid).toBe(true);
});

test('should invalidate a field with a blocking arrayItem validation when validating a form', async () => {
const TestComp = () => {
const { form } = useForm();
formHook = form;

return (
<Form form={form}>
<UseField
path="test-path"
config={{
validations: [
{
validator: emptyField('error-message'),
type: VALIDATION_TYPES.ARRAY_ITEM,
isBlocking: true,
},
],
}}
/>
</Form>
);
};

registerTestBed(TestComp)();

let isValid: boolean = false;

await act(async () => {
isValid = await formHook!.validate();
});

expect(isValid).toBe(false);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -97,12 +97,13 @@ export const ProcessorTypeField: FunctionComponent<Props> = ({ initialType }) =>
const onCreateComboOption = (value: string) => {
// Note: for now, all validations for a comboBox array item have to be synchronous
// If there is a need to support asynchronous validation, we'll work on it (and will need to update the <EuiComboBox /> logic).
const { isValid } = typeField.validate({

const { errors } = typeField.validate({
value,
validationType: VALIDATION_TYPES.ARRAY_ITEM,
}) as FieldValidateResponse;

if (!isValid) {
if (errors.length > 0) {
// Return false to explicitly reject the user's input.
return false;
}
Expand Down

0 comments on commit e8fb07d

Please sign in to comment.