@object-ui/plugin-dashboard 3.1.5 → 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 (84) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/README.md +21 -1
  3. package/dist/index.d.ts +1 -1
  4. package/dist/index.js +1162 -939
  5. package/dist/index.umd.cjs +4 -4
  6. package/dist/packages/plugin-dashboard/src/DashboardConfigPanel.d.ts.map +1 -0
  7. package/dist/packages/plugin-dashboard/src/DashboardConfigPanel.stories.d.ts.map +1 -0
  8. package/dist/packages/plugin-dashboard/src/DashboardGridLayout.d.ts.map +1 -0
  9. package/dist/{src → packages/plugin-dashboard/src}/DashboardRenderer.d.ts +5 -0
  10. package/dist/packages/plugin-dashboard/src/DashboardRenderer.d.ts.map +1 -0
  11. package/dist/packages/plugin-dashboard/src/DashboardRenderer.stories.d.ts.map +1 -0
  12. package/dist/packages/plugin-dashboard/src/DashboardWithConfig.d.ts.map +1 -0
  13. package/dist/{src → packages/plugin-dashboard/src}/MetricCard.d.ts +4 -0
  14. package/dist/packages/plugin-dashboard/src/MetricCard.d.ts.map +1 -0
  15. package/dist/packages/plugin-dashboard/src/MetricWidget.d.ts +31 -0
  16. package/dist/packages/plugin-dashboard/src/MetricWidget.d.ts.map +1 -0
  17. package/dist/packages/plugin-dashboard/src/ObjectDataTable.d.ts.map +1 -0
  18. package/dist/packages/plugin-dashboard/src/ObjectMetricWidget.d.ts +59 -0
  19. package/dist/packages/plugin-dashboard/src/ObjectMetricWidget.d.ts.map +1 -0
  20. package/dist/packages/plugin-dashboard/src/ObjectPivotTable.d.ts.map +1 -0
  21. package/dist/packages/plugin-dashboard/src/PivotTable.d.ts.map +1 -0
  22. package/dist/packages/plugin-dashboard/src/WidgetConfigPanel.d.ts.map +1 -0
  23. package/dist/{src → packages/plugin-dashboard/src}/index.d.ts +4 -2
  24. package/dist/packages/plugin-dashboard/src/index.d.ts.map +1 -0
  25. package/dist/packages/plugin-dashboard/src/utils.d.ts.map +1 -0
  26. package/package.json +44 -11
  27. package/.turbo/turbo-build.log +0 -34
  28. package/dist/src/DashboardConfigPanel.d.ts.map +0 -1
  29. package/dist/src/DashboardConfigPanel.stories.d.ts.map +0 -1
  30. package/dist/src/DashboardGridLayout.d.ts.map +0 -1
  31. package/dist/src/DashboardRenderer.d.ts.map +0 -1
  32. package/dist/src/DashboardRenderer.stories.d.ts.map +0 -1
  33. package/dist/src/DashboardWithConfig.d.ts.map +0 -1
  34. package/dist/src/MetricCard.d.ts.map +0 -1
  35. package/dist/src/MetricWidget.d.ts +0 -24
  36. package/dist/src/MetricWidget.d.ts.map +0 -1
  37. package/dist/src/ObjectDataTable.d.ts.map +0 -1
  38. package/dist/src/ObjectPivotTable.d.ts.map +0 -1
  39. package/dist/src/PivotTable.d.ts.map +0 -1
  40. package/dist/src/WidgetConfigPanel.d.ts.map +0 -1
  41. package/dist/src/index.d.ts.map +0 -1
  42. package/dist/src/utils.d.ts.map +0 -1
  43. package/src/DashboardConfigPanel.stories.tsx +0 -164
  44. package/src/DashboardConfigPanel.tsx +0 -158
  45. package/src/DashboardGridLayout.tsx +0 -367
  46. package/src/DashboardRenderer.stories.tsx +0 -173
  47. package/src/DashboardRenderer.tsx +0 -445
  48. package/src/DashboardWithConfig.tsx +0 -211
  49. package/src/MetricCard.tsx +0 -82
  50. package/src/MetricWidget.tsx +0 -76
  51. package/src/ObjectDataTable.tsx +0 -226
  52. package/src/ObjectPivotTable.tsx +0 -160
  53. package/src/PivotTable.tsx +0 -262
  54. package/src/WidgetConfigPanel.tsx +0 -540
  55. package/src/__tests__/DashboardConfigPanel.test.tsx +0 -206
  56. package/src/__tests__/DashboardGridLayout.test.tsx +0 -199
  57. package/src/__tests__/DashboardRenderer.autoRefresh.test.tsx +0 -124
  58. package/src/__tests__/DashboardRenderer.designMode.test.tsx +0 -386
  59. package/src/__tests__/DashboardRenderer.header.test.tsx +0 -114
  60. package/src/__tests__/DashboardRenderer.mobile.test.tsx +0 -214
  61. package/src/__tests__/DashboardRenderer.widgetData.test.tsx +0 -1283
  62. package/src/__tests__/DashboardWithConfig.test.tsx +0 -276
  63. package/src/__tests__/MetricCard.test.tsx +0 -82
  64. package/src/__tests__/ObjectDataTable.test.tsx +0 -211
  65. package/src/__tests__/ObjectPivotTable.test.tsx +0 -192
  66. package/src/__tests__/PivotTable.test.tsx +0 -162
  67. package/src/__tests__/WidgetConfigPanel.test.tsx +0 -492
  68. package/src/__tests__/ensureWidgetIds.test.tsx +0 -103
  69. package/src/index.tsx +0 -214
  70. package/src/utils.ts +0 -17
  71. package/tsconfig.json +0 -19
  72. package/vite.config.ts +0 -63
  73. package/vitest.config.ts +0 -9
  74. package/vitest.setup.tsx +0 -18
  75. /package/dist/{src → packages/plugin-dashboard/src}/DashboardConfigPanel.d.ts +0 -0
  76. /package/dist/{src → packages/plugin-dashboard/src}/DashboardConfigPanel.stories.d.ts +0 -0
  77. /package/dist/{src → packages/plugin-dashboard/src}/DashboardGridLayout.d.ts +0 -0
  78. /package/dist/{src → packages/plugin-dashboard/src}/DashboardRenderer.stories.d.ts +0 -0
  79. /package/dist/{src → packages/plugin-dashboard/src}/DashboardWithConfig.d.ts +0 -0
  80. /package/dist/{src → packages/plugin-dashboard/src}/ObjectDataTable.d.ts +0 -0
  81. /package/dist/{src → packages/plugin-dashboard/src}/ObjectPivotTable.d.ts +0 -0
  82. /package/dist/{src → packages/plugin-dashboard/src}/PivotTable.d.ts +0 -0
  83. /package/dist/{src → packages/plugin-dashboard/src}/WidgetConfigPanel.d.ts +0 -0
  84. /package/dist/{src → packages/plugin-dashboard/src}/utils.d.ts +0 -0
@@ -1,1283 +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 } from 'vitest';
10
- import { render } from '@testing-library/react';
11
- import { DashboardRenderer } from '../DashboardRenderer';
12
-
13
- /**
14
- * Extract component schemas rendered by SchemaRenderer from the DOM.
15
- * When a component type is not registered, SchemaRenderer renders
16
- * an error block containing a JSON <pre> element with the schema.
17
- * We parse those to verify the schema shape produced by DashboardRenderer.
18
- */
19
- function getRenderedSchemas(container: HTMLElement): any[] {
20
- const pres = container.querySelectorAll('pre');
21
- return Array.from(pres).map(el => JSON.parse(el.textContent!));
22
- }
23
-
24
- describe('DashboardRenderer widget data extraction', () => {
25
- it('should extract chart data from options.data.items', () => {
26
- const schema = {
27
- type: 'dashboard' as const,
28
- name: 'test',
29
- title: 'Test',
30
- widgets: [
31
- {
32
- type: 'bar',
33
- title: 'Test Bar',
34
- layout: { x: 0, y: 0, w: 2, h: 2 },
35
- options: {
36
- xField: 'name',
37
- yField: 'value',
38
- data: {
39
- provider: 'value',
40
- items: [
41
- { name: 'A', value: 100 },
42
- { name: 'B', value: 200 },
43
- ],
44
- },
45
- },
46
- },
47
- ],
48
- } as any;
49
-
50
- const { container } = render(<DashboardRenderer schema={schema} />);
51
- const schemas = getRenderedSchemas(container);
52
- const chartSchema = schemas.find(s => s.type === 'chart');
53
-
54
- expect(chartSchema).toBeDefined();
55
- expect(chartSchema.chartType).toBe('bar');
56
- expect(chartSchema.data).toHaveLength(2);
57
- expect(chartSchema.data[0]).toEqual({ name: 'A', value: 100 });
58
- expect(chartSchema.xAxisKey).toBe('name');
59
- expect(chartSchema.series).toEqual([{ dataKey: 'value' }]);
60
- });
61
-
62
- it('should extract chart data from widget.data.items (backward compat)', () => {
63
- const schema = {
64
- type: 'dashboard' as const,
65
- name: 'test',
66
- title: 'Test',
67
- widgets: [
68
- {
69
- type: 'area',
70
- title: 'Test Area',
71
- layout: { x: 0, y: 0, w: 3, h: 2 },
72
- options: { xField: 'month', yField: 'revenue' },
73
- data: {
74
- provider: 'value',
75
- items: [
76
- { month: 'Jan', revenue: 155000 },
77
- { month: 'Feb', revenue: 87000 },
78
- ],
79
- },
80
- },
81
- ],
82
- } as any;
83
-
84
- const { container } = render(<DashboardRenderer schema={schema} />);
85
- const schemas = getRenderedSchemas(container);
86
- const chartSchema = schemas.find(s => s.type === 'chart');
87
-
88
- expect(chartSchema).toBeDefined();
89
- expect(chartSchema.chartType).toBe('area');
90
- expect(chartSchema.data).toHaveLength(2);
91
- expect(chartSchema.data[0].month).toBe('Jan');
92
- });
93
-
94
- it('should extract table data from options.data.items', () => {
95
- const schema = {
96
- type: 'dashboard' as const,
97
- name: 'test',
98
- title: 'Test',
99
- widgets: [
100
- {
101
- type: 'table',
102
- title: 'Test Table',
103
- layout: { x: 0, y: 0, w: 4, h: 2 },
104
- options: {
105
- columns: [
106
- { header: 'Name', accessorKey: 'name' },
107
- { header: 'Amount', accessorKey: 'amount' },
108
- ],
109
- data: {
110
- provider: 'value',
111
- items: [
112
- { name: 'Item A', amount: '$100' },
113
- { name: 'Item B', amount: '$200' },
114
- { name: 'Item C', amount: '$300' },
115
- ],
116
- },
117
- },
118
- },
119
- ],
120
- } as any;
121
-
122
- const { container } = render(<DashboardRenderer schema={schema} />);
123
- // data-table is a registered component that renders a real table,
124
- // so we verify the data reaches it by checking for rendered cell content
125
- expect(container.textContent).toContain('Item A');
126
- expect(container.textContent).toContain('$200');
127
- expect(container.textContent).toContain('Item C');
128
- });
129
-
130
- it('should handle donut chart data from options', () => {
131
- const schema = {
132
- type: 'dashboard' as const,
133
- name: 'test',
134
- title: 'Test',
135
- widgets: [
136
- {
137
- type: 'donut',
138
- title: 'Test Donut',
139
- layout: { x: 0, y: 0, w: 1, h: 2 },
140
- options: {
141
- xField: 'source',
142
- yField: 'value',
143
- data: {
144
- provider: 'value',
145
- items: [
146
- { source: 'Web', value: 2 },
147
- { source: 'Referral', value: 1 },
148
- ],
149
- },
150
- },
151
- },
152
- ],
153
- } as any;
154
-
155
- const { container } = render(<DashboardRenderer schema={schema} />);
156
- const schemas = getRenderedSchemas(container);
157
- const chartSchema = schemas.find(s => s.type === 'chart');
158
-
159
- expect(chartSchema).toBeDefined();
160
- expect(chartSchema.chartType).toBe('donut');
161
- expect(chartSchema.data).toHaveLength(2);
162
- expect(chartSchema.xAxisKey).toBe('source');
163
- });
164
-
165
- it('should default to empty array when no data is provided', () => {
166
- const schema = {
167
- type: 'dashboard' as const,
168
- name: 'test',
169
- title: 'Test',
170
- widgets: [
171
- {
172
- type: 'bar',
173
- title: 'No Data Bar',
174
- layout: { x: 0, y: 0, w: 2, h: 2 },
175
- options: { xField: 'x', yField: 'y' },
176
- },
177
- ],
178
- } as any;
179
-
180
- const { container } = render(<DashboardRenderer schema={schema} />);
181
- const schemas = getRenderedSchemas(container);
182
- const chartSchema = schemas.find(s => s.type === 'chart');
183
-
184
- expect(chartSchema).toBeDefined();
185
- expect(chartSchema.data).toEqual([]);
186
- });
187
-
188
- it('should render metric widgets using spec shorthand format', () => {
189
- const schema = {
190
- type: 'dashboard' as const,
191
- name: 'test',
192
- title: 'Test',
193
- widgets: [
194
- {
195
- type: 'metric',
196
- layout: { x: 0, y: 0, w: 1, h: 1 },
197
- options: {
198
- label: 'Total Revenue',
199
- value: '$652,000',
200
- trend: { value: 12.5, direction: 'up', label: 'vs last month' },
201
- icon: 'DollarSign',
202
- },
203
- },
204
- {
205
- type: 'metric',
206
- layout: { x: 1, y: 0, w: 1, h: 1 },
207
- options: {
208
- label: 'Active Deals',
209
- value: '5',
210
- trend: { value: 2.1, direction: 'down', label: 'vs last month' },
211
- icon: 'Briefcase',
212
- },
213
- },
214
- ],
215
- } as any;
216
-
217
- const { container } = render(<DashboardRenderer schema={schema} />);
218
-
219
- // MetricWidget is registered in the ComponentRegistry, so it should render
220
- // the label and value from the merged options
221
- expect(container.textContent).toContain('Total Revenue');
222
- expect(container.textContent).toContain('$652,000');
223
- expect(container.textContent).toContain('Active Deals');
224
- expect(container.textContent).toContain('5');
225
- });
226
-
227
- it('should render metric widgets with I18nLabel objects without crashing', () => {
228
- const schema = {
229
- type: 'dashboard' as const,
230
- name: 'test',
231
- title: 'Test',
232
- widgets: [
233
- {
234
- type: 'metric',
235
- layout: { x: 0, y: 0, w: 1, h: 1 },
236
- options: {
237
- label: 'Total Revenue',
238
- value: '$652,000',
239
- trend: { value: 12.5, direction: 'up', label: { key: 'crm.dashboard.trendLabel', defaultValue: 'vs last month' } },
240
- icon: 'DollarSign',
241
- },
242
- },
243
- {
244
- type: 'metric',
245
- layout: { x: 1, y: 0, w: 1, h: 1 },
246
- options: {
247
- label: 'Active Deals',
248
- value: '5',
249
- trend: { value: 2.1, direction: 'down', label: { key: 'crm.dashboard.trendLabel', defaultValue: 'vs last month' } },
250
- icon: 'Briefcase',
251
- },
252
- },
253
- ],
254
- } as any;
255
-
256
- const { container } = render(<DashboardRenderer schema={schema} />);
257
-
258
- // Should resolve I18nLabel objects to their defaultValue strings
259
- expect(container.textContent).toContain('Total Revenue');
260
- expect(container.textContent).toContain('$652,000');
261
- expect(container.textContent).toContain('vs last month');
262
- expect(container.textContent).toContain('Active Deals');
263
- expect(container.textContent).toContain('5');
264
- });
265
-
266
- it('should assign unique keys to widgets without id or title', () => {
267
- const schema = {
268
- type: 'dashboard' as const,
269
- name: 'test',
270
- title: 'Test',
271
- widgets: [
272
- {
273
- type: 'metric',
274
- layout: { x: 0, y: 0, w: 1, h: 1 },
275
- options: { label: 'Metric A', value: '100' },
276
- },
277
- {
278
- type: 'metric',
279
- layout: { x: 1, y: 0, w: 1, h: 1 },
280
- options: { label: 'Metric B', value: '200' },
281
- },
282
- {
283
- type: 'metric',
284
- layout: { x: 2, y: 0, w: 1, h: 1 },
285
- options: { label: 'Metric C', value: '300' },
286
- },
287
- ],
288
- } as any;
289
-
290
- const { container } = render(<DashboardRenderer schema={schema} />);
291
-
292
- // All three metrics should render without React key warnings
293
- expect(container.textContent).toContain('Metric A');
294
- expect(container.textContent).toContain('Metric B');
295
- expect(container.textContent).toContain('Metric C');
296
- expect(container.textContent).toContain('100');
297
- expect(container.textContent).toContain('200');
298
- expect(container.textContent).toContain('300');
299
- });
300
-
301
- it('should produce object-chart schema for chart widgets with provider: object', () => {
302
- const schema = {
303
- type: 'dashboard' as const,
304
- name: 'test',
305
- title: 'Test',
306
- widgets: [
307
- {
308
- type: 'bar',
309
- title: 'Revenue by Account',
310
- object: 'opportunity',
311
- layout: { x: 0, y: 0, w: 4, h: 2 },
312
- options: {
313
- xField: 'account',
314
- yField: 'total',
315
- data: {
316
- provider: 'object',
317
- object: 'opportunity',
318
- aggregate: { field: 'amount', function: 'sum', groupBy: 'account' },
319
- },
320
- },
321
- },
322
- ],
323
- } as any;
324
-
325
- const { container } = render(<DashboardRenderer schema={schema} />);
326
- const schemas = getRenderedSchemas(container);
327
- const chartSchema = schemas.find(s => s.type === 'object-chart');
328
-
329
- expect(chartSchema).toBeDefined();
330
- expect(chartSchema.chartType).toBe('bar');
331
- expect(chartSchema.objectName).toBe('opportunity');
332
- expect(chartSchema.aggregate).toEqual({
333
- field: 'amount',
334
- function: 'sum',
335
- groupBy: 'account',
336
- });
337
- expect(chartSchema.xAxisKey).toBe('account');
338
- expect(chartSchema.series).toEqual([{ dataKey: 'amount' }]);
339
- // Must NOT have an empty data array – data comes from the object source
340
- expect(chartSchema.data).toBeUndefined();
341
- });
342
-
343
- it('should fall back to widget.object when data.object is missing for provider: object', () => {
344
- const schema = {
345
- type: 'dashboard' as const,
346
- name: 'test',
347
- title: 'Test',
348
- widgets: [
349
- {
350
- type: 'area',
351
- title: 'Trend',
352
- object: 'deal',
353
- layout: { x: 0, y: 0, w: 3, h: 2 },
354
- options: {
355
- xField: 'month',
356
- yField: 'revenue',
357
- data: {
358
- provider: 'object',
359
- aggregate: { field: 'revenue', function: 'sum', groupBy: 'month' },
360
- },
361
- },
362
- },
363
- ],
364
- } as any;
365
-
366
- const { container } = render(<DashboardRenderer schema={schema} />);
367
- const schemas = getRenderedSchemas(container);
368
- const chartSchema = schemas.find(s => s.type === 'object-chart');
369
-
370
- expect(chartSchema).toBeDefined();
371
- expect(chartSchema.objectName).toBe('deal');
372
- });
373
-
374
- it('should pass through provider: object config for table widgets', () => {
375
- const schema = {
376
- type: 'dashboard' as const,
377
- name: 'test',
378
- title: 'Test',
379
- widgets: [
380
- {
381
- type: 'table',
382
- title: 'Object Table',
383
- object: 'opportunity',
384
- layout: { x: 0, y: 0, w: 4, h: 2 },
385
- options: {
386
- columns: [
387
- { header: 'Name', accessorKey: 'name' },
388
- { header: 'Amount', accessorKey: 'amount' },
389
- ],
390
- data: {
391
- provider: 'object',
392
- object: 'opportunity',
393
- },
394
- },
395
- },
396
- ],
397
- } as any;
398
-
399
- const { container } = render(<DashboardRenderer schema={schema} />);
400
- const schemas = getRenderedSchemas(container);
401
- // DashboardRenderer now routes object-bound tables to 'object-data-table'
402
- const tableSchema = schemas.find(s => s.type === 'object-data-table');
403
-
404
- if (tableSchema) {
405
- expect(tableSchema.objectName).toBe('opportunity');
406
- expect(tableSchema.dataProvider).toEqual({
407
- provider: 'object',
408
- object: 'opportunity',
409
- });
410
- }
411
- });
412
-
413
- it('should pass through provider: object config for pivot widgets', () => {
414
- const schema = {
415
- type: 'dashboard' as const,
416
- name: 'test',
417
- title: 'Test',
418
- widgets: [
419
- {
420
- type: 'pivot',
421
- title: 'Object Pivot',
422
- object: 'sales',
423
- layout: { x: 0, y: 0, w: 4, h: 2 },
424
- options: {
425
- rowField: 'region',
426
- columnField: 'quarter',
427
- valueField: 'revenue',
428
- data: {
429
- provider: 'object',
430
- object: 'sales',
431
- },
432
- },
433
- },
434
- ],
435
- } as any;
436
-
437
- const { container } = render(<DashboardRenderer schema={schema} />);
438
- const schemas = getRenderedSchemas(container);
439
- // DashboardRenderer now routes object-bound pivots to 'object-pivot'
440
- const pivotSchema = schemas.find(s => s.type === 'object-pivot');
441
-
442
- if (pivotSchema) {
443
- expect(pivotSchema.objectName).toBe('sales');
444
- expect(pivotSchema.dataProvider).toEqual({
445
- provider: 'object',
446
- object: 'sales',
447
- });
448
- }
449
- });
450
-
451
- it('should use yField as series dataKey when provider: object has no aggregate', () => {
452
- const schema = {
453
- type: 'dashboard' as const,
454
- name: 'test',
455
- title: 'Test',
456
- widgets: [
457
- {
458
- type: 'line',
459
- title: 'No Aggregate',
460
- object: 'opportunity',
461
- layout: { x: 0, y: 0, w: 4, h: 2 },
462
- options: {
463
- xField: 'date',
464
- yField: 'revenue',
465
- data: {
466
- provider: 'object',
467
- object: 'opportunity',
468
- },
469
- },
470
- },
471
- ],
472
- } as any;
473
-
474
- const { container } = render(<DashboardRenderer schema={schema} />);
475
- const schemas = getRenderedSchemas(container);
476
- const chartSchema = schemas.find(s => s.type === 'object-chart');
477
-
478
- expect(chartSchema).toBeDefined();
479
- expect(chartSchema.series).toEqual([{ dataKey: 'revenue' }]);
480
- });
481
-
482
- it('should auto-adapt series dataKey from aggregate.field even when yField differs', () => {
483
- const schema = {
484
- type: 'dashboard' as const,
485
- name: 'test',
486
- title: 'Test',
487
- widgets: [
488
- {
489
- type: 'bar',
490
- title: 'Mismatched yField',
491
- object: 'opportunity',
492
- layout: { x: 0, y: 0, w: 4, h: 2 },
493
- options: {
494
- xField: 'account',
495
- yField: 'total',
496
- data: {
497
- provider: 'object',
498
- object: 'opportunity',
499
- aggregate: { field: 'amount', function: 'sum', groupBy: 'account' },
500
- },
501
- },
502
- },
503
- ],
504
- } as any;
505
-
506
- const { container } = render(<DashboardRenderer schema={schema} />);
507
- const schemas = getRenderedSchemas(container);
508
- const chartSchema = schemas.find(s => s.type === 'object-chart');
509
-
510
- expect(chartSchema).toBeDefined();
511
- // Even though yField is 'total', the series should use aggregate.field ('amount')
512
- expect(chartSchema.series).toEqual([{ dataKey: 'amount' }]);
513
- });
514
-
515
- it('should produce object-chart schema for area chart with provider: object aggregate', () => {
516
- const schema = {
517
- type: 'dashboard' as const,
518
- name: 'test',
519
- title: 'Test',
520
- widgets: [
521
- {
522
- type: 'area',
523
- title: 'Revenue Trends',
524
- object: 'opportunity',
525
- layout: { x: 0, y: 0, w: 3, h: 2 },
526
- options: {
527
- xField: 'stage',
528
- yField: 'expected_revenue',
529
- data: {
530
- provider: 'object',
531
- object: 'opportunity',
532
- aggregate: { field: 'expected_revenue', function: 'sum', groupBy: 'stage' },
533
- },
534
- },
535
- },
536
- ],
537
- } as any;
538
-
539
- const { container } = render(<DashboardRenderer schema={schema} />);
540
- const schemas = getRenderedSchemas(container);
541
- const chartSchema = schemas.find(s => s.type === 'object-chart');
542
-
543
- expect(chartSchema).toBeDefined();
544
- expect(chartSchema.chartType).toBe('area');
545
- expect(chartSchema.objectName).toBe('opportunity');
546
- expect(chartSchema.aggregate).toEqual({
547
- field: 'expected_revenue',
548
- function: 'sum',
549
- groupBy: 'stage',
550
- });
551
- expect(chartSchema.xAxisKey).toBe('stage');
552
- expect(chartSchema.series).toEqual([{ dataKey: 'expected_revenue' }]);
553
- expect(chartSchema.data).toBeUndefined();
554
- });
555
-
556
- it('should produce object-chart schema for donut chart with count aggregate', () => {
557
- const schema = {
558
- type: 'dashboard' as const,
559
- name: 'test',
560
- title: 'Test',
561
- widgets: [
562
- {
563
- type: 'donut',
564
- title: 'Lead Source',
565
- object: 'opportunity',
566
- layout: { x: 0, y: 0, w: 1, h: 2 },
567
- options: {
568
- xField: 'lead_source',
569
- yField: 'count',
570
- data: {
571
- provider: 'object',
572
- object: 'opportunity',
573
- aggregate: { field: 'count', function: 'count', groupBy: 'lead_source' },
574
- },
575
- },
576
- },
577
- ],
578
- } as any;
579
-
580
- const { container } = render(<DashboardRenderer schema={schema} />);
581
- const schemas = getRenderedSchemas(container);
582
- const chartSchema = schemas.find(s => s.type === 'object-chart');
583
-
584
- expect(chartSchema).toBeDefined();
585
- expect(chartSchema.chartType).toBe('donut');
586
- expect(chartSchema.objectName).toBe('opportunity');
587
- expect(chartSchema.aggregate.function).toBe('count');
588
- expect(chartSchema.xAxisKey).toBe('lead_source');
589
- expect(chartSchema.series).toEqual([{ dataKey: 'count' }]);
590
- expect(chartSchema.data).toBeUndefined();
591
- });
592
-
593
- it('should produce object-chart schema for line chart with avg aggregate', () => {
594
- const schema = {
595
- type: 'dashboard' as const,
596
- name: 'test',
597
- title: 'Test',
598
- widgets: [
599
- {
600
- type: 'line',
601
- title: 'Avg Deal Size by Stage',
602
- object: 'opportunity',
603
- layout: { x: 0, y: 0, w: 2, h: 2 },
604
- options: {
605
- xField: 'stage',
606
- yField: 'amount',
607
- data: {
608
- provider: 'object',
609
- object: 'opportunity',
610
- aggregate: { field: 'amount', function: 'avg', groupBy: 'stage' },
611
- },
612
- },
613
- },
614
- ],
615
- } as any;
616
-
617
- const { container } = render(<DashboardRenderer schema={schema} />);
618
- const schemas = getRenderedSchemas(container);
619
- const chartSchema = schemas.find(s => s.type === 'object-chart');
620
-
621
- expect(chartSchema).toBeDefined();
622
- expect(chartSchema.chartType).toBe('line');
623
- expect(chartSchema.aggregate.function).toBe('avg');
624
- expect(chartSchema.series).toEqual([{ dataKey: 'amount' }]);
625
- });
626
-
627
- it('should produce object-chart schema for cross-object widget (order)', () => {
628
- const schema = {
629
- type: 'dashboard' as const,
630
- name: 'test',
631
- title: 'Test',
632
- widgets: [
633
- {
634
- type: 'bar',
635
- title: 'Orders by Status',
636
- object: 'order',
637
- layout: { x: 0, y: 0, w: 2, h: 2 },
638
- options: {
639
- xField: 'status',
640
- yField: 'amount',
641
- data: {
642
- provider: 'object',
643
- object: 'order',
644
- aggregate: { field: 'amount', function: 'max', groupBy: 'status' },
645
- },
646
- },
647
- },
648
- ],
649
- } as any;
650
-
651
- const { container } = render(<DashboardRenderer schema={schema} />);
652
- const schemas = getRenderedSchemas(container);
653
- const chartSchema = schemas.find(s => s.type === 'object-chart');
654
-
655
- expect(chartSchema).toBeDefined();
656
- expect(chartSchema.chartType).toBe('bar');
657
- expect(chartSchema.objectName).toBe('order');
658
- expect(chartSchema.aggregate.function).toBe('max');
659
- expect(chartSchema.xAxisKey).toBe('status');
660
- expect(chartSchema.series).toEqual([{ dataKey: 'amount' }]);
661
- });
662
-
663
- it('should render without errors when widgets array is empty', () => {
664
- const schema = {
665
- type: 'dashboard' as const,
666
- name: 'test',
667
- title: 'Empty Dashboard',
668
- widgets: [],
669
- } as any;
670
-
671
- const { container } = render(<DashboardRenderer schema={schema} />);
672
- expect(container).toBeDefined();
673
- expect(container.querySelectorAll('pre').length).toBe(0);
674
- });
675
-
676
- it('should handle chart widget with null data gracefully', () => {
677
- const schema = {
678
- type: 'dashboard' as const,
679
- name: 'test',
680
- title: 'Test',
681
- widgets: [
682
- {
683
- type: 'bar',
684
- title: 'Null Data Bar',
685
- layout: { x: 0, y: 0, w: 2, h: 2 },
686
- options: { xField: 'x', yField: 'y', data: null },
687
- },
688
- ],
689
- } as any;
690
-
691
- const { container } = render(<DashboardRenderer schema={schema} />);
692
- const schemas = getRenderedSchemas(container);
693
- const chartSchema = schemas.find(s => s.type === 'chart');
694
-
695
- expect(chartSchema).toBeDefined();
696
- expect(chartSchema.data).toEqual([]);
697
- });
698
-
699
- it('should not crash data-table when provider:object leaks data config via options spread', () => {
700
- const schema = {
701
- type: 'dashboard' as const,
702
- name: 'test',
703
- title: 'Test',
704
- widgets: [
705
- {
706
- type: 'table',
707
- title: 'Provider Object Table',
708
- object: 'opportunity',
709
- layout: { x: 0, y: 0, w: 4, h: 2 },
710
- options: {
711
- columns: [
712
- { header: 'Name', accessorKey: 'name' },
713
- { header: 'Amount', accessorKey: 'amount' },
714
- ],
715
- data: {
716
- provider: 'object',
717
- object: 'opportunity',
718
- },
719
- },
720
- },
721
- ],
722
- } as any;
723
-
724
- // Must render without throwing. Previously this crashed with
725
- // "paginatedData.some is not a function" because the provider
726
- // config object leaked through as data.
727
- const { container } = render(<DashboardRenderer schema={schema} />);
728
- expect(container).toBeDefined();
729
- // The component should not show a crash error
730
- expect(container.textContent).not.toContain('is not a function');
731
- });
732
-
733
- it('should not crash pivot table when provider:object leaks data config via options spread', () => {
734
- const schema = {
735
- type: 'dashboard' as const,
736
- name: 'test',
737
- title: 'Test',
738
- widgets: [
739
- {
740
- type: 'pivot',
741
- title: 'Provider Object Pivot',
742
- object: 'sales',
743
- layout: { x: 0, y: 0, w: 4, h: 2 },
744
- options: {
745
- rowField: 'region',
746
- columnField: 'quarter',
747
- valueField: 'revenue',
748
- data: {
749
- provider: 'object',
750
- object: 'sales',
751
- },
752
- },
753
- },
754
- ],
755
- } as any;
756
-
757
- // Must render without throwing. Previously this crashed with
758
- // "data is not iterable" because the provider config object
759
- // leaked through as data.
760
- const { container } = render(<DashboardRenderer schema={schema} />);
761
- expect(container).toBeDefined();
762
- expect(container.textContent).not.toContain('is not iterable');
763
- });
764
-
765
- it('should handle scatter chart type as a valid chart widget', () => {
766
- const schema = {
767
- type: 'dashboard' as const,
768
- name: 'test',
769
- title: 'Test',
770
- widgets: [
771
- {
772
- type: 'scatter',
773
- title: 'Scatter Plot',
774
- layout: { x: 0, y: 0, w: 2, h: 2 },
775
- options: {
776
- xField: 'x',
777
- yField: 'y',
778
- data: {
779
- provider: 'value',
780
- items: [
781
- { x: 1, y: 10 },
782
- { x: 2, y: 20 },
783
- ],
784
- },
785
- },
786
- },
787
- ],
788
- } as any;
789
-
790
- const { container } = render(<DashboardRenderer schema={schema} />);
791
- const schemas = getRenderedSchemas(container);
792
- const chartSchema = schemas.find(s => s.type === 'chart');
793
-
794
- expect(chartSchema).toBeDefined();
795
- expect(chartSchema.chartType).toBe('scatter');
796
- expect(chartSchema.data).toHaveLength(2);
797
- expect(chartSchema.xAxisKey).toBe('x');
798
- expect(chartSchema.series).toEqual([{ dataKey: 'y' }]);
799
- });
800
-
801
- it('should produce object-chart schema for scatter chart with provider: object', () => {
802
- const schema = {
803
- type: 'dashboard' as const,
804
- name: 'test',
805
- title: 'Test',
806
- widgets: [
807
- {
808
- type: 'scatter',
809
- title: 'Object Scatter',
810
- object: 'opportunity',
811
- layout: { x: 0, y: 0, w: 3, h: 2 },
812
- options: {
813
- xField: 'amount',
814
- yField: 'probability',
815
- data: {
816
- provider: 'object',
817
- object: 'opportunity',
818
- aggregate: { field: 'probability', function: 'avg', groupBy: 'amount' },
819
- },
820
- },
821
- },
822
- ],
823
- } as any;
824
-
825
- const { container } = render(<DashboardRenderer schema={schema} />);
826
- const schemas = getRenderedSchemas(container);
827
- const chartSchema = schemas.find(s => s.type === 'object-chart');
828
-
829
- expect(chartSchema).toBeDefined();
830
- expect(chartSchema.chartType).toBe('scatter');
831
- expect(chartSchema.objectName).toBe('opportunity');
832
- expect(chartSchema.aggregate.function).toBe('avg');
833
- });
834
-
835
- it('should use widget.categoryField as xAxisKey fallback over options.xField', () => {
836
- const schema = {
837
- type: 'dashboard' as const,
838
- name: 'test',
839
- title: 'Test',
840
- widgets: [
841
- {
842
- type: 'bar',
843
- title: 'Category Field Override',
844
- categoryField: 'forecast_category',
845
- layout: { x: 0, y: 0, w: 2, h: 2 },
846
- options: {
847
- xField: 'stage',
848
- yField: 'amount',
849
- data: {
850
- provider: 'value',
851
- items: [
852
- { forecast_category: 'Pipeline', amount: 100 },
853
- { forecast_category: 'Closed', amount: 200 },
854
- ],
855
- },
856
- },
857
- },
858
- ],
859
- } as any;
860
-
861
- const { container } = render(<DashboardRenderer schema={schema} />);
862
- const schemas = getRenderedSchemas(container);
863
- const chartSchema = schemas.find(s => s.type === 'chart');
864
-
865
- expect(chartSchema).toBeDefined();
866
- // widget.categoryField should override options.xField
867
- expect(chartSchema.xAxisKey).toBe('forecast_category');
868
- });
869
-
870
- it('should use widget.valueField as yField fallback over options.yField', () => {
871
- const schema = {
872
- type: 'dashboard' as const,
873
- name: 'test',
874
- title: 'Test',
875
- widgets: [
876
- {
877
- type: 'line',
878
- title: 'Value Field Override',
879
- valueField: 'expected_revenue',
880
- layout: { x: 0, y: 0, w: 2, h: 2 },
881
- options: {
882
- xField: 'month',
883
- yField: 'amount',
884
- data: {
885
- provider: 'value',
886
- items: [
887
- { month: 'Jan', expected_revenue: 100 },
888
- ],
889
- },
890
- },
891
- },
892
- ],
893
- } as any;
894
-
895
- const { container } = render(<DashboardRenderer schema={schema} />);
896
- const schemas = getRenderedSchemas(container);
897
- const chartSchema = schemas.find(s => s.type === 'chart');
898
-
899
- expect(chartSchema).toBeDefined();
900
- // widget.valueField should override options.yField
901
- expect(chartSchema.series).toEqual([{ dataKey: 'expected_revenue' }]);
902
- });
903
-
904
- it('should construct object-chart from widget-level fields when no data provider exists', () => {
905
- const schema = {
906
- type: 'dashboard' as const,
907
- name: 'test',
908
- title: 'Test',
909
- widgets: [
910
- {
911
- type: 'bar',
912
- title: 'New Widget',
913
- object: 'opportunity',
914
- categoryField: 'stage',
915
- valueField: 'amount',
916
- aggregate: 'sum',
917
- layout: { x: 0, y: 0, w: 2, h: 2 },
918
- },
919
- ],
920
- } as any;
921
-
922
- const { container } = render(<DashboardRenderer schema={schema} />);
923
- const schemas = getRenderedSchemas(container);
924
- const chartSchema = schemas.find(s => s.type === 'object-chart');
925
-
926
- expect(chartSchema).toBeDefined();
927
- expect(chartSchema.chartType).toBe('bar');
928
- expect(chartSchema.objectName).toBe('opportunity');
929
- expect(chartSchema.xAxisKey).toBe('stage');
930
- expect(chartSchema.series).toEqual([{ dataKey: 'amount' }]);
931
- expect(chartSchema.aggregate).toEqual({
932
- field: 'amount',
933
- function: 'sum',
934
- groupBy: 'stage',
935
- });
936
- });
937
-
938
- it('should construct data-table from widget.object when no data provider exists', () => {
939
- const schema = {
940
- type: 'dashboard' as const,
941
- name: 'test',
942
- title: 'Test',
943
- widgets: [
944
- {
945
- type: 'table',
946
- title: 'New Table Widget',
947
- object: 'contact',
948
- layout: { x: 0, y: 0, w: 4, h: 2 },
949
- },
950
- ],
951
- } as any;
952
-
953
- const { container } = render(<DashboardRenderer schema={schema} />);
954
- const schemas = getRenderedSchemas(container);
955
- // DashboardRenderer now routes table+objectName to 'object-data-table'
956
- const tableSchema = schemas.find(s => s.type === 'object-data-table');
957
-
958
- if (tableSchema) {
959
- expect(tableSchema.objectName).toBe('contact');
960
- }
961
- // Either way, it should not crash
962
- expect(container).toBeDefined();
963
- });
964
-
965
- // ---- Live preview: widget-level fields override data provider config ------
966
-
967
- it('should override data provider aggregate.groupBy with widget.categoryField', () => {
968
- const schema = {
969
- type: 'dashboard' as const,
970
- name: 'test',
971
- title: 'Test',
972
- widgets: [
973
- {
974
- type: 'bar',
975
- title: 'Live Preview',
976
- object: 'opportunity',
977
- categoryField: 'region',
978
- layout: { x: 0, y: 0, w: 2, h: 2 },
979
- options: {
980
- xField: 'stage',
981
- yField: 'amount',
982
- data: {
983
- provider: 'object',
984
- object: 'opportunity',
985
- aggregate: { field: 'amount', function: 'sum', groupBy: 'stage' },
986
- },
987
- },
988
- },
989
- ],
990
- } as any;
991
-
992
- const { container } = render(<DashboardRenderer schema={schema} />);
993
- const schemas = getRenderedSchemas(container);
994
- const chartSchema = schemas.find(s => s.type === 'object-chart');
995
-
996
- expect(chartSchema).toBeDefined();
997
- // widget.categoryField ('region') should override aggregate.groupBy ('stage')
998
- expect(chartSchema.aggregate.groupBy).toBe('region');
999
- expect(chartSchema.xAxisKey).toBe('region');
1000
- });
1001
-
1002
- it('should override data provider aggregate.field with widget.valueField', () => {
1003
- const schema = {
1004
- type: 'dashboard' as const,
1005
- name: 'test',
1006
- title: 'Test',
1007
- widgets: [
1008
- {
1009
- type: 'area',
1010
- title: 'Live Preview',
1011
- object: 'opportunity',
1012
- valueField: 'expected_revenue',
1013
- layout: { x: 0, y: 0, w: 3, h: 2 },
1014
- options: {
1015
- xField: 'stage',
1016
- yField: 'amount',
1017
- data: {
1018
- provider: 'object',
1019
- object: 'opportunity',
1020
- aggregate: { field: 'amount', function: 'sum', groupBy: 'stage' },
1021
- },
1022
- },
1023
- },
1024
- ],
1025
- } as any;
1026
-
1027
- const { container } = render(<DashboardRenderer schema={schema} />);
1028
- const schemas = getRenderedSchemas(container);
1029
- const chartSchema = schemas.find(s => s.type === 'object-chart');
1030
-
1031
- expect(chartSchema).toBeDefined();
1032
- // widget.valueField ('expected_revenue') should override aggregate.field ('amount')
1033
- expect(chartSchema.aggregate.field).toBe('expected_revenue');
1034
- expect(chartSchema.series).toEqual([{ dataKey: 'expected_revenue' }]);
1035
- });
1036
-
1037
- it('should override data provider aggregate.function with widget.aggregate', () => {
1038
- const schema = {
1039
- type: 'dashboard' as const,
1040
- name: 'test',
1041
- title: 'Test',
1042
- widgets: [
1043
- {
1044
- type: 'bar',
1045
- title: 'Live Preview',
1046
- object: 'opportunity',
1047
- aggregate: 'count',
1048
- layout: { x: 0, y: 0, w: 2, h: 2 },
1049
- options: {
1050
- xField: 'stage',
1051
- yField: 'amount',
1052
- data: {
1053
- provider: 'object',
1054
- object: 'opportunity',
1055
- aggregate: { field: 'amount', function: 'sum', groupBy: 'stage' },
1056
- },
1057
- },
1058
- },
1059
- ],
1060
- } as any;
1061
-
1062
- const { container } = render(<DashboardRenderer schema={schema} />);
1063
- const schemas = getRenderedSchemas(container);
1064
- const chartSchema = schemas.find(s => s.type === 'object-chart');
1065
-
1066
- expect(chartSchema).toBeDefined();
1067
- // widget.aggregate ('count') should override aggregate.function ('sum')
1068
- expect(chartSchema.aggregate.function).toBe('count');
1069
- });
1070
-
1071
- it('should prefer widget.object over data provider object for objectName', () => {
1072
- const schema = {
1073
- type: 'dashboard' as const,
1074
- name: 'test',
1075
- title: 'Test',
1076
- widgets: [
1077
- {
1078
- type: 'line',
1079
- title: 'Live Preview',
1080
- object: 'contact',
1081
- layout: { x: 0, y: 0, w: 3, h: 2 },
1082
- options: {
1083
- xField: 'month',
1084
- yField: 'count',
1085
- data: {
1086
- provider: 'object',
1087
- object: 'opportunity',
1088
- aggregate: { field: 'count', function: 'count', groupBy: 'month' },
1089
- },
1090
- },
1091
- },
1092
- ],
1093
- } as any;
1094
-
1095
- const { container } = render(<DashboardRenderer schema={schema} />);
1096
- const schemas = getRenderedSchemas(container);
1097
- const chartSchema = schemas.find(s => s.type === 'object-chart');
1098
-
1099
- expect(chartSchema).toBeDefined();
1100
- // widget.object ('contact') should override data.object ('opportunity')
1101
- expect(chartSchema.objectName).toBe('contact');
1102
- });
1103
-
1104
- it('should prefer widget.object for table widgets with data provider', () => {
1105
- const schema = {
1106
- type: 'dashboard' as const,
1107
- name: 'test',
1108
- title: 'Test',
1109
- widgets: [
1110
- {
1111
- type: 'table',
1112
- title: 'Live Preview Table',
1113
- object: 'contact',
1114
- layout: { x: 0, y: 0, w: 4, h: 2 },
1115
- options: {
1116
- data: {
1117
- provider: 'object',
1118
- object: 'opportunity',
1119
- },
1120
- },
1121
- },
1122
- ],
1123
- } as any;
1124
-
1125
- const { container } = render(<DashboardRenderer schema={schema} />);
1126
- const schemas = getRenderedSchemas(container);
1127
- const tableSchema = schemas.find(s => s.type === 'data-table');
1128
-
1129
- if (tableSchema) {
1130
- // widget.object ('contact') should override data.object ('opportunity')
1131
- expect(tableSchema.objectName).toBe('contact');
1132
- }
1133
- });
1134
-
1135
- it('should apply all widget-level field overrides simultaneously for live preview', () => {
1136
- const schema = {
1137
- type: 'dashboard' as const,
1138
- name: 'test',
1139
- title: 'Test',
1140
- widgets: [
1141
- {
1142
- type: 'pie',
1143
- title: 'Full Override',
1144
- object: 'account',
1145
- categoryField: 'industry',
1146
- valueField: 'revenue',
1147
- aggregate: 'avg',
1148
- layout: { x: 0, y: 0, w: 2, h: 2 },
1149
- options: {
1150
- xField: 'stage',
1151
- yField: 'amount',
1152
- data: {
1153
- provider: 'object',
1154
- object: 'opportunity',
1155
- aggregate: { field: 'amount', function: 'sum', groupBy: 'stage' },
1156
- },
1157
- },
1158
- },
1159
- ],
1160
- } as any;
1161
-
1162
- const { container } = render(<DashboardRenderer schema={schema} />);
1163
- const schemas = getRenderedSchemas(container);
1164
- const chartSchema = schemas.find(s => s.type === 'object-chart');
1165
-
1166
- expect(chartSchema).toBeDefined();
1167
- expect(chartSchema.chartType).toBe('pie');
1168
- expect(chartSchema.objectName).toBe('account');
1169
- expect(chartSchema.xAxisKey).toBe('industry');
1170
- expect(chartSchema.aggregate).toEqual({
1171
- field: 'revenue',
1172
- function: 'avg',
1173
- groupBy: 'industry',
1174
- });
1175
- expect(chartSchema.series).toEqual([{ dataKey: 'revenue' }]);
1176
- });
1177
-
1178
- // ---- Pivot widget: object binding without explicit data provider ----------
1179
-
1180
- it('should pass objectName for pivot widget with widget.object but no data', () => {
1181
- const schema = {
1182
- type: 'dashboard' as const,
1183
- name: 'test',
1184
- title: 'Test',
1185
- widgets: [
1186
- {
1187
- type: 'pivot',
1188
- title: 'Pivot by Object',
1189
- object: 'sales',
1190
- layout: { x: 0, y: 0, w: 4, h: 2 },
1191
- options: {
1192
- rowField: 'region',
1193
- columnField: 'quarter',
1194
- valueField: 'revenue',
1195
- },
1196
- },
1197
- ],
1198
- } as any;
1199
-
1200
- // DashboardRenderer routes pivot+objectName to 'object-pivot' type.
1201
- // ObjectPivotTable renders "no data source" message when no context provided.
1202
- const { container } = render(<DashboardRenderer schema={schema} />);
1203
- expect(container).toBeDefined();
1204
- // Should render without crash
1205
- expect(container.textContent).not.toContain('is not iterable');
1206
- });
1207
-
1208
- // ---- Widget description rendering -----------------------------------------
1209
-
1210
- it('should render widget description in card header', () => {
1211
- const schema = {
1212
- type: 'dashboard' as const,
1213
- name: 'test',
1214
- title: 'Test',
1215
- widgets: [
1216
- {
1217
- type: 'bar',
1218
- title: 'My Chart',
1219
- description: 'Monthly sales breakdown',
1220
- layout: { x: 0, y: 0, w: 2, h: 2 },
1221
- options: {
1222
- data: { provider: 'value', items: [{ name: 'A', value: 100 }] },
1223
- },
1224
- },
1225
- ],
1226
- } as any;
1227
-
1228
- const { container } = render(<DashboardRenderer schema={schema} />);
1229
- expect(container.textContent).toContain('Monthly sales breakdown');
1230
- });
1231
-
1232
- it('should resolve I18nLabel description in widget card', () => {
1233
- const schema = {
1234
- type: 'dashboard' as const,
1235
- name: 'test',
1236
- title: 'Test',
1237
- widgets: [
1238
- {
1239
- type: 'bar',
1240
- title: 'My Chart',
1241
- description: { key: 'desc.key', defaultValue: 'Resolved description' },
1242
- layout: { x: 0, y: 0, w: 2, h: 2 },
1243
- options: {
1244
- data: { provider: 'value', items: [{ name: 'A', value: 100 }] },
1245
- },
1246
- },
1247
- ],
1248
- } as any;
1249
-
1250
- const { container } = render(<DashboardRenderer schema={schema} />);
1251
- expect(container.textContent).toContain('Resolved description');
1252
- expect(container.textContent).not.toContain('[object Object]');
1253
- });
1254
-
1255
- // ---- Grid column clamping -------------------------------------------------
1256
-
1257
- it('should clamp widget grid span to dashboard columns', () => {
1258
- const schema = {
1259
- type: 'dashboard' as const,
1260
- name: 'test',
1261
- title: 'Test',
1262
- columns: 3,
1263
- widgets: [
1264
- {
1265
- type: 'bar',
1266
- title: 'Wide Chart',
1267
- layout: { x: 0, y: 0, w: 6, h: 2 },
1268
- options: {
1269
- data: { provider: 'value', items: [{ name: 'A', value: 100 }] },
1270
- },
1271
- },
1272
- ],
1273
- } as any;
1274
-
1275
- const { container } = render(<DashboardRenderer schema={schema} />);
1276
- // The card's gridColumn should be clamped to 3, not 6
1277
- const card = container.querySelector('[class*="overflow-hidden"]');
1278
- expect(card).toBeDefined();
1279
- if (card) {
1280
- expect((card as HTMLElement).style.gridColumn).toBe('span 3');
1281
- }
1282
- });
1283
- });