@object-ui/plugin-dashboard 0.5.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +9 -9
- package/CHANGELOG.md +34 -0
- package/dist/index.css +1 -1
- package/dist/index.js +3612 -4764
- package/dist/index.umd.cjs +5 -5
- package/dist/src/DashboardGridLayout.d.ts +3 -1
- package/dist/src/DashboardGridLayout.d.ts.map +1 -1
- package/dist/src/DashboardRenderer.d.ts +6 -3
- package/dist/src/DashboardRenderer.d.ts.map +1 -1
- package/dist/src/DashboardRenderer.stories.d.ts +24 -0
- package/dist/src/DashboardRenderer.stories.d.ts.map +1 -0
- package/dist/src/index.d.ts +2 -12
- package/dist/src/index.d.ts.map +1 -1
- package/package.json +12 -12
- package/src/DashboardGridLayout.tsx +122 -68
- package/src/DashboardRenderer.stories.tsx +173 -0
- package/src/DashboardRenderer.tsx +161 -99
- package/src/__tests__/DashboardRenderer.autoRefresh.test.tsx +124 -0
- package/src/index.tsx +2 -60
- package/dist/src/ReportBuilder.d.ts +0 -11
- package/dist/src/ReportBuilder.d.ts.map +0 -1
- package/dist/src/ReportRenderer.d.ts +0 -15
- package/dist/src/ReportRenderer.d.ts.map +0 -1
- package/dist/src/ReportViewer.d.ts +0 -11
- package/dist/src/ReportViewer.d.ts.map +0 -1
- package/src/ReportBuilder.tsx +0 -625
- package/src/ReportRenderer.tsx +0 -89
- package/src/ReportViewer.tsx +0 -232
- package/src/__tests__/ReportBuilder.test.tsx +0 -115
- package/src/__tests__/ReportViewer.test.tsx +0 -107
package/src/ReportRenderer.tsx
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@object-ui/components';
|
|
3
|
-
import { ComponentRegistry } from '@object-ui/core';
|
|
4
|
-
|
|
5
|
-
export interface ReportRendererProps {
|
|
6
|
-
schema: {
|
|
7
|
-
type: string;
|
|
8
|
-
id?: string;
|
|
9
|
-
title?: string;
|
|
10
|
-
description?: string;
|
|
11
|
-
chart?: any; // Chart definition
|
|
12
|
-
data?: any[]; // Report data
|
|
13
|
-
columns?: any[]; // Report columns
|
|
14
|
-
className?: string;
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export const ReportRenderer: React.FC<ReportRendererProps> = ({ schema }) => {
|
|
19
|
-
const { title, description, data, columns } = schema;
|
|
20
|
-
|
|
21
|
-
// Get chart component type but don't store the component itself
|
|
22
|
-
const chartType = schema.chart?.type || 'chart';
|
|
23
|
-
const hasChart = !!schema.chart;
|
|
24
|
-
|
|
25
|
-
// In test environment, force fallback to simple table to avoid AG Grid complexity in JSDOM
|
|
26
|
-
const isTest = process.env.NODE_ENV === 'test';
|
|
27
|
-
const showGrid = !isTest;
|
|
28
|
-
|
|
29
|
-
return (
|
|
30
|
-
<Card className={`h-full flex flex-col ${schema.className || ''}`}>
|
|
31
|
-
<CardHeader>
|
|
32
|
-
{title && <CardTitle>{title}</CardTitle>}
|
|
33
|
-
{description && <CardDescription>{description}</CardDescription>}
|
|
34
|
-
</CardHeader>
|
|
35
|
-
<CardContent className="flex-1 overflow-auto space-y-4">
|
|
36
|
-
{/* Render Chart Section if present */}
|
|
37
|
-
{hasChart && (() => {
|
|
38
|
-
const ChartComponent = ComponentRegistry.get(chartType);
|
|
39
|
-
return ChartComponent ? (
|
|
40
|
-
<div className="min-h-[300px] border rounded-md p-4 bg-white/50">
|
|
41
|
-
<ChartComponent schema={{ ...schema.chart, data }} />
|
|
42
|
-
</div>
|
|
43
|
-
) : null;
|
|
44
|
-
})()}
|
|
45
|
-
|
|
46
|
-
{/* Render Data Grid Section */}
|
|
47
|
-
{data && data.length > 0 && (
|
|
48
|
-
<div className="border rounded-md">
|
|
49
|
-
{(() => {
|
|
50
|
-
const GridComponent = showGrid ? (ComponentRegistry.get('aggrid') || ComponentRegistry.get('table')) : null;
|
|
51
|
-
return GridComponent ? (
|
|
52
|
-
<GridComponent
|
|
53
|
-
schema={{
|
|
54
|
-
type: 'aggrid',
|
|
55
|
-
rowData: data,
|
|
56
|
-
columnDefs: columns,
|
|
57
|
-
domLayout: 'autoHeight'
|
|
58
|
-
}}
|
|
59
|
-
/>
|
|
60
|
-
) : (
|
|
61
|
-
// Simple Fallback Table if Grid plugin missing
|
|
62
|
-
<div className="overflow-x-auto">
|
|
63
|
-
<table className="w-full text-sm text-left">
|
|
64
|
-
<thead className="text-xs uppercase bg-gray-50">
|
|
65
|
-
<tr>
|
|
66
|
-
{columns?.map((col: any) => (
|
|
67
|
-
<th key={col.field} className="px-6 py-3">{col.headerName || col.label || col.field}</th>
|
|
68
|
-
))}
|
|
69
|
-
</tr>
|
|
70
|
-
</thead>
|
|
71
|
-
<tbody>
|
|
72
|
-
{data.map((row: any, i: number) => (
|
|
73
|
-
<tr key={i} className="bg-white border-b">
|
|
74
|
-
{columns?.map((col: any) => (
|
|
75
|
-
<td key={col.field} className="px-6 py-4">{row[col.field]}</td>
|
|
76
|
-
))}
|
|
77
|
-
</tr>
|
|
78
|
-
))}
|
|
79
|
-
</tbody>
|
|
80
|
-
</table>
|
|
81
|
-
</div>
|
|
82
|
-
);
|
|
83
|
-
})()}
|
|
84
|
-
</div>
|
|
85
|
-
)}
|
|
86
|
-
</CardContent>
|
|
87
|
-
</Card>
|
|
88
|
-
);
|
|
89
|
-
};
|
package/src/ReportViewer.tsx
DELETED
|
@@ -1,232 +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 React from 'react';
|
|
10
|
-
import { Card, CardContent, CardHeader, CardTitle, CardDescription, Button } from '@object-ui/components';
|
|
11
|
-
import { SchemaRenderer } from '@object-ui/react';
|
|
12
|
-
import { ComponentRegistry } from '@object-ui/core';
|
|
13
|
-
import type { ReportViewerSchema, ReportSection } from '@object-ui/types';
|
|
14
|
-
import { Download, Printer, RefreshCw } from 'lucide-react';
|
|
15
|
-
|
|
16
|
-
export interface ReportViewerProps {
|
|
17
|
-
schema: ReportViewerSchema;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* ReportViewer - Display a generated report with optional toolbar
|
|
22
|
-
* Supports rendering report sections, charts, tables, and export functionality
|
|
23
|
-
*/
|
|
24
|
-
export const ReportViewer: React.FC<ReportViewerProps> = ({ schema }) => {
|
|
25
|
-
const {
|
|
26
|
-
report,
|
|
27
|
-
data,
|
|
28
|
-
showToolbar = true,
|
|
29
|
-
allowExport = true,
|
|
30
|
-
allowPrint = true,
|
|
31
|
-
loading = false
|
|
32
|
-
} = schema;
|
|
33
|
-
|
|
34
|
-
const handleExport = (format: string) => {
|
|
35
|
-
console.log('Export report as:', format);
|
|
36
|
-
// TODO: Implement export functionality
|
|
37
|
-
alert(`Export to ${format.toUpperCase()} - Feature coming soon!`);
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const handlePrint = () => {
|
|
41
|
-
console.log('Print report');
|
|
42
|
-
window.print();
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
const handleRefresh = () => {
|
|
46
|
-
console.log('Refresh report');
|
|
47
|
-
// TODO: Trigger data refresh
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
if (!report) {
|
|
51
|
-
return (
|
|
52
|
-
<Card>
|
|
53
|
-
<CardContent className="p-8 text-center text-muted-foreground">
|
|
54
|
-
No report to display
|
|
55
|
-
</CardContent>
|
|
56
|
-
</Card>
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return (
|
|
61
|
-
<div className="space-y-4">
|
|
62
|
-
{/* Toolbar */}
|
|
63
|
-
{showToolbar && (
|
|
64
|
-
<div className="flex items-center justify-between gap-2 p-4 bg-card rounded-lg border">
|
|
65
|
-
<div>
|
|
66
|
-
<h2 className="text-lg font-semibold">{report.title}</h2>
|
|
67
|
-
{report.description && (
|
|
68
|
-
<p className="text-sm text-muted-foreground">{report.description}</p>
|
|
69
|
-
)}
|
|
70
|
-
</div>
|
|
71
|
-
<div className="flex items-center gap-2">
|
|
72
|
-
{report.refreshInterval && (
|
|
73
|
-
<Button variant="outline" size="sm" onClick={handleRefresh}>
|
|
74
|
-
<RefreshCw className="h-4 w-4 mr-2" />
|
|
75
|
-
Refresh
|
|
76
|
-
</Button>
|
|
77
|
-
)}
|
|
78
|
-
{allowPrint && (
|
|
79
|
-
<Button variant="outline" size="sm" onClick={handlePrint}>
|
|
80
|
-
<Printer className="h-4 w-4 mr-2" />
|
|
81
|
-
Print
|
|
82
|
-
</Button>
|
|
83
|
-
)}
|
|
84
|
-
{allowExport && report.showExportButtons && (
|
|
85
|
-
<>
|
|
86
|
-
<Button
|
|
87
|
-
variant="outline"
|
|
88
|
-
size="sm"
|
|
89
|
-
onClick={() => handleExport(report.defaultExportFormat || 'pdf')}
|
|
90
|
-
>
|
|
91
|
-
<Download className="h-4 w-4 mr-2" />
|
|
92
|
-
Export
|
|
93
|
-
</Button>
|
|
94
|
-
</>
|
|
95
|
-
)}
|
|
96
|
-
</div>
|
|
97
|
-
</div>
|
|
98
|
-
)}
|
|
99
|
-
|
|
100
|
-
{/* Report Content */}
|
|
101
|
-
<Card>
|
|
102
|
-
<CardHeader>
|
|
103
|
-
{!showToolbar && report.title && <CardTitle>{report.title}</CardTitle>}
|
|
104
|
-
{!showToolbar && report.description && <CardDescription>{report.description}</CardDescription>}
|
|
105
|
-
</CardHeader>
|
|
106
|
-
<CardContent className="space-y-6">
|
|
107
|
-
{loading && (
|
|
108
|
-
<div className="text-center py-8 text-muted-foreground">
|
|
109
|
-
Loading report data...
|
|
110
|
-
</div>
|
|
111
|
-
)}
|
|
112
|
-
|
|
113
|
-
{!loading && report.sections?.map((section: ReportSection, index: number) => {
|
|
114
|
-
// Check visibility condition
|
|
115
|
-
if (section.visible === false) {
|
|
116
|
-
return null;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
return (
|
|
120
|
-
<div key={index} className="space-y-2">
|
|
121
|
-
{/* Section Title */}
|
|
122
|
-
{section.title && (
|
|
123
|
-
<h3 className="text-lg font-semibold border-b pb-2">{section.title}</h3>
|
|
124
|
-
)}
|
|
125
|
-
|
|
126
|
-
{/* Section Content */}
|
|
127
|
-
{section.type === 'header' && section.title && (
|
|
128
|
-
<div className="text-2xl font-bold py-4">{section.title}</div>
|
|
129
|
-
)}
|
|
130
|
-
|
|
131
|
-
{section.type === 'summary' && (
|
|
132
|
-
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
133
|
-
{report.fields
|
|
134
|
-
?.filter(f => f.showInSummary)
|
|
135
|
-
.map((field, idx) => (
|
|
136
|
-
<Card key={idx}>
|
|
137
|
-
<CardContent className="p-4">
|
|
138
|
-
<div className="text-sm text-muted-foreground">{field.label || field.name}</div>
|
|
139
|
-
<div className="text-2xl font-bold">
|
|
140
|
-
{/* Calculate aggregation from data */}
|
|
141
|
-
{field.aggregation === 'count' && data?.length}
|
|
142
|
-
{field.aggregation === 'sum' && data?.reduce((sum, item) => sum + (item[field.name] || 0), 0)}
|
|
143
|
-
{/* TODO: Implement other aggregations */}
|
|
144
|
-
</div>
|
|
145
|
-
</CardContent>
|
|
146
|
-
</Card>
|
|
147
|
-
))}
|
|
148
|
-
</div>
|
|
149
|
-
)}
|
|
150
|
-
|
|
151
|
-
{section.type === 'chart' && section.chart && (
|
|
152
|
-
<div className="min-h-[300px]">
|
|
153
|
-
<SchemaRenderer schema={{ ...section.chart, data: data || section.chart.data }} />
|
|
154
|
-
</div>
|
|
155
|
-
)}
|
|
156
|
-
|
|
157
|
-
{section.type === 'table' && (
|
|
158
|
-
<div className="border rounded-lg overflow-hidden">
|
|
159
|
-
<table className="w-full text-sm">
|
|
160
|
-
<thead className="bg-muted">
|
|
161
|
-
<tr>
|
|
162
|
-
{section.columns?.map((col, idx) => (
|
|
163
|
-
<th key={idx} className="px-4 py-2 text-left font-medium">
|
|
164
|
-
{col.label || col.name}
|
|
165
|
-
</th>
|
|
166
|
-
))}
|
|
167
|
-
</tr>
|
|
168
|
-
</thead>
|
|
169
|
-
<tbody>
|
|
170
|
-
{data?.map((row, rowIdx) => (
|
|
171
|
-
<tr key={rowIdx} className="border-t">
|
|
172
|
-
{section.columns?.map((col, colIdx) => (
|
|
173
|
-
<td key={colIdx} className="px-4 py-2">
|
|
174
|
-
{row[col.name]}
|
|
175
|
-
</td>
|
|
176
|
-
))}
|
|
177
|
-
</tr>
|
|
178
|
-
))}
|
|
179
|
-
</tbody>
|
|
180
|
-
</table>
|
|
181
|
-
</div>
|
|
182
|
-
)}
|
|
183
|
-
|
|
184
|
-
{section.type === 'text' && section.text && (
|
|
185
|
-
<div className="prose max-w-none">
|
|
186
|
-
<p>{section.text}</p>
|
|
187
|
-
</div>
|
|
188
|
-
)}
|
|
189
|
-
|
|
190
|
-
{section.type === 'page-break' && (
|
|
191
|
-
<div className="border-t-2 border-dashed my-8 print:page-break-after-always" />
|
|
192
|
-
)}
|
|
193
|
-
|
|
194
|
-
{section.content && (
|
|
195
|
-
<SchemaRenderer schema={section.content} />
|
|
196
|
-
)}
|
|
197
|
-
</div>
|
|
198
|
-
);
|
|
199
|
-
})}
|
|
200
|
-
|
|
201
|
-
{/* Fallback: Show data if no sections defined */}
|
|
202
|
-
{!report.sections && data && data.length > 0 && (
|
|
203
|
-
<div className="border rounded-lg overflow-hidden">
|
|
204
|
-
<table className="w-full text-sm">
|
|
205
|
-
<thead className="bg-muted">
|
|
206
|
-
<tr>
|
|
207
|
-
{report.fields?.map((field, idx) => (
|
|
208
|
-
<th key={idx} className="px-4 py-2 text-left font-medium">
|
|
209
|
-
{field.label || field.name}
|
|
210
|
-
</th>
|
|
211
|
-
))}
|
|
212
|
-
</tr>
|
|
213
|
-
</thead>
|
|
214
|
-
<tbody>
|
|
215
|
-
{data.map((row, rowIdx) => (
|
|
216
|
-
<tr key={rowIdx} className="border-t">
|
|
217
|
-
{report.fields?.map((field, colIdx) => (
|
|
218
|
-
<td key={colIdx} className="px-4 py-2">
|
|
219
|
-
{row[field.name]}
|
|
220
|
-
</td>
|
|
221
|
-
))}
|
|
222
|
-
</tr>
|
|
223
|
-
))}
|
|
224
|
-
</tbody>
|
|
225
|
-
</table>
|
|
226
|
-
</div>
|
|
227
|
-
)}
|
|
228
|
-
</CardContent>
|
|
229
|
-
</Card>
|
|
230
|
-
</div>
|
|
231
|
-
);
|
|
232
|
-
};
|
|
@@ -1,115 +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, screen } from '@testing-library/react';
|
|
11
|
-
import '@testing-library/jest-dom';
|
|
12
|
-
import { ReportBuilder } from '../ReportBuilder';
|
|
13
|
-
import type { ReportBuilderSchema } from '@object-ui/types';
|
|
14
|
-
|
|
15
|
-
describe('ReportBuilder', () => {
|
|
16
|
-
it('should render report builder with title', () => {
|
|
17
|
-
const schema: ReportBuilderSchema = {
|
|
18
|
-
type: 'report-builder',
|
|
19
|
-
availableFields: [],
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
render(<ReportBuilder schema={schema} />);
|
|
23
|
-
|
|
24
|
-
expect(screen.getByText('Report Builder')).toBeInTheDocument();
|
|
25
|
-
expect(screen.getByText(/Configure your report settings/)).toBeInTheDocument();
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('should render with initial report configuration', () => {
|
|
29
|
-
const schema: ReportBuilderSchema = {
|
|
30
|
-
type: 'report-builder',
|
|
31
|
-
report: {
|
|
32
|
-
type: 'report',
|
|
33
|
-
title: 'Sales Report',
|
|
34
|
-
description: 'Monthly sales data',
|
|
35
|
-
fields: [],
|
|
36
|
-
},
|
|
37
|
-
availableFields: [],
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
render(<ReportBuilder schema={schema} />);
|
|
41
|
-
|
|
42
|
-
const titleInput = screen.getByDisplayValue('Sales Report');
|
|
43
|
-
expect(titleInput).toBeInTheDocument();
|
|
44
|
-
|
|
45
|
-
const descInput = screen.getByDisplayValue('Monthly sales data');
|
|
46
|
-
expect(descInput).toBeInTheDocument();
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('should render save and cancel buttons', () => {
|
|
50
|
-
const schema: ReportBuilderSchema = {
|
|
51
|
-
type: 'report-builder',
|
|
52
|
-
availableFields: [],
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
render(<ReportBuilder schema={schema} />);
|
|
56
|
-
|
|
57
|
-
expect(screen.getByText('Save Report')).toBeInTheDocument();
|
|
58
|
-
expect(screen.getByText('Cancel')).toBeInTheDocument();
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('should display empty state when no fields selected in fields tab', async () => {
|
|
62
|
-
const schema: ReportBuilderSchema = {
|
|
63
|
-
type: 'report-builder',
|
|
64
|
-
availableFields: [
|
|
65
|
-
{ name: 'revenue', label: 'Revenue', type: 'number' },
|
|
66
|
-
{ name: 'units', label: 'Units Sold', type: 'number' },
|
|
67
|
-
],
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const { container } = render(<ReportBuilder schema={schema} />);
|
|
71
|
-
|
|
72
|
-
// The empty state message exists in the DOM (just in a hidden tab)
|
|
73
|
-
// Check the component structure instead
|
|
74
|
-
expect(container.querySelector('.space-y-4')).toBeInTheDocument();
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it('should render preview section when enabled', () => {
|
|
78
|
-
const schema: ReportBuilderSchema = {
|
|
79
|
-
type: 'report-builder',
|
|
80
|
-
availableFields: [],
|
|
81
|
-
showPreview: true,
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
render(<ReportBuilder schema={schema} />);
|
|
85
|
-
|
|
86
|
-
expect(screen.getByText('Preview')).toBeInTheDocument();
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it('should render all tab sections', () => {
|
|
90
|
-
const schema: ReportBuilderSchema = {
|
|
91
|
-
type: 'report-builder',
|
|
92
|
-
availableFields: [],
|
|
93
|
-
};
|
|
94
|
-
|
|
95
|
-
render(<ReportBuilder schema={schema} />);
|
|
96
|
-
|
|
97
|
-
expect(screen.getByRole('tab', { name: /Basic/i })).toBeInTheDocument();
|
|
98
|
-
expect(screen.getByRole('tab', { name: /Fields/i })).toBeInTheDocument();
|
|
99
|
-
expect(screen.getByRole('tab', { name: /Filters/i })).toBeInTheDocument();
|
|
100
|
-
expect(screen.getByRole('tab', { name: /Group By/i })).toBeInTheDocument();
|
|
101
|
-
expect(screen.getByRole('tab', { name: /Sections/i })).toBeInTheDocument();
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it('should render export format options in basic tab', () => {
|
|
105
|
-
const schema: ReportBuilderSchema = {
|
|
106
|
-
type: 'report-builder',
|
|
107
|
-
availableFields: [],
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
render(<ReportBuilder schema={schema} />);
|
|
111
|
-
|
|
112
|
-
expect(screen.getByText('Default Export Format')).toBeInTheDocument();
|
|
113
|
-
expect(screen.getByText('Export Options')).toBeInTheDocument();
|
|
114
|
-
});
|
|
115
|
-
});
|
|
@@ -1,107 +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, screen } from '@testing-library/react';
|
|
11
|
-
import '@testing-library/jest-dom';
|
|
12
|
-
import { ReportViewer } from '../ReportViewer';
|
|
13
|
-
import type { ReportViewerSchema } from '@object-ui/types';
|
|
14
|
-
|
|
15
|
-
describe('ReportViewer', () => {
|
|
16
|
-
it('should render empty state when no report is provided', () => {
|
|
17
|
-
const schema: ReportViewerSchema = {
|
|
18
|
-
type: 'report-viewer',
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
render(<ReportViewer schema={schema} />);
|
|
22
|
-
|
|
23
|
-
expect(screen.getByText('No report to display')).toBeInTheDocument();
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it('should render report with title and description', () => {
|
|
27
|
-
const schema: ReportViewerSchema = {
|
|
28
|
-
type: 'report-viewer',
|
|
29
|
-
report: {
|
|
30
|
-
type: 'report',
|
|
31
|
-
title: 'Sales Report',
|
|
32
|
-
description: 'Monthly sales analysis',
|
|
33
|
-
fields: [],
|
|
34
|
-
sections: [],
|
|
35
|
-
},
|
|
36
|
-
showToolbar: true,
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
render(<ReportViewer schema={schema} />);
|
|
40
|
-
|
|
41
|
-
expect(screen.getByText('Sales Report')).toBeInTheDocument();
|
|
42
|
-
expect(screen.getByText('Monthly sales analysis')).toBeInTheDocument();
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('should render export and print buttons when enabled', () => {
|
|
46
|
-
const schema: ReportViewerSchema = {
|
|
47
|
-
type: 'report-viewer',
|
|
48
|
-
report: {
|
|
49
|
-
type: 'report',
|
|
50
|
-
title: 'Test Report',
|
|
51
|
-
showExportButtons: true,
|
|
52
|
-
fields: [],
|
|
53
|
-
sections: [],
|
|
54
|
-
},
|
|
55
|
-
showToolbar: true,
|
|
56
|
-
allowExport: true,
|
|
57
|
-
allowPrint: true,
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
render(<ReportViewer schema={schema} />);
|
|
61
|
-
|
|
62
|
-
expect(screen.getByText('Export')).toBeInTheDocument();
|
|
63
|
-
expect(screen.getByText('Print')).toBeInTheDocument();
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('should render loading state', () => {
|
|
67
|
-
const schema: ReportViewerSchema = {
|
|
68
|
-
type: 'report-viewer',
|
|
69
|
-
report: {
|
|
70
|
-
type: 'report',
|
|
71
|
-
title: 'Test Report',
|
|
72
|
-
fields: [],
|
|
73
|
-
sections: [],
|
|
74
|
-
},
|
|
75
|
-
loading: true,
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
render(<ReportViewer schema={schema} />);
|
|
79
|
-
|
|
80
|
-
expect(screen.getByText('Loading report data...')).toBeInTheDocument();
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
it('should render report data in table when no sections defined', () => {
|
|
84
|
-
const schema: ReportViewerSchema = {
|
|
85
|
-
type: 'report-viewer',
|
|
86
|
-
report: {
|
|
87
|
-
type: 'report',
|
|
88
|
-
title: 'User Report',
|
|
89
|
-
fields: [
|
|
90
|
-
{ name: 'name', label: 'Name' },
|
|
91
|
-
{ name: 'email', label: 'Email' },
|
|
92
|
-
],
|
|
93
|
-
},
|
|
94
|
-
data: [
|
|
95
|
-
{ name: 'John Doe', email: 'john@example.com' },
|
|
96
|
-
{ name: 'Jane Smith', email: 'jane@example.com' },
|
|
97
|
-
],
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
render(<ReportViewer schema={schema} />);
|
|
101
|
-
|
|
102
|
-
expect(screen.getByText('Name')).toBeInTheDocument();
|
|
103
|
-
expect(screen.getByText('Email')).toBeInTheDocument();
|
|
104
|
-
expect(screen.getByText('John Doe')).toBeInTheDocument();
|
|
105
|
-
expect(screen.getByText('jane@example.com')).toBeInTheDocument();
|
|
106
|
-
});
|
|
107
|
-
});
|