@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
package/ng-package.json
ADDED
package/package.json
CHANGED
|
@@ -1,45 +1,34 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@dragonworks/ngx-dashboard",
|
|
3
|
-
"version": "20.0.
|
|
4
|
-
"description": "Angular library for building drag-and-drop grid dashboards with resizable cells and customizable widgets",
|
|
5
|
-
"peerDependencies": {
|
|
6
|
-
"@angular/common": "^20.0.0",
|
|
7
|
-
"@angular/core": "^20.0.0",
|
|
8
|
-
"@angular/material": "^20.0.0",
|
|
9
|
-
"@angular/cdk": "^20.0.0"
|
|
10
|
-
},
|
|
11
|
-
"dependencies": {
|
|
12
|
-
"tslib": "^2.3.0"
|
|
13
|
-
},
|
|
14
|
-
"sideEffects": false,
|
|
15
|
-
"repository": {
|
|
16
|
-
"type": "git",
|
|
17
|
-
"url": "git+https://github.com/TobyBackstrom/ngx-dashboard.git"
|
|
18
|
-
},
|
|
19
|
-
"author": "Toby Backstrom",
|
|
20
|
-
"license": "MIT",
|
|
21
|
-
"bugs": {
|
|
22
|
-
"url": "https://github.com/TobyBackstrom/ngx-dashboard/issues"
|
|
23
|
-
},
|
|
24
|
-
"homepage": "https://dragonworks.dev",
|
|
25
|
-
"keywords": [
|
|
26
|
-
"angular",
|
|
27
|
-
"dashboard",
|
|
28
|
-
"widgets",
|
|
29
|
-
"grid",
|
|
30
|
-
"drag-drop",
|
|
31
|
-
"material-design",
|
|
32
|
-
"md3"
|
|
33
|
-
]
|
|
34
|
-
|
|
35
|
-
"typings": "index.d.ts",
|
|
36
|
-
"exports": {
|
|
37
|
-
"./package.json": {
|
|
38
|
-
"default": "./package.json"
|
|
39
|
-
},
|
|
40
|
-
".": {
|
|
41
|
-
"types": "./index.d.ts",
|
|
42
|
-
"default": "./fesm2022/dragonworks-ngx-dashboard.mjs"
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@dragonworks/ngx-dashboard",
|
|
3
|
+
"version": "20.0.6",
|
|
4
|
+
"description": "Angular library for building drag-and-drop grid dashboards with resizable cells and customizable widgets",
|
|
5
|
+
"peerDependencies": {
|
|
6
|
+
"@angular/common": "^20.0.0",
|
|
7
|
+
"@angular/core": "^20.0.0",
|
|
8
|
+
"@angular/material": "^20.0.0",
|
|
9
|
+
"@angular/cdk": "^20.0.0"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"tslib": "^2.3.0"
|
|
13
|
+
},
|
|
14
|
+
"sideEffects": false,
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/TobyBackstrom/ngx-dashboard.git"
|
|
18
|
+
},
|
|
19
|
+
"author": "Toby Backstrom",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/TobyBackstrom/ngx-dashboard/issues"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://dragonworks.dev",
|
|
25
|
+
"keywords": [
|
|
26
|
+
"angular",
|
|
27
|
+
"dashboard",
|
|
28
|
+
"widgets",
|
|
29
|
+
"grid",
|
|
30
|
+
"drag-drop",
|
|
31
|
+
"material-design",
|
|
32
|
+
"md3"
|
|
33
|
+
]
|
|
34
|
+
}
|
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
|
2
|
+
import { Component, signal, ViewContainerRef } from '@angular/core';
|
|
3
|
+
import { DashboardComponent } from '../dashboard/dashboard.component';
|
|
4
|
+
import { DashboardService } from '../services/dashboard.service';
|
|
5
|
+
import { CellIdUtils, WidgetFactory, DashboardDataDto, Widget, WidgetMetadata } from '../models';
|
|
6
|
+
import { createEmptyDashboard } from '../models/dashboard-data.utils';
|
|
7
|
+
|
|
8
|
+
// Mock widget with state that can be modified after initialization
|
|
9
|
+
interface TestWidgetState {
|
|
10
|
+
value: string;
|
|
11
|
+
counter: number;
|
|
12
|
+
modified: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@Component({
|
|
16
|
+
selector: 'test-widget',
|
|
17
|
+
template: `
|
|
18
|
+
<div class="test-widget">
|
|
19
|
+
<p>Value: {{ state().value }}</p>
|
|
20
|
+
<p>Counter: {{ state().counter }}</p>
|
|
21
|
+
<p>Modified: {{ state().modified ? 'Yes' : 'No' }}</p>
|
|
22
|
+
<button (click)="updateValue()">Update Value</button>
|
|
23
|
+
<button (click)="incrementCounter()">Increment Counter</button>
|
|
24
|
+
</div>
|
|
25
|
+
`,
|
|
26
|
+
})
|
|
27
|
+
class TestWidgetComponent implements Widget {
|
|
28
|
+
static metadata: WidgetMetadata = {
|
|
29
|
+
widgetTypeid: 'test-widget',
|
|
30
|
+
name: 'Test Widget',
|
|
31
|
+
description: 'A test widget for state preservation tests',
|
|
32
|
+
svgIcon: '<svg><rect width="10" height="10"/></svg>',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
state = signal<TestWidgetState>({
|
|
36
|
+
value: 'initial',
|
|
37
|
+
counter: 0,
|
|
38
|
+
modified: false,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
dashboardSetState(state?: unknown) {
|
|
42
|
+
if (state) {
|
|
43
|
+
this.state.update((current) => ({
|
|
44
|
+
...current,
|
|
45
|
+
...(state as TestWidgetState),
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
dashboardGetState(): TestWidgetState {
|
|
51
|
+
return { ...this.state() };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Methods to simulate user interactions that modify widget state
|
|
55
|
+
updateValue() {
|
|
56
|
+
this.state.update(current => ({
|
|
57
|
+
...current,
|
|
58
|
+
value: `updated-${Date.now()}`,
|
|
59
|
+
modified: true,
|
|
60
|
+
}));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
incrementCounter() {
|
|
64
|
+
this.state.update(current => ({
|
|
65
|
+
...current,
|
|
66
|
+
counter: current.counter + 1,
|
|
67
|
+
modified: true,
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
describe('DashboardComponent - Widget State Integration', () => {
|
|
73
|
+
let component: DashboardComponent;
|
|
74
|
+
let fixture: ComponentFixture<DashboardComponent>;
|
|
75
|
+
let dashboardService: jasmine.SpyObj<DashboardService>;
|
|
76
|
+
let testWidgetFactory: WidgetFactory;
|
|
77
|
+
|
|
78
|
+
beforeEach(async () => {
|
|
79
|
+
const dashboardServiceSpy = jasmine.createSpyObj('DashboardService', [
|
|
80
|
+
'getFactory',
|
|
81
|
+
'registerWidgetType',
|
|
82
|
+
'getAllFactories',
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
await TestBed.configureTestingModule({
|
|
86
|
+
imports: [DashboardComponent, TestWidgetComponent],
|
|
87
|
+
providers: [
|
|
88
|
+
{ provide: DashboardService, useValue: dashboardServiceSpy },
|
|
89
|
+
],
|
|
90
|
+
}).compileComponents();
|
|
91
|
+
|
|
92
|
+
fixture = TestBed.createComponent(DashboardComponent);
|
|
93
|
+
component = fixture.componentInstance;
|
|
94
|
+
dashboardService = TestBed.inject(DashboardService) as jasmine.SpyObj<DashboardService>;
|
|
95
|
+
|
|
96
|
+
// Setup mock widget factory
|
|
97
|
+
testWidgetFactory = {
|
|
98
|
+
widgetTypeid: 'test-widget',
|
|
99
|
+
name: 'Test Widget',
|
|
100
|
+
description: 'A test widget',
|
|
101
|
+
svgIcon: '<svg><rect width="10" height="10"/></svg>',
|
|
102
|
+
createInstance: jasmine.createSpy('createInstance').and.callFake((container: ViewContainerRef, state?: unknown) => {
|
|
103
|
+
const componentRef = container.createComponent(TestWidgetComponent);
|
|
104
|
+
if (state) {
|
|
105
|
+
componentRef.instance.dashboardSetState(state);
|
|
106
|
+
}
|
|
107
|
+
return componentRef;
|
|
108
|
+
}),
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
dashboardService.getFactory.and.returnValue(testWidgetFactory);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('Widget state preservation during export', () => {
|
|
115
|
+
it('should export initial widget state without modifications', async () => {
|
|
116
|
+
const initialState: TestWidgetState = {
|
|
117
|
+
value: 'initial',
|
|
118
|
+
counter: 0,
|
|
119
|
+
modified: false,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// Create dashboard with a widget
|
|
123
|
+
const dashboardData: DashboardDataDto = {
|
|
124
|
+
version: '1.0.0',
|
|
125
|
+
dashboardId: 'test-dashboard',
|
|
126
|
+
rows: 5,
|
|
127
|
+
columns: 8,
|
|
128
|
+
gutterSize: '1em',
|
|
129
|
+
cells: [
|
|
130
|
+
{
|
|
131
|
+
row: 2,
|
|
132
|
+
col: 3,
|
|
133
|
+
rowSpan: 2,
|
|
134
|
+
colSpan: 3,
|
|
135
|
+
widgetTypeid: 'test-widget',
|
|
136
|
+
widgetState: initialState,
|
|
137
|
+
},
|
|
138
|
+
],
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// Set the dashboard data
|
|
142
|
+
fixture.componentRef.setInput('dashboardData', dashboardData);
|
|
143
|
+
fixture.detectChanges();
|
|
144
|
+
|
|
145
|
+
// Wait for async operations
|
|
146
|
+
await fixture.whenStable();
|
|
147
|
+
|
|
148
|
+
// Export dashboard
|
|
149
|
+
const exported = component.exportDashboard();
|
|
150
|
+
|
|
151
|
+
expect(exported.cells).toHaveSize(1);
|
|
152
|
+
expect(exported.cells[0].widgetState).toEqual(initialState);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should export live widget state after modifications', async () => {
|
|
156
|
+
const initialState: TestWidgetState = {
|
|
157
|
+
value: 'initial',
|
|
158
|
+
counter: 0,
|
|
159
|
+
modified: false,
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// Create dashboard with a widget
|
|
163
|
+
const dashboardData: DashboardDataDto = {
|
|
164
|
+
version: '1.0.0',
|
|
165
|
+
dashboardId: 'test-dashboard',
|
|
166
|
+
rows: 5,
|
|
167
|
+
columns: 8,
|
|
168
|
+
gutterSize: '1em',
|
|
169
|
+
cells: [
|
|
170
|
+
{
|
|
171
|
+
row: 1,
|
|
172
|
+
col: 1,
|
|
173
|
+
rowSpan: 1,
|
|
174
|
+
colSpan: 1,
|
|
175
|
+
widgetTypeid: 'test-widget',
|
|
176
|
+
widgetState: initialState,
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// Set the dashboard data
|
|
182
|
+
fixture.componentRef.setInput('dashboardData', dashboardData);
|
|
183
|
+
fixture.detectChanges();
|
|
184
|
+
|
|
185
|
+
// Wait for the component to initialize
|
|
186
|
+
await fixture.whenStable();
|
|
187
|
+
|
|
188
|
+
// Find the widget component and modify its state
|
|
189
|
+
const widgetElement = fixture.debugElement.query(
|
|
190
|
+
(el) => el.componentInstance instanceof TestWidgetComponent
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
if (widgetElement) {
|
|
194
|
+
const widgetComponent = widgetElement.componentInstance as TestWidgetComponent;
|
|
195
|
+
|
|
196
|
+
// Modify the widget state
|
|
197
|
+
widgetComponent.updateValue();
|
|
198
|
+
widgetComponent.incrementCounter();
|
|
199
|
+
|
|
200
|
+
// Detect changes
|
|
201
|
+
fixture.detectChanges();
|
|
202
|
+
await fixture.whenStable();
|
|
203
|
+
|
|
204
|
+
// Export dashboard and check if live state is captured
|
|
205
|
+
const exported = component.exportDashboard();
|
|
206
|
+
|
|
207
|
+
expect(exported.cells).toHaveSize(1);
|
|
208
|
+
|
|
209
|
+
const exportedState = exported.cells[0].widgetState as TestWidgetState;
|
|
210
|
+
expect(exportedState.modified).toBe(true);
|
|
211
|
+
expect(exportedState.counter).toBe(1);
|
|
212
|
+
expect(exportedState.value).toContain('updated-');
|
|
213
|
+
expect(exportedState.value).not.toBe('initial');
|
|
214
|
+
} else {
|
|
215
|
+
fail('Widget component not found in the DOM');
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('should handle widgets that do not implement dashboardGetState', async () => {
|
|
220
|
+
// Create a widget that doesn't implement dashboardGetState
|
|
221
|
+
@Component({
|
|
222
|
+
selector: 'simple-widget',
|
|
223
|
+
template: '<div>Simple Widget</div>',
|
|
224
|
+
})
|
|
225
|
+
class SimpleWidgetComponent implements Widget {
|
|
226
|
+
// Intentionally not implementing dashboardGetState
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const simpleWidgetFactory: WidgetFactory = {
|
|
230
|
+
widgetTypeid: 'simple-widget',
|
|
231
|
+
name: 'Simple Widget',
|
|
232
|
+
description: 'A simple widget',
|
|
233
|
+
svgIcon: '<svg><circle r="5"/></svg>',
|
|
234
|
+
createInstance: jasmine.createSpy('createInstance').and.callFake((container: ViewContainerRef, state?: unknown) => {
|
|
235
|
+
return container.createComponent(SimpleWidgetComponent);
|
|
236
|
+
}),
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
dashboardService.getFactory.and.returnValue(simpleWidgetFactory);
|
|
240
|
+
|
|
241
|
+
const dashboardData: DashboardDataDto = {
|
|
242
|
+
version: '1.0.0',
|
|
243
|
+
dashboardId: 'test-dashboard-simple',
|
|
244
|
+
rows: 3,
|
|
245
|
+
columns: 4,
|
|
246
|
+
gutterSize: '1em',
|
|
247
|
+
cells: [
|
|
248
|
+
{
|
|
249
|
+
row: 1,
|
|
250
|
+
col: 1,
|
|
251
|
+
rowSpan: 1,
|
|
252
|
+
colSpan: 1,
|
|
253
|
+
widgetTypeid: 'simple-widget',
|
|
254
|
+
widgetState: { simple: 'data' },
|
|
255
|
+
},
|
|
256
|
+
],
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
fixture.componentRef.setInput('dashboardData', dashboardData);
|
|
260
|
+
fixture.detectChanges();
|
|
261
|
+
await fixture.whenStable();
|
|
262
|
+
|
|
263
|
+
const exported = component.exportDashboard();
|
|
264
|
+
|
|
265
|
+
expect(exported.cells).toHaveSize(1);
|
|
266
|
+
expect(exported.cells[0].widgetState).toEqual({ simple: 'data' });
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('should handle multiple widgets with different state scenarios', async () => {
|
|
270
|
+
const dashboardData: DashboardDataDto = {
|
|
271
|
+
version: '1.0.0',
|
|
272
|
+
dashboardId: 'test-dashboard-multiple',
|
|
273
|
+
rows: 8,
|
|
274
|
+
columns: 8,
|
|
275
|
+
gutterSize: '1em',
|
|
276
|
+
cells: [
|
|
277
|
+
{
|
|
278
|
+
row: 1,
|
|
279
|
+
col: 1,
|
|
280
|
+
rowSpan: 1,
|
|
281
|
+
colSpan: 1,
|
|
282
|
+
widgetTypeid: 'test-widget',
|
|
283
|
+
widgetState: { value: 'widget1', counter: 1, modified: false },
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
row: 2,
|
|
287
|
+
col: 2,
|
|
288
|
+
rowSpan: 1,
|
|
289
|
+
colSpan: 1,
|
|
290
|
+
widgetTypeid: 'test-widget',
|
|
291
|
+
widgetState: { value: 'widget2', counter: 2, modified: false },
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
row: 3,
|
|
295
|
+
col: 3,
|
|
296
|
+
rowSpan: 1,
|
|
297
|
+
colSpan: 1,
|
|
298
|
+
widgetTypeid: 'test-widget',
|
|
299
|
+
widgetState: { value: 'widget3', counter: 3, modified: false },
|
|
300
|
+
},
|
|
301
|
+
],
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
fixture.componentRef.setInput('dashboardData', dashboardData);
|
|
305
|
+
fixture.detectChanges();
|
|
306
|
+
await fixture.whenStable();
|
|
307
|
+
|
|
308
|
+
// Find all widget components (top-level only)
|
|
309
|
+
const widgetElements = fixture.debugElement.queryAll(
|
|
310
|
+
(el) => el.componentInstance instanceof TestWidgetComponent && el.nativeElement.tagName === 'TEST-WIDGET'
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
expect(widgetElements).toHaveSize(3);
|
|
314
|
+
|
|
315
|
+
// Modify only the first and third widgets
|
|
316
|
+
if (widgetElements[0]) {
|
|
317
|
+
(widgetElements[0].componentInstance as TestWidgetComponent).updateValue();
|
|
318
|
+
(widgetElements[0].componentInstance as TestWidgetComponent).incrementCounter();
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (widgetElements[2]) {
|
|
322
|
+
(widgetElements[2].componentInstance as TestWidgetComponent).incrementCounter();
|
|
323
|
+
(widgetElements[2].componentInstance as TestWidgetComponent).incrementCounter();
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
fixture.detectChanges();
|
|
327
|
+
await fixture.whenStable();
|
|
328
|
+
|
|
329
|
+
const exported = component.exportDashboard();
|
|
330
|
+
|
|
331
|
+
expect(exported.cells).toHaveSize(3);
|
|
332
|
+
|
|
333
|
+
// Check that the modified widgets have live state
|
|
334
|
+
const cell1 = exported.cells.find(c => c.row === 1 && c.col === 1);
|
|
335
|
+
const cell2 = exported.cells.find(c => c.row === 2 && c.col === 2);
|
|
336
|
+
const cell3 = exported.cells.find(c => c.row === 3 && c.col === 3);
|
|
337
|
+
|
|
338
|
+
expect(cell1?.widgetState).toEqual(
|
|
339
|
+
jasmine.objectContaining({
|
|
340
|
+
modified: true,
|
|
341
|
+
counter: 2,
|
|
342
|
+
})
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
expect(cell2?.widgetState).toEqual(
|
|
346
|
+
jasmine.objectContaining({
|
|
347
|
+
value: 'widget2',
|
|
348
|
+
counter: 2,
|
|
349
|
+
modified: false,
|
|
350
|
+
})
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
expect(cell3?.widgetState).toEqual(
|
|
354
|
+
jasmine.objectContaining({
|
|
355
|
+
value: 'widget3',
|
|
356
|
+
counter: 5,
|
|
357
|
+
modified: true,
|
|
358
|
+
})
|
|
359
|
+
);
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
describe('Widget state preservation during mode switching', () => {
|
|
364
|
+
it('should preserve widget state when switching from edit to view mode', async () => {
|
|
365
|
+
const initialState: TestWidgetState = {
|
|
366
|
+
value: 'initial',
|
|
367
|
+
counter: 0,
|
|
368
|
+
modified: false,
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
// Create dashboard with a widget
|
|
372
|
+
const dashboardData: DashboardDataDto = {
|
|
373
|
+
version: '1.0.0',
|
|
374
|
+
dashboardId: 'test-dashboard-mode-switch',
|
|
375
|
+
rows: 5,
|
|
376
|
+
columns: 8,
|
|
377
|
+
gutterSize: '1em',
|
|
378
|
+
cells: [
|
|
379
|
+
{
|
|
380
|
+
row: 2,
|
|
381
|
+
col: 3,
|
|
382
|
+
rowSpan: 1,
|
|
383
|
+
colSpan: 1,
|
|
384
|
+
widgetTypeid: 'test-widget',
|
|
385
|
+
widgetState: initialState,
|
|
386
|
+
},
|
|
387
|
+
],
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
// Set the dashboard data in edit mode
|
|
391
|
+
fixture.componentRef.setInput('dashboardData', dashboardData);
|
|
392
|
+
fixture.componentRef.setInput('editMode', true);
|
|
393
|
+
fixture.detectChanges();
|
|
394
|
+
await fixture.whenStable();
|
|
395
|
+
|
|
396
|
+
// Find the widget component and modify its state in edit mode
|
|
397
|
+
const widgetElementInEdit = fixture.debugElement.query(
|
|
398
|
+
(el) => el.componentInstance instanceof TestWidgetComponent
|
|
399
|
+
);
|
|
400
|
+
|
|
401
|
+
expect(widgetElementInEdit).toBeTruthy();
|
|
402
|
+
|
|
403
|
+
if (widgetElementInEdit) {
|
|
404
|
+
const widgetComponent = widgetElementInEdit.componentInstance as TestWidgetComponent;
|
|
405
|
+
|
|
406
|
+
// Modify the widget state
|
|
407
|
+
widgetComponent.updateValue();
|
|
408
|
+
widgetComponent.incrementCounter();
|
|
409
|
+
|
|
410
|
+
// Detect changes
|
|
411
|
+
fixture.detectChanges();
|
|
412
|
+
await fixture.whenStable();
|
|
413
|
+
|
|
414
|
+
// Verify the state was modified
|
|
415
|
+
const currentState = widgetComponent.dashboardGetState();
|
|
416
|
+
expect(currentState.modified).toBe(true);
|
|
417
|
+
expect(currentState.counter).toBe(1);
|
|
418
|
+
expect(currentState.value).toContain('updated-');
|
|
419
|
+
|
|
420
|
+
// Now switch to view mode
|
|
421
|
+
fixture.componentRef.setInput('editMode', false);
|
|
422
|
+
fixture.detectChanges();
|
|
423
|
+
await fixture.whenStable();
|
|
424
|
+
|
|
425
|
+
// Find the widget component in view mode
|
|
426
|
+
const widgetElementInView = fixture.debugElement.query(
|
|
427
|
+
(el) => el.componentInstance instanceof TestWidgetComponent
|
|
428
|
+
);
|
|
429
|
+
|
|
430
|
+
expect(widgetElementInView).toBeTruthy();
|
|
431
|
+
|
|
432
|
+
if (widgetElementInView) {
|
|
433
|
+
const widgetComponentInView = widgetElementInView.componentInstance as TestWidgetComponent;
|
|
434
|
+
|
|
435
|
+
// Check if the widget state was preserved
|
|
436
|
+
const viewModeState = widgetComponentInView.dashboardGetState();
|
|
437
|
+
|
|
438
|
+
// This is the current bug - the state is lost when switching modes
|
|
439
|
+
// The widget gets recreated with the original state from the store
|
|
440
|
+
expect(viewModeState.modified).toBe(true); // This will currently fail
|
|
441
|
+
expect(viewModeState.counter).toBe(1); // This will currently fail
|
|
442
|
+
expect(viewModeState.value).toContain('updated-'); // This will currently fail
|
|
443
|
+
} else {
|
|
444
|
+
fail('Widget component not found in view mode');
|
|
445
|
+
}
|
|
446
|
+
} else {
|
|
447
|
+
fail('Widget component not found in edit mode');
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
it('should preserve widget state when switching from view to edit mode', async () => {
|
|
452
|
+
const initialState: TestWidgetState = {
|
|
453
|
+
value: 'initial',
|
|
454
|
+
counter: 5,
|
|
455
|
+
modified: false,
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
// Create dashboard with a widget
|
|
459
|
+
const dashboardData: DashboardDataDto = {
|
|
460
|
+
version: '1.0.0',
|
|
461
|
+
dashboardId: 'test-dashboard-view-to-edit',
|
|
462
|
+
rows: 5,
|
|
463
|
+
columns: 8,
|
|
464
|
+
gutterSize: '1em',
|
|
465
|
+
cells: [
|
|
466
|
+
{
|
|
467
|
+
row: 1,
|
|
468
|
+
col: 1,
|
|
469
|
+
rowSpan: 1,
|
|
470
|
+
colSpan: 1,
|
|
471
|
+
widgetTypeid: 'test-widget',
|
|
472
|
+
widgetState: initialState,
|
|
473
|
+
},
|
|
474
|
+
],
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
// Set the dashboard data in view mode first
|
|
478
|
+
fixture.componentRef.setInput('dashboardData', dashboardData);
|
|
479
|
+
fixture.componentRef.setInput('editMode', false);
|
|
480
|
+
fixture.detectChanges();
|
|
481
|
+
await fixture.whenStable();
|
|
482
|
+
|
|
483
|
+
// Find the widget component and modify its state in view mode
|
|
484
|
+
const widgetElementInView = fixture.debugElement.query(
|
|
485
|
+
(el) => el.componentInstance instanceof TestWidgetComponent
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
expect(widgetElementInView).toBeTruthy();
|
|
489
|
+
|
|
490
|
+
if (widgetElementInView) {
|
|
491
|
+
const widgetComponent = widgetElementInView.componentInstance as TestWidgetComponent;
|
|
492
|
+
|
|
493
|
+
// Modify the widget state
|
|
494
|
+
widgetComponent.updateValue();
|
|
495
|
+
widgetComponent.incrementCounter();
|
|
496
|
+
|
|
497
|
+
// Detect changes
|
|
498
|
+
fixture.detectChanges();
|
|
499
|
+
await fixture.whenStable();
|
|
500
|
+
|
|
501
|
+
// Verify the state was modified
|
|
502
|
+
const currentState = widgetComponent.dashboardGetState();
|
|
503
|
+
expect(currentState.modified).toBe(true);
|
|
504
|
+
expect(currentState.counter).toBe(6);
|
|
505
|
+
expect(currentState.value).toContain('updated-');
|
|
506
|
+
|
|
507
|
+
// Now switch to edit mode
|
|
508
|
+
fixture.componentRef.setInput('editMode', true);
|
|
509
|
+
fixture.detectChanges();
|
|
510
|
+
await fixture.whenStable();
|
|
511
|
+
|
|
512
|
+
// Find the widget component in edit mode
|
|
513
|
+
const widgetElementInEdit = fixture.debugElement.query(
|
|
514
|
+
(el) => el.componentInstance instanceof TestWidgetComponent
|
|
515
|
+
);
|
|
516
|
+
|
|
517
|
+
expect(widgetElementInEdit).toBeTruthy();
|
|
518
|
+
|
|
519
|
+
if (widgetElementInEdit) {
|
|
520
|
+
const widgetComponentInEdit = widgetElementInEdit.componentInstance as TestWidgetComponent;
|
|
521
|
+
|
|
522
|
+
// Check if the widget state was preserved
|
|
523
|
+
const editModeState = widgetComponentInEdit.dashboardGetState();
|
|
524
|
+
|
|
525
|
+
// This is the current bug - the state is lost when switching modes
|
|
526
|
+
expect(editModeState.modified).toBe(true); // This will currently fail
|
|
527
|
+
expect(editModeState.counter).toBe(6); // This will currently fail
|
|
528
|
+
expect(editModeState.value).toContain('updated-'); // This will currently fail
|
|
529
|
+
} else {
|
|
530
|
+
fail('Widget component not found in edit mode');
|
|
531
|
+
}
|
|
532
|
+
} else {
|
|
533
|
+
fail('Widget component not found in view mode');
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
});
|
|
537
|
+
});
|