From a0ec46d28fc958e6c8b1ea478b46189868e31417 Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Wed, 14 Aug 2024 00:40:56 +0300 Subject: [PATCH] fix: combine field and form level errors --- packages/core/src/useForm/useForm.spec.ts | 49 ++++++++++++++++++- packages/core/src/useForm/useFormActions.ts | 2 +- packages/core/src/useTextField/index.ts | 8 ++- .../core/src/validation/useInputValidity.ts | 6 +-- packages/playground/src/App.vue | 3 +- 5 files changed, 57 insertions(+), 11 deletions(-) diff --git a/packages/core/src/useForm/useForm.spec.ts b/packages/core/src/useForm/useForm.spec.ts index f8807624..f86a355f 100644 --- a/packages/core/src/useForm/useForm.spec.ts +++ b/packages/core/src/useForm/useForm.spec.ts @@ -417,7 +417,8 @@ describe('form validation', () => { inheritAttrs: false, setup: (_, { attrs }) => { const name = (attrs.name || 'test') as string; - const { errorMessage, inputProps } = useTextField({ name, label: name }); + const schema = attrs.schema as TypedSchema; + const { errorMessage, inputProps } = useTextField({ name, label: name, schema }); return { errorMessage: errorMessage, inputProps, name }; }, @@ -633,6 +634,52 @@ describe('form validation', () => { expect(values).toEqual({ test: 'foo' }); }); + + test('combines errors from field-level schemas', async () => { + const handler = vi.fn(); + const schema: TypedSchema = { + async parse() { + return { + errors: [{ path: 'test', messages: ['error'] }], + }; + }, + }; + + const fieldSchema: TypedSchema = { + async parse() { + return { + errors: [{ path: 'field', messages: ['field error'] }], + }; + }, + }; + + await render({ + components: { Child: createInputComponent() }, + setup() { + const { handleSubmit, getError } = useForm({ + schema, + }); + + return { getError, onSubmit: handleSubmit(handler), fieldSchema }; + }, + template: ` +
+ + + {{ getError('test') }} + {{ getError('field') }} + + + + `, + }); + + await fireEvent.click(screen.getByText('Submit')); + await flush(); + expect(screen.getByTestId('form-err').textContent).toBe('error'); + expect(screen.getByTestId('field-err').textContent).toBe('field error'); + expect(handler).not.toHaveBeenCalled(); + }); }); test('form reset clears errors', async () => { diff --git a/packages/core/src/useForm/useFormActions.ts b/packages/core/src/useForm/useFormActions.ts index 349fbdd4..daba9da0 100644 --- a/packages/core/src/useForm/useFormActions.ts +++ b/packages/core/src/useForm/useFormActions.ts @@ -100,7 +100,7 @@ export function useFormActions; } export function useTextField( - _props: Reactivify, + _props: Reactivify, elementRef?: Ref, ) { - const props = normalizeProps(_props); + const props = normalizeProps(_props, ['schema']); const inputId = useUniqId(FieldTypePrefixes.TextField); const inputRef = elementRef || shallowRef(); const field = useFormField({ path: props.name, initialValue: toValue(props.modelValue), disabled: props.disabled, + schema: props.schema, }); const { validityDetails } = useInputValidity({ inputRef, field }); diff --git a/packages/core/src/validation/useInputValidity.ts b/packages/core/src/validation/useInputValidity.ts index b8928d5b..21c54e35 100644 --- a/packages/core/src/validation/useInputValidity.ts +++ b/packages/core/src/validation/useInputValidity.ts @@ -32,12 +32,8 @@ export function useInputValidity(opts: InputValidityOptions) { } function _updateValidity() { - if (schema) { - return validateField(true); - } - if (validationMode === 'native') { - return validateNative(true); + return schema ? validateField(true) : validateNative(true); } form?.requestValidation(); diff --git a/packages/playground/src/App.vue b/packages/playground/src/App.vue index fd606c00..4b47db6a 100644 --- a/packages/playground/src/App.vue +++ b/packages/playground/src/App.vue @@ -9,7 +9,7 @@ --> - + @@ -31,7 +31,6 @@ import { z } from 'zod'; const { getErrors, values, handleSubmit } = useForm({ schema: defineSchema( z.object({ - email: z.string().email(), other: z.string().min(3), }), ),