@object-ui/plugin-grid 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.
Files changed (49) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +21 -1
  3. package/dist/index.js +631 -599
  4. package/dist/index.umd.cjs +8 -8
  5. package/package.json +44 -12
  6. package/.turbo/turbo-build.log +0 -32
  7. package/src/FormulaBar.tsx +0 -151
  8. package/src/GroupRow.tsx +0 -69
  9. package/src/ImportWizard.tsx +0 -412
  10. package/src/InlineEditing.tsx +0 -235
  11. package/src/ListColumnExtensions.test.tsx +0 -373
  12. package/src/ListColumnSchema.test.ts +0 -88
  13. package/src/ObjectGrid.EdgeCases.stories.tsx +0 -147
  14. package/src/ObjectGrid.msw.test.tsx +0 -130
  15. package/src/ObjectGrid.stories.tsx +0 -139
  16. package/src/ObjectGrid.tsx +0 -1598
  17. package/src/SplitPaneGrid.tsx +0 -120
  18. package/src/VirtualGrid.tsx +0 -183
  19. package/src/__tests__/GroupRow.test.tsx +0 -206
  20. package/src/__tests__/ImportPreview.test.tsx +0 -171
  21. package/src/__tests__/InlineEditing.test.tsx +0 -360
  22. package/src/__tests__/VirtualGrid.test.tsx +0 -438
  23. package/src/__tests__/accessibility.test.tsx +0 -254
  24. package/src/__tests__/accessorKey-inference.test.tsx +0 -132
  25. package/src/__tests__/airtable-style.test.tsx +0 -508
  26. package/src/__tests__/column-features.test.tsx +0 -490
  27. package/src/__tests__/grid-export.test.tsx +0 -121
  28. package/src/__tests__/mobile-card-view.test.tsx +0 -355
  29. package/src/__tests__/objectdef-enrichment.test.tsx +0 -566
  30. package/src/__tests__/performance-benchmark.test.tsx +0 -182
  31. package/src/__tests__/phase11-features.test.tsx +0 -418
  32. package/src/__tests__/row-bulk-actions.test.tsx +0 -413
  33. package/src/__tests__/row-height.test.tsx +0 -160
  34. package/src/__tests__/useGroupedData.test.ts +0 -165
  35. package/src/__tests__/view-states.test.tsx +0 -203
  36. package/src/components/BulkActionBar.tsx +0 -66
  37. package/src/components/RowActionMenu.tsx +0 -91
  38. package/src/index.test.tsx +0 -29
  39. package/src/index.tsx +0 -99
  40. package/src/useCellClipboard.ts +0 -136
  41. package/src/useColumnSummary.ts +0 -128
  42. package/src/useGradientColor.ts +0 -103
  43. package/src/useGroupReorder.ts +0 -123
  44. package/src/useGroupedData.ts +0 -187
  45. package/src/useRowColor.ts +0 -74
  46. package/tsconfig.json +0 -9
  47. package/vite.config.ts +0 -58
  48. package/vitest.config.ts +0 -13
  49. package/vitest.setup.ts +0 -1
@@ -1,132 +0,0 @@
1
- /**
2
- * AccessorKey Inference Tests
3
- *
4
- * Tests that accessorKey-format columns receive type inference
5
- * via inferColumnType() + getCellRenderer(), matching the behavior
6
- * of ListColumn (field) format columns.
7
- */
8
- import { describe, it, expect } from 'vitest';
9
- import { render, screen, waitFor } from '@testing-library/react';
10
- import '@testing-library/jest-dom';
11
- import React from 'react';
12
- import { ObjectGrid } from '../ObjectGrid';
13
- import { registerAllFields } from '@object-ui/fields';
14
- import { ActionProvider } from '@object-ui/react';
15
-
16
- registerAllFields();
17
-
18
- // --- Mock Data with various types ---
19
- const mockData = [
20
- {
21
- id: '1',
22
- name: 'Project Alpha',
23
- status: 'in_progress',
24
- priority: 'high',
25
- progress: 75,
26
- start_date: '2024-02-01T00:00:00.000Z',
27
- },
28
- {
29
- id: '2',
30
- name: 'Project Beta',
31
- status: 'completed',
32
- priority: 'low',
33
- progress: 100,
34
- start_date: '2024-03-15T00:00:00.000Z',
35
- },
36
- ];
37
-
38
- // Helper: Render ObjectGrid with accessorKey-format columns
39
- function renderAccessorGrid(columns: any[], data?: any[]) {
40
- const schema: any = {
41
- type: 'object-grid' as const,
42
- objectName: 'test_object',
43
- columns,
44
- data: { provider: 'value', items: data || mockData },
45
- };
46
-
47
- return render(
48
- <ActionProvider>
49
- <ObjectGrid schema={schema} />
50
- </ActionProvider>
51
- );
52
- }
53
-
54
- // =========================================================================
55
- // 1. accessorKey columns get type inference
56
- // =========================================================================
57
- describe('accessorKey-format: type inference', () => {
58
- it('should infer select type for status field and render badges', async () => {
59
- renderAccessorGrid([
60
- { header: 'Name', accessorKey: 'name' },
61
- { header: 'Status', accessorKey: 'status' },
62
- ]);
63
-
64
- await waitFor(() => {
65
- expect(screen.getByText('Name')).toBeInTheDocument();
66
- });
67
-
68
- // Status should be inferred as select and render humanized badges
69
- expect(screen.getByText('In Progress')).toBeInTheDocument();
70
- expect(screen.getByText('Completed')).toBeInTheDocument();
71
- });
72
-
73
- it('should infer date type for date fields', async () => {
74
- renderAccessorGrid([
75
- { header: 'Name', accessorKey: 'name' },
76
- { header: 'Start Date', accessorKey: 'start_date' },
77
- ]);
78
-
79
- await waitFor(() => {
80
- expect(screen.getByText('Name')).toBeInTheDocument();
81
- });
82
-
83
- // Date fields should NOT show raw ISO strings
84
- expect(screen.queryByText('2024-02-01T00:00:00.000Z')).not.toBeInTheDocument();
85
- });
86
-
87
- it('should infer percent type for progress field and render progress bar', async () => {
88
- renderAccessorGrid([
89
- { header: 'Name', accessorKey: 'name' },
90
- { header: 'Progress', accessorKey: 'progress' },
91
- ]);
92
-
93
- await waitFor(() => {
94
- expect(screen.getByText('Name')).toBeInTheDocument();
95
- });
96
-
97
- // Progress should render as percentage with progress bar
98
- expect(screen.getByText('75%')).toBeInTheDocument();
99
- const bars = screen.getAllByRole('progressbar');
100
- expect(bars.length).toBeGreaterThan(0);
101
- });
102
-
103
- it('should NOT override columns that already have a cell renderer', async () => {
104
- const customRenderer = (value: any) => <span data-testid="custom">{value}-custom</span>;
105
- renderAccessorGrid([
106
- { header: 'Name', accessorKey: 'name' },
107
- { header: 'Status', accessorKey: 'status', cell: customRenderer },
108
- ]);
109
-
110
- await waitFor(() => {
111
- expect(screen.getByText('Name')).toBeInTheDocument();
112
- });
113
-
114
- // Custom renderer should be preserved
115
- expect(screen.getByText('in_progress-custom')).toBeInTheDocument();
116
- });
117
-
118
- it('should pass through columns with explicit type', async () => {
119
- renderAccessorGrid([
120
- { header: 'Name', accessorKey: 'name' },
121
- { header: 'Priority', accessorKey: 'priority', type: 'select' },
122
- ]);
123
-
124
- await waitFor(() => {
125
- expect(screen.getByText('Name')).toBeInTheDocument();
126
- });
127
-
128
- // Priority with explicit select type should render as humanized badge
129
- expect(screen.getByText('High')).toBeInTheDocument();
130
- expect(screen.getByText('Low')).toBeInTheDocument();
131
- });
132
- });
@@ -1,508 +0,0 @@
1
- /**
2
- * Airtable-Style Grid Optimizations Tests
3
- *
4
- * Tests for auto-type inference (date, select, boolean, user),
5
- * row number column, compact density, and frozen first column defaults.
6
- */
7
- import { describe, it, expect, vi, beforeEach } from 'vitest';
8
- import { render, screen, waitFor } from '@testing-library/react';
9
- import '@testing-library/jest-dom';
10
- import React from 'react';
11
- import { ObjectGrid } from '../ObjectGrid';
12
- import { registerAllFields } from '@object-ui/fields';
13
- import { ActionProvider } from '@object-ui/react';
14
- import type { ListColumn } from '@object-ui/types';
15
-
16
- registerAllFields();
17
-
18
- // --- Mock Data with various field types ---
19
- const mockData = [
20
- {
21
- id: '1',
22
- subject: 'Task Alpha',
23
- status: 'In Progress',
24
- priority: 'High',
25
- category: 'Engineering',
26
- due_date: '2026-02-20T03:46:37.982Z',
27
- created_at: '2026-01-15T10:00:00.000Z',
28
- is_completed: true,
29
- assignee: 'Alice Smith',
30
- },
31
- {
32
- id: '2',
33
- subject: 'Task Beta',
34
- status: 'Done',
35
- priority: 'Low',
36
- category: 'Design',
37
- due_date: '2026-03-01T00:00:00.000Z',
38
- created_at: '2026-01-20T10:00:00.000Z',
39
- is_completed: false,
40
- assignee: 'Bob Jones',
41
- },
42
- {
43
- id: '3',
44
- subject: 'Task Gamma',
45
- status: 'To Do',
46
- priority: 'Medium',
47
- category: 'Engineering',
48
- due_date: '2026-04-15T00:00:00.000Z',
49
- created_at: '2026-02-01T10:00:00.000Z',
50
- is_completed: false,
51
- assignee: 'Charlie Brown',
52
- },
53
- ];
54
-
55
- function renderGrid(columns: ListColumn[], opts?: Record<string, any>) {
56
- const schema: any = {
57
- type: 'object-grid' as const,
58
- objectName: 'test_object',
59
- columns,
60
- data: { provider: 'value', items: mockData },
61
- ...opts,
62
- };
63
-
64
- return render(
65
- <ActionProvider>
66
- <ObjectGrid schema={schema} />
67
- </ActionProvider>
68
- );
69
- }
70
-
71
- // =========================================================================
72
- // 1. Auto-type inference: Date fields
73
- // =========================================================================
74
- describe('Auto-type inference: Date fields', () => {
75
- it('should format date fields containing "date" in name as human-readable dates', async () => {
76
- renderGrid([
77
- { field: 'subject', label: 'Subject' },
78
- { field: 'due_date', label: 'Due Date' },
79
- ]);
80
-
81
- await waitFor(() => {
82
- expect(screen.getByText('Subject')).toBeInTheDocument();
83
- });
84
-
85
- // Should NOT show raw ISO format
86
- expect(screen.queryByText('2026-02-20T03:46:37.982Z')).not.toBeInTheDocument();
87
- // Should show human-readable format (relative or absolute depending on distance from today)
88
- // Date values are rendered in relative format by default
89
- const cells = screen.getAllByRole('cell');
90
- const dateCell = cells.find(cell => cell.querySelector('span.tabular-nums'));
91
- expect(dateCell).toBeInTheDocument();
92
- });
93
-
94
- it('should format created_at fields as datetime', async () => {
95
- renderGrid([
96
- { field: 'subject', label: 'Subject' },
97
- { field: 'created_at', label: 'Created At' },
98
- ]);
99
-
100
- await waitFor(() => {
101
- expect(screen.getByText('Subject')).toBeInTheDocument();
102
- });
103
-
104
- // Should show human-readable datetime, not ISO string
105
- expect(screen.queryByText('2026-01-15T10:00:00.000Z')).not.toBeInTheDocument();
106
- // created_at now renders as datetime with split date/time display
107
- expect(screen.getByText('1/15/2026')).toBeInTheDocument();
108
- });
109
-
110
- it('should not override explicit type for date-like fields', async () => {
111
- renderGrid([
112
- { field: 'subject', label: 'Subject' },
113
- { field: 'due_date', label: 'Due Date', type: 'text' },
114
- ]);
115
-
116
- await waitFor(() => {
117
- expect(screen.getByText('Subject')).toBeInTheDocument();
118
- });
119
-
120
- // With explicit type: 'text', should render as text
121
- expect(screen.getByText('2026-02-20T03:46:37.982Z')).toBeInTheDocument();
122
- });
123
- });
124
-
125
- // =========================================================================
126
- // 2. Auto-type inference: Select/Badge fields
127
- // =========================================================================
128
- describe('Auto-type inference: Select/Badge fields', () => {
129
- it('should render status field as badge when values are enumerable', async () => {
130
- renderGrid([
131
- { field: 'subject', label: 'Subject' },
132
- { field: 'status', label: 'Status' },
133
- ]);
134
-
135
- await waitFor(() => {
136
- expect(screen.getByText('Subject')).toBeInTheDocument();
137
- });
138
-
139
- // Status values should be present
140
- expect(screen.getByText('In Progress')).toBeInTheDocument();
141
- expect(screen.getByText('Done')).toBeInTheDocument();
142
- expect(screen.getByText('To Do')).toBeInTheDocument();
143
- });
144
-
145
- it('should render priority field as badge', async () => {
146
- renderGrid([
147
- { field: 'subject', label: 'Subject' },
148
- { field: 'priority', label: 'Priority' },
149
- ]);
150
-
151
- await waitFor(() => {
152
- expect(screen.getByText('Subject')).toBeInTheDocument();
153
- });
154
-
155
- expect(screen.getByText('High')).toBeInTheDocument();
156
- expect(screen.getByText('Low')).toBeInTheDocument();
157
- expect(screen.getByText('Medium')).toBeInTheDocument();
158
- });
159
- });
160
-
161
- // =========================================================================
162
- // 3. Auto-type inference: Boolean/Checkbox fields
163
- // =========================================================================
164
- describe('Auto-type inference: Boolean/Checkbox fields', () => {
165
- it('should render is_completed as semantic completion indicator', async () => {
166
- renderGrid([
167
- { field: 'subject', label: 'Subject' },
168
- { field: 'is_completed', label: 'Is Completed' },
169
- ]);
170
-
171
- await waitFor(() => {
172
- expect(screen.getByText('Subject')).toBeInTheDocument();
173
- });
174
-
175
- // Should render green circle completion indicators (not checkboxes)
176
- const indicators = screen.getAllByTestId('completion-indicator');
177
- expect(indicators.length).toBeGreaterThan(0);
178
- });
179
- });
180
-
181
- // =========================================================================
182
- // 4. Row number column
183
- // =========================================================================
184
- describe('Row number column', () => {
185
- it('should show row numbers by default in ObjectGrid', async () => {
186
- renderGrid([
187
- { field: 'subject', label: 'Subject' },
188
- { field: 'status', label: 'Status' },
189
- ]);
190
-
191
- await waitFor(() => {
192
- expect(screen.getByText('Subject')).toBeInTheDocument();
193
- });
194
-
195
- // Row numbers should be rendered (1, 2, 3)
196
- expect(screen.getByText('1')).toBeInTheDocument();
197
- expect(screen.getByText('2')).toBeInTheDocument();
198
- expect(screen.getByText('3')).toBeInTheDocument();
199
- });
200
- });
201
-
202
- // =========================================================================
203
- // 5. Compact row density (default medium)
204
- // =========================================================================
205
- describe('Row density', () => {
206
- it('should apply default medium density classes', async () => {
207
- renderGrid([
208
- { field: 'subject', label: 'Subject' },
209
- ]);
210
-
211
- await waitFor(() => {
212
- expect(screen.getByText('Subject')).toBeInTheDocument();
213
- });
214
-
215
- // Verify data-table is rendered (implicitly has the cellClassName applied)
216
- expect(screen.getByText('Task Alpha')).toBeInTheDocument();
217
- });
218
- });
219
-
220
- // =========================================================================
221
- // 6. Frozen first column default
222
- // =========================================================================
223
- describe('Frozen first column', () => {
224
- it('should default frozenColumns to 1', async () => {
225
- renderGrid([
226
- { field: 'subject', label: 'Subject' },
227
- { field: 'status', label: 'Status' },
228
- ]);
229
-
230
- await waitFor(() => {
231
- expect(screen.getByText('Subject')).toBeInTheDocument();
232
- });
233
-
234
- // The first column header should have sticky class (indicating it's frozen)
235
- const subjectHeader = screen.getByText('Subject').closest('th');
236
- expect(subjectHeader).toHaveClass('sticky');
237
- });
238
-
239
- it('should not freeze columns when frozenColumns is explicitly 0', async () => {
240
- renderGrid(
241
- [
242
- { field: 'subject', label: 'Subject' },
243
- { field: 'status', label: 'Status' },
244
- ],
245
- { frozenColumns: 0 }
246
- );
247
-
248
- await waitFor(() => {
249
- expect(screen.getByText('Subject')).toBeInTheDocument();
250
- });
251
-
252
- const subjectHeader = screen.getByText('Subject').closest('th');
253
- expect(subjectHeader).not.toHaveClass('sticky');
254
- });
255
- });
256
-
257
- // =========================================================================
258
- // 7. Column header type icons
259
- // =========================================================================
260
- describe('Column header type icons', () => {
261
- it('should render type icons in column headers', async () => {
262
- renderGrid([
263
- { field: 'subject', label: 'Subject' },
264
- { field: 'status', label: 'Status' },
265
- { field: 'due_date', label: 'Due Date' },
266
- ]);
267
- await waitFor(() => {
268
- expect(screen.getByText('Subject')).toBeInTheDocument();
269
- });
270
- // Each column header should have an SVG icon
271
- const headers = screen.getAllByRole('columnheader');
272
- // Filter out utility headers (row numbers, checkboxes, actions)
273
- const dataHeaders = headers.filter(h => h.querySelector('svg'));
274
- expect(dataHeaders.length).toBeGreaterThanOrEqual(3);
275
- });
276
- });
277
-
278
- // =========================================================================
279
- // 8. Datetime type inference
280
- // =========================================================================
281
- describe('Datetime type inference', () => {
282
- it('should infer datetime type for created_at fields', async () => {
283
- renderGrid([
284
- { field: 'subject', label: 'Subject' },
285
- { field: 'created_at', label: 'Created At' },
286
- ]);
287
- await waitFor(() => {
288
- expect(screen.getByText('Subject')).toBeInTheDocument();
289
- });
290
- // Should show split datetime format (date and time separately)
291
- // The time part should be rendered in a separate muted span
292
- const dateEl = screen.getByText('1/15/2026');
293
- expect(dateEl).toBeInTheDocument();
294
- });
295
- });
296
-
297
- // =========================================================================
298
- // 9. Compound cell with prefix badge
299
- // =========================================================================
300
- describe('Compound cell with prefix badge', () => {
301
- it('should render prefix badge before the main value', async () => {
302
- renderGrid([
303
- { field: 'subject', label: 'Subject', prefix: { field: 'category', type: 'badge' } },
304
- { field: 'status', label: 'Status' },
305
- ]);
306
- await waitFor(() => {
307
- expect(screen.getByText('Subject')).toBeInTheDocument();
308
- });
309
- // The category values should appear as badges alongside subject
310
- const engineeringBadges = screen.getAllByText('Engineering');
311
- expect(engineeringBadges.length).toBeGreaterThanOrEqual(1);
312
- expect(screen.getByText('Task Alpha')).toBeInTheDocument();
313
- });
314
- });
315
-
316
- // =========================================================================
317
- // 10. Add record row
318
- // =========================================================================
319
- describe('Add record row', () => {
320
- it('should show add record row when operations.create is true', async () => {
321
- renderGrid(
322
- [
323
- { field: 'subject', label: 'Subject' },
324
- { field: 'status', label: 'Status' },
325
- ],
326
- { operations: { create: true } }
327
- );
328
- await waitFor(() => {
329
- expect(screen.getByText('Subject')).toBeInTheDocument();
330
- });
331
- expect(screen.getByTestId('add-record-row')).toBeInTheDocument();
332
- expect(screen.getByText('Add record')).toBeInTheDocument();
333
- });
334
-
335
- it('should not show add record row by default', async () => {
336
- renderGrid([
337
- { field: 'subject', label: 'Subject' },
338
- { field: 'status', label: 'Status' },
339
- ]);
340
- await waitFor(() => {
341
- expect(screen.getByText('Subject')).toBeInTheDocument();
342
- });
343
- expect(screen.queryByTestId('add-record-row')).not.toBeInTheDocument();
344
- });
345
- });
346
-
347
- // =========================================================================
348
- // 11. Primary field auto-link
349
- // =========================================================================
350
- describe('Primary field auto-link', () => {
351
- it('should render first column cells as clickable links (primary field)', async () => {
352
- renderGrid([
353
- { field: 'subject', label: 'Subject' },
354
- { field: 'status', label: 'Status' },
355
- ]);
356
- await waitFor(() => {
357
- expect(screen.getByText('Subject')).toBeInTheDocument();
358
- });
359
-
360
- // First column cells should be rendered as buttons (links) with primary-field-link testid
361
- const primaryLinks = screen.getAllByTestId('primary-field-link');
362
- expect(primaryLinks.length).toBe(3); // 3 data rows
363
- expect(primaryLinks[0]).toHaveTextContent('Task Alpha');
364
- expect(primaryLinks[1]).toHaveTextContent('Task Beta');
365
- expect(primaryLinks[2]).toHaveTextContent('Task Gamma');
366
- });
367
-
368
- it('should style primary field cells with font-medium and text-primary', async () => {
369
- renderGrid([
370
- { field: 'subject', label: 'Subject' },
371
- { field: 'status', label: 'Status' },
372
- ]);
373
- await waitFor(() => {
374
- expect(screen.getByText('Subject')).toBeInTheDocument();
375
- });
376
-
377
- const primaryLinks = screen.getAllByTestId('primary-field-link');
378
- // Primary field links should have text-primary and font-medium classes
379
- expect(primaryLinks[0]).toHaveClass('text-primary');
380
- expect(primaryLinks[0]).toHaveClass('font-medium');
381
- });
382
-
383
- it('should not auto-link first column when it already has explicit link=true', async () => {
384
- renderGrid([
385
- { field: 'subject', label: 'Subject', link: true },
386
- { field: 'status', label: 'Status' },
387
- ]);
388
- await waitFor(() => {
389
- expect(screen.getByText('Subject')).toBeInTheDocument();
390
- });
391
-
392
- // Still should have clickable buttons (via explicit link), but no primary-field-link testid
393
- // because isPrimaryField is only true when !col.link && !col.action
394
- const primaryLinks = screen.queryAllByTestId('primary-field-link');
395
- expect(primaryLinks.length).toBe(0);
396
-
397
- // But the cells should still be buttons (from col.link path)
398
- const subjectCell = screen.getByText('Task Alpha');
399
- expect(subjectCell.closest('button')).toBeTruthy();
400
- });
401
- });
402
-
403
- // =========================================================================
404
- // 12. Empty value display
405
- // =========================================================================
406
- describe('Empty value display', () => {
407
- it('should show styled empty indicator for null/empty values', async () => {
408
- const dataWithEmpty = [
409
- { id: '1', subject: 'Task Alpha', company: null },
410
- { id: '2', subject: 'Task Beta', company: '' },
411
- { id: '3', subject: 'Task Gamma', company: 'Acme' },
412
- ];
413
-
414
- const schema: any = {
415
- type: 'object-grid' as const,
416
- objectName: 'test_object',
417
- columns: [
418
- { field: 'subject', label: 'Subject' },
419
- { field: 'company', label: 'Company' },
420
- ],
421
- data: { provider: 'value', items: dataWithEmpty },
422
- };
423
-
424
- render(
425
- <ActionProvider>
426
- <ObjectGrid schema={schema} />
427
- </ActionProvider>
428
- );
429
-
430
- await waitFor(() => {
431
- expect(screen.getByText('Subject')).toBeInTheDocument();
432
- });
433
-
434
- // Non-empty value should be displayed normally
435
- expect(screen.getByText('Acme')).toBeInTheDocument();
436
-
437
- // Empty values should show muted dash indicators
438
- const emptyIndicators = screen.getAllByText('—');
439
- expect(emptyIndicators.length).toBeGreaterThanOrEqual(2);
440
- // Verify they have the styled classes
441
- emptyIndicators.forEach(el => {
442
- expect(el.className).toContain('italic');
443
- });
444
- });
445
- });
446
-
447
- // =========================================================================
448
- // 13. Record detail panel (form-based)
449
- // =========================================================================
450
- describe('Record detail panel', () => {
451
- it('should render form-based record detail with renderRecordDetail', async () => {
452
- const onRowClick = vi.fn();
453
- renderGrid(
454
- [
455
- { field: 'subject', label: 'Subject' },
456
- { field: 'status', label: 'Status' },
457
- ],
458
- {
459
- navigation: { mode: 'drawer' },
460
- }
461
- );
462
-
463
- await waitFor(() => {
464
- expect(screen.getByText('Subject')).toBeInTheDocument();
465
- });
466
-
467
- // The grid should render successfully with navigation configured
468
- expect(screen.getByText('Task Alpha')).toBeInTheDocument();
469
- });
470
- });
471
-
472
- // =========================================================================
473
- // 13. Auto-type inference: Currency/Amount fields
474
- // =========================================================================
475
- describe('Auto-type inference: Currency/Amount fields', () => {
476
- const currencyData = [
477
- { id: '1', name: 'Order 1', total_amount: 15459.99 },
478
- { id: '2', name: 'Order 2', total_amount: 289.50 },
479
- ];
480
-
481
- function renderCurrencyGrid(columns: ListColumn[]) {
482
- const schema: any = {
483
- type: 'object-grid' as const,
484
- objectName: 'test_object',
485
- columns,
486
- data: { provider: 'value', items: currencyData },
487
- };
488
-
489
- return render(
490
- <ActionProvider>
491
- <ObjectGrid schema={schema} />
492
- </ActionProvider>
493
- );
494
- }
495
-
496
- it('should auto-infer currency type for amount fields and format values', async () => {
497
- renderCurrencyGrid([
498
- { field: 'name', label: 'Name' },
499
- { field: 'total_amount', label: 'Amount' },
500
- ]);
501
- await waitFor(() => {
502
- expect(screen.getByText('Name')).toBeInTheDocument();
503
- });
504
- // Should show formatted currency (e.g. "$15,459.99") instead of raw "15459.99"
505
- expect(screen.queryByText('15459.99')).not.toBeInTheDocument();
506
- expect(screen.getByText(/15,459\.99/)).toBeInTheDocument();
507
- });
508
- });