@dragonworks/ngx-dashboard 20.0.5 → 20.0.6
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/ng-package.json +7 -0
- package/package.json +34 -45
- package/src/lib/__tests__/dashboard-component-widget-state-integration.spec.ts +537 -0
- package/src/lib/cell/__tests__/cell-resize.component.spec.ts +442 -0
- package/src/lib/cell/__tests__/cell.component.spec.ts +541 -0
- package/src/lib/cell/cell-context-menu.component.ts +138 -0
- package/src/lib/cell/cell-context-menu.service.ts +36 -0
- package/src/lib/cell/cell.component.html +37 -0
- package/src/lib/cell/cell.component.scss +198 -0
- package/src/lib/cell/cell.component.ts +375 -0
- package/src/lib/dashboard/dashboard.component.html +18 -0
- package/src/lib/dashboard/dashboard.component.scss +17 -0
- package/src/lib/dashboard/dashboard.component.ts +187 -0
- package/src/lib/dashboard-editor/dashboard-editor.component.html +57 -0
- package/src/lib/dashboard-editor/dashboard-editor.component.scss +87 -0
- package/src/lib/dashboard-editor/dashboard-editor.component.ts +219 -0
- package/src/lib/dashboard-viewer/__tests__/dashboard-viewer.component.spec.ts +258 -0
- package/src/lib/dashboard-viewer/dashboard-viewer.component.html +20 -0
- package/src/lib/dashboard-viewer/dashboard-viewer.component.scss +50 -0
- package/src/lib/dashboard-viewer/dashboard-viewer.component.ts +70 -0
- package/src/lib/drop-zone/__tests__/drop-zone.component.spec.ts +465 -0
- package/src/lib/drop-zone/drop-zone.component.html +20 -0
- package/src/lib/drop-zone/drop-zone.component.scss +67 -0
- package/src/lib/drop-zone/drop-zone.component.ts +122 -0
- package/src/lib/internal-widgets/unknown-widget/unknown-widget.component.ts +72 -0
- package/src/lib/models/cell-data.ts +13 -0
- package/src/lib/models/cell-dialog.ts +7 -0
- package/src/lib/models/cell-id.ts +85 -0
- package/src/lib/models/cell-position.ts +15 -0
- package/src/lib/models/dashboard-data.dto.ts +44 -0
- package/src/lib/models/dashboard-data.utils.ts +49 -0
- package/src/lib/models/drag-data.ts +6 -0
- package/src/lib/models/index.ts +11 -0
- package/src/lib/models/reserved-space.ts +24 -0
- package/src/lib/models/widget-factory.ts +33 -0
- package/src/lib/models/widget-id.ts +70 -0
- package/src/lib/models/widget.ts +21 -0
- package/src/lib/providers/cell-settings-dialog/cell-settings-dialog.component.ts +127 -0
- package/src/lib/providers/cell-settings-dialog/cell-settings-dialog.provider.ts +15 -0
- package/src/lib/providers/cell-settings-dialog/cell-settings-dialog.tokens.ts +20 -0
- package/src/lib/providers/cell-settings-dialog/default-cell-settings-dialog.provider.ts +32 -0
- package/src/lib/providers/cell-settings-dialog/index.ts +3 -0
- package/src/lib/providers/index.ts +1 -0
- package/src/lib/services/__tests__/dashboard-bridge.service.spec.ts +220 -0
- package/src/lib/services/__tests__/dashboard-viewport.service.spec.ts +362 -0
- package/src/lib/services/dashboard-bridge.service.ts +155 -0
- package/src/lib/services/dashboard-viewport.service.ts +148 -0
- package/src/lib/services/dashboard.service.ts +62 -0
- package/src/lib/store/__tests__/dashboard-store-collision-detection.spec.ts +756 -0
- package/src/lib/store/__tests__/dashboard-store-computed-properties.spec.ts +974 -0
- package/src/lib/store/__tests__/dashboard-store-drag-drop.spec.ts +279 -0
- package/src/lib/store/__tests__/dashboard-store-export-import.spec.ts +780 -0
- package/src/lib/store/__tests__/dashboard-store-grid-config.spec.ts +128 -0
- package/src/lib/store/__tests__/dashboard-store-query-methods.spec.ts +229 -0
- package/src/lib/store/__tests__/dashboard-store-resize-operations.spec.ts +652 -0
- package/src/lib/store/__tests__/dashboard-store-widget-management.spec.ts +461 -0
- package/src/lib/store/__tests__/dashboard-store-widget-state-preservation.spec.ts +369 -0
- package/src/lib/store/dashboard-store.ts +239 -0
- package/src/lib/store/features/drag-drop.feature.ts +140 -0
- package/src/lib/store/features/grid-config.feature.ts +43 -0
- package/src/lib/store/features/resize.feature.ts +140 -0
- package/src/lib/store/features/utils/collision.utils.ts +89 -0
- package/src/lib/store/features/utils/grid-query-internal.utils.ts +37 -0
- package/src/lib/store/features/utils/resize.utils.ts +165 -0
- package/src/lib/store/features/widget-management.feature.ts +158 -0
- package/src/lib/styles/_dashboard-grid-vars.scss +11 -0
- package/src/lib/widget-list/__tests__/widget-list-bridge-integration.spec.ts +137 -0
- package/src/lib/widget-list/widget-list.component.html +22 -0
- package/src/lib/widget-list/widget-list.component.scss +154 -0
- package/src/lib/widget-list/widget-list.component.ts +106 -0
- package/src/public-api.ts +21 -0
- package/src/test-setup.ts +10 -0
- package/tsconfig.lib.json +15 -0
- package/tsconfig.lib.prod.json +11 -0
- package/tsconfig.spec.json +14 -0
- package/fesm2022/dragonworks-ngx-dashboard.mjs +0 -2178
- package/fesm2022/dragonworks-ngx-dashboard.mjs.map +0 -1
- package/index.d.ts +0 -678
|
@@ -0,0 +1,780 @@
|
|
|
1
|
+
import { TestBed } from '@angular/core/testing';
|
|
2
|
+
import { DashboardService } from '../../services/dashboard.service';
|
|
3
|
+
import { DashboardStore } from '../dashboard-store';
|
|
4
|
+
import { CellIdUtils, WidgetIdUtils, CellData, WidgetFactory, DashboardDataDto, Widget } from '../../models';
|
|
5
|
+
|
|
6
|
+
describe('DashboardStore - Export/Import Functionality', () => {
|
|
7
|
+
let store: InstanceType<typeof DashboardStore>;
|
|
8
|
+
let mockWidgetFactory: WidgetFactory;
|
|
9
|
+
let mockDashboardService: jasmine.SpyObj<DashboardService>;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
const dashboardServiceSpy = jasmine.createSpyObj('DashboardService', ['getFactory']);
|
|
13
|
+
|
|
14
|
+
TestBed.configureTestingModule({
|
|
15
|
+
providers: [
|
|
16
|
+
DashboardStore,
|
|
17
|
+
{ provide: DashboardService, useValue: dashboardServiceSpy }
|
|
18
|
+
]
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
store = TestBed.inject(DashboardStore);
|
|
22
|
+
mockDashboardService = TestBed.inject(DashboardService) as jasmine.SpyObj<DashboardService>;
|
|
23
|
+
store.setGridConfig({ rows: 8, columns: 12 });
|
|
24
|
+
|
|
25
|
+
mockWidgetFactory = {
|
|
26
|
+
widgetTypeid: 'test-widget',
|
|
27
|
+
createComponent: jasmine.createSpy()
|
|
28
|
+
} as any;
|
|
29
|
+
|
|
30
|
+
mockDashboardService.getFactory.and.returnValue(mockWidgetFactory);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
describe('exportDashboard', () => {
|
|
34
|
+
it('should export empty dashboard', () => {
|
|
35
|
+
const exported = store.exportDashboard();
|
|
36
|
+
|
|
37
|
+
expect(exported).toEqual({
|
|
38
|
+
version: '1.0.0',
|
|
39
|
+
dashboardId: '',
|
|
40
|
+
rows: 8,
|
|
41
|
+
columns: 12,
|
|
42
|
+
gutterSize: '0.5em',
|
|
43
|
+
cells: []
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should export dashboard with single widget', () => {
|
|
48
|
+
const cell: CellData = {
|
|
49
|
+
widgetId: WidgetIdUtils.generate(),
|
|
50
|
+
cellId: CellIdUtils.create(3, 4),
|
|
51
|
+
row: 3,
|
|
52
|
+
col: 4,
|
|
53
|
+
rowSpan: 2,
|
|
54
|
+
colSpan: 3,
|
|
55
|
+
widgetFactory: mockWidgetFactory,
|
|
56
|
+
widgetState: { color: 'red' },
|
|
57
|
+
flat: true,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
store.addWidget(cell);
|
|
61
|
+
const exported = store.exportDashboard();
|
|
62
|
+
|
|
63
|
+
expect(exported.cells.length).toBe(1);
|
|
64
|
+
expect(exported.cells[0]).toEqual({
|
|
65
|
+
row: 3,
|
|
66
|
+
col: 4,
|
|
67
|
+
rowSpan: 2,
|
|
68
|
+
colSpan: 3,
|
|
69
|
+
flat: true,
|
|
70
|
+
widgetTypeid: 'test-widget',
|
|
71
|
+
widgetState: { color: 'red' },
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should export dashboard with multiple widgets', () => {
|
|
76
|
+
const cell1: CellData = {
|
|
77
|
+
widgetId: WidgetIdUtils.generate(),
|
|
78
|
+
cellId: CellIdUtils.create(1, 1),
|
|
79
|
+
row: 1,
|
|
80
|
+
col: 1,
|
|
81
|
+
rowSpan: 1,
|
|
82
|
+
colSpan: 1,
|
|
83
|
+
widgetFactory: mockWidgetFactory,
|
|
84
|
+
widgetState: {},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const cell2: CellData = {
|
|
88
|
+
widgetId: WidgetIdUtils.generate(),
|
|
89
|
+
cellId: CellIdUtils.create(5, 8),
|
|
90
|
+
row: 5,
|
|
91
|
+
col: 8,
|
|
92
|
+
rowSpan: 2,
|
|
93
|
+
colSpan: 4,
|
|
94
|
+
widgetFactory: mockWidgetFactory,
|
|
95
|
+
widgetState: { size: 'large' },
|
|
96
|
+
flat: false,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
store.addWidget(cell1);
|
|
100
|
+
store.addWidget(cell2);
|
|
101
|
+
const exported = store.exportDashboard();
|
|
102
|
+
|
|
103
|
+
expect(exported.cells.length).toBe(2);
|
|
104
|
+
expect(exported.cells).toContain(jasmine.objectContaining({
|
|
105
|
+
row: 1,
|
|
106
|
+
col: 1,
|
|
107
|
+
rowSpan: 1,
|
|
108
|
+
colSpan: 1,
|
|
109
|
+
flat: undefined,
|
|
110
|
+
widgetTypeid: 'test-widget',
|
|
111
|
+
widgetState: {},
|
|
112
|
+
}));
|
|
113
|
+
expect(exported.cells).toContain(jasmine.objectContaining({
|
|
114
|
+
row: 5,
|
|
115
|
+
col: 8,
|
|
116
|
+
rowSpan: 2,
|
|
117
|
+
colSpan: 4,
|
|
118
|
+
flat: false,
|
|
119
|
+
widgetTypeid: 'test-widget',
|
|
120
|
+
widgetState: { size: 'large' },
|
|
121
|
+
}));
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should export custom grid configuration', () => {
|
|
125
|
+
store.setGridConfig({ rows: 20, columns: 30, gutterSize: '2rem' });
|
|
126
|
+
const exported = store.exportDashboard();
|
|
127
|
+
|
|
128
|
+
expect(exported.rows).toBe(20);
|
|
129
|
+
expect(exported.columns).toBe(30);
|
|
130
|
+
expect(exported.gutterSize).toBe('2rem');
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('loadDashboard', () => {
|
|
135
|
+
it('should load empty dashboard', () => {
|
|
136
|
+
const data: DashboardDataDto = {
|
|
137
|
+
version: '1.0.0',
|
|
138
|
+
dashboardId: 'test-dashboard-1',
|
|
139
|
+
rows: 10,
|
|
140
|
+
columns: 15,
|
|
141
|
+
gutterSize: '1em',
|
|
142
|
+
cells: []
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
store.loadDashboard(data);
|
|
146
|
+
|
|
147
|
+
expect(store.rows()).toBe(10);
|
|
148
|
+
expect(store.columns()).toBe(15);
|
|
149
|
+
expect(store.gutterSize()).toBe('1em');
|
|
150
|
+
expect(store.cells()).toEqual([]);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should load dashboard with single widget', () => {
|
|
154
|
+
const data: DashboardDataDto = {
|
|
155
|
+
version: '1.0.0',
|
|
156
|
+
dashboardId: 'test-dashboard-2',
|
|
157
|
+
rows: 8,
|
|
158
|
+
columns: 12,
|
|
159
|
+
gutterSize: '0.5em',
|
|
160
|
+
cells: [{
|
|
161
|
+
row: 5,
|
|
162
|
+
col: 7,
|
|
163
|
+
rowSpan: 3,
|
|
164
|
+
colSpan: 2,
|
|
165
|
+
flat: true,
|
|
166
|
+
widgetTypeid: 'test-widget',
|
|
167
|
+
widgetState: { text: 'Hello World' },
|
|
168
|
+
}]
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
store.loadDashboard(data);
|
|
172
|
+
|
|
173
|
+
expect(store.cells().length).toBe(1);
|
|
174
|
+
const loadedCell = store.cells()[0];
|
|
175
|
+
expect(loadedCell.row).toBe(5);
|
|
176
|
+
expect(loadedCell.col).toBe(7);
|
|
177
|
+
expect(loadedCell.rowSpan).toBe(3);
|
|
178
|
+
expect(loadedCell.colSpan).toBe(2);
|
|
179
|
+
expect(loadedCell.flat).toBe(true);
|
|
180
|
+
expect(loadedCell.widgetFactory).toBe(mockWidgetFactory);
|
|
181
|
+
expect(loadedCell.widgetState).toEqual({ text: 'Hello World' });
|
|
182
|
+
expect(loadedCell.cellId).toEqual(CellIdUtils.create(5, 7));
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should load dashboard with multiple widgets', () => {
|
|
186
|
+
const data: DashboardDataDto = {
|
|
187
|
+
version: '1.0.0',
|
|
188
|
+
dashboardId: 'test-dashboard-3',
|
|
189
|
+
rows: 16,
|
|
190
|
+
columns: 16,
|
|
191
|
+
gutterSize: '0.25em',
|
|
192
|
+
cells: [
|
|
193
|
+
{
|
|
194
|
+
row: 2,
|
|
195
|
+
col: 3,
|
|
196
|
+
rowSpan: 1,
|
|
197
|
+
colSpan: 1,
|
|
198
|
+
widgetTypeid: 'test-widget',
|
|
199
|
+
widgetState: {},
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
row: 8,
|
|
203
|
+
col: 10,
|
|
204
|
+
rowSpan: 4,
|
|
205
|
+
colSpan: 5,
|
|
206
|
+
flat: false,
|
|
207
|
+
widgetTypeid: 'test-widget',
|
|
208
|
+
widgetState: { config: 'advanced' },
|
|
209
|
+
}
|
|
210
|
+
]
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
store.loadDashboard(data);
|
|
214
|
+
|
|
215
|
+
expect(store.rows()).toBe(16);
|
|
216
|
+
expect(store.columns()).toBe(16);
|
|
217
|
+
expect(store.gutterSize()).toBe('0.25em');
|
|
218
|
+
expect(store.cells().length).toBe(2);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it('should create fallback widgets for unknown widget types', () => {
|
|
222
|
+
const consoleSpy = spyOn(console, 'warn');
|
|
223
|
+
const fallbackFactory = {
|
|
224
|
+
widgetTypeid: '__internal/unknown-widget',
|
|
225
|
+
createInstance: jasmine.createSpy()
|
|
226
|
+
} as any;
|
|
227
|
+
|
|
228
|
+
// Call the real getFactory method which handles fallback logic
|
|
229
|
+
mockDashboardService.getFactory.and.callFake((widgetTypeid: string) => {
|
|
230
|
+
if (widgetTypeid === 'unknown-widget' || widgetTypeid === 'another-unknown-widget') {
|
|
231
|
+
console.warn(`Unknown widget type: ${widgetTypeid}, using fallback error widget`);
|
|
232
|
+
return fallbackFactory;
|
|
233
|
+
}
|
|
234
|
+
return mockWidgetFactory;
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const data: DashboardDataDto = {
|
|
238
|
+
version: '1.0.0',
|
|
239
|
+
dashboardId: 'test-dashboard-4',
|
|
240
|
+
rows: 8,
|
|
241
|
+
columns: 12,
|
|
242
|
+
gutterSize: '0.5em',
|
|
243
|
+
cells: [
|
|
244
|
+
{
|
|
245
|
+
row: 1,
|
|
246
|
+
col: 1,
|
|
247
|
+
rowSpan: 1,
|
|
248
|
+
colSpan: 1,
|
|
249
|
+
widgetTypeid: 'unknown-widget',
|
|
250
|
+
widgetState: {},
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
row: 5,
|
|
254
|
+
col: 5,
|
|
255
|
+
rowSpan: 2,
|
|
256
|
+
colSpan: 2,
|
|
257
|
+
widgetTypeid: 'another-unknown-widget',
|
|
258
|
+
widgetState: {},
|
|
259
|
+
}
|
|
260
|
+
]
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
store.loadDashboard(data);
|
|
264
|
+
|
|
265
|
+
expect(store.cells().length).toBe(2); // Fallback widgets created instead of skipped
|
|
266
|
+
expect(consoleSpy).toHaveBeenCalledWith('Unknown widget type: unknown-widget, using fallback error widget');
|
|
267
|
+
expect(consoleSpy).toHaveBeenCalledWith('Unknown widget type: another-unknown-widget, using fallback error widget');
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it('should load mixed valid and invalid widgets', () => {
|
|
271
|
+
const consoleSpy = spyOn(console, 'warn');
|
|
272
|
+
const fallbackFactory = {
|
|
273
|
+
widgetTypeid: '__internal/unknown-widget',
|
|
274
|
+
createInstance: jasmine.createSpy()
|
|
275
|
+
} as any;
|
|
276
|
+
|
|
277
|
+
// Mock factory to return fallback for unknown widgets with console warning
|
|
278
|
+
mockDashboardService.getFactory.and.callFake((widgetTypeid: string) => {
|
|
279
|
+
if (widgetTypeid === 'unknown-widget') {
|
|
280
|
+
console.warn(`Unknown widget type: ${widgetTypeid}, using fallback error widget`);
|
|
281
|
+
return fallbackFactory;
|
|
282
|
+
}
|
|
283
|
+
return mockWidgetFactory;
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
const data: DashboardDataDto = {
|
|
287
|
+
version: '1.0.0',
|
|
288
|
+
dashboardId: 'test-dashboard-5',
|
|
289
|
+
rows: 8,
|
|
290
|
+
columns: 12,
|
|
291
|
+
gutterSize: '0.5em',
|
|
292
|
+
cells: [
|
|
293
|
+
{
|
|
294
|
+
row: 1,
|
|
295
|
+
col: 1,
|
|
296
|
+
rowSpan: 1,
|
|
297
|
+
colSpan: 1,
|
|
298
|
+
widgetTypeid: 'test-widget',
|
|
299
|
+
widgetState: {},
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
row: 3,
|
|
303
|
+
col: 3,
|
|
304
|
+
rowSpan: 1,
|
|
305
|
+
colSpan: 1,
|
|
306
|
+
widgetTypeid: 'unknown-widget',
|
|
307
|
+
widgetState: {},
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
row: 5,
|
|
311
|
+
col: 5,
|
|
312
|
+
rowSpan: 2,
|
|
313
|
+
colSpan: 2,
|
|
314
|
+
widgetTypeid: 'test-widget',
|
|
315
|
+
widgetState: {},
|
|
316
|
+
}
|
|
317
|
+
]
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
store.loadDashboard(data);
|
|
321
|
+
|
|
322
|
+
expect(store.cells().length).toBe(3); // All widgets loaded (2 valid + 1 fallback)
|
|
323
|
+
expect(consoleSpy).toHaveBeenCalledWith('Unknown widget type: unknown-widget, using fallback error widget');
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
it('should replace existing dashboard content', () => {
|
|
327
|
+
// Add initial content
|
|
328
|
+
const initialCell: CellData = {
|
|
329
|
+
widgetId: WidgetIdUtils.generate(),
|
|
330
|
+
cellId: CellIdUtils.create(1, 1),
|
|
331
|
+
row: 1,
|
|
332
|
+
col: 1,
|
|
333
|
+
rowSpan: 1,
|
|
334
|
+
colSpan: 1,
|
|
335
|
+
widgetFactory: mockWidgetFactory,
|
|
336
|
+
widgetState: {},
|
|
337
|
+
};
|
|
338
|
+
store.addWidget(initialCell);
|
|
339
|
+
expect(store.cells().length).toBe(1);
|
|
340
|
+
|
|
341
|
+
// Load new data
|
|
342
|
+
const data: DashboardDataDto = {
|
|
343
|
+
version: '1.0.0',
|
|
344
|
+
dashboardId: 'test-dashboard-6',
|
|
345
|
+
rows: 20,
|
|
346
|
+
columns: 25,
|
|
347
|
+
gutterSize: '3rem',
|
|
348
|
+
cells: [
|
|
349
|
+
{
|
|
350
|
+
row: 10,
|
|
351
|
+
col: 15,
|
|
352
|
+
rowSpan: 3,
|
|
353
|
+
colSpan: 4,
|
|
354
|
+
widgetTypeid: 'test-widget',
|
|
355
|
+
widgetState: { replaced: true },
|
|
356
|
+
}
|
|
357
|
+
]
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
store.loadDashboard(data);
|
|
361
|
+
|
|
362
|
+
expect(store.rows()).toBe(20);
|
|
363
|
+
expect(store.columns()).toBe(25);
|
|
364
|
+
expect(store.gutterSize()).toBe('3rem');
|
|
365
|
+
expect(store.cells().length).toBe(1);
|
|
366
|
+
expect(store.cells()[0].row).toBe(10);
|
|
367
|
+
expect(store.cells()[0].col).toBe(15);
|
|
368
|
+
expect(store.cells()[0].widgetState).toEqual({ replaced: true });
|
|
369
|
+
});
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
describe('exportDashboard - UnknownWidget filtering', () => {
|
|
373
|
+
let unknownWidgetFactory: WidgetFactory;
|
|
374
|
+
|
|
375
|
+
beforeEach(() => {
|
|
376
|
+
unknownWidgetFactory = {
|
|
377
|
+
widgetTypeid: '__internal/unknown-widget',
|
|
378
|
+
name: 'Unknown Widget',
|
|
379
|
+
description: 'Fallback widget',
|
|
380
|
+
svgIcon: '<svg></svg>',
|
|
381
|
+
createInstance: jasmine.createSpy()
|
|
382
|
+
} as any;
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it('should exclude UnknownWidgetComponent from export', () => {
|
|
386
|
+
const validCell: CellData = {
|
|
387
|
+
widgetId: WidgetIdUtils.generate(),
|
|
388
|
+
cellId: CellIdUtils.create(1, 1),
|
|
389
|
+
row: 1,
|
|
390
|
+
col: 1,
|
|
391
|
+
rowSpan: 1,
|
|
392
|
+
colSpan: 1,
|
|
393
|
+
widgetFactory: mockWidgetFactory,
|
|
394
|
+
widgetState: { valid: true },
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
const unknownCell: CellData = {
|
|
398
|
+
widgetId: WidgetIdUtils.generate(),
|
|
399
|
+
cellId: CellIdUtils.create(2, 2),
|
|
400
|
+
row: 2,
|
|
401
|
+
col: 2,
|
|
402
|
+
rowSpan: 1,
|
|
403
|
+
colSpan: 1,
|
|
404
|
+
widgetFactory: unknownWidgetFactory,
|
|
405
|
+
widgetState: { originalWidgetTypeid: 'missing-widget' },
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
store.addWidget(validCell);
|
|
409
|
+
store.addWidget(unknownCell);
|
|
410
|
+
|
|
411
|
+
expect(store.cells().length).toBe(2); // Both widgets added to store
|
|
412
|
+
|
|
413
|
+
const exported = store.exportDashboard();
|
|
414
|
+
|
|
415
|
+
expect(exported.cells.length).toBe(1); // Only valid widget exported
|
|
416
|
+
expect(exported.cells[0]).toEqual({
|
|
417
|
+
row: 1,
|
|
418
|
+
col: 1,
|
|
419
|
+
rowSpan: 1,
|
|
420
|
+
colSpan: 1,
|
|
421
|
+
flat: undefined,
|
|
422
|
+
widgetTypeid: 'test-widget',
|
|
423
|
+
widgetState: { valid: true },
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it('should export empty dashboard when only unknown widgets present', () => {
|
|
428
|
+
const unknownCell1: CellData = {
|
|
429
|
+
widgetId: WidgetIdUtils.generate(),
|
|
430
|
+
cellId: CellIdUtils.create(1, 1),
|
|
431
|
+
row: 1,
|
|
432
|
+
col: 1,
|
|
433
|
+
rowSpan: 1,
|
|
434
|
+
colSpan: 1,
|
|
435
|
+
widgetFactory: unknownWidgetFactory,
|
|
436
|
+
widgetState: { originalWidgetTypeid: 'widget-a' },
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
const unknownCell2: CellData = {
|
|
440
|
+
widgetId: WidgetIdUtils.generate(),
|
|
441
|
+
cellId: CellIdUtils.create(3, 3),
|
|
442
|
+
row: 3,
|
|
443
|
+
col: 3,
|
|
444
|
+
rowSpan: 2,
|
|
445
|
+
colSpan: 2,
|
|
446
|
+
widgetFactory: unknownWidgetFactory,
|
|
447
|
+
widgetState: { originalWidgetTypeid: 'widget-b' },
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
store.addWidget(unknownCell1);
|
|
451
|
+
store.addWidget(unknownCell2);
|
|
452
|
+
|
|
453
|
+
expect(store.cells().length).toBe(2); // Both unknown widgets added to store
|
|
454
|
+
|
|
455
|
+
const exported = store.exportDashboard();
|
|
456
|
+
|
|
457
|
+
expect(exported.cells.length).toBe(0); // No widgets exported
|
|
458
|
+
expect(exported.cells).toEqual([]);
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
it('should preserve valid widgets while filtering unknown widgets in mixed scenario', () => {
|
|
462
|
+
const validCell1: CellData = {
|
|
463
|
+
widgetId: WidgetIdUtils.generate(),
|
|
464
|
+
cellId: CellIdUtils.create(1, 1),
|
|
465
|
+
row: 1,
|
|
466
|
+
col: 1,
|
|
467
|
+
rowSpan: 1,
|
|
468
|
+
colSpan: 1,
|
|
469
|
+
widgetFactory: mockWidgetFactory,
|
|
470
|
+
widgetState: { type: 'first' },
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
const unknownCell: CellData = {
|
|
474
|
+
widgetId: WidgetIdUtils.generate(),
|
|
475
|
+
cellId: CellIdUtils.create(2, 2),
|
|
476
|
+
row: 2,
|
|
477
|
+
col: 2,
|
|
478
|
+
rowSpan: 1,
|
|
479
|
+
colSpan: 1,
|
|
480
|
+
widgetFactory: unknownWidgetFactory,
|
|
481
|
+
widgetState: { originalWidgetTypeid: 'missing' },
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
const validCell2: CellData = {
|
|
485
|
+
widgetId: WidgetIdUtils.generate(),
|
|
486
|
+
cellId: CellIdUtils.create(3, 3),
|
|
487
|
+
row: 3,
|
|
488
|
+
col: 3,
|
|
489
|
+
rowSpan: 2,
|
|
490
|
+
colSpan: 3,
|
|
491
|
+
widgetFactory: mockWidgetFactory,
|
|
492
|
+
widgetState: { type: 'second' },
|
|
493
|
+
flat: true,
|
|
494
|
+
};
|
|
495
|
+
|
|
496
|
+
store.addWidget(validCell1);
|
|
497
|
+
store.addWidget(unknownCell);
|
|
498
|
+
store.addWidget(validCell2);
|
|
499
|
+
|
|
500
|
+
expect(store.cells().length).toBe(3); // All widgets added to store
|
|
501
|
+
|
|
502
|
+
const exported = store.exportDashboard();
|
|
503
|
+
|
|
504
|
+
expect(exported.cells.length).toBe(2); // Only valid widgets exported
|
|
505
|
+
expect(exported.cells).toContain(jasmine.objectContaining({
|
|
506
|
+
row: 1,
|
|
507
|
+
col: 1,
|
|
508
|
+
rowSpan: 1,
|
|
509
|
+
colSpan: 1,
|
|
510
|
+
flat: undefined,
|
|
511
|
+
widgetTypeid: 'test-widget',
|
|
512
|
+
widgetState: { type: 'first' },
|
|
513
|
+
}));
|
|
514
|
+
expect(exported.cells).toContain(jasmine.objectContaining({
|
|
515
|
+
row: 3,
|
|
516
|
+
col: 3,
|
|
517
|
+
rowSpan: 2,
|
|
518
|
+
colSpan: 3,
|
|
519
|
+
flat: true,
|
|
520
|
+
widgetTypeid: 'test-widget',
|
|
521
|
+
widgetState: { type: 'second' },
|
|
522
|
+
}));
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it('should filter unknown widgets with live widget states callback', () => {
|
|
526
|
+
const validCell: CellData = {
|
|
527
|
+
widgetId: WidgetIdUtils.generate(),
|
|
528
|
+
cellId: CellIdUtils.create(1, 1),
|
|
529
|
+
row: 1,
|
|
530
|
+
col: 1,
|
|
531
|
+
rowSpan: 1,
|
|
532
|
+
colSpan: 1,
|
|
533
|
+
widgetFactory: mockWidgetFactory,
|
|
534
|
+
widgetState: { stale: 'data' },
|
|
535
|
+
};
|
|
536
|
+
|
|
537
|
+
const unknownCell: CellData = {
|
|
538
|
+
widgetId: WidgetIdUtils.generate(),
|
|
539
|
+
cellId: CellIdUtils.create(2, 2),
|
|
540
|
+
row: 2,
|
|
541
|
+
col: 2,
|
|
542
|
+
rowSpan: 1,
|
|
543
|
+
colSpan: 1,
|
|
544
|
+
widgetFactory: unknownWidgetFactory,
|
|
545
|
+
widgetState: { originalWidgetTypeid: 'missing' },
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
store.addWidget(validCell);
|
|
549
|
+
store.addWidget(unknownCell);
|
|
550
|
+
|
|
551
|
+
// Mock live widget states for both valid and unknown widgets
|
|
552
|
+
const liveStates = new Map<string, unknown>();
|
|
553
|
+
liveStates.set('1-1', { live: 'valid-data' });
|
|
554
|
+
liveStates.set('2-2', { live: 'unknown-data' }); // This should be ignored
|
|
555
|
+
|
|
556
|
+
const exported = store.exportDashboard(() => liveStates);
|
|
557
|
+
|
|
558
|
+
expect(exported.cells.length).toBe(1); // Only valid widget exported
|
|
559
|
+
expect(exported.cells[0]).toEqual({
|
|
560
|
+
row: 1,
|
|
561
|
+
col: 1,
|
|
562
|
+
rowSpan: 1,
|
|
563
|
+
colSpan: 1,
|
|
564
|
+
flat: undefined,
|
|
565
|
+
widgetTypeid: 'test-widget',
|
|
566
|
+
widgetState: { live: 'valid-data' }, // Uses live state for valid widget
|
|
567
|
+
});
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
it('should maintain grid configuration when filtering unknown widgets', () => {
|
|
571
|
+
store.setGridConfig({ rows: 15, columns: 20, gutterSize: '2em' });
|
|
572
|
+
|
|
573
|
+
const unknownCell: CellData = {
|
|
574
|
+
widgetId: WidgetIdUtils.generate(),
|
|
575
|
+
cellId: CellIdUtils.create(5, 5),
|
|
576
|
+
row: 5,
|
|
577
|
+
col: 5,
|
|
578
|
+
rowSpan: 1,
|
|
579
|
+
colSpan: 1,
|
|
580
|
+
widgetFactory: unknownWidgetFactory,
|
|
581
|
+
widgetState: { originalWidgetTypeid: 'gone' },
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
store.addWidget(unknownCell);
|
|
585
|
+
|
|
586
|
+
const exported = store.exportDashboard();
|
|
587
|
+
|
|
588
|
+
expect(exported.rows).toBe(15);
|
|
589
|
+
expect(exported.columns).toBe(20);
|
|
590
|
+
expect(exported.gutterSize).toBe('2em');
|
|
591
|
+
expect(exported.cells.length).toBe(0); // Unknown widget filtered out
|
|
592
|
+
});
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
describe('exportDashboard with live widget states', () => {
|
|
596
|
+
let mockWidget: jasmine.SpyObj<Widget>;
|
|
597
|
+
|
|
598
|
+
beforeEach(() => {
|
|
599
|
+
mockWidget = jasmine.createSpyObj('Widget', ['dashboardGetState', 'dashboardSetState']);
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
it('should export with live widget states when callback provided', () => {
|
|
603
|
+
const cell: CellData = {
|
|
604
|
+
widgetId: WidgetIdUtils.generate(),
|
|
605
|
+
cellId: CellIdUtils.create(2, 3),
|
|
606
|
+
row: 2,
|
|
607
|
+
col: 3,
|
|
608
|
+
rowSpan: 1,
|
|
609
|
+
colSpan: 1,
|
|
610
|
+
widgetFactory: mockWidgetFactory,
|
|
611
|
+
widgetState: { stale: 'data' }, // This should be ignored
|
|
612
|
+
flat: false,
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
store.addWidget(cell);
|
|
616
|
+
|
|
617
|
+
// Mock live widget states
|
|
618
|
+
const liveStates = new Map<string, unknown>();
|
|
619
|
+
liveStates.set('2-3', { fresh: 'data', updated: true });
|
|
620
|
+
|
|
621
|
+
const exported = store.exportDashboard(() => liveStates);
|
|
622
|
+
|
|
623
|
+
expect(exported.cells.length).toBe(1);
|
|
624
|
+
expect(exported.cells[0]).toEqual({
|
|
625
|
+
row: 2,
|
|
626
|
+
col: 3,
|
|
627
|
+
rowSpan: 1,
|
|
628
|
+
colSpan: 1,
|
|
629
|
+
flat: false,
|
|
630
|
+
widgetTypeid: 'test-widget',
|
|
631
|
+
widgetState: { fresh: 'data', updated: true }, // Uses live state
|
|
632
|
+
});
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
it('should fall back to stored state when live state not available', () => {
|
|
636
|
+
const cell: CellData = {
|
|
637
|
+
widgetId: WidgetIdUtils.generate(),
|
|
638
|
+
cellId: CellIdUtils.create(4, 5),
|
|
639
|
+
row: 4,
|
|
640
|
+
col: 5,
|
|
641
|
+
rowSpan: 2,
|
|
642
|
+
colSpan: 2,
|
|
643
|
+
widgetFactory: mockWidgetFactory,
|
|
644
|
+
widgetState: { fallback: 'state' },
|
|
645
|
+
flat: true,
|
|
646
|
+
};
|
|
647
|
+
|
|
648
|
+
store.addWidget(cell);
|
|
649
|
+
|
|
650
|
+
// Mock live widget states that don't include this cell
|
|
651
|
+
const liveStates = new Map<string, unknown>();
|
|
652
|
+
liveStates.set('1-1', { other: 'widget' });
|
|
653
|
+
|
|
654
|
+
const exported = store.exportDashboard(() => liveStates);
|
|
655
|
+
|
|
656
|
+
expect(exported.cells.length).toBe(1);
|
|
657
|
+
expect(exported.cells[0]).toEqual({
|
|
658
|
+
row: 4,
|
|
659
|
+
col: 5,
|
|
660
|
+
rowSpan: 2,
|
|
661
|
+
colSpan: 2,
|
|
662
|
+
flat: true,
|
|
663
|
+
widgetTypeid: 'test-widget',
|
|
664
|
+
widgetState: { fallback: 'state' }, // Uses stored state
|
|
665
|
+
});
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
it('should handle mixed live and stored states', () => {
|
|
669
|
+
const cell1: CellData = {
|
|
670
|
+
widgetId: WidgetIdUtils.generate(),
|
|
671
|
+
cellId: CellIdUtils.create(1, 1),
|
|
672
|
+
row: 1,
|
|
673
|
+
col: 1,
|
|
674
|
+
rowSpan: 1,
|
|
675
|
+
colSpan: 1,
|
|
676
|
+
widgetFactory: mockWidgetFactory,
|
|
677
|
+
widgetState: { stored: 'state1' },
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
const cell2: CellData = {
|
|
681
|
+
widgetId: WidgetIdUtils.generate(),
|
|
682
|
+
cellId: CellIdUtils.create(2, 2),
|
|
683
|
+
row: 2,
|
|
684
|
+
col: 2,
|
|
685
|
+
rowSpan: 1,
|
|
686
|
+
colSpan: 1,
|
|
687
|
+
widgetFactory: mockWidgetFactory,
|
|
688
|
+
widgetState: { stored: 'state2' },
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
store.addWidget(cell1);
|
|
692
|
+
store.addWidget(cell2);
|
|
693
|
+
|
|
694
|
+
// Only provide live state for one cell
|
|
695
|
+
const liveStates = new Map<string, unknown>();
|
|
696
|
+
liveStates.set('1-1', { live: 'state1', modified: true });
|
|
697
|
+
|
|
698
|
+
const exported = store.exportDashboard(() => liveStates);
|
|
699
|
+
|
|
700
|
+
expect(exported.cells.length).toBe(2);
|
|
701
|
+
|
|
702
|
+
const cell1Export = exported.cells.find(c => c.row === 1 && c.col === 1);
|
|
703
|
+
const cell2Export = exported.cells.find(c => c.row === 2 && c.col === 2);
|
|
704
|
+
|
|
705
|
+
expect(cell1Export?.widgetState).toEqual({ live: 'state1', modified: true });
|
|
706
|
+
expect(cell2Export?.widgetState).toEqual({ stored: 'state2' });
|
|
707
|
+
});
|
|
708
|
+
|
|
709
|
+
it('should work without callback (backward compatibility)', () => {
|
|
710
|
+
const cell: CellData = {
|
|
711
|
+
widgetId: WidgetIdUtils.generate(),
|
|
712
|
+
cellId: CellIdUtils.create(3, 4),
|
|
713
|
+
row: 3,
|
|
714
|
+
col: 4,
|
|
715
|
+
rowSpan: 1,
|
|
716
|
+
colSpan: 1,
|
|
717
|
+
widgetFactory: mockWidgetFactory,
|
|
718
|
+
widgetState: { original: 'state' },
|
|
719
|
+
};
|
|
720
|
+
|
|
721
|
+
store.addWidget(cell);
|
|
722
|
+
|
|
723
|
+
const exported = store.exportDashboard();
|
|
724
|
+
|
|
725
|
+
expect(exported.cells.length).toBe(1);
|
|
726
|
+
expect(exported.cells[0]).toEqual({
|
|
727
|
+
row: 3,
|
|
728
|
+
col: 4,
|
|
729
|
+
rowSpan: 1,
|
|
730
|
+
colSpan: 1,
|
|
731
|
+
flat: undefined,
|
|
732
|
+
widgetTypeid: 'test-widget',
|
|
733
|
+
widgetState: { original: 'state' },
|
|
734
|
+
});
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
it('should handle empty live states map', () => {
|
|
738
|
+
const cell: CellData = {
|
|
739
|
+
widgetId: WidgetIdUtils.generate(),
|
|
740
|
+
cellId: CellIdUtils.create(1, 2),
|
|
741
|
+
row: 1,
|
|
742
|
+
col: 2,
|
|
743
|
+
rowSpan: 1,
|
|
744
|
+
colSpan: 1,
|
|
745
|
+
widgetFactory: mockWidgetFactory,
|
|
746
|
+
widgetState: { default: 'state' },
|
|
747
|
+
};
|
|
748
|
+
|
|
749
|
+
store.addWidget(cell);
|
|
750
|
+
|
|
751
|
+
const exported = store.exportDashboard(() => new Map());
|
|
752
|
+
|
|
753
|
+
expect(exported.cells.length).toBe(1);
|
|
754
|
+
expect(exported.cells[0].widgetState).toEqual({ default: 'state' });
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
it('should handle undefined widget states', () => {
|
|
758
|
+
const cell: CellData = {
|
|
759
|
+
widgetId: WidgetIdUtils.generate(),
|
|
760
|
+
cellId: CellIdUtils.create(5, 6),
|
|
761
|
+
row: 5,
|
|
762
|
+
col: 6,
|
|
763
|
+
rowSpan: 1,
|
|
764
|
+
colSpan: 1,
|
|
765
|
+
widgetFactory: mockWidgetFactory,
|
|
766
|
+
widgetState: { existing: 'state' },
|
|
767
|
+
};
|
|
768
|
+
|
|
769
|
+
store.addWidget(cell);
|
|
770
|
+
|
|
771
|
+
const liveStates = new Map<string, unknown>();
|
|
772
|
+
liveStates.set('5-6', undefined);
|
|
773
|
+
|
|
774
|
+
const exported = store.exportDashboard(() => liveStates);
|
|
775
|
+
|
|
776
|
+
expect(exported.cells.length).toBe(1);
|
|
777
|
+
expect(exported.cells[0].widgetState).toEqual({ existing: 'state' });
|
|
778
|
+
});
|
|
779
|
+
});
|
|
780
|
+
});
|