@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.
Files changed (78) hide show
  1. package/ng-package.json +7 -0
  2. package/package.json +34 -45
  3. package/src/lib/__tests__/dashboard-component-widget-state-integration.spec.ts +537 -0
  4. package/src/lib/cell/__tests__/cell-resize.component.spec.ts +442 -0
  5. package/src/lib/cell/__tests__/cell.component.spec.ts +541 -0
  6. package/src/lib/cell/cell-context-menu.component.ts +138 -0
  7. package/src/lib/cell/cell-context-menu.service.ts +36 -0
  8. package/src/lib/cell/cell.component.html +37 -0
  9. package/src/lib/cell/cell.component.scss +198 -0
  10. package/src/lib/cell/cell.component.ts +375 -0
  11. package/src/lib/dashboard/dashboard.component.html +18 -0
  12. package/src/lib/dashboard/dashboard.component.scss +17 -0
  13. package/src/lib/dashboard/dashboard.component.ts +187 -0
  14. package/src/lib/dashboard-editor/dashboard-editor.component.html +57 -0
  15. package/src/lib/dashboard-editor/dashboard-editor.component.scss +87 -0
  16. package/src/lib/dashboard-editor/dashboard-editor.component.ts +219 -0
  17. package/src/lib/dashboard-viewer/__tests__/dashboard-viewer.component.spec.ts +258 -0
  18. package/src/lib/dashboard-viewer/dashboard-viewer.component.html +20 -0
  19. package/src/lib/dashboard-viewer/dashboard-viewer.component.scss +50 -0
  20. package/src/lib/dashboard-viewer/dashboard-viewer.component.ts +70 -0
  21. package/src/lib/drop-zone/__tests__/drop-zone.component.spec.ts +465 -0
  22. package/src/lib/drop-zone/drop-zone.component.html +20 -0
  23. package/src/lib/drop-zone/drop-zone.component.scss +67 -0
  24. package/src/lib/drop-zone/drop-zone.component.ts +122 -0
  25. package/src/lib/internal-widgets/unknown-widget/unknown-widget.component.ts +72 -0
  26. package/src/lib/models/cell-data.ts +13 -0
  27. package/src/lib/models/cell-dialog.ts +7 -0
  28. package/src/lib/models/cell-id.ts +85 -0
  29. package/src/lib/models/cell-position.ts +15 -0
  30. package/src/lib/models/dashboard-data.dto.ts +44 -0
  31. package/src/lib/models/dashboard-data.utils.ts +49 -0
  32. package/src/lib/models/drag-data.ts +6 -0
  33. package/src/lib/models/index.ts +11 -0
  34. package/src/lib/models/reserved-space.ts +24 -0
  35. package/src/lib/models/widget-factory.ts +33 -0
  36. package/src/lib/models/widget-id.ts +70 -0
  37. package/src/lib/models/widget.ts +21 -0
  38. package/src/lib/providers/cell-settings-dialog/cell-settings-dialog.component.ts +127 -0
  39. package/src/lib/providers/cell-settings-dialog/cell-settings-dialog.provider.ts +15 -0
  40. package/src/lib/providers/cell-settings-dialog/cell-settings-dialog.tokens.ts +20 -0
  41. package/src/lib/providers/cell-settings-dialog/default-cell-settings-dialog.provider.ts +32 -0
  42. package/src/lib/providers/cell-settings-dialog/index.ts +3 -0
  43. package/src/lib/providers/index.ts +1 -0
  44. package/src/lib/services/__tests__/dashboard-bridge.service.spec.ts +220 -0
  45. package/src/lib/services/__tests__/dashboard-viewport.service.spec.ts +362 -0
  46. package/src/lib/services/dashboard-bridge.service.ts +155 -0
  47. package/src/lib/services/dashboard-viewport.service.ts +148 -0
  48. package/src/lib/services/dashboard.service.ts +62 -0
  49. package/src/lib/store/__tests__/dashboard-store-collision-detection.spec.ts +756 -0
  50. package/src/lib/store/__tests__/dashboard-store-computed-properties.spec.ts +974 -0
  51. package/src/lib/store/__tests__/dashboard-store-drag-drop.spec.ts +279 -0
  52. package/src/lib/store/__tests__/dashboard-store-export-import.spec.ts +780 -0
  53. package/src/lib/store/__tests__/dashboard-store-grid-config.spec.ts +128 -0
  54. package/src/lib/store/__tests__/dashboard-store-query-methods.spec.ts +229 -0
  55. package/src/lib/store/__tests__/dashboard-store-resize-operations.spec.ts +652 -0
  56. package/src/lib/store/__tests__/dashboard-store-widget-management.spec.ts +461 -0
  57. package/src/lib/store/__tests__/dashboard-store-widget-state-preservation.spec.ts +369 -0
  58. package/src/lib/store/dashboard-store.ts +239 -0
  59. package/src/lib/store/features/drag-drop.feature.ts +140 -0
  60. package/src/lib/store/features/grid-config.feature.ts +43 -0
  61. package/src/lib/store/features/resize.feature.ts +140 -0
  62. package/src/lib/store/features/utils/collision.utils.ts +89 -0
  63. package/src/lib/store/features/utils/grid-query-internal.utils.ts +37 -0
  64. package/src/lib/store/features/utils/resize.utils.ts +165 -0
  65. package/src/lib/store/features/widget-management.feature.ts +158 -0
  66. package/src/lib/styles/_dashboard-grid-vars.scss +11 -0
  67. package/src/lib/widget-list/__tests__/widget-list-bridge-integration.spec.ts +137 -0
  68. package/src/lib/widget-list/widget-list.component.html +22 -0
  69. package/src/lib/widget-list/widget-list.component.scss +154 -0
  70. package/src/lib/widget-list/widget-list.component.ts +106 -0
  71. package/src/public-api.ts +21 -0
  72. package/src/test-setup.ts +10 -0
  73. package/tsconfig.lib.json +15 -0
  74. package/tsconfig.lib.prod.json +11 -0
  75. package/tsconfig.spec.json +14 -0
  76. package/fesm2022/dragonworks-ngx-dashboard.mjs +0 -2178
  77. package/fesm2022/dragonworks-ngx-dashboard.mjs.map +0 -1
  78. package/index.d.ts +0 -678
@@ -0,0 +1,442 @@
1
+ import { ComponentFixture, TestBed } from '@angular/core/testing';
2
+ import { ViewContainerRef, ElementRef, Renderer2 } from '@angular/core';
3
+ import { CellComponent } from '../cell.component';
4
+ import { DashboardStore } from '../../store/dashboard-store';
5
+ import { DashboardService } from '../../services/dashboard.service';
6
+ import { CellContextMenuService } from '../cell-context-menu.service';
7
+ import { CELL_SETTINGS_DIALOG_PROVIDER } from '../../providers/cell-settings-dialog';
8
+ import { CellId, CellIdUtils, WidgetId, WidgetIdUtils, WidgetFactory } from '../../models';
9
+ import { Component } from '@angular/core';
10
+
11
+ // Mock test widget component
12
+ @Component({
13
+ selector: 'lib-test-widget',
14
+ template: '<div>Test Widget</div>',
15
+ standalone: true,
16
+ })
17
+ class TestWidgetComponent {
18
+ dashboardGetState() {
19
+ return {};
20
+ }
21
+ dashboardSetState(state: any) {}
22
+ dashboardEditState() {}
23
+ }
24
+
25
+ describe('CellComponent - Resize Functionality', () => {
26
+ let component: CellComponent;
27
+ let fixture: ComponentFixture<CellComponent>;
28
+ let store: InstanceType<typeof DashboardStore>;
29
+ let realRenderer: Renderer2;
30
+ let mockDashboardService: jasmine.SpyObj<DashboardService>;
31
+ let mockContextMenuService: jasmine.SpyObj<CellContextMenuService>;
32
+ let mockDialogProvider: jasmine.SpyObj<any>;
33
+
34
+ const mockCellId: CellId = CellIdUtils.create(1, 1);
35
+ const mockWidgetId: WidgetId = WidgetIdUtils.generate();
36
+ const otherCellId: CellId = CellIdUtils.create(2, 2);
37
+
38
+ const mockWidgetFactory: WidgetFactory = {
39
+ widgetTypeid: 'test-widget',
40
+ name: 'Test Widget',
41
+ description: 'A test widget',
42
+ svgIcon: '<svg><rect width="10" height="10"/></svg>',
43
+ createInstance: jasmine
44
+ .createSpy('createInstance')
45
+ .and.callFake((container: ViewContainerRef, state?: unknown) => {
46
+ const componentRef = container.createComponent(TestWidgetComponent);
47
+ return componentRef;
48
+ }),
49
+ };
50
+
51
+ beforeEach(async () => {
52
+ mockDashboardService = jasmine.createSpyObj('DashboardService', [
53
+ 'getFactory',
54
+ ]);
55
+
56
+ mockContextMenuService = jasmine.createSpyObj('CellContextMenuService', [
57
+ 'show',
58
+ 'hide',
59
+ ]);
60
+
61
+ mockDialogProvider = jasmine.createSpyObj('CellSettingsDialogProvider', [
62
+ 'openCellSettings',
63
+ ]);
64
+
65
+ await TestBed.configureTestingModule({
66
+ imports: [CellComponent, TestWidgetComponent],
67
+ providers: [
68
+ DashboardStore,
69
+ { provide: DashboardService, useValue: mockDashboardService },
70
+ { provide: CellContextMenuService, useValue: mockContextMenuService },
71
+ {
72
+ provide: CELL_SETTINGS_DIALOG_PROVIDER,
73
+ useValue: mockDialogProvider,
74
+ },
75
+ ],
76
+ }).compileComponents();
77
+
78
+ store = TestBed.inject(DashboardStore);
79
+ fixture = TestBed.createComponent(CellComponent);
80
+ component = fixture.componentInstance;
81
+
82
+ mockDashboardService.getFactory.and.returnValue(mockWidgetFactory);
83
+
84
+ // Setup component inputs
85
+ fixture.componentRef.setInput('widgetId', mockWidgetId);
86
+ fixture.componentRef.setInput('cellId', mockCellId);
87
+ fixture.componentRef.setInput('row', 2);
88
+ fixture.componentRef.setInput('column', 3);
89
+ fixture.detectChanges();
90
+
91
+ // Get the real renderer from the component's injector
92
+ realRenderer = fixture.debugElement.injector.get(Renderer2);
93
+ spyOn(realRenderer, 'listen').and.returnValue(() => {});
94
+ spyOn(realRenderer, 'addClass').and.callThrough();
95
+ spyOn(realRenderer, 'removeClass').and.callThrough();
96
+ });
97
+
98
+ describe('Resize Start - Public API Behavior', () => {
99
+ let mockMouseEvent: any;
100
+
101
+ beforeEach(() => {
102
+ mockMouseEvent = {
103
+ clientX: 150,
104
+ clientY: 200,
105
+ preventDefault: jasmine.createSpy('preventDefault'),
106
+ stopPropagation: jasmine.createSpy('stopPropagation'),
107
+ } as Partial<MouseEvent>;
108
+
109
+ store.setGridCellDimensions(100, 80);
110
+ });
111
+
112
+ it('should emit resize start event with correct data', () => {
113
+ spyOn(component.resizeStart, 'emit');
114
+
115
+ component.onResizeStart(mockMouseEvent as MouseEvent, 'horizontal');
116
+
117
+ expect(component.resizeStart.emit).toHaveBeenCalledWith({
118
+ cellId: mockCellId,
119
+ direction: 'horizontal',
120
+ });
121
+ });
122
+
123
+ it('should emit resize start event for vertical direction', () => {
124
+ spyOn(component.resizeStart, 'emit');
125
+
126
+ component.onResizeStart(mockMouseEvent as MouseEvent, 'vertical');
127
+
128
+ expect(component.resizeStart.emit).toHaveBeenCalledWith({
129
+ cellId: mockCellId,
130
+ direction: 'vertical',
131
+ });
132
+ });
133
+
134
+ it('should prevent default event behavior', () => {
135
+ component.onResizeStart(mockMouseEvent as MouseEvent, 'horizontal');
136
+
137
+ expect(mockMouseEvent.preventDefault).toHaveBeenCalled();
138
+ expect(mockMouseEvent.stopPropagation).toHaveBeenCalled();
139
+ });
140
+
141
+ it('should add horizontal cursor class', () => {
142
+ component.onResizeStart(mockMouseEvent as MouseEvent, 'horizontal');
143
+
144
+ expect(realRenderer.addClass).toHaveBeenCalledWith(
145
+ document.body,
146
+ 'cursor-col-resize'
147
+ );
148
+ });
149
+
150
+ it('should add vertical cursor class', () => {
151
+ component.onResizeStart(mockMouseEvent as MouseEvent, 'vertical');
152
+
153
+ expect(realRenderer.addClass).toHaveBeenCalledWith(
154
+ document.body,
155
+ 'cursor-row-resize'
156
+ );
157
+ });
158
+
159
+ it('should setup document event listeners', () => {
160
+ component.onResizeStart(mockMouseEvent as MouseEvent, 'horizontal');
161
+
162
+ expect(realRenderer.listen).toHaveBeenCalledWith(
163
+ 'document',
164
+ 'mousemove',
165
+ jasmine.any(Function)
166
+ );
167
+ expect(realRenderer.listen).toHaveBeenCalledWith(
168
+ 'document',
169
+ 'mouseup',
170
+ jasmine.any(Function)
171
+ );
172
+ });
173
+ });
174
+
175
+ describe('Resize Move - Public API Behavior', () => {
176
+ beforeEach(() => {
177
+ store.setGridCellDimensions(100, 80);
178
+
179
+ // Setup initial resize state
180
+ const startEvent = {
181
+ clientX: 150,
182
+ clientY: 200,
183
+ preventDefault: jasmine.createSpy('preventDefault'),
184
+ stopPropagation: jasmine.createSpy('stopPropagation'),
185
+ } as Partial<MouseEvent>;
186
+
187
+ component.onResizeStart(startEvent as MouseEvent, 'horizontal');
188
+ });
189
+
190
+ it('should emit resize move event with correct horizontal delta', () => {
191
+ spyOn(component.resizeMove, 'emit');
192
+
193
+ // Simulate mouse move 100px right (1 cell width)
194
+ const moveEvent = { clientX: 250, clientY: 200 };
195
+ (component as any).handleResizeMove(moveEvent);
196
+
197
+ expect(component.resizeMove.emit).toHaveBeenCalledWith({
198
+ cellId: mockCellId,
199
+ direction: 'horizontal',
200
+ delta: 1,
201
+ });
202
+ });
203
+
204
+ it('should emit resize move event with correct vertical delta', () => {
205
+ // Setup vertical resize
206
+ const startEvent = {
207
+ clientX: 150,
208
+ clientY: 200,
209
+ preventDefault: jasmine.createSpy('preventDefault'),
210
+ stopPropagation: jasmine.createSpy('stopPropagation'),
211
+ } as Partial<MouseEvent>;
212
+
213
+ component.onResizeStart(startEvent as MouseEvent, 'vertical');
214
+ spyOn(component.resizeMove, 'emit');
215
+
216
+ // Simulate mouse move 160px down (2 cell heights)
217
+ const moveEvent = { clientX: 150, clientY: 360 };
218
+ (component as any).handleResizeMove(moveEvent);
219
+
220
+ expect(component.resizeMove.emit).toHaveBeenCalledWith({
221
+ cellId: mockCellId,
222
+ direction: 'vertical',
223
+ delta: 2,
224
+ });
225
+ });
226
+
227
+ it('should handle negative delta correctly', () => {
228
+ spyOn(component.resizeMove, 'emit');
229
+
230
+ // Simulate mouse move 100px left (-1 cell width)
231
+ const moveEvent = { clientX: 50, clientY: 200 };
232
+ (component as any).handleResizeMove(moveEvent);
233
+
234
+ expect(component.resizeMove.emit).toHaveBeenCalledWith({
235
+ cellId: mockCellId,
236
+ direction: 'horizontal',
237
+ delta: -1,
238
+ });
239
+ });
240
+ });
241
+
242
+ describe('Resize End - Public API Behavior', () => {
243
+ beforeEach(() => {
244
+ const startEvent = {
245
+ clientX: 150,
246
+ clientY: 200,
247
+ preventDefault: jasmine.createSpy('preventDefault'),
248
+ stopPropagation: jasmine.createSpy('stopPropagation'),
249
+ } as Partial<MouseEvent>;
250
+
251
+ component.onResizeStart(startEvent as MouseEvent, 'horizontal');
252
+ });
253
+
254
+ it('should emit resize end event', () => {
255
+ spyOn(component.resizeEnd, 'emit');
256
+
257
+ (component as any).handleResizeEnd();
258
+
259
+ expect(component.resizeEnd.emit).toHaveBeenCalledWith({
260
+ cellId: mockCellId,
261
+ apply: true,
262
+ });
263
+ });
264
+
265
+ it('should remove cursor classes', () => {
266
+ (component as any).handleResizeEnd();
267
+
268
+ expect(realRenderer.removeClass).toHaveBeenCalledWith(
269
+ document.body,
270
+ 'cursor-col-resize'
271
+ );
272
+ expect(realRenderer.removeClass).toHaveBeenCalledWith(
273
+ document.body,
274
+ 'cursor-row-resize'
275
+ );
276
+ });
277
+ });
278
+
279
+ describe('Store Integration - Public Behavior', () => {
280
+ beforeEach(() => {
281
+ store.setGridCellDimensions(100, 80);
282
+ });
283
+
284
+ it('should access grid cell dimensions from store', () => {
285
+ expect(component.gridCellDimensions).toBeDefined();
286
+ expect(typeof component.gridCellDimensions).toBe('function');
287
+
288
+ const dimensions = component.gridCellDimensions();
289
+ expect(dimensions).toEqual({ width: 100, height: 80 });
290
+ });
291
+ });
292
+
293
+ describe('Edge Cases - Public Behavior', () => {
294
+ let mockMouseEvent: any;
295
+
296
+ beforeEach(() => {
297
+ store.setGridCellDimensions(100, 80);
298
+
299
+ mockMouseEvent = {
300
+ clientX: 150,
301
+ clientY: 200,
302
+ preventDefault: jasmine.createSpy('preventDefault'),
303
+ stopPropagation: jasmine.createSpy('stopPropagation'),
304
+ } as Partial<MouseEvent>;
305
+ });
306
+
307
+ it('should handle invalid direction gracefully', () => {
308
+ spyOn(component.resizeStart, 'emit');
309
+
310
+ expect(() => {
311
+ component.onResizeStart(mockMouseEvent, 'invalid' as any);
312
+ }).not.toThrow();
313
+
314
+ expect(component.resizeStart.emit).toHaveBeenCalledWith({
315
+ cellId: mockCellId,
316
+ direction: jasmine.any(String),
317
+ });
318
+ });
319
+
320
+ it('should handle extreme coordinates', () => {
321
+ component.onResizeStart(mockMouseEvent as MouseEvent, 'horizontal');
322
+ spyOn(component.resizeMove, 'emit');
323
+
324
+ // Test with very large coordinates
325
+ (component as any).handleResizeMove({ clientX: 999999, clientY: 999999 });
326
+
327
+ expect(component.resizeMove.emit).toHaveBeenCalledWith({
328
+ cellId: mockCellId,
329
+ direction: 'horizontal',
330
+ delta: jasmine.any(Number),
331
+ });
332
+ });
333
+
334
+ it('should handle negative coordinates', () => {
335
+ component.onResizeStart(mockMouseEvent as MouseEvent, 'horizontal');
336
+ spyOn(component.resizeMove, 'emit');
337
+
338
+ // Test with negative coordinates
339
+ (component as any).handleResizeMove({ clientX: -100, clientY: -100 });
340
+
341
+ expect(component.resizeMove.emit).toHaveBeenCalledWith({
342
+ cellId: mockCellId,
343
+ direction: 'horizontal',
344
+ delta: jasmine.any(Number),
345
+ });
346
+ });
347
+
348
+ it('should handle zero cell dimensions', () => {
349
+ store.setGridCellDimensions(0, 0);
350
+ component.onResizeStart(mockMouseEvent as MouseEvent, 'horizontal');
351
+ spyOn(component.resizeMove, 'emit');
352
+
353
+ (component as any).handleResizeMove({ clientX: 200, clientY: 200 });
354
+
355
+ expect(component.resizeMove.emit).toHaveBeenCalledWith({
356
+ cellId: mockCellId,
357
+ direction: 'horizontal',
358
+ delta: jasmine.any(Number),
359
+ });
360
+ });
361
+ });
362
+
363
+ describe('User Scenarios - End-to-End Behavior', () => {
364
+ beforeEach(() => {
365
+ store.setGridCellDimensions(100, 80);
366
+ });
367
+
368
+ it('should handle complete resize workflow', () => {
369
+ spyOn(component.resizeStart, 'emit');
370
+ spyOn(component.resizeMove, 'emit');
371
+ spyOn(component.resizeEnd, 'emit');
372
+
373
+ const startEvent = {
374
+ clientX: 150,
375
+ clientY: 200,
376
+ preventDefault: jasmine.createSpy('preventDefault'),
377
+ stopPropagation: jasmine.createSpy('stopPropagation'),
378
+ } as Partial<MouseEvent>;
379
+
380
+ // Start resize
381
+ component.onResizeStart(startEvent as MouseEvent, 'horizontal');
382
+ expect(component.resizeStart.emit).toHaveBeenCalledWith({
383
+ cellId: mockCellId,
384
+ direction: 'horizontal',
385
+ });
386
+
387
+ // Move resize
388
+ (component as any).handleResizeMove({ clientX: 250, clientY: 200 });
389
+ expect(component.resizeMove.emit).toHaveBeenCalledWith({
390
+ cellId: mockCellId,
391
+ direction: 'horizontal',
392
+ delta: 1,
393
+ });
394
+
395
+ // End resize
396
+ (component as any).handleResizeEnd();
397
+ expect(component.resizeEnd.emit).toHaveBeenCalledWith({
398
+ cellId: mockCellId,
399
+ apply: true,
400
+ });
401
+ });
402
+
403
+ it('should handle multiple sequential resize operations', () => {
404
+ spyOn(component.resizeStart, 'emit');
405
+ spyOn(component.resizeEnd, 'emit');
406
+
407
+ const startEvent = {
408
+ clientX: 150,
409
+ clientY: 200,
410
+ preventDefault: jasmine.createSpy('preventDefault'),
411
+ stopPropagation: jasmine.createSpy('stopPropagation'),
412
+ } as Partial<MouseEvent>;
413
+
414
+ // First resize cycle
415
+ component.onResizeStart(startEvent as MouseEvent, 'horizontal');
416
+ (component as any).handleResizeEnd();
417
+
418
+ // Second resize cycle
419
+ component.onResizeStart(startEvent as MouseEvent, 'vertical');
420
+ (component as any).handleResizeEnd();
421
+
422
+ expect(component.resizeStart.emit).toHaveBeenCalledTimes(2);
423
+ expect(component.resizeEnd.emit).toHaveBeenCalledTimes(2);
424
+ });
425
+
426
+ it('should cleanup on component destroy', () => {
427
+ const startEvent = {
428
+ clientX: 150,
429
+ clientY: 200,
430
+ preventDefault: jasmine.createSpy('preventDefault'),
431
+ stopPropagation: jasmine.createSpy('stopPropagation'),
432
+ } as Partial<MouseEvent>;
433
+
434
+ component.onResizeStart(startEvent as MouseEvent, 'horizontal');
435
+
436
+ // Component destruction should not throw
437
+ expect(() => {
438
+ fixture.destroy();
439
+ }).not.toThrow();
440
+ });
441
+ });
442
+ });