@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.
Files changed (85) hide show
  1. package/dist/fields/src/complex-widgets.test.d.ts +1 -0
  2. package/dist/fields/src/datetime-widgets.test.d.ts +1 -0
  3. package/dist/fields/src/fields.stories.d.ts +56 -0
  4. package/dist/fields/src/index.d.ts +48 -0
  5. package/dist/fields/src/new-widgets.test.d.ts +8 -0
  6. package/dist/fields/src/remaining-widgets.test.d.ts +1 -0
  7. package/dist/fields/src/standard-widgets.test.d.ts +0 -0
  8. package/dist/fields/src/text-rich-widgets.test.d.ts +1 -0
  9. package/dist/fields/src/visual-widgets.test.d.ts +1 -0
  10. package/dist/fields/src/widgets/AddressField.d.ts +16 -0
  11. package/dist/fields/src/widgets/AutoNumberField.d.ts +6 -0
  12. package/dist/fields/src/widgets/AvatarField.d.ts +6 -0
  13. package/dist/fields/src/widgets/CodeField.d.ts +7 -0
  14. package/dist/fields/src/widgets/ColorField.d.ts +6 -0
  15. package/dist/fields/src/widgets/CurrencyField.d.ts +1 -1
  16. package/dist/fields/src/widgets/DateTimeField.d.ts +2 -0
  17. package/dist/fields/src/widgets/FileField.d.ts +2 -0
  18. package/dist/fields/src/widgets/FormulaField.d.ts +6 -0
  19. package/dist/fields/src/widgets/GeolocationField.d.ts +14 -0
  20. package/dist/fields/src/widgets/GridField.d.ts +7 -0
  21. package/dist/fields/src/widgets/ImageField.d.ts +2 -0
  22. package/dist/fields/src/widgets/LocationField.d.ts +2 -0
  23. package/dist/fields/src/widgets/LookupField.d.ts +1 -1
  24. package/dist/fields/src/widgets/MasterDetailField.d.ts +14 -0
  25. package/dist/fields/src/widgets/ObjectField.d.ts +6 -0
  26. package/dist/fields/src/widgets/PasswordField.d.ts +2 -0
  27. package/dist/fields/src/widgets/PercentField.d.ts +2 -0
  28. package/dist/fields/src/widgets/QRCodeField.d.ts +6 -0
  29. package/dist/fields/src/widgets/RatingField.d.ts +6 -0
  30. package/dist/fields/src/widgets/SignatureField.d.ts +6 -0
  31. package/dist/fields/src/widgets/SliderField.d.ts +6 -0
  32. package/dist/fields/src/widgets/SummaryField.d.ts +6 -0
  33. package/dist/fields/src/widgets/TimeField.d.ts +2 -0
  34. package/dist/fields/src/widgets/UserField.d.ts +6 -0
  35. package/dist/fields/src/widgets/VectorField.d.ts +6 -0
  36. package/dist/fields/src/widgets/types.d.ts +2 -0
  37. package/dist/index.cjs +4 -2
  38. package/dist/index.js +2003 -618
  39. package/package.json +10 -8
  40. package/src/complex-widgets.test.tsx +146 -0
  41. package/src/datetime-widgets.test.tsx +93 -0
  42. package/src/fields.stories.tsx +566 -0
  43. package/src/index.tsx +303 -68
  44. package/src/new-widgets.test.tsx +302 -0
  45. package/src/remaining-widgets.test.tsx +172 -0
  46. package/src/standard-widgets.test.tsx +278 -0
  47. package/src/text-rich-widgets.test.tsx +104 -0
  48. package/src/visual-widgets.test.tsx +97 -0
  49. package/src/widgets/AddressField.tsx +112 -0
  50. package/src/widgets/AutoNumberField.tsx +16 -0
  51. package/src/widgets/AvatarField.tsx +110 -0
  52. package/src/widgets/BooleanField.tsx +19 -11
  53. package/src/widgets/CodeField.tsx +34 -0
  54. package/src/widgets/ColorField.tsx +44 -0
  55. package/src/widgets/CurrencyField.tsx +8 -7
  56. package/src/widgets/DateField.tsx +5 -2
  57. package/src/widgets/DateTimeField.tsx +28 -0
  58. package/src/widgets/EmailField.tsx +7 -3
  59. package/src/widgets/FileField.tsx +114 -0
  60. package/src/widgets/FormulaField.tsx +31 -0
  61. package/src/widgets/GeolocationField.tsx +151 -0
  62. package/src/widgets/GridField.tsx +63 -0
  63. package/src/widgets/ImageField.tsx +108 -0
  64. package/src/widgets/LocationField.tsx +46 -0
  65. package/src/widgets/LookupField.tsx +5 -5
  66. package/src/widgets/MasterDetailField.tsx +131 -0
  67. package/src/widgets/NumberField.tsx +8 -4
  68. package/src/widgets/ObjectField.tsx +85 -0
  69. package/src/widgets/PasswordField.tsx +45 -0
  70. package/src/widgets/PercentField.tsx +49 -0
  71. package/src/widgets/PhoneField.tsx +7 -3
  72. package/src/widgets/QRCodeField.tsx +96 -0
  73. package/src/widgets/RatingField.tsx +66 -0
  74. package/src/widgets/RichTextField.tsx +5 -5
  75. package/src/widgets/SelectField.tsx +7 -5
  76. package/src/widgets/SignatureField.tsx +140 -0
  77. package/src/widgets/SliderField.tsx +39 -0
  78. package/src/widgets/SummaryField.tsx +29 -0
  79. package/src/widgets/TextAreaField.tsx +9 -6
  80. package/src/widgets/TextField.tsx +13 -8
  81. package/src/widgets/TimeField.tsx +22 -0
  82. package/src/widgets/UrlField.tsx +7 -3
  83. package/src/widgets/UserField.tsx +88 -0
  84. package/src/widgets/VectorField.tsx +28 -0
  85. 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
+ });