oxform-core 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/index.cjs +751 -0
  2. package/dist/index.cjs.map +1 -0
  3. package/dist/index.d.cts +443 -0
  4. package/dist/index.d.ts +443 -0
  5. package/dist/index.js +747 -0
  6. package/dist/index.js.map +1 -0
  7. package/package.json +20 -15
  8. package/export/index.ts +0 -7
  9. package/export/schema.ts +0 -1
  10. package/src/field-api.constants.ts +0 -15
  11. package/src/field-api.ts +0 -139
  12. package/src/form-api.ts +0 -84
  13. package/src/form-api.types.ts +0 -148
  14. package/src/form-array-field-api.ts +0 -233
  15. package/src/form-context-api.ts +0 -232
  16. package/src/form-field-api.ts +0 -174
  17. package/src/more-types.ts +0 -178
  18. package/src/tests/array/append.spec.ts +0 -138
  19. package/src/tests/array/insert.spec.ts +0 -182
  20. package/src/tests/array/move.spec.ts +0 -175
  21. package/src/tests/array/prepend.spec.ts +0 -138
  22. package/src/tests/array/remove.spec.ts +0 -174
  23. package/src/tests/array/swap.spec.ts +0 -152
  24. package/src/tests/array/update.spec.ts +0 -148
  25. package/src/tests/field/change.spec.ts +0 -226
  26. package/src/tests/field/reset.spec.ts +0 -617
  27. package/src/tests/field/set-errors.spec.ts +0 -254
  28. package/src/tests/field-api/field-api.spec.ts +0 -341
  29. package/src/tests/form-api/reset.spec.ts +0 -535
  30. package/src/tests/form-api/submit.spec.ts +0 -409
  31. package/src/types.ts +0 -5
  32. package/src/utils/get.ts +0 -5
  33. package/src/utils/testing/sleep.ts +0 -1
  34. package/src/utils/testing/tests.ts +0 -18
  35. package/src/utils/update.ts +0 -6
  36. package/src/utils/validate.ts +0 -8
  37. package/tsconfig.json +0 -3
  38. package/tsdown.config.ts +0 -10
  39. package/vitest.config.ts +0 -3
@@ -1,409 +0,0 @@
1
- import { FormApi } from '#form-api';
2
- import { viPromise } from '#utils/testing/tests';
3
- import { afterAll, describe, expect, it, vi } from 'vitest';
4
- import z from 'zod';
5
-
6
- const schema = z.object({
7
- name: z.string(),
8
- nested: z.object({
9
- deep: z.object({
10
- deeper: z.string().array(),
11
- }),
12
- }),
13
- });
14
-
15
- const validValues = {
16
- name: 'John',
17
- nested: {
18
- deep: {
19
- deeper: ['hello'],
20
- },
21
- },
22
- };
23
-
24
- const setup = async ({ values }: { values: z.infer<typeof schema> }) => {
25
- const form = new FormApi({
26
- schema,
27
- defaultValues: values,
28
- });
29
-
30
- form['~mount']();
31
-
32
- return { form };
33
- };
34
-
35
- describe('on success', async () => {
36
- const { form } = await setup({ values: validValues });
37
-
38
- const onSuccess = vi.fn();
39
- const onError = vi.fn();
40
-
41
- await form.submit(onSuccess, onError)();
42
-
43
- it('should call onSuccess', () => {
44
- expect(onError).not.toHaveBeenCalled();
45
- expect(onSuccess).toHaveBeenCalledOnce();
46
- expect(onSuccess).toHaveBeenCalledWith(validValues, form);
47
- });
48
-
49
- it('mark form as successful', () => {
50
- expect(form.store.state.status.successful).toBe(true);
51
- });
52
-
53
- it('should reset the submitting status', () => {
54
- expect(form.store.state.status.submitting).toBe(false);
55
- });
56
-
57
- it('should reset the validating status', () => {
58
- expect(form.store.state.status.validating).toBe(false);
59
- });
60
-
61
- it('should increase the submits count', () => {
62
- expect(form.store.state.status.submits).toBe(1);
63
- });
64
-
65
- it('should mark the form as valid', () => {
66
- expect(form.store.state.status.valid).toBe(true);
67
- });
68
-
69
- it('should mark the form as dirty', () => {
70
- expect(form.store.state.status.dirty).toBe(true);
71
- });
72
-
73
- it('should mark the form as submitted', () => {
74
- expect(form.store.state.status.submitted).toBe(true);
75
- });
76
- });
77
-
78
- describe('on error', async () => {
79
- const { form } = await setup({ values: { ...validValues, name: 2 } as any });
80
-
81
- const onSuccess = vi.fn();
82
- const onError = vi.fn();
83
-
84
- await form.submit(onSuccess, onError)();
85
-
86
- it('should call onError', () => {
87
- expect(onSuccess).not.toHaveBeenCalled();
88
- expect(onError).toHaveBeenCalledOnce();
89
- expect(onError).toHaveBeenCalledWith(
90
- [
91
- {
92
- code: 'invalid_type',
93
- expected: 'string',
94
- message: 'Invalid input: expected string, received number',
95
- path: ['name'],
96
- },
97
- ],
98
- form,
99
- );
100
- });
101
-
102
- it('should set the corresponding field errors', () => {
103
- expect(form.field.errors('nested')).toStrictEqual([]);
104
- expect(form.field.errors('name')).toEqual([
105
- {
106
- code: 'invalid_type',
107
- expected: 'string',
108
- message: 'Invalid input: expected string, received number',
109
- path: ['name'],
110
- },
111
- ]);
112
- });
113
-
114
- it('mark form as successful false', () => {
115
- expect(form.store.state.status.successful).toBe(false);
116
- });
117
-
118
- it('should reset the submitting status', () => {
119
- expect(form.store.state.status.submitting).toBe(false);
120
- });
121
-
122
- it('should reset the validating status', () => {
123
- expect(form.store.state.status.validating).toBe(false);
124
- });
125
-
126
- it('should increase the submits count', () => {
127
- expect(form.store.state.status.submits).toBe(1);
128
- });
129
-
130
- it('should mark the form as invalid', () => {
131
- expect(form.store.state.status.valid).toBe(false);
132
- });
133
-
134
- it('should mark the form as dirty', () => {
135
- expect(form.store.state.status.dirty).toBe(true);
136
- });
137
-
138
- it('should mark the form as submitted', () => {
139
- expect(form.store.state.status.submitted).toBe(true);
140
- });
141
- });
142
-
143
- describe('while submitting', async () => {
144
- const { form } = await setup({ values: validValues });
145
-
146
- const submitting = viPromise();
147
- const submit = form.submit(submitting.fn)();
148
-
149
- it('should mark the form as submitting', () => {
150
- expect(form.store.state.status.submitting).toBe(true);
151
- });
152
-
153
- it('should have not yet increased the submits count', () => {
154
- expect(form.store.state.status.submits).toBe(0);
155
- });
156
-
157
- afterAll(async () => {
158
- await submitting.release();
159
- await submit;
160
- });
161
- });
162
-
163
- it('marks the form as validating when submit is called and schema is async', async () => {
164
- const validating = viPromise();
165
- const submitting = viPromise();
166
-
167
- const form = new FormApi({
168
- schema: z
169
- .object({
170
- name: z.string(),
171
- })
172
- .refine(async () => {
173
- await validating.fn();
174
- return true;
175
- }),
176
- defaultValues: {
177
- name: 'John',
178
- },
179
- });
180
-
181
- form['~mount']();
182
-
183
- const submit = form.submit(submitting.fn)();
184
-
185
- expect(form.store.state.status.validating).toBe(true);
186
-
187
- await validating.release();
188
- await submitting.release();
189
- await submit;
190
-
191
- expect(form.store.state.status.validating).toBe(false);
192
- });
193
-
194
- describe('submit without onError callback', async () => {
195
- const { form } = await setup({ values: { ...validValues, name: 3 } as any });
196
-
197
- await form.submit(() => {})();
198
-
199
- it('should not throw when onError is not provided', () => {
200
- expect(form.store.state.status.successful).toBe(false);
201
- expect(form.store.state.status.submitting).toBe(false);
202
- });
203
-
204
- it('should still set field errors', () => {
205
- expect(form.field.errors('name')).toEqual([
206
- {
207
- code: 'invalid_type',
208
- expected: 'string',
209
- message: 'Invalid input: expected string, received number',
210
- path: ['name'],
211
- },
212
- ]);
213
- });
214
- });
215
-
216
- describe('submit with async onSuccess callback', async () => {
217
- const { form } = await setup({ values: validValues });
218
- const asyncSuccess = viPromise();
219
- const onSuccess = vi.fn().mockImplementation(asyncSuccess.fn);
220
-
221
- const submit = form.submit(onSuccess)();
222
-
223
- it('should handle async success callbacks', async () => {
224
- expect(form.store.state.status.submitting).toBe(true);
225
-
226
- await asyncSuccess.release();
227
- await submit;
228
-
229
- expect(onSuccess).toHaveBeenCalledOnce();
230
- expect(form.store.state.status.submitting).toBe(false);
231
- expect(form.store.state.status.successful).toBe(true);
232
- });
233
- });
234
-
235
- describe('submit with async onError callback', async () => {
236
- const { form } = await setup({ values: { ...validValues, name: 4 } as any });
237
- const asyncError = viPromise();
238
- const onError = vi.fn().mockImplementation(asyncError.fn);
239
-
240
- const submit = form.submit(() => {}, onError)();
241
-
242
- it('should handle async error callbacks', async () => {
243
- expect(form.store.state.status.submitting).toBe(true);
244
-
245
- await asyncError.release();
246
- await submit;
247
-
248
- expect(onError).toHaveBeenCalledOnce();
249
- expect(form.store.state.status.submitting).toBe(false);
250
- expect(form.store.state.status.successful).toBe(false);
251
- });
252
- });
253
-
254
- describe('multiple submit calls', async () => {
255
- const { form } = await setup({ values: validValues });
256
-
257
- await form.submit(() => {})();
258
- await form.submit(() => {})();
259
- await form.submit(() => {})();
260
-
261
- it('should increment submits count correctly', () => {
262
- expect(form.store.state.status.submits).toBe(3);
263
- });
264
-
265
- it('should maintain successful status after multiple successful submits', () => {
266
- expect(form.store.state.status.successful).toBe(true);
267
- });
268
- });
269
-
270
- describe('submit with custom submit validator', async () => {
271
- const customValidator = z.object({
272
- name: z.string().min(5, 'Name must be at least 5 characters'),
273
- });
274
-
275
- const form = new FormApi({
276
- schema,
277
- defaultValues: validValues,
278
- validate: {
279
- submit: customValidator,
280
- },
281
- });
282
-
283
- form['~mount']();
284
-
285
- const onSuccess = vi.fn();
286
- const onError = vi.fn();
287
-
288
- await form.submit(onSuccess, onError)();
289
-
290
- it('should use custom submit validator instead of main schema', () => {
291
- expect(onSuccess).not.toHaveBeenCalled();
292
- expect(onError).toHaveBeenCalledWith(
293
- [
294
- {
295
- code: 'too_small',
296
- origin: 'string',
297
- inclusive: true,
298
- message: 'Name must be at least 5 characters',
299
- minimum: 5,
300
- path: ['name'],
301
- },
302
- ],
303
- form,
304
- );
305
- });
306
-
307
- it('should set corresponding field errors from custom validator', () => {
308
- expect(form.field.errors('name')).toEqual([
309
- {
310
- code: 'too_small',
311
- origin: 'string',
312
- inclusive: true,
313
- message: 'Name must be at least 5 characters',
314
- minimum: 5,
315
- path: ['name'],
316
- },
317
- ]);
318
- });
319
- });
320
-
321
- describe('submit with deep nested errors', async () => {
322
- const invalidNestedValues = {
323
- name: 'John',
324
- nested: {
325
- deep: {
326
- deeper: [5] as any, // should be string array
327
- },
328
- },
329
- };
330
-
331
- const { form } = await setup({ values: invalidNestedValues });
332
-
333
- const onSuccess = vi.fn();
334
- const onError = vi.fn();
335
-
336
- await form.submit(onSuccess, onError)();
337
-
338
- it('should handle deep nested validation errors', () => {
339
- expect(onSuccess).not.toHaveBeenCalled();
340
- expect(onError).toHaveBeenCalledWith(
341
- [
342
- {
343
- code: 'invalid_type',
344
- expected: 'string',
345
- message: 'Invalid input: expected string, received number',
346
- path: ['nested', 'deep', 'deeper', 0],
347
- },
348
- ],
349
- form,
350
- );
351
- });
352
-
353
- it('should set errors on nested paths', () => {
354
- expect(form.field.errors('nested.deep.deeper.0' as any)).toEqual([
355
- {
356
- code: 'invalid_type',
357
- expected: 'string',
358
- message: 'Invalid input: expected string, received number',
359
- path: ['nested', 'deep', 'deeper', 0],
360
- },
361
- ]);
362
- });
363
- });
364
-
365
- describe('submit sequence - success then error', async () => {
366
- const { form } = await setup({ values: validValues });
367
- await form.submit(() => {})();
368
-
369
- form.field.change('name', 5 as any);
370
-
371
- const onError = vi.fn();
372
- await form.submit(() => {}, onError)();
373
-
374
- it('should handle success followed by error correctly', () => {
375
- expect(form.store.state.status.submits).toBe(2);
376
- expect(form.store.state.status.successful).toBe(false);
377
- expect(onError).toHaveBeenCalledOnce();
378
- });
379
- });
380
-
381
- describe('submit sequence - error then success', async () => {
382
- const { form } = await setup({ values: { ...validValues, name: 6 } as any });
383
- await form.submit(() => {})();
384
-
385
- form.field.change('name', 'John');
386
-
387
- const onSuccess = vi.fn();
388
- await form.submit(onSuccess)();
389
-
390
- it('should handle error followed by success correctly', () => {
391
- expect(form.store.state.status.submits).toBe(2);
392
- expect(form.store.state.status.successful).toBe(true);
393
- expect(onSuccess).toHaveBeenCalledOnce();
394
- });
395
-
396
- it('should clear previous errors', () => {
397
- expect(form.field.errors('name')).toEqual([]);
398
- });
399
- });
400
-
401
- it('should set dirty status to true when submit is called', async () => {
402
- const { form } = await setup({ values: validValues });
403
-
404
- expect(form.store.state.status.dirty).toBe(false);
405
-
406
- await form.submit(() => {})();
407
-
408
- expect(form.store.state.status.dirty).toBe(true);
409
- });
package/src/types.ts DELETED
@@ -1,5 +0,0 @@
1
- import type { StandardSchemaV1 } from '@standard-schema/spec';
2
-
3
- export type { StandardSchemaV1 as StandardSchema };
4
- export type EventLike = { target?: { value: any } };
5
- export type { PartialDeep, Simplify } from 'type-fest';
package/src/utils/get.ts DELETED
@@ -1,5 +0,0 @@
1
- export const get = <Data>(data: Data, path: (string | number)[]) => {
2
- return path.reduce((acc, key) => {
3
- return (acc as any)[key] as any;
4
- }, data);
5
- };
@@ -1 +0,0 @@
1
- export const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
@@ -1,18 +0,0 @@
1
- import { vi } from 'vitest';
2
-
3
- export const viPromise = <T = void>() => {
4
- let resolvePromise: (value: T) => void;
5
-
6
- const promise = new Promise<T>(resolve => {
7
- resolvePromise = resolve;
8
- });
9
-
10
- const fn = vi.fn(() => promise);
11
-
12
- const release = async (value: T) => {
13
- resolvePromise(value);
14
- await promise;
15
- };
16
-
17
- return { fn, release };
18
- };
@@ -1,6 +0,0 @@
1
- export type UpdaterFn<TInput, TOutput = TInput> = (input: TInput) => TOutput;
2
- export type Updater<TInput, TOutput = TInput> = TOutput | UpdaterFn<TInput, TOutput>;
3
-
4
- export function update<TInput, TOutput = TInput>(updater: Updater<TInput, TOutput>, input: TInput): TOutput {
5
- return typeof updater === 'function' ? (updater as UpdaterFn<TInput, TOutput>)(input) : updater;
6
- }
@@ -1,8 +0,0 @@
1
- import type { StandardSchema } from '#types';
2
-
3
- export const validate = async <Schema extends StandardSchema>(schema: Schema, input: unknown) => {
4
- let result = schema['~standard'].validate(input);
5
- if (result instanceof Promise) result = await result;
6
-
7
- return result as StandardSchema.Result<StandardSchema.InferOutput<Schema>>;
8
- };
package/tsconfig.json DELETED
@@ -1,3 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.json"
3
- }
package/tsdown.config.ts DELETED
@@ -1,10 +0,0 @@
1
- import { defineConfig } from 'tsdown';
2
- import base from '../../tsdown.config';
3
-
4
- export default defineConfig({
5
- ...base,
6
- entry: {
7
- index: 'export/index.ts',
8
- schema: 'export/schema.ts',
9
- },
10
- });
package/vitest.config.ts DELETED
@@ -1,3 +0,0 @@
1
- import base from '../../vitest.config';
2
-
3
- export default base;