@object-ui/plugin-charts 3.0.2 → 3.1.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 +7 -7
- package/CHANGELOG.md +9 -0
- package/dist/{AdvancedChartImpl-BPJDgZhN.js → AdvancedChartImpl-D5NQFQLZ.js} +948 -920
- package/dist/{ChartImpl-C-IeuOgj.js → ChartImpl-WXTkPN08.js} +1 -1
- package/dist/{index-B49zCCfG.js → index-xUWSanB8.js} +249 -202
- package/dist/index.js +1 -1
- package/dist/index.umd.cjs +21 -21
- package/dist/src/AdvancedChartImpl.d.ts +3 -2
- package/dist/src/ChartRenderer.d.ts +1 -1
- package/dist/src/ObjectChart.d.ts +11 -0
- package/package.json +6 -6
- package/src/AdvancedChartImpl.tsx +44 -3
- package/src/ChartRenderer.tsx +2 -2
- package/src/ObjectChart.tsx +87 -21
- package/src/__tests__/ObjectChart.aggregation.test.ts +166 -0
- package/src/__tests__/ObjectChart.dataFetch.test.tsx +303 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for ObjectChart data fetching & fault tolerance.
|
|
3
|
+
*
|
|
4
|
+
* Verifies that ObjectChart:
|
|
5
|
+
* - Calls dataSource.find() when objectName is set and no bound data
|
|
6
|
+
* - Handles missing/invalid dataSource gracefully
|
|
7
|
+
* - Works without a SchemaRendererProvider
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
11
|
+
import { render, waitFor } from '@testing-library/react';
|
|
12
|
+
import React from 'react';
|
|
13
|
+
import { SchemaRendererProvider } from '@object-ui/react';
|
|
14
|
+
import { ObjectChart } from '../ObjectChart';
|
|
15
|
+
|
|
16
|
+
// Suppress console.error from React error boundary / fetch errors
|
|
17
|
+
const originalConsoleError = console.error;
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
console.error = vi.fn();
|
|
20
|
+
});
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
console.error = originalConsoleError;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('ObjectChart data fetching', () => {
|
|
26
|
+
it('should call dataSource.find when objectName is set and no bind path', async () => {
|
|
27
|
+
const mockFind = vi.fn().mockResolvedValue([
|
|
28
|
+
{ stage: 'Prospect', amount: 100 },
|
|
29
|
+
{ stage: 'Proposal', amount: 200 },
|
|
30
|
+
]);
|
|
31
|
+
const dataSource = { find: mockFind };
|
|
32
|
+
|
|
33
|
+
render(
|
|
34
|
+
<SchemaRendererProvider dataSource={dataSource}>
|
|
35
|
+
<ObjectChart
|
|
36
|
+
schema={{
|
|
37
|
+
type: 'object-chart',
|
|
38
|
+
objectName: 'opportunity',
|
|
39
|
+
chartType: 'bar',
|
|
40
|
+
xAxisKey: 'stage',
|
|
41
|
+
series: [{ dataKey: 'amount' }],
|
|
42
|
+
}}
|
|
43
|
+
/>
|
|
44
|
+
</SchemaRendererProvider>
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
await waitFor(() => {
|
|
48
|
+
expect(mockFind).toHaveBeenCalledWith('opportunity', { $filter: undefined });
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should NOT call dataSource.find when schema.data is provided', () => {
|
|
53
|
+
const mockFind = vi.fn();
|
|
54
|
+
const dataSource = { find: mockFind };
|
|
55
|
+
|
|
56
|
+
render(
|
|
57
|
+
<SchemaRendererProvider dataSource={dataSource}>
|
|
58
|
+
<ObjectChart
|
|
59
|
+
schema={{
|
|
60
|
+
type: 'object-chart',
|
|
61
|
+
objectName: 'opportunity',
|
|
62
|
+
chartType: 'bar',
|
|
63
|
+
data: [{ stage: 'A', amount: 100 }],
|
|
64
|
+
xAxisKey: 'stage',
|
|
65
|
+
series: [{ dataKey: 'amount' }],
|
|
66
|
+
}}
|
|
67
|
+
/>
|
|
68
|
+
</SchemaRendererProvider>
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
expect(mockFind).not.toHaveBeenCalled();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('should apply aggregation to fetched data', async () => {
|
|
75
|
+
const mockFind = vi.fn().mockResolvedValue([
|
|
76
|
+
{ stage: 'Prospect', amount: 100 },
|
|
77
|
+
{ stage: 'Prospect', amount: 200 },
|
|
78
|
+
{ stage: 'Proposal', amount: 300 },
|
|
79
|
+
]);
|
|
80
|
+
const dataSource = { find: mockFind };
|
|
81
|
+
|
|
82
|
+
const { container } = render(
|
|
83
|
+
<SchemaRendererProvider dataSource={dataSource}>
|
|
84
|
+
<ObjectChart
|
|
85
|
+
schema={{
|
|
86
|
+
type: 'object-chart',
|
|
87
|
+
objectName: 'opportunity',
|
|
88
|
+
chartType: 'bar',
|
|
89
|
+
xAxisKey: 'stage',
|
|
90
|
+
series: [{ dataKey: 'amount' }],
|
|
91
|
+
aggregate: { field: 'amount', function: 'sum', groupBy: 'stage' },
|
|
92
|
+
}}
|
|
93
|
+
/>
|
|
94
|
+
</SchemaRendererProvider>
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
await waitFor(() => {
|
|
98
|
+
expect(mockFind).toHaveBeenCalled();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('should prefer dataSource.aggregate() over find() when aggregate config is set', async () => {
|
|
103
|
+
const mockFind = vi.fn().mockResolvedValue([]);
|
|
104
|
+
const mockAggregate = vi.fn().mockResolvedValue([
|
|
105
|
+
{ stage: 'Prospect', amount: 300 },
|
|
106
|
+
{ stage: 'Proposal', amount: 300 },
|
|
107
|
+
]);
|
|
108
|
+
const dataSource = { find: mockFind, aggregate: mockAggregate };
|
|
109
|
+
|
|
110
|
+
render(
|
|
111
|
+
<SchemaRendererProvider dataSource={dataSource}>
|
|
112
|
+
<ObjectChart
|
|
113
|
+
schema={{
|
|
114
|
+
type: 'object-chart',
|
|
115
|
+
objectName: 'opportunity',
|
|
116
|
+
chartType: 'bar',
|
|
117
|
+
xAxisKey: 'stage',
|
|
118
|
+
series: [{ dataKey: 'amount' }],
|
|
119
|
+
aggregate: { field: 'amount', function: 'sum', groupBy: 'stage' },
|
|
120
|
+
}}
|
|
121
|
+
/>
|
|
122
|
+
</SchemaRendererProvider>
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
await waitFor(() => {
|
|
126
|
+
expect(mockAggregate).toHaveBeenCalledWith('opportunity', {
|
|
127
|
+
field: 'amount',
|
|
128
|
+
function: 'sum',
|
|
129
|
+
groupBy: 'stage',
|
|
130
|
+
filter: undefined,
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
// find() should NOT be called when aggregate() is available
|
|
134
|
+
expect(mockFind).not.toHaveBeenCalled();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should fall back to find() when aggregate() is not available', async () => {
|
|
138
|
+
const mockFind = vi.fn().mockResolvedValue([
|
|
139
|
+
{ stage: 'Prospect', amount: 100 },
|
|
140
|
+
{ stage: 'Prospect', amount: 200 },
|
|
141
|
+
]);
|
|
142
|
+
// dataSource without aggregate method
|
|
143
|
+
const dataSource = { find: mockFind };
|
|
144
|
+
|
|
145
|
+
render(
|
|
146
|
+
<SchemaRendererProvider dataSource={dataSource}>
|
|
147
|
+
<ObjectChart
|
|
148
|
+
schema={{
|
|
149
|
+
type: 'object-chart',
|
|
150
|
+
objectName: 'opportunity',
|
|
151
|
+
chartType: 'bar',
|
|
152
|
+
xAxisKey: 'stage',
|
|
153
|
+
series: [{ dataKey: 'amount' }],
|
|
154
|
+
aggregate: { field: 'amount', function: 'sum', groupBy: 'stage' },
|
|
155
|
+
}}
|
|
156
|
+
/>
|
|
157
|
+
</SchemaRendererProvider>
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
await waitFor(() => {
|
|
161
|
+
expect(mockFind).toHaveBeenCalledWith('opportunity', { $filter: undefined });
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('should NOT use aggregate() when no aggregate config is set', async () => {
|
|
166
|
+
const mockFind = vi.fn().mockResolvedValue([
|
|
167
|
+
{ stage: 'Prospect', amount: 100 },
|
|
168
|
+
]);
|
|
169
|
+
const mockAggregate = vi.fn().mockResolvedValue([]);
|
|
170
|
+
const dataSource = { find: mockFind, aggregate: mockAggregate };
|
|
171
|
+
|
|
172
|
+
render(
|
|
173
|
+
<SchemaRendererProvider dataSource={dataSource}>
|
|
174
|
+
<ObjectChart
|
|
175
|
+
schema={{
|
|
176
|
+
type: 'object-chart',
|
|
177
|
+
objectName: 'opportunity',
|
|
178
|
+
chartType: 'bar',
|
|
179
|
+
xAxisKey: 'stage',
|
|
180
|
+
series: [{ dataKey: 'amount' }],
|
|
181
|
+
}}
|
|
182
|
+
/>
|
|
183
|
+
</SchemaRendererProvider>
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
await waitFor(() => {
|
|
187
|
+
expect(mockFind).toHaveBeenCalled();
|
|
188
|
+
});
|
|
189
|
+
// aggregate() should NOT be called when no aggregate config
|
|
190
|
+
expect(mockAggregate).not.toHaveBeenCalled();
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('should pass filter to aggregate() when both aggregate and filter are set', async () => {
|
|
194
|
+
const mockAggregate = vi.fn().mockResolvedValue([
|
|
195
|
+
{ stage: 'Won', amount: 500 },
|
|
196
|
+
]);
|
|
197
|
+
const dataSource = { find: vi.fn(), aggregate: mockAggregate };
|
|
198
|
+
const filter = { status: 'active' };
|
|
199
|
+
|
|
200
|
+
render(
|
|
201
|
+
<SchemaRendererProvider dataSource={dataSource}>
|
|
202
|
+
<ObjectChart
|
|
203
|
+
schema={{
|
|
204
|
+
type: 'object-chart',
|
|
205
|
+
objectName: 'opportunity',
|
|
206
|
+
chartType: 'bar',
|
|
207
|
+
xAxisKey: 'stage',
|
|
208
|
+
series: [{ dataKey: 'amount' }],
|
|
209
|
+
filter,
|
|
210
|
+
aggregate: { field: 'amount', function: 'sum', groupBy: 'stage' },
|
|
211
|
+
}}
|
|
212
|
+
/>
|
|
213
|
+
</SchemaRendererProvider>
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
await waitFor(() => {
|
|
217
|
+
expect(mockAggregate).toHaveBeenCalledWith('opportunity', {
|
|
218
|
+
field: 'amount',
|
|
219
|
+
function: 'sum',
|
|
220
|
+
groupBy: 'stage',
|
|
221
|
+
filter: { status: 'active' },
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
describe('ObjectChart fault tolerance', () => {
|
|
228
|
+
it('should not crash when dataSource has no find method', () => {
|
|
229
|
+
const { container } = render(
|
|
230
|
+
<SchemaRendererProvider dataSource={{}}>
|
|
231
|
+
<ObjectChart
|
|
232
|
+
schema={{
|
|
233
|
+
type: 'object-chart',
|
|
234
|
+
objectName: 'opportunity',
|
|
235
|
+
chartType: 'bar',
|
|
236
|
+
xAxisKey: 'stage',
|
|
237
|
+
series: [{ dataKey: 'amount' }],
|
|
238
|
+
}}
|
|
239
|
+
/>
|
|
240
|
+
</SchemaRendererProvider>
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
// Should render without crashing
|
|
244
|
+
expect(container).toBeDefined();
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it('should not crash when rendered outside SchemaRendererProvider', () => {
|
|
248
|
+
const { container } = render(
|
|
249
|
+
<ObjectChart
|
|
250
|
+
schema={{
|
|
251
|
+
type: 'object-chart',
|
|
252
|
+
chartType: 'bar',
|
|
253
|
+
xAxisKey: 'stage',
|
|
254
|
+
series: [{ dataKey: 'amount' }],
|
|
255
|
+
}}
|
|
256
|
+
/>
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
// Should render without crashing
|
|
260
|
+
expect(container).toBeDefined();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('should show "No data source available" when no dataSource and objectName set', () => {
|
|
264
|
+
const { container } = render(
|
|
265
|
+
<ObjectChart
|
|
266
|
+
schema={{
|
|
267
|
+
type: 'object-chart',
|
|
268
|
+
objectName: 'opportunity',
|
|
269
|
+
chartType: 'bar',
|
|
270
|
+
xAxisKey: 'stage',
|
|
271
|
+
series: [{ dataKey: 'amount' }],
|
|
272
|
+
}}
|
|
273
|
+
/>
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
expect(container.textContent).toContain('No data source available');
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
it('should use dataSource prop over context when both are present', async () => {
|
|
280
|
+
const contextFind = vi.fn().mockResolvedValue([]);
|
|
281
|
+
const propFind = vi.fn().mockResolvedValue([{ stage: 'A', amount: 1 }]);
|
|
282
|
+
|
|
283
|
+
render(
|
|
284
|
+
<SchemaRendererProvider dataSource={{ find: contextFind }}>
|
|
285
|
+
<ObjectChart
|
|
286
|
+
dataSource={{ find: propFind }}
|
|
287
|
+
schema={{
|
|
288
|
+
type: 'object-chart',
|
|
289
|
+
objectName: 'opportunity',
|
|
290
|
+
chartType: 'bar',
|
|
291
|
+
xAxisKey: 'stage',
|
|
292
|
+
series: [{ dataKey: 'amount' }],
|
|
293
|
+
}}
|
|
294
|
+
/>
|
|
295
|
+
</SchemaRendererProvider>
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
await waitFor(() => {
|
|
299
|
+
expect(propFind).toHaveBeenCalled();
|
|
300
|
+
});
|
|
301
|
+
expect(contextFind).not.toHaveBeenCalled();
|
|
302
|
+
});
|
|
303
|
+
});
|