@object-ui/fields 4.0.1 → 4.0.3

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 (26) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/package.json +7 -7
  3. package/dist/packages/fields/src/__tests__/boolean-checkbox.test.d.ts +0 -0
  4. package/dist/packages/fields/src/__tests__/cell-renderers.test.d.ts +0 -0
  5. package/dist/packages/fields/src/__tests__/datetime-cell.test.d.ts +0 -0
  6. package/dist/packages/fields/src/__tests__/datetime-timezone.test.d.ts +0 -0
  7. package/dist/packages/fields/src/__tests__/extreme-inputs.test.d.ts +0 -0
  8. package/dist/packages/fields/src/__tests__/form-variants.test.d.ts +0 -0
  9. package/dist/packages/fields/src/lookup-depends-on.test.d.ts +0 -1
  10. package/dist/packages/fields/src/new-widgets.test.d.ts +0 -8
  11. package/dist/packages/fields/src/record-picker.test.d.ts +0 -1
  12. package/dist/packages/fields/src/remaining-widgets.test.d.ts +0 -1
  13. package/dist/packages/fields/src/text-rich-widgets.test.d.ts +0 -1
  14. package/dist/packages/fields/src/visual-widgets.test.d.ts +0 -1
  15. package/src/__tests__/boolean-checkbox.test.tsx +0 -74
  16. package/src/__tests__/cell-renderers.test.tsx +0 -1193
  17. package/src/__tests__/datetime-cell.test.tsx +0 -46
  18. package/src/__tests__/datetime-timezone.test.tsx +0 -770
  19. package/src/__tests__/extreme-inputs.test.tsx +0 -567
  20. package/src/__tests__/form-variants.test.tsx +0 -306
  21. package/src/lookup-depends-on.test.tsx +0 -130
  22. package/src/new-widgets.test.tsx +0 -302
  23. package/src/record-picker.test.tsx +0 -1703
  24. package/src/remaining-widgets.test.tsx +0 -211
  25. package/src/text-rich-widgets.test.tsx +0 -104
  26. package/src/visual-widgets.test.tsx +0 -97
@@ -1,211 +0,0 @@
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
- it('slider renders and syncs with input value', () => {
97
- render(<PercentField {...baseProps} value={0.5} />);
98
- const slider = screen.getByTestId('percent-slider');
99
- expect(slider).toBeInTheDocument();
100
- });
101
-
102
- it('slider does not fire onChange when disabled', () => {
103
- const handleChange = vi.fn();
104
- render(<PercentField {...baseProps} onChange={handleChange} value={0.5} disabled={true} />);
105
- const slider = screen.getByTestId('percent-slider');
106
- expect(slider).toBeInTheDocument();
107
- // The slider should be disabled, so no changes should fire
108
- expect(handleChange).not.toHaveBeenCalled();
109
- });
110
-
111
- it('slider is not rendered in readonly mode', () => {
112
- render(<PercentField {...baseProps} value={0.5} readonly={true} />);
113
- expect(screen.queryByTestId('percent-slider')).not.toBeInTheDocument();
114
- });
115
-
116
- it('handles null value gracefully for slider', () => {
117
- render(<PercentField {...baseProps} value={null as any} />);
118
- const slider = screen.getByTestId('percent-slider');
119
- expect(slider).toBeInTheDocument();
120
- });
121
- });
122
-
123
- // 5. ImageField
124
- describe('ImageField', () => {
125
- const images = [
126
- { url: 'http://example.com/1.jpg', name: 'Img 1' },
127
- { url: 'http://example.com/2.jpg', name: 'Img 2' }
128
- ];
129
-
130
- it('should render images in readonly mode', () => {
131
- render(<ImageField {...baseProps} value={images} readonly={true} />);
132
- const imgs = screen.getAllByRole('img');
133
- expect(imgs).toHaveLength(2);
134
- expect(imgs[0]).toHaveAttribute('src', 'http://example.com/1.jpg');
135
- });
136
-
137
- it('renders a Crop button per image when crop is enabled (default)', () => {
138
- render(<ImageField {...baseProps} value={images} field={{ ...baseProps.field, multiple: true } as any} />);
139
- // Two crop buttons (one per image) with stable test ids — opacity is hover-controlled
140
- // but the buttons are still present in the DOM for a11y/keyboard users.
141
- expect(screen.getByTestId('image-field-crop-0')).toBeInTheDocument();
142
- expect(screen.getByTestId('image-field-crop-1')).toBeInTheDocument();
143
- });
144
-
145
- it('hides the Crop button when field.crop = false', () => {
146
- render(<ImageField {...baseProps} value={images} field={{ ...baseProps.field, multiple: true, crop: false } as any} />);
147
- expect(screen.queryByTestId('image-field-crop-0')).not.toBeInTheDocument();
148
- });
149
-
150
- // Complex interaction omitted for brevity, focusing on render contracts
151
- });
152
-
153
- // 6. LocationField
154
- describe('LocationField', () => {
155
- it('should render lat,long string', () => {
156
- const loc = { latitude: 34.05, longitude: -118.25 };
157
- render(<LocationField {...baseProps} value={loc} />);
158
- const input = screen.getByRole('textbox') as HTMLInputElement;
159
- expect(input.value).toContain('34.05, -118.25');
160
- });
161
-
162
- it('should parse string input to object', () => {
163
- const handleChange = vi.fn();
164
- render(<LocationField {...baseProps} onChange={handleChange} />);
165
- const input = screen.getByRole('textbox');
166
- fireEvent.change(input, { target: { value: '10.5, 20.5' } });
167
- expect(handleChange).toHaveBeenCalledWith({ latitude: 10.5, longitude: 20.5 });
168
- });
169
- });
170
-
171
- // 7. ObjectField
172
- describe('ObjectField', () => {
173
- const obj = { foo: 'bar', num: 1 };
174
-
175
- it('should render JSON string in readonly', () => {
176
- render(<ObjectField {...baseProps} value={obj} readonly={true} />);
177
- // pre tag usually contains formatted JSON
178
- expect(screen.getByText(/"foo": "bar"/)).toBeInTheDocument();
179
- });
180
-
181
- it('should parse valid JSON input', () => {
182
- const handleChange = vi.fn();
183
- render(<ObjectField {...baseProps} onChange={handleChange} value={obj} />);
184
- const textarea = screen.getByRole('textbox');
185
- const newJson = '{"test": true}';
186
- fireEvent.change(textarea, { target: { value: newJson } });
187
- expect(handleChange).toHaveBeenCalledWith({ test: true });
188
- });
189
- });
190
-
191
- // 8. UserField
192
- describe('UserField', () => {
193
- const user = { name: 'Alice Smith', id: 'u1' };
194
-
195
- it('should render user avatar in readonly', () => {
196
- render(<UserField {...baseProps} value={user} readonly={true} />);
197
- // Avatar fallback logic splits name
198
- expect(screen.getByText('AS')).toBeInTheDocument();
199
- });
200
- });
201
-
202
- // 9. VectorField
203
- describe('VectorField', () => {
204
- it('should render vector values', () => {
205
- const vec = [0.12345, 0.67891, 0.00001];
206
- render(<VectorField {...baseProps} value={vec} />);
207
- expect(screen.getByText(/0.1235/)).toBeInTheDocument(); // toFixed(4)
208
- expect(screen.getByText('(3D)')).toBeInTheDocument();
209
- });
210
- });
211
- });
@@ -1,104 +0,0 @@
1
- import { render, screen, fireEvent } from '@testing-library/react';
2
- import { describe, it, expect, vi } from 'vitest';
3
- import { TextAreaField } from './widgets/TextAreaField';
4
- import { RichTextField } from './widgets/RichTextField';
5
- import { CodeField } from './widgets/CodeField';
6
- import { PasswordField } from './widgets/PasswordField';
7
- import type { FieldWidgetProps } from './widgets/types';
8
-
9
- const mockField = {
10
- name: 'test_field',
11
- label: 'Test Field',
12
- } as any;
13
-
14
- const baseProps: FieldWidgetProps<string> = {
15
- field: mockField,
16
- value: '',
17
- onChange: vi.fn(),
18
- readonly: false,
19
- };
20
-
21
- describe('Text & Rich Content Widgets', () => {
22
- describe('TextAreaField', () => {
23
- it('renders textarea in edit mode', () => {
24
- render(<TextAreaField {...baseProps} />);
25
- expect(screen.getByRole('textbox')).toBeInTheDocument();
26
- });
27
-
28
- it('handles max length indicator', () => {
29
- const propsWithMax = {
30
- ...baseProps,
31
- field: { ...mockField, max_length: 100 }
32
- };
33
- render(<TextAreaField {...propsWithMax} value="testing" />);
34
- expect(screen.getByLabelText('Character count: 7 of 100')).toBeInTheDocument();
35
- });
36
-
37
- it('renders whitespace-pre-wrap in readonly mode', () => {
38
- const text = 'Line 1\nLine 2';
39
- render(<TextAreaField {...baseProps} readonly value={text} />);
40
- // Check existence logic
41
- const display = screen.getByText((content) =>
42
- content.includes('Line 1') && content.includes('Line 2')
43
- );
44
- expect(display).toHaveClass('whitespace-pre-wrap');
45
- });
46
- });
47
-
48
- describe('RichTextField', () => {
49
- // Current implementation is a simpler textarea wrapper
50
- it('renders textarea with format indicator', () => {
51
- render(<RichTextField {...baseProps} />);
52
- expect(screen.getByRole('textbox')).toBeInTheDocument();
53
- expect(screen.getByText((content) => content.includes('Format: markdown'))).toBeInTheDocument();
54
- });
55
-
56
- it('renders prose in readonly mode', () => {
57
- render(<RichTextField {...baseProps} readonly value="# Heading" />);
58
- const display = screen.getByText('# Heading');
59
- expect(display).toHaveClass('prose');
60
- });
61
- });
62
-
63
- describe('CodeField', () => {
64
- it('renders textarea with monospace font', () => {
65
- render(<CodeField {...baseProps} />);
66
- const input = screen.getByRole('textbox');
67
- expect(input).toHaveClass('font-mono');
68
- });
69
-
70
- it('renders code block in readonly mode', () => {
71
- render(<CodeField {...baseProps} readonly value="const x = 1;" />);
72
- // Check for pre > code structure
73
- const pre = document.querySelector('pre');
74
- expect(pre).toBeInTheDocument();
75
- expect(pre).toHaveTextContent('const x = 1;');
76
- });
77
- });
78
-
79
- describe('PasswordField', () => {
80
- it('renders password input by default', () => {
81
- const { container } = render(<PasswordField {...baseProps} />);
82
- const input = container.querySelector('input[type="password"]');
83
- expect(input).toBeInTheDocument();
84
- });
85
-
86
- it('toggles visibility', () => {
87
- const { container } = render(<PasswordField {...baseProps} />);
88
- const button = screen.getByRole('button'); // The eye icon button
89
- const input = container.querySelector('input') as HTMLInputElement;
90
-
91
- expect(input.type).toBe('password');
92
- fireEvent.click(button);
93
- expect(input.type).toBe('text');
94
- fireEvent.click(button);
95
- expect(input.type).toBe('password');
96
- });
97
-
98
- it('renders masked bullets in readonly mode', () => {
99
- render(<PasswordField {...baseProps} readonly value="secret" />);
100
- expect(screen.getByText('••••••••')).toBeInTheDocument();
101
- expect(screen.queryByText('secret')).not.toBeInTheDocument();
102
- });
103
- });
104
- });
@@ -1,97 +0,0 @@
1
- import { render, screen, fireEvent } from '@testing-library/react';
2
- import { describe, it, expect, vi } from 'vitest';
3
- import { ColorField } from './widgets/ColorField';
4
- import { RatingField } from './widgets/RatingField';
5
- import { SliderField } from './widgets/SliderField';
6
- import { QRCodeField } from './widgets/QRCodeField'; // Intentionally simpler than Image/Avatar
7
- import type { FieldWidgetProps } from './widgets/types';
8
-
9
- const mockField = {
10
- name: 'test_field',
11
- label: 'Test Field',
12
- } as any;
13
-
14
- const baseProps: FieldWidgetProps<any> = {
15
- field: mockField,
16
- value: undefined,
17
- onChange: vi.fn(),
18
- readonly: false,
19
- };
20
-
21
- describe('Visual & Display Widgets', () => {
22
- describe('ColorField', () => {
23
- it('renders color picker + text input in edit mode', () => {
24
- const { container } = render(<ColorField {...baseProps} value="#00ff00" />);
25
- const colorInput = container.querySelector('input[type="color"]');
26
- const textInput = screen.getByRole('textbox');
27
-
28
- expect(colorInput).toBeInTheDocument();
29
- expect(textInput).toBeInTheDocument();
30
- expect(textInput).toHaveValue('#00ff00');
31
- });
32
-
33
- it('syncs change from color picker', () => {
34
- const { container } = render(<ColorField {...baseProps} />);
35
- const colorInput = container.querySelector('input[type="color"]') as HTMLInputElement;
36
- fireEvent.change(colorInput, { target: { value: '#ff0000' } });
37
- expect(baseProps.onChange).toHaveBeenCalledWith('#ff0000');
38
- });
39
-
40
- it('shows preview box in readonly mode', () => {
41
- const { container } = render(<ColorField {...baseProps} readonly value="#0000ff" />);
42
- // Check for the style attribute on the div
43
- // We search for a div that has the style background-color
44
- // This is a bit fragile but standard for inline styles
45
- const preview = container.querySelector('div[style="background-color: #0000ff;"]');
46
- // Or looser check:
47
- const divs = container.querySelectorAll('div');
48
- const found = Array.from(divs).some(d => d.style.backgroundColor === 'rgb(0, 0, 255)' || d.style.backgroundColor === '#0000ff');
49
- expect(found).toBe(true);
50
- expect(screen.getByText('#0000ff')).toBeInTheDocument();
51
- });
52
- });
53
-
54
- describe('RatingField', () => {
55
- it('renders stars', () => {
56
- render(<RatingField {...baseProps} value={3} />);
57
- // Should see 5 buttons (default max)
58
- const buttons = screen.getAllByRole('button');
59
- expect(buttons).toHaveLength(5);
60
- });
61
-
62
- it('highlights correct number of stars in readonly', () => {
63
- const { container } = render(<RatingField {...baseProps} readonly value={3} />);
64
- // Should have 3 filled stars, 2 empty.
65
- // Logic: fill-yellow-400
66
- const filled = container.querySelectorAll('.fill-yellow-400');
67
- expect(filled).toHaveLength(3);
68
- expect(screen.getByText('3 / 5')).toBeInTheDocument();
69
- });
70
-
71
- it('calls onChange when clicking star', () => {
72
- render(<RatingField {...baseProps} />);
73
- const buttons = screen.getAllByRole('button');
74
- fireEvent.click(buttons[3]); // 4th star (index 3) -> value 4
75
- expect(baseProps.onChange).toHaveBeenCalledWith(4);
76
- });
77
- });
78
-
79
- describe('SliderField', () => {
80
- it('renders slider', () => {
81
- const { container } = render(<SliderField {...baseProps} value={50} />);
82
- // Radix slider usually has role="slider"
83
- const slider = screen.getByRole('slider');
84
- expect(slider).toBeInTheDocument();
85
- // Check text display
86
- expect(screen.getByText('50')).toBeInTheDocument();
87
- });
88
-
89
- it('renders readonly view', () => {
90
- render(<SliderField {...baseProps} readonly value={75} />);
91
- expect(screen.getByText('75')).toBeInTheDocument();
92
- expect(screen.getByText('/ 100')).toBeInTheDocument();
93
- expect(screen.queryByRole('slider')).not.toBeInTheDocument();
94
- });
95
- });
96
-
97
- });