diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Examples.tsx index 6e8b3ff8249..43f41433fe6 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Examples.tsx @@ -46,7 +46,7 @@ export const CreateBasicFieldComponent = () => { const preparedProps = { label: 'What is the secret of this field?', fromInput, - validator: (value) => { + onChangeValidator: (value) => { if (value === 'secret') { return new Error('Do not reveal the secret!') } diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Handler/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Handler/Examples.tsx index bcec3cdca64..49d5373c001 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Handler/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/Handler/Examples.tsx @@ -157,7 +157,7 @@ export const AsyncChangeAndValidation = () => { label='Type "valid" to validate the field' path="/myField" required - validator={validator} + onChangeValidator={validator} onChange={onChangeField} autoComplete="off" /> diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/SubmitIndicator/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/SubmitIndicator/Examples.tsx index a124f1ad4c3..5ac5347ffd7 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/SubmitIndicator/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Form/SubmitIndicator/Examples.tsx @@ -60,7 +60,7 @@ export const AsyncChangeBehavior = () => { path="/myField1" label="Label (with async validation)" placeholder="Write something ..." - validator={delay} + onChangeValidator={delay} /> { { + onChangeValidator={(arrayValue) => { const findFirstDuplication = (arr) => arr.findIndex((e, i) => arr.indexOf(e) !== i) @@ -535,7 +535,7 @@ export const WithArrayValidator = () => { { + onChangeValidator={(arrayValue) => { if (!(arrayValue && arrayValue.length > 1)) { return new Error('You need at least two items') } diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/Container/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/Container/Examples.tsx index 728efbe86fd..2d23ca2e142 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/Container/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/Wizard/Container/Examples.tsx @@ -178,13 +178,13 @@ export const AsyncWizardContainer = () => { diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/String/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/String/Examples.tsx index 44270be7695..ff177a77e50 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/String/Examples.tsx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/String/Examples.tsx @@ -316,13 +316,13 @@ export const ValidatePattern = () => { ) } -export const SynchronousExternalValidator = () => { +export const SynchronousExternalChangeValidator = () => { return ( + onChangeValidator={(value) => value.length < 4 ? Error('At least 4 characters') : undefined } onChange={(value) => console.log('onChange', value)} @@ -331,13 +331,13 @@ export const SynchronousExternalValidator = () => { ) } -export const AsynchronousExternalValidator = () => { +export const AsynchronousExternalChangeValidator = () => { return ( + onChangeValidator={(value) => new Promise((resolve) => setTimeout( () => diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/String/demos.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/String/demos.mdx index 8694985c37e..5497f8c66b1 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/String/demos.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/base-fields/String/demos.mdx @@ -74,11 +74,11 @@ This example demonstrates how the status message width adjusts according to the ### Synchronous external validator (called on every change) - + ### Asynchronous external validator (called on every change) - + ### Synchronous external validator (called on blur) diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/create-component/useFieldProps/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/create-component/useFieldProps/info.mdx index c96f6287005..d7991787276 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/create-component/useFieldProps/info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/create-component/useFieldProps/info.mdx @@ -80,8 +80,8 @@ All properties are optional and can be used as needed. These properties can be p - `required` if true, it will call `validateRequired` for validation. - `schema` or `pattern` for JSON schema validation powered by [ajv](https://ajv.js.org/). -- `validator` your custom validation function. It will run on every keystroke. Can be an async function. Use it together with [debounceAsync](/uilib/helpers/functions/#debounce). -- `onBlurValidator` your custom validation function. It will run on a `handleBlur()` call. Use it over `validator` for validations with side-effects. Can be an async function. +- `onChangeValidator` your custom validation function. It will run on every keystroke. Can be an async function. Use it together with [debounceAsync](/uilib/helpers/functions/#debounce). +- `onBlurValidator` your custom validation function. It will run on a `handleBlur()` call. Use it over `onChangeValidator` for validations with side-effects. Can be an async function. - `validateRequired` does allow you to provide a custom logic for how the `required` property should validate. See example down below. - `validateInitially` in order to show an error without a change and blur event. Used for rare cases. - `validateUnchanged` in order to validate without a change and blur event. Used for rare cases. @@ -166,7 +166,8 @@ During validation, the different APIs do have a prioritization order and will st 1. `required` property 1. `schema` property (including `pattern`) -1. `validator` property +1. `onChangeValidator` property +1. `onBlurValidator` property ### Error handling diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/BankAccountNumber/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/BankAccountNumber/info.mdx index adf6216e669..e285bd6e2e8 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/BankAccountNumber/info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/BankAccountNumber/info.mdx @@ -20,5 +20,5 @@ There is a corresponding [Value.BankAccountNumber](/uilib/extensions/forms/Value ### Internal validators exposed -`Field.BankAccountNumber` expose the `bankAccountNumberValidator` validator through its `validator` and `onBlurValidator` property, take a look at [this demo](/uilib/extensions/forms/feature-fields/BankAccountNumber/demos/#extend-validation-with-custom-validation-function). +`Field.BankAccountNumber` expose the `bankAccountNumberValidator` validator through its `onChangeValidator` and `onBlurValidator` property, take a look at [this demo](/uilib/extensions/forms/feature-fields/BankAccountNumber/demos/#extend-validation-with-custom-validation-function). The `bankAccountNumberValidator` validator, validates if the bank account number provided is a [Norwegian bank account number](https://no.wikipedia.org/wiki/Kontonummer) or not. diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/NationalIdentityNumber/demos.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/NationalIdentityNumber/demos.mdx index 446131cc0b7..844530b2ac5 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/NationalIdentityNumber/demos.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/NationalIdentityNumber/demos.mdx @@ -60,7 +60,7 @@ Below is an example of the error message displayed when there's an invalid D num ### Validation function -You can provide your own validation function, either to `validator` or `onBlurValidator`. +You can provide your own validation function, either to `onChangeValidator` or `onBlurValidator`. diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/NationalIdentityNumber/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/NationalIdentityNumber/info.mdx index b3beafc20cf..3af48203034 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/NationalIdentityNumber/info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/NationalIdentityNumber/info.mdx @@ -23,7 +23,7 @@ There is a corresponding [Value.NationalIdentityNumber](/uilib/extensions/forms/ ### Internal validators exposed -`Field.NationalIdentityNumber` expose the following validators through its `validator` and `onBlurValidator` property: +`Field.NationalIdentityNumber` expose the following validators through its `onChangeValidator` and `onBlurValidator` property: - `dnrValidator`: validates a D number. - `fnrValidator`: validates a national identity number (fødselsnummer). diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/OrganizationNumber/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/OrganizationNumber/info.mdx index 64cfad6512a..a6eedb8e83e 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/OrganizationNumber/info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/OrganizationNumber/info.mdx @@ -21,5 +21,5 @@ There is a corresponding [Value.OrganizationNumber](/uilib/extensions/forms/Valu ### Internal validators exposed -`Field.OrganizationNumber` expose the `organizationNumberValidator` validator through its `validator` and `onBlurValidator` property, take a look at [this demo](/uilib/extensions/forms/feature-fields/OrganizationNumber/demos/#extend-validation-with-custom-validation-function). +`Field.OrganizationNumber` expose the `organizationNumberValidator` validator through its `onChangeValidator` and `onBlurValidator` property, take a look at [this demo](/uilib/extensions/forms/feature-fields/OrganizationNumber/demos/#extend-validation-with-custom-validation-function). The `organizationNumberValidator` validator, validates if the organization number provided is a [Norwegian organization number](https://www.brreg.no/om-oss/registrene-vare/om-enhetsregisteret/organisasjonsnummeret/) or not. diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/PhoneNumber/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/PhoneNumber/info.mdx index 4f84024a570..78ffab6ce97 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/PhoneNumber/info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/PhoneNumber/info.mdx @@ -46,7 +46,7 @@ Countries are sorted in alphabetically order, with the following prioritized cou ## Validation -By default it has no validation. But you can enable it by giving a `required`, `pattern`, `schema` or `validator` property with the needed validation. More about validation in the [Getting Started](/uilib/extensions/forms/getting-started/#validation) section. +By default it has no validation. But you can enable it by giving a `required`, `pattern`, `schema`, `onChangeValidator` or `onBlurValidator` property with the needed validation. More about validation in the [Getting Started](/uilib/extensions/forms/getting-started/#validation) section. ### Norwegian mobile numbers diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/more-fields/Password/info.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/more-fields/Password/info.mdx index 6cc6d4d8378..61301f8cc6b 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/more-fields/Password/info.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/feature-fields/more-fields/Password/info.mdx @@ -13,4 +13,4 @@ render() ## Validation -By default it has no validation. But you can enable it by giving a `required`, `pattern`, `schema` or `validator` property with the needed validation. More about validation in the [Getting Started](/uilib/extensions/forms/getting-started/#validation) section. +By default it has no validation. But you can enable it by giving a `required`, `pattern`, `schema`, `onChangeValidator` or `onBlurValidator` property with the needed validation. More about validation in the [Getting Started](/uilib/extensions/forms/getting-started/#validation) section. diff --git a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/getting-started.mdx b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/getting-started.mdx index ec4929c6141..ed2dfa82abb 100644 --- a/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/getting-started.mdx +++ b/packages/dnb-design-system-portal/src/docs/uilib/extensions/forms/getting-started.mdx @@ -42,7 +42,7 @@ import AsyncChangeExample from './Form/Handler/parts/async-change-example.mdx' - [required](#required) - [pattern](#pattern) - [schema](#schema) - - [onBlurValidator and validator](#onblurvalidator-and-validator) + - [onBlurValidator and onChangeValidator](#onblurvalidator-and-onchangevalidator) - [Connect with another field](#connect-with-another-field) - [Async validation](#async-validation) - [Async validator with debounce](#async-validator-with-debounce) @@ -390,13 +390,13 @@ More info about the async change behavior in the form [Handler](/uilib/extension #### Async field validation -A similar indicator behavior will occur when using async functions for field validation, such as `validator` or `onBlurValidation`, your form will exhibit async behavior. This means that the validation needs to be successfully completed before the form can be submitted. +A similar indicator behavior will occur when using async functions for field validation, such as `onChangeValidator` or `onBlurValidation`, your form will exhibit async behavior. This means that the validation needs to be successfully completed before the form can be submitted. ### Validation and error handling Every field component has a built-in validation that is based on the type of data it handles. This validation is automatically applied to the field when the user interacts with it. The validation is also applied when the user submits the form. -In addition, you can add your own validation to a field component. This is done by adding a `required`, `pattern`, `schema` or `validator` property. +In addition, you can add your own validation to a field component. This is done by adding a `required`, `pattern`, `schema`, `onChangeValidator` or `onBlurValidation` property. Fields which have the `disabled` property or the `readOnly` property, will skip validation. @@ -487,9 +487,9 @@ const schema = { ``` -#### onBlurValidator and validator +#### onBlurValidator and onChangeValidator -The `onBlurValidator` and `validator` properties accepts a function that takes the current value of the field as an argument and returns an error message if the value is invalid: +The `onBlurValidator` and `onChangeValidator` properties accepts a function that takes the current value of the field as an argument and returns an error message if the value is invalid: ```tsx const onChangeValidator = (value) => { @@ -498,14 +498,14 @@ const onChangeValidator = (value) => { return new Error('Invalid value message') } } -render() +render() ``` You can find more info about error messages in the [error messages](/uilib/extensions/forms/Form/error-messages/) docs. ##### Connect with another field -You can also use the `connectWithPath` function to connect the validator to another field. This allows you to rerun the validator function once the value of the connected field changes: +You can also use the `connectWithPath` function to connect the validator (`onChangeValidator` and `onBlurValidator`) to another field. This allows you to rerun the validator function once the value of the connected field changes: ```tsx import { Form, Field } from '@dnb/eufemia/extensions/forms' @@ -523,15 +523,15 @@ render( , ) ``` -By default, the validator function will only run when the "/withValidator" field is changed. When the error message is shown, it will update the message with the new value of the "/myReference" field. +By default, the `onChangeValidator` function will only run when the "/withOnChangeValidator" field is changed. When the error message is shown, it will update the message with the new value of the "/myReference" field. You can also change this behavior for testing purposes by using the following properties: @@ -576,7 +576,7 @@ const onChangeValidator = debounceAsync(async function myValidator(value) { return error } }) -render() +render() ``` ### Localization and translation diff --git a/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/Provider.tsx b/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/Provider.tsx index ae0e7f08457..8d7660d0a9a 100644 --- a/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/Provider.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/Provider.tsx @@ -603,7 +603,10 @@ export default function Provider( for (const path in fieldPropsRef.current) { if (mountedFieldsRef.current[path]?.isMounted) { const props = fieldPropsRef.current[path] - if (isAsync(props.validator) || isAsync(props.onBlurValidator)) { + if ( + isAsync(props.onChangeValidator) || + isAsync(props.onBlurValidator) + ) { return true } } diff --git a/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/__tests__/Provider.test.tsx b/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/__tests__/Provider.test.tsx index eebcf937713..dbb5210eb6b 100644 --- a/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/__tests__/Provider.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/DataContext/Provider/__tests__/Provider.test.tsx @@ -1082,7 +1082,7 @@ describe('DataContext.Provider', () => { expect(onSubmit).toHaveBeenCalledTimes(1) }) - describe('should evaluate long validator and onBlurValidator before continue with async onSubmit', () => { + describe('should evaluate long onChangeValidator and onBlurValidator before continue with async onSubmit', () => { let eventsStart = [] let eventsEnd = [] @@ -1110,12 +1110,12 @@ describe('DataContext.Provider', () => { eventsEnd.push('onChangeField') } - const validator = async () => { - eventsStart.push('validator') + const onChangeValidator = async () => { + eventsStart.push('onChangeValidator') await wait(10) - eventsEnd.push('validator') + eventsEnd.push('onChangeValidator') } const onBlurValidator = async () => { @@ -1135,7 +1135,7 @@ describe('DataContext.Provider', () => { @@ -1152,7 +1152,7 @@ describe('DataContext.Provider', () => { await waitFor(() => { expect(eventsStart).toEqual([ - 'validator', + 'onChangeValidator', 'onChangeForm', 'onChangeField', ]) @@ -1168,7 +1168,7 @@ describe('DataContext.Provider', () => { @@ -1186,12 +1186,12 @@ describe('DataContext.Provider', () => { await wait(100) expect(eventsStart).toEqual([ - 'validator', + 'onChangeValidator', 'onBlurValidator', 'onSubmit', ]) expect(eventsEnd).toEqual([ - 'validator', + 'onChangeValidator', 'onBlurValidator', 'onSubmit', ]) @@ -1203,10 +1203,10 @@ describe('DataContext.Provider', () => { await wait(10) return { info: 'Info message' } as const }) - const validator = jest.fn(async (value) => { + const onChangeValidator = jest.fn(async (value) => { await wait(10) - if (value === 'validator-error') { - return new Error('validator-error') + if (value === 'onChangeValidator-error') { + return new Error('onChangeValidator-error') } }) const onBlurValidator = jest.fn(async (value) => { @@ -1220,7 +1220,7 @@ describe('DataContext.Provider', () => { @@ -1250,12 +1250,12 @@ describe('DataContext.Provider', () => { await waitFor(() => { expect(onSubmit).toHaveBeenCalledTimes(0) expect(onBlurValidator).toHaveBeenCalledTimes(0) - expect(validator).toHaveBeenCalledTimes(0) + expect(onChangeValidator).toHaveBeenCalledTimes(0) }) // Use fireEvent over userEvent, because of its sync nature fireEvent.change(input, { - target: { value: 'validator-error' }, + target: { value: 'onChangeValidator-error' }, }) await waitFor(() => { @@ -1266,13 +1266,13 @@ describe('DataContext.Provider', () => { const status = document.querySelector( '.dnb-forms-field-block .dnb-form-status' ) - expect(status).toHaveTextContent('validator-error') + expect(status).toHaveTextContent('onChangeValidator-error') }) await waitFor(() => { expect(onSubmit).toHaveBeenCalledTimes(0) expect(onBlurValidator).toHaveBeenCalledTimes(0) - expect(validator).toHaveBeenCalledTimes(1) + expect(onChangeValidator).toHaveBeenCalledTimes(1) }) // Use fireEvent over userEvent, because of its sync nature @@ -1294,7 +1294,7 @@ describe('DataContext.Provider', () => { await waitFor(() => { expect(onSubmit).toHaveBeenCalledTimes(0) expect(onBlurValidator).toHaveBeenCalledTimes(0) - expect(validator).toHaveBeenCalledTimes(2) + expect(onChangeValidator).toHaveBeenCalledTimes(2) }) // Use fireEvent over userEvent, because of its sync nature @@ -1309,7 +1309,7 @@ describe('DataContext.Provider', () => { await waitFor(() => { expect(onSubmit).toHaveBeenCalledTimes(0) expect(onBlurValidator).toHaveBeenCalledTimes(1) - expect(validator).toHaveBeenCalledTimes(2) + expect(onChangeValidator).toHaveBeenCalledTimes(2) }) await userEvent.click(submitButton) @@ -1317,7 +1317,7 @@ describe('DataContext.Provider', () => { await waitFor(() => { expect(onSubmit).toHaveBeenCalledTimes(0) expect(onBlurValidator).toHaveBeenCalledTimes(1) - expect(validator).toHaveBeenCalledTimes(2) + expect(onChangeValidator).toHaveBeenCalledTimes(2) }) await waitFor(() => { @@ -1345,20 +1345,20 @@ describe('DataContext.Provider', () => { await waitFor(() => { expect(onSubmit).toHaveBeenCalledTimes(1) expect(onBlurValidator).toHaveBeenCalledTimes(3) - expect(validator).toHaveBeenCalledTimes(12) + expect(onChangeValidator).toHaveBeenCalledTimes(12) }) }) - it('should set "formState" to "pending" when "validator" is async', async () => { + it('should set "formState" to "pending" when "onChangeValidator" is async', async () => { const result = createRef() - const validator = async () => { + const onChangeValidator = async () => { return new Error('My error') } const { rerender } = render( - + ) @@ -1375,14 +1375,14 @@ describe('DataContext.Provider', () => { expect(result.current.formState).toBeUndefined() }) - const syncValidator = () => { + const syncOnChangeValidator = () => { return new Error('My error') } rerender( - + ) @@ -1496,7 +1496,7 @@ describe('DataContext.Provider', () => { .fn() .mockImplementation(async () => null) - const validator = debounceAsync(async (value) => { + const onChangeValidator = debounceAsync(async (value) => { await wait(400) if (value === 'invalid') { return Error('My error') @@ -1508,7 +1508,7 @@ describe('DataContext.Provider', () => { @@ -1593,7 +1593,7 @@ describe('DataContext.Provider', () => { }) }) - it('should emit onChange only when validator is evaluated successfully', async () => { + it('should emit onChange only when onChangeValidator is evaluated successfully', async () => { const onChangeContext = jest.fn().mockImplementation(async () => { await wait(10) }) @@ -1601,25 +1601,27 @@ describe('DataContext.Provider', () => { await wait(10) }) - const validator = jest.fn().mockImplementation(async (value) => { - /** - * It seems that this test on CI fails during way slower performance. - * The higher timeout is to ensure the typed value will be handle by the async revalidation, even the value was valid when continue typing. - * - * The slower the performance, the higher the timeout needs to be. - */ - await wait(60) - if (value !== 'valid') { - return Error(`value: ${value}`) - } - }) + const onChangeValidator = jest + .fn() + .mockImplementation(async (value) => { + /** + * It seems that this test on CI fails during way slower performance. + * The higher timeout is to ensure the typed value will be handle by the async revalidation, even the value was valid when continue typing. + * + * The slower the performance, the higher the timeout needs to be. + */ + await wait(60) + if (value !== 'valid') { + return Error(`value: ${value}`) + } + }) render( @@ -1986,7 +1988,7 @@ describe('DataContext.Provider', () => { }) }) - it('should fulfill async validator before the form and field event', async () => { + it('should fulfill async onChangeValidator before the form and field event', async () => { const onChangeForm: OnChange = async ({ myField }) => { return { info: 'onChangeForm-info', @@ -2003,7 +2005,7 @@ describe('DataContext.Provider', () => { Error('onChangeField-error'), } } - const validator = debounceAsync(async (value) => { + const onChangeValidator = debounceAsync(async (value) => { if (value === 'invalid') { return Error('My error') } @@ -2015,7 +2017,7 @@ describe('DataContext.Provider', () => { label="My label" path="/myField" onChange={onChangeField} - validator={validator} + onChangeValidator={onChangeValidator} /> @@ -2056,9 +2058,9 @@ describe('DataContext.Provider', () => { it('should show indicator during all async operations', async () => { const events = [] - const validator = debounceAsync(async () => { + const onChangeValidator = debounceAsync(async () => { await wait(101) - events.push('validator') + events.push('onChangeValidator') }) const onChangeForm: OnChange = async () => { await wait(102) @@ -2075,7 +2077,7 @@ describe('DataContext.Provider', () => { label="My label" path="/myField" onChange={onChangeField} - validator={validator} + onChangeValidator={onChangeValidator} /> ) @@ -2088,14 +2090,14 @@ describe('DataContext.Provider', () => { await userEvent.type(input, '123') await waitFor(() => { - expect(events).toEqual(['validator']) + expect(events).toEqual(['onChangeValidator']) expect(indicator).toHaveClass( 'dnb-forms-submit-indicator--state-pending' ) }) await waitFor(() => { - expect(events).toEqual(['validator', 'onChangeForm']) + expect(events).toEqual(['onChangeValidator', 'onChangeForm']) expect(indicator).toHaveClass( 'dnb-forms-submit-indicator--state-pending' ) @@ -2103,7 +2105,7 @@ describe('DataContext.Provider', () => { await waitFor(() => { expect(events).toEqual([ - 'validator', + 'onChangeValidator', 'onChangeForm', 'onChangeField', ]) @@ -3976,7 +3978,7 @@ describe('DataContext.Provider', () => { await wait(10) }) - const validator = jest.fn(async (value) => { + const onChangeValidator = jest.fn(async (value) => { await wait(10) if (value !== 123) { return new Error('Invalid') @@ -3994,7 +3996,7 @@ describe('DataContext.Provider', () => { path="/count" label={data?.count} onChange={onChange} - validator={validator} + onChangeValidator={onChangeValidator} /> {JSON.stringify(data)} @@ -4020,8 +4022,11 @@ describe('DataContext.Provider', () => { await waitFor(() => { expect(onChange).toHaveBeenCalledTimes(0) - expect(validator).toHaveBeenCalledTimes(1) - expect(validator).toHaveBeenLastCalledWith(12, expect.anything()) + expect(onChangeValidator).toHaveBeenCalledTimes(1) + expect(onChangeValidator).toHaveBeenLastCalledWith( + 12, + expect.anything() + ) }) fireEvent.change(input, { @@ -4032,8 +4037,11 @@ describe('DataContext.Provider', () => { expect(onChange).toHaveBeenCalledTimes(0) expect(onChange).toHaveBeenCalledTimes(0) - expect(validator).toHaveBeenCalledTimes(2) - expect(validator).toHaveBeenLastCalledWith(123, expect.anything()) + expect(onChangeValidator).toHaveBeenCalledTimes(2) + expect(onChangeValidator).toHaveBeenLastCalledWith( + 123, + expect.anything() + ) // executed in sync and unvalidated expect(onDataChange).toHaveBeenCalledTimes(4) @@ -4043,8 +4051,11 @@ describe('DataContext.Provider', () => { expect(onChange).toHaveBeenCalledTimes(1) expect(onChange).toHaveBeenLastCalledWith(123) - expect(validator).toHaveBeenCalledTimes(2) - expect(validator).toHaveBeenLastCalledWith(123, expect.anything()) + expect(onChangeValidator).toHaveBeenCalledTimes(2) + expect(onChangeValidator).toHaveBeenLastCalledWith( + 123, + expect.anything() + ) }) }) diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/BankAccountNumber/BankAccountNumber.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/BankAccountNumber/BankAccountNumber.tsx index 89abcdae5e5..fafb91f0d2f 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/BankAccountNumber/BankAccountNumber.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/BankAccountNumber/BankAccountNumber.tsx @@ -46,7 +46,9 @@ function BankAccountNumber(props: Props) { const { validate = true, omitMask, + // Deprecated – can be removed in v11 validator, + onChangeValidator = validator, onBlurValidator = bankAccountNumberValidator, label: labelProp, width, @@ -97,7 +99,7 @@ function BankAccountNumber(props: Props) { mask, width: width ?? 'medium', inputMode: 'numeric', - validator: validate ? validator : undefined, + onChangeValidator: validate ? onChangeValidator : undefined, onBlurValidator: validate ? onBlurValidatorToUse : undefined, exportValidators: { bankAccountNumberValidator }, } diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/BankAccountNumber/__tests__/BankAccountNumber.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/BankAccountNumber/__tests__/BankAccountNumber.test.tsx index 9a89d99d702..02d87441d89 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/BankAccountNumber/__tests__/BankAccountNumber.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/BankAccountNumber/__tests__/BankAccountNumber.test.tsx @@ -28,6 +28,57 @@ describe('Field.BankAccountNumber', () => { expect(input).toHaveAttribute('inputmode', 'numeric') }) + // Deprecated – can be removed in v11 + it('should validate given function as validator', async () => { + const text = 'Custom Error message' + const validator = jest.fn((value) => { + return value.length < 4 ? new Error(text) : undefined + }) + + render( + + ) + + await waitFor(() => { + expect(validator).toHaveBeenCalledTimes(1) + }) + + const element = document.querySelector('.dnb-form-status') + + expect(element).toBeInTheDocument() + expect(element.textContent).toBe(text) + }) + + it('should validate given function as onChangeValidator', async () => { + const text = 'Custom Error message' + const onChangeValidator = jest.fn((value) => { + return value.length < 4 ? new Error(text) : undefined + }) + + render( + + ) + + await waitFor(() => { + expect(onChangeValidator).toHaveBeenCalledTimes(1) + }) + + const element = document.querySelector('.dnb-form-status') + + expect(element).toBeInTheDocument() + expect(element.textContent).toBe(text) + }) + describe('should validate Norwegian bank account numbers', () => { const validBankAccountNumbers = [ '52440407897', diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/NationalIdentityNumber/NationalIdentityNumber.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/NationalIdentityNumber/NationalIdentityNumber.tsx index 650876cfdd7..96b9b5d8abc 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/NationalIdentityNumber/NationalIdentityNumber.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/NationalIdentityNumber/NationalIdentityNumber.tsx @@ -92,8 +92,10 @@ function NationalIdentityNumber(props: Props) { const { validate = true, omitMask, - onBlurValidator = dnrAndFnrValidator, + // Deprecated – can be removed in v11 validator, + onChangeValidator = validator, + onBlurValidator = dnrAndFnrValidator, width, label: labelProp, } = props @@ -129,7 +131,7 @@ function NationalIdentityNumber(props: Props) { mask, width: width ?? 'medium', inputMode: 'numeric', - validator: validate ? validator : undefined, + onChangeValidator: validate ? onChangeValidator : undefined, onBlurValidator: validate ? onBlurValidatorToUse : undefined, exportValidators: { dnrValidator, diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/NationalIdentityNumber/__tests__/NationalIdentityNumber.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/NationalIdentityNumber/__tests__/NationalIdentityNumber.test.tsx index 72b3e73d4e8..096901fc34e 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/NationalIdentityNumber/__tests__/NationalIdentityNumber.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/NationalIdentityNumber/__tests__/NationalIdentityNumber.test.tsx @@ -134,7 +134,8 @@ describe('Field.NationalIdentityNumber', () => { expect(dummyValidator).toHaveBeenCalledWith('6', expect.anything()) }) - it('should validate given function', async () => { + // Deprecated – can be removed in v11 + it('should validate given function as validator', async () => { const text = 'Custom Error message' const validator = jest.fn((value) => { return value.length < 4 ? new Error(text) : undefined @@ -159,20 +160,45 @@ describe('Field.NationalIdentityNumber', () => { expect(element.textContent).toBe(text) }) + it('should validate given function as onChangeValidator', async () => { + const text = 'Custom Error message' + const onChangeValidator = jest.fn((value) => { + return value.length < 4 ? new Error(text) : undefined + }) + + render( + + ) + + await waitFor(() => { + expect(onChangeValidator).toHaveBeenCalledTimes(1) + }) + + const element = document.querySelector('.dnb-form-status') + + expect(element).toBeInTheDocument() + expect(element.textContent).toBe(text) + }) + it('should contain errorMessages as second parameter', () => { - const validator = jest.fn() + const onChangeValidator = jest.fn() render( ) - expect(validator).toHaveBeenCalledTimes(1) - expect(validator).toHaveBeenCalledWith( + expect(onChangeValidator).toHaveBeenCalledTimes(1) + expect(onChangeValidator).toHaveBeenCalledWith( '123', expect.objectContaining({ errorMessages: expect.objectContaining({ @@ -313,7 +339,7 @@ describe('Field.NationalIdentityNumber', () => { expect(screen.queryByRole('alert')).toBeNull() }) - it('should not validate custom validator when validate false', async () => { + it('should not validate custom onChangeValidator when validate false', async () => { const customValidator: Validator = (value) => { if (value?.length < 4) { return new Error('My error') @@ -324,7 +350,7 @@ describe('Field.NationalIdentityNumber', () => { @@ -333,7 +359,7 @@ describe('Field.NationalIdentityNumber', () => { expect(screen.queryByRole('alert')).toBeNull() }) - it('should not validate extended validator when validate false', async () => { + it('should not validate extended onChangeValidator when validate false', async () => { const invalidFnrBornInApril = '29040112345' const bornInApril = (value: string) => value.substring(2, 4) === '04' @@ -352,7 +378,7 @@ describe('Field.NationalIdentityNumber', () => { value={invalidFnrBornInApril} validateInitially validate={false} - validator={customValidator} + onChangeValidator={customValidator} /> ) @@ -507,7 +533,7 @@ describe('Field.NationalIdentityNumber', () => { ) }) - describe('should extend validation using custom validator', () => { + describe('should extend validation using custom onChangeValidator', () => { const validFnrNumApril = ['14046512368', '10042223293'] const validDNumApril = ['51041678171'] @@ -542,7 +568,7 @@ describe('Field.NationalIdentityNumber', () => { it.each(validIds)('Valid identity number: %s', async (fnrNum) => { render( @@ -554,7 +580,7 @@ describe('Field.NationalIdentityNumber', () => { it.each(invalidIds)('Invalid identity number: %s', async (id) => { render( @@ -571,7 +597,7 @@ describe('Field.NationalIdentityNumber', () => { it.each(invalidDNumApril)('Invalid D number: %s', async (dNum) => { render( @@ -588,7 +614,7 @@ describe('Field.NationalIdentityNumber', () => { it.each(invalidDNumTooShort)('Invalid D number: %s', async (dNum) => { render( @@ -607,7 +633,7 @@ describe('Field.NationalIdentityNumber', () => { async (fnr) => { render( @@ -627,7 +653,7 @@ describe('Field.NationalIdentityNumber', () => { async (fnr) => { render( diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/NationalIdentityNumber/__tests__/NationalIdentityNumberMinimumAgeValidator.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/NationalIdentityNumber/__tests__/NationalIdentityNumberMinimumAgeValidator.test.tsx index 30e958f7212..d8c0f56b462 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/NationalIdentityNumber/__tests__/NationalIdentityNumberMinimumAgeValidator.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/NationalIdentityNumber/__tests__/NationalIdentityNumberMinimumAgeValidator.test.tsx @@ -186,13 +186,13 @@ describe('Field.NationalIdentityNumber with minimumAgeValidator', () => { const invalidDnums = ['69020112345', '690'] const invalidFnrs = ['29020112345', '290'] - describe('when provided as the only validator validation function', () => { + describe('when provided as the onChangeValidator function', () => { it.each(validIds)( 'Identity number is 18 years or older : %s', async (validId) => { render( { async (invalidId) => { render( { ) }) - describe('when extending the dnrAndFnrValidator as validator', () => { + describe('when extending the dnrAndFnrValidator as onChangeValidator', () => { it.each(validIds)( 'Identity number is 18 years or older : %s', async (validId) => { render( @@ -289,7 +291,9 @@ describe('Field.NationalIdentityNumber with minimumAgeValidator', () => { render( @@ -309,7 +313,9 @@ describe('Field.NationalIdentityNumber with minimumAgeValidator', () => { render( @@ -329,7 +335,9 @@ describe('Field.NationalIdentityNumber with minimumAgeValidator', () => { render( @@ -428,14 +436,14 @@ describe('Field.NationalIdentityNumber with minimumAgeValidator', () => { ) }) - describe('when extending the dnrValidator as validator', () => { + describe('when extending the dnrValidator as onChangeValidator', () => { it.each(dnr18YearsOldAndOlder)( 'D number is 18 years or older : %s', async (validDnum) => { render( @@ -453,7 +461,7 @@ describe('Field.NationalIdentityNumber with minimumAgeValidator', () => { render( @@ -473,7 +481,7 @@ describe('Field.NationalIdentityNumber with minimumAgeValidator', () => { render( @@ -547,14 +555,14 @@ describe('Field.NationalIdentityNumber with minimumAgeValidator', () => { }) }) - describe('when extending the fnrValidator as validator', () => { + describe('when extending the fnrValidator as onChangeValidator', () => { it.each(fnr18YearsOldAndOlder)( 'Identity number(fnr) is 18 years or older : %s', async (validFnr) => { render( @@ -572,7 +580,7 @@ describe('Field.NationalIdentityNumber with minimumAgeValidator', () => { render( @@ -592,7 +600,7 @@ describe('Field.NationalIdentityNumber with minimumAgeValidator', () => { render( diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/NationalIdentityNumber/stories/NationalIdentityNumber.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/NationalIdentityNumber/stories/NationalIdentityNumber.stories.tsx index 02b55500e24..3df8b1737ca 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/NationalIdentityNumber/stories/NationalIdentityNumber.stories.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/NationalIdentityNumber/stories/NationalIdentityNumber.stories.tsx @@ -51,34 +51,34 @@ export function ValidatorsUndefinedFalse() { return (

Validate Initially:

@@ -175,34 +175,34 @@ export function NationalIdentityNumberValidator() {

Validate Initially:

@@ -245,34 +245,34 @@ export function DNumberValidator() {

Validate Initially:

@@ -342,67 +342,67 @@ export function AdultValidator() {

Validate Initially:

@@ -464,56 +464,56 @@ export function AdultValidatorAndDefaultValidator() { return (

Validate Initially:

@@ -525,56 +525,56 @@ export function CustomValidatorFunction() { return (

Validate Initially:

@@ -643,56 +643,56 @@ export function CustomValidatorFunctionReturnArray() { return (

Validate Initially:

diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/Number/__tests__/Number.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/Number/__tests__/Number.test.tsx index 4815ed4a3da..4cbb1b16de4 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/Number/__tests__/Number.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/Number/__tests__/Number.test.tsx @@ -548,29 +548,32 @@ describe('Field.Number', () => { expect(screen.queryByRole('alert')).toBeInTheDocument() }) - it('should call validator with validateInitially', async () => { - const validator = jest.fn(() => { + it('should call onChangeValidator with validateInitially', async () => { + const onChangeValidator = jest.fn(() => { return new Error('Validator message') }) render( ) - expect(validator).toHaveBeenCalledTimes(1) - expect(validator).toHaveBeenCalledWith(123, expect.anything()) + expect(onChangeValidator).toHaveBeenCalledTimes(1) + expect(onChangeValidator).toHaveBeenCalledWith( + 123, + expect.anything() + ) await waitFor(() => { expect(screen.queryByRole('alert')).toBeInTheDocument() }) }) - it('should call validator on form submit', async () => { - const validator = jest.fn(() => { + it('should call onChangeValidator on form submit', async () => { + const onChangeValidator = jest.fn(() => { return new Error('Validator message') }) @@ -578,7 +581,7 @@ describe('Field.Number', () => { @@ -586,8 +589,11 @@ describe('Field.Number', () => { fireEvent.submit(document.querySelector('form')) - expect(validator).toHaveBeenCalledTimes(1) - expect(validator).toHaveBeenCalledWith(123, expect.anything()) + expect(onChangeValidator).toHaveBeenCalledTimes(1) + expect(onChangeValidator).toHaveBeenCalledWith( + 123, + expect.anything() + ) await waitFor(() => { expect(screen.queryByRole('alert')).toBeInTheDocument() diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/Number/stories/Number.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/Number/stories/Number.stories.tsx index 87b383f3810..d74f85e5e5f 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/Number/stories/Number.stories.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/Number/stories/Number.stories.tsx @@ -45,8 +45,8 @@ export const Number = () => { } export const WithFreshValidator = () => { - const validator: UseFieldProps['validator'] = useCallback( - (num, { connectWithPath }) => { + const validator: UseFieldProps['onChangeValidator'] = + useCallback((num, { connectWithPath }) => { const { getValue } = connectWithPath('/refValue') const amount = getValue() // console.log('amount', amount, amount >= num) @@ -56,9 +56,7 @@ export const WithFreshValidator = () => { if (num === undefined) { return new Error(`No amount was given`) } - }, - [] - ) + }, []) return ( { diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/OrganizationNumber/OrganizationNumber.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/OrganizationNumber/OrganizationNumber.tsx index 79dabac77ee..2b6afda008d 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/OrganizationNumber/OrganizationNumber.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/OrganizationNumber/OrganizationNumber.tsx @@ -42,7 +42,9 @@ function OrganizationNumber(props: Props) { const { validate = true, omitMask, + // Deprecated – can be removed in v11 validator, + onChangeValidator = validator, onBlurValidator = organizationNumberValidator, label: labelProp, width, @@ -67,7 +69,7 @@ function OrganizationNumber(props: Props) { mask, width: width ?? 'medium', inputMode: 'numeric', - validator: validate ? validator : undefined, + onChangeValidator: validate ? onChangeValidator : undefined, onBlurValidator: validate ? onBlurValidatorToUse : undefined, exportValidators: { organizationNumberValidator }, } diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/OrganizationNumber/__tests__/OrganizationNumber.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/OrganizationNumber/__tests__/OrganizationNumber.test.tsx index 30e402dcec3..e4843eb9229 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/OrganizationNumber/__tests__/OrganizationNumber.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/OrganizationNumber/__tests__/OrganizationNumber.test.tsx @@ -95,6 +95,57 @@ describe('Field.OrganizationNumber', () => { }) }) + // Deprecated – can be removed in v11 + it('should validate given function as validator', async () => { + const text = 'Custom Error message' + const validator = jest.fn((value) => { + return value.length < 4 ? new Error(text) : undefined + }) + + render( + + ) + + await waitFor(() => { + expect(validator).toHaveBeenCalledTimes(1) + }) + + const element = document.querySelector('.dnb-form-status') + + expect(element).toBeInTheDocument() + expect(element.textContent).toBe(text) + }) + + it('should validate given function as onChangeValidator', async () => { + const text = 'Custom Error message' + const onChangeValidator = jest.fn((value) => { + return value.length < 4 ? new Error(text) : undefined + }) + + render( + + ) + + await waitFor(() => { + expect(onChangeValidator).toHaveBeenCalledTimes(1) + }) + + const element = document.querySelector('.dnb-form-status') + + expect(element).toBeInTheDocument() + expect(element.textContent).toBe(text) + }) + it('should display error if required and validateInitially', async () => { render() @@ -289,7 +340,7 @@ describe('Field.OrganizationNumber', () => { value={invalidOrgNo} validateInitially validate={false} - validator={customValidator} + onChangeValidator={customValidator} onBlurValidator={false} /> @@ -326,7 +377,7 @@ describe('Field.OrganizationNumber', () => { value={invalidOrgNo} validateInitially validate={false} - validator={customValidator} + onChangeValidator={customValidator} onBlurValidator={false} /> diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/OrganizationNumber/stories/OrganizationNumber.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/OrganizationNumber/stories/OrganizationNumber.stories.tsx index 4af93be8b42..51db385f261 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/OrganizationNumber/stories/OrganizationNumber.stories.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/OrganizationNumber/stories/OrganizationNumber.stories.tsx @@ -98,34 +98,34 @@ export function OrganizationNumberValidator() {

Validate Initially:

@@ -141,34 +141,34 @@ export function CustomValidator() {

Validate Initially:

@@ -223,34 +223,34 @@ export function CustomValidatorReturnArray() {

Validate Initially:

@@ -307,19 +307,22 @@ export function StringValidatorSimple() { return ( - - - + + +

Validate Initially:

- +
diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/PhoneNumber/__tests__/PhoneNumber.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/PhoneNumber/__tests__/PhoneNumber.test.tsx index 29efd59316e..4e605f8fd71 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/PhoneNumber/__tests__/PhoneNumber.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/PhoneNumber/__tests__/PhoneNumber.test.tsx @@ -669,23 +669,23 @@ describe('Field.PhoneNumber', () => { expect(document.querySelector('[role="alert"]')).toBeInTheDocument() }) - it('should handle "validator" property with country code', async () => { - const validator = jest.fn(() => { + it('should handle "onChangeValidator" property with country code', async () => { + const onChangeValidator = jest.fn(() => { return new Error('some error') }) render( ) - expect(validator).toHaveBeenCalledTimes(1) - expect(validator).toHaveBeenCalledWith( + expect(onChangeValidator).toHaveBeenCalledTimes(1) + expect(onChangeValidator).toHaveBeenCalledWith( '+41 9999', expect.objectContaining({ errorMessages: expect.objectContaining({ diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/PhoneNumber/stories/PhoneNumber.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/PhoneNumber/stories/PhoneNumber.stories.tsx index 202ab3d8e48..b1bf2fbf5a9 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/PhoneNumber/stories/PhoneNumber.stories.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/PhoneNumber/stories/PhoneNumber.stories.tsx @@ -16,7 +16,7 @@ const makeRequest = async (value) => { }) } -const validator = async (value) => { +const onChangeValidator = async (value) => { // Delay the response const isValid = await makeRequest(value) if (!isValid) { @@ -36,7 +36,7 @@ export function PhoneNumber() { { }) }) - describe('validation using a synchronous external validator function', () => { - it('should show error returned by validator', async () => { - const validator = jest.fn(syncValidatorReturningError) + describe('validation using a synchronous external onChangeValidator function', () => { + it('should show error returned by onChangeValidator', async () => { + const onChangeValidator = jest.fn(syncValidatorReturningError) render( ) await waitFor(() => { // Wait for since external validators are processed asynchronously - expect(validator).toHaveBeenCalledTimes(1) - expect(validator).toHaveBeenNthCalledWith( + expect(onChangeValidator).toHaveBeenCalledTimes(1) + expect(onChangeValidator).toHaveBeenNthCalledWith( 1, 'abc', expect.anything() @@ -945,18 +945,18 @@ describe('Field.String', () => { fireEvent.blur(input) await waitFor(() => { - expect(validator).toHaveBeenCalledTimes(4) - expect(validator).toHaveBeenNthCalledWith( + expect(onChangeValidator).toHaveBeenCalledTimes(4) + expect(onChangeValidator).toHaveBeenNthCalledWith( 2, 'abcd', expect.anything() ) - expect(validator).toHaveBeenNthCalledWith( + expect(onChangeValidator).toHaveBeenNthCalledWith( 3, 'abcde', expect.anything() ) - expect(validator).toHaveBeenNthCalledWith( + expect(onChangeValidator).toHaveBeenNthCalledWith( 4, 'abcdef', expect.anything() @@ -967,12 +967,12 @@ describe('Field.String', () => { }) }) - it('should not show error when validator returns undefined', async () => { - const validator = jest.fn(syncValidatorReturningUndefined) + it('should not show error when onChangeValidator returns undefined', async () => { + const onChangeValidator = jest.fn(syncValidatorReturningUndefined) render( ) @@ -982,20 +982,20 @@ describe('Field.String', () => { }) }) - describe('validation using an asynchronous external validator function', () => { - it('should show error returned by validator', async () => { - const validator = jest.fn(asyncValidatorResolvingWithError) + describe('validation using an asynchronous external onChangeValidator function', () => { + it('should show error returned by onChangeValidator', async () => { + const onChangeValidator = jest.fn(asyncValidatorResolvingWithError) render( ) await waitFor(() => { // Wait for since external validators are processed asynchronously - expect(validator).toHaveBeenCalledTimes(1) - expect(validator).toHaveBeenNthCalledWith( + expect(onChangeValidator).toHaveBeenCalledTimes(1) + expect(onChangeValidator).toHaveBeenNthCalledWith( 1, 'abc', expect.anything() @@ -1012,18 +1012,18 @@ describe('Field.String', () => { fireEvent.blur(input) }) - expect(validator).toHaveBeenCalledTimes(4) - expect(validator).toHaveBeenNthCalledWith( + expect(onChangeValidator).toHaveBeenCalledTimes(4) + expect(onChangeValidator).toHaveBeenNthCalledWith( 2, 'abcd', expect.anything() ) - expect(validator).toHaveBeenNthCalledWith( + expect(onChangeValidator).toHaveBeenNthCalledWith( 3, 'abcde', expect.anything() ) - expect(validator).toHaveBeenNthCalledWith( + expect(onChangeValidator).toHaveBeenNthCalledWith( 4, 'abcdef', expect.anything() @@ -1033,12 +1033,14 @@ describe('Field.String', () => { ).toBeInTheDocument() }) - it('should not show error when validator returns undefined', async () => { - const validator = jest.fn(asyncValidatorResolvingWithUndefined) + it('should not show error when onChangeValidator returns undefined', async () => { + const onChangeValidator = jest.fn( + asyncValidatorResolvingWithUndefined + ) render( ) @@ -1050,19 +1052,19 @@ describe('Field.String', () => { }) describe('validation using a synchronous external onBlurValidator function', () => { - it('should show error returned by validator', async () => { - const validator = jest.fn(syncValidatorReturningError) + it('should show error returned by onBlurValidator', async () => { + const onBlurValidator = jest.fn(syncValidatorReturningError) render( ) await waitFor(() => { // Wait for since external validators are processed asynchronously - expect(validator).toHaveBeenCalledTimes(1) + expect(onBlurValidator).toHaveBeenCalledTimes(1) expect(screen.queryByRole('alert')).toBeInTheDocument() }) const input = document.querySelector('input') @@ -1071,13 +1073,13 @@ describe('Field.String', () => { await waitFor(() => { // Wait for since external validators are processed asynchronously - expect(validator).toHaveBeenCalledTimes(2) - expect(validator).toHaveBeenNthCalledWith( + expect(onBlurValidator).toHaveBeenCalledTimes(2) + expect(onBlurValidator).toHaveBeenNthCalledWith( 1, 'abc', expect.anything() ) - expect(validator).toHaveBeenNthCalledWith( + expect(onBlurValidator).toHaveBeenNthCalledWith( 2, 'abcdef', expect.anything() @@ -1089,12 +1091,12 @@ describe('Field.String', () => { }) }) - it('should not show error when validator returns undefined', async () => { - const validator = jest.fn(syncValidatorReturningUndefined) + it('should not show error when onBlurValidator returns undefined', async () => { + const onBlurValidator = jest.fn(syncValidatorReturningUndefined) render( ) @@ -1108,19 +1110,19 @@ describe('Field.String', () => { }) describe('validation using an asynchronous external onBlurValidator function', () => { - it('should show error returned by validator', async () => { - const validator = jest.fn(asyncValidatorResolvingWithError) + it('should show error returned by onBlurValidator', async () => { + const onBlurValidator = jest.fn(asyncValidatorResolvingWithError) render( ) await waitFor(() => { // Wait for since external validators are processed asynchronously - expect(validator).toHaveBeenCalledTimes(1) + expect(onBlurValidator).toHaveBeenCalledTimes(1) expect(screen.queryByRole('alert')).toBeInTheDocument() }) const input = document.querySelector('input') @@ -1129,13 +1131,13 @@ describe('Field.String', () => { await waitFor(() => { // Wait for since external validators are processed asynchronously - expect(validator).toHaveBeenCalledTimes(2) - expect(validator).toHaveBeenNthCalledWith( + expect(onBlurValidator).toHaveBeenCalledTimes(2) + expect(onBlurValidator).toHaveBeenNthCalledWith( 1, 'abc', expect.anything() ) - expect(validator).toHaveBeenNthCalledWith( + expect(onBlurValidator).toHaveBeenNthCalledWith( 2, 'abcdef', expect.anything() @@ -1147,12 +1149,14 @@ describe('Field.String', () => { }) }) - it('should not show error when validator returns undefined', async () => { - const validator = jest.fn(asyncValidatorResolvingWithUndefined) + it('should not show error when onBlurValidator returns undefined', async () => { + const onBlurValidator = jest.fn( + asyncValidatorResolvingWithUndefined + ) render( ) @@ -1234,7 +1238,7 @@ describe('Field.String', () => { ).toBe('At least 4.') }) - it('should provide error message to the validator', async () => { + it('should provide error message to the onBlurValidator', async () => { let collectDeprecatedMessage = null let collectCustomMessage = null const customMessage = 'Your custom error message' diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/String/stories/String.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/String/stories/String.stories.tsx index 97f01bb2125..0e132a3a0da 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/String/stories/String.stories.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/String/stories/String.stories.tsx @@ -106,7 +106,7 @@ export function ErrorMessages() { { + onChangeValidator={() => { return new FormError('OrganizationNumber.errorRequired') }} errorMessages={{ @@ -120,7 +120,7 @@ export function ErrorMessages() { value="abc" minLength={4} // pattern="[0-9]" - // validator={() => { + // onChangeValidator={() => { // return new FormError('OrganizationNumber.errorRequired') // }} // errorMessages={{ diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Handler/__tests__/Handler.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Form/Handler/__tests__/Handler.test.tsx index 902f8482cc1..e0cce0782fa 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Form/Handler/__tests__/Handler.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Form/Handler/__tests__/Handler.test.tsx @@ -544,7 +544,7 @@ describe('Form.Handler', () => { expect(inputElement).toBeDisabled() }) - it('should not disable form elements during on async validator handling', async () => { + it('should not disable form elements during an async validator handling', async () => { const onSubmit = async () => null const asyncValidator = async () => { return null @@ -568,7 +568,7 @@ describe('Form.Handler', () => { rerender( - + ) @@ -772,7 +772,7 @@ describe('Form.Handler', () => { }) }) - it('should call onSubmit and onSubmitComplete with async validator', async () => { + it('should call onSubmit and onSubmitComplete with async onChangeValidator', async () => { const onSubmit: OnSubmit = jest.fn() const onSubmitComplete = jest.fn() @@ -788,7 +788,7 @@ describe('Form.Handler', () => { @@ -819,7 +819,7 @@ describe('Form.Handler', () => { }) }) - it('should not call async validator when field is not mounted anymore', async () => { + it('should not call async onChangeValidator when field is not mounted anymore', async () => { const onSubmit: OnSubmit = jest.fn() const asyncValidator = jest.fn(async () => { return null @@ -830,7 +830,7 @@ describe('Form.Handler', () => { diff --git a/packages/dnb-eufemia/src/extensions/forms/Form/Handler/stories/FormHandler.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/Form/Handler/stories/FormHandler.stories.tsx index 03f7c65b3de..5533b021b12 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Form/Handler/stories/FormHandler.stories.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Form/Handler/stories/FormHandler.stories.tsx @@ -135,7 +135,7 @@ export function AdvancedForm() { label="Field A (onBlurValidator)" path="/fieldA" onBlurValidator={firstValidator} - // validator={firstValidator} + // onChangeValidator={firstValidator} // continuousValidation // validateInitially // validateUnchanged @@ -152,10 +152,10 @@ export function AdvancedForm() { onBlurValidator={secondValidator} /> @@ -273,7 +273,7 @@ export function SimpleForm() { value="vali" label="Name" path="/myField1" - validator={secondValidator} + onChangeValidator={secondValidator} onBlurValidator={thirdValidator} onChange={onFieldChange} /> diff --git a/packages/dnb-eufemia/src/extensions/forms/Iterate/Array/ArrayDocs.ts b/packages/dnb-eufemia/src/extensions/forms/Iterate/Array/ArrayDocs.ts index e5912f3d3f7..90a8abb1d12 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Iterate/Array/ArrayDocs.ts +++ b/packages/dnb-eufemia/src/extensions/forms/Iterate/Array/ArrayDocs.ts @@ -52,7 +52,7 @@ export const ArrayProperties: PropertiesTableProps = { type: 'unknown', status: 'optional', }, - validator: DataValueWritePropsProperties.validator, + onChangeValidator: DataValueWritePropsProperties.onChangeValidator, validateInitially: DataValueWritePropsProperties.validateInitially, continuousValidation: DataValueWritePropsProperties.continuousValidation, containerMode: { diff --git a/packages/dnb-eufemia/src/extensions/forms/Iterate/Array/__tests__/Array.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Iterate/Array/__tests__/Array.test.tsx index 58b73db770c..c9b34f5e142 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Iterate/Array/__tests__/Array.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Iterate/Array/__tests__/Array.test.tsx @@ -504,9 +504,9 @@ describe('Iterate.Array', () => { }) }) - describe('validator', () => { - it('should validate validator initially (validateInitially)', async () => { - const validator = jest.fn((arrayValue) => { + describe('onChangeValidator', () => { + it('should validate onChangeValidator initially (validateInitially)', async () => { + const onChangeValidator = jest.fn((arrayValue) => { if (arrayValue.length === 2) { return new Error('Error message') } @@ -520,7 +520,7 @@ describe('Iterate.Array', () => { > @@ -529,8 +529,8 @@ describe('Iterate.Array', () => { ) - expect(validator).toHaveBeenCalledTimes(1) - expect(validator).toHaveBeenCalledWith( + expect(onChangeValidator).toHaveBeenCalledTimes(1) + expect(onChangeValidator).toHaveBeenCalledWith( ['foo', 'bar'], expect.anything() ) @@ -546,8 +546,8 @@ describe('Iterate.Array', () => { fireEvent.click(document.querySelector('button')) - expect(validator).toHaveBeenCalledTimes(2) - expect(validator).toHaveBeenCalledWith( + expect(onChangeValidator).toHaveBeenCalledTimes(2) + expect(onChangeValidator).toHaveBeenCalledWith( ['foo', 'bar', 'baz'], expect.anything() ) @@ -559,8 +559,8 @@ describe('Iterate.Array', () => { }) }) - it('should validate validator on form submit', async () => { - const validator = jest.fn((arrayValue) => { + it('should validate onChangeValidator on form submit', async () => { + const onChangeValidator = jest.fn((arrayValue) => { if (arrayValue.length === 2) { return new Error('Error message') } @@ -572,20 +572,23 @@ describe('Iterate.Array', () => { items: ['foo', 'bar'], }} > - + ) - expect(validator).toHaveBeenCalledTimes(0) + expect(onChangeValidator).toHaveBeenCalledTimes(0) const form = document.querySelector('form') fireEvent.submit(form) - expect(validator).toHaveBeenCalledTimes(1) - expect(validator).toHaveBeenCalledWith( + expect(onChangeValidator).toHaveBeenCalledTimes(1) + expect(onChangeValidator).toHaveBeenCalledWith( ['foo', 'bar'], expect.anything() ) @@ -601,8 +604,8 @@ describe('Iterate.Array', () => { fireEvent.click(document.querySelector('button')) - expect(validator).toHaveBeenCalledTimes(2) - expect(validator).toHaveBeenCalledWith( + expect(onChangeValidator).toHaveBeenCalledTimes(2) + expect(onChangeValidator).toHaveBeenCalledWith( ['foo', 'bar', 'baz'], expect.anything() ) @@ -618,7 +621,7 @@ describe('Iterate.Array', () => { const findFirstDuplication = (arr) => arr.findIndex((e, i) => arr.indexOf(e) !== i) - const validator = jest.fn((arrayValue) => { + const onChangeValidator = jest.fn((arrayValue) => { const index = findFirstDuplication(arrayValue) if (index > -1) { const value = arrayValue[index] @@ -628,7 +631,10 @@ describe('Iterate.Array', () => { render( - + diff --git a/packages/dnb-eufemia/src/extensions/forms/Iterate/Array/types.ts b/packages/dnb-eufemia/src/extensions/forms/Iterate/Array/types.ts index ad8aef4cced..6e8eeaa9e68 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Iterate/Array/types.ts +++ b/packages/dnb-eufemia/src/extensions/forms/Iterate/Array/types.ts @@ -24,7 +24,7 @@ export type Props = Omit< limit?: number countPath?: Path countPathLimit?: number - validator?: Validator + onChangeValidator?: Validator withoutFlex?: boolean animate?: boolean placeholder?: React.ReactNode diff --git a/packages/dnb-eufemia/src/extensions/forms/Iterate/EditContainer/__tests__/EditAndViewContainer.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Iterate/EditContainer/__tests__/EditAndViewContainer.test.tsx index db4e2e32dc1..78d1a364a09 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Iterate/EditContainer/__tests__/EditAndViewContainer.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Iterate/EditContainer/__tests__/EditAndViewContainer.test.tsx @@ -86,7 +86,7 @@ describe('EditContainer and ViewContainer', () => { { + onChangeValidator={(value) => { if (value === '01') { return new Error('error') } diff --git a/packages/dnb-eufemia/src/extensions/forms/Iterate/stories/Iterate.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/Iterate/stories/Iterate.stories.tsx index 3e17807f36e..0945868a7ce 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Iterate/stories/Iterate.stories.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Iterate/stories/Iterate.stories.tsx @@ -184,7 +184,7 @@ export const InitialOpen = () => { path="/countries" // defaultValue={['NO']} defaultValue={[null]} - validator={(arrayValue) => { + onChangeValidator={(arrayValue) => { const findFirstDuplication = (arr) => arr.findIndex((e, i) => arr.indexOf(e) !== i) @@ -228,7 +228,7 @@ export const WithArrayValidator = () => { { + onChangeValidator={(arrayValue) => { if (!(arrayValue?.length > 0)) { return new Error('You need at least one item') } diff --git a/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/WizardContainer.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/WizardContainer.test.tsx index 3f23b7426c0..74f6732aeac 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/WizardContainer.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Wizard/Container/__tests__/WizardContainer.test.tsx @@ -281,9 +281,9 @@ describe('Wizard.Container', () => { }) it('should support navigating back and forth with async validators', async () => { - const validator = async (value: string) => { + const onChangeValidator = async (value: string) => { if (value !== 'valid') { - return new Error('validator-error') + return new Error('onChangeValidator-error') } } @@ -300,7 +300,7 @@ describe('Wizard.Container', () => { @@ -337,7 +337,7 @@ describe('Wizard.Container', () => { expect(output()).toHaveTextContent('Step 1') expect(screen.queryByRole('alert')).toHaveTextContent( - 'validator-error' + 'onChangeValidator-error' ) fireEvent.blur(input()) @@ -372,7 +372,7 @@ describe('Wizard.Container', () => { expect(output()).toHaveTextContent('Step 1') expect(screen.queryByRole('alert')).toHaveTextContent( - 'validator-error' + 'onChangeValidator-error' ) fireEvent.blur(input()) @@ -1336,14 +1336,17 @@ describe('Wizard.Container', () => { }) }) - it('should handle async validator', async () => { + it('should handle async onChangeValidator', async () => { const asyncValidator = async () => null render( Step 1 - + @@ -1389,7 +1392,7 @@ describe('Wizard.Container', () => { }) }) - it('should handle async validator with error', async () => { + it('should handle async onChangeValidator with error', async () => { const asyncValidator = async (value: string) => { if (value !== 'valid') { return new Error('Error message') @@ -1400,7 +1403,7 @@ describe('Wizard.Container', () => { Step 1 - + diff --git a/packages/dnb-eufemia/src/extensions/forms/Wizard/stories/Wizard.stories.tsx b/packages/dnb-eufemia/src/extensions/forms/Wizard/stories/Wizard.stories.tsx index 73f9c1cb82c..e1fc5d6dfa0 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Wizard/stories/Wizard.stories.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Wizard/stories/Wizard.stories.tsx @@ -188,14 +188,14 @@ export function AsyncStepChange() { diff --git a/packages/dnb-eufemia/src/extensions/forms/hooks/DataValueWritePropsDocs.ts b/packages/dnb-eufemia/src/extensions/forms/hooks/DataValueWritePropsDocs.ts index 17efdac19ff..e31d5ee2e1b 100644 --- a/packages/dnb-eufemia/src/extensions/forms/hooks/DataValueWritePropsDocs.ts +++ b/packages/dnb-eufemia/src/extensions/forms/hooks/DataValueWritePropsDocs.ts @@ -76,7 +76,7 @@ export const DataValueWritePropsProperties: PropertiesTableProps = { type: 'object', status: 'optional', }, - validator: { + onChangeValidator: { doc: 'Custom validator function that is triggered on every change done by the user. The function can be either asynchronous or synchronous. The first parameter is the value, and the second parameter returns an object containing { errorMessages, connectWithPath, validators }.', type: 'function', status: 'optional', diff --git a/packages/dnb-eufemia/src/extensions/forms/hooks/__tests__/useFieldProps.test.tsx b/packages/dnb-eufemia/src/extensions/forms/hooks/__tests__/useFieldProps.test.tsx index 0224e2b8f98..85ca015d91a 100644 --- a/packages/dnb-eufemia/src/extensions/forms/hooks/__tests__/useFieldProps.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/hooks/__tests__/useFieldProps.test.tsx @@ -284,10 +284,10 @@ describe('useFieldProps', () => { }) describe('with local validation', () => { - it('should return error when validator callback returns error', async () => { + it('should return error when onChangeValidator callback returns error', async () => { const { result, rerender } = renderHook(useFieldProps, { initialProps: { - validator: () => new Error('This is wrong...'), + onChangeValidator: () => new Error('This is wrong...'), value: 'foo', validateInitially: true, }, @@ -298,7 +298,7 @@ describe('useFieldProps', () => { }) rerender({ - validator: () => undefined, + onChangeValidator: () => undefined, value: 'bar', validateInitially: true, }) @@ -415,7 +415,7 @@ describe('useFieldProps', () => { const { result } = renderHook(useFieldProps, { initialProps: { - validator: onChangeValidator, + onChangeValidator, onBlurValidator, }, }) @@ -452,7 +452,7 @@ describe('useFieldProps', () => { const { result } = renderHook(useFieldProps, { initialProps: { onBlurValidator, - validator: onChangeValidator, + onChangeValidator, required: true, }, }) @@ -518,7 +518,7 @@ describe('useFieldProps', () => { const { result } = renderHook(useFieldProps, { initialProps: { onBlurValidator, - validator: onChangeValidator, + onChangeValidator, required: true, }, }) @@ -541,7 +541,7 @@ describe('useFieldProps', () => { }) }) - describe('with async validator', () => { + describe('with async onChangeValidator', () => { const validateBlur = async (result, value = Date.now()) => { act(() => { result.current.handleChange(String(value)) @@ -550,7 +550,7 @@ describe('useFieldProps', () => { } it('should set fieldState', async () => { - const validator = async () => { + const onChangeValidator = async () => { await wait(1) return null @@ -558,7 +558,7 @@ describe('useFieldProps', () => { const { result, rerender } = renderHook(useFieldProps, { initialProps: { - validator, + onChangeValidator, value: '', }, }) @@ -580,7 +580,7 @@ describe('useFieldProps', () => { }) rerender({ - validator, + onChangeValidator, value: '456', }) @@ -611,7 +611,7 @@ describe('useFieldProps', () => { const { result, rerender } = renderHook(useFieldProps, { initialProps: { - validator, + onChangeValidator: validator, onBlurValidator: undefined, value: '', info: 'Info message', @@ -659,7 +659,7 @@ describe('useFieldProps', () => { }) rerender({ - validator, + onChangeValidator: validator, onBlurValidator: undefined, value: '456', info: 'Info message changed', @@ -691,7 +691,7 @@ describe('useFieldProps', () => { }) rerender({ - validator: undefined, + onChangeValidator: undefined, onBlurValidator: validator, value: '456', info: 'Info message changed', @@ -725,8 +725,8 @@ describe('useFieldProps', () => { }) }) - it('should set fieldState to error for async validator', async () => { - const validator = async () => { + it('should set fieldState to error for async onChangeValidator', async () => { + const onChangeValidator = async () => { await wait(1) return new Error('Error message') @@ -734,7 +734,7 @@ describe('useFieldProps', () => { const { result, rerender } = renderHook(useFieldProps, { initialProps: { - validator, + onChangeValidator, value: '', }, }) @@ -753,7 +753,7 @@ describe('useFieldProps', () => { }) rerender({ - validator, + onChangeValidator, value: '456', }) @@ -859,7 +859,7 @@ describe('useFieldProps', () => { }) it('should hide fieldState error when disabled', async () => { - const validator = async () => { + const onChangeValidator = async () => { await wait(1) return new Error('Error message') @@ -867,7 +867,7 @@ describe('useFieldProps', () => { const { result, rerender } = renderHook(useFieldProps, { initialProps: { - validator, + onChangeValidator, value: '', disabled: undefined, }, @@ -887,7 +887,7 @@ describe('useFieldProps', () => { }) rerender({ - validator, + onChangeValidator, value: '456', disabled: true, }) @@ -1096,7 +1096,7 @@ describe('useFieldProps', () => { schema, // Step 3. - validator: async (value: string) => { + onChangeValidator: async (value: string) => { return value === 'throw-onChangeValidator' ? new Error(value) : undefined @@ -1521,7 +1521,7 @@ describe('useFieldProps', () => { }) }) - it('should validate "validator" before onChange call', async () => { + it('should validate "onChangeValidator" before onChange call', async () => { const events = [] const onChange: OnChange = async (value) => { @@ -1531,14 +1531,14 @@ describe('useFieldProps', () => { return { success: 'saved' } as const } } - const validator = async () => { - events.push('validator') + const onChangeValidator = async () => { + events.push('onChangeValidator') } const { result } = renderHook(useFieldProps, { initialProps: { onChange, - validator, + onChangeValidator, value: '', }, }) @@ -1548,11 +1548,11 @@ describe('useFieldProps', () => { await validateBlur(result, '123') expect(result.current.fieldState).toBe('pending') - expect(events).toEqual(['validator']) + expect(events).toEqual(['onChangeValidator']) await waitFor(() => { expect(result.current.fieldState).toBe('complete') - expect(events).toEqual(['validator', 'onChange']) + expect(events).toEqual(['onChangeValidator', 'onChange']) }) // Reset events @@ -1561,11 +1561,11 @@ describe('useFieldProps', () => { await validateBlur(result, '456') expect(result.current.fieldState).toBe('pending') - expect(events).toEqual(['validator']) + expect(events).toEqual(['onChangeValidator']) await waitFor(() => { expect(result.current.fieldState).toBe('success') - expect(events).toEqual(['validator', 'onChange']) + expect(events).toEqual(['onChangeValidator', 'onChange']) }) }) @@ -1619,13 +1619,13 @@ describe('useFieldProps', () => { it('should set "disabled" on blur when "onBlurValidator" and async onChange is given', async () => { const onChange: OnChange = async () => null - const validator = async () => null + const onChangeValidator = async () => null const onBlurValidator = async () => null const { result, rerender } = renderHook(useFieldProps, { initialProps: { onChange, - validator, + onChangeValidator, onBlurValidator, }, }) @@ -1650,7 +1650,7 @@ describe('useFieldProps', () => { rerender({ onChange, - validator: undefined, + onChangeValidator: undefined, onBlurValidator, }) @@ -1669,7 +1669,7 @@ describe('useFieldProps', () => { }) }) - it('should validate "validator" and "onBlurValidator" before onChange call', async () => { + it('should validate "onChangeValidator" and "onBlurValidator" before onChange call', async () => { const events = [] const onChange: OnChange = async (value) => { @@ -1679,8 +1679,8 @@ describe('useFieldProps', () => { return { success: 'saved' } as const } } - const validator = async () => { - events.push('validator') + const onChangeValidator = async () => { + events.push('onChangeValidator') } const onBlurValidator = async () => { events.push('onBlurValidator') @@ -1689,7 +1689,7 @@ describe('useFieldProps', () => { const { result } = renderHook(useFieldProps, { initialProps: { onChange, - validator, + onChangeValidator, onBlurValidator, value: '', }, @@ -1700,12 +1700,12 @@ describe('useFieldProps', () => { await validateBlur(result, '123') expect(result.current.fieldState).toBe('pending') - expect(events).toEqual(['validator', 'onBlurValidator']) + expect(events).toEqual(['onChangeValidator', 'onBlurValidator']) await waitFor(() => { expect(result.current.fieldState).toBe('complete') expect(events).toEqual([ - 'validator', + 'onChangeValidator', 'onBlurValidator', 'onChange', ]) @@ -1717,27 +1717,27 @@ describe('useFieldProps', () => { await validateBlur(result, '456') expect(result.current.fieldState).toBe('pending') - expect(events).toEqual(['validator', 'onBlurValidator']) + expect(events).toEqual(['onChangeValidator', 'onBlurValidator']) await wait(100) await waitFor(() => { expect(result.current.fieldState).toBe('success') expect(events).toEqual([ - 'validator', + 'onChangeValidator', 'onBlurValidator', 'onChange', ]) }) }) - it('should skip onChange call when "validator" returns error', async () => { + it('should skip onChange call when "onChangeValidator" returns error', async () => { const events = [] const onChange: OnChange = async () => { events.push('onChange') } - const validator = async () => { - events.push('validator') + const onChangeValidator = async () => { + events.push('onChangeValidator') return new Error('Error message') } @@ -1745,7 +1745,7 @@ describe('useFieldProps', () => { const { result } = renderHook(useFieldProps, { initialProps: { onChange, - validator, + onChangeValidator, value: '', }, }) @@ -1755,7 +1755,7 @@ describe('useFieldProps', () => { await validateBlur(result, '123') expect(result.current.fieldState).toBe('pending') - expect(events).toEqual(['validator']) + expect(events).toEqual(['onChangeValidator']) await waitFor(() => { expect(result.current.fieldState).toBe('error') @@ -1767,11 +1767,11 @@ describe('useFieldProps', () => { await validateBlur(result, '456') expect(result.current.fieldState).toBe('pending') - expect(events).toEqual(['validator']) + expect(events).toEqual(['onChangeValidator']) await waitFor(() => { expect(result.current.fieldState).toBe('error') - expect(events).toEqual(['validator']) + expect(events).toEqual(['onChangeValidator']) }) }) @@ -1852,7 +1852,7 @@ describe('useFieldProps', () => { }) }) - it('should wait for both "validator" and "onBlurValidator" and DataContext onChange before local onChange call', async () => { + it('should wait for both "onChangeValidator" and "onBlurValidator" and DataContext onChange before local onChange call', async () => { const events = [] const path = '/foo' @@ -1868,8 +1868,8 @@ describe('useFieldProps', () => { return new Error('Error message') } } - const validator = async () => { - events.push('validator') + const onChangeValidator = async () => { + events.push('onChangeValidator') } const onBlurValidator = async () => { events.push('onBlurValidator') @@ -1878,7 +1878,7 @@ describe('useFieldProps', () => { const { result } = renderHook(useFieldProps, { initialProps: { onChange, - validator, + onChangeValidator, onBlurValidator, path, value: '', @@ -1893,12 +1893,12 @@ describe('useFieldProps', () => { await validateBlur(result, '123') expect(result.current.fieldState).toBe('pending') - expect(events).toEqual(['validator', 'onBlurValidator']) + expect(events).toEqual(['onChangeValidator', 'onBlurValidator']) await waitFor(() => { expect(result.current.fieldState).toBe('error') expect(events).toEqual([ - 'validator', + 'onChangeValidator', 'onBlurValidator', 'onChangeForm', ]) @@ -1910,12 +1910,12 @@ describe('useFieldProps', () => { await validateBlur(result, '456') expect(result.current.fieldState).toBe('pending') - expect(events).toEqual(['validator', 'onBlurValidator']) + expect(events).toEqual(['onChangeValidator', 'onBlurValidator']) await waitFor(() => { expect(result.current.fieldState).toBe('success') expect(events).toEqual([ - 'validator', + 'onChangeValidator', 'onBlurValidator', 'onChangeForm', 'onChangeField', @@ -1923,7 +1923,7 @@ describe('useFieldProps', () => { }) }) - it('should handle gracefully the "validator" and "onBlurValidator" and DataContext "onChange" and before the local onChange call', async () => { + it('should handle gracefully the "onChangeValidator" and "onBlurValidator" and DataContext "onChange" and before the local onChange call', async () => { const events = [] const path = '/foo' @@ -1945,11 +1945,11 @@ describe('useFieldProps', () => { return { success: 'saved' } as const } - const validator = async (value) => { - events.push('validator') + const onChangeValidator = async (value) => { + events.push('onChangeValidator') if (value === 'invalid') { - return new Error('Error message by validator') + return new Error('Error message by onChangeValidator') } } const onBlurValidator = async (value) => { @@ -1963,7 +1963,7 @@ describe('useFieldProps', () => { const { result } = renderHook(useFieldProps, { initialProps: { onChange: onChangeField, - validator, + onChangeValidator, onBlurValidator, path, value: '', @@ -1986,7 +1986,7 @@ describe('useFieldProps', () => { await waitFor(() => { expect(events).toEqual([ - 'validator', + 'onChangeValidator', 'onChangeForm', 'onChangeField', ]) @@ -2002,7 +2002,7 @@ describe('useFieldProps', () => { await waitFor(() => { expect(events).toEqual([ - 'validator', + 'onChangeValidator', 'onChangeForm', 'onChangeField', 'onBlurValidator', @@ -2018,12 +2018,12 @@ describe('useFieldProps', () => { result.current.handleChange('invalid') }) - expect(events).toEqual(['validator']) + expect(events).toEqual(['onChangeValidator']) expect(result.current.fieldState).toBe('pending') expect(result.current.error).toBeUndefined() await waitFor(() => { - expect(events).toEqual(['validator']) + expect(events).toEqual(['onChangeValidator']) expect(result.current.fieldState).toBe('error') expect(result.current.error).toBeInstanceOf(Error) }) @@ -2035,11 +2035,11 @@ describe('useFieldProps', () => { expect(result.current.fieldState).toBe('pending') expect(result.current.error).toBeInstanceOf(Error) expect(getError(result.current.error).message).toBe( - 'Error message by validator' + 'Error message by onChangeValidator' ) await waitFor(() => { - expect(events).toEqual(['validator', 'onBlurValidator']) + expect(events).toEqual(['onChangeValidator', 'onBlurValidator']) expect(result.current.fieldState).toBe('error') expect(result.current.error).toBeInstanceOf(Error) expect(getError(result.current.error).message).toBe( @@ -2054,13 +2054,13 @@ describe('useFieldProps', () => { result.current.handleChange('valid') }) - expect(events).toEqual(['validator']) + expect(events).toEqual(['onChangeValidator']) expect(result.current.fieldState).toBe('pending') expect(result.current.error).toBeUndefined() await waitFor(() => { expect(events).toEqual([ - 'validator', + 'onChangeValidator', 'onChangeForm', 'onChangeField', ]) @@ -2086,7 +2086,7 @@ describe('useFieldProps', () => { }) }) - it('should have correct order for when calling the "validator" (if initiated and "onBlurValidator") and DataContext "onChange", before the local onChange call', async () => { + it('should have correct order for when calling the "onChangeValidator" (if initiated and "onBlurValidator") and DataContext "onChange", before the local onChange call', async () => { const events = [] const path = '/foo' @@ -2112,11 +2112,11 @@ describe('useFieldProps', () => { return { success: 'saved' } as const } } - const validator = async (value) => { - events.push('validator') + const onChangeValidator = async (value) => { + events.push('onChangeValidator') - if (value === 'invalid-validator') { - return new Error('Error in validator') + if (value === 'invalid-onChangeValidator') { + return new Error('Error in onChangeValidator') } } const onBlurValidator = async (value) => { @@ -2130,7 +2130,7 @@ describe('useFieldProps', () => { const { result } = renderHook(useFieldProps, { initialProps: { onChange: onChangeField, - validator, + onChangeValidator, onBlurValidator, path, value: '', @@ -2143,7 +2143,7 @@ describe('useFieldProps', () => { /** * The order we test in is: * - * - validator + * - onChangeValidator * - onBlurValidator (if initiated) * - onChangeForm * - onChangeField @@ -2160,7 +2160,7 @@ describe('useFieldProps', () => { await waitFor(() => { expect(events).toEqual([ - 'validator', + 'onChangeValidator', 'onChangeForm', 'onChangeField', ]) @@ -2176,7 +2176,7 @@ describe('useFieldProps', () => { await waitFor(() => { expect(events).toEqual([ - 'validator', + 'onChangeValidator', 'onChangeForm', 'onChangeField', 'onBlurValidator', @@ -2189,18 +2189,18 @@ describe('useFieldProps', () => { events.splice(0, events.length) act(() => { - result.current.handleChange('invalid-validator') + result.current.handleChange('invalid-onChangeValidator') }) - expect(events).toEqual(['validator']) + expect(events).toEqual(['onChangeValidator']) expect(result.current.fieldState).toBe('pending') expect(result.current.error).toBeUndefined() await waitFor(() => { - expect(events).toEqual(['validator']) + expect(events).toEqual(['onChangeValidator']) expect(result.current.fieldState).toBe('error') expect(getError(result.current.error).message).toBe( - 'Error in validator' + 'Error in onChangeValidator' ) }) @@ -2211,7 +2211,7 @@ describe('useFieldProps', () => { result.current.handleChange('invalid-onBlurValidator') }) - expect(events).toEqual(['validator']) + expect(events).toEqual(['onChangeValidator']) await wait(1) @@ -2223,7 +2223,7 @@ describe('useFieldProps', () => { await waitFor(() => { expect(events).toEqual([ - 'validator', + 'onChangeValidator', 'onChangeForm', 'onChangeField', 'onBlurValidator', @@ -2241,12 +2241,12 @@ describe('useFieldProps', () => { result.current.handleChange('invalid-onChangeForm') }) - expect(events).toEqual(['validator']) + expect(events).toEqual(['onChangeValidator']) expect(result.current.fieldState).toBe('pending') await waitFor(() => { - expect(events).toEqual(['validator', 'onChangeForm']) + expect(events).toEqual(['onChangeValidator', 'onChangeForm']) expect(result.current.fieldState).toBe('error') expect(getError(result.current.error).message).toBe( 'Error in onChangeForm' @@ -2260,13 +2260,13 @@ describe('useFieldProps', () => { result.current.handleChange('invalid-onChangeField') }) - expect(events).toEqual(['validator']) + expect(events).toEqual(['onChangeValidator']) expect(result.current.fieldState).toBe('pending') await waitFor(() => { expect(events).toEqual([ - 'validator', + 'onChangeValidator', 'onChangeForm', 'onChangeField', ]) @@ -2283,7 +2283,7 @@ describe('useFieldProps', () => { result.current.handleChange('invalid-onBlurValidator') }) - expect(events).toEqual(['validator']) + expect(events).toEqual(['onChangeValidator']) await wait(1) @@ -2293,7 +2293,7 @@ describe('useFieldProps', () => { await waitFor(() => { expect(events).toEqual([ - 'validator', + 'onChangeValidator', 'onChangeForm', 'onChangeField', 'onBlurValidator', @@ -3372,6 +3372,7 @@ describe('useFieldProps', () => { expect(result.current.error).toBeInstanceOf(Error) }) + // Deprecated – can be removed in v11 describe('validator', () => { describe('validateInitially', () => { it('should show error message initially', async () => { @@ -4371,17 +4372,17 @@ describe('useFieldProps', () => { }) }) - describe('onBlurValidator', () => { + describe('onChangeValidator', () => { describe('validateInitially', () => { it('should show error message initially', async () => { - const onBlurValidator = jest.fn(() => { + const onChangeValidator = jest.fn(() => { return new Error('My Error') }) render( @@ -4390,11 +4391,11 @@ describe('useFieldProps', () => { await waitFor(() => { expect(screen.queryByRole('alert')).toBeInTheDocument() }) - expect(onBlurValidator).toHaveBeenCalledTimes(1) + expect(onChangeValidator).toHaveBeenCalledTimes(1) }) - it('should show error message initially when validator is async', async () => { - const onBlurValidator = jest.fn(async () => { + it('should show error message initially when onChangeValidator is async', async () => { + const onChangeValidator = jest.fn(async () => { return new Error('My Error') }) @@ -4402,46 +4403,40 @@ describe('useFieldProps', () => { ) - expect( - document.querySelector('.dnb-forms-submit-indicator') - ).toHaveClass('dnb-forms-submit-indicator--state-pending') - await waitFor(() => { expect(screen.queryByRole('alert')).toBeInTheDocument() }) - expect(onBlurValidator).toHaveBeenCalledTimes(1) + expect(onChangeValidator).toHaveBeenCalledTimes(1) }) }) describe('connectWithPath', () => { - const validatorFn: UseFieldProps['validator'] = ( - num, - { connectWithPath } - ) => { - const amount = connectWithPath('/refValue').getValue() + const onChangeValidatorFn: UseFieldProps['onChangeValidator'] = + (num, { connectWithPath }) => { + const amount = connectWithPath('/refValue').getValue() - if (amount >= num) { - return new Error(`The amount should be greater than ${amount}`) + if (amount >= num) { + return new Error(`The amount should be greater than ${amount}`) + } } - } - it('should show validator error on form submit', async () => { - const validator = jest.fn(validatorFn) + it('should show onChangeValidator error on form submit', async () => { + const onChangeValidator = jest.fn(onChangeValidatorFn) render( ) @@ -4456,9 +4451,8 @@ describe('useFieldProps', () => { 'The amount should be greater than 2' ) }) - - expect(validator).toHaveBeenCalledTimes(1) - expect(validator).toHaveBeenLastCalledWith( + expect(onChangeValidator).toHaveBeenCalledTimes(1) + expect(onChangeValidator).toHaveBeenLastCalledWith( 2, expect.objectContaining({ connectWithPath: expect.any(Function), @@ -4467,56 +4461,55 @@ describe('useFieldProps', () => { }) it('should update error message on input change', async () => { - const validator = jest.fn(validatorFn) + const onChangeValidator = jest.fn(onChangeValidatorFn) render( - + ) - const [inputWithRefValue, inputWithValidator] = Array.from( + const [inputWithRefValue] = Array.from( document.querySelectorAll('input') ) expect(screen.queryByRole('alert')).not.toBeInTheDocument() - // Make a change to the input with the validator - await userEvent.type(inputWithValidator, '2') - fireEvent.blur(inputWithValidator) + // Show error message + fireEvent.submit(document.querySelector('form')) await waitFor(() => { expect(screen.queryByRole('alert')).toBeInTheDocument() expect(screen.queryByRole('alert')).toHaveTextContent( - 'The amount should be greater than 12' + 'The amount should be greater than 2' ) }) // Make a change to the ref input - await userEvent.type(inputWithRefValue, '3') + await userEvent.type(inputWithRefValue, '2') expect(screen.queryByRole('alert')).toHaveTextContent( - 'The amount should be greater than 123' + 'The amount should be greater than 22' ) }) it('should hide error message when validation is successful', async () => { - const validator = jest.fn(validatorFn) + const onChangeValidator = jest.fn(onChangeValidatorFn) render( ) @@ -4532,13 +4525,16 @@ describe('useFieldProps', () => { await waitFor(() => { expect(screen.queryByRole('alert')).toBeInTheDocument() + expect(screen.queryByRole('alert')).toHaveTextContent( + 'The amount should be greater than 2' + ) }) await userEvent.type(inputWithRefValue, '{Backspace}') expect(screen.queryByRole('alert')).not.toBeInTheDocument() - expect(validator).toHaveBeenCalledTimes(2) - expect(validator).toHaveBeenLastCalledWith( + expect(onChangeValidator).toHaveBeenCalledTimes(2) + expect(onChangeValidator).toHaveBeenLastCalledWith( 2, expect.objectContaining({ connectWithPath: expect.any(Function), @@ -4546,17 +4542,17 @@ describe('useFieldProps', () => { ) }) - it('should keep error message hidden during ref input change', async () => { - const validator = jest.fn(validatorFn) + it('should keep error message hidden after validation is successful and another input change', async () => { + const onChangeValidator = jest.fn(onChangeValidatorFn) render( ) @@ -4572,12 +4568,15 @@ describe('useFieldProps', () => { await waitFor(() => { expect(screen.queryByRole('alert')).toBeInTheDocument() + expect(screen.queryByRole('alert')).toHaveTextContent( + 'The amount should be greater than 2' + ) }) await userEvent.type(inputWithRefValue, '{Backspace}') expect(screen.queryByRole('alert')).not.toBeInTheDocument() - expect(validator).toHaveBeenCalledTimes(2) + expect(onChangeValidator).toHaveBeenCalledTimes(2) await userEvent.type(inputWithRefValue, '2') @@ -4586,16 +4585,16 @@ describe('useFieldProps', () => { describe('validateInitially', () => { it('should show error message initially', async () => { - const validator = jest.fn(validatorFn) + const onChangeValidator = jest.fn(onChangeValidatorFn) render( @@ -4603,20 +4602,23 @@ describe('useFieldProps', () => { await waitFor(() => { expect(screen.queryByRole('alert')).toBeInTheDocument() + expect(screen.queryByRole('alert')).toHaveTextContent( + 'The amount should be greater than 2' + ) }) }) - it('should not show error message while typing', async () => { - const validator = jest.fn(validatorFn) + it('should not show error message after it was hidden while typing', async () => { + const onChangeValidator = jest.fn(onChangeValidatorFn) render( @@ -4626,7 +4628,12 @@ describe('useFieldProps', () => { document.querySelectorAll('input') ) - expect(screen.queryByRole('alert')).toBeInTheDocument() + await waitFor(() => { + expect(screen.queryByRole('alert')).toBeInTheDocument() + expect(screen.queryByRole('alert')).toHaveTextContent( + 'The amount should be greater than 2' + ) + }) await userEvent.type(inputWithRefValue, '{Backspace}') @@ -4639,36 +4646,41 @@ describe('useFieldProps', () => { }) describe('validateUnchanged', () => { - it('should not show error message initially', async () => { - const validator = jest.fn(validatorFn) + it('should show error message initially', async () => { + const onChangeValidator = jest.fn(onChangeValidatorFn) render( ) - expect(screen.queryByRole('alert')).not.toBeInTheDocument() + await waitFor(() => { + expect(screen.queryByRole('alert')).toBeInTheDocument() + expect(screen.queryByRole('alert')).toHaveTextContent( + 'The amount should be greater than 2' + ) + }) }) - it('should not show error message while typing', async () => { - const validator = jest.fn(validatorFn) + it('should hide and show error message while typing', async () => { + const onChangeValidator = jest.fn(onChangeValidatorFn) render( @@ -4678,7 +4690,12 @@ describe('useFieldProps', () => { document.querySelectorAll('input') ) - expect(screen.queryByRole('alert')).not.toBeInTheDocument() + await waitFor(() => { + expect(screen.queryByRole('alert')).toBeInTheDocument() + expect(screen.queryByRole('alert')).toHaveTextContent( + 'The amount should be greater than 2' + ) + }) await userEvent.type(inputWithRefValue, '{Backspace}') @@ -4686,22 +4703,1006 @@ describe('useFieldProps', () => { await userEvent.type(inputWithRefValue, '3') - expect(screen.queryByRole('alert')).not.toBeInTheDocument() + await waitFor(() => { + expect(screen.queryByRole('alert')).toBeInTheDocument() + expect(screen.queryByRole('alert')).toHaveTextContent( + 'The amount should be greater than 3' + ) + }) }) }) describe('continuousValidation', () => { it('should show not show error message initially', async () => { - const validator = jest.fn(validatorFn) + const onChangeValidator = jest.fn(onChangeValidatorFn) render( + + ) + + expect(screen.queryByRole('alert')).not.toBeInTheDocument() + }) + + it('should hide and show error message while typing', async () => { + const onChangeValidator = jest.fn(onChangeValidatorFn) + + render( + + + + + + ) + + const [inputWithRefValue] = Array.from( + document.querySelectorAll('input') + ) + + // Show error message + fireEvent.submit(document.querySelector('form')) + + await waitFor(() => { + expect(screen.queryByRole('alert')).toBeInTheDocument() + expect(screen.queryByRole('alert')).toHaveTextContent( + 'The amount should be greater than 2' + ) + }) + + await userEvent.type(inputWithRefValue, '{Backspace}') + + expect(screen.queryByRole('alert')).not.toBeInTheDocument() + + await userEvent.type(inputWithRefValue, '3') + + await waitFor(() => { + expect(screen.queryByRole('alert')).toBeInTheDocument() + expect(screen.queryByRole('alert')).toHaveTextContent( + 'The amount should be greater than 3' + ) + }) + }) + }) + }) + + describe('onChangeValidators given as an array', () => { + it('should call all onChangeValidators returned as an array', async () => { + const fooValidator = jest.fn((value) => { + if (value.includes('foo')) { + return new Error('foo') + } + }) + + const barValidator = jest.fn((value) => { + if (value.includes('bar')) { + return new Error('bar') + } + }) + + const myOnChangeValidator = jest.fn(() => { + return [fooValidator, barValidator] + }) + + render( + + + + ) + + await waitFor(() => { + expect(screen.queryByRole('alert')).toHaveTextContent('foo') + }) + expect(myOnChangeValidator).toHaveBeenCalledTimes(1) + expect(fooValidator).toHaveBeenCalledTimes(1) + expect(barValidator).toHaveBeenCalledTimes(0) + + await userEvent.type( + document.querySelector('input'), + '{Backspace}bar' + ) + + await waitFor(() => { + expect(screen.queryByRole('alert')).toHaveTextContent('bar') + }) + expect(myOnChangeValidator).toHaveBeenCalledTimes(5) + expect(fooValidator).toHaveBeenCalledTimes(5) + expect(barValidator).toHaveBeenCalledTimes(4) + }) + + it('should call all validators returned as an array (mixed async and sync)', async () => { + const fooValidator = jest.fn(async (value) => { + if (value.includes('foo')) { + return new Error('foo') + } + }) + + const barValidator = jest.fn((value) => { + if (value.includes('bar')) { + return new Error('bar') + } + }) + + // The main validator needs to be async, because it contains async validators in the array + const myValidator = jest.fn(async () => { + return [fooValidator, barValidator] + }) + + render( + + + + ) + + await waitFor(() => { + expect(screen.queryByRole('alert')).toHaveTextContent('foo') + }) + expect(myValidator).toHaveBeenCalledTimes(1) + expect(fooValidator).toHaveBeenCalledTimes(1) + expect(barValidator).toHaveBeenCalledTimes(0) + + await userEvent.type( + document.querySelector('input'), + '{Backspace}bar' + ) + + await waitFor(() => { + expect(screen.queryByRole('alert')).toHaveTextContent('bar') + }) + expect(myValidator).toHaveBeenCalledTimes(5) + expect(fooValidator).toHaveBeenCalledTimes(5) + expect(barValidator).toHaveBeenCalledTimes(4) + }) + }) + + describe('exportValidators', () => { + it('should call exported validators from mock component', async () => { + let internalValidators, fooValidator, barValidator, bazValidator + + const MockComponent = (props) => { + barValidator = jest.fn((value) => { + if (value.includes('bar')) { + return new Error('bar') + } + }) + + bazValidator = jest.fn((value) => { + if (value.includes('baz')) { + return new Error('baz') + } + }) + + internalValidators = jest.fn((value) => { + return barValidator(value) || bazValidator(value) + }) + + return ( + + ) + } + + const publicValidator = jest.fn( + (value, { validators: { barValidator, bazValidator } }) => { + fooValidator = jest.fn(() => { + if (value.includes('foo')) { + return new Error('foo') + } + }) + + return [fooValidator, barValidator, bazValidator] + } + ) + + render( + + + + ) + + await waitFor(() => { + expect(screen.queryByRole('alert')).toHaveTextContent('foo') + }) + expect(publicValidator).toHaveBeenCalledTimes(1) + expect(fooValidator).toHaveBeenCalledTimes(1) + expect(barValidator).toHaveBeenCalledTimes(0) + expect(bazValidator).toHaveBeenCalledTimes(0) + expect(internalValidators).toHaveBeenCalledTimes(0) + + expect(publicValidator).toHaveBeenLastCalledWith( + 'foo', + expect.objectContaining({ + validators: { + barValidator, + bazValidator, + }, + }) + ) + + await userEvent.type( + document.querySelector('input'), + '{Backspace}bar' + ) + await waitFor(() => { + expect(screen.queryByRole('alert')).toHaveTextContent('bar') + }) + expect(publicValidator).toHaveBeenCalledTimes(5) + expect(fooValidator).toHaveBeenCalledTimes(1) + expect(barValidator).toHaveBeenCalledTimes(4) + expect(bazValidator).toHaveBeenCalledTimes(3) + expect(internalValidators).toHaveBeenCalledTimes(0) + + await userEvent.type( + document.querySelector('input'), + '{Backspace}baz' + ) + + await waitFor(() => { + expect(screen.queryByRole('alert')).toHaveTextContent('baz') + }) + expect(publicValidator).toHaveBeenCalledTimes(9) + expect(fooValidator).toHaveBeenCalledTimes(1) + expect(barValidator).toHaveBeenCalledTimes(8) + expect(bazValidator).toHaveBeenCalledTimes(7) + expect(internalValidators).toHaveBeenCalledTimes(0) + }) + + it('should export and call same validator without "Maximum call stack size exceeded"', async () => { + const onBlurValidator = jest.fn((value) => { + if (value === '1234') { + return Error('Error message') + } + }) + + render( + + ) + + const input = document.querySelector('input') + + await userEvent.type(input, '123') + fireEvent.blur(input) + + expect(onBlurValidator).toHaveBeenCalledTimes(1) + expect(document.querySelector('.dnb-form-status')).toBeNull() + + await userEvent.type(input, '4') + fireEvent.blur(input) + + expect(onBlurValidator).toHaveBeenCalledTimes(2) + await waitFor(() => { + expect( + document.querySelector('.dnb-form-status') + ).toHaveTextContent('Error message') + }) + }) + + it('should show error on every value change', async () => { + const exportedValidator = jest.fn((value) => { + if (value === '1234') { + return Error('Error message') + } + }) + + const myValidator = jest.fn((value, { validators }) => { + const { exportedValidator } = validators + + return [exportedValidator] + }) + + const MockComponent = (props) => { + return ( + + ) + } + + render() + + const input = document.querySelector('input') + + await userEvent.type(input, '123') + fireEvent.blur(input) + + expect(document.querySelector('.dnb-form-status')).toBeNull() + + await userEvent.type(input, '4') + fireEvent.blur(input) + + expect(exportedValidator).toHaveBeenCalledTimes(2) + expect(myValidator).toHaveBeenCalledTimes(2) + await waitFor(() => { + expect( + document.querySelector('.dnb-form-status') + ).toHaveTextContent('Error message') + }) + + await userEvent.type(input, '{Backspace}4') + fireEvent.blur(input) + + expect(exportedValidator).toHaveBeenCalledTimes(3) + expect(myValidator).toHaveBeenCalledTimes(3) + await waitFor(() => { + expect( + document.querySelector('.dnb-form-status') + ).toHaveTextContent('Error message') + }) + }) + + it('should support mixed sync and async validators', async () => { + const exportedValidator = jest.fn(async (value) => { + if (value === '1234') { + return Error('Error message') + } + }) + + const myValidator = jest.fn((value, { validators }) => { + const { exportedValidator } = validators + + return [exportedValidator] + }) + + const MockComponent = (props) => { + return ( + + ) + } + + render() + + const input = document.querySelector('input') + + await userEvent.type(input, '123') + fireEvent.blur(input) + + expect(document.querySelector('.dnb-form-status')).toBeNull() + + await userEvent.type(input, '4') + fireEvent.blur(input) + + expect(exportedValidator).toHaveBeenCalledTimes(1) + expect(myValidator).toHaveBeenCalledTimes(4) + await waitFor(() => { + expect( + document.querySelector('.dnb-form-status') + ).toHaveTextContent('Error message') + }) + + await userEvent.type(input, '{Backspace}4') + fireEvent.blur(input) + + expect(exportedValidator).toHaveBeenCalledTimes(2) + expect(myValidator).toHaveBeenCalledTimes(6) + await waitFor(() => { + expect( + document.querySelector('.dnb-form-status') + ).toHaveTextContent('Error message') + }) + }) + + it('should only call returned validators (barValidator should not be called)', async () => { + let internalValidators, fooValidator, barValidator, bazValidator + + const MockComponent = (props) => { + barValidator = jest.fn((value) => { + if (value.includes('bar')) { + return new Error('bar') + } + }) + + bazValidator = jest.fn((value) => { + if (value.includes('baz')) { + return new Error('baz') + } + }) + + internalValidators = jest.fn((value) => { + return barValidator(value) || bazValidator(value) + }) + + return ( + + ) + } + + const publicValidator = jest.fn( + (value, { validators: { bazValidator } }) => { + fooValidator = jest.fn(() => { + if (value.includes('foo')) { + return new Error('foo') + } + }) + + return [fooValidator, bazValidator] + } + ) + + render( + + + + ) + + await waitFor(() => { + expect(screen.queryByRole('alert')).toHaveTextContent('foo') + }) + expect(publicValidator).toHaveBeenCalledTimes(1) + expect(fooValidator).toHaveBeenCalledTimes(1) + expect(barValidator).toHaveBeenCalledTimes(0) + expect(bazValidator).toHaveBeenCalledTimes(0) + expect(internalValidators).toHaveBeenCalledTimes(0) + + expect(publicValidator).toHaveBeenLastCalledWith( + 'foo', + expect.objectContaining({ + validators: { + barValidator, + bazValidator, + }, + }) + ) + + await userEvent.type( + document.querySelector('input'), + '{Backspace}bar' // remove one letter from bar, so the bar validator should return undefined + ) + await waitFor(() => { + // Here we should not see the bar validator called + expect(screen.queryByRole('alert')).not.toBeInTheDocument() + }) + expect(publicValidator).toHaveBeenCalledTimes(5) + expect(fooValidator).toHaveBeenCalledTimes(1) + expect(barValidator).toHaveBeenCalledTimes(0) + expect(bazValidator).toHaveBeenCalledTimes(4) + expect(internalValidators).toHaveBeenCalledTimes(0) + + await userEvent.type( + document.querySelector('input'), + '{Backspace}baz' + ) + + await waitFor(() => { + expect(screen.queryByRole('alert')).toHaveTextContent('baz') + }) + expect(publicValidator).toHaveBeenCalledTimes(9) + expect(fooValidator).toHaveBeenCalledTimes(1) + expect(barValidator).toHaveBeenCalledTimes(0) + expect(bazValidator).toHaveBeenCalledTimes(8) + expect(internalValidators).toHaveBeenCalledTimes(0) + }) + + it('should show error when validateInitially is set to true', async () => { + const exportedValidator = jest.fn(() => { + return Error('Error message') + }) + + const myValidator = jest.fn((value, { validators }) => { + const { exportedValidator } = validators + return [exportedValidator] + }) + + const MockComponent = (props) => { + return ( + + ) + } + + render() + + await waitFor(() => { + expect( + document.querySelector('.dnb-form-status') + ).toBeInTheDocument() + }) + }) + + it('should not run exported internal validators when a onChangeValidator is given', async () => { + const exportedValidator = jest.fn(() => { + return undefined + }) + + const myValidator = jest.fn(() => { + return undefined + }) + + const MockComponent = (props) => { + return ( + + ) + } + + render() + + await expect(() => { + expect( + document.querySelector('.dnb-form-status') + ).toBeInTheDocument() + }).toNeverResolve() + }) + + it('should not call internal validators when they are not returned in the publicValidator', async () => { + let internalValidators, barValidator, bazValidator + + const MockComponent = (props) => { + barValidator = jest.fn((value) => { + if (value.includes('bar')) { + return new Error('bar') + } + }) + + bazValidator = jest.fn((value) => { + if (value.includes('baz')) { + return new Error('baz') + } + }) + + internalValidators = jest.fn((value) => { + return barValidator(value) || bazValidator(value) + }) + + return ( + + ) + } + + const publicValidator = jest.fn((value) => { + if (value.includes('foo')) { + return new Error('foo') + } + }) + + render( + + + + ) + + await waitFor(() => { + expect(screen.queryByRole('alert')).toHaveTextContent('foo') + }) + expect(publicValidator).toHaveBeenCalledTimes(1) + expect(barValidator).toHaveBeenCalledTimes(0) + expect(bazValidator).toHaveBeenCalledTimes(0) + expect(internalValidators).toHaveBeenCalledTimes(0) + + expect(publicValidator).toHaveBeenLastCalledWith( + 'foo', + expect.objectContaining({ + validators: { + barValidator, + bazValidator, + }, + }) + ) + + await userEvent.type( + document.querySelector('input'), + '{Backspace}bar' + ) + await expect(() => { + expect(screen.queryByRole('alert')).toBeInTheDocument() + }).toNeverResolve() + expect(publicValidator).toHaveBeenCalledTimes(5) + expect(barValidator).toHaveBeenCalledTimes(0) + expect(bazValidator).toHaveBeenCalledTimes(0) + expect(internalValidators).toHaveBeenCalledTimes(0) + + await userEvent.type( + document.querySelector('input'), + '{Backspace}baz' + ) + + await expect(() => { + expect(screen.queryByRole('alert')).toBeInTheDocument() + }).toNeverResolve() + expect(publicValidator).toHaveBeenCalledTimes(9) + expect(barValidator).toHaveBeenCalledTimes(0) + expect(bazValidator).toHaveBeenCalledTimes(0) + expect(internalValidators).toHaveBeenCalledTimes(0) + }) + }) + }) + + describe('onBlurValidator', () => { + describe('validateInitially', () => { + it('should show error message initially', async () => { + const onBlurValidator = jest.fn(() => { + return new Error('My Error') + }) + + render( + + + + ) + + await waitFor(() => { + expect(screen.queryByRole('alert')).toBeInTheDocument() + }) + expect(onBlurValidator).toHaveBeenCalledTimes(1) + }) + + it('should show error message initially when onBlurValidator is async', async () => { + const onBlurValidator = jest.fn(async () => { + return new Error('My Error') + }) + + render( + + + + ) + + expect( + document.querySelector('.dnb-forms-submit-indicator') + ).toHaveClass('dnb-forms-submit-indicator--state-pending') + + await waitFor(() => { + expect(screen.queryByRole('alert')).toBeInTheDocument() + }) + expect(onBlurValidator).toHaveBeenCalledTimes(1) + }) + }) + + describe('connectWithPath', () => { + const onBlurValidatorFn: UseFieldProps['onBlurValidator'] = ( + num, + { connectWithPath } + ) => { + const amount = connectWithPath('/refValue').getValue() + + if (amount >= num) { + return new Error(`The amount should be greater than ${amount}`) + } + } + + it('should show onBlurValidator error on form submit', async () => { + const onBlurValidator = jest.fn(onBlurValidatorFn) + + render( + + + + + + ) + + expect(screen.queryByRole('alert')).not.toBeInTheDocument() + + fireEvent.submit(document.querySelector('form')) + + await waitFor(() => { + expect(screen.queryByRole('alert')).toBeInTheDocument() + expect(screen.queryByRole('alert')).toHaveTextContent( + 'The amount should be greater than 2' + ) + }) + + expect(onBlurValidator).toHaveBeenCalledTimes(1) + expect(onBlurValidator).toHaveBeenLastCalledWith( + 2, + expect.objectContaining({ + connectWithPath: expect.any(Function), + }) + ) + }) + + it('should update error message on input change', async () => { + const onBlurValidator = jest.fn(onBlurValidatorFn) + + render( + + + + + + ) + + const [inputWithRefValue, inputWithOnBlurValidator] = Array.from( + document.querySelectorAll('input') + ) + + expect(screen.queryByRole('alert')).not.toBeInTheDocument() + + // Make a change to the input with the validator + await userEvent.type(inputWithOnBlurValidator, '2') + fireEvent.blur(inputWithOnBlurValidator) + + await waitFor(() => { + expect(screen.queryByRole('alert')).toBeInTheDocument() + expect(screen.queryByRole('alert')).toHaveTextContent( + 'The amount should be greater than 12' + ) + }) + + // Make a change to the ref input + await userEvent.type(inputWithRefValue, '3') + + expect(screen.queryByRole('alert')).toHaveTextContent( + 'The amount should be greater than 123' + ) + }) + + it('should hide error message when validation is successful', async () => { + const onBlurValidator = jest.fn(onBlurValidatorFn) + + render( + + + + + + ) + + const [inputWithRefValue] = Array.from( + document.querySelectorAll('input') + ) + + expect(screen.queryByRole('alert')).not.toBeInTheDocument() + + // Show error message + fireEvent.submit(document.querySelector('form')) + + await waitFor(() => { + expect(screen.queryByRole('alert')).toBeInTheDocument() + }) + + await userEvent.type(inputWithRefValue, '{Backspace}') + + expect(screen.queryByRole('alert')).not.toBeInTheDocument() + expect(onBlurValidator).toHaveBeenCalledTimes(2) + expect(onBlurValidator).toHaveBeenLastCalledWith( + 2, + expect.objectContaining({ + connectWithPath: expect.any(Function), + }) + ) + }) + + it('should keep error message hidden during ref input change', async () => { + const onBlurValidator = jest.fn(onBlurValidatorFn) + + render( + + + + + + ) + + const [inputWithRefValue] = Array.from( + document.querySelectorAll('input') + ) + + expect(screen.queryByRole('alert')).not.toBeInTheDocument() + + // Show error message + fireEvent.submit(document.querySelector('form')) + + await waitFor(() => { + expect(screen.queryByRole('alert')).toBeInTheDocument() + }) + + await userEvent.type(inputWithRefValue, '{Backspace}') + + expect(screen.queryByRole('alert')).not.toBeInTheDocument() + expect(onBlurValidator).toHaveBeenCalledTimes(2) + + await userEvent.type(inputWithRefValue, '2') + + expect(screen.queryByRole('alert')).not.toBeInTheDocument() + }) + + describe('validateInitially', () => { + it('should show error message initially', async () => { + const onBlurValidator = jest.fn(onBlurValidatorFn) + + render( + + + + + + ) + + await waitFor(() => { + expect(screen.queryByRole('alert')).toBeInTheDocument() + }) + }) + + it('should not show error message while typing', async () => { + const onBlurValidator = jest.fn(onBlurValidatorFn) + + render( + + + + + + ) + + const [inputWithRefValue] = Array.from( + document.querySelectorAll('input') + ) + + expect(screen.queryByRole('alert')).toBeInTheDocument() + + await userEvent.type(inputWithRefValue, '{Backspace}') + + expect(screen.queryByRole('alert')).not.toBeInTheDocument() + + await userEvent.type(inputWithRefValue, '3') + + expect(screen.queryByRole('alert')).not.toBeInTheDocument() + }) + }) + + describe('validateUnchanged', () => { + it('should not show error message initially', async () => { + const onBlurValidator = jest.fn(onBlurValidatorFn) + + render( + + + + + + ) + + expect(screen.queryByRole('alert')).not.toBeInTheDocument() + }) + + it('should not show error message while typing', async () => { + const onBlurValidator = jest.fn(onBlurValidatorFn) + + render( + + + + + + ) + + const [inputWithRefValue] = Array.from( + document.querySelectorAll('input') + ) + + expect(screen.queryByRole('alert')).not.toBeInTheDocument() + + await userEvent.type(inputWithRefValue, '{Backspace}') + + expect(screen.queryByRole('alert')).not.toBeInTheDocument() + + await userEvent.type(inputWithRefValue, '3') + + expect(screen.queryByRole('alert')).not.toBeInTheDocument() + }) + }) + + describe('continuousValidation', () => { + it('should show not show error message initially', async () => { + const onBlurValidator = jest.fn(onBlurValidatorFn) + + render( + + + + diff --git a/packages/dnb-eufemia/src/extensions/forms/hooks/useFieldProps.ts b/packages/dnb-eufemia/src/extensions/forms/hooks/useFieldProps.ts index 66121efcd63..0cdfbba0333 100644 --- a/packages/dnb-eufemia/src/extensions/forms/hooks/useFieldProps.ts +++ b/packages/dnb-eufemia/src/extensions/forms/hooks/useFieldProps.ts @@ -121,7 +121,9 @@ export default function useFieldProps( onBlur, onChange, onBlurValidator, + // Deprecated – can be removed in v11 validator, + onChangeValidator = validator, exportValidators, schema, validateInitially, @@ -343,10 +345,10 @@ export default function useFieldProps( dataContextError ) - const onChangeValidatorRef = useRef(validator) + const onChangeValidatorRef = useRef(onChangeValidator) useUpdateEffect(() => { - onChangeValidatorRef.current = validator - }, [validator]) + onChangeValidatorRef.current = onChangeValidator + }, [onChangeValidator]) // Tobias, will this still work? now that we do onChangeValidator = validator? const onBlurValidatorRef = useRef(onBlurValidator) useUpdateEffect(() => { onBlurValidatorRef.current = onBlurValidator @@ -386,7 +388,7 @@ export default function useFieldProps( } const eventPool = useRef({ - validator: null, + onChangeValidator: null, onBlurValidator: null, onChangeContext: null, onChangeLocal: null, @@ -1488,7 +1490,7 @@ export default function useFieldProps( } addToPool( - 'validator', + 'onChangeValidator', validateValue, isAsync(onChangeValidatorRef.current) ) @@ -2051,7 +2053,7 @@ export default function useFieldProps( } addToPool( - 'validator', + 'onChangeValidator', startOnChangeValidatorValidation, isAsync(onChangeValidatorRef.current) ) diff --git a/packages/dnb-eufemia/src/extensions/forms/types.ts b/packages/dnb-eufemia/src/extensions/forms/types.ts index b65c5707c4e..75883d11fb5 100644 --- a/packages/dnb-eufemia/src/extensions/forms/types.ts +++ b/packages/dnb-eufemia/src/extensions/forms/types.ts @@ -307,7 +307,9 @@ export interface UseFieldProps< // - Validation required?: boolean schema?: AllJSONSchemaVersions + /** @deprecated Use `onChangeValidator` instead */ validator?: Validator + onChangeValidator?: Validator onBlurValidator?: Validator exportValidators?: Record> validateRequired?: (