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:
Corbin Crutchley
2023-08-27 20:51:20 -07:00
committed by GitHub
parent fdf57588dd
commit 25237e40d4
4 changed files with 254 additions and 20 deletions

View File

@@ -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)
})
}
}

View 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'])
})
})

View File

@@ -1,5 +0,0 @@
describe('tests', () => {
it('should test', () => {
expect(true).toBe(true)
})
})

View File

@@ -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