diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/String/__tests__/String.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/String/__tests__/String.test.tsx index ccf47a55194..bfd137344c7 100644 --- a/packages/dnb-eufemia/src/extensions/forms/Field/String/__tests__/String.test.tsx +++ b/packages/dnb-eufemia/src/extensions/forms/Field/String/__tests__/String.test.tsx @@ -663,10 +663,10 @@ describe('Field.String', () => { render() const input = document.querySelector('input') await userEvent.type(input, 'def') - expect(onChange.mock.calls).toHaveLength(3) - expect(onChange.mock.calls[0][0]).toEqual('abcd') - expect(onChange.mock.calls[1][0]).toEqual('abcde') - expect(onChange.mock.calls[2][0]).toEqual('abcdef') + expect(onChange).toHaveBeenCalledTimes(3) + expect(onChange).toHaveBeenNthCalledWith(1, 'abcd') + expect(onChange).toHaveBeenNthCalledWith(2, 'abcde') + expect(onChange).toHaveBeenNthCalledWith(3, 'abcdef') }) it('calls onFocus with current value', () => { @@ -676,8 +676,8 @@ describe('Field.String', () => { act(() => { input.focus() }) - expect(onFocus.mock.calls).toHaveLength(1) - expect(onFocus.mock.calls[0][0]).toEqual('blah') + expect(onFocus).toHaveBeenCalledTimes(1) + expect(onFocus).toHaveBeenNthCalledWith(1, 'blah') }) it('calls onBlur with current value', async () => { @@ -687,12 +687,12 @@ describe('Field.String', () => { input.focus() fireEvent.blur(input) await wait(0) - expect(onBlur.mock.calls).toHaveLength(1) - expect(onBlur.mock.calls[0][0]).toEqual('song2') + expect(onBlur).toHaveBeenCalledTimes(1) + expect(onBlur).toHaveBeenNthCalledWith(1, 'song2') await userEvent.type(input, '345') fireEvent.blur(input) - expect(onBlur.mock.calls).toHaveLength(2) - expect(onBlur.mock.calls[1][0]).toEqual('song2345') + expect(onBlur).toHaveBeenCalledTimes(2) + expect(onBlur).toHaveBeenNthCalledWith(2, 'song2345') }) }) @@ -881,8 +881,12 @@ describe('Field.String', () => { ) await waitFor(() => { // Wait for since external validators are processed asynchronously - expect(validator.mock.calls).toHaveLength(1) - expect((validator.mock.calls[0] as unknown[])[0]).toEqual('abc') + expect(validator).toHaveBeenCalledTimes(1) + expect(validator).toHaveBeenNthCalledWith( + 1, + 'abc', + expect.anything() + ) expect( screen.getByText('I think this is wrong') ).toBeInTheDocument() @@ -893,13 +897,21 @@ describe('Field.String', () => { fireEvent.blur(input) await waitFor(() => { - expect(validator.mock.calls).toHaveLength(4) - expect((validator.mock.calls[1] as unknown[])[0]).toEqual('abcd') - expect((validator.mock.calls[2] as unknown[])[0]).toEqual( - 'abcde' + expect(validator).toHaveBeenCalledTimes(4) + expect(validator).toHaveBeenNthCalledWith( + 2, + 'abcd', + expect.anything() + ) + expect(validator).toHaveBeenNthCalledWith( + 3, + 'abcde', + expect.anything() ) - expect((validator.mock.calls[3] as unknown[])[0]).toEqual( - 'abcdef' + expect(validator).toHaveBeenNthCalledWith( + 4, + 'abcdef', + expect.anything() ) expect( screen.getByText('I think this is wrong') @@ -935,8 +947,12 @@ describe('Field.String', () => { ) await waitFor(() => { // Wait for since external validators are processed asynchronously - expect(validator.mock.calls).toHaveLength(1) - expect((validator.mock.calls[0] as unknown[])[0]).toEqual('abc') + expect(validator).toHaveBeenCalledTimes(1) + expect(validator).toHaveBeenNthCalledWith( + 1, + 'abc', + expect.anything() + ) expect( screen.getByText('Whats left when nothing is right?') ).toBeInTheDocument() @@ -949,10 +965,22 @@ describe('Field.String', () => { fireEvent.blur(input) }) - expect(validator.mock.calls).toHaveLength(4) - expect((validator.mock.calls[1] as unknown[])[0]).toEqual('abcd') - expect((validator.mock.calls[2] as unknown[])[0]).toEqual('abcde') - expect((validator.mock.calls[3] as unknown[])[0]).toEqual('abcdef') + expect(validator).toHaveBeenCalledTimes(4) + expect(validator).toHaveBeenNthCalledWith( + 2, + 'abcd', + expect.anything() + ) + expect(validator).toHaveBeenNthCalledWith( + 3, + 'abcde', + expect.anything() + ) + expect(validator).toHaveBeenNthCalledWith( + 4, + 'abcdef', + expect.anything() + ) expect( screen.getByText('Whats left when nothing is right?') ).toBeInTheDocument() @@ -988,8 +1016,8 @@ describe('Field.String', () => { await waitFor(() => { // Wait for since external validators are processed asynchronously - expect(validator.mock.calls).toHaveLength(0) - expect(screen.queryByRole('alert')).not.toBeInTheDocument() + expect(validator).toHaveBeenCalledTimes(1) + expect(screen.queryByRole('alert')).toBeInTheDocument() }) const input = document.querySelector('input') await userEvent.type(input, 'def') @@ -997,9 +1025,16 @@ describe('Field.String', () => { await waitFor(() => { // Wait for since external validators are processed asynchronously - expect(validator.mock.calls).toHaveLength(1) - expect((validator.mock.calls[0] as unknown[])[0]).toEqual( - 'abcdef' + expect(validator).toHaveBeenCalledTimes(2) + expect(validator).toHaveBeenNthCalledWith( + 1, + 'abc', + expect.anything() + ) + expect(validator).toHaveBeenNthCalledWith( + 2, + 'abcdef', + expect.anything() ) expect( @@ -1040,8 +1075,8 @@ describe('Field.String', () => { await waitFor(() => { // Wait for since external validators are processed asynchronously - expect(validator.mock.calls).toHaveLength(0) - expect(screen.queryByRole('alert')).not.toBeInTheDocument() + expect(validator).toHaveBeenCalledTimes(1) + expect(screen.queryByRole('alert')).toBeInTheDocument() }) const input = document.querySelector('input') await userEvent.type(input, 'def') @@ -1049,9 +1084,16 @@ describe('Field.String', () => { await waitFor(() => { // Wait for since external validators are processed asynchronously - expect(validator.mock.calls).toHaveLength(1) - expect((validator.mock.calls[0] as unknown[])[0]).toEqual( - 'abcdef' + expect(validator).toHaveBeenCalledTimes(2) + expect(validator).toHaveBeenNthCalledWith( + 1, + 'abc', + expect.anything() + ) + expect(validator).toHaveBeenNthCalledWith( + 2, + 'abcdef', + expect.anything() ) expect( @@ -1153,29 +1195,36 @@ describe('Field.String', () => { await userEvent.type(input, 'O!') await waitFor(() => { - expect(inputOnChange.mock.calls).toHaveLength(2) - expect(inputOnChange.mock.calls[0][0]).toEqual('FOOO') - expect(inputOnChange.mock.calls[1][0]).toEqual('FOOO!') - - expect(dataContextOnChange.mock.calls).toHaveLength(2) - expect(dataContextOnChange.mock.calls[0][0]).toEqual({ - foo: 'FOOO', - bar: 'BAAAR', - }) - expect(dataContextOnChange.mock.calls[1][0]).toEqual({ - foo: 'FOOO!', - bar: 'BAAAR', - }) + expect(inputOnChange).toHaveBeenNthCalledWith(1, 'FOOO') + expect(inputOnChange).toHaveBeenNthCalledWith(2, 'FOOO!') - expect(dataContextOnPathChange.mock.calls).toHaveLength(2) - expect(dataContextOnPathChange.mock.calls[0]).toEqual([ + expect(dataContextOnChange).toHaveBeenNthCalledWith( + 1, + { + foo: 'FOOO', + bar: 'BAAAR', + }, + expect.anything() + ) + expect(dataContextOnChange).toHaveBeenNthCalledWith( + 2, + { + foo: 'FOOO!', + bar: 'BAAAR', + }, + expect.anything() + ) + + expect(dataContextOnPathChange).toHaveBeenNthCalledWith( + 1, '/foo', - 'FOOO', - ]) - expect(dataContextOnPathChange.mock.calls[1]).toEqual([ + 'FOOO' + ) + expect(dataContextOnPathChange).toHaveBeenNthCalledWith( + 2, '/foo', - 'FOOO!', - ]) + 'FOOO!' + ) }) }) }) 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 e0a1e95e030..fafba2acdf5 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 @@ -2968,6 +2968,46 @@ describe('useFieldProps', () => { }) describe('validator', () => { + describe('validateInitially', () => { + it('should show error message initially', async () => { + const validator = jest.fn(() => { + return new Error('My Error') + }) + + render( + + + + ) + + await waitFor(() => { + expect(screen.queryByRole('alert')).toBeInTheDocument() + }) + expect(validator).toHaveBeenCalledTimes(1) + }) + + it('should show error message initially when validator is async', async () => { + const validator = jest.fn(async () => { + return new Error('My Error') + }) + + render( + + + + ) + + await waitFor(() => { + expect(screen.queryByRole('alert')).toBeInTheDocument() + }) + expect(validator).toHaveBeenCalledTimes(1) + }) + }) + describe('connectWithPath', () => { const validatorFn: UseFieldProps['validator'] = ( num, @@ -3773,6 +3813,36 @@ describe('useFieldProps', () => { 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 call internal validates when they are not returned in the publicValidator', async () => { let internalValidators, barValidator, bazValidator @@ -3866,6 +3936,49 @@ describe('useFieldProps', () => { }) 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 validator is async', async () => { + const onBlurValidator = jest.fn(async () => { + return new Error('My Error') + }) + + render( + + + + ) + + await waitFor(() => { + expect(screen.queryByRole('alert')).toBeInTheDocument() + }) + expect(onBlurValidator).toHaveBeenCalledTimes(1) + }) + }) + describe('connectWithPath', () => { const validatorFn: UseFieldProps['validator'] = ( num, @@ -4262,6 +4375,35 @@ describe('useFieldProps', () => { 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() + }) + }) }) describe('setMountedFieldState', () => { diff --git a/packages/dnb-eufemia/src/extensions/forms/hooks/useFieldProps.ts b/packages/dnb-eufemia/src/extensions/forms/hooks/useFieldProps.ts index c33d0155924..a382858069b 100644 --- a/packages/dnb-eufemia/src/extensions/forms/hooks/useFieldProps.ts +++ b/packages/dnb-eufemia/src/extensions/forms/hooks/useFieldProps.ts @@ -1027,6 +1027,19 @@ export default function useFieldProps( } } + // Only for when "validateInitially" is set to true + if ( + onBlurValidatorRef.current && + validateInitially && + !changedRef.current + ) { + const { result } = await callOnBlurValidator() + + if (result instanceof Error) { + throw result + } + } + if (isProcessActive()) { clearErrorState() } @@ -1051,6 +1064,7 @@ export default function useFieldProps( validateInitially, validateUnchanged, startOnChangeValidatorValidation, + callOnBlurValidator, persistErrorState, ])