mirror of
https://github.com/LukeHagar/form.git
synced 2025-12-09 20:37:47 +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
|
if (!options) return
|
||||||
|
|
||||||
this.store.batch(() => {
|
this.store.batch(() => {
|
||||||
if (
|
const shouldUpdateValues =
|
||||||
options.defaultState &&
|
options.defaultValues &&
|
||||||
|
options.defaultValues !== this.options.defaultValues
|
||||||
|
|
||||||
|
const shouldUpdateState =
|
||||||
options.defaultState !== this.options.defaultState
|
options.defaultState !== this.options.defaultState
|
||||||
) {
|
|
||||||
this.store.setState((prev) => ({
|
if (!shouldUpdateValues || !shouldUpdateValues) {
|
||||||
...prev,
|
return
|
||||||
...options.defaultState,
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.defaultValues !== this.options.defaultValues) {
|
this.store.setState(() =>
|
||||||
this.store.setState(() => getDefaultFormState(options.defaultValues!))
|
getDefaultFormState(
|
||||||
}
|
Object.assign(
|
||||||
|
{},
|
||||||
|
shouldUpdateState ? options.defaultState : {},
|
||||||
|
shouldUpdateValues
|
||||||
|
? {
|
||||||
|
values: options.defaultValues,
|
||||||
|
}
|
||||||
|
: {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
this.options = options
|
this.options = options
|
||||||
}
|
}
|
||||||
|
|
||||||
reset = () =>
|
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) => {
|
validateAllFields = async (cause: ValidationCause) => {
|
||||||
const fieldValidationPromises: Promise<ValidationError>[] = [] as any
|
const fieldValidationPromises: Promise<ValidationError>[] = [] as any
|
||||||
@@ -327,7 +344,7 @@ export class FormApi<TFormData> {
|
|||||||
|
|
||||||
pushFieldValue = <TField extends DeepKeys<TFormData>>(
|
pushFieldValue = <TField extends DeepKeys<TFormData>>(
|
||||||
field: TField,
|
field: TField,
|
||||||
value: DeepValue<TFormData, TField>,
|
value: DeepValue<TFormData, TField>[number],
|
||||||
opts?: { touch?: boolean },
|
opts?: { touch?: boolean },
|
||||||
) => {
|
) => {
|
||||||
return this.setFieldValue(
|
return this.setFieldValue(
|
||||||
@@ -340,7 +357,7 @@ export class FormApi<TFormData> {
|
|||||||
insertFieldValue = <TField extends DeepKeys<TFormData>>(
|
insertFieldValue = <TField extends DeepKeys<TFormData>>(
|
||||||
field: TField,
|
field: TField,
|
||||||
index: number,
|
index: number,
|
||||||
value: DeepValue<TFormData, TField>,
|
value: DeepValue<TFormData, TField>[number],
|
||||||
opts?: { touch?: boolean },
|
opts?: { touch?: boolean },
|
||||||
) => {
|
) => {
|
||||||
this.setFieldValue(
|
this.setFieldValue(
|
||||||
@@ -378,7 +395,7 @@ export class FormApi<TFormData> {
|
|||||||
this.setFieldValue(field, (prev: any) => {
|
this.setFieldValue(field, (prev: any) => {
|
||||||
const prev1 = prev[index1]!
|
const prev1 = prev[index1]!
|
||||||
const prev2 = prev[index2]!
|
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
|
: updater
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a value from an object using a path, including dot notation.
|
||||||
|
*/
|
||||||
export function getBy(obj: any, path: any) {
|
export function getBy(obj: any, path: any) {
|
||||||
const pathArray = makePathArray(path)
|
const pathArray = makePathArray(path)
|
||||||
const pathObj = pathArray
|
const pathObj = pathArray
|
||||||
@@ -24,6 +27,9 @@ export function getBy(obj: any, path: any) {
|
|||||||
}, obj)
|
}, obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a value on an object using a path, including dot notation.
|
||||||
|
*/
|
||||||
export function setBy(obj: any, _path: any, updater: Updater<any>) {
|
export function setBy(obj: any, _path: any, updater: Updater<any>) {
|
||||||
const path = makePathArray(_path)
|
const path = makePathArray(_path)
|
||||||
|
|
||||||
@@ -75,7 +81,7 @@ const intReplace = `${intPrefix}$1`
|
|||||||
|
|
||||||
function makePathArray(str: string) {
|
function makePathArray(str: string) {
|
||||||
if (typeof str !== 'string') {
|
if (typeof str !== 'string') {
|
||||||
throw new Error()
|
throw new Error('Path must be a string.')
|
||||||
}
|
}
|
||||||
|
|
||||||
return str
|
return str
|
||||||
|
|||||||
Reference in New Issue
Block a user