mirror of
https://github.com/LukeHagar/form.git
synced 2025-12-06 04:19:43 +00:00
fix: Fix minor FormApi bugs and add initial form API tests (#404)
* test(form-core): add initial form api tests * fix: behavior of FormApi.update works more reliably * chore: migrate from spread operator to object assign * fix: reset form should handle option values as expected now * fix(form-core): pushFieldValue and insertFieldValue should now have the correct typings * fix(form-core): swapFieldValues should not throw an error anymore
This commit is contained in:
@@ -164,26 +164,43 @@ export class FormApi<TFormData> {
|
||||
if (!options) return
|
||||
|
||||
this.store.batch(() => {
|
||||
if (
|
||||
options.defaultState &&
|
||||
const shouldUpdateValues =
|
||||
options.defaultValues &&
|
||||
options.defaultValues !== this.options.defaultValues
|
||||
|
||||
const shouldUpdateState =
|
||||
options.defaultState !== this.options.defaultState
|
||||
) {
|
||||
this.store.setState((prev) => ({
|
||||
...prev,
|
||||
...options.defaultState,
|
||||
}))
|
||||
|
||||
if (!shouldUpdateValues || !shouldUpdateValues) {
|
||||
return
|
||||
}
|
||||
|
||||
if (options.defaultValues !== this.options.defaultValues) {
|
||||
this.store.setState(() => getDefaultFormState(options.defaultValues!))
|
||||
}
|
||||
this.store.setState(() =>
|
||||
getDefaultFormState(
|
||||
Object.assign(
|
||||
{},
|
||||
shouldUpdateState ? options.defaultState : {},
|
||||
shouldUpdateValues
|
||||
? {
|
||||
values: options.defaultValues,
|
||||
}
|
||||
: {},
|
||||
),
|
||||
),
|
||||
)
|
||||
})
|
||||
|
||||
this.options = options
|
||||
}
|
||||
|
||||
reset = () =>
|
||||
this.store.setState(() => getDefaultFormState(this.options.defaultValues!))
|
||||
this.store.setState(() =>
|
||||
getDefaultFormState({
|
||||
...this.options?.defaultState,
|
||||
values:
|
||||
this.options?.defaultValues ?? this.options?.defaultState?.values,
|
||||
}),
|
||||
)
|
||||
|
||||
validateAllFields = async (cause: ValidationCause) => {
|
||||
const fieldValidationPromises: Promise<ValidationError>[] = [] as any
|
||||
@@ -327,7 +344,7 @@ export class FormApi<TFormData> {
|
||||
|
||||
pushFieldValue = <TField extends DeepKeys<TFormData>>(
|
||||
field: TField,
|
||||
value: DeepValue<TFormData, TField>,
|
||||
value: DeepValue<TFormData, TField>[number],
|
||||
opts?: { touch?: boolean },
|
||||
) => {
|
||||
return this.setFieldValue(
|
||||
@@ -340,7 +357,7 @@ export class FormApi<TFormData> {
|
||||
insertFieldValue = <TField extends DeepKeys<TFormData>>(
|
||||
field: TField,
|
||||
index: number,
|
||||
value: DeepValue<TFormData, TField>,
|
||||
value: DeepValue<TFormData, TField>[number],
|
||||
opts?: { touch?: boolean },
|
||||
) => {
|
||||
this.setFieldValue(
|
||||
@@ -378,7 +395,7 @@ export class FormApi<TFormData> {
|
||||
this.setFieldValue(field, (prev: any) => {
|
||||
const prev1 = prev[index1]!
|
||||
const prev2 = prev[index2]!
|
||||
return setBy(setBy(prev, [index1], prev2), [index2], prev1)
|
||||
return setBy(setBy(prev, `${index1}`, prev2), `${index2}`, prev1)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
216
packages/form-core/src/tests/FormApi.spec.tsx
Normal file
216
packages/form-core/src/tests/FormApi.spec.tsx
Normal file
@@ -0,0 +1,216 @@
|
||||
import { expect } from 'vitest'
|
||||
|
||||
import { FormApi } from '../FormApi'
|
||||
|
||||
describe('form api', () => {
|
||||
it('should get default form state', () => {
|
||||
const form = new FormApi()
|
||||
|
||||
expect(form.state).toEqual({
|
||||
values: undefined,
|
||||
fieldMeta: {},
|
||||
canSubmit: true,
|
||||
isFieldsValid: true,
|
||||
isFieldsValidating: false,
|
||||
isFormValid: true,
|
||||
isFormValidating: false,
|
||||
isSubmitted: false,
|
||||
isSubmitting: false,
|
||||
isTouched: false,
|
||||
isValid: true,
|
||||
isValidating: false,
|
||||
submissionAttempts: 0,
|
||||
formValidationCount: 0,
|
||||
})
|
||||
})
|
||||
|
||||
it('should get default form state when default values are passed', () => {
|
||||
const form = new FormApi({
|
||||
defaultValues: {
|
||||
name: 'test',
|
||||
},
|
||||
})
|
||||
|
||||
expect(form.state).toEqual({
|
||||
values: {
|
||||
name: 'test',
|
||||
},
|
||||
fieldMeta: {},
|
||||
canSubmit: true,
|
||||
isFieldsValid: true,
|
||||
isFieldsValidating: false,
|
||||
isFormValid: true,
|
||||
isFormValidating: false,
|
||||
isSubmitted: false,
|
||||
isSubmitting: false,
|
||||
isTouched: false,
|
||||
isValid: true,
|
||||
isValidating: false,
|
||||
submissionAttempts: 0,
|
||||
formValidationCount: 0,
|
||||
})
|
||||
})
|
||||
|
||||
it('should get default form state when default state is passed', () => {
|
||||
const form = new FormApi({
|
||||
defaultState: {
|
||||
submissionAttempts: 30,
|
||||
},
|
||||
})
|
||||
|
||||
expect(form.state).toEqual({
|
||||
values: undefined,
|
||||
fieldMeta: {},
|
||||
canSubmit: true,
|
||||
isFieldsValid: true,
|
||||
isFieldsValidating: false,
|
||||
isFormValid: true,
|
||||
isFormValidating: false,
|
||||
isSubmitted: false,
|
||||
isSubmitting: false,
|
||||
isTouched: false,
|
||||
isValid: true,
|
||||
isValidating: false,
|
||||
submissionAttempts: 30,
|
||||
formValidationCount: 0,
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle updating form state', () => {
|
||||
const form = new FormApi({
|
||||
defaultValues: {
|
||||
name: 'test',
|
||||
},
|
||||
})
|
||||
|
||||
form.update({
|
||||
defaultValues: {
|
||||
name: 'other',
|
||||
},
|
||||
defaultState: {
|
||||
submissionAttempts: 300,
|
||||
},
|
||||
})
|
||||
|
||||
expect(form.state).toEqual({
|
||||
values: {
|
||||
name: 'other',
|
||||
},
|
||||
fieldMeta: {},
|
||||
canSubmit: true,
|
||||
isFieldsValid: true,
|
||||
isFieldsValidating: false,
|
||||
isFormValid: true,
|
||||
isFormValidating: false,
|
||||
isSubmitted: false,
|
||||
isSubmitting: false,
|
||||
isTouched: false,
|
||||
isValid: true,
|
||||
isValidating: false,
|
||||
submissionAttempts: 300,
|
||||
formValidationCount: 0,
|
||||
})
|
||||
})
|
||||
|
||||
it('should reset the form state properly', () => {
|
||||
const form = new FormApi({
|
||||
defaultValues: {
|
||||
name: 'test',
|
||||
},
|
||||
})
|
||||
|
||||
form.pushFieldValue('name', 'other')
|
||||
form.state.submissionAttempts = 300
|
||||
|
||||
form.reset()
|
||||
|
||||
expect(form.state).toEqual({
|
||||
values: {
|
||||
name: 'test',
|
||||
},
|
||||
fieldMeta: {},
|
||||
canSubmit: true,
|
||||
isFieldsValid: true,
|
||||
isFieldsValidating: false,
|
||||
isFormValid: true,
|
||||
isFormValidating: false,
|
||||
isSubmitted: false,
|
||||
isSubmitting: false,
|
||||
isTouched: false,
|
||||
isValid: true,
|
||||
isValidating: false,
|
||||
submissionAttempts: 0,
|
||||
formValidationCount: 0,
|
||||
})
|
||||
})
|
||||
|
||||
it("should get a field's value", () => {
|
||||
const form = new FormApi({
|
||||
defaultValues: {
|
||||
name: 'test',
|
||||
},
|
||||
})
|
||||
|
||||
expect(form.getFieldValue('name')).toEqual('test')
|
||||
})
|
||||
|
||||
it("should set a field's value", () => {
|
||||
const form = new FormApi({
|
||||
defaultValues: {
|
||||
name: 'test',
|
||||
},
|
||||
})
|
||||
|
||||
form.setFieldValue('name', 'other')
|
||||
|
||||
expect(form.getFieldValue('name')).toEqual('other')
|
||||
})
|
||||
|
||||
it("should push an array field's value", () => {
|
||||
const form = new FormApi({
|
||||
defaultValues: {
|
||||
names: ['test'],
|
||||
},
|
||||
})
|
||||
|
||||
form.pushFieldValue('names', 'other')
|
||||
|
||||
expect(form.getFieldValue('names')).toStrictEqual(['test', 'other'])
|
||||
})
|
||||
|
||||
it("should insert an array field's value", () => {
|
||||
const form = new FormApi({
|
||||
defaultValues: {
|
||||
names: ['one', 'two', 'three'],
|
||||
},
|
||||
})
|
||||
|
||||
form.insertFieldValue('names', 1, 'other')
|
||||
|
||||
expect(form.getFieldValue('names')).toStrictEqual(['one', 'other', 'three'])
|
||||
})
|
||||
|
||||
it("should remove an array field's value", () => {
|
||||
const form = new FormApi({
|
||||
defaultValues: {
|
||||
names: ['one', 'two', 'three'],
|
||||
},
|
||||
})
|
||||
|
||||
form.removeFieldValue('names', 1)
|
||||
|
||||
expect(form.getFieldValue('names')).toStrictEqual(['one', 'three'])
|
||||
})
|
||||
|
||||
it("should swap an array field's value", () => {
|
||||
const form = new FormApi({
|
||||
defaultValues: {
|
||||
names: ['one', 'two', 'three'],
|
||||
},
|
||||
})
|
||||
|
||||
form.swapFieldValues('names', 1, 2)
|
||||
|
||||
expect(form.getFieldValue('names')).toStrictEqual(['one', 'three', 'two'])
|
||||
})
|
||||
})
|
||||
@@ -1,5 +0,0 @@
|
||||
describe('tests', () => {
|
||||
it('should test', () => {
|
||||
expect(true).toBe(true)
|
||||
})
|
||||
})
|
||||
@@ -13,6 +13,9 @@ export function functionalUpdate<TInput, TOutput = TInput>(
|
||||
: updater
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a value from an object using a path, including dot notation.
|
||||
*/
|
||||
export function getBy(obj: any, path: any) {
|
||||
const pathArray = makePathArray(path)
|
||||
const pathObj = pathArray
|
||||
@@ -24,6 +27,9 @@ export function getBy(obj: any, path: any) {
|
||||
}, obj)
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a value on an object using a path, including dot notation.
|
||||
*/
|
||||
export function setBy(obj: any, _path: any, updater: Updater<any>) {
|
||||
const path = makePathArray(_path)
|
||||
|
||||
@@ -75,7 +81,7 @@ const intReplace = `${intPrefix}$1`
|
||||
|
||||
function makePathArray(str: string) {
|
||||
if (typeof str !== 'string') {
|
||||
throw new Error()
|
||||
throw new Error('Path must be a string.')
|
||||
}
|
||||
|
||||
return str
|
||||
|
||||
Reference in New Issue
Block a user