Add functionality to truncate amount of instances
[portal/nonrtric-controlpanel.git] / webapp-frontend / src / app / policy / policy-instance / policy-instance.component.spec.ts
1 /*-
2  * ========================LICENSE_START=================================
3  * O-RAN-SC
4  * %%
5  * Copyright (C) 2020 Nordix Foundation
6  * %%
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ========================LICENSE_END===================================
19  */
20
21 import { HarnessLoader } from "@angular/cdk/testing";
22 import { TestbedHarnessEnvironment } from "@angular/cdk/testing/testbed";
23 import { HttpResponse } from "@angular/common/http";
24 import { Component, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
25 import { ComponentFixture, TestBed } from "@angular/core/testing";
26 import { FormsModule, ReactiveFormsModule } from "@angular/forms";
27 import { MatButtonHarness } from "@angular/material/button/testing";
28 import { MatDialog } from "@angular/material/dialog";
29 import { MatIconModule } from "@angular/material/icon";
30 import { MatInputHarness } from "@angular/material/input/testing";
31 import { MatSortModule } from "@angular/material/sort";
32 import { MatSortHarness } from "@angular/material/sort/testing";
33 import { MatTableModule } from "@angular/material/table";
34 import { MatTableHarness } from "@angular/material/table/testing";
35 import { By } from "@angular/platform-browser";
36 import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
37 import {
38   PolicyInstance,
39   PolicyInstances,
40   PolicyStatus,
41   PolicyTypeSchema,
42 } from "@interfaces/policy.types";
43 import { PolicyService } from "@services/policy/policy.service";
44 import { ConfirmDialogService } from "@services/ui/confirm-dialog.service";
45 import { NotificationService } from "@services/ui/notification.service";
46 import { UiService } from "@services/ui/ui.service";
47 import { ToastrModule } from "ngx-toastr";
48 import { Observable, of } from "rxjs";
49 import { PolicyInstanceDialogComponent } from "../policy-instance-dialog/policy-instance-dialog.component";
50 import { PolicyInstanceComponent } from "./policy-instance.component";
51
52 const lastModifiedTime = "2021-01-26T13:15:11.895297Z";
53 describe("PolicyInstanceComponent", () => {
54   let hostComponent: PolicyInstanceComponentHostComponent;
55   let componentUnderTest: PolicyInstanceComponent;
56   let hostFixture: ComponentFixture<PolicyInstanceComponentHostComponent>;
57   let loader: HarnessLoader;
58   let policyServiceSpy: jasmine.SpyObj<PolicyService>;
59   let dialogSpy: jasmine.SpyObj<MatDialog>;
60   let notificationServiceSpy: jasmine.SpyObj<NotificationService>;
61   let confirmServiceSpy: jasmine.SpyObj<ConfirmDialogService>;
62
63   const policyInstances = {
64     policy_ids: ["policy1", "policy2"],
65   } as PolicyInstances;
66   const policyTypeSchema = JSON.parse(
67     '{"title": "1", "description": "Type 1 policy type"}'
68   );
69   const policy1 = {
70     policy_id: "policy1",
71     policy_data: "{}",
72     ric_id: "1",
73     service_id: "service",
74     lastModified: "Now",
75   } as PolicyInstance;
76   const policy2 = {
77     policy_id: "policy2",
78     policy_data: "{}",
79     ric_id: "2",
80     service_id: "service",
81     lastModified: "Now",
82   } as PolicyInstance;
83   const policy1Status = {
84     last_modified: lastModifiedTime,
85   } as PolicyStatus;
86   const policy2Status = {
87     last_modified: lastModifiedTime,
88   } as PolicyStatus;
89
90   const policyIdToInstanceMap = {
91     policy1: policy1,
92     policy2: policy2,
93   };
94   const policyIdToStatusMap = {
95     policy1: policy1Status,
96     policy2: policy2Status,
97   };
98
99   @Component({
100     selector: "policy-instance-compnent-host-component",
101     template:
102       "<nrcp-policy-instance [policyTypeSchema]=policyType></nrcp-policy-instance>",
103   })
104   class PolicyInstanceComponentHostComponent {
105     policyType = {
106       id: "type1",
107       name: "1",
108       schemaObject: policyTypeSchema,
109     } as PolicyTypeSchema;
110   }
111
112   beforeEach(async () => {
113     policyServiceSpy = jasmine.createSpyObj("PolicyService", [
114       "getPolicyInstancesByType",
115       "getPolicyInstance",
116       "getPolicyStatus",
117       "deletePolicy",
118     ]);
119
120     dialogSpy = jasmine.createSpyObj("MatDialog", ["open"]);
121     notificationServiceSpy = jasmine.createSpyObj("NotificationService", [
122       "success",
123       "warn",
124     ]);
125     confirmServiceSpy = jasmine.createSpyObj("ConfirmDialogService", [
126       "openConfirmDialog",
127     ]);
128
129     TestBed.configureTestingModule({
130       imports: [
131         BrowserAnimationsModule,
132         FormsModule,
133         MatIconModule,
134         MatSortModule,
135         MatTableModule,
136         ReactiveFormsModule,
137         ToastrModule.forRoot(),
138       ],
139       schemas: [CUSTOM_ELEMENTS_SCHEMA],
140       declarations: [
141         PolicyInstanceComponent,
142         PolicyInstanceComponentHostComponent,
143       ],
144       providers: [
145         { provide: PolicyService, useValue: policyServiceSpy },
146         { provide: MatDialog, useValue: dialogSpy },
147         { provide: NotificationService, useValue: notificationServiceSpy },
148         { provide: ConfirmDialogService, useValue: confirmServiceSpy },
149         UiService,
150       ],
151     });
152   });
153
154   describe("content and dialogs", () => {
155     beforeEach(() => {
156       policyServiceSpy.getPolicyInstancesByType.and.returnValue(
157         of(policyInstances)
158       );
159       policyServiceSpy.getPolicyInstance.and.callFake(function (
160         policyId: string
161       ) {
162         return of(policyIdToInstanceMap[policyId]);
163       });
164       policyServiceSpy.getPolicyStatus.and.callFake(function (
165         policyId: string
166       ) {
167         return of(policyIdToStatusMap[policyId]);
168       });
169
170       compileAndGetComponents();
171     });
172
173     it("should create", () => {
174       expect(hostComponent).toBeTruthy();
175
176       expect(componentUnderTest).toBeTruthy();
177     });
178
179     it("should set correct dark mode from UIService", () => {
180       const uiService: UiService = TestBed.inject(UiService);
181       expect(componentUnderTest.darkMode).toBeTruthy();
182
183       uiService.darkModeState.next(false);
184       hostFixture.detectChanges();
185       expect(componentUnderTest.darkMode).toBeFalsy();
186     });
187
188     it("should contain number of instances heading and value, create and refresh buttons, and policies table", async () => {
189       const instancesHeading =
190         hostFixture.debugElement.nativeElement.querySelector("div");
191       expect(instancesHeading.innerText).toContain("Number of instances: 2");
192
193       const createButton: MatButtonHarness = await loader.getHarness(
194         MatButtonHarness.with({ selector: "#createButton" })
195       );
196       expect(createButton).toBeTruthy();
197       const createIcon =
198         hostFixture.debugElement.nativeElement.querySelector("#createIcon");
199       expect(createIcon.innerText).toContain("add_box");
200
201       const refreshButton: MatButtonHarness = await loader.getHarness(
202         MatButtonHarness.with({ selector: "#refreshButton" })
203       );
204       expect(refreshButton).toBeTruthy();
205       const refreshIcon =
206         hostFixture.debugElement.nativeElement.querySelector("#refreshIcon");
207       expect(refreshIcon.innerText).toContain("refresh");
208
209       const policiesTable = await loader.getHarness(
210         MatTableHarness.with({ selector: "#policiesTable" })
211       );
212       expect(policiesTable).toBeTruthy();
213     });
214
215     it("should open dialog to create policy and refresh policies after successful creation", async () => {
216       const dialogRefSpy = setupDialogRefSpy();
217       dialogSpy.open.and.returnValue(dialogRefSpy);
218
219       spyOn(componentUnderTest, "getPolicyInstances");
220
221       const createButton: MatButtonHarness = await loader.getHarness(
222         MatButtonHarness.with({ selector: "#createButton" })
223       );
224       await createButton.click();
225
226       expect(dialogSpy.open).toHaveBeenCalledWith(
227         PolicyInstanceDialogComponent,
228         {
229           maxWidth: "1200px",
230           maxHeight: "900px",
231           width: "900px",
232           role: "dialog",
233           disableClose: false,
234           panelClass: "dark-theme",
235           data: {
236             createSchema: policyTypeSchema,
237             instanceId: null,
238             instanceJson: null,
239             name: "1",
240             ric: null,
241           },
242         }
243       );
244       expect(componentUnderTest.getPolicyInstances).toHaveBeenCalled();
245     });
246
247     it("should open dialog to edit policy and refresh policies after successful update", async () => {
248       const dialogRefSpy = setupDialogRefSpy();
249       dialogSpy.open.and.returnValue(dialogRefSpy);
250
251       spyOn(componentUnderTest, "getPolicyInstances");
252
253       const editButton: MatButtonHarness = await loader.getHarness(
254         MatButtonHarness.with({ selector: "#policy1EditButton" })
255       );
256       await editButton.click();
257
258       expect(dialogSpy.open).toHaveBeenCalledWith(
259         PolicyInstanceDialogComponent,
260         {
261           maxWidth: "1200px",
262           maxHeight: "900px",
263           width: "900px",
264           role: "dialog",
265           disableClose: false,
266           panelClass: "dark-theme",
267           data: {
268             createSchema: policyTypeSchema,
269             instanceId: "policy1",
270             instanceJson: "{}",
271             name: "1",
272             ric: "1",
273           },
274         }
275       );
276       expect(componentUnderTest.getPolicyInstances).toHaveBeenCalled();
277     });
278
279     it("should open dialog to edit policy and not refresh policies when dialog closed wihtout submit", async () => {
280       const dialogRefSpy = setupDialogRefSpy(false);
281       dialogSpy.open.and.returnValue(dialogRefSpy);
282
283       spyOn(componentUnderTest, "getPolicyInstances");
284
285       const editButton: MatButtonHarness = await loader.getHarness(
286         MatButtonHarness.with({ selector: "#policy1EditButton" })
287       );
288       await editButton.click();
289
290       expect(componentUnderTest.getPolicyInstances).not.toHaveBeenCalled();
291     });
292
293     it("should open instance dialog when clicking in any policy cell in table", async () => {
294       spyOn(componentUnderTest, "modifyInstance");
295
296       const policiesTable = await loader.getHarness(
297         MatTableHarness.with({ selector: "#policiesTable" })
298       );
299       const firstRow = (await policiesTable.getRows())[0];
300       const idCell = (await firstRow.getCells())[0];
301       (await idCell.host()).click();
302       const ownerCell = (await firstRow.getCells())[1];
303       (await ownerCell.host()).click();
304       const serviceCell = (await firstRow.getCells())[2];
305       (await serviceCell.host()).click();
306       const lastModifiedCell = (await firstRow.getCells())[3];
307       (await lastModifiedCell.host()).click();
308
309       // Totally unnecessary call just to make the framework count the number of calls to the spy correctly!
310       await policiesTable.getRows();
311
312       expect(componentUnderTest.modifyInstance).toHaveBeenCalledTimes(4);
313     });
314
315     it("should open dialog asking for delete and delete when ok response and refresh table afterwards", async () => {
316       const dialogRefSpy = setupDialogRefSpy();
317       confirmServiceSpy.openConfirmDialog.and.returnValue(dialogRefSpy);
318       const createResponse = { status: 204 } as HttpResponse<Object>;
319       policyServiceSpy.deletePolicy.and.returnValue(of(createResponse));
320
321       spyOn(componentUnderTest, "getPolicyInstances");
322       const deleteButton: MatButtonHarness = await loader.getHarness(
323         MatButtonHarness.with({ selector: "#policy1DeleteButton" })
324       );
325       await deleteButton.click();
326
327       expect(confirmServiceSpy.openConfirmDialog).toHaveBeenCalledWith(
328         "Delete Policy",
329         "Are you sure you want to delete this policy instance?"
330       );
331       expect(policyServiceSpy.deletePolicy).toHaveBeenCalledWith("policy1");
332       expect(notificationServiceSpy.success).toHaveBeenCalledWith(
333         "Delete succeeded!"
334       );
335       expect(componentUnderTest.getPolicyInstances).toHaveBeenCalled();
336     });
337
338     it("should open dialog asking for delete and not delete whith Cancel as response", async () => {
339       const dialogRefSpy = setupDialogRefSpy(false);
340       confirmServiceSpy.openConfirmDialog.and.returnValue(dialogRefSpy);
341
342       const deleteButton: MatButtonHarness = await loader.getHarness(
343         MatButtonHarness.with({ selector: "#policy1DeleteButton" })
344       );
345       await deleteButton.click();
346
347       expect(policyServiceSpy.deletePolicy).not.toHaveBeenCalled();
348     });
349
350     it("should refresh table", async () => {
351       spyOn(componentUnderTest, "getPolicyInstances");
352
353       const refreshButton: MatButtonHarness = await loader.getHarness(
354         MatButtonHarness.with({ selector: "#refreshButton" })
355       );
356       await refreshButton.click();
357
358       expect(componentUnderTest.getPolicyInstances).toHaveBeenCalled();
359     });
360   });
361
362   describe("no instances", () => {
363     beforeEach(() => {
364       policyServiceSpy.getPolicyInstancesByType.and.returnValue(
365         of({
366           policy_ids: [],
367         } as PolicyInstances)
368       );
369
370       compileAndGetComponents();
371     });
372
373     it("should display message of no instances", async () => {
374       const policiesTable = await loader.getHarness(
375         MatTableHarness.with({ selector: "#policiesTable" })
376       );
377       const footerRows = await policiesTable.getFooterRows();
378       const footerRow = footerRows[0];
379       const footerRowHost = await footerRow.host();
380
381       expect(await footerRowHost.hasClass("display-none")).toBeFalsy();
382       const footerTexts = await footerRow.getCellTextByColumnName();
383       expect(footerTexts["noRecordsFound"]).toEqual("No records found.");
384     });
385   });
386
387   describe("#policiesTable", () => {
388     const expectedPolicy1Row = {
389       instanceId: "policy1",
390       ric: "1",
391       service: "service",
392       lastModified: toLocalTime(lastModifiedTime),
393       action: "editdelete",
394     };
395
396     beforeEach(() => {
397       policyServiceSpy.getPolicyInstancesByType.and.returnValue(
398         of(policyInstances)
399       );
400       policyServiceSpy.getPolicyInstance.and.callFake(function (
401         policyId: string
402       ) {
403         return of(policyIdToInstanceMap[policyId]);
404       });
405       policyServiceSpy.getPolicyStatus.and.callFake(function (
406         policyId: string
407       ) {
408         return of(policyIdToStatusMap[policyId]);
409       });
410
411       compileAndGetComponents();
412     });
413
414     it("should contain correct headings", async () => {
415       const policiesTable = await loader.getHarness(
416         MatTableHarness.with({ selector: "#policiesTable" })
417       );
418       const headerRow = (await policiesTable.getHeaderRows())[0];
419       const headers = await headerRow.getCellTextByColumnName();
420
421       expect(headers).toEqual({
422         instanceId: "Instance",
423         ric: "Target",
424         service: "Owner",
425         lastModified: "Last modified",
426         action: "Action",
427       });
428     });
429
430     it("should contain data after initialization", async () => {
431       const expectedJobRows = [
432         expectedPolicy1Row,
433         {
434           instanceId: "policy2",
435           ric: "2",
436           service: "service",
437           lastModified: toLocalTime(lastModifiedTime),
438           action: "editdelete",
439         },
440       ];
441       const policiesTable = await loader.getHarness(
442         MatTableHarness.with({ selector: "#policiesTable" })
443       );
444       const policyRows = await policiesTable.getRows();
445       expect(policyRows.length).toEqual(2);
446       policyRows.forEach((row) => {
447         row.getCellTextByColumnName().then((values) => {
448           expect(expectedJobRows).toContain(jasmine.objectContaining(values));
449         });
450       });
451
452       // No message about no entries
453       const footerRows = await policiesTable.getFooterRows();
454       const footerRow = await footerRows[0];
455       const footerRowHost = await footerRow.host();
456
457       expect(await footerRowHost.hasClass("display-none")).toBeTruthy();
458     });
459
460     it("should have filtering for all four policy data headings", async () => {
461       const policiesTable = await loader.getHarness(
462         MatTableHarness.with({ selector: "#policiesTable" })
463       );
464
465       const idFilterInput = await loader.getHarness(
466         MatInputHarness.with({ selector: "#policyInstanceIdFilter" })
467       );
468       await idFilterInput.setValue("1");
469       const policyRows = await policiesTable.getRows();
470       expect(policyRows.length).toEqual(1);
471       expect(await policyRows[0].getCellTextByColumnName()).toEqual(
472         expectedPolicy1Row
473       );
474
475       const targetFilterInput = await loader.getHarness(
476         MatInputHarness.with({ selector: "#policyInstanceTargetFilter" })
477       );
478       expect(targetFilterInput).toBeTruthy();
479
480       const ownerFilterInput = await loader.getHarness(
481         MatInputHarness.with({ selector: "#policyInstanceOwnerFilter" })
482       );
483       expect(ownerFilterInput).toBeTruthy();
484
485       const lastModifiedFilterInput = await loader.getHarness(
486         MatInputHarness.with({ selector: "#policyInstanceLastModifiedFilter" })
487       );
488       expect(lastModifiedFilterInput).toBeTruthy();
489     });
490
491     it("should not sort when click in filter inputs", async () => {
492       spyOn(componentUnderTest, "stopSort").and.callThrough();
493
494       const idFilterInputDiv =
495         hostFixture.debugElement.nativeElement.querySelector("#idSortStop");
496       idFilterInputDiv.click();
497
498       const targetFilterInputDiv =
499         hostFixture.debugElement.nativeElement.querySelector("#targetSortStop");
500       targetFilterInputDiv.click();
501
502       const ownerFilterInputDiv =
503         hostFixture.debugElement.nativeElement.querySelector("#ownerSortStop");
504       ownerFilterInputDiv.click();
505
506       const lastModifiedFilterInputDiv =
507         hostFixture.debugElement.nativeElement.querySelector(
508           "#lastModifiedSortStop"
509         );
510       lastModifiedFilterInputDiv.click();
511
512       expect(componentUnderTest.stopSort).toHaveBeenCalledTimes(4);
513
514       const eventSpy = jasmine.createSpyObj("any", ["stopPropagation"]);
515       componentUnderTest.stopSort(eventSpy);
516       expect(eventSpy.stopPropagation).toHaveBeenCalled();
517     });
518
519     describe("#truncate data", () => {
520       fit("should verify that data is correctly truncated when needed", async () => {
521         policyServiceSpy.getPolicyInstancesByType.and.returnValue(
522           of(policyInstances)
523         );
524         policyServiceSpy.getPolicyInstance.and.callFake(function (
525           policyId: string
526         ) {
527           return of(policyIdToInstanceMap[policyId]);
528         });
529         policyServiceSpy.getPolicyStatus.and.callFake(function (
530           policyId: string
531         ) {
532           return of(policyIdToStatusMap[policyId]);
533         });
534         compileAndGetComponents();
535         componentUnderTest.slice = 1;
536         componentUnderTest.ngOnInit();
537
538         const policiesTable = await loader.getHarness(
539           MatTableHarness.with({ selector: "#policiesTable" })
540         );
541         const policyRows = await policiesTable.getRows();
542         expect(policyRows.length).toEqual(1);
543         policyRows[0].getCellTextByColumnName().then((values) => {
544           expect(expectedPolicy1Row).toEqual(jasmine.objectContaining(values));
545         });
546
547         expect(componentUnderTest.truncated).toBeTruthy();
548       });
549     });
550
551     describe("#sorting", () => {
552       it("should verify sort functionality on the table", async () => {
553         const sort = await loader.getHarness(MatSortHarness);
554         const headers = await sort.getSortHeaders({ sortDirection: "" });
555         expect(headers.length).toBe(4);
556
557         await headers[0].click();
558         expect(await headers[0].isActive()).toBe(true);
559         expect(await headers[0].getSortDirection()).toBe("asc");
560
561         await headers[0].click();
562         expect(await headers[0].getSortDirection()).toBe("desc");
563       });
564
565       it("should sort table asc and desc by first header", async () => {
566         const sort = await loader.getHarness(MatSortHarness);
567         const policyTable = await loader.getHarness(
568           MatTableHarness.with({ selector: "#policiesTable" })
569         );
570         const firstHeader = (await sort.getSortHeaders())[0];
571         expect(await firstHeader.getSortDirection()).toBe("");
572
573         await firstHeader.click();
574         expect(await firstHeader.getSortDirection()).toBe("asc");
575         let policyRows = await policyTable.getRows();
576         expect(await policyRows[0].getCellTextByColumnName()).toEqual(
577           expectedPolicy1Row
578         );
579
580         await firstHeader.click();
581         expect(await firstHeader.getSortDirection()).toBe("desc");
582         policyRows = await policyTable.getRows();
583         expect(
584           await policyRows[policyRows.length - 1].getCellTextByColumnName()
585         ).toEqual(expectedPolicy1Row);
586       });
587
588       it("should sort table asc and desc by second header", async () => {
589         const sort = await loader.getHarness(MatSortHarness);
590         const jobsTable = await loader.getHarness(
591           MatTableHarness.with({ selector: "#policiesTable" })
592         );
593         const firstHeader = (await sort.getSortHeaders())[1];
594         expect(await firstHeader.getSortDirection()).toBe("");
595
596         await firstHeader.click();
597         expect(await firstHeader.getSortDirection()).toBe("asc");
598         let policyRows = await jobsTable.getRows();
599         policyRows = await jobsTable.getRows();
600         expect(await policyRows[0].getCellTextByColumnName()).toEqual(
601           expectedPolicy1Row
602         );
603
604         await firstHeader.click();
605         expect(await firstHeader.getSortDirection()).toBe("desc");
606         policyRows = await jobsTable.getRows();
607         expect(
608           await policyRows[policyRows.length - 1].getCellTextByColumnName()
609         ).toEqual(expectedPolicy1Row);
610       });
611
612       it("should sort table asc and desc by third header", async () => {
613         const sort = await loader.getHarness(MatSortHarness);
614         const jobsTable = await loader.getHarness(
615           MatTableHarness.with({ selector: "#policiesTable" })
616         );
617         const firstHeader = (await sort.getSortHeaders())[2];
618         expect(await firstHeader.getSortDirection()).toBe("");
619
620         await firstHeader.click();
621         expect(await firstHeader.getSortDirection()).toBe("asc");
622         let policyRows = await jobsTable.getRows();
623         policyRows = await jobsTable.getRows();
624         expect(await policyRows[0].getCellTextByColumnName()).toEqual(
625           expectedPolicy1Row
626         );
627
628         await firstHeader.click();
629         expect(await firstHeader.getSortDirection()).toBe("desc");
630         policyRows = await jobsTable.getRows();
631         expect(
632           await policyRows[policyRows.length - 1].getCellTextByColumnName()
633         ).toEqual(expectedPolicy1Row);
634       });
635
636       it("should sort table asc and desc by fourth header", async () => {
637         const sort = await loader.getHarness(MatSortHarness);
638         const jobsTable = await loader.getHarness(
639           MatTableHarness.with({ selector: "#policiesTable" })
640         );
641         const firstHeader = (await sort.getSortHeaders())[3];
642         expect(await firstHeader.getSortDirection()).toBe("");
643
644         await firstHeader.click();
645         expect(await firstHeader.getSortDirection()).toBe("asc");
646         let policyRows = await jobsTable.getRows();
647         policyRows = await jobsTable.getRows();
648         expect(await policyRows[0].getCellTextByColumnName()).toEqual(
649           expectedPolicy1Row
650         );
651
652         await firstHeader.click();
653         expect(await firstHeader.getSortDirection()).toBe("desc");
654         policyRows = await jobsTable.getRows();
655         expect(
656           await policyRows[policyRows.length - 1].getCellTextByColumnName()
657         ).toEqual(expectedPolicy1Row);
658       });
659     });
660   });
661
662   function compileAndGetComponents() {
663     TestBed.compileComponents();
664
665     hostFixture = TestBed.createComponent(PolicyInstanceComponentHostComponent);
666     hostComponent = hostFixture.componentInstance;
667     componentUnderTest = hostFixture.debugElement.query(
668       By.directive(PolicyInstanceComponent)
669     ).componentInstance;
670     hostFixture.detectChanges();
671     loader = TestbedHarnessEnvironment.loader(hostFixture);
672     return { hostFixture, hostComponent, componentUnderTest, loader };
673   }
674 });
675
676 function setupDialogRefSpy(returnValue: boolean = true) {
677   const afterClosedObservable = new Observable((observer) => {
678     observer.next(returnValue);
679   });
680
681   const dialogRefSpy = jasmine.createSpyObj("MatDialogRef", ["afterClosed"]);
682   dialogRefSpy.afterClosed.and.returnValue(afterClosedObservable);
683   return dialogRefSpy;
684 }
685
686 function toLocalTime(utcTime: string): string {
687   const date = new Date(utcTime);
688   const toutc = date.toUTCString();
689   return new Date(toutc + " UTC").toLocaleString();
690 }