@object-ui/fields 0.3.0 → 0.5.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/dist/fields/src/complex-widgets.test.d.ts +1 -0
- package/dist/fields/src/datetime-widgets.test.d.ts +1 -0
- package/dist/fields/src/fields.stories.d.ts +56 -0
- package/dist/fields/src/index.d.ts +48 -0
- package/dist/fields/src/new-widgets.test.d.ts +8 -0
- package/dist/fields/src/remaining-widgets.test.d.ts +1 -0
- package/dist/fields/src/standard-widgets.test.d.ts +0 -0
- package/dist/fields/src/text-rich-widgets.test.d.ts +1 -0
- package/dist/fields/src/visual-widgets.test.d.ts +1 -0
- package/dist/fields/src/widgets/AddressField.d.ts +16 -0
- package/dist/fields/src/widgets/AutoNumberField.d.ts +6 -0
- package/dist/fields/src/widgets/AvatarField.d.ts +6 -0
- package/dist/fields/src/widgets/CodeField.d.ts +7 -0
- package/dist/fields/src/widgets/ColorField.d.ts +6 -0
- package/dist/fields/src/widgets/CurrencyField.d.ts +1 -1
- package/dist/fields/src/widgets/DateTimeField.d.ts +2 -0
- package/dist/fields/src/widgets/FileField.d.ts +2 -0
- package/dist/fields/src/widgets/FormulaField.d.ts +6 -0
- package/dist/fields/src/widgets/GeolocationField.d.ts +14 -0
- package/dist/fields/src/widgets/GridField.d.ts +7 -0
- package/dist/fields/src/widgets/ImageField.d.ts +2 -0
- package/dist/fields/src/widgets/LocationField.d.ts +2 -0
- package/dist/fields/src/widgets/LookupField.d.ts +1 -1
- package/dist/fields/src/widgets/MasterDetailField.d.ts +14 -0
- package/dist/fields/src/widgets/ObjectField.d.ts +6 -0
- package/dist/fields/src/widgets/PasswordField.d.ts +2 -0
- package/dist/fields/src/widgets/PercentField.d.ts +2 -0
- package/dist/fields/src/widgets/QRCodeField.d.ts +6 -0
- package/dist/fields/src/widgets/RatingField.d.ts +6 -0
- package/dist/fields/src/widgets/SignatureField.d.ts +6 -0
- package/dist/fields/src/widgets/SliderField.d.ts +6 -0
- package/dist/fields/src/widgets/SummaryField.d.ts +6 -0
- package/dist/fields/src/widgets/TimeField.d.ts +2 -0
- package/dist/fields/src/widgets/UserField.d.ts +6 -0
- package/dist/fields/src/widgets/VectorField.d.ts +6 -0
- package/dist/fields/src/widgets/types.d.ts +2 -0
- package/dist/index.cjs +4 -2
- package/dist/index.js +2003 -618
- package/package.json +10 -8
- package/src/complex-widgets.test.tsx +146 -0
- package/src/datetime-widgets.test.tsx +93 -0
- package/src/fields.stories.tsx +566 -0
- package/src/index.tsx +303 -68
- package/src/new-widgets.test.tsx +302 -0
- package/src/remaining-widgets.test.tsx +172 -0
- package/src/standard-widgets.test.tsx +278 -0
- package/src/text-rich-widgets.test.tsx +104 -0
- package/src/visual-widgets.test.tsx +97 -0
- package/src/widgets/AddressField.tsx +112 -0
- package/src/widgets/AutoNumberField.tsx +16 -0
- package/src/widgets/AvatarField.tsx +110 -0
- package/src/widgets/BooleanField.tsx +19 -11
- package/src/widgets/CodeField.tsx +34 -0
- package/src/widgets/ColorField.tsx +44 -0
- package/src/widgets/CurrencyField.tsx +8 -7
- package/src/widgets/DateField.tsx +5 -2
- package/src/widgets/DateTimeField.tsx +28 -0
- package/src/widgets/EmailField.tsx +7 -3
- package/src/widgets/FileField.tsx +114 -0
- package/src/widgets/FormulaField.tsx +31 -0
- package/src/widgets/GeolocationField.tsx +151 -0
- package/src/widgets/GridField.tsx +63 -0
- package/src/widgets/ImageField.tsx +108 -0
- package/src/widgets/LocationField.tsx +46 -0
- package/src/widgets/LookupField.tsx +5 -5
- package/src/widgets/MasterDetailField.tsx +131 -0
- package/src/widgets/NumberField.tsx +8 -4
- package/src/widgets/ObjectField.tsx +85 -0
- package/src/widgets/PasswordField.tsx +45 -0
- package/src/widgets/PercentField.tsx +49 -0
- package/src/widgets/PhoneField.tsx +7 -3
- package/src/widgets/QRCodeField.tsx +96 -0
- package/src/widgets/RatingField.tsx +66 -0
- package/src/widgets/RichTextField.tsx +5 -5
- package/src/widgets/SelectField.tsx +7 -5
- package/src/widgets/SignatureField.tsx +140 -0
- package/src/widgets/SliderField.tsx +39 -0
- package/src/widgets/SummaryField.tsx +29 -0
- package/src/widgets/TextAreaField.tsx +9 -6
- package/src/widgets/TextField.tsx +13 -8
- package/src/widgets/TimeField.tsx +22 -0
- package/src/widgets/UrlField.tsx +7 -3
- package/src/widgets/UserField.tsx +88 -0
- package/src/widgets/VectorField.tsx +28 -0
- package/src/widgets/types.ts +2 -0
|
@@ -0,0 +1,302 @@
|
|
|
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 } from 'vitest';
|
|
10
|
+
import { render, screen } from '@testing-library/react';
|
|
11
|
+
import { ColorField } from './widgets/ColorField';
|
|
12
|
+
import { SliderField } from './widgets/SliderField';
|
|
13
|
+
import { RatingField } from './widgets/RatingField';
|
|
14
|
+
import { CodeField } from './widgets/CodeField';
|
|
15
|
+
import { AvatarField } from './widgets/AvatarField';
|
|
16
|
+
import { AddressField } from './widgets/AddressField';
|
|
17
|
+
import { GeolocationField } from './widgets/GeolocationField';
|
|
18
|
+
import { SignatureField } from './widgets/SignatureField';
|
|
19
|
+
import { QRCodeField } from './widgets/QRCodeField';
|
|
20
|
+
import { MasterDetailField } from './widgets/MasterDetailField';
|
|
21
|
+
|
|
22
|
+
const mockField: any = {
|
|
23
|
+
name: 'test_field',
|
|
24
|
+
label: 'Test Field',
|
|
25
|
+
type: 'text',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
describe('New Field Widgets', () => {
|
|
29
|
+
describe('ColorField', () => {
|
|
30
|
+
it('should render in readonly mode', () => {
|
|
31
|
+
const { container } = render(
|
|
32
|
+
<ColorField
|
|
33
|
+
value="#ff0000"
|
|
34
|
+
onChange={() => {}}
|
|
35
|
+
field={mockField}
|
|
36
|
+
readonly={true}
|
|
37
|
+
/>
|
|
38
|
+
);
|
|
39
|
+
expect(container).toBeDefined();
|
|
40
|
+
expect(container.textContent).toContain('#ff0000');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should render in edit mode', () => {
|
|
44
|
+
const { container } = render(
|
|
45
|
+
<ColorField
|
|
46
|
+
value="#00ff00"
|
|
47
|
+
onChange={() => {}}
|
|
48
|
+
field={mockField}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
expect(container).toBeDefined();
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe('SliderField', () => {
|
|
56
|
+
it('should render with value', () => {
|
|
57
|
+
const { container } = render(
|
|
58
|
+
<SliderField
|
|
59
|
+
value={50}
|
|
60
|
+
onChange={() => {}}
|
|
61
|
+
field={mockField}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
expect(container).toBeDefined();
|
|
65
|
+
expect(container.textContent).toContain('50');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should render in readonly mode', () => {
|
|
69
|
+
const { container } = render(
|
|
70
|
+
<SliderField
|
|
71
|
+
value={75}
|
|
72
|
+
onChange={() => {}}
|
|
73
|
+
field={mockField}
|
|
74
|
+
readonly={true}
|
|
75
|
+
/>
|
|
76
|
+
);
|
|
77
|
+
expect(container.textContent).toContain('75');
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('RatingField', () => {
|
|
82
|
+
it('should render stars', () => {
|
|
83
|
+
const { container } = render(
|
|
84
|
+
<RatingField
|
|
85
|
+
value={3}
|
|
86
|
+
onChange={() => {}}
|
|
87
|
+
field={mockField}
|
|
88
|
+
/>
|
|
89
|
+
);
|
|
90
|
+
expect(container).toBeDefined();
|
|
91
|
+
expect(container.textContent).toContain('3 / 5');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should render in readonly mode', () => {
|
|
95
|
+
const { container } = render(
|
|
96
|
+
<RatingField
|
|
97
|
+
value={4}
|
|
98
|
+
onChange={() => {}}
|
|
99
|
+
field={mockField}
|
|
100
|
+
readonly={true}
|
|
101
|
+
/>
|
|
102
|
+
);
|
|
103
|
+
expect(container.textContent).toContain('4 / 5');
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('CodeField', () => {
|
|
108
|
+
it('should render textarea', () => {
|
|
109
|
+
const { container } = render(
|
|
110
|
+
<CodeField
|
|
111
|
+
value="console.log('hello');"
|
|
112
|
+
onChange={() => {}}
|
|
113
|
+
field={mockField}
|
|
114
|
+
/>
|
|
115
|
+
);
|
|
116
|
+
expect(container).toBeDefined();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should render readonly code', () => {
|
|
120
|
+
const { container } = render(
|
|
121
|
+
<CodeField
|
|
122
|
+
value="const x = 42;"
|
|
123
|
+
onChange={() => {}}
|
|
124
|
+
field={mockField}
|
|
125
|
+
readonly={true}
|
|
126
|
+
/>
|
|
127
|
+
);
|
|
128
|
+
expect(container).toBeDefined();
|
|
129
|
+
expect(container.textContent).toContain('const x = 42;');
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
describe('AvatarField', () => {
|
|
134
|
+
it('should render avatar', () => {
|
|
135
|
+
const { container } = render(
|
|
136
|
+
<AvatarField
|
|
137
|
+
value=""
|
|
138
|
+
onChange={() => {}}
|
|
139
|
+
field={mockField}
|
|
140
|
+
/>
|
|
141
|
+
);
|
|
142
|
+
expect(container).toBeDefined();
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should render readonly avatar', () => {
|
|
146
|
+
const { container } = render(
|
|
147
|
+
<AvatarField
|
|
148
|
+
value="https://example.com/avatar.jpg"
|
|
149
|
+
onChange={() => {}}
|
|
150
|
+
field={mockField}
|
|
151
|
+
readonly={true}
|
|
152
|
+
/>
|
|
153
|
+
);
|
|
154
|
+
expect(container).toBeDefined();
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
describe('AddressField', () => {
|
|
159
|
+
it('should render address fields', () => {
|
|
160
|
+
const { container } = render(
|
|
161
|
+
<AddressField
|
|
162
|
+
value={{ street: '123 Main St', city: 'San Francisco' }}
|
|
163
|
+
onChange={() => {}}
|
|
164
|
+
field={mockField}
|
|
165
|
+
/>
|
|
166
|
+
);
|
|
167
|
+
expect(container).toBeDefined();
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it('should render readonly address', () => {
|
|
171
|
+
const { container } = render(
|
|
172
|
+
<AddressField
|
|
173
|
+
value={{ street: '456 Oak Ave', city: 'New York', state: 'NY' }}
|
|
174
|
+
onChange={() => {}}
|
|
175
|
+
field={mockField}
|
|
176
|
+
readonly={true}
|
|
177
|
+
/>
|
|
178
|
+
);
|
|
179
|
+
expect(container.textContent).toContain('456 Oak Ave');
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
describe('GeolocationField', () => {
|
|
184
|
+
it('should render geolocation inputs', () => {
|
|
185
|
+
const { container } = render(
|
|
186
|
+
<GeolocationField
|
|
187
|
+
value={{ latitude: 37.7749, longitude: -122.4194 }}
|
|
188
|
+
onChange={() => {}}
|
|
189
|
+
field={mockField}
|
|
190
|
+
/>
|
|
191
|
+
);
|
|
192
|
+
expect(container).toBeDefined();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('should render readonly geolocation', () => {
|
|
196
|
+
const { container } = render(
|
|
197
|
+
<GeolocationField
|
|
198
|
+
value={{ latitude: 40.7128, longitude: -74.0060 }}
|
|
199
|
+
onChange={() => {}}
|
|
200
|
+
field={mockField}
|
|
201
|
+
readonly={true}
|
|
202
|
+
/>
|
|
203
|
+
);
|
|
204
|
+
expect(container.textContent).toContain('40.712800');
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe('SignatureField', () => {
|
|
209
|
+
it('should render signature canvas', () => {
|
|
210
|
+
const { container } = render(
|
|
211
|
+
<SignatureField
|
|
212
|
+
value=""
|
|
213
|
+
field={mockField}
|
|
214
|
+
onChange={() => {}}
|
|
215
|
+
/>
|
|
216
|
+
);
|
|
217
|
+
expect(container).toBeDefined();
|
|
218
|
+
expect(container.querySelector('canvas')).toBeDefined();
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('should render readonly signature', () => {
|
|
222
|
+
const { container } = render(
|
|
223
|
+
<SignatureField
|
|
224
|
+
value="data:image/png;base64,..."
|
|
225
|
+
field={mockField}
|
|
226
|
+
onChange={() => {}}
|
|
227
|
+
readonly={true}
|
|
228
|
+
/>
|
|
229
|
+
);
|
|
230
|
+
expect(container).toBeDefined();
|
|
231
|
+
});
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
describe('QRCodeField', () => {
|
|
235
|
+
it('should render QR code input', () => {
|
|
236
|
+
const { container } = render(
|
|
237
|
+
<QRCodeField
|
|
238
|
+
value="https://example.com"
|
|
239
|
+
onChange={() => {}}
|
|
240
|
+
field={mockField}
|
|
241
|
+
/>
|
|
242
|
+
);
|
|
243
|
+
expect(container).toBeDefined();
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('should render readonly QR code', () => {
|
|
247
|
+
const { container } = render(
|
|
248
|
+
<QRCodeField
|
|
249
|
+
value="Test Data"
|
|
250
|
+
onChange={() => {}}
|
|
251
|
+
field={mockField}
|
|
252
|
+
readonly={true}
|
|
253
|
+
/>
|
|
254
|
+
);
|
|
255
|
+
expect(container.textContent).toContain('Test Data');
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
describe('MasterDetailField', () => {
|
|
260
|
+
it('should render empty state', () => {
|
|
261
|
+
const { container } = render(
|
|
262
|
+
<MasterDetailField
|
|
263
|
+
value={[]}
|
|
264
|
+
field={mockField}
|
|
265
|
+
onChange={() => {}}
|
|
266
|
+
/>
|
|
267
|
+
);
|
|
268
|
+
expect(container).toBeDefined();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('should render with items', () => {
|
|
272
|
+
const items = [
|
|
273
|
+
{ id: '1', label: 'Item 1' },
|
|
274
|
+
{ id: '2', label: 'Item 2' },
|
|
275
|
+
];
|
|
276
|
+
const { container } = render(
|
|
277
|
+
<MasterDetailField
|
|
278
|
+
value={items}
|
|
279
|
+
field={mockField}
|
|
280
|
+
onChange={() => {}}
|
|
281
|
+
/>
|
|
282
|
+
);
|
|
283
|
+
expect(container.textContent).toContain('Item 1');
|
|
284
|
+
expect(container.textContent).toContain('Item 2');
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it('should render readonly items', () => {
|
|
288
|
+
const items = [
|
|
289
|
+
{ id: '1', label: 'Item A' },
|
|
290
|
+
];
|
|
291
|
+
const { container } = render(
|
|
292
|
+
<MasterDetailField
|
|
293
|
+
value={items}
|
|
294
|
+
field={mockField}
|
|
295
|
+
onChange={() => {}}
|
|
296
|
+
readonly={true}
|
|
297
|
+
/>
|
|
298
|
+
);
|
|
299
|
+
expect(container.textContent).toContain('Item A');
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
});
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
|
|
2
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
|
3
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
4
|
+
import { AutoNumberField } from './widgets/AutoNumberField';
|
|
5
|
+
import { FormulaField } from './widgets/FormulaField';
|
|
6
|
+
import { SummaryField } from './widgets/SummaryField';
|
|
7
|
+
import { PercentField } from './widgets/PercentField';
|
|
8
|
+
import { ImageField } from './widgets/ImageField';
|
|
9
|
+
import { LocationField } from './widgets/LocationField';
|
|
10
|
+
import { ObjectField } from './widgets/ObjectField';
|
|
11
|
+
import { UserField } from './widgets/UserField';
|
|
12
|
+
import { VectorField } from './widgets/VectorField';
|
|
13
|
+
import type { FieldWidgetProps } from './widgets/types';
|
|
14
|
+
|
|
15
|
+
// ------------- Mocks & Setup -------------
|
|
16
|
+
|
|
17
|
+
const mockField = {
|
|
18
|
+
name: 'test_field',
|
|
19
|
+
label: 'Test Field',
|
|
20
|
+
} as any;
|
|
21
|
+
|
|
22
|
+
const baseProps: FieldWidgetProps<any> = {
|
|
23
|
+
field: mockField,
|
|
24
|
+
value: undefined,
|
|
25
|
+
onChange: vi.fn(),
|
|
26
|
+
readonly: false,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
describe('Remaining Field Widgets', () => {
|
|
30
|
+
|
|
31
|
+
// 1. AutoNumberField
|
|
32
|
+
describe('AutoNumberField', () => {
|
|
33
|
+
it('should render value as string', () => {
|
|
34
|
+
render(<AutoNumberField {...baseProps} value={1001} />);
|
|
35
|
+
expect(screen.getByText('1001')).toBeInTheDocument();
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('should render dash for null value', () => {
|
|
39
|
+
render(<AutoNumberField {...baseProps} value={null as any} />);
|
|
40
|
+
expect(screen.getByText('-')).toBeInTheDocument();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// 2. FormulaField
|
|
45
|
+
describe('FormulaField', () => {
|
|
46
|
+
it('should format return_type=currency', () => {
|
|
47
|
+
const field = { ...mockField, return_type: 'currency' };
|
|
48
|
+
render(<FormulaField {...baseProps} field={field} value={123.456} />);
|
|
49
|
+
// Checks for 2 decimal places
|
|
50
|
+
expect(screen.getByText('123.46')).toBeInTheDocument();
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('should format return_type=boolean', () => {
|
|
54
|
+
const field = { ...mockField, return_type: 'boolean' };
|
|
55
|
+
render(<FormulaField {...baseProps} field={field} value={true} />);
|
|
56
|
+
expect(screen.getByText('Yes')).toBeInTheDocument();
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// 3. SummaryField
|
|
61
|
+
describe('SummaryField', () => {
|
|
62
|
+
it('should render count as integer', () => {
|
|
63
|
+
const field = { ...mockField, summary_type: 'count' };
|
|
64
|
+
render(<SummaryField {...baseProps} field={field} value={42} />);
|
|
65
|
+
expect(screen.getByText('42')).toBeInTheDocument();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('should render sum with decimals', () => {
|
|
69
|
+
const field = { ...mockField, summary_type: 'sum' };
|
|
70
|
+
render(<SummaryField {...baseProps} field={field} value={10.566} />);
|
|
71
|
+
expect(screen.getByText('10.57')).toBeInTheDocument();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
// 4. PercentField
|
|
76
|
+
describe('PercentField', () => {
|
|
77
|
+
it('should display value * 100 in input', () => {
|
|
78
|
+
render(<PercentField {...baseProps} value={0.5} />);
|
|
79
|
+
const input = screen.getByRole('spinbutton') as HTMLInputElement;
|
|
80
|
+
expect(input.value).toBe('50');
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should display formatted value in readonly', () => {
|
|
84
|
+
render(<PercentField {...baseProps} value={0.125} readonly={true} />);
|
|
85
|
+
expect(screen.getByText('12.50%')).toBeInTheDocument();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('should call onChange with value / 100', () => {
|
|
89
|
+
const handleChange = vi.fn();
|
|
90
|
+
render(<PercentField {...baseProps} onChange={handleChange} value={0} />);
|
|
91
|
+
const input = screen.getByRole('spinbutton');
|
|
92
|
+
fireEvent.change(input, { target: { value: '75' } });
|
|
93
|
+
expect(handleChange).toHaveBeenCalledWith(0.75);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// 5. ImageField
|
|
98
|
+
describe('ImageField', () => {
|
|
99
|
+
const images = [
|
|
100
|
+
{ url: 'http://example.com/1.jpg', name: 'Img 1' },
|
|
101
|
+
{ url: 'http://example.com/2.jpg', name: 'Img 2' }
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
it('should render images in readonly mode', () => {
|
|
105
|
+
render(<ImageField {...baseProps} value={images} readonly={true} />);
|
|
106
|
+
const imgs = screen.getAllByRole('img');
|
|
107
|
+
expect(imgs).toHaveLength(2);
|
|
108
|
+
expect(imgs[0]).toHaveAttribute('src', 'http://example.com/1.jpg');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Complex interaction omitted for brevity, focusing on render contracts
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// 6. LocationField
|
|
115
|
+
describe('LocationField', () => {
|
|
116
|
+
it('should render lat,long string', () => {
|
|
117
|
+
const loc = { latitude: 34.05, longitude: -118.25 };
|
|
118
|
+
render(<LocationField {...baseProps} value={loc} />);
|
|
119
|
+
const input = screen.getByRole('textbox') as HTMLInputElement;
|
|
120
|
+
expect(input.value).toContain('34.05, -118.25');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it('should parse string input to object', () => {
|
|
124
|
+
const handleChange = vi.fn();
|
|
125
|
+
render(<LocationField {...baseProps} onChange={handleChange} />);
|
|
126
|
+
const input = screen.getByRole('textbox');
|
|
127
|
+
fireEvent.change(input, { target: { value: '10.5, 20.5' } });
|
|
128
|
+
expect(handleChange).toHaveBeenCalledWith({ latitude: 10.5, longitude: 20.5 });
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// 7. ObjectField
|
|
133
|
+
describe('ObjectField', () => {
|
|
134
|
+
const obj = { foo: 'bar', num: 1 };
|
|
135
|
+
|
|
136
|
+
it('should render JSON string in readonly', () => {
|
|
137
|
+
render(<ObjectField {...baseProps} value={obj} readonly={true} />);
|
|
138
|
+
// pre tag usually contains formatted JSON
|
|
139
|
+
expect(screen.getByText(/"foo": "bar"/)).toBeInTheDocument();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it('should parse valid JSON input', () => {
|
|
143
|
+
const handleChange = vi.fn();
|
|
144
|
+
render(<ObjectField {...baseProps} onChange={handleChange} value={obj} />);
|
|
145
|
+
const textarea = screen.getByRole('textbox');
|
|
146
|
+
const newJson = '{"test": true}';
|
|
147
|
+
fireEvent.change(textarea, { target: { value: newJson } });
|
|
148
|
+
expect(handleChange).toHaveBeenCalledWith({ test: true });
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// 8. UserField
|
|
153
|
+
describe('UserField', () => {
|
|
154
|
+
const user = { name: 'Alice Smith', id: 'u1' };
|
|
155
|
+
|
|
156
|
+
it('should render user avatar in readonly', () => {
|
|
157
|
+
render(<UserField {...baseProps} value={user} readonly={true} />);
|
|
158
|
+
// Avatar fallback logic splits name
|
|
159
|
+
expect(screen.getByText('AS')).toBeInTheDocument();
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
// 9. VectorField
|
|
164
|
+
describe('VectorField', () => {
|
|
165
|
+
it('should render vector values', () => {
|
|
166
|
+
const vec = [0.12345, 0.67891, 0.00001];
|
|
167
|
+
render(<VectorField {...baseProps} value={vec} />);
|
|
168
|
+
expect(screen.getByText(/0.1235/)).toBeInTheDocument(); // toFixed(4)
|
|
169
|
+
expect(screen.getByText('(3D)')).toBeInTheDocument();
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
});
|