Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added toObject and make toJSON produce valid JSON output #74

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/silent-zebras-change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@formwerk/core': minor
---

feat: added toObject and make toJSON produce valid JSON output
188 changes: 180 additions & 8 deletions packages/core/src/useForm/useForm.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'));

Expand Down Expand Up @@ -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());
Expand All @@ -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' });
Expand Down Expand Up @@ -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: `
<form @submit="onSubmit" novalidate>
Expand Down Expand Up @@ -601,7 +773,7 @@ describe('form validation', () => {
schema,
});

return { onSubmit: handleSubmit(v => handler(v.toJSON())) };
return { onSubmit: handleSubmit(v => handler(v.toObject())) };
},
template: `
<form @submit="onSubmit" novalidate>
Expand Down Expand Up @@ -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: `
<form @submit="onSubmit" novalidate>
Expand Down Expand Up @@ -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: `
<form @submit="onSubmit" novalidate>
Expand Down Expand Up @@ -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: `
<form @submit="onSubmit" novalidate>
Expand Down Expand Up @@ -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: `
<form @submit="onSubmit" novalidate>
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/useForm/useForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
});
Expand Down
13 changes: 10 additions & 3 deletions packages/core/src/useForm/useFormActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<TForm extends FormObject> {
values: Partial<TForm>;
Expand All @@ -28,7 +29,8 @@ export interface FormActionsOptions<TForm extends FormObject = FormObject, TOutp

export type ConsumableData<TOutput extends FormObject> = {
toFormData: () => FormData;
toJSON: () => TOutput;
toObject: () => TOutput;
toJSON: () => Jsonify<TOutput>;
};

export interface SubmitContext {
Expand Down Expand Up @@ -141,16 +143,21 @@ export function useFormActions<TForm extends FormObject = FormObject, TOutput ex
}

function withConsumers<TData extends FormObject>(data: TData): ConsumableData<TData> {
const toJSON = () => data;
const toObject = () => data;
const toFormData = () => {
const formData = new FormData();
appendToFormData(data, formData);

return formData;
};

function toJSON() {
return JSON.parse(JSON.stringify(toObject()));
}

return {
toJSON,
toObject,
toFormData,
toJSON,
};
}
2 changes: 1 addition & 1 deletion packages/core/src/useFormGroup/useFormGroup.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ test('submission combines group data with form data', async () => {
setup() {
const { handleSubmit } = useForm({});

const onSubmit = handleSubmit(v => submitHandler(v.toJSON()));
const onSubmit = handleSubmit(v => submitHandler(v.toObject()));

return {
onSubmit,
Expand Down
2 changes: 1 addition & 1 deletion packages/ecosystem/src/arktype/arktype.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ test('Arktype schemas are supported', async () => {
return {
getError,
onSubmit: handleSubmit(v => {
handler(v.toJSON());
handler(v.toObject());
}),
};
},
Expand Down
2 changes: 1 addition & 1 deletion packages/ecosystem/src/valibot/valibot.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ test('valibot schemas are supported', async () => {
return {
getError,
onSubmit: handleSubmit(v => {
handler(v.toJSON());
handler(v.toObject());
}),
};
},
Expand Down
2 changes: 1 addition & 1 deletion packages/playground/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const { handleSubmit, values } = useForm<
>();

const onSubmit = handleSubmit(data => {
console.log(data.toJSON());
console.log(data.toObject());
});
</script>

Expand Down
Loading