@object-ui/plugin-dashboard 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 (47) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +21 -1
  3. package/dist/index.js +869 -787
  4. package/dist/index.umd.cjs +4 -4
  5. package/dist/packages/plugin-dashboard/src/DashboardRenderer.d.ts +5 -0
  6. package/dist/packages/plugin-dashboard/src/DashboardRenderer.d.ts.map +1 -1
  7. package/dist/packages/plugin-dashboard/src/MetricWidget.d.ts +4 -1
  8. package/dist/packages/plugin-dashboard/src/MetricWidget.d.ts.map +1 -1
  9. package/dist/packages/plugin-dashboard/src/ObjectMetricWidget.d.ts +2 -0
  10. package/dist/packages/plugin-dashboard/src/ObjectMetricWidget.d.ts.map +1 -1
  11. package/dist/packages/plugin-dashboard/src/index.d.ts +1 -1
  12. package/package.json +40 -7
  13. package/.turbo/turbo-build.log +0 -41
  14. package/src/DashboardConfigPanel.stories.tsx +0 -164
  15. package/src/DashboardConfigPanel.tsx +0 -158
  16. package/src/DashboardGridLayout.tsx +0 -367
  17. package/src/DashboardRenderer.stories.tsx +0 -173
  18. package/src/DashboardRenderer.tsx +0 -479
  19. package/src/DashboardWithConfig.tsx +0 -211
  20. package/src/MetricCard.tsx +0 -102
  21. package/src/MetricWidget.tsx +0 -96
  22. package/src/ObjectDataTable.tsx +0 -226
  23. package/src/ObjectMetricWidget.tsx +0 -159
  24. package/src/ObjectPivotTable.tsx +0 -160
  25. package/src/PivotTable.tsx +0 -262
  26. package/src/WidgetConfigPanel.tsx +0 -540
  27. package/src/__tests__/DashboardConfigPanel.test.tsx +0 -206
  28. package/src/__tests__/DashboardGridLayout.test.tsx +0 -199
  29. package/src/__tests__/DashboardRenderer.autoRefresh.test.tsx +0 -124
  30. package/src/__tests__/DashboardRenderer.designMode.test.tsx +0 -386
  31. package/src/__tests__/DashboardRenderer.header.test.tsx +0 -114
  32. package/src/__tests__/DashboardRenderer.mobile.test.tsx +0 -214
  33. package/src/__tests__/DashboardRenderer.widgetData.test.tsx +0 -1411
  34. package/src/__tests__/DashboardWithConfig.test.tsx +0 -276
  35. package/src/__tests__/MetricCard.test.tsx +0 -107
  36. package/src/__tests__/ObjectDataTable.test.tsx +0 -211
  37. package/src/__tests__/ObjectMetricWidget.test.tsx +0 -196
  38. package/src/__tests__/ObjectPivotTable.test.tsx +0 -192
  39. package/src/__tests__/PivotTable.test.tsx +0 -162
  40. package/src/__tests__/WidgetConfigPanel.test.tsx +0 -492
  41. package/src/__tests__/ensureWidgetIds.test.tsx +0 -103
  42. package/src/index.tsx +0 -236
  43. package/src/utils.ts +0 -17
  44. package/tsconfig.json +0 -19
  45. package/vite.config.ts +0 -64
  46. package/vitest.config.ts +0 -9
  47. package/vitest.setup.tsx +0 -18
@@ -1,386 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { render, screen, fireEvent } from '@testing-library/react';
3
- import { DashboardRenderer } from '../DashboardRenderer';
4
- import type { DashboardSchema } from '@object-ui/types';
5
-
6
- // Mock SchemaRenderer to avoid pulling in the full renderer tree.
7
- // Forwards className and includes an interactive child to simulate real chart content.
8
- vi.mock('@object-ui/react', () => ({
9
- SchemaRenderer: ({ schema, className }: { schema: any; className?: string }) => (
10
- <div data-testid="schema-renderer" className={className}>
11
- <button data-testid={`interactive-child-${schema?.type ?? 'unknown'}`}>
12
- {schema?.type ?? 'unknown'}
13
- </button>
14
- </div>
15
- ),
16
- }));
17
-
18
- const DASHBOARD_WITH_WIDGETS: DashboardSchema = {
19
- type: 'dashboard',
20
- title: 'Test Dashboard',
21
- columns: 2,
22
- widgets: [
23
- { id: 'w1', title: 'Revenue', type: 'metric' },
24
- { id: 'w2', title: 'Sales Chart', type: 'bar' },
25
- { id: 'w3', title: 'Orders Table', type: 'table' },
26
- ],
27
- };
28
-
29
- describe('DashboardRenderer design mode', () => {
30
- describe('Widget selection', () => {
31
- it('should render widget test IDs in design mode', () => {
32
- const onWidgetClick = vi.fn();
33
- render(
34
- <DashboardRenderer
35
- schema={DASHBOARD_WITH_WIDGETS}
36
- designMode
37
- selectedWidgetId={null}
38
- onWidgetClick={onWidgetClick}
39
- />,
40
- );
41
-
42
- expect(screen.getByTestId('dashboard-preview-widget-w1')).toBeInTheDocument();
43
- expect(screen.getByTestId('dashboard-preview-widget-w2')).toBeInTheDocument();
44
- expect(screen.getByTestId('dashboard-preview-widget-w3')).toBeInTheDocument();
45
- });
46
-
47
- it('should not render widget test IDs when not in design mode', () => {
48
- render(<DashboardRenderer schema={DASHBOARD_WITH_WIDGETS} />);
49
-
50
- expect(screen.queryByTestId('dashboard-preview-widget-w1')).not.toBeInTheDocument();
51
- expect(screen.queryByTestId('dashboard-preview-widget-w2')).not.toBeInTheDocument();
52
- });
53
-
54
- it('should call onWidgetClick when a widget is clicked', () => {
55
- const onWidgetClick = vi.fn();
56
- render(
57
- <DashboardRenderer
58
- schema={DASHBOARD_WITH_WIDGETS}
59
- designMode
60
- selectedWidgetId={null}
61
- onWidgetClick={onWidgetClick}
62
- />,
63
- );
64
-
65
- fireEvent.click(screen.getByTestId('dashboard-preview-widget-w1'));
66
- expect(onWidgetClick).toHaveBeenCalledWith('w1');
67
- });
68
-
69
- it('should set aria-selected on the selected widget', () => {
70
- render(
71
- <DashboardRenderer
72
- schema={DASHBOARD_WITH_WIDGETS}
73
- designMode
74
- selectedWidgetId="w2"
75
- onWidgetClick={vi.fn()}
76
- />,
77
- );
78
-
79
- expect(screen.getByTestId('dashboard-preview-widget-w2')).toHaveAttribute('aria-selected', 'true');
80
- expect(screen.getByTestId('dashboard-preview-widget-w1')).toHaveAttribute('aria-selected', 'false');
81
- });
82
-
83
- it('should apply selection styling classes on selected widget', () => {
84
- const { container } = render(
85
- <DashboardRenderer
86
- schema={DASHBOARD_WITH_WIDGETS}
87
- designMode
88
- selectedWidgetId="w1"
89
- onWidgetClick={vi.fn()}
90
- />,
91
- );
92
-
93
- const selectedWidget = screen.getByTestId('dashboard-preview-widget-w1');
94
- expect(selectedWidget.className).toContain('ring-2');
95
- expect(selectedWidget.className).toContain('ring-primary');
96
- });
97
-
98
- it('should have role="button" on widgets in design mode', () => {
99
- render(
100
- <DashboardRenderer
101
- schema={DASHBOARD_WITH_WIDGETS}
102
- designMode
103
- selectedWidgetId={null}
104
- onWidgetClick={vi.fn()}
105
- />,
106
- );
107
-
108
- expect(screen.getByTestId('dashboard-preview-widget-w1')).toHaveAttribute('role', 'button');
109
- });
110
-
111
- it('should have tabIndex=0 for keyboard accessibility in design mode', () => {
112
- render(
113
- <DashboardRenderer
114
- schema={DASHBOARD_WITH_WIDGETS}
115
- designMode
116
- selectedWidgetId={null}
117
- onWidgetClick={vi.fn()}
118
- />,
119
- );
120
-
121
- expect(screen.getByTestId('dashboard-preview-widget-w1')).toHaveAttribute('tabindex', '0');
122
- });
123
-
124
- it('should have aria-label on widgets in design mode', () => {
125
- render(
126
- <DashboardRenderer
127
- schema={DASHBOARD_WITH_WIDGETS}
128
- designMode
129
- selectedWidgetId={null}
130
- onWidgetClick={vi.fn()}
131
- />,
132
- );
133
-
134
- expect(screen.getByTestId('dashboard-preview-widget-w1')).toHaveAttribute(
135
- 'aria-label',
136
- 'Widget: Revenue',
137
- );
138
- });
139
- });
140
-
141
- describe('Click deselection', () => {
142
- it('should call onWidgetClick(null) when clicking background', () => {
143
- const onWidgetClick = vi.fn();
144
- const { container } = render(
145
- <DashboardRenderer
146
- schema={DASHBOARD_WITH_WIDGETS}
147
- designMode
148
- selectedWidgetId="w1"
149
- onWidgetClick={onWidgetClick}
150
- />,
151
- );
152
-
153
- // Click on the outer grid container (background)
154
- const gridContainer = container.firstElementChild as HTMLElement;
155
- fireEvent.click(gridContainer);
156
- expect(onWidgetClick).toHaveBeenCalledWith(null);
157
- });
158
- });
159
-
160
- describe('Keyboard navigation', () => {
161
- it('should select next widget with ArrowRight', () => {
162
- const onWidgetClick = vi.fn();
163
- render(
164
- <DashboardRenderer
165
- schema={DASHBOARD_WITH_WIDGETS}
166
- designMode
167
- selectedWidgetId="w1"
168
- onWidgetClick={onWidgetClick}
169
- />,
170
- );
171
-
172
- fireEvent.keyDown(screen.getByTestId('dashboard-preview-widget-w1'), { key: 'ArrowRight' });
173
- expect(onWidgetClick).toHaveBeenCalledWith('w2');
174
- });
175
-
176
- it('should select previous widget with ArrowLeft', () => {
177
- const onWidgetClick = vi.fn();
178
- render(
179
- <DashboardRenderer
180
- schema={DASHBOARD_WITH_WIDGETS}
181
- designMode
182
- selectedWidgetId="w2"
183
- onWidgetClick={onWidgetClick}
184
- />,
185
- );
186
-
187
- fireEvent.keyDown(screen.getByTestId('dashboard-preview-widget-w2'), { key: 'ArrowLeft' });
188
- expect(onWidgetClick).toHaveBeenCalledWith('w1');
189
- });
190
-
191
- it('should deselect with Escape', () => {
192
- const onWidgetClick = vi.fn();
193
- render(
194
- <DashboardRenderer
195
- schema={DASHBOARD_WITH_WIDGETS}
196
- designMode
197
- selectedWidgetId="w1"
198
- onWidgetClick={onWidgetClick}
199
- />,
200
- );
201
-
202
- fireEvent.keyDown(screen.getByTestId('dashboard-preview-widget-w1'), { key: 'Escape' });
203
- expect(onWidgetClick).toHaveBeenCalledWith(null);
204
- });
205
-
206
- it('should select with Enter key', () => {
207
- const onWidgetClick = vi.fn();
208
- render(
209
- <DashboardRenderer
210
- schema={DASHBOARD_WITH_WIDGETS}
211
- designMode
212
- selectedWidgetId={null}
213
- onWidgetClick={onWidgetClick}
214
- />,
215
- );
216
-
217
- fireEvent.keyDown(screen.getByTestId('dashboard-preview-widget-w2'), { key: 'Enter' });
218
- expect(onWidgetClick).toHaveBeenCalledWith('w2');
219
- });
220
-
221
- it('should select with Space key', () => {
222
- const onWidgetClick = vi.fn();
223
- render(
224
- <DashboardRenderer
225
- schema={DASHBOARD_WITH_WIDGETS}
226
- designMode
227
- selectedWidgetId={null}
228
- onWidgetClick={onWidgetClick}
229
- />,
230
- );
231
-
232
- fireEvent.keyDown(screen.getByTestId('dashboard-preview-widget-w1'), { key: ' ' });
233
- expect(onWidgetClick).toHaveBeenCalledWith('w1');
234
- });
235
- });
236
-
237
- describe('Content pointer-events in design mode', () => {
238
- it('should apply pointer-events-none to widget content in design mode', () => {
239
- render(
240
- <DashboardRenderer
241
- schema={DASHBOARD_WITH_WIDGETS}
242
- designMode
243
- selectedWidgetId={null}
244
- onWidgetClick={vi.fn()}
245
- />,
246
- );
247
-
248
- // Card widget (bar chart) — content wrapper should have pointer-events-none
249
- const barWidget = screen.getByTestId('dashboard-preview-widget-w2');
250
- const contentWrapper = barWidget.querySelector('.pointer-events-none');
251
- expect(contentWrapper).toBeInTheDocument();
252
- });
253
-
254
- it('should apply pointer-events-none to self-contained (metric) widget content', () => {
255
- render(
256
- <DashboardRenderer
257
- schema={DASHBOARD_WITH_WIDGETS}
258
- designMode
259
- selectedWidgetId={null}
260
- onWidgetClick={vi.fn()}
261
- />,
262
- );
263
-
264
- // Metric widget — SchemaRenderer receives pointer-events-none className
265
- const metricWidget = screen.getByTestId('dashboard-preview-widget-w1');
266
- const contentWrapper = metricWidget.querySelector('.pointer-events-none');
267
- expect(contentWrapper).toBeInTheDocument();
268
- });
269
-
270
- it('should NOT apply pointer-events-none when not in design mode', () => {
271
- const { container } = render(<DashboardRenderer schema={DASHBOARD_WITH_WIDGETS} />);
272
-
273
- // No design-mode content wrapper should have pointer-events-none class
274
- // (data-table ghost rows have pointer-events-none internally, which is unrelated to design mode)
275
- expect(container.querySelector('.pointer-events-none:not([data-testid="ghost-row"])')).not.toBeInTheDocument();
276
- });
277
-
278
- it('should still call onWidgetClick when clicking on Card-based widget content area', () => {
279
- const onWidgetClick = vi.fn();
280
- render(
281
- <DashboardRenderer
282
- schema={DASHBOARD_WITH_WIDGETS}
283
- designMode
284
- selectedWidgetId={null}
285
- onWidgetClick={onWidgetClick}
286
- />,
287
- );
288
-
289
- // Click on the bar chart widget (Card-based)
290
- fireEvent.click(screen.getByTestId('dashboard-preview-widget-w2'));
291
- expect(onWidgetClick).toHaveBeenCalledWith('w2');
292
- });
293
-
294
- it('should still call onWidgetClick when clicking on table widget', () => {
295
- const onWidgetClick = vi.fn();
296
- render(
297
- <DashboardRenderer
298
- schema={DASHBOARD_WITH_WIDGETS}
299
- designMode
300
- selectedWidgetId={null}
301
- onWidgetClick={onWidgetClick}
302
- />,
303
- );
304
-
305
- // Click on the table widget (Card-based)
306
- fireEvent.click(screen.getByTestId('dashboard-preview-widget-w3'));
307
- expect(onWidgetClick).toHaveBeenCalledWith('w3');
308
- });
309
- });
310
-
311
- describe('Click-capture overlay in design mode', () => {
312
- it('should render a click-capture overlay on Card-based widgets in design mode', () => {
313
- render(
314
- <DashboardRenderer
315
- schema={DASHBOARD_WITH_WIDGETS}
316
- designMode
317
- selectedWidgetId={null}
318
- onWidgetClick={vi.fn()}
319
- />,
320
- );
321
-
322
- // Card widget (bar chart) — should have an absolute overlay div
323
- const barWidget = screen.getByTestId('dashboard-preview-widget-w2');
324
- const overlay = barWidget.querySelector('[data-testid="widget-click-overlay"]');
325
- expect(overlay).toBeInTheDocument();
326
- expect(overlay?.className).toContain('absolute');
327
- expect(overlay?.className).toContain('inset-0');
328
- expect(overlay?.className).toContain('z-10');
329
- });
330
-
331
- it('should render a click-capture overlay on self-contained widgets in design mode', () => {
332
- render(
333
- <DashboardRenderer
334
- schema={DASHBOARD_WITH_WIDGETS}
335
- designMode
336
- selectedWidgetId={null}
337
- onWidgetClick={vi.fn()}
338
- />,
339
- );
340
-
341
- // Metric widget — should have an absolute overlay div
342
- const metricWidget = screen.getByTestId('dashboard-preview-widget-w1');
343
- const overlay = metricWidget.querySelector('[data-testid="widget-click-overlay"]');
344
- expect(overlay).toBeInTheDocument();
345
- expect(overlay?.className).toContain('absolute');
346
- expect(overlay?.className).toContain('inset-0');
347
- expect(overlay?.className).toContain('z-10');
348
- });
349
-
350
- it('should NOT render overlays when not in design mode', () => {
351
- const { container } = render(<DashboardRenderer schema={DASHBOARD_WITH_WIDGETS} />);
352
-
353
- expect(container.querySelector('[data-testid="widget-click-overlay"]')).not.toBeInTheDocument();
354
- });
355
-
356
- it('should apply relative positioning to widget container in design mode', () => {
357
- render(
358
- <DashboardRenderer
359
- schema={DASHBOARD_WITH_WIDGETS}
360
- designMode
361
- selectedWidgetId={null}
362
- onWidgetClick={vi.fn()}
363
- />,
364
- );
365
-
366
- // Card widget should have relative for overlay positioning
367
- const barWidget = screen.getByTestId('dashboard-preview-widget-w2');
368
- expect(barWidget.className).toContain('relative');
369
-
370
- // Metric widget should have relative for overlay positioning
371
- const metricWidget = screen.getByTestId('dashboard-preview-widget-w1');
372
- expect(metricWidget.className).toContain('relative');
373
- });
374
- });
375
-
376
- describe('Non-design mode behavior', () => {
377
- it('should not add design mode attributes when designMode is off', () => {
378
- const { container } = render(<DashboardRenderer schema={DASHBOARD_WITH_WIDGETS} />);
379
-
380
- // No widget should have data-widget-id
381
- expect(container.querySelector('[data-widget-id]')).not.toBeInTheDocument();
382
- // No widget should have role=button
383
- expect(container.querySelector('[role="button"]')).not.toBeInTheDocument();
384
- });
385
- });
386
- });
@@ -1,114 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { render, screen } from '@testing-library/react';
3
- import { DashboardRenderer } from '../DashboardRenderer';
4
- import type { DashboardSchema } from '@object-ui/types';
5
-
6
- // Mock SchemaRenderer to avoid pulling in the full renderer tree
7
- vi.mock('@object-ui/react', () => ({
8
- SchemaRenderer: ({ schema }: { schema: any }) => (
9
- <div data-testid="schema-renderer">{schema?.type ?? 'unknown'}</div>
10
- ),
11
- }));
12
-
13
- describe('DashboardRenderer header', () => {
14
- const baseSchema: DashboardSchema = {
15
- type: 'dashboard',
16
- name: 'test_dashboard',
17
- title: 'Sales Dashboard',
18
- description: 'Monthly overview of sales data',
19
- widgets: [],
20
- };
21
-
22
- it('should render title when showTitle is not false', () => {
23
- const schema: DashboardSchema = {
24
- ...baseSchema,
25
- header: { showTitle: true },
26
- };
27
- render(<DashboardRenderer schema={schema} />);
28
-
29
- expect(screen.getByText('Sales Dashboard')).toBeInTheDocument();
30
- });
31
-
32
- it('should not render title when showTitle is false', () => {
33
- const schema: DashboardSchema = {
34
- ...baseSchema,
35
- header: { showTitle: false },
36
- };
37
- render(<DashboardRenderer schema={schema} />);
38
-
39
- expect(screen.queryByText('Sales Dashboard')).not.toBeInTheDocument();
40
- });
41
-
42
- it('should render description when showDescription is not false', () => {
43
- const schema: DashboardSchema = {
44
- ...baseSchema,
45
- header: { showDescription: true },
46
- };
47
- render(<DashboardRenderer schema={schema} />);
48
-
49
- expect(screen.getByText('Monthly overview of sales data')).toBeInTheDocument();
50
- });
51
-
52
- it('should not render description when showDescription is false', () => {
53
- const schema: DashboardSchema = {
54
- ...baseSchema,
55
- header: { showDescription: false },
56
- };
57
- render(<DashboardRenderer schema={schema} />);
58
-
59
- expect(screen.queryByText('Monthly overview of sales data')).not.toBeInTheDocument();
60
- });
61
-
62
- it('should render action buttons', () => {
63
- const schema: DashboardSchema = {
64
- ...baseSchema,
65
- header: {
66
- actions: [
67
- { label: 'Export', action: 'export' },
68
- { label: 'Share', action: 'share' },
69
- ],
70
- },
71
- };
72
- render(<DashboardRenderer schema={schema} />);
73
-
74
- expect(screen.getByText('Export')).toBeInTheDocument();
75
- expect(screen.getByText('Share')).toBeInTheDocument();
76
- });
77
-
78
- it('should render recordCount badge when prop provided', () => {
79
- const schema: DashboardSchema = {
80
- ...baseSchema,
81
- header: {},
82
- };
83
- const onRefresh = vi.fn();
84
- render(<DashboardRenderer schema={schema} onRefresh={onRefresh} recordCount={1234} />);
85
-
86
- expect(screen.getByText('1,234 records')).toBeInTheDocument();
87
- });
88
-
89
- it('should set data-user-actions attribute when userActions provided', () => {
90
- const schema: DashboardSchema = {
91
- ...baseSchema,
92
- header: {},
93
- };
94
- const userActions = { sort: true, search: false, filter: true };
95
- const { container } = render(
96
- <DashboardRenderer schema={schema} userActions={userActions} />
97
- );
98
-
99
- const root = container.firstElementChild as HTMLElement;
100
- expect(root.getAttribute('data-user-actions')).toBe(JSON.stringify(userActions));
101
- });
102
-
103
- it('should not render header section when schema.header is undefined', () => {
104
- const schema: DashboardSchema = {
105
- ...baseSchema,
106
- header: undefined,
107
- };
108
- render(<DashboardRenderer schema={schema} />);
109
-
110
- // Neither title nor description should appear in a header context
111
- // The title text itself should not be rendered since there is no header
112
- expect(screen.queryByText('Sales Dashboard')).not.toBeInTheDocument();
113
- });
114
- });