@dragonworks/ngx-dashboard 20.0.6 → 20.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.
Files changed (78) hide show
  1. package/fesm2022/dragonworks-ngx-dashboard.mjs +2250 -0
  2. package/fesm2022/dragonworks-ngx-dashboard.mjs.map +1 -0
  3. package/index.d.ts +727 -0
  4. package/package.json +45 -34
  5. package/ng-package.json +0 -7
  6. package/src/lib/__tests__/dashboard-component-widget-state-integration.spec.ts +0 -537
  7. package/src/lib/cell/__tests__/cell-resize.component.spec.ts +0 -442
  8. package/src/lib/cell/__tests__/cell.component.spec.ts +0 -541
  9. package/src/lib/cell/cell-context-menu.component.ts +0 -138
  10. package/src/lib/cell/cell-context-menu.service.ts +0 -36
  11. package/src/lib/cell/cell.component.html +0 -37
  12. package/src/lib/cell/cell.component.scss +0 -198
  13. package/src/lib/cell/cell.component.ts +0 -375
  14. package/src/lib/dashboard/dashboard.component.html +0 -18
  15. package/src/lib/dashboard/dashboard.component.scss +0 -17
  16. package/src/lib/dashboard/dashboard.component.ts +0 -187
  17. package/src/lib/dashboard-editor/dashboard-editor.component.html +0 -57
  18. package/src/lib/dashboard-editor/dashboard-editor.component.scss +0 -87
  19. package/src/lib/dashboard-editor/dashboard-editor.component.ts +0 -219
  20. package/src/lib/dashboard-viewer/__tests__/dashboard-viewer.component.spec.ts +0 -258
  21. package/src/lib/dashboard-viewer/dashboard-viewer.component.html +0 -20
  22. package/src/lib/dashboard-viewer/dashboard-viewer.component.scss +0 -50
  23. package/src/lib/dashboard-viewer/dashboard-viewer.component.ts +0 -70
  24. package/src/lib/drop-zone/__tests__/drop-zone.component.spec.ts +0 -465
  25. package/src/lib/drop-zone/drop-zone.component.html +0 -20
  26. package/src/lib/drop-zone/drop-zone.component.scss +0 -67
  27. package/src/lib/drop-zone/drop-zone.component.ts +0 -122
  28. package/src/lib/internal-widgets/unknown-widget/unknown-widget.component.ts +0 -72
  29. package/src/lib/models/cell-data.ts +0 -13
  30. package/src/lib/models/cell-dialog.ts +0 -7
  31. package/src/lib/models/cell-id.ts +0 -85
  32. package/src/lib/models/cell-position.ts +0 -15
  33. package/src/lib/models/dashboard-data.dto.ts +0 -44
  34. package/src/lib/models/dashboard-data.utils.ts +0 -49
  35. package/src/lib/models/drag-data.ts +0 -6
  36. package/src/lib/models/index.ts +0 -11
  37. package/src/lib/models/reserved-space.ts +0 -24
  38. package/src/lib/models/widget-factory.ts +0 -33
  39. package/src/lib/models/widget-id.ts +0 -70
  40. package/src/lib/models/widget.ts +0 -21
  41. package/src/lib/providers/cell-settings-dialog/cell-settings-dialog.component.ts +0 -127
  42. package/src/lib/providers/cell-settings-dialog/cell-settings-dialog.provider.ts +0 -15
  43. package/src/lib/providers/cell-settings-dialog/cell-settings-dialog.tokens.ts +0 -20
  44. package/src/lib/providers/cell-settings-dialog/default-cell-settings-dialog.provider.ts +0 -32
  45. package/src/lib/providers/cell-settings-dialog/index.ts +0 -3
  46. package/src/lib/providers/index.ts +0 -1
  47. package/src/lib/services/__tests__/dashboard-bridge.service.spec.ts +0 -220
  48. package/src/lib/services/__tests__/dashboard-viewport.service.spec.ts +0 -362
  49. package/src/lib/services/dashboard-bridge.service.ts +0 -155
  50. package/src/lib/services/dashboard-viewport.service.ts +0 -148
  51. package/src/lib/services/dashboard.service.ts +0 -62
  52. package/src/lib/store/__tests__/dashboard-store-collision-detection.spec.ts +0 -756
  53. package/src/lib/store/__tests__/dashboard-store-computed-properties.spec.ts +0 -974
  54. package/src/lib/store/__tests__/dashboard-store-drag-drop.spec.ts +0 -279
  55. package/src/lib/store/__tests__/dashboard-store-export-import.spec.ts +0 -780
  56. package/src/lib/store/__tests__/dashboard-store-grid-config.spec.ts +0 -128
  57. package/src/lib/store/__tests__/dashboard-store-query-methods.spec.ts +0 -229
  58. package/src/lib/store/__tests__/dashboard-store-resize-operations.spec.ts +0 -652
  59. package/src/lib/store/__tests__/dashboard-store-widget-management.spec.ts +0 -461
  60. package/src/lib/store/__tests__/dashboard-store-widget-state-preservation.spec.ts +0 -369
  61. package/src/lib/store/dashboard-store.ts +0 -239
  62. package/src/lib/store/features/drag-drop.feature.ts +0 -140
  63. package/src/lib/store/features/grid-config.feature.ts +0 -43
  64. package/src/lib/store/features/resize.feature.ts +0 -140
  65. package/src/lib/store/features/utils/collision.utils.ts +0 -89
  66. package/src/lib/store/features/utils/grid-query-internal.utils.ts +0 -37
  67. package/src/lib/store/features/utils/resize.utils.ts +0 -165
  68. package/src/lib/store/features/widget-management.feature.ts +0 -158
  69. package/src/lib/styles/_dashboard-grid-vars.scss +0 -11
  70. package/src/lib/widget-list/__tests__/widget-list-bridge-integration.spec.ts +0 -137
  71. package/src/lib/widget-list/widget-list.component.html +0 -22
  72. package/src/lib/widget-list/widget-list.component.scss +0 -154
  73. package/src/lib/widget-list/widget-list.component.ts +0 -106
  74. package/src/public-api.ts +0 -21
  75. package/src/test-setup.ts +0 -10
  76. package/tsconfig.lib.json +0 -15
  77. package/tsconfig.lib.prod.json +0 -11
  78. package/tsconfig.spec.json +0 -14
@@ -1,974 +0,0 @@
1
- import { TestBed } from '@angular/core/testing';
2
- import { DashboardStore } from '../dashboard-store';
3
- import { CellIdUtils, WidgetIdUtils, CellData, DragData, WidgetMetadata, WidgetFactory } from '../../models';
4
- import { DashboardService } from '../../services/dashboard.service';
5
-
6
- describe('DashboardStore - Computed Properties', () => {
7
- let store: InstanceType<typeof DashboardStore>;
8
- let mockWidgetFactory: WidgetFactory;
9
- let testWidgetMetadata: WidgetMetadata;
10
- let dashboardService: jasmine.SpyObj<DashboardService>;
11
-
12
- beforeEach(() => {
13
- const spy = jasmine.createSpyObj('DashboardService', ['getFactory']);
14
-
15
- TestBed.configureTestingModule({
16
- providers: [
17
- DashboardStore,
18
- { provide: DashboardService, useValue: spy }
19
- ]
20
- });
21
-
22
- store = TestBed.inject(DashboardStore);
23
- dashboardService = TestBed.inject(DashboardService) as jasmine.SpyObj<DashboardService>;
24
- store.setGridConfig({ rows: 16, columns: 16 });
25
-
26
- mockWidgetFactory = {
27
- widgetTypeid: 'test-widget',
28
- createComponent: jasmine.createSpy('createComponent')
29
- } as any;
30
-
31
- testWidgetMetadata = {
32
- widgetTypeid: 'test-widget',
33
- name: 'Test Widget',
34
- description: 'A test widget for unit tests',
35
- svgIcon: '<svg></svg>'
36
- };
37
-
38
- dashboardService.getFactory.and.returnValue(mockWidgetFactory);
39
- });
40
-
41
- describe('resizePreviewCells', () => {
42
- let cellId: ReturnType<typeof CellIdUtils.create>;
43
-
44
- beforeEach(() => {
45
- cellId = CellIdUtils.create(4, 4);
46
- const cell: CellData = {
47
- widgetId: WidgetIdUtils.generate(),
48
- cellId,
49
- row: 4,
50
- col: 4,
51
- rowSpan: 2,
52
- colSpan: 3,
53
- widgetFactory: mockWidgetFactory,
54
- widgetState: {},
55
- };
56
- store.addWidget(cell);
57
- });
58
-
59
- it('should return empty array when no resize is active', () => {
60
- expect(store.resizePreviewCells()).toEqual([]);
61
- });
62
-
63
- it('should return empty array when resize data exists but widget is removed', () => {
64
- store.startResize(cellId);
65
- const widget = store.cells().find(c => CellIdUtils.equals(c.cellId, cellId))!; store.removeWidget(widget.widgetId);
66
-
67
- expect(store.resizePreviewCells()).toEqual([]);
68
- });
69
-
70
- it('should calculate preview cells for original widget size', () => {
71
- store.startResize(cellId);
72
-
73
- const previewCells = store.resizePreviewCells();
74
-
75
- expect(previewCells.length).toBe(6); // 2x3 widget
76
- expect(previewCells).toContain(jasmine.objectContaining({ row: 4, col: 4 }));
77
- expect(previewCells).toContain(jasmine.objectContaining({ row: 4, col: 5 }));
78
- expect(previewCells).toContain(jasmine.objectContaining({ row: 4, col: 6 }));
79
- expect(previewCells).toContain(jasmine.objectContaining({ row: 5, col: 4 }));
80
- expect(previewCells).toContain(jasmine.objectContaining({ row: 5, col: 5 }));
81
- expect(previewCells).toContain(jasmine.objectContaining({ row: 5, col: 6 }));
82
- });
83
-
84
- it('should update preview cells when resize preview changes horizontally', () => {
85
- store.startResize(cellId);
86
- store.updateResizePreview('horizontal', 2); // colSpan becomes 5
87
-
88
- const previewCells = store.resizePreviewCells();
89
-
90
- expect(previewCells.length).toBe(10); // 2x5 widget
91
- expect(previewCells).toContain(jasmine.objectContaining({ row: 4, col: 4 }));
92
- expect(previewCells).toContain(jasmine.objectContaining({ row: 4, col: 8 })); // rightmost
93
- expect(previewCells).toContain(jasmine.objectContaining({ row: 5, col: 4 }));
94
- expect(previewCells).toContain(jasmine.objectContaining({ row: 5, col: 8 })); // bottom-right
95
- });
96
-
97
- it('should update preview cells when resize preview changes vertically', () => {
98
- store.startResize(cellId);
99
- store.updateResizePreview('vertical', 1); // rowSpan becomes 3
100
-
101
- const previewCells = store.resizePreviewCells();
102
-
103
- expect(previewCells.length).toBe(9); // 3x3 widget
104
- expect(previewCells).toContain(jasmine.objectContaining({ row: 4, col: 4 }));
105
- expect(previewCells).toContain(jasmine.objectContaining({ row: 6, col: 4 })); // bottommost
106
- expect(previewCells).toContain(jasmine.objectContaining({ row: 6, col: 6 })); // bottom-right
107
- });
108
-
109
- it('should handle single cell preview when resized to minimum', () => {
110
- store.startResize(cellId);
111
- store.updateResizePreview('horizontal', -5); // Reduce to minimum
112
- store.updateResizePreview('vertical', -5); // Reduce to minimum
113
-
114
- const previewCells = store.resizePreviewCells();
115
-
116
- expect(previewCells.length).toBe(1);
117
- expect(previewCells[0]).toEqual(jasmine.objectContaining({ row: 4, col: 4 }));
118
- });
119
-
120
- it('should handle large preview sizes', () => {
121
- store.startResize(cellId);
122
- store.updateResizePreview('horizontal', 8); // colSpan becomes 11
123
- store.updateResizePreview('vertical', 6); // rowSpan becomes 8
124
-
125
- const previewCells = store.resizePreviewCells();
126
-
127
- expect(previewCells.length).toBe(88); // 8x11 widget
128
- expect(previewCells).toContain(jasmine.objectContaining({ row: 4, col: 4 })); // top-left
129
- expect(previewCells).toContain(jasmine.objectContaining({ row: 11, col: 14 })); // bottom-right
130
- });
131
-
132
- it('should update reactively when resize preview changes multiple times', () => {
133
- store.startResize(cellId);
134
-
135
- // Initial state
136
- expect(store.resizePreviewCells().length).toBe(6);
137
-
138
- // First change
139
- store.updateResizePreview('horizontal', 1);
140
- expect(store.resizePreviewCells().length).toBe(8); // 2x4
141
-
142
- // Second change
143
- store.updateResizePreview('vertical', 1);
144
- expect(store.resizePreviewCells().length).toBe(12); // 3x4
145
-
146
- // Third change
147
- store.updateResizePreview('horizontal', -2);
148
- const finalResizeData = store.resizeData();
149
- expect(finalResizeData?.previewRowSpan).toBe(3);
150
- expect(finalResizeData?.previewColSpan).toBe(1);
151
- expect(store.resizePreviewCells().length).toBe(3); // 3x1
152
- });
153
- });
154
-
155
- describe('resizePreviewMap', () => {
156
- let cellId: ReturnType<typeof CellIdUtils.create>;
157
-
158
- beforeEach(() => {
159
- cellId = CellIdUtils.create(8, 8);
160
- const cell: CellData = {
161
- widgetId: WidgetIdUtils.generate(),
162
- cellId,
163
- row: 8,
164
- col: 8,
165
- rowSpan: 1,
166
- colSpan: 1,
167
- widgetFactory: mockWidgetFactory,
168
- widgetState: {},
169
- };
170
- store.addWidget(cell);
171
- });
172
-
173
- it('should return empty set when no resize is active', () => {
174
- expect(store.resizePreviewMap().size).toBe(0);
175
- });
176
-
177
- it('should create map from preview cells', () => {
178
- store.startResize(cellId);
179
- store.updateResizePreview('horizontal', 2);
180
- store.updateResizePreview('vertical', 1);
181
-
182
- const previewMap = store.resizePreviewMap();
183
-
184
- expect(previewMap.size).toBe(6); // 2x3 grid
185
- expect(previewMap.has(CellIdUtils.create(8, 8))).toBe(true);
186
- expect(previewMap.has(CellIdUtils.create(8, 9))).toBe(true);
187
- expect(previewMap.has(CellIdUtils.create(8, 10))).toBe(true);
188
- expect(previewMap.has(CellIdUtils.create(9, 8))).toBe(true);
189
- expect(previewMap.has(CellIdUtils.create(9, 9))).toBe(true);
190
- expect(previewMap.has(CellIdUtils.create(9, 10))).toBe(true);
191
- });
192
-
193
- it('should not contain cells outside preview area', () => {
194
- store.startResize(cellId);
195
- store.updateResizePreview('horizontal', 1);
196
-
197
- const previewMap = store.resizePreviewMap();
198
-
199
- expect(previewMap.has(CellIdUtils.create(8, 8))).toBe(true);
200
- expect(previewMap.has(CellIdUtils.create(8, 9))).toBe(true);
201
- expect(previewMap.has(CellIdUtils.create(8, 10))).toBe(false);
202
- expect(previewMap.has(CellIdUtils.create(9, 8))).toBe(false);
203
- expect(previewMap.has(CellIdUtils.create(7, 8))).toBe(false);
204
- });
205
-
206
- it('should update when preview changes', () => {
207
- store.startResize(cellId);
208
-
209
- let previewMap = store.resizePreviewMap();
210
- expect(previewMap.size).toBe(1);
211
-
212
- store.updateResizePreview('horizontal', 1);
213
- previewMap = store.resizePreviewMap();
214
- expect(previewMap.size).toBe(2);
215
-
216
- store.updateResizePreview('vertical', 2);
217
- previewMap = store.resizePreviewMap();
218
- expect(previewMap.size).toBe(6);
219
- });
220
-
221
- it('should handle single cell map efficiently', () => {
222
- store.startResize(cellId);
223
-
224
- const previewMap = store.resizePreviewMap();
225
-
226
- expect(previewMap.size).toBe(1);
227
- expect(previewMap.has(CellIdUtils.create(8, 8))).toBe(true);
228
- });
229
-
230
- it('should be empty when widget is removed during resize', () => {
231
- store.startResize(cellId);
232
- store.updateResizePreview('horizontal', 2);
233
-
234
- const widget = store.cells().find(c => CellIdUtils.equals(c.cellId, cellId))!; store.removeWidget(widget.widgetId);
235
-
236
- expect(store.resizePreviewMap().size).toBe(0);
237
- });
238
- });
239
-
240
- describe('highlightedZones', () => {
241
- it('should return empty array when no drag data', () => {
242
- expect(store.highlightedZones()).toEqual([]);
243
- });
244
-
245
- it('should return empty array when no hovered drop zone', () => {
246
- const dragData: DragData = {
247
- kind: 'widget',
248
- content: testWidgetMetadata
249
- };
250
- store.startDrag(dragData);
251
-
252
- expect(store.highlightedZones()).toEqual([]);
253
- });
254
-
255
- it('should return empty array when both drag data and hovered zone are null', () => {
256
- expect(store.highlightedZones()).toEqual([]);
257
- });
258
-
259
- it('should calculate highlighted zones for widget drag (single cell)', () => {
260
- const dragData: DragData = {
261
- kind: 'widget',
262
- content: testWidgetMetadata
263
- };
264
- store.startDrag(dragData);
265
- store.setHoveredDropZone({ row: 5, col: 8 });
266
-
267
- const zones = store.highlightedZones();
268
-
269
- expect(zones.length).toBe(1);
270
- expect(zones[0]).toEqual({ row: 5, col: 8 });
271
- });
272
-
273
- it('should calculate highlighted zones for cell drag (multi-cell)', () => {
274
- const dragData: DragData = {
275
- kind: 'cell',
276
- content: {
277
- widgetId: WidgetIdUtils.generate(),
278
- cellId: CellIdUtils.create(3, 3),
279
- row: 3,
280
- col: 3,
281
- rowSpan: 2,
282
- colSpan: 3,
283
- }
284
- };
285
- store.startDrag(dragData);
286
- store.setHoveredDropZone({ row: 6, col: 7 });
287
-
288
- const zones = store.highlightedZones();
289
-
290
- expect(zones.length).toBe(6); // 2x3 grid
291
- expect(zones).toContain(jasmine.objectContaining({ row: 6, col: 7 }));
292
- expect(zones).toContain(jasmine.objectContaining({ row: 6, col: 8 }));
293
- expect(zones).toContain(jasmine.objectContaining({ row: 6, col: 9 }));
294
- expect(zones).toContain(jasmine.objectContaining({ row: 7, col: 7 }));
295
- expect(zones).toContain(jasmine.objectContaining({ row: 7, col: 8 }));
296
- expect(zones).toContain(jasmine.objectContaining({ row: 7, col: 9 }));
297
- });
298
-
299
- it('should update when hovered drop zone changes', () => {
300
- const dragData: DragData = {
301
- kind: 'cell',
302
- content: {
303
- widgetId: WidgetIdUtils.generate(),
304
- cellId: CellIdUtils.create(1, 1),
305
- row: 1,
306
- col: 1,
307
- rowSpan: 1,
308
- colSpan: 2,
309
- }
310
- };
311
- store.startDrag(dragData);
312
-
313
- store.setHoveredDropZone({ row: 3, col: 3 });
314
- let zones = store.highlightedZones();
315
- expect(zones).toEqual([
316
- { row: 3, col: 3 },
317
- { row: 3, col: 4 }
318
- ]);
319
-
320
- store.setHoveredDropZone({ row: 8, col: 10 });
321
- zones = store.highlightedZones();
322
- expect(zones).toEqual([
323
- { row: 8, col: 10 },
324
- { row: 8, col: 11 }
325
- ]);
326
- });
327
-
328
- it('should update when drag data changes', () => {
329
- store.setHoveredDropZone({ row: 5, col: 5 });
330
-
331
- const dragData1: DragData = {
332
- kind: 'widget',
333
- content: testWidgetMetadata
334
- };
335
- store.startDrag(dragData1);
336
- let zones = store.highlightedZones();
337
- expect(zones.length).toBe(1);
338
-
339
- const dragData2: DragData = {
340
- kind: 'cell',
341
- content: {
342
- widgetId: WidgetIdUtils.generate(),
343
- cellId: CellIdUtils.create(2, 2),
344
- row: 2,
345
- col: 2,
346
- rowSpan: 3,
347
- colSpan: 2,
348
- }
349
- };
350
- store.startDrag(dragData2);
351
- zones = store.highlightedZones();
352
- expect(zones.length).toBe(6); // 3x2 grid
353
- });
354
-
355
- it('should handle large cell drag spans', () => {
356
- const dragData: DragData = {
357
- kind: 'cell',
358
- content: {
359
- widgetId: WidgetIdUtils.generate(),
360
- cellId: CellIdUtils.create(1, 1),
361
- row: 1,
362
- col: 1,
363
- rowSpan: 4,
364
- colSpan: 5,
365
- }
366
- };
367
- store.startDrag(dragData);
368
- store.setHoveredDropZone({ row: 2, col: 3 });
369
-
370
- const zones = store.highlightedZones();
371
-
372
- expect(zones.length).toBe(20); // 4x5 grid
373
- expect(zones).toContain(jasmine.objectContaining({ row: 2, col: 3 })); // top-left
374
- expect(zones).toContain(jasmine.objectContaining({ row: 2, col: 7 })); // top-right
375
- expect(zones).toContain(jasmine.objectContaining({ row: 5, col: 3 })); // bottom-left
376
- expect(zones).toContain(jasmine.objectContaining({ row: 5, col: 7 })); // bottom-right
377
- });
378
-
379
- it('should clear when drag ends', () => {
380
- const dragData: DragData = {
381
- kind: 'widget',
382
- content: testWidgetMetadata
383
- };
384
- store.startDrag(dragData);
385
- store.setHoveredDropZone({ row: 5, col: 5 });
386
-
387
- expect(store.highlightedZones().length).toBe(1);
388
-
389
- store.endDrag();
390
-
391
- expect(store.highlightedZones()).toEqual([]);
392
- });
393
- });
394
-
395
- describe('highlightMap', () => {
396
- it('should return empty set when no highlighted zones', () => {
397
- expect(store.highlightMap().size).toBe(0);
398
- });
399
-
400
- it('should create map from highlighted zones for widget drag', () => {
401
- const dragData: DragData = {
402
- kind: 'widget',
403
- content: testWidgetMetadata
404
- };
405
- store.startDrag(dragData);
406
- store.setHoveredDropZone({ row: 7, col: 9 });
407
-
408
- const highlightMap = store.highlightMap();
409
-
410
- expect(highlightMap.size).toBe(1);
411
- expect(highlightMap.has(CellIdUtils.create(7, 9))).toBe(true);
412
- });
413
-
414
- it('should create map from highlighted zones for cell drag', () => {
415
- const dragData: DragData = {
416
- kind: 'cell',
417
- content: {
418
- widgetId: WidgetIdUtils.generate(),
419
- cellId: CellIdUtils.create(4, 4),
420
- row: 4,
421
- col: 4,
422
- rowSpan: 2,
423
- colSpan: 2,
424
- }
425
- };
426
- store.startDrag(dragData);
427
- store.setHoveredDropZone({ row: 8, col: 10 });
428
-
429
- const highlightMap = store.highlightMap();
430
-
431
- expect(highlightMap.size).toBe(4);
432
- expect(highlightMap.has(CellIdUtils.create(8, 10))).toBe(true);
433
- expect(highlightMap.has(CellIdUtils.create(8, 11))).toBe(true);
434
- expect(highlightMap.has(CellIdUtils.create(9, 10))).toBe(true);
435
- expect(highlightMap.has(CellIdUtils.create(9, 11))).toBe(true);
436
- });
437
-
438
- it('should not contain cells outside highlighted zones', () => {
439
- const dragData: DragData = {
440
- kind: 'cell',
441
- content: {
442
- widgetId: WidgetIdUtils.generate(),
443
- cellId: CellIdUtils.create(1, 1),
444
- row: 1,
445
- col: 1,
446
- rowSpan: 1,
447
- colSpan: 2,
448
- }
449
- };
450
- store.startDrag(dragData);
451
- store.setHoveredDropZone({ row: 5, col: 5 });
452
-
453
- const highlightMap = store.highlightMap();
454
-
455
- expect(highlightMap.has(CellIdUtils.create(5, 5))).toBe(true);
456
- expect(highlightMap.has(CellIdUtils.create(5, 6))).toBe(true);
457
- expect(highlightMap.has(CellIdUtils.create(5, 7))).toBe(false);
458
- expect(highlightMap.has(CellIdUtils.create(6, 5))).toBe(false);
459
- expect(highlightMap.has(CellIdUtils.create(4, 5))).toBe(false);
460
- });
461
-
462
- it('should update when highlighted zones change', () => {
463
- const dragData: DragData = {
464
- kind: 'widget',
465
- content: testWidgetMetadata
466
- };
467
- store.startDrag(dragData);
468
-
469
- store.setHoveredDropZone({ row: 3, col: 3 });
470
- let highlightMap = store.highlightMap();
471
- expect(highlightMap.size).toBe(1);
472
- expect(highlightMap.has(CellIdUtils.create(3, 3))).toBe(true);
473
-
474
- store.setHoveredDropZone({ row: 7, col: 12 });
475
- highlightMap = store.highlightMap();
476
- expect(highlightMap.size).toBe(1);
477
- expect(highlightMap.has(CellIdUtils.create(3, 3))).toBe(false);
478
- expect(highlightMap.has(CellIdUtils.create(7, 12))).toBe(true);
479
- });
480
-
481
- it('should handle large highlighted zones efficiently', () => {
482
- const dragData: DragData = {
483
- kind: 'cell',
484
- content: {
485
- widgetId: WidgetIdUtils.generate(),
486
- cellId: CellIdUtils.create(1, 1),
487
- row: 1,
488
- col: 1,
489
- rowSpan: 5,
490
- colSpan: 4,
491
- }
492
- };
493
- store.startDrag(dragData);
494
- store.setHoveredDropZone({ row: 2, col: 2 });
495
-
496
- const highlightMap = store.highlightMap();
497
-
498
- expect(highlightMap.size).toBe(20); // 5x4 grid
499
- expect(highlightMap.has(CellIdUtils.create(2, 2))).toBe(true);
500
- expect(highlightMap.has(CellIdUtils.create(6, 5))).toBe(true);
501
- expect(highlightMap.has(CellIdUtils.create(1, 1))).toBe(false);
502
- expect(highlightMap.has(CellIdUtils.create(7, 6))).toBe(false);
503
- });
504
-
505
- it('should be empty when drag ends', () => {
506
- const dragData: DragData = {
507
- kind: 'widget',
508
- content: testWidgetMetadata
509
- };
510
- store.startDrag(dragData);
511
- store.setHoveredDropZone({ row: 5, col: 5 });
512
-
513
- expect(store.highlightMap().size).toBe(1);
514
-
515
- store.endDrag();
516
-
517
- expect(store.highlightMap().size).toBe(0);
518
- });
519
- });
520
-
521
- describe('invalidHighlightMap', () => {
522
- let widget1: CellData;
523
- let widget2: CellData;
524
-
525
- beforeEach(() => {
526
- // Add some existing widgets to create collision scenarios
527
- widget1 = {
528
- widgetId: WidgetIdUtils.generate(),
529
- cellId: CellIdUtils.create(3, 3),
530
- row: 3,
531
- col: 3,
532
- rowSpan: 2,
533
- colSpan: 2,
534
- widgetFactory: mockWidgetFactory,
535
- widgetState: {},
536
- };
537
- widget2 = {
538
- widgetId: WidgetIdUtils.generate(),
539
- cellId: CellIdUtils.create(8, 8),
540
- row: 8,
541
- col: 8,
542
- rowSpan: 1,
543
- colSpan: 3,
544
- widgetFactory: mockWidgetFactory,
545
- widgetState: {},
546
- };
547
- store.addWidget(widget1);
548
- store.addWidget(widget2);
549
- });
550
-
551
- it('should return empty set when no drag data', () => {
552
- expect(store.invalidHighlightMap().size).toBe(0);
553
- });
554
-
555
- it('should return empty set when no hovered drop zone', () => {
556
- const dragData: DragData = {
557
- kind: 'widget',
558
- content: testWidgetMetadata
559
- };
560
- store.startDrag(dragData);
561
-
562
- expect(store.invalidHighlightMap().size).toBe(0);
563
- });
564
-
565
- it('should return empty set for valid placement', () => {
566
- const dragData: DragData = {
567
- kind: 'widget',
568
- content: testWidgetMetadata
569
- };
570
- store.startDrag(dragData);
571
- store.setHoveredDropZone({ row: 1, col: 1 }); // Empty space
572
-
573
- expect(store.invalidHighlightMap().size).toBe(0);
574
- });
575
-
576
- it('should return invalid cells for collision with existing widget', () => {
577
- const dragData: DragData = {
578
- kind: 'widget',
579
- content: testWidgetMetadata
580
- };
581
- store.startDrag(dragData);
582
- store.setHoveredDropZone({ row: 3, col: 3 }); // Collides with widget1
583
-
584
- const invalidMap = store.invalidHighlightMap();
585
-
586
- expect(invalidMap.size).toBe(1);
587
- expect(invalidMap.has(CellIdUtils.create(3, 3))).toBe(true);
588
- });
589
-
590
- it('should return invalid cells for partial collision', () => {
591
- const dragData: DragData = {
592
- kind: 'cell',
593
- content: {
594
- widgetId: WidgetIdUtils.generate(),
595
- cellId: CellIdUtils.create(1, 1),
596
- row: 1,
597
- col: 1,
598
- rowSpan: 2,
599
- colSpan: 2,
600
- }
601
- };
602
- store.startDrag(dragData);
603
- store.setHoveredDropZone({ row: 2, col: 2 }); // Partially overlaps widget1
604
-
605
- const invalidMap = store.invalidHighlightMap();
606
-
607
- expect(invalidMap.size).toBe(4); // All cells in the 2x2 footprint
608
- expect(invalidMap.has(CellIdUtils.create(2, 2))).toBe(true);
609
- expect(invalidMap.has(CellIdUtils.create(2, 3))).toBe(true);
610
- expect(invalidMap.has(CellIdUtils.create(3, 2))).toBe(true);
611
- expect(invalidMap.has(CellIdUtils.create(3, 3))).toBe(true);
612
- });
613
-
614
- it('should return invalid cells for out of bounds placement', () => {
615
- const dragData: DragData = {
616
- kind: 'widget',
617
- content: testWidgetMetadata
618
- };
619
- store.startDrag(dragData);
620
- store.setHoveredDropZone({ row: 17, col: 5 }); // Row out of bounds
621
-
622
- const invalidMap = store.invalidHighlightMap();
623
-
624
- expect(invalidMap.size).toBe(1);
625
- expect(invalidMap.has(CellIdUtils.create(17, 5))).toBe(true);
626
- });
627
-
628
- it('should return invalid cells for multi-cell out of bounds', () => {
629
- const dragData: DragData = {
630
- kind: 'cell',
631
- content: {
632
- widgetId: WidgetIdUtils.generate(),
633
- cellId: CellIdUtils.create(1, 1),
634
- row: 1,
635
- col: 1,
636
- rowSpan: 2,
637
- colSpan: 3,
638
- }
639
- };
640
- store.startDrag(dragData);
641
- store.setHoveredDropZone({ row: 15, col: 15 }); // Spans out of bounds
642
-
643
- const invalidMap = store.invalidHighlightMap();
644
-
645
- expect(invalidMap.size).toBe(6); // All cells in the 2x3 footprint
646
- expect(invalidMap.has(CellIdUtils.create(15, 15))).toBe(true);
647
- expect(invalidMap.has(CellIdUtils.create(15, 16))).toBe(true);
648
- expect(invalidMap.has(CellIdUtils.create(15, 17))).toBe(true); // Out of bounds
649
- expect(invalidMap.has(CellIdUtils.create(16, 15))).toBe(true);
650
- expect(invalidMap.has(CellIdUtils.create(16, 16))).toBe(true);
651
- expect(invalidMap.has(CellIdUtils.create(16, 17))).toBe(true); // Out of bounds
652
- });
653
-
654
- it('should exclude self when moving existing cell', () => {
655
- const cellId = CellIdUtils.create(3, 3);
656
- const dragData: DragData = {
657
- kind: 'cell',
658
- content: {
659
- widgetId: widget1.widgetId, // Use the SAME widgetId from beforeEach
660
- cellId,
661
- row: 3,
662
- col: 3,
663
- rowSpan: 2,
664
- colSpan: 2,
665
- }
666
- };
667
- store.startDrag(dragData);
668
- store.setHoveredDropZone({ row: 4, col: 4 }); // Partial self-overlap
669
-
670
- const invalidMap = store.invalidHighlightMap();
671
-
672
- expect(invalidMap.size).toBe(0); // Self-overlap should be allowed
673
- });
674
-
675
- it('should detect collision when moving cell to occupied space', () => {
676
- const cellId = CellIdUtils.create(3, 3);
677
- const dragData: DragData = {
678
- kind: 'cell',
679
- content: {
680
- widgetId: WidgetIdUtils.generate(),
681
- cellId,
682
- row: 3,
683
- col: 3,
684
- rowSpan: 2,
685
- colSpan: 2,
686
- }
687
- };
688
- store.startDrag(dragData);
689
- store.setHoveredDropZone({ row: 8, col: 8 }); // Collides with widget2
690
-
691
- const invalidMap = store.invalidHighlightMap();
692
-
693
- expect(invalidMap.size).toBe(4); // All cells in the 2x2 footprint
694
- expect(invalidMap.has(CellIdUtils.create(8, 8))).toBe(true);
695
- expect(invalidMap.has(CellIdUtils.create(8, 9))).toBe(true);
696
- expect(invalidMap.has(CellIdUtils.create(9, 8))).toBe(true);
697
- expect(invalidMap.has(CellIdUtils.create(9, 9))).toBe(true);
698
- });
699
-
700
- it('should update when hovered zone changes from valid to invalid', () => {
701
- const dragData: DragData = {
702
- kind: 'widget',
703
- content: testWidgetMetadata
704
- };
705
- store.startDrag(dragData);
706
-
707
- // Valid placement
708
- store.setHoveredDropZone({ row: 1, col: 1 });
709
- expect(store.invalidHighlightMap().size).toBe(0);
710
-
711
- // Invalid placement
712
- store.setHoveredDropZone({ row: 3, col: 3 });
713
- expect(store.invalidHighlightMap().size).toBe(1);
714
-
715
- // Valid placement again
716
- store.setHoveredDropZone({ row: 6, col: 6 });
717
- expect(store.invalidHighlightMap().size).toBe(0);
718
- });
719
-
720
- it('should handle complex collision scenarios', () => {
721
- const dragData: DragData = {
722
- kind: 'cell',
723
- content: {
724
- widgetId: WidgetIdUtils.generate(),
725
- cellId: CellIdUtils.create(1, 1),
726
- row: 1,
727
- col: 1,
728
- rowSpan: 3,
729
- colSpan: 4,
730
- }
731
- };
732
- store.startDrag(dragData);
733
- store.setHoveredDropZone({ row: 2, col: 2 }); // Large widget overlapping both existing widgets
734
-
735
- const invalidMap = store.invalidHighlightMap();
736
-
737
- expect(invalidMap.size).toBe(12); // All cells in the 3x4 footprint are invalid
738
- expect(invalidMap.has(CellIdUtils.create(2, 2))).toBe(true);
739
- expect(invalidMap.has(CellIdUtils.create(4, 5))).toBe(true);
740
- });
741
-
742
- it('should be empty when drag ends', () => {
743
- const dragData: DragData = {
744
- kind: 'widget',
745
- content: testWidgetMetadata
746
- };
747
- store.startDrag(dragData);
748
- store.setHoveredDropZone({ row: 3, col: 3 }); // Invalid placement
749
-
750
- expect(store.invalidHighlightMap().size).toBe(1);
751
-
752
- store.endDrag();
753
-
754
- expect(store.invalidHighlightMap().size).toBe(0);
755
- });
756
- });
757
-
758
- describe('isValidPlacement', () => {
759
- let existingWidget: CellData;
760
-
761
- beforeEach(() => {
762
- // Add existing widget for collision testing
763
- existingWidget = {
764
- widgetId: WidgetIdUtils.generate(),
765
- cellId: CellIdUtils.create(5, 5),
766
- row: 5,
767
- col: 5,
768
- rowSpan: 2,
769
- colSpan: 2,
770
- widgetFactory: mockWidgetFactory,
771
- widgetState: {},
772
- };
773
- store.addWidget(existingWidget);
774
- });
775
-
776
- it('should return true when no drag data', () => {
777
- expect(store.isValidPlacement()).toBe(true);
778
- });
779
-
780
- it('should return true when no hovered drop zone', () => {
781
- const dragData: DragData = {
782
- kind: 'widget',
783
- content: testWidgetMetadata
784
- };
785
- store.startDrag(dragData);
786
-
787
- expect(store.isValidPlacement()).toBe(true);
788
- });
789
-
790
- it('should return true for valid placement', () => {
791
- const dragData: DragData = {
792
- kind: 'widget',
793
- content: testWidgetMetadata
794
- };
795
- store.startDrag(dragData);
796
- store.setHoveredDropZone({ row: 1, col: 1 }); // Empty space
797
-
798
- expect(store.isValidPlacement()).toBe(true);
799
- });
800
-
801
- it('should return false for collision', () => {
802
- const dragData: DragData = {
803
- kind: 'widget',
804
- content: testWidgetMetadata
805
- };
806
- store.startDrag(dragData);
807
- store.setHoveredDropZone({ row: 5, col: 5 }); // Collides with existing widget
808
-
809
- expect(store.isValidPlacement()).toBe(false);
810
- });
811
-
812
- it('should return false for out of bounds', () => {
813
- const dragData: DragData = {
814
- kind: 'widget',
815
- content: testWidgetMetadata
816
- };
817
- store.startDrag(dragData);
818
- store.setHoveredDropZone({ row: 17, col: 5 }); // Row out of bounds
819
-
820
- expect(store.isValidPlacement()).toBe(false);
821
- });
822
-
823
- it('should return true for self-overlap when moving existing cell', () => {
824
- const cellId = CellIdUtils.create(5, 5);
825
- const dragData: DragData = {
826
- kind: 'cell',
827
- content: {
828
- widgetId: existingWidget.widgetId, // Use the SAME widgetId from beforeEach
829
- cellId,
830
- row: 5,
831
- col: 5,
832
- rowSpan: 2,
833
- colSpan: 2,
834
- }
835
- };
836
- store.startDrag(dragData);
837
- store.setHoveredDropZone({ row: 6, col: 6 }); // Partial self-overlap
838
-
839
- expect(store.isValidPlacement()).toBe(true);
840
- });
841
-
842
- it('should return false for collision when moving cell to occupied space', () => {
843
- // Add another widget
844
- const widget2: CellData = {
845
- widgetId: WidgetIdUtils.generate(),
846
- cellId: CellIdUtils.create(10, 10),
847
- row: 10,
848
- col: 10,
849
- rowSpan: 1,
850
- colSpan: 1,
851
- widgetFactory: mockWidgetFactory,
852
- widgetState: {},
853
- };
854
- store.addWidget(widget2);
855
-
856
- const cellId = CellIdUtils.create(5, 5);
857
- const dragData: DragData = {
858
- kind: 'cell',
859
- content: {
860
- widgetId: WidgetIdUtils.generate(),
861
- cellId,
862
- row: 5,
863
- col: 5,
864
- rowSpan: 2,
865
- colSpan: 2,
866
- }
867
- };
868
- store.startDrag(dragData);
869
- store.setHoveredDropZone({ row: 10, col: 10 }); // Collides with widget2
870
-
871
- expect(store.isValidPlacement()).toBe(false);
872
- });
873
-
874
- it('should update reactively when placement validity changes', () => {
875
- const dragData: DragData = {
876
- kind: 'widget',
877
- content: testWidgetMetadata
878
- };
879
- store.startDrag(dragData);
880
-
881
- store.setHoveredDropZone({ row: 1, col: 1 });
882
- expect(store.isValidPlacement()).toBe(true);
883
-
884
- store.setHoveredDropZone({ row: 5, col: 5 });
885
- expect(store.isValidPlacement()).toBe(false);
886
-
887
- store.setHoveredDropZone({ row: 8, col: 8 });
888
- expect(store.isValidPlacement()).toBe(true);
889
- });
890
- });
891
-
892
- describe('computed properties integration', () => {
893
- it('should maintain consistency between highlight maps and zones', () => {
894
- const dragData: DragData = {
895
- kind: 'cell',
896
- content: {
897
- widgetId: WidgetIdUtils.generate(),
898
- cellId: CellIdUtils.create(1, 1),
899
- row: 1,
900
- col: 1,
901
- rowSpan: 2,
902
- colSpan: 3,
903
- }
904
- };
905
- store.startDrag(dragData);
906
- store.setHoveredDropZone({ row: 3, col: 4 });
907
-
908
- const zones = store.highlightedZones();
909
- const highlightMap = store.highlightMap();
910
-
911
- expect(zones.length).toBe(highlightMap.size);
912
-
913
- zones.forEach(zone => {
914
- expect(highlightMap.has(CellIdUtils.create(zone.row, zone.col))).toBe(true);
915
- });
916
- });
917
-
918
- it('should maintain consistency between resize preview cells and map', () => {
919
- const cellId = CellIdUtils.create(4, 4);
920
- const cell: CellData = {
921
- widgetId: WidgetIdUtils.generate(),
922
- cellId,
923
- row: 4,
924
- col: 4,
925
- rowSpan: 2,
926
- colSpan: 3,
927
- widgetFactory: mockWidgetFactory,
928
- widgetState: {},
929
- };
930
- store.addWidget(cell);
931
- store.startResize(cellId);
932
- store.updateResizePreview('horizontal', 1);
933
-
934
- const previewCells = store.resizePreviewCells();
935
- const previewMap = store.resizePreviewMap();
936
-
937
- expect(previewCells.length).toBe(previewMap.size);
938
-
939
- previewCells.forEach(cell => {
940
- expect(previewMap.has(CellIdUtils.create(cell.row, cell.col))).toBe(true);
941
- });
942
- });
943
-
944
- it('should handle simultaneous drag and resize operations', () => {
945
- // Start resize
946
- const cellId = CellIdUtils.create(3, 3);
947
- const cell: CellData = {
948
- widgetId: WidgetIdUtils.generate(),
949
- cellId,
950
- row: 3,
951
- col: 3,
952
- rowSpan: 1,
953
- colSpan: 1,
954
- widgetFactory: mockWidgetFactory,
955
- widgetState: {},
956
- };
957
- store.addWidget(cell);
958
- store.startResize(cellId);
959
-
960
- // Start drag
961
- const dragData: DragData = {
962
- kind: 'widget',
963
- content: testWidgetMetadata
964
- };
965
- store.startDrag(dragData);
966
- store.setHoveredDropZone({ row: 8, col: 8 });
967
-
968
- // Both should work independently
969
- expect(store.resizePreviewCells().length).toBe(1);
970
- expect(store.highlightedZones().length).toBe(1);
971
- expect(store.isValidPlacement()).toBe(true);
972
- });
973
- });
974
- });