UNPKG

@remoteoss/json-schema-form

Version:

Headless UI form powered by JSON Schemas

619 lines (581 loc) 27.2 kB
import { createSchemaWithRulesOnFieldA, createSchemaWithThreePropertiesWithRuleOnFieldA, multiRuleSchema, schemaWhereValidationAndComputedValueIsAppliedOnNormalThenStatement, schemaInlineComputedAttrForMaximumMinimumValues, schemaInlineComputedAttrForTitle, schemaWithBadOperation, schemaWithChecksAndThenValidationsOnThen, schemaWithComputedAttributeThatDoesntExist, schemaWithComputedAttributeThatDoesntExistDescription, schemaWithComputedAttributeThatDoesntExistTitle, schemaWithComputedAttributes, schemaWithComputedAttributesAndErrorMessages, schemaWithComputedValueChecksInIf, schemaWithDeepVarThatDoesNotExist, schemaWithDeepVarThatDoesNotExistOnFieldset, schemaWithGreaterThanChecksForThreeFields, schemaWithIfStatementWithComputedValuesAndValidationChecks, schemaWithInlineMultipleRulesForComputedAttributes, schemaWithInlineRuleForComputedAttributeWithCopy, schemaWithInlinedRuleOnComputedAttributeThatReferencesUnknownVar, schemaWithJSFLogicAndInlineRule, schemaWithMissingComputedValue, schemaWithMissingRule, schemaWithMultipleComputedValueChecks, schemaWithNativeAndJSONLogicChecks, schemaWithNonRequiredField, schemaWithPropertiesCheckAndValidationsInAIf, schemaWithPropertyThatDoesNotExistInThatLevelButDoesInFieldset, schemaWithTwoRules, schemaWithTwoValidationsWhereOneOfThemIsAppliedConditionally, schemaWithUnknownVariableInComputedValues, schemaWithUnknownVariableInValidations, schemaWithValidationThatDoesNotExistOnProperty, badSchemaThatWillNotSetAForcedValue, schemaWithReduceAccumulator, schemaWithComputedPresentationAttributes, } from './jsonLogic.fixtures'; import { mockConsole, restoreConsoleAndEnsureItWasNotCalled } from './testUtils'; import { createHeadlessForm } from '@/createHeadlessForm'; beforeEach(mockConsole); afterEach(restoreConsoleAndEnsureItWasNotCalled); describe('jsonLogic: cross-values validations', () => { describe('Does not conflict with native JSON schema', () => { it('Given an optional field and empty value, jsonLogic validations are ignored', () => { const { handleValidation } = createHeadlessForm(schemaWithNonRequiredField, { strictInputType: false, }); expect(handleValidation({}).formErrors).toBeUndefined(); expect(handleValidation({ field_a: 0, field_b: 10 }).formErrors).toEqual({ field_b: 'Must be greater than field_a', }); expect(handleValidation({ field_a: 'incorrect value' }).formErrors).toEqual({ field_a: 'The value must be a number', }); expect(handleValidation({ field_a: 11 }).formErrors).toBeUndefined(); }); it('Native validations have higher precedence than jsonLogic validations', () => { const { handleValidation } = createHeadlessForm(schemaWithNativeAndJSONLogicChecks, { strictInputType: false, }); expect(handleValidation({}).formErrors).toEqual({ field_a: 'Required field' }); expect(handleValidation({ field_a: 0 }).formErrors).toEqual({ field_a: 'Must be greater or equal to 100', }); expect(handleValidation({ field_a: 101 }).formErrors).toEqual({ field_a: 'Must be a multiple of 10', }); expect(handleValidation({ field_a: 110 }).formErrors).toBeUndefined(); }); }); describe('Relative: <, >, =', () => { it('bigger: field_a > field_b', () => { const schema = createSchemaWithRulesOnFieldA({ a_greater_than_b: { errorMessage: 'Field A must be bigger than field B', rule: { '>': [{ var: 'field_a' }, { var: 'field_b' }] }, }, }); const { handleValidation } = createHeadlessForm(schema, { strictInputType: false }); const { formErrors } = handleValidation({ field_a: 1, field_b: 2 }); expect(formErrors.field_a).toEqual('Field A must be bigger than field B'); expect(handleValidation({ field_a: 2, field_b: 0 }).formErrors).toEqual(undefined); }); it('smaller: field_a < field_b', () => { const schema = createSchemaWithRulesOnFieldA({ a_less_than_b: { errorMessage: 'Field A must be smaller than field B', rule: { '<': [{ var: 'field_a' }, { var: 'field_b' }] }, }, }); const { handleValidation } = createHeadlessForm(schema, { strictInputType: false }); const { formErrors } = handleValidation({ field_a: 2, field_b: 2 }); expect(formErrors.field_a).toEqual('Field A must be smaller than field B'); expect(handleValidation({ field_a: 0, field_b: 2 }).formErrors).toEqual(undefined); }); it('equal: field_a = field_b', () => { const schema = createSchemaWithRulesOnFieldA({ a_equals_b: { errorMessage: 'Field A must equal field B', rule: { '==': [{ var: 'field_a' }, { var: 'field_b' }] }, }, }); const { handleValidation } = createHeadlessForm(schema, { strictInputType: false }); const { formErrors } = handleValidation({ field_a: 3, field_b: 2 }); expect(formErrors.field_a).toEqual('Field A must equal field B'); expect(handleValidation({ field_a: 2, field_b: 2 }).formErrors).toEqual(undefined); }); }); describe('Incorrectly written schemas', () => { afterEach(() => console.error.mockClear()); const cases = [ [ 'x-jsf-logic.validations: throw when theres a missing rule', schemaWithMissingRule, '[json-schema-form] json-logic error: Validation "a_greater_than_ten" has missing rule.', ], [ 'x-jsf-logic.validations: throw when theres a value that does not exist in a rule', schemaWithUnknownVariableInValidations, '[json-schema-form] json-logic error: rule "a_equals_ten" has no variable "field_a".', ], [ 'x-jsf-logic.computedValues: throw when theres a value that does not exist in a rule', schemaWithUnknownVariableInComputedValues, '[json-schema-form] json-logic error: rule "a_times_ten" has no variable "field_a".', ], [ 'x-jsf-logic.computedValues: throw when theres a missing computed value', schemaWithMissingComputedValue, '[json-schema-form] json-logic error: Computed value "a_plus_ten" has missing rule.', ], [ 'x-jsf-logic-computedAttrs: error if theres a value that does not exist on an attribute.', schemaWithComputedAttributeThatDoesntExist, `[json-schema-form] json-logic error: Computed value "iDontExist" doesn't exist in field "field_a".`, ], [ 'x-jsf-logic-computedAttrs: error if theres a value that does not exist on a template string (title).', schemaWithComputedAttributeThatDoesntExistTitle, `[json-schema-form] json-logic error: Computed value "iDontExist" doesn't exist in field "field_a".`, ], [ 'x-jsf-logic-computedAttrs: error if theres a value that does not exist on a template string (description).', schemaWithComputedAttributeThatDoesntExistDescription, `[json-schema-form] json-logic error: Computed value "iDontExist" doesn't exist in field "field_a".`, ], [ 'x-jsf-logic-computedAttrs:, error if theres a value referenced that does not exist on an inline rule.', schemaWithInlinedRuleOnComputedAttributeThatReferencesUnknownVar, `[json-schema-form] json-logic error: fieldName "IdontExist" doesn't exist in field "field_a.x-jsf-logic-computedAttrs.title".`, ], [ 'x-jsf-logic.validations: error if a field does not exist in a deeply nested rule', schemaWithDeepVarThatDoesNotExist, '[json-schema-form] json-logic error: rule "dummy_rule" has no variable "field_b".', ], [ 'x-jsf-logic.validations: error if rule does not exist on a fieldset property', schemaWithDeepVarThatDoesNotExistOnFieldset, '[json-schema-form] json-logic error: rule "dummy_rule" has no variable "field_a".', ], [ 'x-jsf-validations: error if a validation name does not exist', schemaWithValidationThatDoesNotExistOnProperty, `[json-schema-form] json-logic error: "field_a" required validation "iDontExist" doesn't exist.`, ], [ 'x-jsf-logic.validations: A top level logic keyword will not be able to reference fieldset properties', schemaWithPropertyThatDoesNotExistInThatLevelButDoesInFieldset, '[json-schema-form] json-logic error: rule "validation_parent" has no variable "child".', ], [ 'x-jsf-logic.validations: error if unknown operation', schemaWithBadOperation, '[json-schema-form] json-logic error: in "badOperator" rule there is an unknown operator "++".', ], ]; test.each(cases)('%p', (_, schema, expectedErrorString) => { const { error } = createHeadlessForm(schema, { strictInputType: false }); const expectedError = new Error(expectedErrorString); expect(console.error).toHaveBeenCalledWith('JSON Schema invalid!', expectedError); expect(error).toEqual(expectedError); }); }); describe('Arithmetic: +, -, *, /', () => { it('multiple: field_a > field_b * 2', () => { const schema = createSchemaWithRulesOnFieldA({ a_greater_than_b_multiplied_by_2: { errorMessage: 'Field A must be at least twice as big as field b', rule: { '>': [{ var: 'field_a' }, { '*': [{ var: 'field_b' }, 2] }] }, }, }); const { handleValidation } = createHeadlessForm(schema, { strictInputType: false }); const { formErrors } = handleValidation({ field_a: 1, field_b: 4 }); expect(formErrors.field_a).toEqual('Field A must be at least twice as big as field b'); expect(handleValidation({ field_a: 3, field_b: 1 }).formErrors).toEqual(undefined); }); it('divide: field_a > field_b / 2', () => { const { handleValidation } = createHeadlessForm( createSchemaWithRulesOnFieldA({ a_greater_than_b_divided_by_2: { errorMessage: 'Field A must be greater than field_b / 2', rule: { '>': [{ var: 'field_a' }, { '/': [{ var: 'field_b' }, 2] }] }, }, }), { strictInputType: false } ); expect(handleValidation({ field_a: 2, field_b: 4 }).formErrors).toEqual({ field_a: 'Field A must be greater than field_b / 2', }); expect(handleValidation({ field_a: 3, field_b: 5 }).formErrors).toEqual(undefined); }); it('sum: field_a > field_b + field_c', () => { const schema = createSchemaWithThreePropertiesWithRuleOnFieldA({ a_is_greater_than_b_plus_c: { errorMessage: 'Field A must be greater than field_b and field_b added together', rule: { '>': [{ var: 'field_a' }, { '+': [{ var: 'field_b' }, { var: 'field_c' }] }], }, }, }); const { handleValidation } = createHeadlessForm(schema, { strictInputType: false }); const { formErrors } = handleValidation({ field_a: 0, field_b: 1, field_c: 2 }); expect(formErrors.field_a).toEqual( 'Field A must be greater than field_b and field_b added together' ); expect(handleValidation({ field_a: 4, field_b: 1, field_c: 2 }).formErrors).toEqual( undefined ); }); }); // TODO: Implement this test. describe.skip('Reduce', () => { it('reduce: working_hours_per_day * work_days', () => { const { fields, handleValidation } = createHeadlessForm(schemaWithReduceAccumulator, { strictInputType: false, }); handleValidation({ work_days: ['monday', 'tuesday'], working_hours_per_day: 8, }); const field = fields.find((i) => i.name === 'working_hours_per_week'); expect(field.const).toEqual(16); expect(field.default).toEqual(16); expect(field.label).toEqual('16 hours per week'); }); }); describe('Logical: ||, &&', () => { it('AND: field_a > field_b && field_a > field_c (implicit with multiple rules in a single field)', () => { const schema = createSchemaWithThreePropertiesWithRuleOnFieldA({ a_is_greater_than_b: { errorMessage: 'Field A must be greater than field_b', rule: { '>': [{ var: 'field_a' }, { var: 'field_b' }], }, }, a_is_greater_than_c: { errorMessage: 'Field A must be greater than field_c', rule: { '>': [{ var: 'field_a' }, { var: 'field_c' }], }, }, }); const { handleValidation } = createHeadlessForm(schema, { strictInputType: false }); expect(handleValidation({ field_a: 1, field_b: 10, field_c: 0 }).formErrors.field_a).toEqual( 'Field A must be greater than field_b' ); expect(handleValidation({ field_a: 1, field_b: 0, field_c: 10 }).formErrors.field_a).toEqual( 'Field A must be greater than field_c' ); expect(handleValidation({ field_a: 10, field_b: 5, field_c: 5 }).formErrors).toEqual( undefined ); }); it('OR: field_a > field_b or field_a > field_c', () => { const schema = createSchemaWithThreePropertiesWithRuleOnFieldA({ field_a_is_greater_than_b_or_c: { errorMessage: 'Field A must be greater than field_b or field_c', rule: { or: [ { '>': [{ var: 'field_a' }, { var: 'field_b' }] }, { '>': [{ var: 'field_a' }, { var: 'field_c' }] }, ], }, }, }); const { handleValidation } = createHeadlessForm(schema, { strictInputType: false }); expect(handleValidation({ field_a: 0, field_b: 10, field_c: 10 }).formErrors.field_a).toEqual( 'Field A must be greater than field_b or field_c' ); expect(handleValidation({ field_a: 1, field_b: 0, field_c: 10 }).formErrors).toEqual( undefined ); expect(handleValidation({ field_a: 10, field_b: 5, field_c: 5 }).formErrors).toEqual( undefined ); }); }); describe('Multiple validations', () => { it('two rules: A > B; A is even', () => { const { handleValidation } = createHeadlessForm(multiRuleSchema, { strictInputType: false }); expect(handleValidation({ field_a: 1 }).formErrors).toEqual({ field_a: 'A must be even', field_b: 'Required field', }); expect(handleValidation({ field_a: 1, field_b: 2 }).formErrors).toEqual({ field_a: 'A must be bigger than B', }); expect(handleValidation({ field_a: 3, field_b: 2 }).formErrors).toEqual({ field_a: 'A must be even', }); expect(handleValidation({ field_a: 4, field_b: 2 }).formErrors).toEqual(undefined); }); it('2 seperate fields with rules failing', () => { const { handleValidation } = createHeadlessForm(schemaWithTwoRules, { strictInputType: false, }); expect(handleValidation({ field_a: 1, field_b: 3 }).formErrors).toEqual({ field_a: 'A must be bigger than B', field_b: 'B must be even', }); expect(handleValidation({ field_a: 4, field_b: 2 }).formErrors).toEqual(undefined); }); }); describe('Derive values', () => { it('field_b is field_a * 2', () => { const { fields, handleValidation } = createHeadlessForm(schemaWithComputedAttributes, { strictInputType: false, initialValues: { field_a: 2 }, }); const fieldB = fields.find((i) => i.name === 'field_b'); expect(fieldB.description).toEqual( 'This field is 2 times bigger than field_a with value of 4.' ); expect(fieldB.default).toEqual(4); expect(fieldB.forcedValue).toEqual(4); handleValidation({ field_a: 4 }); expect(fieldB.default).toEqual(8); expect(fieldB.label).toEqual('This is 8!'); }); it('A forced value will not be set when const and default are not equal', () => { const { fields } = createHeadlessForm(badSchemaThatWillNotSetAForcedValue, { strictInputType: false, initialValues: { field_a: 2 }, }); expect(fields[1]).toMatchObject({ const: 6, default: 4 }); expect(fields[1]).not.toMatchObject({ forcedValue: expect.any(Number) }); }); it('Derived errorMessages and statements work', () => { const { fields, handleValidation } = createHeadlessForm( schemaWithComputedAttributesAndErrorMessages, { strictInputType: false } ); const fieldB = fields.find((i) => i.name === 'field_b'); expect(handleValidation({ field_a: 2, field_b: 0 }).formErrors).toEqual({ field_b: 'Must be bigger than 4', }); expect(handleValidation({ field_a: 2, field_b: 100 }).formErrors).toEqual({ field_b: 'Must be smaller than 8', }); expect(fieldB.minimum).toEqual(4); expect(fieldB.maximum).toEqual(8); expect(fieldB.statement).toEqual({ description: 'Must be bigger than 4 and smaller than 8' }); }); it('presentation attributes work', () => { const { fields, handleValidation } = createHeadlessForm( schemaWithComputedPresentationAttributes, { strictInputType: false } ); const fieldB = fields.find((i) => i.name === 'amount'); expect(handleValidation({ currency: 'UGX' }).formErrors).toEqual(undefined); expect(fieldB.inputType).toEqual('money'); expect(fieldB.currency).toEqual('UGX'); }); it('Use a inline-rule in a schema for a title attribute', () => { const { fields, handleValidation } = createHeadlessForm( schemaWithInlineRuleForComputedAttributeWithCopy, { strictInputType: false, } ); const [, fieldB] = fields; expect(handleValidation({ field_a: 0, field_b: null }).formErrors).toEqual(undefined); expect(fieldB.label).toEqual('I need this to work using the 10.'); expect(handleValidation({ field_a: 10 }).formErrors).toEqual(undefined); expect(fieldB.label).toEqual('I need this to work using the 20.'); }); it('Use multiple inline rules with different identifiers', () => { const { fields, handleValidation } = createHeadlessForm( schemaWithInlineMultipleRulesForComputedAttributes, { strictInputType: false, } ); const [, fieldB] = fields; expect(handleValidation({ field_a: 10, field_b: null }).formErrors).toEqual(undefined); expect(fieldB.description).toEqual('Must be between 5 and 20.'); }); it('Use an inline rule in a schema for a title but it just uses the value', () => { const { fields, handleValidation } = createHeadlessForm(schemaInlineComputedAttrForTitle, { strictInputType: false, }); const [, fieldB] = fields; expect(handleValidation({ field_a: 10, field_b: null }).formErrors).toEqual(undefined); expect(fieldB.label).toEqual('20'); }); it('Use an inline rule for a minimum, maximum value', () => { const { fields, handleValidation } = createHeadlessForm( schemaInlineComputedAttrForMaximumMinimumValues, { strictInputType: false, } ); const [, fieldB] = fields; // FIXME: We are currently setting NaN here because of how the data clean up works for json-logic // We should probably set this as undefined when theres no values set? // tracked in INF-53. expect(fieldB).toMatchObject({ minimum: NaN, maximum: NaN }); expect(handleValidation({ field_a: 10, field_b: null }).formErrors).toBeUndefined(); expect(fieldB).toMatchObject({ minimum: 0, maximum: 20 }); expect(handleValidation({ field_a: 50, field_b: 20 }).formErrors).toEqual({ field_b: 'Must be greater or equal to 40', }); expect(fieldB).toMatchObject({ minimum: 40, maximum: 60 }); expect(handleValidation({ field_a: 50, field_b: 70 }).formErrors).toEqual({ field_b: 'Must be smaller or equal to 60', }); expect(fieldB).toMatchObject({ minimum: 40, maximum: 60 }); expect(handleValidation({ field_a: 50, field_b: 50 }).formErrors).toBeUndefined(); expect(fieldB).toMatchObject({ minimum: 40, maximum: 60 }); }); it('Mix use of multiple inline rules and an external rule', () => { const { fields, handleValidation } = createHeadlessForm(schemaWithJSFLogicAndInlineRule, { strictInputType: false, }); handleValidation({ field_a: 10 }); const [, fieldB] = fields; expect(fieldB.label).toEqual('Going to use 20 and 4'); }); }); describe('Conditionals', () => { it('when field_a > field_b, show field_c', () => { const { fields, handleValidation } = createHeadlessForm( schemaWithGreaterThanChecksForThreeFields, { strictInputType: false } ); const fieldC = fields.find((i) => i.name === 'field_c'); expect(fieldC.isVisible).toEqual(false); expect(handleValidation({ field_a: 1, field_b: 3 }).formErrors).toEqual(undefined); expect(handleValidation({ field_a: 1 }).formErrors).toEqual({ field_b: 'Required field', }); expect(handleValidation({ field_a: 1, field_b: undefined }).formErrors).toEqual({ field_b: 'Required field', }); expect(handleValidation({ field_a: 10, field_b: 3 }).formErrors).toEqual({ field_c: 'Required field', }); expect(fieldC.isVisible).toEqual(true); expect(handleValidation({ field_a: 10, field_b: 3, field_c: 0 }).formErrors).toEqual( undefined ); }); it('A schema with both a `x-jsf-validations` and `properties` check', () => { const { fields, handleValidation } = createHeadlessForm( schemaWithPropertiesCheckAndValidationsInAIf, { strictInputType: false } ); const fieldC = fields.find((i) => i.name === 'field_c'); expect(handleValidation({ field_a: 1, field_b: 3 }).formErrors).toEqual(undefined); expect(fieldC.isVisible).toEqual(false); expect(handleValidation({ field_a: 10, field_b: 3 }).formErrors).toEqual({ field_c: 'Required field', }); expect(fieldC.isVisible).toEqual(true); expect(handleValidation({ field_a: 5, field_b: 3 }).formErrors).toEqual(undefined); }); it('Conditionally apply a validation on a property depending on values', () => { const { fields, handleValidation } = createHeadlessForm( schemaWithChecksAndThenValidationsOnThen, { strictInputType: false } ); const cField = fields.find((i) => i.name === 'field_c'); expect(cField.isVisible).toEqual(false); expect(cField.description).toEqual(undefined); expect(handleValidation({ field_a: 10, field_b: 5 }).formErrors).toEqual({ field_c: 'Required field', }); expect(handleValidation({ field_a: 10, field_b: 5, field_c: 0 }).formErrors).toEqual({ field_c: 'Needs more numbers', }); expect(cField.description).toBe('I am a description!'); expect(handleValidation({ field_a: 10, field_b: 5, field_c: 201 }).formErrors).toEqual( undefined ); expect(handleValidation({ field_a: 5, field_b: 10 }).formErrors).toBeUndefined(); expect(cField).toMatchObject({ isVisible: false, // description: null, the description will currently be `I am a description!`, how do we change it back to null from here? Needs to be fixed by RMT-58 }); }); it('Should apply a conditional based on a true computedValue', () => { const { fields, handleValidation } = createHeadlessForm(schemaWithComputedValueChecksInIf, { strictInputType: false, }); const cField = fields.find((i) => i.name === 'field_c'); expect(cField.isVisible).toEqual(false); expect(cField.description).toEqual(undefined); expect(handleValidation({ field_a: 10, field_b: 5 }).formErrors).toEqual({ field_c: 'Required field', }); expect(handleValidation({ field_a: 10, field_b: 5, field_c: 201 }).formErrors).toEqual( undefined ); }); it('Handle multiple computedValue checks by ANDing them together', () => { const { handleValidation } = createHeadlessForm(schemaWithMultipleComputedValueChecks, { strictInputType: false, }); expect(handleValidation({}).formErrors).toEqual({ field_a: 'Required field', field_b: 'Required field', }); expect(handleValidation({ field_a: 10, field_b: 8 }).formErrors).toEqual({ field_c: 'Required field', }); expect(handleValidation({ field_a: 10, field_b: 8, field_c: 0 }).formErrors).toEqual({ field_c: 'Must be two times B', }); expect(handleValidation({ field_a: 10, field_b: 8, field_c: 17 }).formErrors).toEqual( undefined ); }); it('Handle having a true condition with both validations and computedValue checks', () => { const { handleValidation } = createHeadlessForm( schemaWithIfStatementWithComputedValuesAndValidationChecks, { strictInputType: false } ); expect(handleValidation({ field_a: 1, field_b: 1 }).formErrors).toEqual(undefined); expect(handleValidation({ field_a: 10, field_b: 20 }).formErrors).toEqual(undefined); expect(handleValidation({ field_a: 10, field_b: 9 }).formErrors).toEqual({ field_c: 'Required field', }); expect(handleValidation({ field_a: 10, field_b: 9, field_c: 10 }).formErrors).toEqual( undefined ); }); it('Apply validations and computed values on normal if statement.', () => { const { fields, handleValidation } = createHeadlessForm( schemaWhereValidationAndComputedValueIsAppliedOnNormalThenStatement, { strictInputType: false } ); expect(handleValidation({ field_a: 0, field_b: 0 }).formErrors).toEqual(undefined); expect(handleValidation({ field_a: 20, field_b: 0 }).formErrors).toEqual({ field_b: 'Must be greater than Field A + 10', }); const [, fieldB] = fields; expect(fieldB.label).toEqual('Must be greater than 30.'); expect(handleValidation({ field_a: 20, field_b: 31 }).formErrors).toEqual(undefined); }); it('When we have a required validation on a top level property and another validation is added, both should be accounted for.', () => { const { handleValidation } = createHeadlessForm( schemaWithTwoValidationsWhereOneOfThemIsAppliedConditionally, { strictInputType: false } ); expect(handleValidation({ field_a: 10, field_b: 0 }).formErrors).toEqual({ field_b: 'Must be greater than A', }); expect(handleValidation({ field_a: 10, field_b: 20 }).formErrors).toEqual(undefined); expect(handleValidation({ field_a: 20, field_b: 10 }).formErrors).toEqual({ field_b: 'Must be greater than A', }); expect(handleValidation({ field_a: 20, field_b: 21 }).formErrors).toEqual({ field_b: 'Must be greater than two times A', }); expect(handleValidation({ field_a: 20, field_b: 41 }).formErrors).toEqual(); }); }); });