@akinon/akiform-builder 1.5.6 → 1.5.8
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/cjs/constants.d.ts +2 -1
- package/dist/cjs/constants.d.ts.map +1 -1
- package/dist/cjs/constants.js +2 -1
- package/dist/cjs/field-builder.d.ts +5 -0
- package/dist/cjs/field-builder.d.ts.map +1 -1
- package/dist/cjs/field-builder.js +18 -1
- package/dist/cjs/types.d.ts +11 -6
- package/dist/cjs/types.d.ts.map +1 -1
- package/dist/esm/constants.d.ts +2 -1
- package/dist/esm/constants.d.ts.map +1 -1
- package/dist/esm/constants.js +2 -1
- package/dist/esm/field-builder.d.ts +5 -0
- package/dist/esm/field-builder.d.ts.map +1 -1
- package/dist/esm/field-builder.js +18 -1
- package/dist/esm/types.d.ts +11 -6
- package/dist/esm/types.d.ts.map +1 -1
- package/package.json +20 -20
- package/dist/cjs/__tests__/akiform-builder.test.d.ts +0 -2
- package/dist/cjs/__tests__/akiform-builder.test.d.ts.map +0 -1
- package/dist/cjs/__tests__/akiform-builder.test.js +0 -1423
- package/dist/cjs/__tests__/field-builder.test.d.ts +0 -2
- package/dist/cjs/__tests__/field-builder.test.d.ts.map +0 -1
- package/dist/cjs/__tests__/field-builder.test.js +0 -296
- package/dist/cjs/src/akiform-builder.d.ts +0 -7
- package/dist/cjs/src/akiform-builder.d.ts.map +0 -1
- package/dist/cjs/src/akiform-builder.js +0 -458
- package/dist/cjs/src/field-builder.d.ts +0 -54
- package/dist/cjs/src/field-builder.d.ts.map +0 -1
- package/dist/cjs/src/field-builder.js +0 -153
- package/dist/cjs/src/i18n/index.d.ts +0 -2
- package/dist/cjs/src/i18n/index.d.ts.map +0 -1
- package/dist/cjs/src/i18n/index.js +0 -14
- package/dist/cjs/src/i18n/translations/en.d.ts +0 -14
- package/dist/cjs/src/i18n/translations/en.d.ts.map +0 -1
- package/dist/cjs/src/i18n/translations/en.js +0 -15
- package/dist/cjs/src/i18n/translations/tr.d.ts +0 -14
- package/dist/cjs/src/i18n/translations/tr.d.ts.map +0 -1
- package/dist/cjs/src/i18n/translations/tr.js +0 -15
- package/dist/cjs/src/index.d.ts +0 -4
- package/dist/cjs/src/index.d.ts.map +0 -1
- package/dist/cjs/src/index.js +0 -21
- package/dist/cjs/src/types.d.ts +0 -106
- package/dist/cjs/src/types.d.ts.map +0 -1
- package/dist/cjs/src/types.js +0 -2
- package/dist/esm/__tests__/akiform-builder.test.d.ts +0 -2
- package/dist/esm/__tests__/akiform-builder.test.d.ts.map +0 -1
- package/dist/esm/__tests__/akiform-builder.test.js +0 -1421
- package/dist/esm/__tests__/field-builder.test.d.ts +0 -2
- package/dist/esm/__tests__/field-builder.test.d.ts.map +0 -1
- package/dist/esm/__tests__/field-builder.test.js +0 -294
- package/dist/esm/src/akiform-builder.d.ts +0 -7
- package/dist/esm/src/akiform-builder.d.ts.map +0 -1
- package/dist/esm/src/akiform-builder.js +0 -455
- package/dist/esm/src/field-builder.d.ts +0 -54
- package/dist/esm/src/field-builder.d.ts.map +0 -1
- package/dist/esm/src/field-builder.js +0 -150
- package/dist/esm/src/i18n/index.d.ts +0 -2
- package/dist/esm/src/i18n/index.d.ts.map +0 -1
- package/dist/esm/src/i18n/index.js +0 -11
- package/dist/esm/src/i18n/translations/en.d.ts +0 -14
- package/dist/esm/src/i18n/translations/en.d.ts.map +0 -1
- package/dist/esm/src/i18n/translations/en.js +0 -13
- package/dist/esm/src/i18n/translations/tr.d.ts +0 -14
- package/dist/esm/src/i18n/translations/tr.d.ts.map +0 -1
- package/dist/esm/src/i18n/translations/tr.js +0 -13
- package/dist/esm/src/index.d.ts +0 -4
- package/dist/esm/src/index.d.ts.map +0 -1
- package/dist/esm/src/index.js +0 -3
- package/dist/esm/src/types.d.ts +0 -106
- package/dist/esm/src/types.d.ts.map +0 -1
- package/dist/esm/src/types.js +0 -1
|
@@ -1,1421 +0,0 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
-
import { useController } from '@akinon/akiform';
|
|
3
|
-
import { akival } from '@akinon/akival';
|
|
4
|
-
import { fireEvent, render, screen, userEvent, waitFor } from '@akinon/utils';
|
|
5
|
-
import * as React from 'react';
|
|
6
|
-
import { act } from 'react';
|
|
7
|
-
import { AkiformBuilder, THROTTLE_DELAY } from '../src/akiform-builder';
|
|
8
|
-
import { field } from '../src/field-builder';
|
|
9
|
-
describe('AkiformBuilder', () => {
|
|
10
|
-
const mockOnSubmit = vi.fn();
|
|
11
|
-
const mockOnReset = vi.fn();
|
|
12
|
-
const mockOnValueChange = vi.fn();
|
|
13
|
-
const expandCollapsable = async () => {
|
|
14
|
-
const user = userEvent.setup();
|
|
15
|
-
const expandIcons = (await screen.findAllByRole('button')).filter(el => el.className.includes('akinon-collapse-expand-icon'));
|
|
16
|
-
for (const expandIcon of expandIcons) {
|
|
17
|
-
await user.click(expandIcon);
|
|
18
|
-
}
|
|
19
|
-
};
|
|
20
|
-
const defaultFields = [
|
|
21
|
-
{
|
|
22
|
-
key: 'name',
|
|
23
|
-
label: 'Name',
|
|
24
|
-
type: 'text',
|
|
25
|
-
placeholder: 'Enter your name'
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
key: 'age',
|
|
29
|
-
label: 'Age',
|
|
30
|
-
type: 'number',
|
|
31
|
-
placeholder: 'Enter your age',
|
|
32
|
-
help: 'Description text 2'
|
|
33
|
-
}
|
|
34
|
-
];
|
|
35
|
-
beforeEach(() => {
|
|
36
|
-
vi.clearAllMocks();
|
|
37
|
-
});
|
|
38
|
-
describe('AkiformBuilder in uncontrolled mode', () => {
|
|
39
|
-
it('renders form fields correctly', async () => {
|
|
40
|
-
const newDefaultFields = defaultFields.map((field, key) => (Object.assign(Object.assign({}, field), { help: `Description text ${key}`, labelDescription: `Label description text ${key}` })));
|
|
41
|
-
await act(async () => {
|
|
42
|
-
render(React.createElement(AkiformBuilder, { fields: newDefaultFields, onSubmit: mockOnSubmit }));
|
|
43
|
-
});
|
|
44
|
-
for (const field of newDefaultFields) {
|
|
45
|
-
if (field.label) {
|
|
46
|
-
expect(screen.getByText(field.label)).toBeInTheDocument();
|
|
47
|
-
}
|
|
48
|
-
if (field.placeholder) {
|
|
49
|
-
expect(screen.getByPlaceholderText(field.placeholder)).toBeInTheDocument();
|
|
50
|
-
}
|
|
51
|
-
expect(screen.getByText(field.help)).toBeInTheDocument();
|
|
52
|
-
expect(screen.getByText(field.labelDescription)).toBeInTheDocument();
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
it('renders form fields correctly with row and column fields', async () => {
|
|
56
|
-
const newDefaultFields = [
|
|
57
|
-
{
|
|
58
|
-
type: 'row',
|
|
59
|
-
key: 'mainrow',
|
|
60
|
-
rowProps: {
|
|
61
|
-
align: 'middle'
|
|
62
|
-
},
|
|
63
|
-
columnFields: [
|
|
64
|
-
{
|
|
65
|
-
type: 'column',
|
|
66
|
-
key: 'col1',
|
|
67
|
-
columnProps: {
|
|
68
|
-
span: '24'
|
|
69
|
-
},
|
|
70
|
-
fields: [
|
|
71
|
-
{
|
|
72
|
-
type: 'row',
|
|
73
|
-
key: 'row1',
|
|
74
|
-
columnFields: [
|
|
75
|
-
{
|
|
76
|
-
type: 'column',
|
|
77
|
-
key: 'col1_1',
|
|
78
|
-
fields: [
|
|
79
|
-
{
|
|
80
|
-
type: 'text',
|
|
81
|
-
key: 'test1',
|
|
82
|
-
label: 'Test Field 1'
|
|
83
|
-
}
|
|
84
|
-
]
|
|
85
|
-
}
|
|
86
|
-
]
|
|
87
|
-
},
|
|
88
|
-
{
|
|
89
|
-
type: 'row',
|
|
90
|
-
key: 'row2',
|
|
91
|
-
columnFields: [
|
|
92
|
-
{
|
|
93
|
-
type: 'column',
|
|
94
|
-
key: 'col2_1',
|
|
95
|
-
fields: [
|
|
96
|
-
{
|
|
97
|
-
type: 'text',
|
|
98
|
-
key: 'test2',
|
|
99
|
-
label: 'Test Field 2'
|
|
100
|
-
}
|
|
101
|
-
]
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
type: 'column',
|
|
105
|
-
key: 'col2_2',
|
|
106
|
-
fields: [
|
|
107
|
-
{
|
|
108
|
-
type: 'text',
|
|
109
|
-
key: 'test3',
|
|
110
|
-
label: 'Test Field 3'
|
|
111
|
-
}
|
|
112
|
-
]
|
|
113
|
-
}
|
|
114
|
-
]
|
|
115
|
-
}
|
|
116
|
-
]
|
|
117
|
-
}
|
|
118
|
-
]
|
|
119
|
-
}
|
|
120
|
-
];
|
|
121
|
-
await act(async () => {
|
|
122
|
-
render(React.createElement(AkiformBuilder, { fields: newDefaultFields, onSubmit: mockOnSubmit }));
|
|
123
|
-
});
|
|
124
|
-
// Check fields
|
|
125
|
-
expect(screen.getByLabelText('Test Field 1')).toBeInTheDocument();
|
|
126
|
-
expect(screen.getByLabelText('Test Field 2')).toBeInTheDocument();
|
|
127
|
-
expect(screen.getByLabelText('Test Field 3')).toBeInTheDocument();
|
|
128
|
-
// Check row and column count
|
|
129
|
-
expect(document.querySelectorAll('.akinon-col')).toHaveLength(11);
|
|
130
|
-
expect(document.querySelectorAll('.akinon-row')).toHaveLength(7);
|
|
131
|
-
// Check props
|
|
132
|
-
expect(document.querySelector('.akinon-row-middle')).toBeInTheDocument();
|
|
133
|
-
expect(document.querySelector('.akinon-col-24')).toBeInTheDocument();
|
|
134
|
-
});
|
|
135
|
-
it('handles form with custom field and onChange', async () => {
|
|
136
|
-
const CustomComponent = ({ field, control }) => {
|
|
137
|
-
const { field: controllerField } = useController({
|
|
138
|
-
name: field.key,
|
|
139
|
-
control
|
|
140
|
-
});
|
|
141
|
-
return React.createElement("input", Object.assign({ "data-testid": "custom-input" }, controllerField));
|
|
142
|
-
};
|
|
143
|
-
const fields = [
|
|
144
|
-
{
|
|
145
|
-
key: 'customField',
|
|
146
|
-
label: 'Custom Field',
|
|
147
|
-
type: 'custom',
|
|
148
|
-
render: ({ field, formValues, control }) => (React.createElement(CustomComponent, { field: field, formValues: formValues, control: control }))
|
|
149
|
-
}
|
|
150
|
-
];
|
|
151
|
-
const onValueChangeMock = vi.fn();
|
|
152
|
-
await act(async () => {
|
|
153
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: mockOnSubmit, onValueChange: onValueChangeMock }));
|
|
154
|
-
});
|
|
155
|
-
const customInput = screen.getByTestId('custom-input');
|
|
156
|
-
expect(customInput).toBeInTheDocument();
|
|
157
|
-
await act(async () => {
|
|
158
|
-
await userEvent.type(customInput, 'test value');
|
|
159
|
-
});
|
|
160
|
-
await waitFor(() => {
|
|
161
|
-
expect(onValueChangeMock).toHaveBeenCalledWith(expect.objectContaining({
|
|
162
|
-
customField: 'test value'
|
|
163
|
-
}));
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
it('resets form when reset button is clicked', async () => {
|
|
167
|
-
await act(async () => {
|
|
168
|
-
render(React.createElement(AkiformBuilder, { fields: defaultFields, onSubmit: mockOnSubmit, onReset: mockOnReset, showResetButton: true }));
|
|
169
|
-
});
|
|
170
|
-
await act(async () => {
|
|
171
|
-
fireEvent.change(screen.getByLabelText('Name'), {
|
|
172
|
-
target: { value: 'John Doe' }
|
|
173
|
-
});
|
|
174
|
-
fireEvent.change(screen.getByLabelText('Age'), {
|
|
175
|
-
target: { value: '30' }
|
|
176
|
-
});
|
|
177
|
-
fireEvent.click(screen.getByText('RESET'));
|
|
178
|
-
});
|
|
179
|
-
await waitFor(() => {
|
|
180
|
-
expect(mockOnReset).toHaveBeenCalled();
|
|
181
|
-
expect(screen.getByLabelText('Name')).toHaveValue('');
|
|
182
|
-
expect(screen.getByLabelText('Age')).toHaveValue('');
|
|
183
|
-
});
|
|
184
|
-
});
|
|
185
|
-
it('exposes reset method through ref', async () => {
|
|
186
|
-
const ref = React.createRef();
|
|
187
|
-
const onResetMock = vi.fn();
|
|
188
|
-
await act(async () => {
|
|
189
|
-
render(React.createElement(AkiformBuilder, { ref: ref, fields: defaultFields, onSubmit: mockOnSubmit, onReset: onResetMock }));
|
|
190
|
-
});
|
|
191
|
-
// Fill in some values
|
|
192
|
-
await act(async () => {
|
|
193
|
-
fireEvent.change(screen.getByLabelText('Name'), {
|
|
194
|
-
target: { value: 'John Doe' }
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
// Call reset through ref
|
|
198
|
-
await act(async () => {
|
|
199
|
-
var _a;
|
|
200
|
-
(_a = ref.current) === null || _a === void 0 ? void 0 : _a.reset();
|
|
201
|
-
});
|
|
202
|
-
// Check if form is reset and onReset is called
|
|
203
|
-
expect(screen.getByLabelText('Name')).toHaveValue('');
|
|
204
|
-
expect(onResetMock).toHaveBeenCalled();
|
|
205
|
-
// Test partial reset
|
|
206
|
-
await act(async () => {
|
|
207
|
-
fireEvent.change(screen.getByLabelText('Name'), {
|
|
208
|
-
target: { value: 'John Doe' }
|
|
209
|
-
});
|
|
210
|
-
fireEvent.change(screen.getByLabelText('Age'), {
|
|
211
|
-
target: { value: '30' }
|
|
212
|
-
});
|
|
213
|
-
});
|
|
214
|
-
await act(async () => {
|
|
215
|
-
var _a;
|
|
216
|
-
(_a = ref.current) === null || _a === void 0 ? void 0 : _a.reset({ name: 'Jane Doe' });
|
|
217
|
-
});
|
|
218
|
-
expect(screen.getByLabelText('Name')).toHaveValue('Jane Doe');
|
|
219
|
-
expect(screen.getByLabelText('Age')).toHaveValue('30');
|
|
220
|
-
});
|
|
221
|
-
it('calls onValueChange when form values change in uncontrolled mode', async () => {
|
|
222
|
-
await act(async () => {
|
|
223
|
-
render(React.createElement(AkiformBuilder, { fields: defaultFields, onSubmit: mockOnSubmit, onValueChange: mockOnValueChange }));
|
|
224
|
-
});
|
|
225
|
-
await act(async () => {
|
|
226
|
-
fireEvent.change(screen.getByLabelText('Name'), {
|
|
227
|
-
target: { value: 'John Doe' }
|
|
228
|
-
});
|
|
229
|
-
});
|
|
230
|
-
await waitFor(() => {
|
|
231
|
-
expect(mockOnValueChange).toHaveBeenCalledWith(expect.objectContaining({
|
|
232
|
-
name: 'John Doe'
|
|
233
|
-
}));
|
|
234
|
-
});
|
|
235
|
-
});
|
|
236
|
-
it('applies throttling in uncontrolled mode', async () => {
|
|
237
|
-
vi.useFakeTimers();
|
|
238
|
-
await act(async () => {
|
|
239
|
-
render(React.createElement(AkiformBuilder, { fields: defaultFields, onSubmit: mockOnSubmit, onValueChange: mockOnValueChange }));
|
|
240
|
-
});
|
|
241
|
-
await act(async () => {
|
|
242
|
-
fireEvent.change(screen.getByLabelText('Name'), {
|
|
243
|
-
target: { value: 'John Doe' }
|
|
244
|
-
});
|
|
245
|
-
});
|
|
246
|
-
expect(mockOnValueChange).not.toHaveBeenCalled();
|
|
247
|
-
await act(async () => {
|
|
248
|
-
vi.advanceTimersByTime(THROTTLE_DELAY);
|
|
249
|
-
});
|
|
250
|
-
expect(mockOnValueChange).toHaveBeenCalledWith(expect.objectContaining({ name: 'John Doe' }));
|
|
251
|
-
vi.useRealTimers();
|
|
252
|
-
});
|
|
253
|
-
it('renders different field types correctly', async () => {
|
|
254
|
-
const fields = [
|
|
255
|
-
{
|
|
256
|
-
key: 'name',
|
|
257
|
-
label: 'Name',
|
|
258
|
-
type: 'text',
|
|
259
|
-
help: 'Help text',
|
|
260
|
-
labelDescription: 'Label description text',
|
|
261
|
-
placeholder: 'Enter your name'
|
|
262
|
-
},
|
|
263
|
-
{ key: 'age', label: 'Age', type: 'number' },
|
|
264
|
-
{
|
|
265
|
-
key: 'gender',
|
|
266
|
-
label: 'Gender',
|
|
267
|
-
type: 'select',
|
|
268
|
-
options: [
|
|
269
|
-
{ value: 'male', label: 'Male' },
|
|
270
|
-
{ value: 'female', label: 'Female' }
|
|
271
|
-
]
|
|
272
|
-
},
|
|
273
|
-
{ key: 'subscribe', label: 'Subscribe', type: 'checkbox' },
|
|
274
|
-
{ key: 'birthdate', label: 'Birth Date', type: 'date' },
|
|
275
|
-
{ key: 'bio', label: 'Biography', type: 'textarea' }
|
|
276
|
-
];
|
|
277
|
-
await act(async () => {
|
|
278
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: mockOnSubmit }));
|
|
279
|
-
});
|
|
280
|
-
expect(screen.getByPlaceholderText('Enter your name')).toHaveAttribute('type', 'text');
|
|
281
|
-
expect(screen.getByText('Help text')).toBeInTheDocument();
|
|
282
|
-
expect(screen.getByText('Label description text')).toBeInTheDocument();
|
|
283
|
-
expect(screen.getByLabelText('Age')).toHaveClass('akinon-input-number-input');
|
|
284
|
-
expect(screen.getByLabelText('Gender')).toBeInTheDocument();
|
|
285
|
-
expect(screen.getByLabelText('Subscribe')).toHaveAttribute('type', 'checkbox');
|
|
286
|
-
expect(screen.getByLabelText('Birth Date')).toBeInTheDocument();
|
|
287
|
-
expect(screen.getByLabelText('Biography')).toBeInTheDocument();
|
|
288
|
-
});
|
|
289
|
-
it('renders field array correctly', async () => {
|
|
290
|
-
const user = userEvent.setup();
|
|
291
|
-
const fields = [
|
|
292
|
-
field()
|
|
293
|
-
.key('addresses')
|
|
294
|
-
.label('Addresses')
|
|
295
|
-
.type('fieldArray')
|
|
296
|
-
.defaultExpanded(true)
|
|
297
|
-
.fields([
|
|
298
|
-
field()
|
|
299
|
-
.key('street')
|
|
300
|
-
.label('Street')
|
|
301
|
-
.type('text')
|
|
302
|
-
.help('Help text')
|
|
303
|
-
.labelDescription('Label description text')
|
|
304
|
-
.build(),
|
|
305
|
-
field().key('city').label('City').type('text').build()
|
|
306
|
-
])
|
|
307
|
-
.build()
|
|
308
|
-
];
|
|
309
|
-
await act(async () => {
|
|
310
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: mockOnSubmit }));
|
|
311
|
-
});
|
|
312
|
-
expect(screen.getByText('Add Addresses')).toBeInTheDocument();
|
|
313
|
-
await user.click(screen.getByTestId('addresses-add-button'));
|
|
314
|
-
await expandCollapsable();
|
|
315
|
-
expect(screen.getAllByText('Street')).toHaveLength(1);
|
|
316
|
-
expect(screen.getAllByText('City')).toHaveLength(1);
|
|
317
|
-
expect(screen.getByText('Help text')).toBeInTheDocument();
|
|
318
|
-
expect(screen.getByText('Label description text')).toBeInTheDocument();
|
|
319
|
-
});
|
|
320
|
-
it('handles form with field array and nested validation', async () => {
|
|
321
|
-
const user = userEvent.setup();
|
|
322
|
-
const fields = [
|
|
323
|
-
{
|
|
324
|
-
key: 'items',
|
|
325
|
-
label: 'Items',
|
|
326
|
-
type: 'fieldArray',
|
|
327
|
-
fields: [
|
|
328
|
-
{
|
|
329
|
-
key: 'name',
|
|
330
|
-
label: 'Item Name',
|
|
331
|
-
type: 'text',
|
|
332
|
-
validation: akival.string().required('Item name is required')
|
|
333
|
-
},
|
|
334
|
-
{
|
|
335
|
-
key: 'quantity',
|
|
336
|
-
label: 'Quantity',
|
|
337
|
-
type: 'number',
|
|
338
|
-
validation: akival.number().min(1, 'Quantity must be at least 1')
|
|
339
|
-
}
|
|
340
|
-
]
|
|
341
|
-
}
|
|
342
|
-
];
|
|
343
|
-
const onSubmitMock = vi.fn();
|
|
344
|
-
await act(async () => {
|
|
345
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: onSubmitMock }));
|
|
346
|
-
});
|
|
347
|
-
// Add an item
|
|
348
|
-
await user.click(screen.getByTestId('items-add-button'));
|
|
349
|
-
// Try to submit without filling required fields
|
|
350
|
-
await act(async () => {
|
|
351
|
-
fireEvent.submit(screen.getByTestId('akiform-builder'));
|
|
352
|
-
});
|
|
353
|
-
await waitFor(() => {
|
|
354
|
-
expect(screen.getByText('An error occurred in here')).toBeInTheDocument();
|
|
355
|
-
expect(onSubmitMock).not.toHaveBeenCalled();
|
|
356
|
-
});
|
|
357
|
-
await expandCollapsable();
|
|
358
|
-
// Fill in valid data
|
|
359
|
-
await act(async () => {
|
|
360
|
-
const nameInput = screen.getByRole('textbox', { name: /item name/i });
|
|
361
|
-
const quantityInput = screen.getByRole('spinbutton', {
|
|
362
|
-
name: /quantity/i
|
|
363
|
-
});
|
|
364
|
-
await userEvent.type(nameInput, 'Test Item');
|
|
365
|
-
await userEvent.type(quantityInput, '2');
|
|
366
|
-
});
|
|
367
|
-
// Submit the form
|
|
368
|
-
await act(async () => {
|
|
369
|
-
fireEvent.submit(screen.getByTestId('akiform-builder'));
|
|
370
|
-
});
|
|
371
|
-
await waitFor(() => {
|
|
372
|
-
expect(onSubmitMock).toHaveBeenCalledWith(expect.objectContaining({
|
|
373
|
-
items: [{ name: 'Test Item', quantity: 2 }]
|
|
374
|
-
}), expect.anything());
|
|
375
|
-
});
|
|
376
|
-
});
|
|
377
|
-
it('handles field array operations correctly', async () => {
|
|
378
|
-
const user = userEvent.setup();
|
|
379
|
-
const fields = [
|
|
380
|
-
{
|
|
381
|
-
key: 'items',
|
|
382
|
-
label: 'Items',
|
|
383
|
-
type: 'fieldArray',
|
|
384
|
-
fields: [
|
|
385
|
-
{ key: 'name', label: 'Item Name', type: 'text' },
|
|
386
|
-
{ key: 'quantity', label: 'Quantity', type: 'number' }
|
|
387
|
-
]
|
|
388
|
-
}
|
|
389
|
-
];
|
|
390
|
-
const TestComponent = () => {
|
|
391
|
-
const [formValues, setFormValues] = React.useState({});
|
|
392
|
-
return (React.createElement(React.Fragment, null,
|
|
393
|
-
React.createElement(AkiformBuilder, { fields: fields, onSubmit: mockOnSubmit, onValueChange: values => {
|
|
394
|
-
setFormValues(values);
|
|
395
|
-
} }),
|
|
396
|
-
React.createElement("div", { "data-testid": "form-values" }, JSON.stringify(formValues))));
|
|
397
|
-
};
|
|
398
|
-
await act(async () => {
|
|
399
|
-
render(React.createElement(TestComponent, null));
|
|
400
|
-
});
|
|
401
|
-
// Add an item
|
|
402
|
-
await user.click(screen.getByTestId('items-add-button'));
|
|
403
|
-
await expandCollapsable();
|
|
404
|
-
// Fill in the fields
|
|
405
|
-
await act(async () => {
|
|
406
|
-
fireEvent.change(screen.getByLabelText('Item Name'), {
|
|
407
|
-
target: { value: 'Test Item' }
|
|
408
|
-
});
|
|
409
|
-
});
|
|
410
|
-
await act(async () => {
|
|
411
|
-
fireEvent.change(screen.getByLabelText('Quantity'), {
|
|
412
|
-
target: { value: '5' }
|
|
413
|
-
});
|
|
414
|
-
});
|
|
415
|
-
// Wait for the form values to update
|
|
416
|
-
await waitFor(() => {
|
|
417
|
-
const formValuesElement = screen.getByTestId('form-values');
|
|
418
|
-
const formValues = JSON.parse(formValuesElement.textContent || '{}');
|
|
419
|
-
expect(formValues).toEqual(expect.objectContaining({
|
|
420
|
-
items: [{ name: 'Test Item', quantity: 5 }]
|
|
421
|
-
}));
|
|
422
|
-
});
|
|
423
|
-
// Submit the form
|
|
424
|
-
await act(async () => {
|
|
425
|
-
fireEvent.submit(screen.getByTestId('akiform-builder'));
|
|
426
|
-
});
|
|
427
|
-
// Check the submitted values
|
|
428
|
-
await waitFor(() => {
|
|
429
|
-
expect(mockOnSubmit).toHaveBeenCalledWith(expect.objectContaining({
|
|
430
|
-
items: [{ name: 'Test Item', quantity: 5 }]
|
|
431
|
-
}), expect.anything());
|
|
432
|
-
});
|
|
433
|
-
});
|
|
434
|
-
it('applies layout options correctly', async () => {
|
|
435
|
-
await act(async () => {
|
|
436
|
-
render(React.createElement(AkiformBuilder, { fields: defaultFields, onSubmit: mockOnSubmit, layout: "horizontal", layoutOptions: { labelCol: { span: 8 }, wrapperCol: { span: 16 } } }));
|
|
437
|
-
});
|
|
438
|
-
const form = screen.getByTestId('akiform-builder');
|
|
439
|
-
expect(form).toHaveClass('akinon-form-horizontal');
|
|
440
|
-
});
|
|
441
|
-
it('conditionally renders and disables fields', async () => {
|
|
442
|
-
const fields = [
|
|
443
|
-
{ key: 'showField', label: 'Show Field', type: 'checkbox' },
|
|
444
|
-
{
|
|
445
|
-
key: 'conditionalField',
|
|
446
|
-
label: 'Conditional Field',
|
|
447
|
-
type: 'text',
|
|
448
|
-
config: { visible: values => values.showField }
|
|
449
|
-
},
|
|
450
|
-
{ key: 'enableField', label: 'Enable Field', type: 'checkbox' },
|
|
451
|
-
{
|
|
452
|
-
key: 'disableableField',
|
|
453
|
-
label: 'Disableable Field',
|
|
454
|
-
type: 'text',
|
|
455
|
-
config: { disabled: values => !values.enableField }
|
|
456
|
-
}
|
|
457
|
-
];
|
|
458
|
-
await act(async () => {
|
|
459
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: mockOnSubmit }));
|
|
460
|
-
});
|
|
461
|
-
expect(screen.queryByLabelText('Conditional Field')).not.toBeInTheDocument();
|
|
462
|
-
await act(async () => {
|
|
463
|
-
fireEvent.click(screen.getByLabelText('Show Field'));
|
|
464
|
-
});
|
|
465
|
-
expect(screen.getByLabelText('Conditional Field')).toBeInTheDocument();
|
|
466
|
-
expect(screen.getByLabelText('Disableable Field')).toBeDisabled();
|
|
467
|
-
await act(async () => {
|
|
468
|
-
fireEvent.click(screen.getByLabelText('Enable Field'));
|
|
469
|
-
});
|
|
470
|
-
expect(screen.getByLabelText('Disableable Field')).not.toBeDisabled();
|
|
471
|
-
});
|
|
472
|
-
it('applies custom validation', async () => {
|
|
473
|
-
const fields = [
|
|
474
|
-
{
|
|
475
|
-
key: 'email',
|
|
476
|
-
label: 'Email',
|
|
477
|
-
type: 'text',
|
|
478
|
-
validation: akival.string().email('Invalid email format')
|
|
479
|
-
}
|
|
480
|
-
];
|
|
481
|
-
await act(async () => {
|
|
482
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: mockOnSubmit }));
|
|
483
|
-
});
|
|
484
|
-
await act(async () => {
|
|
485
|
-
fireEvent.change(screen.getByRole('textbox', { name: /email/i }), {
|
|
486
|
-
target: { value: 'invalid-email' }
|
|
487
|
-
});
|
|
488
|
-
fireEvent.submit(screen.getByTestId('akiform-builder'));
|
|
489
|
-
});
|
|
490
|
-
await waitFor(() => {
|
|
491
|
-
expect(screen.getByText('Invalid email format')).toBeInTheDocument();
|
|
492
|
-
});
|
|
493
|
-
});
|
|
494
|
-
it('resets form using ref', async () => {
|
|
495
|
-
const ref = React.createRef();
|
|
496
|
-
await act(async () => {
|
|
497
|
-
render(React.createElement(AkiformBuilder, { ref: ref, fields: defaultFields, onSubmit: mockOnSubmit }));
|
|
498
|
-
});
|
|
499
|
-
await act(async () => {
|
|
500
|
-
fireEvent.change(screen.getByLabelText('Name'), {
|
|
501
|
-
target: { value: 'John Doe' }
|
|
502
|
-
});
|
|
503
|
-
fireEvent.change(screen.getByLabelText('Age'), {
|
|
504
|
-
target: { value: '30' }
|
|
505
|
-
});
|
|
506
|
-
});
|
|
507
|
-
await act(async () => {
|
|
508
|
-
var _a;
|
|
509
|
-
(_a = ref.current) === null || _a === void 0 ? void 0 : _a.reset();
|
|
510
|
-
});
|
|
511
|
-
expect(screen.getByLabelText('Name')).toHaveValue('');
|
|
512
|
-
expect(screen.getByLabelText('Age')).toHaveValue('');
|
|
513
|
-
});
|
|
514
|
-
it('displays error states', async () => {
|
|
515
|
-
const fields = [
|
|
516
|
-
{
|
|
517
|
-
key: 'name',
|
|
518
|
-
label: 'Name',
|
|
519
|
-
type: 'text',
|
|
520
|
-
validation: akival.string().required('Name is required')
|
|
521
|
-
}
|
|
522
|
-
];
|
|
523
|
-
await act(async () => {
|
|
524
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: mockOnSubmit }));
|
|
525
|
-
});
|
|
526
|
-
await act(async () => {
|
|
527
|
-
fireEvent.submit(screen.getByTestId('akiform-builder'));
|
|
528
|
-
});
|
|
529
|
-
await waitFor(() => {
|
|
530
|
-
expect(screen.getByText('Name is required')).toBeInTheDocument();
|
|
531
|
-
});
|
|
532
|
-
});
|
|
533
|
-
it('handles form submission', async () => {
|
|
534
|
-
const onSubmit = vi.fn();
|
|
535
|
-
await act(async () => {
|
|
536
|
-
render(React.createElement(AkiformBuilder, { fields: defaultFields, onSubmit: onSubmit }));
|
|
537
|
-
});
|
|
538
|
-
await act(async () => {
|
|
539
|
-
fireEvent.change(screen.getByLabelText('Name'), {
|
|
540
|
-
target: { value: 'John Doe' }
|
|
541
|
-
});
|
|
542
|
-
fireEvent.change(screen.getByLabelText('Age'), {
|
|
543
|
-
target: { value: '30' }
|
|
544
|
-
});
|
|
545
|
-
fireEvent.submit(screen.getByTestId('akiform-builder'));
|
|
546
|
-
});
|
|
547
|
-
await waitFor(() => {
|
|
548
|
-
expect(onSubmit).toHaveBeenCalledWith(expect.objectContaining({
|
|
549
|
-
name: 'John Doe',
|
|
550
|
-
age: 30
|
|
551
|
-
}), expect.anything());
|
|
552
|
-
});
|
|
553
|
-
});
|
|
554
|
-
it('handles form submission with errors', async () => {
|
|
555
|
-
const fields = [
|
|
556
|
-
{
|
|
557
|
-
key: 'name',
|
|
558
|
-
label: 'Name',
|
|
559
|
-
type: 'text',
|
|
560
|
-
validation: akival.string().required('Name is required')
|
|
561
|
-
}
|
|
562
|
-
];
|
|
563
|
-
const onSubmit = vi.fn();
|
|
564
|
-
await act(async () => {
|
|
565
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: onSubmit }));
|
|
566
|
-
});
|
|
567
|
-
await act(async () => {
|
|
568
|
-
fireEvent.submit(screen.getByTestId('akiform-builder'));
|
|
569
|
-
});
|
|
570
|
-
await waitFor(() => {
|
|
571
|
-
expect(screen.getByText('Name is required')).toBeInTheDocument();
|
|
572
|
-
expect(onSubmit).not.toHaveBeenCalled();
|
|
573
|
-
});
|
|
574
|
-
});
|
|
575
|
-
it('handles form with initial values', async () => {
|
|
576
|
-
const initialValues = { name: 'John Doe', age: 30 };
|
|
577
|
-
await act(async () => {
|
|
578
|
-
render(React.createElement(AkiformBuilder, { fields: defaultFields, onSubmit: mockOnSubmit, initialValues: initialValues }));
|
|
579
|
-
});
|
|
580
|
-
await waitFor(() => {
|
|
581
|
-
expect(screen.getByLabelText('Name')).toHaveValue('John Doe');
|
|
582
|
-
expect(screen.getByLabelText('Age')).toHaveValue('30');
|
|
583
|
-
});
|
|
584
|
-
});
|
|
585
|
-
it('handles form with custom layout', async () => {
|
|
586
|
-
await act(async () => {
|
|
587
|
-
render(React.createElement(AkiformBuilder, { fields: defaultFields, onSubmit: mockOnSubmit, layout: "vertical" }));
|
|
588
|
-
});
|
|
589
|
-
const form = screen.getByTestId('akiform-builder');
|
|
590
|
-
expect(form).toHaveClass('akinon-form-vertical');
|
|
591
|
-
});
|
|
592
|
-
it('renders custom field correctly', async () => {
|
|
593
|
-
const CustomComponent = ({ field }) => (React.createElement("div", { "data-testid": "custom-field" }, field.label));
|
|
594
|
-
const fields = [
|
|
595
|
-
{
|
|
596
|
-
key: 'customField',
|
|
597
|
-
label: 'Custom Field',
|
|
598
|
-
type: 'custom',
|
|
599
|
-
help: 'Help text',
|
|
600
|
-
labelDescription: 'Label description text',
|
|
601
|
-
render: ({ field }) => (React.createElement(CustomComponent, { field: field }))
|
|
602
|
-
}
|
|
603
|
-
];
|
|
604
|
-
await act(async () => {
|
|
605
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: vi.fn() }));
|
|
606
|
-
});
|
|
607
|
-
expect(screen.getByTestId('custom-field')).toHaveTextContent('Custom Field');
|
|
608
|
-
expect(screen.getByText('Help text')).toBeInTheDocument();
|
|
609
|
-
expect(screen.getByText('Label description text')).toBeInTheDocument();
|
|
610
|
-
});
|
|
611
|
-
it('cleans up throttle timeout on unmount', async () => {
|
|
612
|
-
vi.useFakeTimers();
|
|
613
|
-
const { unmount } = render(React.createElement(AkiformBuilder, { fields: defaultFields, onSubmit: mockOnSubmit, onValueChange: mockOnValueChange }));
|
|
614
|
-
await act(async () => {
|
|
615
|
-
fireEvent.change(screen.getByLabelText('Name'), {
|
|
616
|
-
target: { value: 'John Doe' }
|
|
617
|
-
});
|
|
618
|
-
});
|
|
619
|
-
unmount();
|
|
620
|
-
// Advance timers and ensure onValueChange is not called
|
|
621
|
-
await act(async () => {
|
|
622
|
-
vi.advanceTimersByTime(THROTTLE_DELAY + 100);
|
|
623
|
-
});
|
|
624
|
-
expect(mockOnValueChange).not.toHaveBeenCalled();
|
|
625
|
-
vi.useRealTimers();
|
|
626
|
-
});
|
|
627
|
-
it('handles custom field with undefined render prop', async () => {
|
|
628
|
-
const fields = [
|
|
629
|
-
{
|
|
630
|
-
key: 'customField',
|
|
631
|
-
label: 'Custom Field',
|
|
632
|
-
type: 'custom',
|
|
633
|
-
render: undefined
|
|
634
|
-
}
|
|
635
|
-
];
|
|
636
|
-
await act(async () => {
|
|
637
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: vi.fn() }));
|
|
638
|
-
});
|
|
639
|
-
// The custom field should not be rendered, but the form should not crash
|
|
640
|
-
expect(screen.queryByLabelText('Custom Field')).not.toBeInTheDocument();
|
|
641
|
-
// The form should still be rendered
|
|
642
|
-
expect(screen.getByTestId('akiform-builder')).toBeInTheDocument();
|
|
643
|
-
});
|
|
644
|
-
it('renders custom submit and reset button props', async () => {
|
|
645
|
-
const submitButtonProps = {
|
|
646
|
-
className: 'custom-submit-btn',
|
|
647
|
-
children: 'submit button'
|
|
648
|
-
};
|
|
649
|
-
const resetButtonProps = {
|
|
650
|
-
className: 'custom-reset-btn',
|
|
651
|
-
children: 'reset button'
|
|
652
|
-
};
|
|
653
|
-
await act(async () => {
|
|
654
|
-
render(React.createElement(AkiformBuilder, { fields: defaultFields, onSubmit: mockOnSubmit, showResetButton: true, onReset: mockOnReset, submitButtonProps: submitButtonProps, resetButtonProps: resetButtonProps }));
|
|
655
|
-
});
|
|
656
|
-
const submitButton = screen
|
|
657
|
-
.getByText(submitButtonProps.children)
|
|
658
|
-
.closest('button');
|
|
659
|
-
const resetButton = screen
|
|
660
|
-
.getByText(resetButtonProps.children)
|
|
661
|
-
.closest('button');
|
|
662
|
-
expect(submitButton).toBeInTheDocument();
|
|
663
|
-
expect(resetButton).toBeInTheDocument();
|
|
664
|
-
expect(submitButton).toHaveClass(submitButtonProps.className);
|
|
665
|
-
expect(resetButton).toHaveClass(resetButtonProps.className);
|
|
666
|
-
await userEvent.click(resetButton);
|
|
667
|
-
expect(mockOnReset).toHaveBeenCalled();
|
|
668
|
-
await userEvent.click(submitButton);
|
|
669
|
-
expect(mockOnSubmit).toHaveBeenCalled();
|
|
670
|
-
});
|
|
671
|
-
});
|
|
672
|
-
describe('AkiformBuilder in controlled mode', () => {
|
|
673
|
-
it('uses provided values in controlled mode', async () => {
|
|
674
|
-
const fields = [
|
|
675
|
-
{ key: 'name', label: 'Name', type: 'text' },
|
|
676
|
-
{ key: 'age', label: 'Age', type: 'number' }
|
|
677
|
-
];
|
|
678
|
-
const controlledValues = { name: 'John Doe', age: 30 };
|
|
679
|
-
const onChangeMock = vi.fn();
|
|
680
|
-
await act(async () => {
|
|
681
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: vi.fn(), controlled: true, values: controlledValues, onValueChange: onChangeMock }));
|
|
682
|
-
});
|
|
683
|
-
expect(screen.getByLabelText('Name')).toHaveValue('John Doe');
|
|
684
|
-
expect(screen.getByLabelText('Age')).toHaveValue('30');
|
|
685
|
-
});
|
|
686
|
-
it('calls onValueChange when form values change in controlled mode', async () => {
|
|
687
|
-
const fields = [
|
|
688
|
-
{ key: 'name', label: 'Name', type: 'text' }
|
|
689
|
-
];
|
|
690
|
-
const onChangeMock = vi.fn();
|
|
691
|
-
await act(async () => {
|
|
692
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: vi.fn(), controlled: true, values: { name: '' }, onValueChange: onChangeMock }));
|
|
693
|
-
});
|
|
694
|
-
await act(async () => {
|
|
695
|
-
fireEvent.change(screen.getByLabelText('Name'), {
|
|
696
|
-
target: { value: 'Jane Doe' }
|
|
697
|
-
});
|
|
698
|
-
});
|
|
699
|
-
await waitFor(() => {
|
|
700
|
-
expect(onChangeMock).toHaveBeenCalledWith(expect.objectContaining({ name: 'Jane Doe' }));
|
|
701
|
-
});
|
|
702
|
-
});
|
|
703
|
-
it('calls onValueChange immediately in controlled mode', async () => {
|
|
704
|
-
const onValueChangeMock = vi.fn();
|
|
705
|
-
await act(async () => {
|
|
706
|
-
render(React.createElement(AkiformBuilder, { fields: defaultFields, onSubmit: mockOnSubmit, controlled: true, values: { name: 'John Doe', age: 30 }, onValueChange: onValueChangeMock }));
|
|
707
|
-
});
|
|
708
|
-
expect(onValueChangeMock).toHaveBeenCalledWith(expect.objectContaining({ name: 'John Doe', age: 30 }));
|
|
709
|
-
});
|
|
710
|
-
it('handles conditional rendering and disabling of fields', async () => {
|
|
711
|
-
const fields = [
|
|
712
|
-
{ key: 'showField', label: 'Show Field', type: 'checkbox' },
|
|
713
|
-
{
|
|
714
|
-
key: 'conditionalField',
|
|
715
|
-
label: 'Conditional Field',
|
|
716
|
-
type: 'text',
|
|
717
|
-
config: { visible: values => values.showField }
|
|
718
|
-
},
|
|
719
|
-
{ key: 'enableField', label: 'Enable Field', type: 'checkbox' },
|
|
720
|
-
{
|
|
721
|
-
key: 'disableableField',
|
|
722
|
-
label: 'Disableable Field',
|
|
723
|
-
type: 'text',
|
|
724
|
-
config: { disabled: values => !values.enableField }
|
|
725
|
-
}
|
|
726
|
-
];
|
|
727
|
-
const onValueChangeMock = vi.fn();
|
|
728
|
-
await act(async () => {
|
|
729
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: vi.fn(), controlled: true, values: { showField: false, enableField: false }, onValueChange: onValueChangeMock }));
|
|
730
|
-
});
|
|
731
|
-
expect(screen.queryByLabelText('Conditional Field')).not.toBeInTheDocument();
|
|
732
|
-
expect(screen.getByLabelText('Disableable Field')).toBeDisabled();
|
|
733
|
-
await act(async () => {
|
|
734
|
-
fireEvent.click(screen.getByLabelText('Show Field'));
|
|
735
|
-
fireEvent.click(screen.getByLabelText('Enable Field'));
|
|
736
|
-
});
|
|
737
|
-
expect(screen.getByLabelText('Conditional Field')).toBeInTheDocument();
|
|
738
|
-
expect(screen.getByLabelText('Disableable Field')).not.toBeDisabled();
|
|
739
|
-
expect(onValueChangeMock).toHaveBeenCalledWith(expect.objectContaining({
|
|
740
|
-
showField: true,
|
|
741
|
-
enableField: true
|
|
742
|
-
}));
|
|
743
|
-
});
|
|
744
|
-
});
|
|
745
|
-
describe('FieldArrayComponent', () => {
|
|
746
|
-
it('renders field array with initial values', async () => {
|
|
747
|
-
const fields = [
|
|
748
|
-
{
|
|
749
|
-
key: 'items',
|
|
750
|
-
label: 'Items',
|
|
751
|
-
type: 'fieldArray',
|
|
752
|
-
fields: [
|
|
753
|
-
{ key: 'name', label: 'Item Name', type: 'text' },
|
|
754
|
-
{ key: 'quantity', label: 'Quantity', type: 'number' }
|
|
755
|
-
]
|
|
756
|
-
}
|
|
757
|
-
];
|
|
758
|
-
const initialValues = {
|
|
759
|
-
items: [
|
|
760
|
-
{ name: 'Item 1', quantity: 1 },
|
|
761
|
-
{ name: 'Item 2', quantity: 2 }
|
|
762
|
-
]
|
|
763
|
-
};
|
|
764
|
-
await act(async () => {
|
|
765
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: vi.fn(), initialValues: initialValues }));
|
|
766
|
-
});
|
|
767
|
-
await expandCollapsable();
|
|
768
|
-
expect(screen.getAllByLabelText('Item Name')).toHaveLength(2);
|
|
769
|
-
expect(screen.getAllByLabelText('Quantity')).toHaveLength(2);
|
|
770
|
-
});
|
|
771
|
-
it('adds and removes field array items', async () => {
|
|
772
|
-
const user = userEvent.setup();
|
|
773
|
-
const fields = [
|
|
774
|
-
{
|
|
775
|
-
key: 'items',
|
|
776
|
-
label: 'Items',
|
|
777
|
-
type: 'fieldArray',
|
|
778
|
-
fields: [{ key: 'name', label: 'Item Name', type: 'text' }]
|
|
779
|
-
}
|
|
780
|
-
];
|
|
781
|
-
await act(async () => {
|
|
782
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: vi.fn() }));
|
|
783
|
-
});
|
|
784
|
-
// Add an item
|
|
785
|
-
await user.click(screen.getByTestId('items-add-button'));
|
|
786
|
-
await expandCollapsable();
|
|
787
|
-
expect(screen.getByLabelText('Item Name')).toBeInTheDocument();
|
|
788
|
-
// Add another item
|
|
789
|
-
await user.click(screen.getByLabelText('Add Items'));
|
|
790
|
-
await expandCollapsable();
|
|
791
|
-
expect(screen.getAllByLabelText('Item Name')).toHaveLength(2);
|
|
792
|
-
// Remove an item
|
|
793
|
-
await user.click(screen.getAllByLabelText('Remove Items')[0]);
|
|
794
|
-
await expandCollapsable();
|
|
795
|
-
expect(screen.getAllByLabelText('Item Name')).toHaveLength(1);
|
|
796
|
-
});
|
|
797
|
-
it('handles field array with conditional fields', async () => {
|
|
798
|
-
const user = userEvent.setup();
|
|
799
|
-
const fields = [
|
|
800
|
-
{
|
|
801
|
-
key: 'items',
|
|
802
|
-
label: 'Items',
|
|
803
|
-
type: 'fieldArray',
|
|
804
|
-
fields: [
|
|
805
|
-
{ key: 'name', label: 'Item Name', type: 'text' },
|
|
806
|
-
{
|
|
807
|
-
key: 'description',
|
|
808
|
-
label: 'Description',
|
|
809
|
-
type: 'text',
|
|
810
|
-
config: {
|
|
811
|
-
visible: values => values.name && values.name.length > 5
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
]
|
|
815
|
-
}
|
|
816
|
-
];
|
|
817
|
-
const { rerender } = render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: vi.fn() }));
|
|
818
|
-
await expandCollapsable();
|
|
819
|
-
// Add an item
|
|
820
|
-
await user.click(screen.getByLabelText('Add Items'));
|
|
821
|
-
await expandCollapsable();
|
|
822
|
-
// Description should not be visible initially
|
|
823
|
-
expect(screen.queryByLabelText('Description')).not.toBeInTheDocument();
|
|
824
|
-
// Enter a name longer than 5 characters
|
|
825
|
-
await act(async () => {
|
|
826
|
-
fireEvent.change(screen.getByLabelText('Item Name'), {
|
|
827
|
-
target: { value: 'Long Name' }
|
|
828
|
-
});
|
|
829
|
-
});
|
|
830
|
-
// Force a re-render to trigger the conditional rendering
|
|
831
|
-
rerender(React.createElement(AkiformBuilder, { fields: fields, onSubmit: vi.fn() }));
|
|
832
|
-
// Description should now be visible
|
|
833
|
-
await waitFor(() => {
|
|
834
|
-
expect(screen.getByLabelText('Description')).toBeInTheDocument();
|
|
835
|
-
});
|
|
836
|
-
});
|
|
837
|
-
});
|
|
838
|
-
describe('Edge cases and uncovered scenarios', () => {
|
|
839
|
-
it('handles field array with no fields', async () => {
|
|
840
|
-
const fields = [
|
|
841
|
-
{
|
|
842
|
-
key: 'emptyFieldArray',
|
|
843
|
-
label: 'Empty Field Array',
|
|
844
|
-
type: 'fieldArray',
|
|
845
|
-
fields: []
|
|
846
|
-
}
|
|
847
|
-
];
|
|
848
|
-
await act(async () => {
|
|
849
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: vi.fn() }));
|
|
850
|
-
});
|
|
851
|
-
// The field array should be rendered with an "Add" button
|
|
852
|
-
expect(screen.getByTestId('emptyFieldArray-add-button')).toBeInTheDocument();
|
|
853
|
-
});
|
|
854
|
-
it('handles form submission with field array', async () => {
|
|
855
|
-
const user = userEvent.setup();
|
|
856
|
-
const fields = [
|
|
857
|
-
{
|
|
858
|
-
key: 'items',
|
|
859
|
-
label: 'Items',
|
|
860
|
-
type: 'fieldArray',
|
|
861
|
-
fields: [
|
|
862
|
-
{ key: 'name', label: 'Item Name', type: 'text' },
|
|
863
|
-
{ key: 'quantity', label: 'Quantity', type: 'number' }
|
|
864
|
-
]
|
|
865
|
-
}
|
|
866
|
-
];
|
|
867
|
-
const onSubmitMock = vi.fn();
|
|
868
|
-
await act(async () => {
|
|
869
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: onSubmitMock }));
|
|
870
|
-
});
|
|
871
|
-
// Add two items
|
|
872
|
-
await user.click(screen.getByTestId('items-add-button'));
|
|
873
|
-
await user.click(screen.getByLabelText('Add Items'));
|
|
874
|
-
await expandCollapsable();
|
|
875
|
-
// Fill in the fields
|
|
876
|
-
const itemNames = screen.getAllByLabelText('Item Name');
|
|
877
|
-
const quantities = screen.getAllByLabelText('Quantity');
|
|
878
|
-
await act(async () => {
|
|
879
|
-
fireEvent.change(itemNames[0], { target: { value: 'Item 1' } });
|
|
880
|
-
fireEvent.change(quantities[0], { target: { value: '5' } });
|
|
881
|
-
fireEvent.change(itemNames[1], { target: { value: 'Item 2' } });
|
|
882
|
-
fireEvent.change(quantities[1], { target: { value: '10' } });
|
|
883
|
-
});
|
|
884
|
-
// Submit the form
|
|
885
|
-
await act(async () => {
|
|
886
|
-
fireEvent.submit(screen.getByTestId('akiform-builder'));
|
|
887
|
-
});
|
|
888
|
-
await waitFor(() => {
|
|
889
|
-
expect(onSubmitMock).toHaveBeenCalledWith(expect.objectContaining({
|
|
890
|
-
items: [
|
|
891
|
-
{ name: 'Item 1', quantity: 5 },
|
|
892
|
-
{ name: 'Item 2', quantity: 10 }
|
|
893
|
-
]
|
|
894
|
-
}), expect.anything());
|
|
895
|
-
});
|
|
896
|
-
});
|
|
897
|
-
it('handles form with all field types', async () => {
|
|
898
|
-
const fields = [
|
|
899
|
-
{ key: 'text', label: 'Text', type: 'text' },
|
|
900
|
-
{ key: 'number', label: 'Number', type: 'number' },
|
|
901
|
-
{
|
|
902
|
-
key: 'select',
|
|
903
|
-
label: 'Select',
|
|
904
|
-
type: 'select',
|
|
905
|
-
options: [{ value: 'option1', label: 'Option 1' }]
|
|
906
|
-
},
|
|
907
|
-
{ key: 'checkbox', label: 'Checkbox', type: 'checkbox' },
|
|
908
|
-
{ key: 'date', label: 'Date', type: 'date' },
|
|
909
|
-
{ key: 'textarea', label: 'Textarea', type: 'textarea' },
|
|
910
|
-
{
|
|
911
|
-
key: 'fieldArray',
|
|
912
|
-
label: 'Field Array',
|
|
913
|
-
type: 'fieldArray',
|
|
914
|
-
fields: [{ key: 'subfield', label: 'Subfield', type: 'text' }]
|
|
915
|
-
},
|
|
916
|
-
{
|
|
917
|
-
key: 'custom',
|
|
918
|
-
label: 'Custom',
|
|
919
|
-
type: 'custom',
|
|
920
|
-
render: () => React.createElement("div", { "data-testid": "custom-field" }, "Custom Field")
|
|
921
|
-
}
|
|
922
|
-
];
|
|
923
|
-
await act(async () => {
|
|
924
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: vi.fn() }));
|
|
925
|
-
});
|
|
926
|
-
expect(screen.getByLabelText('Text')).toBeInTheDocument();
|
|
927
|
-
expect(screen.getByLabelText('Number')).toBeInTheDocument();
|
|
928
|
-
expect(screen.getByLabelText('Select')).toBeInTheDocument();
|
|
929
|
-
expect(screen.getByLabelText('Checkbox')).toBeInTheDocument();
|
|
930
|
-
expect(screen.getByLabelText('Date')).toBeInTheDocument();
|
|
931
|
-
expect(screen.getByLabelText('Textarea')).toBeInTheDocument();
|
|
932
|
-
expect(screen.getByTestId('fieldArray-add-button')).toBeInTheDocument(); // For field array
|
|
933
|
-
expect(screen.getByTestId('custom-field')).toBeInTheDocument();
|
|
934
|
-
});
|
|
935
|
-
it('handles form with inline layout', async () => {
|
|
936
|
-
await act(async () => {
|
|
937
|
-
render(React.createElement(AkiformBuilder, { fields: defaultFields, onSubmit: vi.fn(), layout: "inline" }));
|
|
938
|
-
});
|
|
939
|
-
const form = screen.getByTestId('akiform-builder');
|
|
940
|
-
expect(form).toHaveClass('akinon-form-inline');
|
|
941
|
-
});
|
|
942
|
-
it('handles form reset with custom reset handler', async () => {
|
|
943
|
-
const onResetMock = vi.fn();
|
|
944
|
-
await act(async () => {
|
|
945
|
-
render(React.createElement(AkiformBuilder, { fields: defaultFields, onSubmit: vi.fn(), onReset: onResetMock, showResetButton: true }));
|
|
946
|
-
});
|
|
947
|
-
await act(async () => {
|
|
948
|
-
fireEvent.click(screen.getByText('RESET'));
|
|
949
|
-
});
|
|
950
|
-
expect(onResetMock).toHaveBeenCalled();
|
|
951
|
-
});
|
|
952
|
-
});
|
|
953
|
-
describe('AkiformBuilder with FieldBuilder', () => {
|
|
954
|
-
it('renders form fields created with FieldBuilder', async () => {
|
|
955
|
-
const fields = [
|
|
956
|
-
field()
|
|
957
|
-
.key('name')
|
|
958
|
-
.label('Name')
|
|
959
|
-
.type('text')
|
|
960
|
-
.placeholder('Enter your name')
|
|
961
|
-
.help('Help text')
|
|
962
|
-
.labelDescription('Label description text')
|
|
963
|
-
.build(),
|
|
964
|
-
field()
|
|
965
|
-
.key('age')
|
|
966
|
-
.label('Age')
|
|
967
|
-
.type('number')
|
|
968
|
-
.placeholder('Enter your age')
|
|
969
|
-
.build(),
|
|
970
|
-
field()
|
|
971
|
-
.key('country')
|
|
972
|
-
.label('Country')
|
|
973
|
-
.type('select')
|
|
974
|
-
.options([
|
|
975
|
-
{ value: 'us', label: 'United States' },
|
|
976
|
-
{ value: 'ca', label: 'Canada' }
|
|
977
|
-
])
|
|
978
|
-
.build(),
|
|
979
|
-
field()
|
|
980
|
-
.key('subscribe')
|
|
981
|
-
.label('Subscribe to newsletter')
|
|
982
|
-
.type('checkbox')
|
|
983
|
-
.build(),
|
|
984
|
-
field().key('birthdate').label('Birth Date').type('date').build(),
|
|
985
|
-
field().key('description').label('Description').type('textarea').build()
|
|
986
|
-
];
|
|
987
|
-
await act(async () => {
|
|
988
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: vi.fn() }));
|
|
989
|
-
});
|
|
990
|
-
expect(screen.getByText('Name')).toBeInTheDocument();
|
|
991
|
-
expect(screen.getByText('Label description text')).toBeInTheDocument();
|
|
992
|
-
expect(screen.getByText('Help text')).toBeInTheDocument();
|
|
993
|
-
expect(screen.getByLabelText('Age')).toBeInTheDocument();
|
|
994
|
-
expect(screen.getByLabelText('Country')).toBeInTheDocument();
|
|
995
|
-
expect(screen.getByLabelText('Subscribe to newsletter')).toBeInTheDocument();
|
|
996
|
-
expect(screen.getByLabelText('Birth Date')).toBeInTheDocument();
|
|
997
|
-
expect(screen.getByLabelText('Description')).toBeInTheDocument();
|
|
998
|
-
});
|
|
999
|
-
it('handles form submission with fields created by FieldBuilder', async () => {
|
|
1000
|
-
const fields = [
|
|
1001
|
-
field()
|
|
1002
|
-
.key('name')
|
|
1003
|
-
.label('Name')
|
|
1004
|
-
.type('text')
|
|
1005
|
-
.validation(akival.string().required('Name is required'))
|
|
1006
|
-
.build(),
|
|
1007
|
-
field()
|
|
1008
|
-
.key('age')
|
|
1009
|
-
.label('Age')
|
|
1010
|
-
.type('number')
|
|
1011
|
-
.validation(akival.number().min(18, 'Must be at least 18'))
|
|
1012
|
-
.build()
|
|
1013
|
-
];
|
|
1014
|
-
const onSubmitMock = vi.fn();
|
|
1015
|
-
await act(async () => {
|
|
1016
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: onSubmitMock }));
|
|
1017
|
-
});
|
|
1018
|
-
await act(async () => {
|
|
1019
|
-
fireEvent.change(screen.getByRole('textbox', { name: /name/i }), {
|
|
1020
|
-
target: { value: 'John Doe' }
|
|
1021
|
-
});
|
|
1022
|
-
fireEvent.change(screen.getByRole('spinbutton', { name: /age/i }), {
|
|
1023
|
-
target: { value: '25' }
|
|
1024
|
-
});
|
|
1025
|
-
fireEvent.submit(screen.getByTestId('akiform-builder'));
|
|
1026
|
-
});
|
|
1027
|
-
await waitFor(() => {
|
|
1028
|
-
expect(onSubmitMock).toHaveBeenCalledWith(expect.objectContaining({
|
|
1029
|
-
name: 'John Doe',
|
|
1030
|
-
age: 25
|
|
1031
|
-
}), expect.anything());
|
|
1032
|
-
});
|
|
1033
|
-
});
|
|
1034
|
-
it('handles form with custom field created by FieldBuilder', async () => {
|
|
1035
|
-
const CustomComponent = ({ field }) => (React.createElement("div", { "data-testid": "custom-field" }, field.label));
|
|
1036
|
-
const fields = [
|
|
1037
|
-
field()
|
|
1038
|
-
.key('customField')
|
|
1039
|
-
.label('Custom Field')
|
|
1040
|
-
.type('custom')
|
|
1041
|
-
.help('Help text')
|
|
1042
|
-
.labelDescription('Label description text')
|
|
1043
|
-
.render(({ field }) => React.createElement(CustomComponent, { field: field }))
|
|
1044
|
-
.build()
|
|
1045
|
-
];
|
|
1046
|
-
await act(async () => {
|
|
1047
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: vi.fn() }));
|
|
1048
|
-
});
|
|
1049
|
-
expect(screen.getByTestId('custom-field')).toHaveTextContent('Custom Field');
|
|
1050
|
-
expect(screen.getByText('Help text')).toBeInTheDocument();
|
|
1051
|
-
expect(screen.getByText('Label description text')).toBeInTheDocument();
|
|
1052
|
-
});
|
|
1053
|
-
it('handles form with field array created by FieldBuilder', async () => {
|
|
1054
|
-
const user = userEvent.setup();
|
|
1055
|
-
const fields = [
|
|
1056
|
-
field()
|
|
1057
|
-
.key('addresses')
|
|
1058
|
-
.label('Addresses')
|
|
1059
|
-
.type('fieldArray')
|
|
1060
|
-
.fields([
|
|
1061
|
-
field()
|
|
1062
|
-
.key('street')
|
|
1063
|
-
.label('Street')
|
|
1064
|
-
.type('text')
|
|
1065
|
-
.help('Help text')
|
|
1066
|
-
.labelDescription('Label description text')
|
|
1067
|
-
.placeholder('Enter your street')
|
|
1068
|
-
.build(),
|
|
1069
|
-
field().key('city').label('City').type('text').build()
|
|
1070
|
-
])
|
|
1071
|
-
.build()
|
|
1072
|
-
];
|
|
1073
|
-
const onSubmitMock = vi.fn();
|
|
1074
|
-
await act(async () => {
|
|
1075
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: onSubmitMock }));
|
|
1076
|
-
});
|
|
1077
|
-
// Add an address
|
|
1078
|
-
await user.click(screen.getByTestId('addresses-add-button'));
|
|
1079
|
-
await expandCollapsable();
|
|
1080
|
-
//Check rendered fields
|
|
1081
|
-
expect(screen.getByText('Street')).toBeInTheDocument();
|
|
1082
|
-
expect(screen.getByText('Help text')).toBeInTheDocument();
|
|
1083
|
-
expect(screen.getByText('Label description text')).toBeInTheDocument();
|
|
1084
|
-
expect(screen.getByLabelText('City')).toBeInTheDocument();
|
|
1085
|
-
// Fill in the fields
|
|
1086
|
-
await act(async () => {
|
|
1087
|
-
fireEvent.change(screen.getByPlaceholderText('Enter your street'), {
|
|
1088
|
-
target: { value: '123 Main St' }
|
|
1089
|
-
});
|
|
1090
|
-
fireEvent.change(screen.getByLabelText('City'), {
|
|
1091
|
-
target: { value: 'Anytown' }
|
|
1092
|
-
});
|
|
1093
|
-
});
|
|
1094
|
-
// Submit the form
|
|
1095
|
-
await act(async () => {
|
|
1096
|
-
fireEvent.submit(screen.getByTestId('akiform-builder'));
|
|
1097
|
-
});
|
|
1098
|
-
await waitFor(() => {
|
|
1099
|
-
expect(onSubmitMock).toHaveBeenCalledWith(expect.objectContaining({
|
|
1100
|
-
addresses: [{ street: '123 Main St', city: 'Anytown' }]
|
|
1101
|
-
}), expect.anything());
|
|
1102
|
-
});
|
|
1103
|
-
});
|
|
1104
|
-
it('handles conditional rendering with FieldBuilder', async () => {
|
|
1105
|
-
const fields = [
|
|
1106
|
-
field().key('showField').label('Show Field').type('checkbox').build(),
|
|
1107
|
-
field()
|
|
1108
|
-
.key('conditionalField')
|
|
1109
|
-
.label('Conditional Field')
|
|
1110
|
-
.type('text')
|
|
1111
|
-
.config({ visible: (values) => values.showField })
|
|
1112
|
-
.build()
|
|
1113
|
-
];
|
|
1114
|
-
await act(async () => {
|
|
1115
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: vi.fn() }));
|
|
1116
|
-
});
|
|
1117
|
-
expect(screen.queryByLabelText('Conditional Field')).not.toBeInTheDocument();
|
|
1118
|
-
await act(async () => {
|
|
1119
|
-
fireEvent.click(screen.getByLabelText('Show Field'));
|
|
1120
|
-
});
|
|
1121
|
-
expect(screen.getByLabelText('Conditional Field')).toBeInTheDocument();
|
|
1122
|
-
});
|
|
1123
|
-
it('handles disabled fields with FieldBuilder', async () => {
|
|
1124
|
-
const fields = [
|
|
1125
|
-
field()
|
|
1126
|
-
.key('disabledField')
|
|
1127
|
-
.label('Disabled Field')
|
|
1128
|
-
.type('text')
|
|
1129
|
-
.config({ disabled: true })
|
|
1130
|
-
.build()
|
|
1131
|
-
];
|
|
1132
|
-
await act(async () => {
|
|
1133
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: vi.fn() }));
|
|
1134
|
-
});
|
|
1135
|
-
expect(screen.getByLabelText('Disabled Field')).toBeDisabled();
|
|
1136
|
-
});
|
|
1137
|
-
});
|
|
1138
|
-
describe('Accessibility features', () => {
|
|
1139
|
-
it('renders form with proper ARIA attributes', async () => {
|
|
1140
|
-
const fields = [
|
|
1141
|
-
{
|
|
1142
|
-
key: 'name',
|
|
1143
|
-
label: 'Name',
|
|
1144
|
-
type: 'text',
|
|
1145
|
-
validation: akival.string().required()
|
|
1146
|
-
},
|
|
1147
|
-
{ key: 'age', label: 'Age', type: 'number' }
|
|
1148
|
-
];
|
|
1149
|
-
await act(async () => {
|
|
1150
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: vi.fn() }));
|
|
1151
|
-
});
|
|
1152
|
-
const form = screen.getByRole('form');
|
|
1153
|
-
expect(form).toHaveAttribute('aria-label', 'Form');
|
|
1154
|
-
const nameInput = screen.getByRole('textbox', { name: /name/i });
|
|
1155
|
-
expect(nameInput).toHaveAttribute('aria-required', 'true');
|
|
1156
|
-
expect(nameInput).toHaveAttribute('aria-invalid', 'false');
|
|
1157
|
-
const ageInput = screen.getByRole('spinbutton', { name: /age/i });
|
|
1158
|
-
expect(ageInput).toHaveAttribute('aria-required', 'false');
|
|
1159
|
-
expect(ageInput).toHaveAttribute('aria-invalid', 'false');
|
|
1160
|
-
});
|
|
1161
|
-
it('updates aria-invalid attribute when form is submitted with errors', async () => {
|
|
1162
|
-
const fields = [
|
|
1163
|
-
{
|
|
1164
|
-
key: 'name',
|
|
1165
|
-
label: 'Name',
|
|
1166
|
-
type: 'text',
|
|
1167
|
-
validation: akival.string().required('Name is required')
|
|
1168
|
-
}
|
|
1169
|
-
];
|
|
1170
|
-
await act(async () => {
|
|
1171
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: vi.fn() }));
|
|
1172
|
-
});
|
|
1173
|
-
const nameInput = screen.getByRole('textbox', { name: /name/i });
|
|
1174
|
-
expect(nameInput).toHaveAttribute('aria-invalid', 'false');
|
|
1175
|
-
await act(async () => {
|
|
1176
|
-
fireEvent.submit(screen.getByRole('form'));
|
|
1177
|
-
});
|
|
1178
|
-
await waitFor(() => {
|
|
1179
|
-
expect(nameInput).toHaveAttribute('aria-invalid', 'true');
|
|
1180
|
-
expect(screen.getByText('Name is required')).toBeInTheDocument();
|
|
1181
|
-
});
|
|
1182
|
-
});
|
|
1183
|
-
it('renders field array with proper ARIA attributes', async () => {
|
|
1184
|
-
const user = userEvent.setup();
|
|
1185
|
-
const fields = [
|
|
1186
|
-
{
|
|
1187
|
-
key: 'addresses',
|
|
1188
|
-
label: 'Addresses',
|
|
1189
|
-
type: 'fieldArray',
|
|
1190
|
-
fields: [
|
|
1191
|
-
{ key: 'street', label: 'Street', type: 'text' },
|
|
1192
|
-
{ key: 'city', label: 'City', type: 'text' }
|
|
1193
|
-
]
|
|
1194
|
-
}
|
|
1195
|
-
];
|
|
1196
|
-
await act(async () => {
|
|
1197
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: vi.fn() }));
|
|
1198
|
-
});
|
|
1199
|
-
await user.click(screen.getByTestId('addresses-add-button'));
|
|
1200
|
-
await expandCollapsable();
|
|
1201
|
-
const fieldArrayGroup = screen.getByRole('group', { name: 'Addresses' });
|
|
1202
|
-
expect(fieldArrayGroup).toBeInTheDocument();
|
|
1203
|
-
const addButton = screen.getByLabelText('Add Addresses');
|
|
1204
|
-
expect(addButton).toBeInTheDocument();
|
|
1205
|
-
await user.click(addButton);
|
|
1206
|
-
await expandCollapsable();
|
|
1207
|
-
const removeButton = screen.getAllByLabelText('Remove Addresses')[1];
|
|
1208
|
-
expect(removeButton).toBeInTheDocument();
|
|
1209
|
-
});
|
|
1210
|
-
it('supports keyboard navigation', async () => {
|
|
1211
|
-
const user = userEvent.setup();
|
|
1212
|
-
const fields = [
|
|
1213
|
-
{ key: 'name', label: 'Name', type: 'text' },
|
|
1214
|
-
{ key: 'age', label: 'Age', type: 'number' }
|
|
1215
|
-
];
|
|
1216
|
-
await act(async () => {
|
|
1217
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: vi.fn(), showResetButton: true }));
|
|
1218
|
-
});
|
|
1219
|
-
const nameInput = screen.getByLabelText('Name');
|
|
1220
|
-
const ageInput = screen.getByLabelText('Age');
|
|
1221
|
-
const submitButton = screen.getByRole('button', { name: 'SUBMIT' });
|
|
1222
|
-
const resetButton = screen.getByRole('button', { name: 'RESET' });
|
|
1223
|
-
await user.tab();
|
|
1224
|
-
expect(document.activeElement).toBe(nameInput);
|
|
1225
|
-
await user.tab();
|
|
1226
|
-
expect(document.activeElement).toBe(ageInput);
|
|
1227
|
-
await user.tab();
|
|
1228
|
-
expect(document.activeElement).toBe(submitButton);
|
|
1229
|
-
await user.tab();
|
|
1230
|
-
expect(document.activeElement).toBe(resetButton);
|
|
1231
|
-
});
|
|
1232
|
-
});
|
|
1233
|
-
describe('AkiformBuilder with sections', () => {
|
|
1234
|
-
it('renders form with sections', async () => {
|
|
1235
|
-
const fields = [
|
|
1236
|
-
field()
|
|
1237
|
-
.key('personalInfo')
|
|
1238
|
-
.label('Personal Information')
|
|
1239
|
-
.type('section')
|
|
1240
|
-
.fields([
|
|
1241
|
-
field()
|
|
1242
|
-
.key('name')
|
|
1243
|
-
.label('Name')
|
|
1244
|
-
.type('text')
|
|
1245
|
-
.help('Help text')
|
|
1246
|
-
.labelDescription('Label description text')
|
|
1247
|
-
.build(),
|
|
1248
|
-
field().key('age').label('Age').type('number').build()
|
|
1249
|
-
])
|
|
1250
|
-
.collapsible(true)
|
|
1251
|
-
.defaultExpanded(true)
|
|
1252
|
-
.build(),
|
|
1253
|
-
field()
|
|
1254
|
-
.key('contactInfo')
|
|
1255
|
-
.label('Contact Information')
|
|
1256
|
-
.type('section')
|
|
1257
|
-
.collapsible(true)
|
|
1258
|
-
.defaultExpanded(false)
|
|
1259
|
-
.fields([
|
|
1260
|
-
field().key('email').label('Email').type('text').build(),
|
|
1261
|
-
field().key('phone').label('Phone').type('text').build()
|
|
1262
|
-
])
|
|
1263
|
-
.build()
|
|
1264
|
-
];
|
|
1265
|
-
await act(async () => {
|
|
1266
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: vi.fn() }));
|
|
1267
|
-
});
|
|
1268
|
-
expect(screen.getByText('Personal Information')).toBeInTheDocument();
|
|
1269
|
-
expect(screen.getByText('Contact Information')).toBeInTheDocument();
|
|
1270
|
-
expect(screen.getByText('Name')).toBeInTheDocument();
|
|
1271
|
-
expect(screen.getByText('Help text')).toBeInTheDocument();
|
|
1272
|
-
expect(screen.getByText('Label description text')).toBeInTheDocument();
|
|
1273
|
-
expect(screen.getByLabelText('Age')).toBeInTheDocument();
|
|
1274
|
-
// Check if the first section is expanded by default
|
|
1275
|
-
expect(screen.getByText('Name').closest('label')).toBeVisible();
|
|
1276
|
-
// Check if the second section is collapsed by default
|
|
1277
|
-
expect(screen.queryByLabelText('Email')).not.toBeInTheDocument();
|
|
1278
|
-
// Expand the second section
|
|
1279
|
-
const contactInfoSection = screen.getByText('Contact Information');
|
|
1280
|
-
await userEvent.click(contactInfoSection);
|
|
1281
|
-
// Wait for the section to expand
|
|
1282
|
-
await waitFor(() => {
|
|
1283
|
-
expect(screen.getByLabelText('Email')).toBeInTheDocument();
|
|
1284
|
-
});
|
|
1285
|
-
// Check if the Email field is now visible
|
|
1286
|
-
// const emailInput = screen.getByLabelText('Email');
|
|
1287
|
-
// expect(emailInput).toBeVisible();
|
|
1288
|
-
// Additional debug information
|
|
1289
|
-
// if (!emailInput.isConnected) {
|
|
1290
|
-
// console.error('Email input is not connected to the DOM');
|
|
1291
|
-
// }
|
|
1292
|
-
// console.log(
|
|
1293
|
-
// 'Email input visibility:',
|
|
1294
|
-
// window.getComputedStyle(emailInput).display
|
|
1295
|
-
// );
|
|
1296
|
-
});
|
|
1297
|
-
it('handles form submission with sections', async () => {
|
|
1298
|
-
const onSubmitMock = vi.fn();
|
|
1299
|
-
const fields = [
|
|
1300
|
-
field()
|
|
1301
|
-
.key('personalInfo')
|
|
1302
|
-
.label('Personal Information')
|
|
1303
|
-
.type('section')
|
|
1304
|
-
.fields([
|
|
1305
|
-
field().key('name').label('Name').type('text').build(),
|
|
1306
|
-
field().key('age').label('Age').type('number').build()
|
|
1307
|
-
])
|
|
1308
|
-
.build(),
|
|
1309
|
-
field()
|
|
1310
|
-
.key('contactInfo')
|
|
1311
|
-
.label('Contact Information')
|
|
1312
|
-
.type('section')
|
|
1313
|
-
.fields([field().key('email').label('Email').type('text').build()])
|
|
1314
|
-
.build()
|
|
1315
|
-
];
|
|
1316
|
-
await act(async () => {
|
|
1317
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: onSubmitMock }));
|
|
1318
|
-
});
|
|
1319
|
-
// Expand both sections
|
|
1320
|
-
await userEvent.click(screen.getByText('Personal Information'));
|
|
1321
|
-
await userEvent.click(screen.getByText('Contact Information'));
|
|
1322
|
-
// Fill in the form
|
|
1323
|
-
await userEvent.type(screen.getByLabelText('Name'), 'John Doe', {
|
|
1324
|
-
delay: 1
|
|
1325
|
-
});
|
|
1326
|
-
await userEvent.type(screen.getByLabelText('Age'), '30', { delay: 1 });
|
|
1327
|
-
await userEvent.type(screen.getByLabelText('Email'), 'john@example.com', {
|
|
1328
|
-
delay: 1
|
|
1329
|
-
});
|
|
1330
|
-
// Wait for submit button to become interactable
|
|
1331
|
-
const submitButton = screen.getByRole('button', { name: /submit/i });
|
|
1332
|
-
await waitFor(() => {
|
|
1333
|
-
expect(submitButton).toBeEnabled();
|
|
1334
|
-
});
|
|
1335
|
-
// Submit the form
|
|
1336
|
-
await userEvent.click(submitButton);
|
|
1337
|
-
// Validate submission
|
|
1338
|
-
expect(onSubmitMock).toHaveBeenCalledWith({
|
|
1339
|
-
name: 'John Doe',
|
|
1340
|
-
age: 30,
|
|
1341
|
-
email: 'john@example.com'
|
|
1342
|
-
}, expect.anything());
|
|
1343
|
-
});
|
|
1344
|
-
it('handles conditional rendering within sections', async () => {
|
|
1345
|
-
const fields = [
|
|
1346
|
-
field()
|
|
1347
|
-
.key('personalInfo')
|
|
1348
|
-
.label('Personal Information')
|
|
1349
|
-
.type('section')
|
|
1350
|
-
.fields([
|
|
1351
|
-
field().key('name').label('Name').type('text').build(),
|
|
1352
|
-
field().key('showAge').label('Show Age').type('checkbox').build(),
|
|
1353
|
-
field()
|
|
1354
|
-
.key('age')
|
|
1355
|
-
.label('Age')
|
|
1356
|
-
.type('number')
|
|
1357
|
-
.config({ visible: (values) => values.showAge })
|
|
1358
|
-
.build()
|
|
1359
|
-
])
|
|
1360
|
-
.defaultExpanded(true)
|
|
1361
|
-
.build()
|
|
1362
|
-
];
|
|
1363
|
-
await act(async () => {
|
|
1364
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: vi.fn() }));
|
|
1365
|
-
});
|
|
1366
|
-
expect(screen.getByLabelText('Name')).toBeInTheDocument();
|
|
1367
|
-
expect(screen.getByLabelText('Show Age')).toBeInTheDocument();
|
|
1368
|
-
expect(screen.queryByLabelText('Age')).not.toBeInTheDocument();
|
|
1369
|
-
await userEvent.click(screen.getByLabelText('Show Age'));
|
|
1370
|
-
expect(await screen.findByLabelText('Age')).toBeInTheDocument();
|
|
1371
|
-
});
|
|
1372
|
-
});
|
|
1373
|
-
describe('AkiformBuilder with tooltip', () => {
|
|
1374
|
-
it('renders form field with tooltip as TooltipProps', async () => {
|
|
1375
|
-
const fields = [
|
|
1376
|
-
field()
|
|
1377
|
-
.key('name')
|
|
1378
|
-
.label('Name')
|
|
1379
|
-
.type('text')
|
|
1380
|
-
.tooltip({ title: 'Enter your full name', defaultOpen: true })
|
|
1381
|
-
.build()
|
|
1382
|
-
];
|
|
1383
|
-
await act(async () => {
|
|
1384
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: vi.fn() }));
|
|
1385
|
-
});
|
|
1386
|
-
const tooltipTrigger = screen.getByLabelText('Name');
|
|
1387
|
-
expect(tooltipTrigger).toBeInTheDocument();
|
|
1388
|
-
// Simulate hovering over the field to show the tooltip
|
|
1389
|
-
await act(async () => {
|
|
1390
|
-
fireEvent.mouseEnter(tooltipTrigger);
|
|
1391
|
-
});
|
|
1392
|
-
// Wait for the tooltip to appear
|
|
1393
|
-
await waitFor(() => {
|
|
1394
|
-
expect(screen.getByText('Enter your full name')).toBeInTheDocument();
|
|
1395
|
-
});
|
|
1396
|
-
});
|
|
1397
|
-
it('renders form field with tooltip as string', async () => {
|
|
1398
|
-
const fields = [
|
|
1399
|
-
field()
|
|
1400
|
-
.key('email')
|
|
1401
|
-
.label('Email')
|
|
1402
|
-
.type('text')
|
|
1403
|
-
.tooltip({ title: 'Enter your full name', defaultOpen: true })
|
|
1404
|
-
.build()
|
|
1405
|
-
];
|
|
1406
|
-
await act(async () => {
|
|
1407
|
-
render(React.createElement(AkiformBuilder, { fields: fields, onSubmit: vi.fn() }));
|
|
1408
|
-
});
|
|
1409
|
-
const tooltipTrigger = screen.getByLabelText('Email');
|
|
1410
|
-
expect(tooltipTrigger).toBeInTheDocument();
|
|
1411
|
-
// Simulate hovering over the field to show the tooltip
|
|
1412
|
-
await act(async () => {
|
|
1413
|
-
fireEvent.mouseEnter(tooltipTrigger);
|
|
1414
|
-
});
|
|
1415
|
-
// Wait for the tooltip to appear
|
|
1416
|
-
await waitFor(() => {
|
|
1417
|
-
expect(screen.getByText('Enter your full name')).toBeInTheDocument();
|
|
1418
|
-
});
|
|
1419
|
-
});
|
|
1420
|
-
});
|
|
1421
|
-
});
|