From 8d11e6d20007b6a3c8782be95a8ec10d2e67dbda Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Sat, 16 Nov 2024 01:10:58 +0200 Subject: [PATCH 1/2] feat: added toObject and make toJSON produce valid JSON output --- .changeset/silent-zebras-change.md | 5 + packages/core/src/useForm/useForm.spec.ts | 188 +++++++++++++++++- packages/core/src/useForm/useForm.ts | 2 +- packages/core/src/useForm/useFormActions.ts | 13 +- .../src/useFormGroup/useFormGroup.spec.ts | 2 +- .../ecosystem/src/arktype/arktype.spec.ts | 2 +- .../ecosystem/src/valibot/valibot.spec.ts | 2 +- 7 files changed, 199 insertions(+), 15 deletions(-) create mode 100644 .changeset/silent-zebras-change.md diff --git a/.changeset/silent-zebras-change.md b/.changeset/silent-zebras-change.md new file mode 100644 index 00000000..7dfea579 --- /dev/null +++ b/.changeset/silent-zebras-change.md @@ -0,0 +1,5 @@ +--- +'@formwerk/core': minor +--- + +feat: added toObject and make toJSON produce valid JSON output diff --git a/packages/core/src/useForm/useForm.spec.ts b/packages/core/src/useForm/useForm.spec.ts index 2e4ad9fc..882677f0 100644 --- a/packages/core/src/useForm/useForm.spec.ts +++ b/packages/core/src/useForm/useForm.spec.ts @@ -180,7 +180,7 @@ describe('form submit', () => { }); const cb = vi.fn(); - const onSubmit = handleSubmit(v => cb(v.toJSON())); + const onSubmit = handleSubmit(v => cb(v.toObject())); await onSubmit(new Event('submit')); @@ -244,7 +244,7 @@ describe('form submit', () => { ); const cb = vi.fn(); - const onSubmit = handleSubmit(v => cb(v.toJSON())); + const onSubmit = handleSubmit(v => cb(v.toObject())); expect(values).toEqual(defaults()); await onSubmit(new Event('submit')); expect(cb).toHaveBeenLastCalledWith(defaults()); @@ -255,6 +255,178 @@ describe('form submit', () => { expect(cb).toHaveBeenLastCalledWith({ multiple: ['field 1', 'field 3', 'field 4'] }); }); + test('can submit with object data', async () => { + const file1 = new File([''], 'test1.jpg', { type: 'image/jpeg' }); + const file2 = new File([''], 'test2.pdf', { type: 'application/pdf' }); + + const input = { + name: 'John Doe', + age: 30, + isStudent: false, + grades: { + math: 95, + science: 88, + }, + hobbies: ['reading', 'cycling', null], + address: { + street: '123 Main St', + city: 'New York', + zipCode: null, + }, + profilePic: file1, + documents: [ + file2, + { + resume: file1, + coverLetter: file2, + }, + ], + emptyObject: {}, + nullValue: null, + undefinedValue: undefined, + nestedArrays: [ + [1, 2], + [3, [4, 5]], + ], + } as const; + + const { form } = await renderSetup(() => { + return { form: useForm({ initialValues: input }) }; + }); + + let data!: typeof input; + const cb = vi.fn(v => (data = v)); + const onSubmit = form.handleSubmit(v => cb(v.toObject())); + await onSubmit(new Event('submit')); + + expect(data.name).toBe('John Doe'); + expect(data.age).toBe(30); + expect(data.isStudent).toBe(false); + + // Nested object + expect(data.grades.math).toBe(95); + expect(data.grades.science).toBe(88); + + // Array + expect(data.hobbies[0]).toBe('reading'); + expect(data.hobbies[1]).toBe('cycling'); + expect(data.hobbies[2]).toBe(null); + + // Nested object with null value + expect(data.address.street).toBe('123 Main St'); + expect(data.address.city).toBe('New York'); + expect(data.address.zipCode).toBe(null); + + // File object + expect(data.profilePic).toEqual(file1); + + // Array with mixed types + expect(data.documents[0]).toEqual(file2); + expect(data.documents[1].resume).toEqual(file1); + expect(data.documents[1].coverLetter).toEqual(file2); + + // Empty object (should not have an entry) + expect(data.emptyObject).toEqual({}); + + // Null and undefined values + expect(data.nullValue).toBe(null); + expect(data.undefinedValue).toBe(undefined); + expect(data).toHaveProperty('undefinedValue'); + + // Nested arrays + expect(data.nestedArrays[0][0]).toBe(1); + expect(data.nestedArrays[0][1]).toBe(2); + expect(data.nestedArrays[1][0]).toBe(3); + expect(data.nestedArrays[1][1][0]).toBe(4); + expect(data.nestedArrays[1][1][1]).toBe(5); + }); + + test('can submit with JSON data', async () => { + const file1 = new File([''], 'test1.jpg', { type: 'image/jpeg' }); + const file2 = new File([''], 'test2.pdf', { type: 'application/pdf' }); + + const input = { + name: 'John Doe', + age: 30, + isStudent: false, + grades: { + math: 95, + science: 88, + }, + hobbies: ['reading', 'cycling', null], + address: { + street: '123 Main St', + city: 'New York', + zipCode: null, + }, + profilePic: file1, + documents: [ + file2, + { + resume: file1, + coverLetter: file2, + }, + ], + emptyObject: {}, + nullValue: null, + undefinedValue: undefined, + nestedArrays: [ + [1, 2], + [3, [4, 5]], + ], + } as const; + + const { form } = await renderSetup(() => { + return { form: useForm({ initialValues: input }) }; + }); + + let data!: typeof input; + const cb = vi.fn(v => (data = v)); + const onSubmit = form.handleSubmit(v => cb(v.toJSON())); + await onSubmit(new Event('submit')); + + expect(data.name).toBe('John Doe'); + expect(data.age).toBe(30); + expect(data.isStudent).toBe(false); + + // Nested object + expect(data.grades.math).toBe(95); + expect(data.grades.science).toBe(88); + + // Array + expect(data.hobbies[0]).toBe('reading'); + expect(data.hobbies[1]).toBe('cycling'); + expect(data.hobbies[2]).toBe(null); + + // Nested object with null value + expect(data.address.street).toBe('123 Main St'); + expect(data.address.city).toBe('New York'); + expect(data.address.zipCode).toBe(null); + + // File object + expect(data.profilePic).toEqual({}); + + // Array with mixed types + expect(data.documents[0]).toEqual({}); + expect(data.documents[1].resume).toEqual({}); + expect(data.documents[1].coverLetter).toEqual({}); + + // Empty object (should not have an entry) + expect(data.emptyObject).toEqual({}); + + // Null and undefined values + expect(data.nullValue).toBe(null); + expect(data.undefinedValue).toBe(undefined); + expect(data).not.toHaveProperty('undefinedValue'); + + // Nested arrays + expect(data.nestedArrays[0][0]).toBe(1); + expect(data.nestedArrays[0][1]).toBe(2); + expect(data.nestedArrays[1][0]).toBe(3); + expect(data.nestedArrays[1][1][0]).toBe(4); + expect(data.nestedArrays[1][1][1]).toBe(5); + }); + test('can submit with FormData', async () => { const file1 = new File([''], 'test1.jpg', { type: 'image/jpeg' }); const file2 = new File([''], 'test2.pdf', { type: 'application/pdf' }); @@ -521,7 +693,7 @@ describe('form validation', () => { setup() { const { handleSubmit } = useForm(); - return { onSubmit: handleSubmit(v => handler(v.toJSON())) }; + return { onSubmit: handleSubmit(v => handler(v.toObject())) }; }, template: `
@@ -601,7 +773,7 @@ describe('form validation', () => { schema, }); - return { onSubmit: handleSubmit(v => handler(v.toJSON())) }; + return { onSubmit: handleSubmit(v => handler(v.toObject())) }; }, template: ` @@ -631,7 +803,7 @@ describe('form validation', () => { schema, }); - return { getError, onSubmit: handleSubmit(v => handler(v.toJSON())) }; + return { getError, onSubmit: handleSubmit(v => handler(v.toObject())) }; }, template: ` @@ -671,7 +843,7 @@ describe('form validation', () => { setFieldErrors('test', 'error'); - return { getError, onSubmit: handleSubmit(v => handler(v.toJSON())) }; + return { getError, onSubmit: handleSubmit(v => handler(v.toObject())) }; }, template: ` @@ -712,7 +884,7 @@ describe('form validation', () => { setFieldErrors('test', 'error'); - return { getError, onSubmit: handleSubmit(v => handler(v.toJSON())) }; + return { getError, onSubmit: handleSubmit(v => handler(v.toObject())) }; }, template: ` @@ -802,7 +974,7 @@ describe('form validation', () => { schema, }); - return { getError, onSubmit: handleSubmit(v => handler(v.toJSON())), fieldSchema }; + return { getError, onSubmit: handleSubmit(v => handler(v.toObject())), fieldSchema }; }, template: ` diff --git a/packages/core/src/useForm/useForm.ts b/packages/core/src/useForm/useForm.ts index 6f03cca5..c859c2e7 100644 --- a/packages/core/src/useForm/useForm.ts +++ b/packages/core/src/useForm/useForm.ts @@ -139,7 +139,7 @@ export function useForm< const onSubmit = actions.handleSubmit((output, { form }) => { if (form) { - form.__formOut = output.toJSON(); + form.__formOut = output.toObject(); form.submit(); } }); diff --git a/packages/core/src/useForm/useFormActions.ts b/packages/core/src/useForm/useFormActions.ts index e57a4d8b..3a822a23 100644 --- a/packages/core/src/useForm/useFormActions.ts +++ b/packages/core/src/useForm/useFormActions.ts @@ -14,6 +14,7 @@ import { BaseFormContext, SetValueOptions } from './formContext'; import { unsetPath } from '../utils/path'; import { useValidationProvider } from '../validation/useValidationProvider'; import { appendToFormData } from '../utils/formData'; +import type { Jsonify } from 'type-fest'; export interface ResetState { values: Partial; @@ -28,7 +29,8 @@ export interface FormActionsOptions = { toFormData: () => FormData; - toJSON: () => TOutput; + toObject: () => TOutput; + toJSON: () => Jsonify; }; export interface SubmitContext { @@ -141,7 +143,7 @@ export function useFormActions(data: TData): ConsumableData { - const toJSON = () => data; + const toObject = () => data; const toFormData = () => { const formData = new FormData(); appendToFormData(data, formData); @@ -149,8 +151,13 @@ function withConsumers(data: TData): ConsumableData { setup() { const { handleSubmit } = useForm({}); - const onSubmit = handleSubmit(v => submitHandler(v.toJSON())); + const onSubmit = handleSubmit(v => submitHandler(v.toObject())); return { onSubmit, diff --git a/packages/ecosystem/src/arktype/arktype.spec.ts b/packages/ecosystem/src/arktype/arktype.spec.ts index 1777a676..46321f72 100644 --- a/packages/ecosystem/src/arktype/arktype.spec.ts +++ b/packages/ecosystem/src/arktype/arktype.spec.ts @@ -26,7 +26,7 @@ test('Arktype schemas are supported', async () => { return { getError, onSubmit: handleSubmit(v => { - handler(v.toJSON()); + handler(v.toObject()); }), }; }, diff --git a/packages/ecosystem/src/valibot/valibot.spec.ts b/packages/ecosystem/src/valibot/valibot.spec.ts index 549c9d6a..06e82e70 100644 --- a/packages/ecosystem/src/valibot/valibot.spec.ts +++ b/packages/ecosystem/src/valibot/valibot.spec.ts @@ -24,7 +24,7 @@ test('valibot schemas are supported', async () => { return { getError, onSubmit: handleSubmit(v => { - handler(v.toJSON()); + handler(v.toObject()); }), }; }, From e9cc5923f58c32d912f45a312adf295822195595 Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Sat, 16 Nov 2024 01:11:14 +0200 Subject: [PATCH 2/2] fix(playground): use toObject --- packages/playground/src/App.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/playground/src/App.vue b/packages/playground/src/App.vue index 582a298f..331985b6 100644 --- a/packages/playground/src/App.vue +++ b/packages/playground/src/App.vue @@ -24,7 +24,7 @@ const { handleSubmit, values } = useForm< >(); const onSubmit = handleSubmit(data => { - console.log(data.toJSON()); + console.log(data.toObject()); });