mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-14 20:19:34 +01:00
fix(Form)!: include nested state in submit data (#3028)
This commit is contained in:
@@ -195,7 +195,7 @@ This will give you access to the following:
|
||||
| Name | Type |
|
||||
| ---- | ---- |
|
||||
| `submit()`{lang="ts-type"} | `Promise<void>`{lang="ts-type"} <br> <div class="text-[var(--ui-text-toned)] mt-1"><p>Triggers form submission.</p> |
|
||||
| `validate(path?: string \| string[], opts: { silent?: boolean })`{lang="ts-type"} | `Promise<T>`{lang="ts-type"} <br> <div class="text-[var(--ui-text-toned)] mt-1"><p>Triggers form validation. Will raise any errors unless `opts.silent` is set to true.</p> |
|
||||
| `validate(opts: { name?: string \| string[], silent?: boolean, nested?: boolean, transform?: boolean })`{lang="ts-type"} | `Promise<T>`{lang="ts-type"} <br> <div class="text-[var(--ui-text-toned)] mt-1"><p>Triggers form validation. Will raise any errors unless `opts.silent` is set to true.</p> |
|
||||
| `clear(path?: string)`{lang="ts-type"} | `void` <br> <div class="text-[var(--ui-text-toned)] mt-1"><p>Clears form errors associated with a specific path. If no path is provided, clears all form errors.</p> |
|
||||
| `getErrors(path?: string)`{lang="ts-type"} | `FormError[]`{lang="ts-type"} <br> <div class="text-[var(--ui-text-toned)] mt-1"><p>Retrieves form errors associated with a specific path. If no path is provided, returns all form errors.</p></div> |
|
||||
| `setErrors(errors: FormError[], path?: string)`{lang="ts-type"} | `void` <br> <div class="text-[var(--ui-text-toned)] mt-1"><p>Sets form errors for a given path. If no path is provided, overrides all errors.</p> |
|
||||
|
||||
@@ -60,7 +60,7 @@ const parentBus = inject(
|
||||
|
||||
provide(formBusInjectionKey, bus)
|
||||
|
||||
const nestedForms = ref<Map<string | number, { validate: () => any }>>(new Map())
|
||||
const nestedForms = ref<Map<string | number, { validate: typeof _validate }>>(new Map())
|
||||
|
||||
onMounted(async () => {
|
||||
bus.on(async (event) => {
|
||||
@@ -121,12 +121,12 @@ async function getErrors(): Promise<FormErrorWithId[]> {
|
||||
return resolveErrorIds(errs)
|
||||
}
|
||||
|
||||
async function _validate(opts: { name?: string | string[], silent?: boolean, nested?: boolean } = { silent: false, nested: true }): Promise<T | false> {
|
||||
async function _validate(opts: { name?: string | string[], silent?: boolean, nested?: boolean, transform?: boolean } = { silent: false, nested: true, transform: false }): Promise<T | false> {
|
||||
const names = opts.name && !Array.isArray(opts.name) ? [opts.name] : opts.name as string[]
|
||||
|
||||
const nestedValidatePromises = !names && opts.nested
|
||||
? Array.from(nestedForms.value.values()).map(
|
||||
({ validate }) => validate().then(() => undefined).catch((error: Error) => {
|
||||
({ validate }) => validate(opts).then(() => undefined).catch((error: Error) => {
|
||||
if (!(error instanceof FormValidationException)) {
|
||||
throw error
|
||||
}
|
||||
@@ -151,13 +151,17 @@ async function _validate(opts: { name?: string | string[], silent?: boolean, nes
|
||||
errors.value = await getErrors()
|
||||
}
|
||||
|
||||
const childErrors = (await Promise.all(nestedValidatePromises)).filter(val => val)
|
||||
const childErrors = (await Promise.all(nestedValidatePromises)).filter(val => val !== undefined)
|
||||
|
||||
if (errors.value.length + childErrors.length > 0) {
|
||||
if (opts.silent) return false
|
||||
throw new FormValidationException(formId, errors.value, childErrors)
|
||||
}
|
||||
|
||||
if (opts.transform) {
|
||||
Object.assign(props.state, transformedState.value)
|
||||
}
|
||||
|
||||
return props.state as T
|
||||
}
|
||||
|
||||
@@ -170,8 +174,7 @@ async function onSubmitWrapper(payload: Event) {
|
||||
const event = payload as FormSubmitEvent<any>
|
||||
|
||||
try {
|
||||
await _validate({ nested: true })
|
||||
event.data = props.schema ? transformedState.value : props.state
|
||||
event.data = await _validate({ nested: true, transform: true })
|
||||
await props.onSubmit?.(event)
|
||||
} catch (error) {
|
||||
if (!(error instanceof FormValidationException)) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import type { GetObjectField } from './utils'
|
||||
import type { Struct as SuperstructSchema } from 'superstruct'
|
||||
|
||||
export interface Form<T> {
|
||||
validate (opts?: { name: string | string[], silent?: false, nested?: boolean }): Promise<T | false>
|
||||
validate (opts?: { name?: string | string[], silent?: boolean, nested?: boolean, transform?: boolean }): Promise<T | false>
|
||||
clear (path?: string): void
|
||||
errors: Ref<FormError[]>
|
||||
setErrors (errs: FormError[], path?: string): void
|
||||
@@ -95,7 +95,7 @@ export class FormValidationException extends Error {
|
||||
errors: FormErrorWithId[]
|
||||
children?: FormValidationException[]
|
||||
|
||||
constructor(formId: string | number, errors: FormErrorWithId[], childErrors: FormValidationException[]) {
|
||||
constructor(formId: string | number, errors: FormErrorWithId[], childErrors?: FormValidationException[]) {
|
||||
super('Form validation exception')
|
||||
this.formId = formId
|
||||
this.errors = errors
|
||||
|
||||
@@ -347,6 +347,15 @@ describe('Form', () => {
|
||||
expect(nestedField.text()).toBe('Required')
|
||||
})
|
||||
|
||||
test('submit event contains nested attributes', async () => {
|
||||
state.email = 'bob@dylan.com'
|
||||
state.password = 'strongpassword'
|
||||
state.nested.field = 'nested'
|
||||
|
||||
await form.value.submit()
|
||||
expect(wrapper.setupState.onSubmit).toHaveBeenCalledWith(expect.objectContaining({ data: { email: 'bob@dylan.com', password: 'strongpassword', nested: { field: 'nested' } } }))
|
||||
})
|
||||
|
||||
test('submit works when child is disabled', async () => {
|
||||
await form.value.submit()
|
||||
expect(wrapper.setupState.onError).toHaveBeenCalledTimes(1)
|
||||
|
||||
Reference in New Issue
Block a user