oxform-core 0.1.0
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/export/index.ts +7 -0
- package/export/schema.ts +1 -0
- package/package.json +40 -0
- package/readme.md +13 -0
- package/src/field-api.constants.ts +15 -0
- package/src/field-api.ts +139 -0
- package/src/form-api.ts +84 -0
- package/src/form-api.types.ts +148 -0
- package/src/form-array-field-api.ts +233 -0
- package/src/form-context-api.ts +232 -0
- package/src/form-field-api.ts +174 -0
- package/src/more-types.ts +178 -0
- package/src/tests/array/append.spec.ts +138 -0
- package/src/tests/array/insert.spec.ts +182 -0
- package/src/tests/array/move.spec.ts +175 -0
- package/src/tests/array/prepend.spec.ts +138 -0
- package/src/tests/array/remove.spec.ts +174 -0
- package/src/tests/array/swap.spec.ts +152 -0
- package/src/tests/array/update.spec.ts +148 -0
- package/src/tests/field/change.spec.ts +226 -0
- package/src/tests/field/reset.spec.ts +617 -0
- package/src/tests/field/set-errors.spec.ts +254 -0
- package/src/tests/field-api/field-api.spec.ts +341 -0
- package/src/tests/form-api/reset.spec.ts +535 -0
- package/src/tests/form-api/submit.spec.ts +409 -0
- package/src/types.ts +5 -0
- package/src/utils/get.ts +5 -0
- package/src/utils/testing/sleep.ts +1 -0
- package/src/utils/testing/tests.ts +18 -0
- package/src/utils/update.ts +6 -0
- package/src/utils/validate.ts +8 -0
- package/tsconfig.json +3 -0
- package/tsdown.config.ts +10 -0
- package/vitest.config.ts +3 -0
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { FormApi } from '#form-api';
|
|
2
|
+
import type { FormOptions } from '#form-api.types';
|
|
3
|
+
import { sleep } from '#utils/testing/sleep';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
import z from 'zod';
|
|
6
|
+
|
|
7
|
+
const schema = z.object({
|
|
8
|
+
array: z.string().array(),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const defaultValues = {
|
|
12
|
+
array: ['item1', 'item2', 'item3'],
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const setup = (options?: Partial<FormOptions<typeof schema>>) => {
|
|
16
|
+
const form = new FormApi({
|
|
17
|
+
schema,
|
|
18
|
+
defaultValues,
|
|
19
|
+
...options,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
form['~mount']();
|
|
23
|
+
|
|
24
|
+
return { form };
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
describe('value updates', () => {
|
|
28
|
+
it('should remove an item from an array', () => {
|
|
29
|
+
const { form } = setup();
|
|
30
|
+
|
|
31
|
+
form.array.remove('array', 1);
|
|
32
|
+
|
|
33
|
+
expect(form.field.get('array')).toStrictEqual(['item1', 'item3']);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('removes the first item if the index is 0', () => {
|
|
37
|
+
const { form } = setup();
|
|
38
|
+
|
|
39
|
+
form.array.remove('array', 0);
|
|
40
|
+
|
|
41
|
+
expect(form.field.get('array')).toStrictEqual(['item2', 'item3']);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('removes the last item if the index is equal to the length', () => {
|
|
45
|
+
const { form } = setup();
|
|
46
|
+
|
|
47
|
+
form.array.remove('array', 2);
|
|
48
|
+
|
|
49
|
+
expect(form.field.get('array')).toStrictEqual(['item1', 'item2']);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should set the array to empty if it is undefined', () => {
|
|
53
|
+
const { form } = setup();
|
|
54
|
+
|
|
55
|
+
form.field.change('array', undefined as any);
|
|
56
|
+
form.array.remove('array', 0);
|
|
57
|
+
|
|
58
|
+
expect(form.field.get('array')).toStrictEqual([]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should set the array to empty if it is null', () => {
|
|
62
|
+
const { form } = setup();
|
|
63
|
+
|
|
64
|
+
form.field.change('array', null as any);
|
|
65
|
+
form.array.remove('array', 0);
|
|
66
|
+
|
|
67
|
+
expect(form.field.get('array')).toStrictEqual([]);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
describe('meta handling', () => {
|
|
72
|
+
it('should update the array field meta when removing', () => {
|
|
73
|
+
const { form } = setup();
|
|
74
|
+
|
|
75
|
+
form.array.remove('array', 1);
|
|
76
|
+
|
|
77
|
+
expect(form.field.meta('array').dirty).toBe(true);
|
|
78
|
+
expect(form.field.meta('array').touched).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should not update the array field meta when removing with should.dirty false', () => {
|
|
82
|
+
const { form } = setup();
|
|
83
|
+
|
|
84
|
+
form.array.remove('array', 1, { should: { dirty: false } });
|
|
85
|
+
|
|
86
|
+
expect(form.field.meta('array').dirty).toBe(false);
|
|
87
|
+
expect(form.field.meta('array').touched).toBe(true);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should not update the array field meta when removing with should.touch false', () => {
|
|
91
|
+
const { form } = setup();
|
|
92
|
+
|
|
93
|
+
form.array.remove('array', 1, { should: { touch: false } });
|
|
94
|
+
|
|
95
|
+
expect(form.field.meta('array').dirty).toBe(true);
|
|
96
|
+
expect(form.field.meta('array').touched).toBe(false);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should update the rest of the items meta when removing at the start', () => {
|
|
100
|
+
const { form } = setup();
|
|
101
|
+
|
|
102
|
+
form.field.change('array.1', 'updated', { should: { touch: false } });
|
|
103
|
+
form.field.change('array.2', 'updated', { should: { dirty: false } });
|
|
104
|
+
|
|
105
|
+
form.array.remove('array', 0);
|
|
106
|
+
|
|
107
|
+
expect(form.field.meta('array.0').dirty).toBe(true);
|
|
108
|
+
expect(form.field.meta('array.0').touched).toBe(false);
|
|
109
|
+
|
|
110
|
+
expect(form.field.meta('array.1').dirty).toBe(false);
|
|
111
|
+
expect(form.field.meta('array.1').touched).toBe(true);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should update the rest of the items meta when removing at the end', () => {
|
|
115
|
+
const { form } = setup();
|
|
116
|
+
|
|
117
|
+
form.field.change('array.0', 'updated', { should: { touch: false } });
|
|
118
|
+
form.field.change('array.1', 'updated', { should: { dirty: false } });
|
|
119
|
+
|
|
120
|
+
form.array.remove('array', 2);
|
|
121
|
+
|
|
122
|
+
expect(form.field.meta('array.0').dirty).toBe(true);
|
|
123
|
+
expect(form.field.meta('array.0').touched).toBe(false);
|
|
124
|
+
|
|
125
|
+
expect(form.field.meta('array.1').dirty).toBe(false);
|
|
126
|
+
expect(form.field.meta('array.1').touched).toBe(true);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should update the rest of the items meta when removing in the middle', () => {
|
|
130
|
+
const { form } = setup();
|
|
131
|
+
|
|
132
|
+
form.field.change('array.0', 'updated', { should: { touch: false } });
|
|
133
|
+
form.field.change('array.2', 'updated', { should: { dirty: false } });
|
|
134
|
+
form.array.remove('array', 1);
|
|
135
|
+
|
|
136
|
+
expect(form.field.meta('array.0').dirty).toBe(true);
|
|
137
|
+
expect(form.field.meta('array.0').touched).toBe(false);
|
|
138
|
+
|
|
139
|
+
expect(form.field.meta('array.1').dirty).toBe(false);
|
|
140
|
+
expect(form.field.meta('array.1').touched).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe('edge cases', () => {
|
|
145
|
+
it('should handle negative index', () => {
|
|
146
|
+
const { form } = setup();
|
|
147
|
+
|
|
148
|
+
form.array.remove('array', -100);
|
|
149
|
+
|
|
150
|
+
expect(form.field.get('array')).toEqual(['item2', 'item3']);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should handle an index greater than the length', () => {
|
|
154
|
+
const { form } = setup();
|
|
155
|
+
|
|
156
|
+
form.array.remove('array', 100);
|
|
157
|
+
|
|
158
|
+
expect(form.field.get('array')).toEqual(['item1', 'item2']);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe('validation', () => {
|
|
163
|
+
it('should validate on remove by default', async () => {
|
|
164
|
+
const { form } = setup({ validate: { change: schema } });
|
|
165
|
+
|
|
166
|
+
form.field.change('array.0', 0 as any);
|
|
167
|
+
form.array.remove('array', 0);
|
|
168
|
+
|
|
169
|
+
await sleep(0);
|
|
170
|
+
|
|
171
|
+
expect(form.field.meta('array.0').valid).toBe(true);
|
|
172
|
+
expect(form.field.errors('array.0')).toHaveLength(0);
|
|
173
|
+
});
|
|
174
|
+
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { FormApi } from '#form-api';
|
|
2
|
+
import type { FormOptions } from '#form-api.types';
|
|
3
|
+
import { sleep } from '#utils/testing/sleep';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
import z from 'zod';
|
|
6
|
+
|
|
7
|
+
const schema = z.object({
|
|
8
|
+
array: z.string().array(),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const defaultValues = {
|
|
12
|
+
array: ['item1', 'item2'],
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const setup = (options?: Partial<FormOptions<typeof schema>>) => {
|
|
16
|
+
const form = new FormApi({
|
|
17
|
+
schema,
|
|
18
|
+
defaultValues,
|
|
19
|
+
...options,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
form['~mount']();
|
|
23
|
+
|
|
24
|
+
return { form };
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
describe('value updates', () => {
|
|
28
|
+
it('should swap two items in an array', () => {
|
|
29
|
+
const { form } = setup();
|
|
30
|
+
|
|
31
|
+
form.array.swap('array', 0, 1);
|
|
32
|
+
|
|
33
|
+
expect(form.field.get('array')).toStrictEqual(['item2', 'item1']);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should swap two items when one is not in the array', () => {
|
|
37
|
+
const { form } = setup();
|
|
38
|
+
|
|
39
|
+
form.array.swap('array', 0, 2);
|
|
40
|
+
|
|
41
|
+
expect(form.field.get('array')).toStrictEqual([undefined, 'item2', 'item1']);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should swap two items when both are not in the array', () => {
|
|
45
|
+
const { form } = setup();
|
|
46
|
+
|
|
47
|
+
form.array.swap('array', 2, 3);
|
|
48
|
+
|
|
49
|
+
expect(form.field.get('array')).toStrictEqual(['item1', 'item2', undefined, undefined]);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('meta handling', () => {
|
|
54
|
+
it('should update the array field meta when swapping', () => {
|
|
55
|
+
const { form } = setup();
|
|
56
|
+
|
|
57
|
+
form.array.swap('array', 0, 1);
|
|
58
|
+
|
|
59
|
+
expect(form.field.meta('array').dirty).toBe(true);
|
|
60
|
+
expect(form.field.meta('array').touched).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should not update the array field meta when swapping with should.dirty false', () => {
|
|
64
|
+
const { form } = setup();
|
|
65
|
+
|
|
66
|
+
form.array.swap('array', 0, 1, { should: { dirty: false } });
|
|
67
|
+
|
|
68
|
+
expect(form.field.meta('array').dirty).toBe(false);
|
|
69
|
+
expect(form.field.meta('array').touched).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should not update the array field meta when swapping with should.touch false', () => {
|
|
73
|
+
const { form } = setup();
|
|
74
|
+
|
|
75
|
+
form.array.swap('array', 0, 1, { should: { touch: false } });
|
|
76
|
+
|
|
77
|
+
expect(form.field.meta('array').dirty).toBe(true);
|
|
78
|
+
expect(form.field.meta('array').touched).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should update the swapped item meta when swapping', () => {
|
|
82
|
+
const { form } = setup();
|
|
83
|
+
|
|
84
|
+
form.field.change('array.0', 'updated', { should: { touch: false } });
|
|
85
|
+
form.field.change('array.1', 'updated', { should: { dirty: false } });
|
|
86
|
+
form.array.swap('array', 0, 1);
|
|
87
|
+
|
|
88
|
+
expect(form.field.meta('array.0').dirty).toBe(false);
|
|
89
|
+
expect(form.field.meta('array.0').touched).toBe(true);
|
|
90
|
+
|
|
91
|
+
expect(form.field.meta('array.1').dirty).toBe(true);
|
|
92
|
+
expect(form.field.meta('array.1').touched).toBe(false);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('should leave the rest of the items untouched when swapping', () => {
|
|
96
|
+
const { form } = setup();
|
|
97
|
+
|
|
98
|
+
form.field.change('array', ['item1', 'item2', 'item3', 'item4']);
|
|
99
|
+
form.array.swap('array', 0, 2);
|
|
100
|
+
|
|
101
|
+
expect(form.field.meta('array.1').dirty).toEqual(false);
|
|
102
|
+
expect(form.field.meta('array.3').dirty).toEqual(false);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe('edge cases', () => {
|
|
107
|
+
it('should handle negative index', () => {
|
|
108
|
+
const { form } = setup();
|
|
109
|
+
|
|
110
|
+
form.array.swap('array', -100, 1);
|
|
111
|
+
|
|
112
|
+
expect(form.field.get('array')).toEqual(['item2', 'item1']);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it('should handle swapping with the same index', () => {
|
|
116
|
+
const { form } = setup();
|
|
117
|
+
|
|
118
|
+
form.array.swap('array', 0, 0);
|
|
119
|
+
|
|
120
|
+
expect(form.field.get('array')).toEqual(['item1', 'item2']);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('validation', () => {
|
|
125
|
+
it('should validate on swap by default', async () => {
|
|
126
|
+
const { form } = setup({ validate: { change: schema } });
|
|
127
|
+
|
|
128
|
+
form.field.change('array.0', 0 as any, { should: { validate: false } });
|
|
129
|
+
form.array.swap('array', 0, 1);
|
|
130
|
+
|
|
131
|
+
await sleep(0);
|
|
132
|
+
|
|
133
|
+
expect(form.field.meta('array.1').valid).toBe(false);
|
|
134
|
+
expect(form.field.errors('array.1')).toHaveLength(1);
|
|
135
|
+
|
|
136
|
+
expect(form.field.meta('array.0').valid).toBe(true);
|
|
137
|
+
expect(form.field.errors('array.0')).toHaveLength(0);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should not validate on swap by default when should.validate is false', async () => {
|
|
141
|
+
const { form } = setup({ validate: { change: schema } });
|
|
142
|
+
|
|
143
|
+
form.field.change('array.0', 0 as any, { should: { validate: false } });
|
|
144
|
+
form.array.swap('array', 0, 1, { should: { validate: false } });
|
|
145
|
+
|
|
146
|
+
expect(form.field.meta('array.1').valid).toBe(true);
|
|
147
|
+
expect(form.field.errors('array.1')).toHaveLength(0);
|
|
148
|
+
|
|
149
|
+
expect(form.field.meta('array.0').valid).toBe(true);
|
|
150
|
+
expect(form.field.errors('array.0')).toHaveLength(0);
|
|
151
|
+
});
|
|
152
|
+
});
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { FormApi } from '#form-api';
|
|
2
|
+
import type { FormOptions } from '#form-api.types';
|
|
3
|
+
import { sleep } from '#utils/testing/sleep';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
import z from 'zod';
|
|
6
|
+
|
|
7
|
+
const schema = z.object({
|
|
8
|
+
array: z.string().array(),
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const defaultValues = {
|
|
12
|
+
array: ['item1', 'item2', 'item3'],
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const setup = (options?: Partial<FormOptions<typeof schema>>) => {
|
|
16
|
+
const form = new FormApi({
|
|
17
|
+
schema,
|
|
18
|
+
defaultValues,
|
|
19
|
+
...options,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
form['~mount']();
|
|
23
|
+
|
|
24
|
+
return { form };
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
describe('value updates', () => {
|
|
28
|
+
it('should update an item in an array', () => {
|
|
29
|
+
const { form } = setup();
|
|
30
|
+
|
|
31
|
+
form.array.update('array', 1, 'updated');
|
|
32
|
+
|
|
33
|
+
expect(form.field.get('array')).toStrictEqual(['item1', 'updated', 'item3']);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should update an item if the array is undefined', () => {
|
|
37
|
+
const { form } = setup();
|
|
38
|
+
|
|
39
|
+
form.field.change('array', undefined as any);
|
|
40
|
+
form.array.update('array', 0, 'updated');
|
|
41
|
+
|
|
42
|
+
expect(form.field.get('array')).toStrictEqual(['updated']);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('should update an item if the array is null', () => {
|
|
46
|
+
const { form } = setup();
|
|
47
|
+
|
|
48
|
+
form.field.change('array', null as any);
|
|
49
|
+
form.array.update('array', 0, 'updated');
|
|
50
|
+
|
|
51
|
+
expect(form.field.get('array')).toStrictEqual(['updated']);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should update an item if the array is empty', () => {
|
|
55
|
+
const { form } = setup();
|
|
56
|
+
|
|
57
|
+
form.field.change('array', []);
|
|
58
|
+
form.array.update('array', 0, 'updated');
|
|
59
|
+
|
|
60
|
+
expect(form.field.get('array')).toStrictEqual(['updated']);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should allow passing an updater function', () => {
|
|
64
|
+
const { form } = setup();
|
|
65
|
+
|
|
66
|
+
form.array.update('array', 0, value => value.length + ' updated');
|
|
67
|
+
|
|
68
|
+
expect(form.field.get('array')).toStrictEqual(['3 updated', 'item2', 'item3']);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('meta handling', () => {
|
|
73
|
+
it('should update the array field meta when updating', () => {
|
|
74
|
+
const { form } = setup();
|
|
75
|
+
|
|
76
|
+
form.array.update('array', 1, 'updated');
|
|
77
|
+
|
|
78
|
+
expect(form.field.meta('array').dirty).toBe(true);
|
|
79
|
+
expect(form.field.meta('array').touched).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should not update the array field meta when updating with should.dirty false', () => {
|
|
83
|
+
const { form } = setup();
|
|
84
|
+
|
|
85
|
+
form.array.update('array', 1, 'updated', { should: { dirty: false } });
|
|
86
|
+
|
|
87
|
+
expect(form.field.meta('array').dirty).toBe(false);
|
|
88
|
+
expect(form.field.meta('array').touched).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('should not update the array field meta when updating with should.touch false', () => {
|
|
92
|
+
const { form } = setup();
|
|
93
|
+
|
|
94
|
+
form.array.update('array', 1, 'updated', { should: { touch: false } });
|
|
95
|
+
|
|
96
|
+
expect(form.field.meta('array').dirty).toBe(true);
|
|
97
|
+
expect(form.field.meta('array').touched).toBe(false);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should update the updated item meta when updating', () => {
|
|
101
|
+
const { form } = setup();
|
|
102
|
+
|
|
103
|
+
form.array.update('array', 0, 'updated');
|
|
104
|
+
|
|
105
|
+
expect(form.field.meta('array.0').dirty).toBe(true);
|
|
106
|
+
expect(form.field.meta('array.0').touched).toBe(true);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('edge cases', () => {
|
|
111
|
+
it('should handle negative index', () => {
|
|
112
|
+
const { form } = setup();
|
|
113
|
+
|
|
114
|
+
form.array.update('array', -100, 'updated');
|
|
115
|
+
|
|
116
|
+
expect(form.field.get('array')).toEqual(['updated', 'item2', 'item3']);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should handle updating an index out of bounds', () => {
|
|
120
|
+
const { form } = setup();
|
|
121
|
+
|
|
122
|
+
form.array.update('array', 5, 'updated');
|
|
123
|
+
|
|
124
|
+
expect(form.field.get('array')).toEqual(['item1', 'item2', 'updated']);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe('validation', () => {
|
|
129
|
+
it('should validate on update by default', async () => {
|
|
130
|
+
const { form } = setup({ validate: { change: schema } });
|
|
131
|
+
|
|
132
|
+
form.array.update('array', 0, 0 as any);
|
|
133
|
+
|
|
134
|
+
await sleep(0);
|
|
135
|
+
|
|
136
|
+
expect(form.field.meta('array.0').valid).toBe(false);
|
|
137
|
+
expect(form.field.errors('array.0')).toHaveLength(1);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('should not validate on update by default when should.validate is false', async () => {
|
|
141
|
+
const { form } = setup({ validate: { change: schema } });
|
|
142
|
+
|
|
143
|
+
form.array.update('array', 0, 0 as any, { should: { validate: false } });
|
|
144
|
+
|
|
145
|
+
expect(form.field.meta('array.0').valid).toBe(true);
|
|
146
|
+
expect(form.field.errors('array.0')).toHaveLength(0);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { FormApi } from '#form-api';
|
|
2
|
+
import type { FormOptions } from '#form-api.types';
|
|
3
|
+
import { sleep } from '#utils/testing/sleep';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
import z from 'zod';
|
|
6
|
+
|
|
7
|
+
const schema = z.object({
|
|
8
|
+
string: z.string(),
|
|
9
|
+
optional: z.string().optional(),
|
|
10
|
+
nullable: z.string().nullable(),
|
|
11
|
+
nullish: z.string().nullish(),
|
|
12
|
+
number: z.number(),
|
|
13
|
+
boolean: z.boolean(),
|
|
14
|
+
array: z.string().array(),
|
|
15
|
+
object: z.object({
|
|
16
|
+
nested: z.object({
|
|
17
|
+
deep: z.string(),
|
|
18
|
+
}),
|
|
19
|
+
}),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const defaultValues = {
|
|
23
|
+
string: 'string',
|
|
24
|
+
optional: undefined,
|
|
25
|
+
nullable: null,
|
|
26
|
+
nullish: null,
|
|
27
|
+
number: 1,
|
|
28
|
+
boolean: true,
|
|
29
|
+
array: ['array'],
|
|
30
|
+
object: {
|
|
31
|
+
nested: {
|
|
32
|
+
deep: 'deep',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const setup = (options?: Partial<FormOptions<typeof schema>>) => {
|
|
38
|
+
const form = new FormApi({
|
|
39
|
+
schema,
|
|
40
|
+
defaultValues,
|
|
41
|
+
...options,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
form['~mount']();
|
|
45
|
+
|
|
46
|
+
return { form };
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
describe('value updates', () => {
|
|
50
|
+
it('should update string field value', () => {
|
|
51
|
+
const { form } = setup();
|
|
52
|
+
|
|
53
|
+
form.field.change('string', 'updated');
|
|
54
|
+
|
|
55
|
+
expect(form.field.get('string')).toBe('updated');
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should update optional field value', () => {
|
|
59
|
+
const { form } = setup();
|
|
60
|
+
|
|
61
|
+
form.field.change('optional', 'updated');
|
|
62
|
+
|
|
63
|
+
expect(form.field.get('optional')).toBe('updated');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should update nullable field value', () => {
|
|
67
|
+
const { form } = setup();
|
|
68
|
+
|
|
69
|
+
form.field.change('nullable', 'updated');
|
|
70
|
+
|
|
71
|
+
expect(form.field.get('nullable')).toBe('updated');
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should update nullish field value', () => {
|
|
75
|
+
const { form } = setup();
|
|
76
|
+
|
|
77
|
+
form.field.change('nullish', 'updated');
|
|
78
|
+
|
|
79
|
+
expect(form.field.get('nullish')).toBe('updated');
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should update number field value', () => {
|
|
83
|
+
const { form } = setup();
|
|
84
|
+
|
|
85
|
+
form.field.change('number', 2);
|
|
86
|
+
|
|
87
|
+
expect(form.field.get('number')).toBe(2);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should update boolean field value', () => {
|
|
91
|
+
const { form } = setup();
|
|
92
|
+
|
|
93
|
+
form.field.change('boolean', false);
|
|
94
|
+
|
|
95
|
+
expect(form.field.get('boolean')).toBe(false);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('should update array field value', () => {
|
|
99
|
+
const { form } = setup();
|
|
100
|
+
|
|
101
|
+
const newArray = ['new1', 'new2'];
|
|
102
|
+
form.field.change('array', newArray);
|
|
103
|
+
|
|
104
|
+
expect(form.field.get('array')).toStrictEqual(newArray);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should update an array index field value', () => {
|
|
108
|
+
const { form } = setup();
|
|
109
|
+
|
|
110
|
+
form.field.change('array.2', 'updated');
|
|
111
|
+
|
|
112
|
+
expect(form.field.get('array.2')).toEqual('updated');
|
|
113
|
+
expect(form.field.get('array')).toEqual(['array', undefined, 'updated']);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should update object field value', () => {
|
|
117
|
+
const { form } = setup();
|
|
118
|
+
|
|
119
|
+
const newObject = {
|
|
120
|
+
nested: {
|
|
121
|
+
deep: 'new',
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
form.field.change('object', newObject);
|
|
125
|
+
|
|
126
|
+
expect(form.field.get('object')).toStrictEqual(newObject);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should update nested field value', () => {
|
|
130
|
+
const { form } = setup();
|
|
131
|
+
|
|
132
|
+
form.field.change('object.nested.deep', 'updated');
|
|
133
|
+
|
|
134
|
+
expect(form.field.get('object.nested.deep')).toBe('updated');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should allow passing an updater function', () => {
|
|
138
|
+
const { form } = setup();
|
|
139
|
+
|
|
140
|
+
form.field.change('string', value => value + ' updated');
|
|
141
|
+
|
|
142
|
+
expect(form.field.get('string')).toBe('string updated');
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe('should update field metadata', () => {
|
|
147
|
+
it('on first change', () => {
|
|
148
|
+
const { form } = setup();
|
|
149
|
+
|
|
150
|
+
form.field.change('string', 'updated');
|
|
151
|
+
|
|
152
|
+
expect(form.field.meta('string').dirty).toBe(true);
|
|
153
|
+
expect(form.field.meta('string').touched).toBe(true);
|
|
154
|
+
expect(form.field.meta('string').blurred).toBe(false);
|
|
155
|
+
expect(form.field.meta('string').pristine).toBe(false);
|
|
156
|
+
expect(form.field.meta('string').default).toBe(false);
|
|
157
|
+
expect(form.field.meta('string').valid).toBe(true);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('on second change', () => {
|
|
161
|
+
const { form } = setup();
|
|
162
|
+
|
|
163
|
+
form.field.change('string', 'updated');
|
|
164
|
+
form.field.change('string', 'updated again');
|
|
165
|
+
|
|
166
|
+
expect(form.field.meta('string').dirty).toBe(true);
|
|
167
|
+
expect(form.field.meta('string').touched).toBe(true);
|
|
168
|
+
expect(form.field.meta('string').blurred).toBe(false);
|
|
169
|
+
expect(form.field.meta('string').pristine).toBe(false);
|
|
170
|
+
expect(form.field.meta('string').default).toBe(false);
|
|
171
|
+
expect(form.field.meta('string').valid).toBe(true);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('should not mark fields as dirty if should.dirty is false', () => {
|
|
175
|
+
const { form } = setup();
|
|
176
|
+
|
|
177
|
+
form.field.change('string', 'updated', { should: { dirty: false } });
|
|
178
|
+
|
|
179
|
+
expect(form.field.meta('string').dirty).toBe(false);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should not mark fields as touched if should.touch is false', () => {
|
|
183
|
+
const { form } = setup();
|
|
184
|
+
|
|
185
|
+
form.field.change('string', 'updated', { should: { touch: false } });
|
|
186
|
+
|
|
187
|
+
expect(form.field.meta('string').touched).toBe(false);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe('if form.options.validate.change is provided ', () => {
|
|
192
|
+
it('should validate on update by default', async () => {
|
|
193
|
+
const { form } = setup({ validate: { change: schema } });
|
|
194
|
+
|
|
195
|
+
form.field.change('string', 2 as any);
|
|
196
|
+
|
|
197
|
+
await sleep(0);
|
|
198
|
+
|
|
199
|
+
expect(form.field.get('string')).toBe(2);
|
|
200
|
+
expect(form.field.meta('string').valid).toBe(false);
|
|
201
|
+
expect(form.field.errors('string')).toHaveLength(1);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should skip validation if should.validate is false', async () => {
|
|
205
|
+
const { form } = setup({ validate: { change: schema } });
|
|
206
|
+
|
|
207
|
+
form.field.change('string', 2 as any, { should: { validate: false } });
|
|
208
|
+
|
|
209
|
+
expect(form.field.get('string')).toBe(2);
|
|
210
|
+
expect(form.field.meta('string').valid).toBe(true);
|
|
211
|
+
expect(form.field.errors('string')).toHaveLength(0);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe('if form.options.validate.change is not provided ', () => {
|
|
216
|
+
it('should not validate on update by default', async () => {
|
|
217
|
+
const { form } = setup();
|
|
218
|
+
|
|
219
|
+
form.field.change('string', 2 as any);
|
|
220
|
+
await sleep(0);
|
|
221
|
+
|
|
222
|
+
expect(form.field.get('string')).toBe(2);
|
|
223
|
+
expect(form.field.meta('string').valid).toBe(true);
|
|
224
|
+
expect(form.field.errors('string')).toHaveLength(0);
|
|
225
|
+
});
|
|
226
|
+
});
|