@teamnovu/kit-vue-forms 0.1.13 → 0.1.15

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.
@@ -1,359 +1,471 @@
1
- import { describe, expect, it } from 'vitest'
2
- import { effectScope, isReactive, nextTick, reactive, ref } from 'vue'
3
- import { z } from 'zod'
4
- import { useForm } from '../src/composables/useForm'
1
+ import { describe, expect, it, vi } from "vitest";
2
+ import { effectScope, isReactive, nextTick, reactive, ref } from "vue";
3
+ import { z } from "zod";
4
+ import { useForm } from "../src/composables/useForm";
5
5
 
6
- const scope = effectScope()
6
+ const scope = effectScope();
7
7
 
8
- describe('useForm', () => {
9
- beforeAll(() => {
10
-
11
- })
12
- it('should initialize form with initial data', () => {
8
+ describe("useForm", () => {
9
+ it("should initialize form with initial data", () => {
13
10
  const initialData = {
14
- name: 'John',
11
+ name: "John",
15
12
  age: 30,
16
- }
17
- const form = useForm({ initialData })
13
+ };
14
+ const form = useForm({ initialData });
18
15
 
19
- expect(form.data.value).toEqual(initialData)
20
- expect(form.initialData.value).toEqual(initialData)
21
- })
16
+ expect(form.data.value).toEqual(initialData);
17
+ expect(form.initialData.value).toEqual(initialData);
18
+ });
22
19
 
23
- it('should initialize form with reactive initial data', async () => {
20
+ it("should initialize form with reactive initial data", async () => {
24
21
  const initialData = ref({
25
- name: 'John',
22
+ name: "John",
26
23
  age: 30,
27
- })
28
- const form = useForm({ initialData })
24
+ });
25
+ const form = useForm({ initialData });
29
26
 
30
27
  expect(form.data.value).toEqual({
31
- name: 'John',
28
+ name: "John",
32
29
  age: 30,
33
- })
30
+ });
34
31
 
35
32
  // Update reactive initial data
36
33
  initialData.value = {
37
- name: 'Jane',
34
+ name: "Jane",
38
35
  age: 25,
39
- }
40
- await nextTick()
36
+ };
37
+ await nextTick();
41
38
 
42
39
  expect(form.initialData.value).toEqual({
43
- name: 'Jane',
40
+ name: "Jane",
44
41
  age: 25,
45
- })
46
- })
42
+ });
43
+ });
47
44
 
48
- it('should reinitialize form with new initial data', async () => {
45
+ it("should reinitialize form with new initial data", async () => {
49
46
  const initialData = ref({
50
- name: 'John',
47
+ name: "John",
51
48
  age: 30,
52
- })
53
- const form = useForm({ initialData })
49
+ });
50
+ const form = useForm({ initialData });
54
51
 
55
52
  expect(form.data.value).toEqual({
56
- name: 'John',
53
+ name: "John",
57
54
  age: 30,
58
- })
55
+ });
59
56
 
60
57
  // Update reactive initial data
61
58
  initialData.value = {
62
- name: 'Jane',
59
+ name: "Jane",
63
60
  age: 25,
64
- }
65
- await nextTick()
61
+ };
62
+ await nextTick();
66
63
 
67
64
  expect(form.data.value).toEqual({
68
- name: 'Jane',
65
+ name: "Jane",
69
66
  age: 25,
70
- })
71
- })
67
+ });
68
+ });
72
69
 
73
- it('should have initial state values', () => {
74
- const form = useForm({ initialData: { name: 'John' } })
70
+ it("should have initial state values", () => {
71
+ const form = useForm({ initialData: { name: "John" } });
75
72
 
76
- expect(form.isDirty.value).toBe(false)
77
- expect(form.isTouched.value).toBe(false)
78
- expect(form.isValid.value).toBe(true)
79
- expect(form.isValidated.value).toBe(false)
80
- })
73
+ expect(form.isDirty.value).toBe(false);
74
+ expect(form.isTouched.value).toBe(false);
75
+ expect(form.isValid.value).toBe(true);
76
+ expect(form.isValidated.value).toBe(false);
77
+ });
81
78
 
82
- it('should define fields and auto-register them', () => {
79
+ it("should define fields and auto-register them", () => {
83
80
  const form = useForm({
84
81
  initialData: {
85
- name: 'John',
86
- email: 'john@example.com',
82
+ name: "John",
83
+ email: "john@example.com",
87
84
  },
88
- })
85
+ });
89
86
 
90
- const nameField = form.defineField({ path: 'name' })
91
- const emailField = form.defineField({ path: 'email' })
87
+ const nameField = form.defineField({ path: "name" });
88
+ const emailField = form.defineField({ path: "email" });
92
89
 
93
- expect(nameField.path.value).toBe('name')
94
- expect(emailField.path.value).toBe('email')
95
- expect(form.fields.value.length).toBe(2)
96
- })
90
+ expect(nameField.path.value).toBe("name");
91
+ expect(emailField.path.value).toBe("email");
92
+ expect(form.fields.value.length).toBe(2);
93
+ });
97
94
 
98
- it('should get registered fields', () => {
95
+ it("should get registered fields", () => {
99
96
  const form = useForm({
100
97
  initialData: {
101
- name: 'John',
102
- email: 'john@example.com',
98
+ name: "John",
99
+ email: "john@example.com",
103
100
  },
104
- })
101
+ });
105
102
 
106
- const nameField = form.defineField({ path: 'name' })
107
- form.defineField({ path: 'email' })
103
+ const nameField = form.defineField({ path: "name" });
104
+ form.defineField({ path: "email" });
108
105
 
109
- const retrievedField = form.getField('name')
110
- expect(retrievedField?.path.value).toBe('name')
111
- expect(retrievedField).toBe(nameField)
112
- })
106
+ const retrievedField = form.getField("name");
107
+ expect(retrievedField?.path.value).toBe("name");
108
+ expect(retrievedField).toBe(nameField);
109
+ });
113
110
 
114
- it('should handle nested object initial data', () => {
111
+ it("should handle nested object initial data", () => {
115
112
  const initialData = {
116
113
  user: {
117
- name: 'John',
114
+ name: "John",
118
115
  address: {
119
- street: '123 Main St',
120
- city: 'New York',
116
+ street: "123 Main St",
117
+ city: "New York",
121
118
  },
122
119
  },
123
- }
124
- const form = useForm({ initialData })
120
+ };
121
+ const form = useForm({ initialData });
125
122
 
126
- expect(form.data.value).toEqual(initialData)
127
- expect(form.initialData.value).toEqual(initialData)
128
- })
123
+ expect(form.data.value).toEqual(initialData);
124
+ expect(form.initialData.value).toEqual(initialData);
125
+ });
129
126
 
130
- it('should validate with schema', async () => {
127
+ it("should validate with schema", async () => {
131
128
  const schema = z.object({
132
129
  name: z.string().min(2),
133
130
  age: z.number().min(18),
134
- })
131
+ });
135
132
 
136
133
  const form = useForm({
137
134
  initialData: {
138
- name: 'A',
135
+ name: "A",
139
136
  age: 16,
140
137
  },
141
138
  schema,
142
- })
139
+ });
143
140
 
144
- const result = await form.validateForm()
141
+ const result = await form.validateForm();
145
142
 
146
- expect(result.isValid).toBe(false)
147
- expect(form.isValidated.value).toBe(true)
148
- expect(form.errors.value.propertyErrors.name).toBeDefined()
149
- expect(form.errors.value.propertyErrors.age).toBeDefined()
150
- })
143
+ expect(result.isValid).toBe(false);
144
+ expect(form.isValidated.value).toBe(true);
145
+ expect(form.errors.value.propertyErrors.name).toBeDefined();
146
+ expect(form.errors.value.propertyErrors.age).toBeDefined();
147
+ });
151
148
 
152
- it('should validate with custom function', async () => {
149
+ it("should validate with custom function", async () => {
153
150
  const validateFn = async (data: { name: string }) => {
154
151
  const errors = {
155
152
  general: [],
156
153
  propertyErrors: {} as Record<string, string[]>,
157
- }
154
+ };
158
155
 
159
156
  if (data.name.length < 2) {
160
- errors.propertyErrors.name = ['Name too short']
157
+ errors.propertyErrors.name = ["Name too short"];
161
158
  }
162
159
 
163
160
  return {
164
161
  isValid: Object.keys(errors.propertyErrors).length === 0,
165
162
  errors,
166
- }
167
- }
163
+ };
164
+ };
168
165
 
169
166
  const form = useForm({
170
- initialData: { name: 'A' },
167
+ initialData: { name: "A" },
171
168
  validateFn,
172
- })
169
+ });
173
170
 
174
- const result = await form.validateForm()
171
+ const result = await form.validateForm();
175
172
 
176
- expect(result.isValid).toBe(false)
177
- expect(result.errors.propertyErrors.name).toEqual(['Name too short'])
178
- })
173
+ expect(result.isValid).toBe(false);
174
+ expect(result.errors.propertyErrors.name).toEqual(["Name too short"]);
175
+ });
179
176
 
180
- it('should pass validation with valid data', async () => {
177
+ it("should pass validation with valid data", async () => {
181
178
  const schema = z.object({
182
179
  name: z.string().min(2),
183
180
  age: z.number().min(18),
184
- })
181
+ });
185
182
 
186
183
  const form = useForm({
187
184
  initialData: {
188
- name: 'John',
185
+ name: "John",
189
186
  age: 30,
190
187
  },
191
188
  schema,
192
- })
189
+ });
193
190
 
194
- const result = await form.validateForm()
191
+ const result = await form.validateForm();
195
192
 
196
- expect(result.isValid).toBe(true)
197
- expect(form.isValidated.value).toBe(true)
198
- expect(form.errors.value.general).toEqual([])
199
- expect(form.errors.value.propertyErrors).toEqual({})
200
- })
193
+ expect(result.isValid).toBe(true);
194
+ expect(form.isValidated.value).toBe(true);
195
+ expect(form.errors.value.general).toEqual([]);
196
+ expect(form.errors.value.propertyErrors).toEqual({});
197
+ });
201
198
 
202
- it('can handle arrays on top level', async () => {
203
- const schema = z.array(z.string())
199
+ it("can handle arrays on top level", async () => {
200
+ const schema = z.array(z.string());
204
201
 
205
202
  const form = useForm({
206
- initialData: ['item1', 'item2'],
203
+ initialData: ["item1", "item2"],
207
204
  schema,
208
- })
205
+ });
209
206
 
210
- const result = await form.validateForm()
207
+ const result = await form.validateForm();
211
208
 
212
- expect(result.isValid).toBe(true)
213
- expect(form.isValidated.value).toBe(true)
214
- expect(form.errors.value.general).toEqual([])
215
- expect(form.errors.value.propertyErrors).toEqual({})
209
+ expect(result.isValid).toBe(true);
210
+ expect(form.isValidated.value).toBe(true);
211
+ expect(form.errors.value.general).toEqual([]);
212
+ expect(form.errors.value.propertyErrors).toEqual({});
216
213
 
217
- const rootField = form.defineField({ path: '' })
218
- const itemField = form.defineField({ path: '1' })
214
+ const rootField = form.defineField({ path: "" });
215
+ const itemField = form.defineField({ path: "1" });
219
216
 
220
- expect(rootField.data.value).toEqual(['item1', 'item2'])
221
- expect(itemField.data.value).toBe('item2')
222
- })
217
+ expect(rootField.data.value).toEqual(["item1", "item2"]);
218
+ expect(itemField.data.value).toBe("item2");
219
+ });
223
220
 
224
- it('can handle late initialized fields', async () => {
221
+ it("can handle late initialized fields", async () => {
225
222
  const form = useForm({
226
223
  initialData: {
227
- name: 'John',
224
+ name: "John",
228
225
  age: 30,
229
226
  },
230
- })
227
+ });
231
228
 
232
229
  // No fields defined yet
233
- expect(form.fields.value).toEqual([])
234
- expect(form.isDirty.value).toBe(false)
230
+ expect(form.fields.value).toEqual([]);
231
+ expect(form.isDirty.value).toBe(false);
235
232
 
236
- const nameField = form.defineField({ path: 'name' })
233
+ const nameField = form.defineField({ path: "name" });
237
234
 
238
- expect(form.fields.value.length).toBe(1)
239
- expect(form.isDirty.value).toBe(false)
235
+ expect(form.fields.value.length).toBe(1);
236
+ expect(form.isDirty.value).toBe(false);
240
237
 
241
- nameField.setData('Bob')
238
+ nameField.setData("Bob");
242
239
 
243
- expect(form.isDirty.value).toBe(true)
244
- })
240
+ expect(form.isDirty.value).toBe(true);
241
+ });
245
242
 
246
- it('fields can be made reactive', async () => {
243
+ it("fields can be made reactive", async () => {
247
244
  const form = useForm({
248
245
  initialData: {
249
- name: 'John',
246
+ name: "John",
250
247
  age: 30,
251
248
  },
252
- })
249
+ });
253
250
 
254
- const nameField = form.defineField({ path: 'name' })
251
+ const nameField = form.defineField({ path: "name" });
255
252
 
256
- expect(isReactive(reactive(nameField))).toBe(true)
257
- })
253
+ expect(isReactive(reactive(nameField))).toBe(true);
254
+ });
258
255
 
259
- it('fields should only be removed when all references are gone', async () => {
256
+ it("fields should only be removed when all references are gone", async () => {
260
257
  const form = useForm({
261
258
  initialData: {
262
- name: 'John',
259
+ name: "John",
263
260
  age: 30,
264
261
  },
265
- })
262
+ });
266
263
 
267
- expect(form.fields.value).toEqual([])
264
+ expect(form.fields.value).toEqual([]);
268
265
 
269
- const scope1 = effectScope()
266
+ const scope1 = effectScope();
270
267
 
271
268
  scope1.run(() => {
272
- form.defineField({ path: 'name' })
273
- })
269
+ form.defineField({ path: "name" });
270
+ });
274
271
 
275
- expect(form.fields.value.length).toBe(1)
272
+ expect(form.fields.value.length).toBe(1);
276
273
 
277
- const scope2 = effectScope()
274
+ const scope2 = effectScope();
278
275
 
279
276
  scope2.run(() => {
280
- form.defineField({ path: 'name' })
281
- })
277
+ form.defineField({ path: "name" });
278
+ });
282
279
 
283
- expect(form.fields.value.length).toBe(1)
280
+ expect(form.fields.value.length).toBe(1);
284
281
 
285
- scope1.stop()
282
+ scope1.stop();
286
283
 
287
- expect(form.fields.value.length).toBe(1)
284
+ expect(form.fields.value.length).toBe(1);
288
285
 
289
- scope2.stop()
286
+ scope2.stop();
290
287
 
291
- expect(form.fields.value.length).toBe(0)
292
- })
288
+ expect(form.fields.value.length).toBe(0);
289
+ });
293
290
 
294
- it('it should take over the new initial data from the form', { timeout: 500 }, async () => {
295
- const initialData = ref({
296
- name: 'foo',
297
- })
291
+ it(
292
+ "it should take over the new initial data from the form",
293
+ { timeout: 500 },
294
+ async () => {
295
+ const initialData = ref({
296
+ name: "foo",
297
+ });
298
298
 
299
- const form = useForm({
300
- initialData,
301
- })
299
+ const form = useForm({
300
+ initialData,
301
+ });
302
302
 
303
- const nameField = form.getField('name')
303
+ const nameField = form.getField("name");
304
304
 
305
- expect(form.isDirty.value).toBe(false)
306
- expect(nameField.dirty.value).toBe(false)
305
+ expect(form.isDirty.value).toBe(false);
306
+ expect(nameField.dirty.value).toBe(false);
307
307
 
308
- nameField.setData('modified')
308
+ nameField.setData("modified");
309
309
 
310
- expect(form.isDirty.value).toBe(true)
311
- expect(nameField.dirty.value).toBe(true)
310
+ expect(form.isDirty.value).toBe(true);
311
+ expect(nameField.dirty.value).toBe(true);
312
312
 
313
- initialData.value = {
314
- name: 'bar',
315
- }
313
+ initialData.value = {
314
+ name: "bar",
315
+ };
316
316
 
317
- expect(form.initialData.value.name).toBe('bar')
318
- expect(nameField.initialValue.value).toBe('bar')
319
- expect(nameField.data.value).toBe('bar')
317
+ expect(form.initialData.value.name).toBe("bar");
318
+ expect(nameField.initialValue.value).toBe("bar");
319
+ expect(nameField.data.value).toBe("bar");
320
320
 
321
- nameField.setInitialData('another')
321
+ nameField.setInitialData("another");
322
322
 
323
- expect(nameField.initialValue.value).toBe('another')
324
- expect(nameField.data.value).toBe('another')
325
- })
323
+ expect(nameField.initialValue.value).toBe("another");
324
+ expect(nameField.data.value).toBe("another");
325
+ },
326
+ );
326
327
 
327
- it('it should take over the new initial data from the form after setting initial data on field', { timeout: 500 }, async () => {
328
- const initialData = ref({
329
- name: null as null | number,
330
- })
328
+ it(
329
+ "it should take over the new initial data from the form after setting initial data on field",
330
+ { timeout: 500 },
331
+ async () => {
332
+ const initialData = ref({
333
+ name: null as null | number,
334
+ });
335
+
336
+ const form = useForm({
337
+ initialData,
338
+ });
339
+
340
+ const nameField = form.getField("name");
341
+
342
+ expect(form.isDirty.value).toBe(false);
343
+ expect(nameField.dirty.value).toBe(false);
344
+
345
+ nameField.setInitialData(0);
331
346
 
347
+ expect(form.isDirty.value).toBe(false);
348
+ expect(nameField.dirty.value).toBe(false);
349
+
350
+ initialData.value = {
351
+ name: null,
352
+ };
353
+
354
+ expect(form.initialData.value.name).toBe(null);
355
+ expect(nameField.initialValue.value).toBe(null);
356
+ expect(nameField.data.value).toBe(null);
357
+
358
+ nameField.setInitialData(23);
359
+
360
+ expect(nameField.initialValue.value).toBe(23);
361
+ expect(nameField.data.value).toBe(23);
362
+ },
363
+ );
364
+
365
+ it("it should keep values on unmount if keepValuesOnUnmount is true (default)", async () => {
332
366
  const form = useForm({
333
- initialData,
334
- })
367
+ initialData: {
368
+ name: "A",
369
+ },
370
+ });
335
371
 
336
- const nameField = form.getField('name')
372
+ effectScope().run(() => {
373
+ const nameField = form.defineField({ path: "name" });
337
374
 
338
- expect(form.isDirty.value).toBe(false)
339
- expect(nameField.dirty.value).toBe(false)
375
+ expect(form.fields.value.length).toBe(1);
376
+ expect(nameField.data.value).toBe("A");
340
377
 
341
- nameField.setInitialData(0)
378
+ nameField.setData("Modified");
379
+ });
342
380
 
343
- expect(form.isDirty.value).toBe(false)
344
- expect(nameField.dirty.value).toBe(false)
381
+ expect(form.data.value.name).toBe("Modified");
382
+ });
345
383
 
346
- initialData.value = {
347
- name: null,
348
- }
384
+ it("it should NOT keep values on unmount if keepValuesOnUnmount is false", async () => {
385
+ const form = useForm({
386
+ initialData: {
387
+ name: "A",
388
+ },
389
+ keepValuesOnUnmount: false,
390
+ });
349
391
 
350
- expect(form.initialData.value.name).toBe(null)
351
- expect(nameField.initialValue.value).toBe(null)
352
- expect(nameField.data.value).toBe(null)
392
+ const scope = effectScope();
353
393
 
354
- nameField.setInitialData(23)
394
+ scope.run(() => {
395
+ const nameField = form.defineField({ path: "name" });
355
396
 
356
- expect(nameField.initialValue.value).toBe(23)
357
- expect(nameField.data.value).toBe(23)
358
- })
359
- })
397
+ expect(form.fields.value.length).toBe(1);
398
+ expect(nameField.data.value).toBe("A");
399
+
400
+ nameField.setData("Modified");
401
+ });
402
+
403
+ scope.stop();
404
+
405
+ expect(form.data.value.name).toBe("A");
406
+ });
407
+
408
+ describe("useForm - submit handler", () => {
409
+ it(
410
+ "it should not call the handler when validation errors exist",
411
+ { timeout: 500 },
412
+ async () => {
413
+ const initialData = ref({
414
+ name: null as null | number,
415
+ });
416
+
417
+ const schema = z.object({
418
+ name: z.number().min(5),
419
+ });
420
+
421
+ const form = useForm({
422
+ initialData,
423
+ schema,
424
+ });
425
+
426
+ const nameField = form.getField("name");
427
+
428
+ const cb = vi.fn();
429
+
430
+ const handler = form.submitHandler(cb);
431
+
432
+ await handler(new SubmitEvent("submit"));
433
+
434
+ expect(form.isValidated.value).toBe(true);
435
+ expect(cb).not.toHaveBeenCalled();
436
+ expect(nameField.errors.value?.length).toBe(1);
437
+ },
438
+ );
439
+
440
+ it(
441
+ "it should call the handler when no validation errors exist",
442
+ { timeout: 500 },
443
+ async () => {
444
+ const initialData = ref({
445
+ name: 10,
446
+ });
447
+
448
+ const schema = z.object({
449
+ name: z.number().min(5),
450
+ });
451
+
452
+ const form = useForm({
453
+ initialData,
454
+ schema,
455
+ });
456
+
457
+ const nameField = form.getField("name");
458
+
459
+ const cb = vi.fn();
460
+
461
+ const handler = form.submitHandler(cb);
462
+
463
+ await handler(new SubmitEvent("submit"));
464
+
465
+ expect(form.isValidated.value).toBe(true);
466
+ expect(cb).toHaveBeenCalled();
467
+ expect(nameField.errors.value?.length).toBe(0);
468
+ },
469
+ );
470
+ });
471
+ });