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,254 @@
|
|
|
1
|
+
import { FormApi } from '#form-api';
|
|
2
|
+
import type { FormIssue } from '#form-api.types';
|
|
3
|
+
import { describe, expect, it } from 'vitest';
|
|
4
|
+
import z from 'zod';
|
|
5
|
+
|
|
6
|
+
const schema = z.object({
|
|
7
|
+
name: z.string(),
|
|
8
|
+
email: z.string(),
|
|
9
|
+
nested: z.object({
|
|
10
|
+
value: z.string(),
|
|
11
|
+
}),
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const defaultValues = {
|
|
15
|
+
name: 'John',
|
|
16
|
+
email: 'john@example.com',
|
|
17
|
+
nested: {
|
|
18
|
+
value: 'test',
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const setup = () => {
|
|
23
|
+
const form = new FormApi({
|
|
24
|
+
schema,
|
|
25
|
+
defaultValues,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
form['~mount']();
|
|
29
|
+
return { form };
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const mockError: FormIssue = {
|
|
33
|
+
code: 'custom',
|
|
34
|
+
message: 'Custom error message',
|
|
35
|
+
path: ['name'],
|
|
36
|
+
} as any;
|
|
37
|
+
|
|
38
|
+
const mockError2: FormIssue = {
|
|
39
|
+
code: 'custom_2',
|
|
40
|
+
message: 'Second custom error',
|
|
41
|
+
path: ['name'],
|
|
42
|
+
} as any;
|
|
43
|
+
|
|
44
|
+
describe('replace mode (default)', () => {
|
|
45
|
+
it('should set errors for a field with no existing errors', () => {
|
|
46
|
+
const { form } = setup();
|
|
47
|
+
|
|
48
|
+
form.field.setErrors('name', [mockError]);
|
|
49
|
+
|
|
50
|
+
expect(form.field.errors('name')).toEqual([mockError]);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should replace existing errors by default', () => {
|
|
54
|
+
const { form } = setup();
|
|
55
|
+
|
|
56
|
+
form.field.setErrors('name', [mockError]);
|
|
57
|
+
expect(form.field.errors('name')).toEqual([mockError]);
|
|
58
|
+
|
|
59
|
+
form.field.setErrors('name', [mockError2]);
|
|
60
|
+
expect(form.field.errors('name')).toEqual([mockError2]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should replace existing errors when mode is explicitly set to replace', () => {
|
|
64
|
+
const { form } = setup();
|
|
65
|
+
|
|
66
|
+
form.field.setErrors('name', [mockError]);
|
|
67
|
+
form.field.setErrors('name', [mockError2], { mode: 'replace' });
|
|
68
|
+
|
|
69
|
+
expect(form.field.errors('name')).toEqual([mockError2]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should clear errors when setting empty array', () => {
|
|
73
|
+
const { form } = setup();
|
|
74
|
+
|
|
75
|
+
form.field.setErrors('name', [mockError]);
|
|
76
|
+
expect(form.field.errors('name')).toEqual([mockError]);
|
|
77
|
+
|
|
78
|
+
form.field.setErrors('name', []);
|
|
79
|
+
expect(form.field.errors('name')).toEqual([]);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('append mode', () => {
|
|
84
|
+
it('should append new errors to existing ones', () => {
|
|
85
|
+
const { form } = setup();
|
|
86
|
+
|
|
87
|
+
form.field.setErrors('name', [mockError]);
|
|
88
|
+
form.field.setErrors('name', [mockError2], { mode: 'append' });
|
|
89
|
+
|
|
90
|
+
expect(form.field.errors('name')).toEqual([mockError, mockError2]);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should append to empty error list', () => {
|
|
94
|
+
const { form } = setup();
|
|
95
|
+
|
|
96
|
+
form.field.setErrors('name', [mockError], { mode: 'append' });
|
|
97
|
+
|
|
98
|
+
expect(form.field.errors('name')).toEqual([mockError]);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should append multiple errors at once', () => {
|
|
102
|
+
const { form } = setup();
|
|
103
|
+
|
|
104
|
+
const additionalError: FormIssue = {
|
|
105
|
+
code: 'custom_3',
|
|
106
|
+
message: 'Third custom error',
|
|
107
|
+
path: ['name'],
|
|
108
|
+
} as any;
|
|
109
|
+
|
|
110
|
+
form.field.setErrors('name', [mockError]);
|
|
111
|
+
form.field.setErrors('name', [mockError2, additionalError], { mode: 'append' });
|
|
112
|
+
|
|
113
|
+
expect(form.field.errors('name')).toEqual([mockError, mockError2, additionalError]);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('keep mode', () => {
|
|
118
|
+
it('should keep existing errors when they exist', () => {
|
|
119
|
+
const { form } = setup();
|
|
120
|
+
|
|
121
|
+
form.field.setErrors('name', [mockError]);
|
|
122
|
+
form.field.setErrors('name', [mockError2], { mode: 'keep' });
|
|
123
|
+
|
|
124
|
+
expect(form.field.errors('name')).toEqual([mockError]);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should set new errors when no existing errors', () => {
|
|
128
|
+
const { form } = setup();
|
|
129
|
+
|
|
130
|
+
form.field.setErrors('name', [mockError], { mode: 'keep' });
|
|
131
|
+
|
|
132
|
+
expect(form.field.errors('name')).toEqual([mockError]);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should not change errors when trying to set empty array with existing errors', () => {
|
|
136
|
+
const { form } = setup();
|
|
137
|
+
|
|
138
|
+
form.field.setErrors('name', [mockError]);
|
|
139
|
+
form.field.setErrors('name', [], { mode: 'keep' });
|
|
140
|
+
|
|
141
|
+
expect(form.field.errors('name')).toEqual([mockError]);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
describe('nested fields', () => {
|
|
146
|
+
it('should set errors for nested fields', () => {
|
|
147
|
+
const { form } = setup();
|
|
148
|
+
|
|
149
|
+
const nestedError: FormIssue = {
|
|
150
|
+
code: 'custom',
|
|
151
|
+
message: 'Nested error',
|
|
152
|
+
path: ['nested', 'value'],
|
|
153
|
+
} as any;
|
|
154
|
+
|
|
155
|
+
form.field.setErrors('nested.value', [nestedError]);
|
|
156
|
+
|
|
157
|
+
expect(form.field.errors('nested.value')).toEqual([nestedError]);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should work with all modes for nested fields', () => {
|
|
161
|
+
const { form } = setup();
|
|
162
|
+
|
|
163
|
+
const nestedError1: FormIssue = {
|
|
164
|
+
code: 'custom_1',
|
|
165
|
+
message: 'First nested error',
|
|
166
|
+
path: ['nested', 'value'],
|
|
167
|
+
} as any;
|
|
168
|
+
|
|
169
|
+
const nestedError2: FormIssue = {
|
|
170
|
+
code: 'custom_2',
|
|
171
|
+
message: 'Second nested error',
|
|
172
|
+
path: ['nested', 'value'],
|
|
173
|
+
} as any;
|
|
174
|
+
|
|
175
|
+
form.field.setErrors('nested.value', [nestedError1]);
|
|
176
|
+
form.field.setErrors('nested.value', [nestedError2], { mode: 'append' });
|
|
177
|
+
expect(form.field.errors('nested.value')).toEqual([nestedError1, nestedError2]);
|
|
178
|
+
|
|
179
|
+
form.field.setErrors('nested.value', [{ ...nestedError1, code: 'should_not_appear' } as any], { mode: 'keep' });
|
|
180
|
+
expect(form.field.errors('nested.value')).toEqual([nestedError1, nestedError2]);
|
|
181
|
+
|
|
182
|
+
form.field.setErrors('nested.value', [nestedError1], { mode: 'replace' });
|
|
183
|
+
expect(form.field.errors('nested.value')).toEqual([nestedError1]);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe('multiple fields', () => {
|
|
188
|
+
it('should not affect other fields when setting errors', () => {
|
|
189
|
+
const { form } = setup();
|
|
190
|
+
|
|
191
|
+
const nameError: FormIssue = {
|
|
192
|
+
code: 'custom',
|
|
193
|
+
message: 'Name error',
|
|
194
|
+
path: ['name'],
|
|
195
|
+
} as any;
|
|
196
|
+
|
|
197
|
+
const emailError: FormIssue = {
|
|
198
|
+
code: 'custom',
|
|
199
|
+
message: 'Email error',
|
|
200
|
+
path: ['email'],
|
|
201
|
+
} as any;
|
|
202
|
+
|
|
203
|
+
form.field.setErrors('name', [nameError]);
|
|
204
|
+
form.field.setErrors('email', [emailError]);
|
|
205
|
+
|
|
206
|
+
expect(form.field.errors('name')).toEqual([nameError]);
|
|
207
|
+
expect(form.field.errors('email')).toEqual([emailError]);
|
|
208
|
+
|
|
209
|
+
form.field.setErrors('name', []);
|
|
210
|
+
expect(form.field.errors('name')).toEqual([]);
|
|
211
|
+
expect(form.field.errors('email')).toEqual([emailError]);
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
describe('form status updates', () => {
|
|
216
|
+
it('should update form validity when errors are set', () => {
|
|
217
|
+
const { form } = setup();
|
|
218
|
+
|
|
219
|
+
expect(form.store.state.status.valid).toBe(true);
|
|
220
|
+
|
|
221
|
+
form.field.setErrors('name', [mockError]);
|
|
222
|
+
|
|
223
|
+
expect(form.store.state.status.valid).toBe(false);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('should update form validity when errors are cleared', () => {
|
|
227
|
+
const { form } = setup();
|
|
228
|
+
|
|
229
|
+
form.field.setErrors('name', [mockError]);
|
|
230
|
+
expect(form.store.state.status.valid).toBe(false);
|
|
231
|
+
|
|
232
|
+
form.field.setErrors('name', []);
|
|
233
|
+
expect(form.store.state.status.valid).toBe(true);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('should maintain field meta state when setting errors manually', () => {
|
|
237
|
+
const { form } = setup();
|
|
238
|
+
|
|
239
|
+
form.field.change('name', 'Jane');
|
|
240
|
+
form.field.focus('name');
|
|
241
|
+
form.field.blur('name');
|
|
242
|
+
|
|
243
|
+
const metaBefore = form.field.meta('name');
|
|
244
|
+
|
|
245
|
+
form.field.setErrors('name', [mockError]);
|
|
246
|
+
|
|
247
|
+
const metaAfter = form.field.meta('name');
|
|
248
|
+
|
|
249
|
+
expect(metaAfter.dirty).toBe(metaBefore.dirty);
|
|
250
|
+
expect(metaAfter.touched).toBe(metaBefore.touched);
|
|
251
|
+
expect(metaAfter.blurred).toBe(metaBefore.blurred);
|
|
252
|
+
expect(metaAfter.valid).toBe(false); // This should change due to error
|
|
253
|
+
});
|
|
254
|
+
});
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { FieldApi } from '#field-api';
|
|
2
|
+
import { FormApi } from '#form-api';
|
|
3
|
+
import type { FormIssue } from '#form-api.types';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
import z from 'zod';
|
|
6
|
+
|
|
7
|
+
const schema = z.object({
|
|
8
|
+
name: z.string().min(1, 'Name is required'),
|
|
9
|
+
email: z.string(),
|
|
10
|
+
nested: z.object({
|
|
11
|
+
value: z.string().min(1, 'Value is required'),
|
|
12
|
+
}),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const defaultValues = {
|
|
16
|
+
name: 'John',
|
|
17
|
+
email: 'john@example.com',
|
|
18
|
+
nested: {
|
|
19
|
+
value: 'test',
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const setup = () => {
|
|
24
|
+
const form = new FormApi({
|
|
25
|
+
schema,
|
|
26
|
+
defaultValues,
|
|
27
|
+
});
|
|
28
|
+
form['~mount']();
|
|
29
|
+
|
|
30
|
+
const nameField = new FieldApi({ form, name: 'name' });
|
|
31
|
+
nameField['~mount']();
|
|
32
|
+
|
|
33
|
+
const emailField = new FieldApi({ form, name: 'email' });
|
|
34
|
+
emailField['~mount']();
|
|
35
|
+
|
|
36
|
+
const nestedField = new FieldApi({ form, name: 'nested.value' });
|
|
37
|
+
nestedField['~mount']();
|
|
38
|
+
|
|
39
|
+
return { form, nameField, emailField, nestedField };
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
describe('FieldApi methods', () => {
|
|
43
|
+
describe('validate method', () => {
|
|
44
|
+
it('should validate the specific field only', async () => {
|
|
45
|
+
const { nameField, form } = setup();
|
|
46
|
+
|
|
47
|
+
// Make the name field invalid
|
|
48
|
+
nameField.change('');
|
|
49
|
+
|
|
50
|
+
// Validate only the name field
|
|
51
|
+
const issues = await nameField.validate({ type: 'submit' });
|
|
52
|
+
|
|
53
|
+
expect(issues).toHaveLength(1);
|
|
54
|
+
expect(issues[0].path).toEqual(['name']);
|
|
55
|
+
expect(form.field.errors('name')).toHaveLength(1);
|
|
56
|
+
expect(form.field.errors('email')).toHaveLength(0);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should validate field with different validation types', async () => {
|
|
60
|
+
const { nameField } = setup();
|
|
61
|
+
|
|
62
|
+
// Test different validation types
|
|
63
|
+
await nameField.validate({ type: 'blur' });
|
|
64
|
+
await nameField.validate({ type: 'focus' });
|
|
65
|
+
await nameField.validate({ type: 'submit' });
|
|
66
|
+
await nameField.validate({ type: 'change' });
|
|
67
|
+
|
|
68
|
+
// Should not throw and should work with all types
|
|
69
|
+
expect(nameField.errors).toEqual([]);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should validate field without options (using default schema)', async () => {
|
|
73
|
+
const { nameField } = setup();
|
|
74
|
+
|
|
75
|
+
nameField.change(''); // Make invalid
|
|
76
|
+
const issues = await nameField.validate();
|
|
77
|
+
|
|
78
|
+
expect(issues).toHaveLength(1);
|
|
79
|
+
expect(issues[0].path).toEqual(['name']);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('setErrors method', () => {
|
|
84
|
+
const mockError: FormIssue = {
|
|
85
|
+
code: 'custom',
|
|
86
|
+
message: 'Custom field error',
|
|
87
|
+
path: ['name'],
|
|
88
|
+
} as any;
|
|
89
|
+
|
|
90
|
+
const mockError2: FormIssue = {
|
|
91
|
+
code: 'custom_2',
|
|
92
|
+
message: 'Second field error',
|
|
93
|
+
path: ['name'],
|
|
94
|
+
} as any;
|
|
95
|
+
|
|
96
|
+
it('should set errors in replace mode by default', () => {
|
|
97
|
+
const { nameField } = setup();
|
|
98
|
+
|
|
99
|
+
nameField.setErrors([mockError]);
|
|
100
|
+
expect(nameField.errors).toEqual([mockError]);
|
|
101
|
+
|
|
102
|
+
// Replace with new error
|
|
103
|
+
nameField.setErrors([mockError2]);
|
|
104
|
+
expect(nameField.errors).toEqual([mockError2]);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('should set errors in append mode', () => {
|
|
108
|
+
const { nameField } = setup();
|
|
109
|
+
|
|
110
|
+
nameField.setErrors([mockError]);
|
|
111
|
+
nameField.setErrors([mockError2], { mode: 'append' });
|
|
112
|
+
|
|
113
|
+
expect(nameField.errors).toEqual([mockError, mockError2]);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should set errors in keep mode', () => {
|
|
117
|
+
const { nameField } = setup();
|
|
118
|
+
|
|
119
|
+
// Set initial error
|
|
120
|
+
nameField.setErrors([mockError]);
|
|
121
|
+
|
|
122
|
+
// Try to set new error with keep mode - should keep existing
|
|
123
|
+
nameField.setErrors([mockError2], { mode: 'keep' });
|
|
124
|
+
expect(nameField.errors).toEqual([mockError]);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('should set errors in keep mode when no existing errors', () => {
|
|
128
|
+
const { nameField } = setup();
|
|
129
|
+
|
|
130
|
+
nameField.setErrors([mockError], { mode: 'keep' });
|
|
131
|
+
expect(nameField.errors).toEqual([mockError]);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('should clear errors when setting empty array', () => {
|
|
135
|
+
const { nameField } = setup();
|
|
136
|
+
|
|
137
|
+
nameField.setErrors([mockError]);
|
|
138
|
+
expect(nameField.errors).toEqual([mockError]);
|
|
139
|
+
|
|
140
|
+
nameField.setErrors([]);
|
|
141
|
+
expect(nameField.errors).toEqual([]);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should work with nested fields', () => {
|
|
145
|
+
const { nestedField } = setup();
|
|
146
|
+
|
|
147
|
+
const nestedError: FormIssue = {
|
|
148
|
+
code: 'custom',
|
|
149
|
+
message: 'Nested field error',
|
|
150
|
+
path: ['nested', 'value'],
|
|
151
|
+
} as any;
|
|
152
|
+
|
|
153
|
+
nestedField.setErrors([nestedError]);
|
|
154
|
+
expect(nestedField.errors).toEqual([nestedError]);
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe('reset method', () => {
|
|
159
|
+
it('should reset field to default value', () => {
|
|
160
|
+
const { nameField } = setup();
|
|
161
|
+
|
|
162
|
+
// Change the field value
|
|
163
|
+
nameField.change('Jane');
|
|
164
|
+
expect(nameField.value).toBe('Jane');
|
|
165
|
+
|
|
166
|
+
// Reset the field
|
|
167
|
+
nameField.reset();
|
|
168
|
+
expect(nameField.value).toBe('John'); // back to default
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('should reset field to specific value', () => {
|
|
172
|
+
const { nameField } = setup();
|
|
173
|
+
|
|
174
|
+
nameField.change('Jane');
|
|
175
|
+
nameField.reset({ value: 'Bob' });
|
|
176
|
+
|
|
177
|
+
expect(nameField.value).toBe('Bob');
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
it('should reset field meta by default', () => {
|
|
181
|
+
const { nameField } = setup();
|
|
182
|
+
|
|
183
|
+
// Make the field dirty and touched
|
|
184
|
+
nameField.change('Jane');
|
|
185
|
+
nameField.focus();
|
|
186
|
+
nameField.blur();
|
|
187
|
+
|
|
188
|
+
expect(nameField.meta.dirty).toBe(true);
|
|
189
|
+
expect(nameField.meta.touched).toBe(true);
|
|
190
|
+
expect(nameField.meta.blurred).toBe(true);
|
|
191
|
+
|
|
192
|
+
// Reset the field
|
|
193
|
+
nameField.reset();
|
|
194
|
+
|
|
195
|
+
expect(nameField.meta.dirty).toBe(false);
|
|
196
|
+
expect(nameField.meta.touched).toBe(false);
|
|
197
|
+
expect(nameField.meta.blurred).toBe(false);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('should keep field meta when specified', () => {
|
|
201
|
+
const { nameField } = setup();
|
|
202
|
+
|
|
203
|
+
// Make the field dirty and touched
|
|
204
|
+
nameField.change('Jane');
|
|
205
|
+
nameField.focus();
|
|
206
|
+
nameField.blur();
|
|
207
|
+
|
|
208
|
+
const metaBefore = { ...nameField.meta };
|
|
209
|
+
|
|
210
|
+
// Reset but keep meta
|
|
211
|
+
nameField.reset({ keep: { meta: true } });
|
|
212
|
+
|
|
213
|
+
expect(nameField.value).toBe('John'); // value should reset
|
|
214
|
+
expect(nameField.meta.dirty).toBe(metaBefore.dirty);
|
|
215
|
+
expect(nameField.meta.touched).toBe(metaBefore.touched);
|
|
216
|
+
expect(nameField.meta.blurred).toBe(metaBefore.blurred);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should reset field errors by default', () => {
|
|
220
|
+
const { nameField } = setup();
|
|
221
|
+
|
|
222
|
+
const mockError: FormIssue = {
|
|
223
|
+
code: 'custom',
|
|
224
|
+
message: 'Test error',
|
|
225
|
+
path: ['name'],
|
|
226
|
+
} as any;
|
|
227
|
+
|
|
228
|
+
nameField.setErrors([mockError]);
|
|
229
|
+
expect(nameField.errors).toEqual([mockError]);
|
|
230
|
+
|
|
231
|
+
nameField.reset();
|
|
232
|
+
expect(nameField.errors).toEqual([]);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it('should keep field errors when specified', () => {
|
|
236
|
+
const { nameField } = setup();
|
|
237
|
+
|
|
238
|
+
const mockError: FormIssue = {
|
|
239
|
+
code: 'custom',
|
|
240
|
+
message: 'Test error',
|
|
241
|
+
path: ['name'],
|
|
242
|
+
} as any;
|
|
243
|
+
|
|
244
|
+
nameField.setErrors([mockError]);
|
|
245
|
+
nameField.change('Jane');
|
|
246
|
+
|
|
247
|
+
nameField.reset({ keep: { errors: true } });
|
|
248
|
+
|
|
249
|
+
expect(nameField.value).toBe('John'); // value should reset
|
|
250
|
+
expect(nameField.errors).toEqual([mockError]); // errors should be kept
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('should work with nested fields', () => {
|
|
254
|
+
const { nestedField } = setup();
|
|
255
|
+
|
|
256
|
+
nestedField.change('changed');
|
|
257
|
+
expect(nestedField.value).toBe('changed');
|
|
258
|
+
|
|
259
|
+
nestedField.reset();
|
|
260
|
+
expect(nestedField.value).toBe('test'); // back to default
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
describe('integration with existing methods', () => {
|
|
265
|
+
it('should work with change and validation together', async () => {
|
|
266
|
+
const { nameField } = setup();
|
|
267
|
+
|
|
268
|
+
// Change to invalid value
|
|
269
|
+
nameField.change('');
|
|
270
|
+
|
|
271
|
+
// Validate the field
|
|
272
|
+
const issues = await nameField.validate({ type: 'submit' });
|
|
273
|
+
expect(issues).toHaveLength(1);
|
|
274
|
+
expect(nameField.errors).toHaveLength(1);
|
|
275
|
+
|
|
276
|
+
// Change to valid value
|
|
277
|
+
nameField.change('Jane');
|
|
278
|
+
|
|
279
|
+
// Validate again
|
|
280
|
+
const validIssues = await nameField.validate({ type: 'submit' });
|
|
281
|
+
expect(validIssues).toHaveLength(0);
|
|
282
|
+
expect(nameField.errors).toHaveLength(0);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('should work with focus, blur, and validation', async () => {
|
|
286
|
+
const { nameField } = setup();
|
|
287
|
+
|
|
288
|
+
nameField.focus();
|
|
289
|
+
expect(nameField.meta.touched).toBe(true);
|
|
290
|
+
|
|
291
|
+
nameField.blur();
|
|
292
|
+
expect(nameField.meta.blurred).toBe(true);
|
|
293
|
+
|
|
294
|
+
await nameField.validate({ type: 'blur' });
|
|
295
|
+
// Should not throw and should work properly
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('should maintain consistency with form state', () => {
|
|
299
|
+
const { nameField, form } = setup();
|
|
300
|
+
|
|
301
|
+
const mockError: FormIssue = {
|
|
302
|
+
code: 'custom',
|
|
303
|
+
message: 'Test error',
|
|
304
|
+
path: ['name'],
|
|
305
|
+
} as any;
|
|
306
|
+
|
|
307
|
+
// Set error through field
|
|
308
|
+
nameField.setErrors([mockError]);
|
|
309
|
+
|
|
310
|
+
// Should be reflected in form
|
|
311
|
+
expect(form.field.errors('name')).toEqual([mockError]);
|
|
312
|
+
expect(form.store.state.status.valid).toBe(false);
|
|
313
|
+
|
|
314
|
+
// Clear error through field
|
|
315
|
+
nameField.setErrors([]);
|
|
316
|
+
|
|
317
|
+
// Should be reflected in form
|
|
318
|
+
expect(form.field.errors('name')).toEqual([]);
|
|
319
|
+
expect(form.store.state.status.valid).toBe(true);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('should work with form-level operations', async () => {
|
|
323
|
+
const { nameField, form } = setup();
|
|
324
|
+
|
|
325
|
+
// Change field value
|
|
326
|
+
nameField.change('Jane');
|
|
327
|
+
expect(nameField.value).toBe('Jane');
|
|
328
|
+
expect(form.field.get('name')).toBe('Jane');
|
|
329
|
+
|
|
330
|
+
// Reset through form
|
|
331
|
+
form.field.reset('name');
|
|
332
|
+
expect(nameField.value).toBe('John');
|
|
333
|
+
|
|
334
|
+
// Validate through form
|
|
335
|
+
nameField.change('');
|
|
336
|
+
const issues = await form.validate('name', { type: 'submit' });
|
|
337
|
+
expect(issues).toHaveLength(1);
|
|
338
|
+
expect(nameField.errors).toHaveLength(1);
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
});
|