@object-ui/plugin-form 3.3.0 → 3.3.1
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/CHANGELOG.md +11 -0
- package/README.md +21 -1
- package/dist/index.js +109 -66
- package/dist/index.umd.cjs +2 -2
- package/dist/packages/plugin-form/src/DrawerForm.d.ts +2 -0
- package/dist/packages/plugin-form/src/autoLayout.d.ts +11 -4
- package/package.json +42 -10
- package/.turbo/turbo-build.log +0 -32
- package/src/DrawerForm.tsx +0 -410
- package/src/EmbeddableForm.tsx +0 -240
- package/src/FormAnalytics.tsx +0 -209
- package/src/FormSection.tsx +0 -152
- package/src/FormVariants.test.tsx +0 -219
- package/src/ModalForm.tsx +0 -485
- package/src/ObjectForm.msw.test.tsx +0 -156
- package/src/ObjectForm.stories.tsx +0 -85
- package/src/ObjectForm.test.tsx +0 -61
- package/src/ObjectForm.tsx +0 -609
- package/src/SplitForm.tsx +0 -300
- package/src/TabbedForm.tsx +0 -395
- package/src/WizardForm.tsx +0 -502
- package/src/__tests__/EmbeddableFormPrefill.test.tsx +0 -186
- package/src/__tests__/MobileUX.test.tsx +0 -433
- package/src/__tests__/NewVariants.test.tsx +0 -684
- package/src/__tests__/autoLayout.test.ts +0 -339
- package/src/__tests__/form-validation-submit.test.tsx +0 -286
- package/src/autoLayout.ts +0 -166
- package/src/index.tsx +0 -134
- package/tsconfig.json +0 -9
- package/vite.config.ts +0 -58
- package/vitest.config.ts +0 -12
- package/vitest.setup.ts +0 -1
|
@@ -1,684 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ObjectUI
|
|
3
|
-
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
-
*
|
|
5
|
-
* This source code is licensed under the MIT license found in the
|
|
6
|
-
* LICENSE file in the root directory of this source tree.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
10
|
-
import { render, screen, waitFor } from '@testing-library/react';
|
|
11
|
-
import React from 'react';
|
|
12
|
-
import { ObjectForm } from '../ObjectForm';
|
|
13
|
-
import { SplitForm } from '../SplitForm';
|
|
14
|
-
import { DrawerForm } from '../DrawerForm';
|
|
15
|
-
import { ModalForm } from '../ModalForm';
|
|
16
|
-
|
|
17
|
-
// Mock dataSource used across tests
|
|
18
|
-
const mockObjectSchema = {
|
|
19
|
-
name: 'contacts',
|
|
20
|
-
fields: {
|
|
21
|
-
firstName: { label: 'First Name', type: 'text', required: true },
|
|
22
|
-
lastName: { label: 'Last Name', type: 'text', required: false },
|
|
23
|
-
email: { label: 'Email', type: 'email', required: true },
|
|
24
|
-
phone: { label: 'Phone', type: 'phone', required: false },
|
|
25
|
-
street: { label: 'Street', type: 'text', required: false },
|
|
26
|
-
city: { label: 'City', type: 'text', required: false },
|
|
27
|
-
},
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const createMockDataSource = () => ({
|
|
31
|
-
getObjectSchema: vi.fn().mockResolvedValue(mockObjectSchema),
|
|
32
|
-
findOne: vi.fn().mockResolvedValue({ firstName: 'John', lastName: 'Doe' }),
|
|
33
|
-
find: vi.fn().mockResolvedValue([]),
|
|
34
|
-
create: vi.fn().mockResolvedValue({ id: '1' }),
|
|
35
|
-
update: vi.fn().mockResolvedValue({ id: '1' }),
|
|
36
|
-
delete: vi.fn().mockResolvedValue(true),
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
// ─── SplitForm Tests ────────────────────────────────────────────────────
|
|
40
|
-
|
|
41
|
-
describe('SplitForm', () => {
|
|
42
|
-
it('renders with split panel layout', async () => {
|
|
43
|
-
const mockDataSource = createMockDataSource();
|
|
44
|
-
|
|
45
|
-
render(
|
|
46
|
-
<SplitForm
|
|
47
|
-
schema={{
|
|
48
|
-
type: 'object-form',
|
|
49
|
-
formType: 'split',
|
|
50
|
-
objectName: 'contacts',
|
|
51
|
-
mode: 'create',
|
|
52
|
-
sections: [
|
|
53
|
-
{
|
|
54
|
-
label: 'Personal',
|
|
55
|
-
fields: ['firstName', 'lastName'],
|
|
56
|
-
},
|
|
57
|
-
{
|
|
58
|
-
label: 'Contact',
|
|
59
|
-
fields: ['email', 'phone'],
|
|
60
|
-
},
|
|
61
|
-
],
|
|
62
|
-
}}
|
|
63
|
-
dataSource={mockDataSource as any}
|
|
64
|
-
/>
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
// Wait for schema to load
|
|
68
|
-
await waitFor(() => {
|
|
69
|
-
expect(mockDataSource.getObjectSchema).toHaveBeenCalledWith('contacts');
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
// Should show section labels
|
|
73
|
-
await waitFor(() => {
|
|
74
|
-
expect(screen.getByText('Personal')).toBeInTheDocument();
|
|
75
|
-
});
|
|
76
|
-
expect(screen.getByText('Contact')).toBeInTheDocument();
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it('renders loading state initially', () => {
|
|
80
|
-
const mockDataSource = createMockDataSource();
|
|
81
|
-
// Make getObjectSchema hang (never resolve)
|
|
82
|
-
mockDataSource.getObjectSchema.mockReturnValue(new Promise(() => {}));
|
|
83
|
-
|
|
84
|
-
render(
|
|
85
|
-
<SplitForm
|
|
86
|
-
schema={{
|
|
87
|
-
type: 'object-form',
|
|
88
|
-
formType: 'split',
|
|
89
|
-
objectName: 'contacts',
|
|
90
|
-
mode: 'create',
|
|
91
|
-
sections: [
|
|
92
|
-
{ label: 'Section 1', fields: ['firstName'] },
|
|
93
|
-
{ label: 'Section 2', fields: ['email'] },
|
|
94
|
-
],
|
|
95
|
-
}}
|
|
96
|
-
dataSource={mockDataSource as any}
|
|
97
|
-
/>
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
expect(screen.getByText('Loading form...')).toBeInTheDocument();
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
it('shows error state on fetch failure', async () => {
|
|
104
|
-
const mockDataSource = createMockDataSource();
|
|
105
|
-
mockDataSource.getObjectSchema.mockRejectedValue(new Error('Network error'));
|
|
106
|
-
|
|
107
|
-
render(
|
|
108
|
-
<SplitForm
|
|
109
|
-
schema={{
|
|
110
|
-
type: 'object-form',
|
|
111
|
-
formType: 'split',
|
|
112
|
-
objectName: 'contacts',
|
|
113
|
-
mode: 'create',
|
|
114
|
-
sections: [
|
|
115
|
-
{ label: 'Section 1', fields: ['firstName'] },
|
|
116
|
-
{ label: 'Section 2', fields: ['email'] },
|
|
117
|
-
],
|
|
118
|
-
}}
|
|
119
|
-
dataSource={mockDataSource as any}
|
|
120
|
-
/>
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
await waitFor(() => {
|
|
124
|
-
expect(screen.getByText('Error loading form')).toBeInTheDocument();
|
|
125
|
-
});
|
|
126
|
-
expect(screen.getByText('Network error')).toBeInTheDocument();
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
// ─── DrawerForm Tests ───────────────────────────────────────────────────
|
|
131
|
-
|
|
132
|
-
describe('DrawerForm', () => {
|
|
133
|
-
it('renders inside a sheet with title and description', async () => {
|
|
134
|
-
const mockDataSource = createMockDataSource();
|
|
135
|
-
|
|
136
|
-
render(
|
|
137
|
-
<DrawerForm
|
|
138
|
-
schema={{
|
|
139
|
-
type: 'object-form',
|
|
140
|
-
formType: 'drawer',
|
|
141
|
-
objectName: 'contacts',
|
|
142
|
-
mode: 'create',
|
|
143
|
-
title: 'Create Contact',
|
|
144
|
-
description: 'Fill in the contact details',
|
|
145
|
-
open: true,
|
|
146
|
-
fields: ['firstName', 'lastName', 'email'],
|
|
147
|
-
}}
|
|
148
|
-
dataSource={mockDataSource as any}
|
|
149
|
-
/>
|
|
150
|
-
);
|
|
151
|
-
|
|
152
|
-
// Wait for schema and rendering
|
|
153
|
-
await waitFor(() => {
|
|
154
|
-
expect(mockDataSource.getObjectSchema).toHaveBeenCalledWith('contacts');
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
// Title and description should be visible
|
|
158
|
-
await waitFor(() => {
|
|
159
|
-
expect(screen.getByText('Create Contact')).toBeInTheDocument();
|
|
160
|
-
});
|
|
161
|
-
expect(screen.getByText('Fill in the contact details')).toBeInTheDocument();
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
it('calls onOpenChange when closing', async () => {
|
|
165
|
-
const onOpenChange = vi.fn();
|
|
166
|
-
const mockDataSource = createMockDataSource();
|
|
167
|
-
|
|
168
|
-
render(
|
|
169
|
-
<DrawerForm
|
|
170
|
-
schema={{
|
|
171
|
-
type: 'object-form',
|
|
172
|
-
formType: 'drawer',
|
|
173
|
-
objectName: 'contacts',
|
|
174
|
-
mode: 'create',
|
|
175
|
-
title: 'Create Contact',
|
|
176
|
-
open: true,
|
|
177
|
-
onOpenChange,
|
|
178
|
-
fields: ['firstName'],
|
|
179
|
-
}}
|
|
180
|
-
dataSource={mockDataSource as any}
|
|
181
|
-
/>
|
|
182
|
-
);
|
|
183
|
-
|
|
184
|
-
// The close button (X) in the Sheet should exist
|
|
185
|
-
await waitFor(() => {
|
|
186
|
-
const closeButton = screen.getByRole('button', { name: /close/i });
|
|
187
|
-
expect(closeButton).toBeInTheDocument();
|
|
188
|
-
});
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it('renders with sections layout inside drawer', async () => {
|
|
192
|
-
const mockDataSource = createMockDataSource();
|
|
193
|
-
|
|
194
|
-
render(
|
|
195
|
-
<DrawerForm
|
|
196
|
-
schema={{
|
|
197
|
-
type: 'object-form',
|
|
198
|
-
formType: 'drawer',
|
|
199
|
-
objectName: 'contacts',
|
|
200
|
-
mode: 'create',
|
|
201
|
-
title: 'New Contact',
|
|
202
|
-
open: true,
|
|
203
|
-
sections: [
|
|
204
|
-
{
|
|
205
|
-
label: 'Personal Info',
|
|
206
|
-
fields: ['firstName', 'lastName'],
|
|
207
|
-
columns: 2,
|
|
208
|
-
},
|
|
209
|
-
{
|
|
210
|
-
label: 'Contact Details',
|
|
211
|
-
fields: ['email', 'phone'],
|
|
212
|
-
},
|
|
213
|
-
],
|
|
214
|
-
}}
|
|
215
|
-
dataSource={mockDataSource as any}
|
|
216
|
-
/>
|
|
217
|
-
);
|
|
218
|
-
|
|
219
|
-
await waitFor(() => {
|
|
220
|
-
expect(screen.getByText('New Contact')).toBeInTheDocument();
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
await waitFor(() => {
|
|
224
|
-
expect(screen.getByText('Personal Info')).toBeInTheDocument();
|
|
225
|
-
});
|
|
226
|
-
expect(screen.getByText('Contact Details')).toBeInTheDocument();
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
it('renders when open is false (hidden)', () => {
|
|
230
|
-
const mockDataSource = createMockDataSource();
|
|
231
|
-
|
|
232
|
-
const { container } = render(
|
|
233
|
-
<DrawerForm
|
|
234
|
-
schema={{
|
|
235
|
-
type: 'object-form',
|
|
236
|
-
formType: 'drawer',
|
|
237
|
-
objectName: 'contacts',
|
|
238
|
-
mode: 'create',
|
|
239
|
-
open: false,
|
|
240
|
-
fields: ['firstName'],
|
|
241
|
-
}}
|
|
242
|
-
dataSource={mockDataSource as any}
|
|
243
|
-
/>
|
|
244
|
-
);
|
|
245
|
-
|
|
246
|
-
// When open=false, the sheet should not render content
|
|
247
|
-
expect(screen.queryByText('Create Contact')).not.toBeInTheDocument();
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
// ─── ModalForm Tests ────────────────────────────────────────────────────
|
|
252
|
-
|
|
253
|
-
describe('ModalForm', () => {
|
|
254
|
-
it('renders inside a dialog with title and description', async () => {
|
|
255
|
-
const mockDataSource = createMockDataSource();
|
|
256
|
-
|
|
257
|
-
render(
|
|
258
|
-
<ModalForm
|
|
259
|
-
schema={{
|
|
260
|
-
type: 'object-form',
|
|
261
|
-
formType: 'modal',
|
|
262
|
-
objectName: 'contacts',
|
|
263
|
-
mode: 'create',
|
|
264
|
-
title: 'Create Contact',
|
|
265
|
-
description: 'Enter contact information',
|
|
266
|
-
open: true,
|
|
267
|
-
fields: ['firstName', 'lastName', 'email'],
|
|
268
|
-
}}
|
|
269
|
-
dataSource={mockDataSource as any}
|
|
270
|
-
/>
|
|
271
|
-
);
|
|
272
|
-
|
|
273
|
-
await waitFor(() => {
|
|
274
|
-
expect(mockDataSource.getObjectSchema).toHaveBeenCalledWith('contacts');
|
|
275
|
-
});
|
|
276
|
-
|
|
277
|
-
await waitFor(() => {
|
|
278
|
-
expect(screen.getByText('Create Contact')).toBeInTheDocument();
|
|
279
|
-
});
|
|
280
|
-
expect(screen.getByText('Enter contact information')).toBeInTheDocument();
|
|
281
|
-
});
|
|
282
|
-
|
|
283
|
-
it('renders with sections layout inside modal', async () => {
|
|
284
|
-
const mockDataSource = createMockDataSource();
|
|
285
|
-
|
|
286
|
-
render(
|
|
287
|
-
<ModalForm
|
|
288
|
-
schema={{
|
|
289
|
-
type: 'object-form',
|
|
290
|
-
formType: 'modal',
|
|
291
|
-
objectName: 'contacts',
|
|
292
|
-
mode: 'create',
|
|
293
|
-
title: 'New Contact',
|
|
294
|
-
open: true,
|
|
295
|
-
sections: [
|
|
296
|
-
{
|
|
297
|
-
label: 'Basic Info',
|
|
298
|
-
fields: ['firstName', 'lastName'],
|
|
299
|
-
columns: 2,
|
|
300
|
-
},
|
|
301
|
-
{
|
|
302
|
-
label: 'Contact Details',
|
|
303
|
-
fields: ['email', 'phone'],
|
|
304
|
-
},
|
|
305
|
-
],
|
|
306
|
-
}}
|
|
307
|
-
dataSource={mockDataSource as any}
|
|
308
|
-
/>
|
|
309
|
-
);
|
|
310
|
-
|
|
311
|
-
await waitFor(() => {
|
|
312
|
-
expect(screen.getByText('New Contact')).toBeInTheDocument();
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
await waitFor(() => {
|
|
316
|
-
expect(screen.getByText('Basic Info')).toBeInTheDocument();
|
|
317
|
-
});
|
|
318
|
-
expect(screen.getByText('Contact Details')).toBeInTheDocument();
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
it('does not render content when open is false', () => {
|
|
322
|
-
const mockDataSource = createMockDataSource();
|
|
323
|
-
|
|
324
|
-
render(
|
|
325
|
-
<ModalForm
|
|
326
|
-
schema={{
|
|
327
|
-
type: 'object-form',
|
|
328
|
-
formType: 'modal',
|
|
329
|
-
objectName: 'contacts',
|
|
330
|
-
mode: 'create',
|
|
331
|
-
title: 'Hidden Modal',
|
|
332
|
-
open: false,
|
|
333
|
-
fields: ['firstName'],
|
|
334
|
-
}}
|
|
335
|
-
dataSource={mockDataSource as any}
|
|
336
|
-
/>
|
|
337
|
-
);
|
|
338
|
-
|
|
339
|
-
expect(screen.queryByText('Hidden Modal')).not.toBeInTheDocument();
|
|
340
|
-
});
|
|
341
|
-
|
|
342
|
-
it('applies size class for large modal', async () => {
|
|
343
|
-
const mockDataSource = createMockDataSource();
|
|
344
|
-
|
|
345
|
-
render(
|
|
346
|
-
<ModalForm
|
|
347
|
-
schema={{
|
|
348
|
-
type: 'object-form',
|
|
349
|
-
formType: 'modal',
|
|
350
|
-
objectName: 'contacts',
|
|
351
|
-
mode: 'create',
|
|
352
|
-
title: 'Large Modal',
|
|
353
|
-
open: true,
|
|
354
|
-
modalSize: 'xl',
|
|
355
|
-
fields: ['firstName'],
|
|
356
|
-
}}
|
|
357
|
-
dataSource={mockDataSource as any}
|
|
358
|
-
/>
|
|
359
|
-
);
|
|
360
|
-
|
|
361
|
-
await waitFor(() => {
|
|
362
|
-
expect(screen.getByText('Large Modal')).toBeInTheDocument();
|
|
363
|
-
});
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
it('shows error state on data fetch failure', async () => {
|
|
367
|
-
const mockDataSource = createMockDataSource();
|
|
368
|
-
mockDataSource.getObjectSchema.mockRejectedValue(new Error('Server error'));
|
|
369
|
-
|
|
370
|
-
render(
|
|
371
|
-
<ModalForm
|
|
372
|
-
schema={{
|
|
373
|
-
type: 'object-form',
|
|
374
|
-
formType: 'modal',
|
|
375
|
-
objectName: 'contacts',
|
|
376
|
-
mode: 'create',
|
|
377
|
-
open: true,
|
|
378
|
-
fields: ['firstName'],
|
|
379
|
-
}}
|
|
380
|
-
dataSource={mockDataSource as any}
|
|
381
|
-
/>
|
|
382
|
-
);
|
|
383
|
-
|
|
384
|
-
await waitFor(() => {
|
|
385
|
-
expect(screen.getByText('Error loading form')).toBeInTheDocument();
|
|
386
|
-
});
|
|
387
|
-
expect(screen.getByText('Server error')).toBeInTheDocument();
|
|
388
|
-
});
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
// ─── ObjectForm Routing Tests ───────────────────────────────────────────
|
|
392
|
-
|
|
393
|
-
describe('ObjectForm routing to new variants', () => {
|
|
394
|
-
it('routes formType=split to SplitForm', async () => {
|
|
395
|
-
const mockDataSource = createMockDataSource();
|
|
396
|
-
|
|
397
|
-
render(
|
|
398
|
-
<ObjectForm
|
|
399
|
-
schema={{
|
|
400
|
-
type: 'object-form',
|
|
401
|
-
objectName: 'contacts',
|
|
402
|
-
mode: 'create',
|
|
403
|
-
formType: 'split',
|
|
404
|
-
sections: [
|
|
405
|
-
{ label: 'Left Panel', fields: ['firstName', 'lastName'] },
|
|
406
|
-
{ label: 'Right Panel', fields: ['email', 'phone'] },
|
|
407
|
-
],
|
|
408
|
-
}}
|
|
409
|
-
dataSource={mockDataSource as any}
|
|
410
|
-
/>
|
|
411
|
-
);
|
|
412
|
-
|
|
413
|
-
// Should load via SplitForm
|
|
414
|
-
await waitFor(() => {
|
|
415
|
-
expect(mockDataSource.getObjectSchema).toHaveBeenCalledWith('contacts');
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
await waitFor(() => {
|
|
419
|
-
expect(screen.getByText('Left Panel')).toBeInTheDocument();
|
|
420
|
-
});
|
|
421
|
-
expect(screen.getByText('Right Panel')).toBeInTheDocument();
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
it('routes formType=drawer to DrawerForm', async () => {
|
|
425
|
-
const mockDataSource = createMockDataSource();
|
|
426
|
-
|
|
427
|
-
render(
|
|
428
|
-
<ObjectForm
|
|
429
|
-
schema={{
|
|
430
|
-
type: 'object-form',
|
|
431
|
-
objectName: 'contacts',
|
|
432
|
-
mode: 'create',
|
|
433
|
-
formType: 'drawer',
|
|
434
|
-
title: 'Drawer Form Title',
|
|
435
|
-
open: true,
|
|
436
|
-
fields: ['firstName', 'lastName'],
|
|
437
|
-
}}
|
|
438
|
-
dataSource={mockDataSource as any}
|
|
439
|
-
/>
|
|
440
|
-
);
|
|
441
|
-
|
|
442
|
-
await waitFor(() => {
|
|
443
|
-
expect(screen.getByText('Drawer Form Title')).toBeInTheDocument();
|
|
444
|
-
});
|
|
445
|
-
});
|
|
446
|
-
|
|
447
|
-
it('routes formType=modal to ModalForm', async () => {
|
|
448
|
-
const mockDataSource = createMockDataSource();
|
|
449
|
-
|
|
450
|
-
render(
|
|
451
|
-
<ObjectForm
|
|
452
|
-
schema={{
|
|
453
|
-
type: 'object-form',
|
|
454
|
-
objectName: 'contacts',
|
|
455
|
-
mode: 'create',
|
|
456
|
-
formType: 'modal',
|
|
457
|
-
title: 'Modal Form Title',
|
|
458
|
-
open: true,
|
|
459
|
-
fields: ['firstName', 'lastName'],
|
|
460
|
-
}}
|
|
461
|
-
dataSource={mockDataSource as any}
|
|
462
|
-
/>
|
|
463
|
-
);
|
|
464
|
-
|
|
465
|
-
await waitFor(() => {
|
|
466
|
-
expect(screen.getByText('Modal Form Title')).toBeInTheDocument();
|
|
467
|
-
});
|
|
468
|
-
});
|
|
469
|
-
});
|
|
470
|
-
|
|
471
|
-
// ─── Auto-Layout Integration Tests ──────────────────────────────────────
|
|
472
|
-
|
|
473
|
-
/**
|
|
474
|
-
* These tests verify that ModalForm and DrawerForm apply applyAutoLayout
|
|
475
|
-
* when rendering flat fields (no sections), consistent with SimpleObjectForm.
|
|
476
|
-
*/
|
|
477
|
-
describe('ModalForm auto-layout integration', () => {
|
|
478
|
-
// Schema with 6 fields (>3) should trigger 2-column inference
|
|
479
|
-
const manyFieldsSchema = {
|
|
480
|
-
name: 'contacts',
|
|
481
|
-
fields: {
|
|
482
|
-
firstName: { label: 'First Name', type: 'text', required: true },
|
|
483
|
-
lastName: { label: 'Last Name', type: 'text', required: false },
|
|
484
|
-
email: { label: 'Email', type: 'email', required: true },
|
|
485
|
-
phone: { label: 'Phone', type: 'phone', required: false },
|
|
486
|
-
street: { label: 'Street', type: 'text', required: false },
|
|
487
|
-
city: { label: 'City', type: 'text', required: false },
|
|
488
|
-
},
|
|
489
|
-
};
|
|
490
|
-
|
|
491
|
-
it('applies auto-layout with 2 columns for 6 flat fields', async () => {
|
|
492
|
-
const mockDataSource = createMockDataSource();
|
|
493
|
-
mockDataSource.getObjectSchema.mockResolvedValue(manyFieldsSchema);
|
|
494
|
-
|
|
495
|
-
render(
|
|
496
|
-
<ModalForm
|
|
497
|
-
schema={{
|
|
498
|
-
type: 'object-form',
|
|
499
|
-
formType: 'modal',
|
|
500
|
-
objectName: 'contacts',
|
|
501
|
-
mode: 'create',
|
|
502
|
-
title: 'Auto Layout Modal',
|
|
503
|
-
open: true,
|
|
504
|
-
}}
|
|
505
|
-
dataSource={mockDataSource as any}
|
|
506
|
-
/>
|
|
507
|
-
);
|
|
508
|
-
|
|
509
|
-
await waitFor(() => {
|
|
510
|
-
expect(screen.getByText('Auto Layout Modal')).toBeInTheDocument();
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
// With 6 fields (>3), auto-layout should infer columns=2
|
|
514
|
-
// The form element receives columns="2" as an attribute
|
|
515
|
-
await waitFor(() => {
|
|
516
|
-
const formEl = document.querySelector('form[columns="2"]');
|
|
517
|
-
expect(formEl).not.toBeNull();
|
|
518
|
-
});
|
|
519
|
-
});
|
|
520
|
-
|
|
521
|
-
it('does not apply multi-column for 3 or fewer fields', async () => {
|
|
522
|
-
const fewFieldsSchema = {
|
|
523
|
-
name: 'simple',
|
|
524
|
-
fields: {
|
|
525
|
-
firstName: { label: 'First Name', type: 'text', required: true },
|
|
526
|
-
lastName: { label: 'Last Name', type: 'text', required: false },
|
|
527
|
-
},
|
|
528
|
-
};
|
|
529
|
-
const mockDataSource = createMockDataSource();
|
|
530
|
-
mockDataSource.getObjectSchema.mockResolvedValue(fewFieldsSchema);
|
|
531
|
-
|
|
532
|
-
render(
|
|
533
|
-
<ModalForm
|
|
534
|
-
schema={{
|
|
535
|
-
type: 'object-form',
|
|
536
|
-
formType: 'modal',
|
|
537
|
-
objectName: 'simple',
|
|
538
|
-
mode: 'create',
|
|
539
|
-
title: 'Few Fields Modal',
|
|
540
|
-
open: true,
|
|
541
|
-
}}
|
|
542
|
-
dataSource={mockDataSource as any}
|
|
543
|
-
/>
|
|
544
|
-
);
|
|
545
|
-
|
|
546
|
-
await waitFor(() => {
|
|
547
|
-
expect(screen.getByText('Few Fields Modal')).toBeInTheDocument();
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
// With 2 fields (≤3), auto-layout should infer columns=1
|
|
551
|
-
await waitFor(() => {
|
|
552
|
-
const formEl = document.querySelector('form[columns="2"]');
|
|
553
|
-
expect(formEl).toBeNull();
|
|
554
|
-
});
|
|
555
|
-
});
|
|
556
|
-
|
|
557
|
-
it('respects explicit columns override', async () => {
|
|
558
|
-
const mockDataSource = createMockDataSource();
|
|
559
|
-
mockDataSource.getObjectSchema.mockResolvedValue(manyFieldsSchema);
|
|
560
|
-
|
|
561
|
-
render(
|
|
562
|
-
<ModalForm
|
|
563
|
-
schema={{
|
|
564
|
-
type: 'object-form',
|
|
565
|
-
formType: 'modal',
|
|
566
|
-
objectName: 'contacts',
|
|
567
|
-
mode: 'create',
|
|
568
|
-
title: 'Explicit Columns Modal',
|
|
569
|
-
open: true,
|
|
570
|
-
columns: 3,
|
|
571
|
-
}}
|
|
572
|
-
dataSource={mockDataSource as any}
|
|
573
|
-
/>
|
|
574
|
-
);
|
|
575
|
-
|
|
576
|
-
await waitFor(() => {
|
|
577
|
-
expect(screen.getByText('Explicit Columns Modal')).toBeInTheDocument();
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
// User specified columns=3
|
|
581
|
-
await waitFor(() => {
|
|
582
|
-
const formEl = document.querySelector('form[columns="3"]');
|
|
583
|
-
expect(formEl).not.toBeNull();
|
|
584
|
-
});
|
|
585
|
-
});
|
|
586
|
-
});
|
|
587
|
-
|
|
588
|
-
describe('DrawerForm auto-layout integration', () => {
|
|
589
|
-
const manyFieldsSchema = {
|
|
590
|
-
name: 'contacts',
|
|
591
|
-
fields: {
|
|
592
|
-
firstName: { label: 'First Name', type: 'text', required: true },
|
|
593
|
-
lastName: { label: 'Last Name', type: 'text', required: false },
|
|
594
|
-
email: { label: 'Email', type: 'email', required: true },
|
|
595
|
-
phone: { label: 'Phone', type: 'phone', required: false },
|
|
596
|
-
street: { label: 'Street', type: 'text', required: false },
|
|
597
|
-
city: { label: 'City', type: 'text', required: false },
|
|
598
|
-
},
|
|
599
|
-
};
|
|
600
|
-
|
|
601
|
-
it('applies auto-layout with 2 columns for 6 flat fields', async () => {
|
|
602
|
-
const mockDataSource = createMockDataSource();
|
|
603
|
-
mockDataSource.getObjectSchema.mockResolvedValue(manyFieldsSchema);
|
|
604
|
-
|
|
605
|
-
render(
|
|
606
|
-
<DrawerForm
|
|
607
|
-
schema={{
|
|
608
|
-
type: 'object-form',
|
|
609
|
-
formType: 'drawer',
|
|
610
|
-
objectName: 'contacts',
|
|
611
|
-
mode: 'create',
|
|
612
|
-
title: 'Auto Layout Drawer',
|
|
613
|
-
open: true,
|
|
614
|
-
}}
|
|
615
|
-
dataSource={mockDataSource as any}
|
|
616
|
-
/>
|
|
617
|
-
);
|
|
618
|
-
|
|
619
|
-
await waitFor(() => {
|
|
620
|
-
expect(screen.getByText('Auto Layout Drawer')).toBeInTheDocument();
|
|
621
|
-
});
|
|
622
|
-
|
|
623
|
-
// With 6 fields (>3), auto-layout should infer columns=2
|
|
624
|
-
await waitFor(() => {
|
|
625
|
-
const formEl = document.querySelector('form[columns="2"]');
|
|
626
|
-
expect(formEl).not.toBeNull();
|
|
627
|
-
});
|
|
628
|
-
});
|
|
629
|
-
|
|
630
|
-
it('does not apply multi-column for 3 or fewer fields', async () => {
|
|
631
|
-
const fewFieldsSchema = {
|
|
632
|
-
name: 'simple',
|
|
633
|
-
fields: {
|
|
634
|
-
firstName: { label: 'First Name', type: 'text', required: true },
|
|
635
|
-
lastName: { label: 'Last Name', type: 'text', required: false },
|
|
636
|
-
},
|
|
637
|
-
};
|
|
638
|
-
const mockDataSource = createMockDataSource();
|
|
639
|
-
mockDataSource.getObjectSchema.mockResolvedValue(fewFieldsSchema);
|
|
640
|
-
|
|
641
|
-
render(
|
|
642
|
-
<DrawerForm
|
|
643
|
-
schema={{
|
|
644
|
-
type: 'object-form',
|
|
645
|
-
formType: 'drawer',
|
|
646
|
-
objectName: 'simple',
|
|
647
|
-
mode: 'create',
|
|
648
|
-
title: 'Few Fields Drawer',
|
|
649
|
-
open: true,
|
|
650
|
-
}}
|
|
651
|
-
dataSource={mockDataSource as any}
|
|
652
|
-
/>
|
|
653
|
-
);
|
|
654
|
-
|
|
655
|
-
await waitFor(() => {
|
|
656
|
-
expect(screen.getByText('Few Fields Drawer')).toBeInTheDocument();
|
|
657
|
-
});
|
|
658
|
-
|
|
659
|
-
// With 2 fields (≤3), should remain 1 column
|
|
660
|
-
await waitFor(() => {
|
|
661
|
-
const formEl = document.querySelector('form[columns="2"]');
|
|
662
|
-
expect(formEl).toBeNull();
|
|
663
|
-
});
|
|
664
|
-
});
|
|
665
|
-
});
|
|
666
|
-
|
|
667
|
-
// ─── Export Tests ────────────────────────────────────────────────────────
|
|
668
|
-
|
|
669
|
-
describe('New variant exports', () => {
|
|
670
|
-
it('exports SplitForm', () => {
|
|
671
|
-
expect(SplitForm).toBeDefined();
|
|
672
|
-
expect(typeof SplitForm).toBe('function');
|
|
673
|
-
});
|
|
674
|
-
|
|
675
|
-
it('exports DrawerForm', () => {
|
|
676
|
-
expect(DrawerForm).toBeDefined();
|
|
677
|
-
expect(typeof DrawerForm).toBe('function');
|
|
678
|
-
});
|
|
679
|
-
|
|
680
|
-
it('exports ModalForm', () => {
|
|
681
|
-
expect(ModalForm).toBeDefined();
|
|
682
|
-
expect(typeof ModalForm).toBe('function');
|
|
683
|
-
});
|
|
684
|
-
});
|