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.
- package/dist/index.cjs +751 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +443 -0
- package/dist/index.d.ts +443 -0
- package/dist/index.js +747 -0
- package/dist/index.js.map +1 -0
- package/package.json +20 -15
- package/export/index.ts +0 -7
- package/export/schema.ts +0 -1
- package/src/field-api.constants.ts +0 -15
- package/src/field-api.ts +0 -139
- package/src/form-api.ts +0 -84
- package/src/form-api.types.ts +0 -148
- package/src/form-array-field-api.ts +0 -233
- package/src/form-context-api.ts +0 -232
- package/src/form-field-api.ts +0 -174
- package/src/more-types.ts +0 -178
- package/src/tests/array/append.spec.ts +0 -138
- package/src/tests/array/insert.spec.ts +0 -182
- package/src/tests/array/move.spec.ts +0 -175
- package/src/tests/array/prepend.spec.ts +0 -138
- package/src/tests/array/remove.spec.ts +0 -174
- package/src/tests/array/swap.spec.ts +0 -152
- package/src/tests/array/update.spec.ts +0 -148
- package/src/tests/field/change.spec.ts +0 -226
- package/src/tests/field/reset.spec.ts +0 -617
- package/src/tests/field/set-errors.spec.ts +0 -254
- package/src/tests/field-api/field-api.spec.ts +0 -341
- package/src/tests/form-api/reset.spec.ts +0 -535
- package/src/tests/form-api/submit.spec.ts +0 -409
- package/src/types.ts +0 -5
- package/src/utils/get.ts +0 -5
- package/src/utils/testing/sleep.ts +0 -1
- package/src/utils/testing/tests.ts +0 -18
- package/src/utils/update.ts +0 -6
- package/src/utils/validate.ts +0 -8
- package/tsconfig.json +0 -3
- package/tsdown.config.ts +0 -10
- 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
package/src/utils/get.ts
DELETED
|
@@ -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
|
-
};
|
package/src/utils/update.ts
DELETED
|
@@ -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
|
-
}
|
package/src/utils/validate.ts
DELETED
|
@@ -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
package/tsdown.config.ts
DELETED
package/vitest.config.ts
DELETED