@teamnovu/kit-vue-forms 0.1.22 → 0.1.24

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,297 +1,185 @@
1
- import { describe, expect, it } from 'vitest'
2
- import { nextTick } from 'vue'
3
- import { useForm } from '../src/composables/useForm'
4
- import { HashStore, useFieldArray } from '../src/composables/useFieldArray'
5
-
6
- describe('useFieldArray', () => {
7
- describe('HashStore', () => {
8
- it('should store and retrieve primitive values with identity hash', () => {
9
- const store = new HashStore<string[]>()
10
-
11
- store.set(5, ['id-1'])
12
- expect(store.has(5)).toBe(true)
13
- expect(store.get(5)).toEqual(['id-1'])
14
-
15
- store.set(10, ['id-2'])
16
- expect(store.get(10)).toEqual(['id-2'])
17
- expect(store.get(5)).toEqual(['id-1'])
18
- })
19
-
20
- it('should store objects by reference with identity hash', () => {
21
- const store = new HashStore<string[]>()
1
+ import { describe, expect, it } from "vitest";
2
+ import { nextTick } from "vue";
3
+ import { useForm } from "../src/composables/useForm";
4
+ import { HashStore, useFieldArray } from "../src/composables/useFieldArray";
5
+
6
+ describe("useFieldArray", () => {
7
+ describe("HashStore", () => {
8
+ it("should store and retrieve primitive values with identity hash", () => {
9
+ const store = new HashStore<string[]>();
10
+
11
+ store.set(5, ["id-1"]);
12
+ expect(store.has(5)).toBe(true);
13
+ expect(store.get(5)).toEqual(["id-1"]);
14
+
15
+ store.set(10, ["id-2"]);
16
+ expect(store.get(10)).toEqual(["id-2"]);
17
+ expect(store.get(5)).toEqual(["id-1"]);
18
+ });
19
+
20
+ it("should store objects by reference with identity hash", () => {
21
+ const store = new HashStore<string[]>();
22
22
  const obj1 = {
23
23
  id: 1,
24
- name: 'A',
25
- }
24
+ name: "A",
25
+ };
26
26
  const obj2 = {
27
27
  id: 1,
28
- name: 'A',
29
- } // Same content, different reference
28
+ name: "A",
29
+ }; // Same content, different reference
30
30
 
31
- store.set(obj1, ['uuid-1'])
31
+ store.set(obj1, ["uuid-1"]);
32
32
 
33
- expect(store.has(obj1)).toBe(true)
34
- expect(store.has(obj2)).toBe(false)
35
- expect(store.get(obj1)).toEqual(['uuid-1'])
36
- expect(store.get(obj2)).toBeUndefined()
37
- })
33
+ expect(store.has(obj1)).toBe(true);
34
+ expect(store.has(obj2)).toBe(false);
35
+ expect(store.get(obj1)).toEqual(["uuid-1"]);
36
+ expect(store.get(obj2)).toBeUndefined();
37
+ });
38
38
 
39
- it('should use custom hash function for semantic equality', () => {
40
- const store = new HashStore<string[], { id: number }>(item => item.id)
39
+ it("should use custom hash function for semantic equality", () => {
40
+ const store = new HashStore<string[], { id: number }>((item) => item.id);
41
41
  const obj1 = {
42
42
  id: 1,
43
- name: 'A',
44
- }
43
+ name: "A",
44
+ };
45
45
  const obj2 = {
46
46
  id: 1,
47
- name: 'B',
48
- } // Same ID, different name
47
+ name: "B",
48
+ }; // Same ID, different name
49
49
  const obj3 = {
50
50
  id: 2,
51
- name: 'A',
52
- }
51
+ name: "A",
52
+ };
53
53
 
54
- store.set(obj1, ['uuid-1'])
54
+ store.set(obj1, ["uuid-1"]);
55
55
 
56
- expect(store.has(obj2)).toBe(true)
57
- expect(store.get(obj2)).toEqual(['uuid-1'])
58
- expect(store.has(obj3)).toBe(false)
59
- })
56
+ expect(store.has(obj2)).toBe(true);
57
+ expect(store.get(obj2)).toEqual(["uuid-1"]);
58
+ expect(store.has(obj3)).toBe(false);
59
+ });
60
60
 
61
- it('should overwrite value on subsequent set calls', () => {
62
- const store = new HashStore<string[]>()
63
- const item = { id: 1 }
61
+ it("should overwrite value on subsequent set calls", () => {
62
+ const store = new HashStore<string[]>();
63
+ const item = { id: 1 };
64
64
 
65
- store.set(item, ['id-1'])
66
- expect(store.get(item)).toEqual(['id-1'])
65
+ store.set(item, ["id-1"]);
66
+ expect(store.get(item)).toEqual(["id-1"]);
67
67
 
68
- store.set(item, ['id-1', 'id-2'])
69
- expect(store.get(item)).toEqual(['id-1', 'id-2'])
70
- })
71
- })
68
+ store.set(item, ["id-1", "id-2"]);
69
+ expect(store.get(item)).toEqual(["id-1", "id-2"]);
70
+ });
71
+ });
72
72
 
73
- describe('Core Functionality', () => {
74
- it('should initialize with array data and generate IDs', () => {
73
+ describe("ID Persistence", () => {
74
+ it("should maintain IDs when items are reordered", async () => {
75
75
  const form = useForm({
76
76
  initialData: {
77
77
  items: [
78
78
  {
79
79
  id: 1,
80
- name: 'Item 1',
80
+ name: "First",
81
81
  },
82
82
  {
83
83
  id: 2,
84
- name: 'Item 2',
85
- },
86
- ],
87
- },
88
- })
89
- const fieldArray = useFieldArray(form, 'items')
90
-
91
- expect(fieldArray.fields.value).toHaveLength(2)
92
- expect(fieldArray.fields.value[0]).toHaveProperty('id')
93
- expect(fieldArray.fields.value[0]).toHaveProperty('item')
94
- expect(fieldArray.fields.value[0].item).toEqual({
95
- id: 1,
96
- name: 'Item 1',
97
- })
98
- expect(typeof fieldArray.fields.value[0].id).toBe('string')
99
- expect(fieldArray.errors.value).toEqual([])
100
- expect(fieldArray.dirty.value).toBe(false)
101
- })
102
-
103
- it('should push new items to the array', async () => {
104
- const form = useForm({
105
- initialData: {
106
- items: [
107
- {
108
- id: 1,
109
- name: 'First',
110
- },
111
- ],
112
- },
113
- })
114
- const fieldArray = useFieldArray(form, 'items')
115
-
116
- expect(fieldArray.fields.value).toHaveLength(1)
117
-
118
- fieldArray.push({
119
- id: 2,
120
- name: 'Second',
121
- })
122
- await nextTick()
123
-
124
- expect(fieldArray.fields.value).toHaveLength(2)
125
- expect(fieldArray.fields.value[1].item).toEqual({
126
- id: 2,
127
- name: 'Second',
128
- })
129
- expect(fieldArray.dirty.value).toBe(true)
130
- })
131
-
132
- it('should remove item by reference', async () => {
133
- const form = useForm({
134
- initialData: {
135
- items: [
136
- {
137
- id: 1,
138
- name: 'Keep',
139
- },
140
- {
141
- id: 2,
142
- name: 'Remove',
143
- },
144
- ],
145
- },
146
- })
147
- const fieldArray = useFieldArray(form, 'items')
148
- const itemToRemove = fieldArray.fields.value[1].item
149
-
150
- fieldArray.remove(itemToRemove)
151
- await nextTick()
152
-
153
- expect(fieldArray.fields.value).toHaveLength(1)
154
- expect(fieldArray.fields.value[0].item.name).toBe('Keep')
155
- expect(fieldArray.dirty.value).toBe(true)
156
- })
157
-
158
- it('should remove item by index', async () => {
159
- const form = useForm({
160
- initialData: {
161
- items: [
162
- {
163
- id: 1,
164
- name: 'First',
165
- },
166
- {
167
- id: 2,
168
- name: 'Second',
169
- },
170
- {
171
- id: 3,
172
- name: 'Third',
173
- },
174
- ],
175
- },
176
- })
177
- const fieldArray = useFieldArray(form, 'items')
178
-
179
- fieldArray.removeByIndex(1)
180
- await nextTick()
181
-
182
- expect(fieldArray.fields.value).toHaveLength(2)
183
- expect(fieldArray.fields.value[0].item.name).toBe('First')
184
- expect(fieldArray.fields.value[1].item.name).toBe('Third')
185
- })
186
- })
187
-
188
- describe('ID Persistence', () => {
189
- it('should maintain IDs when items are reordered', async () => {
190
- const form = useForm({
191
- initialData: {
192
- items: [
193
- {
194
- id: 1,
195
- name: 'First',
196
- },
197
- {
198
- id: 2,
199
- name: 'Second',
84
+ name: "Second",
200
85
  },
201
86
  {
202
87
  id: 3,
203
- name: 'Third',
88
+ name: "Third",
204
89
  },
205
90
  ],
206
91
  },
207
- })
208
- const fieldArray = useFieldArray(form, 'items', {
92
+ });
93
+ const fieldArray = useFieldArray(form, "items", {
209
94
  hashFn: (item: { id: number }) => item.id,
210
- })
95
+ });
211
96
 
212
97
  // Capture initial IDs
213
- const id1 = fieldArray.fields.value[0].id
214
- const id2 = fieldArray.fields.value[1].id
215
- const id3 = fieldArray.fields.value[2].id
98
+ const id1 = fieldArray.items.value[0].id;
99
+ const id2 = fieldArray.items.value[1].id;
100
+ const id3 = fieldArray.items.value[2].id;
216
101
 
217
102
  // Reverse the array
218
- const arrayField = form.getField('items')
219
- arrayField.setData([...arrayField.data.value].reverse())
220
- await nextTick()
103
+ const arrayField = form.getField("items");
104
+ arrayField.setData([...arrayField.data.value].reverse());
105
+ await nextTick();
221
106
 
222
107
  // Verify order changed but IDs followed the items
223
- expect(fieldArray.fields.value[0].item.name).toBe('Third')
224
- expect(fieldArray.fields.value[0].id).toBe(id3)
225
- expect(fieldArray.fields.value[1].item.name).toBe('Second')
226
- expect(fieldArray.fields.value[1].id).toBe(id2)
227
- expect(fieldArray.fields.value[2].item.name).toBe('First')
228
- expect(fieldArray.fields.value[2].id).toBe(id1)
229
- })
230
-
231
- it('should maintain IDs based on custom hash function', async () => {
108
+ expect(fieldArray.items.value[0].item.name).toBe("Third");
109
+ expect(fieldArray.items.value[0].id).toBe(id3);
110
+ expect(fieldArray.items.value[0].path).toBe("items.0");
111
+ expect(fieldArray.items.value[1].item.name).toBe("Second");
112
+ expect(fieldArray.items.value[1].id).toBe(id2);
113
+ expect(fieldArray.items.value[1].path).toBe("items.1");
114
+ expect(fieldArray.items.value[2].item.name).toBe("First");
115
+ expect(fieldArray.items.value[2].id).toBe(id1);
116
+ expect(fieldArray.items.value[2].path).toBe("items.2");
117
+ });
118
+
119
+ it("should maintain IDs based on custom hash function", async () => {
232
120
  const form = useForm({
233
121
  initialData: {
234
122
  products: [
235
123
  {
236
- sku: 'ABC',
237
- name: 'Widget',
124
+ sku: "ABC",
125
+ name: "Widget",
238
126
  price: 10,
239
127
  },
240
128
  {
241
- sku: 'DEF',
242
- name: 'Gadget',
129
+ sku: "DEF",
130
+ name: "Gadget",
243
131
  price: 20,
244
132
  },
245
133
  ],
246
134
  },
247
- })
248
- const fieldArray = useFieldArray(form, 'products', {
135
+ });
136
+ const fieldArray = useFieldArray(form, "products", {
249
137
  hashFn: (item: { sku: string }) => item.sku,
250
- })
138
+ });
251
139
 
252
- const idABC = fieldArray.fields.value[0].id
253
- const idDEF = fieldArray.fields.value[1].id
140
+ const idABC = fieldArray.items.value[0].id;
141
+ const idDEF = fieldArray.items.value[1].id;
254
142
 
255
143
  // Update price and swap order
256
- const arrayField = form.getField('products')
144
+ const arrayField = form.getField("products");
257
145
  arrayField.setData([
258
146
  {
259
- sku: 'DEF',
260
- name: 'Gadget',
147
+ sku: "DEF",
148
+ name: "Gadget",
261
149
  price: 25,
262
150
  },
263
151
  {
264
- sku: 'ABC',
265
- name: 'Widget',
152
+ sku: "ABC",
153
+ name: "Widget",
266
154
  price: 15,
267
155
  },
268
- ])
269
- await nextTick()
156
+ ]);
157
+ await nextTick();
270
158
 
271
159
  // IDs should persist because SKUs didn't change
272
- expect(fieldArray.fields.value[0].id).toBe(idDEF)
273
- expect(fieldArray.fields.value[1].id).toBe(idABC)
160
+ expect(fieldArray.items.value[0].id).toBe(idDEF);
161
+ expect(fieldArray.items.value[1].id).toBe(idABC);
274
162
 
275
163
  // Change SKU - should get new ID
276
164
  arrayField.setData([
277
165
  {
278
- sku: 'XYZ',
279
- name: 'Widget',
166
+ sku: "XYZ",
167
+ name: "Widget",
280
168
  price: 15,
281
169
  },
282
170
  {
283
- sku: 'DEF',
284
- name: 'Gadget',
171
+ sku: "DEF",
172
+ name: "Gadget",
285
173
  price: 25,
286
174
  },
287
- ])
288
- await nextTick()
175
+ ]);
176
+ await nextTick();
289
177
 
290
- expect(fieldArray.fields.value[0].id).not.toBe(idABC)
291
- expect(fieldArray.fields.value[1].id).toBe(idDEF)
292
- })
178
+ expect(fieldArray.items.value[0].id).not.toBe(idABC);
179
+ expect(fieldArray.items.value[1].id).toBe(idDEF);
180
+ });
293
181
 
294
- it('should assign IDs in order for items with same hash', async () => {
182
+ it("should assign IDs in order for items with same hash", async () => {
295
183
  // When multiple items share the same hash, IDs are assigned in order
296
184
  // from the stored ID list. This means reordering items with same hash
297
185
  // will NOT preserve ID-to-item mapping (IDs follow position, not identity).
@@ -299,83 +187,164 @@ describe('useFieldArray', () => {
299
187
  initialData: {
300
188
  items: [
301
189
  {
302
- type: 'tag',
303
- value: 'vue',
190
+ type: "tag",
191
+ value: "vue",
304
192
  },
305
193
  {
306
- type: 'tag',
307
- value: 'react',
194
+ type: "tag",
195
+ value: "react",
308
196
  },
309
197
  {
310
- type: 'tag',
311
- value: 'svelte',
198
+ type: "tag",
199
+ value: "svelte",
312
200
  },
313
201
  ],
314
202
  },
315
- })
203
+ });
316
204
  // Hash by type only - all items have same hash 'tag'
317
- const fieldArray = useFieldArray(form, 'items', {
205
+ const fieldArray = useFieldArray(form, "items", {
318
206
  hashFn: (item: { type: string }) => item.type,
319
- })
207
+ });
320
208
 
321
209
  // All items get unique IDs despite same hash
322
- const [id1, id2, id3] = fieldArray.fields.value.map(f => f.id)
323
- expect(new Set([id1, id2, id3]).size).toBe(3)
210
+ const [id1, id2, id3] = fieldArray.items.value.map((f) => f.id);
211
+ expect(new Set([id1, id2, id3]).size).toBe(3);
324
212
 
325
213
  // Reorder: move last item to first position
326
- const arrayField = form.getField('items')
214
+ const arrayField = form.getField("items");
327
215
  arrayField.setData([
328
216
  {
329
- type: 'tag',
330
- value: 'svelte',
217
+ type: "tag",
218
+ value: "svelte",
331
219
  },
332
220
  {
333
- type: 'tag',
334
- value: 'vue',
221
+ type: "tag",
222
+ value: "vue",
335
223
  },
336
224
  {
337
- type: 'tag',
338
- value: 'react',
225
+ type: "tag",
226
+ value: "react",
339
227
  },
340
- ])
341
- await nextTick()
228
+ ]);
229
+ await nextTick();
342
230
 
343
231
  // IDs are assigned in order from stored list, NOT following items
344
232
  // This is expected behavior with hash collisions
345
- expect(fieldArray.fields.value[0].id).toBe(id1) // svelte gets id1 (was vue's)
346
- expect(fieldArray.fields.value[1].id).toBe(id2) // vue gets id2 (was react's)
347
- expect(fieldArray.fields.value[2].id).toBe(id3) // react gets id3 (was svelte's)
233
+ expect(fieldArray.items.value[0].id).toBe(id1); // svelte gets id1 (was vue's)
234
+ expect(fieldArray.items.value[1].id).toBe(id2); // vue gets id2 (was react's)
235
+ expect(fieldArray.items.value[2].id).toBe(id3); // react gets id3 (was svelte's)
348
236
 
349
237
  // The values confirm the reorder happened
350
- expect(fieldArray.fields.value[0].item.value).toBe('svelte')
351
- expect(fieldArray.fields.value[1].item.value).toBe('vue')
352
- expect(fieldArray.fields.value[2].item.value).toBe('react')
353
- })
354
- })
355
-
356
- describe('Form Integration', () => {
357
- it('should track dirty state correctly', async () => {
238
+ expect(fieldArray.items.value[0].item.value).toBe("svelte");
239
+ expect(fieldArray.items.value[1].item.value).toBe("vue");
240
+ expect(fieldArray.items.value[2].item.value).toBe("react");
241
+ });
242
+
243
+ it("should push an item to the array", async () => {
358
244
  const form = useForm({
359
- initialData: { items: [{ name: 'Item' }] },
360
- })
361
- const fieldArray = useFieldArray(form, 'items')
245
+ initialData: {
246
+ items: [
247
+ {
248
+ id: 1,
249
+ name: "First",
250
+ },
251
+ ],
252
+ },
253
+ });
254
+ const fieldArray = useFieldArray(form, "items", {
255
+ hashFn: (item: { id: number }) => item.id,
256
+ });
362
257
 
363
- expect(form.isDirty.value).toBe(false)
364
- expect(fieldArray.dirty.value).toBe(false)
258
+ // Capture initial IDs
259
+ const id1 = fieldArray.items.value[0].id;
365
260
 
366
- // Make a change
367
- fieldArray.push({ name: 'New Item' })
368
- await nextTick()
261
+ // Push a new item
262
+ const newItem = fieldArray.push({
263
+ id: 2,
264
+ name: "Second",
265
+ });
266
+
267
+ expect(newItem.item.name).toBe("Second");
268
+ expect(newItem.path).toBe("items.1");
269
+ expect(newItem.id).toBeDefined();
369
270
 
370
- expect(form.isDirty.value).toBe(true)
371
- expect(fieldArray.dirty.value).toBe(true)
271
+ await nextTick();
372
272
 
373
- // Reset form
374
- form.reset()
375
- await nextTick()
273
+ // Verify existing item's ID is unchanged
274
+ expect(fieldArray.items.value[0].id).toBe(id1);
275
+ expect(fieldArray.items.value[1].id).toBe(newItem.id);
276
+ });
376
277
 
377
- expect(form.isDirty.value).toBe(false)
378
- expect(fieldArray.dirty.value).toBe(false)
379
- })
380
- })
381
- })
278
+ it("should remove an item from the array", async () => {
279
+ const form = useForm({
280
+ initialData: {
281
+ items: [
282
+ {
283
+ id: 1,
284
+ name: "First",
285
+ },
286
+ {
287
+ id: 2,
288
+ name: "Second",
289
+ },
290
+ ],
291
+ },
292
+ });
293
+ const fieldArray = useFieldArray(form, "items", {
294
+ hashFn: (item: { id: number }) => item.id,
295
+ });
296
+
297
+ // Capture initial IDs
298
+ const id1 = fieldArray.items.value[0].id;
299
+
300
+ // Remove an item
301
+ fieldArray.remove(id1);
302
+
303
+ await nextTick();
304
+
305
+ // Verify the correct item was removed
306
+ expect(fieldArray.items.value.length).toBe(1);
307
+ expect(fieldArray.items.value[0].item.name).toBe("Second");
308
+ });
309
+
310
+ it("should insert an item into the array", async () => {
311
+ const form = useForm({
312
+ initialData: {
313
+ items: [
314
+ {
315
+ id: 1,
316
+ name: "First",
317
+ },
318
+ {
319
+ id: 3,
320
+ name: "Third",
321
+ },
322
+ ],
323
+ },
324
+ });
325
+ const fieldArray = useFieldArray(form, "items", {
326
+ hashFn: (item: { id: number }) => item.id,
327
+ });
328
+
329
+ // Capture initial IDs
330
+ const id1 = fieldArray.items.value[0].id;
331
+ const id3 = fieldArray.items.value[1].id;
332
+
333
+ // Insert an item
334
+ const insertedItem = fieldArray.insert({ id: 2, name: "Second" }, 1);
335
+ expect(insertedItem.item.name).toBe("Second");
336
+ expect(insertedItem.path).toBe("items.1");
337
+ expect(insertedItem.id).toBeDefined();
338
+
339
+ await nextTick();
340
+
341
+ // Verify the item was inserted correctly
342
+ expect(fieldArray.items.value.length).toBe(3);
343
+ expect(fieldArray.items.value[0].id).toBe(id1);
344
+ expect(fieldArray.items.value[0].item.name).toBe("First");
345
+ expect(fieldArray.items.value[1].item.name).toBe("Second");
346
+ expect(fieldArray.items.value[2].id).toBe(id3);
347
+ expect(fieldArray.items.value[2].item.name).toBe("Third");
348
+ });
349
+ });
350
+ });