2 * ========================LICENSE_START=================================
5 * Copyright (C) 2020 Nordix Foundation
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
11 * http://www.apache.org/licenses/LICENSE-2.0
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===================================
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";
42 } from "@app/interfaces/policy.types";
43 import { PolicyService } from "@app/services/policy/policy.service";
44 import { ConfirmDialogService } from "@app/services/ui/confirm-dialog.service";
45 import { NotificationService } from "@app/services/ui/notification.service";
46 import { UiService } from "@app/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";
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>;
63 const policyInstances = {
64 policy_ids: ["policy1", "policy2"],
66 const policyTypeSchema = JSON.parse(
67 '{"title": "1", "description": "Type 1 policy type"}'
73 service_id: "service",
80 service_id: "service",
83 const policy1Status = {
84 last_modified: lastModifiedTime,
86 const policy2Status = {
87 last_modified: lastModifiedTime,
90 const policyIdToInstanceMap = {
94 const policyIdToStatusMap = {
95 policy1: policy1Status,
96 policy2: policy2Status,
100 selector: "policy-instance-compnent-host-component",
102 "<nrcp-policy-instance [policyTypeSchema]=policyType></nrcp-policy-instance>",
104 class PolicyInstanceComponentHostComponent {
108 schemaObject: policyTypeSchema,
109 } as PolicyTypeSchema;
112 beforeEach(async () => {
113 policyServiceSpy = jasmine.createSpyObj("PolicyService", [
114 "getPolicyInstancesByType",
120 dialogSpy = jasmine.createSpyObj("MatDialog", ["open"]);
121 notificationServiceSpy = jasmine.createSpyObj("NotificationService", [
125 confirmServiceSpy = jasmine.createSpyObj("ConfirmDialogService", [
129 TestBed.configureTestingModule({
131 BrowserAnimationsModule,
137 ToastrModule.forRoot(),
139 schemas: [CUSTOM_ELEMENTS_SCHEMA],
141 PolicyInstanceComponent,
142 PolicyInstanceComponentHostComponent,
145 { provide: PolicyService, useValue: policyServiceSpy },
146 { provide: MatDialog, useValue: dialogSpy },
147 { provide: NotificationService, useValue: notificationServiceSpy },
148 { provide: ConfirmDialogService, useValue: confirmServiceSpy },
154 describe("content and dialogs", () => {
156 policyServiceSpy.getPolicyInstancesByType.and.returnValue(
159 policyServiceSpy.getPolicyInstance.and.callFake(function (
162 return of(policyIdToInstanceMap[policyId]);
164 policyServiceSpy.getPolicyStatus.and.callFake(function (
167 return of(policyIdToStatusMap[policyId]);
170 compileAndGetComponents();
173 it("should create", () => {
174 expect(hostComponent).toBeTruthy();
176 expect(componentUnderTest).toBeTruthy();
179 it("should set correct dark mode from UIService", () => {
180 const uiService: UiService = TestBed.inject(UiService);
181 expect(componentUnderTest.darkMode).toBeTruthy();
183 uiService.darkModeState.next(false);
184 hostFixture.detectChanges();
185 expect(componentUnderTest.darkMode).toBeFalsy();
188 it("should contain number of instances heading and value, create and refresh buttons, and policies table", async () => {
189 const instancesHeading = hostFixture.debugElement.nativeElement.querySelector(
192 expect(instancesHeading.innerText).toContain("Number of instances: 2");
194 const createButton: MatButtonHarness = await loader.getHarness(
195 MatButtonHarness.with({ selector: "#createButton" })
197 expect(createButton).toBeTruthy();
198 const createIcon = hostFixture.debugElement.nativeElement.querySelector(
201 expect(createIcon.innerText).toContain("add_box");
203 const refreshButton: MatButtonHarness = await loader.getHarness(
204 MatButtonHarness.with({ selector: "#refreshButton" })
206 expect(refreshButton).toBeTruthy();
207 const refreshIcon = hostFixture.debugElement.nativeElement.querySelector(
210 expect(refreshIcon.innerText).toContain("refresh");
212 const policiesTable = await loader.getHarness(
213 MatTableHarness.with({ selector: "#policiesTable" })
215 expect(policiesTable).toBeTruthy();
218 it("should open dialog to create policy and refresh policies after successful creation", async () => {
219 const dialogRefSpy = setupDialogRefSpy();
220 dialogSpy.open.and.returnValue(dialogRefSpy);
222 spyOn(componentUnderTest, "getPolicyInstances");
224 const createButton: MatButtonHarness = await loader.getHarness(
225 MatButtonHarness.with({ selector: "#createButton" })
227 await createButton.click();
229 expect(dialogSpy.open).toHaveBeenCalledWith(
230 PolicyInstanceDialogComponent,
237 panelClass: "dark-theme",
239 createSchema: policyTypeSchema,
247 expect(componentUnderTest.getPolicyInstances).toHaveBeenCalled();
250 it("should open dialog to edit policy and refresh policies after successful update", async () => {
251 const dialogRefSpy = setupDialogRefSpy();
252 dialogSpy.open.and.returnValue(dialogRefSpy);
254 spyOn(componentUnderTest, "getPolicyInstances");
256 const editButton: MatButtonHarness = await loader.getHarness(
257 MatButtonHarness.with({ selector: "#policy1EditButton" })
259 await editButton.click();
261 expect(dialogSpy.open).toHaveBeenCalledWith(
262 PolicyInstanceDialogComponent,
269 panelClass: "dark-theme",
271 createSchema: policyTypeSchema,
272 instanceId: "policy1",
279 expect(componentUnderTest.getPolicyInstances).toHaveBeenCalled();
282 it("should open dialog to edit policy and not refresh policies when dialog closed wihtout submit", async () => {
283 const dialogRefSpy = setupDialogRefSpy(false);
284 dialogSpy.open.and.returnValue(dialogRefSpy);
286 spyOn(componentUnderTest, "getPolicyInstances");
288 const editButton: MatButtonHarness = await loader.getHarness(
289 MatButtonHarness.with({ selector: "#policy1EditButton" })
291 await editButton.click();
293 expect(componentUnderTest.getPolicyInstances).not.toHaveBeenCalled();
296 it("should open instance dialog when clicking in any policy cell in table", async () => {
297 spyOn(componentUnderTest, "modifyInstance");
299 const policiesTable = await loader.getHarness(
300 MatTableHarness.with({ selector: "#policiesTable" })
302 const firstRow = (await policiesTable.getRows())[0];
303 const idCell = (await firstRow.getCells())[0];
304 (await idCell.host()).click();
305 const ownerCell = (await firstRow.getCells())[1];
306 (await ownerCell.host()).click();
307 const serviceCell = (await firstRow.getCells())[2];
308 (await serviceCell.host()).click();
309 const lastModifiedCell = (await firstRow.getCells())[3];
310 (await lastModifiedCell.host()).click();
312 // Totally unnecessary call just to make the bloody framework count the number of calls to the spy correctly!
313 await policiesTable.getRows();
315 expect(componentUnderTest.modifyInstance).toHaveBeenCalledTimes(4);
318 it("should open dialog asking for delete and delete when ok response and refresh table afterwards", async () => {
319 const dialogRefSpy = setupDialogRefSpy();
320 confirmServiceSpy.openConfirmDialog.and.returnValue(dialogRefSpy);
321 const createResponse = { status: 204 } as HttpResponse<Object>;
322 policyServiceSpy.deletePolicy.and.returnValue(of(createResponse));
324 spyOn(componentUnderTest, "getPolicyInstances");
325 const deleteButton: MatButtonHarness = await loader.getHarness(
326 MatButtonHarness.with({ selector: "#policy1DeleteButton" })
328 await deleteButton.click();
330 expect(confirmServiceSpy.openConfirmDialog).toHaveBeenCalledWith(
332 "Are you sure you want to delete this policy instance?"
334 expect(policyServiceSpy.deletePolicy).toHaveBeenCalledWith("policy1");
335 expect(notificationServiceSpy.success).toHaveBeenCalledWith(
338 expect(componentUnderTest.getPolicyInstances).toHaveBeenCalled();
341 it("should open dialog asking for delete and not delete whith Cancel as response", async () => {
342 const dialogRefSpy = setupDialogRefSpy(false);
343 confirmServiceSpy.openConfirmDialog.and.returnValue(dialogRefSpy);
345 const deleteButton: MatButtonHarness = await loader.getHarness(
346 MatButtonHarness.with({ selector: "#policy1DeleteButton" })
348 await deleteButton.click();
350 expect(policyServiceSpy.deletePolicy).not.toHaveBeenCalled();
353 it("should refresh table", async () => {
354 spyOn(componentUnderTest, "getPolicyInstances");
356 const refreshButton: MatButtonHarness = await loader.getHarness(
357 MatButtonHarness.with({ selector: "#refreshButton" })
359 await refreshButton.click();
361 expect(componentUnderTest.getPolicyInstances).toHaveBeenCalled();
365 describe("no instances", () => {
367 policyServiceSpy.getPolicyInstancesByType.and.returnValue(
370 } as PolicyInstances)
373 compileAndGetComponents();
376 it("should display message of no instances", async () => {
377 const policiesTable = await loader.getHarness(
378 MatTableHarness.with({ selector: "#policiesTable" })
380 const footerRows = await policiesTable.getFooterRows();
381 const footerRow = footerRows[0];
382 const footerRowHost = await footerRow.host();
384 expect(await footerRowHost.hasClass("display-none")).toBeFalsy();
385 const footerTexts = await footerRow.getCellTextByColumnName();
386 expect(footerTexts["noRecordsFound"]).toEqual("No records found.");
390 describe("#policiesTable", () => {
391 const expectedPolicy1Row = {
392 instanceId: "policy1",
395 lastModified: toLocalTime(lastModifiedTime),
396 action: "editdelete",
400 policyServiceSpy.getPolicyInstancesByType.and.returnValue(
403 policyServiceSpy.getPolicyInstance.and.callFake(function (
406 return of(policyIdToInstanceMap[policyId]);
408 policyServiceSpy.getPolicyStatus.and.callFake(function (
411 return of(policyIdToStatusMap[policyId]);
414 compileAndGetComponents();
417 it("should contain correct headings", async () => {
418 const policiesTable = await loader.getHarness(
419 MatTableHarness.with({ selector: "#policiesTable" })
421 const headerRow = (await policiesTable.getHeaderRows())[0];
422 const headers = await headerRow.getCellTextByColumnName();
424 expect(headers).toEqual({
425 instanceId: "Instance",
428 lastModified: "Last modified",
433 it("should contain data after initialization", async () => {
434 const expectedJobRows = [
437 instanceId: "policy2",
440 lastModified: toLocalTime(lastModifiedTime),
441 action: "editdelete",
444 const policiesTable = await loader.getHarness(
445 MatTableHarness.with({ selector: "#policiesTable" })
447 const policyRows = await policiesTable.getRows();
448 expect(policyRows.length).toEqual(2);
449 policyRows.forEach((row) => {
450 row.getCellTextByColumnName().then((values) => {
451 expect(expectedJobRows).toContain(jasmine.objectContaining(values));
455 // No message about no entries
456 const footerRows = await policiesTable.getFooterRows();
457 const footerRow = await footerRows[0];
458 const footerRowHost = await footerRow.host();
460 expect(await footerRowHost.hasClass("display-none")).toBeTruthy();
463 it("should have filtering for all four policy data headings", async () => {
464 const policiesTable = await loader.getHarness(
465 MatTableHarness.with({ selector: "#policiesTable" })
468 const idFilterInput = await loader.getHarness(
469 MatInputHarness.with({ selector: "#policyInstanceIdFilter" })
471 await idFilterInput.setValue("1");
472 const policyRows = await policiesTable.getRows();
473 expect(policyRows.length).toEqual(1);
474 expect(await policyRows[0].getCellTextByColumnName()).toEqual(
478 const targetFilterInput = await loader.getHarness(
479 MatInputHarness.with({ selector: "#policyInstanceTargetFilter" })
481 expect(targetFilterInput).toBeTruthy();
483 const ownerFilterInput = await loader.getHarness(
484 MatInputHarness.with({ selector: "#policyInstanceOwnerFilter" })
486 expect(ownerFilterInput).toBeTruthy();
488 const lastModifiedFilterInput = await loader.getHarness(
489 MatInputHarness.with({ selector: "#policyInstanceLastModifiedFilter" })
491 expect(lastModifiedFilterInput).toBeTruthy();
494 it("should not sort when click in filter inputs", async () => {
495 spyOn(componentUnderTest, "stopSort").and.callThrough();
497 const idFilterInputDiv = hostFixture.debugElement.nativeElement.querySelector(
500 idFilterInputDiv.click();
502 const targetFilterInputDiv = hostFixture.debugElement.nativeElement.querySelector(
505 targetFilterInputDiv.click();
507 const ownerFilterInputDiv = hostFixture.debugElement.nativeElement.querySelector(
510 ownerFilterInputDiv.click();
512 const lastModifiedFilterInputDiv = hostFixture.debugElement.nativeElement.querySelector(
513 "#lastModifiedSortStop"
515 lastModifiedFilterInputDiv.click();
517 expect(componentUnderTest.stopSort).toHaveBeenCalledTimes(4);
519 const eventSpy = jasmine.createSpyObj("any", ["stopPropagation"]);
520 componentUnderTest.stopSort(eventSpy);
521 expect(eventSpy.stopPropagation).toHaveBeenCalled();
524 describe("#sorting", () => {
525 it("should verify sort functionality on the table", async () => {
526 const sort = await loader.getHarness(MatSortHarness);
527 const headers = await sort.getSortHeaders({ sortDirection: "" });
528 expect(headers.length).toBe(4);
530 await headers[0].click();
531 expect(await headers[0].isActive()).toBe(true);
532 expect(await headers[0].getSortDirection()).toBe("asc");
534 await headers[0].click();
535 expect(await headers[0].getSortDirection()).toBe("desc");
538 it("should sort table asc and desc by first header", async () => {
539 const sort = await loader.getHarness(MatSortHarness);
540 const policyTable = await loader.getHarness(
541 MatTableHarness.with({ selector: "#policiesTable" })
543 const firstHeader = (await sort.getSortHeaders())[0];
544 expect(await firstHeader.getSortDirection()).toBe("");
546 await firstHeader.click();
547 expect(await firstHeader.getSortDirection()).toBe("asc");
548 let policyRows = await policyTable.getRows();
549 expect(await policyRows[0].getCellTextByColumnName()).toEqual(
553 await firstHeader.click();
554 expect(await firstHeader.getSortDirection()).toBe("desc");
555 policyRows = await policyTable.getRows();
557 await policyRows[policyRows.length - 1].getCellTextByColumnName()
558 ).toEqual(expectedPolicy1Row);
561 it("should sort table asc and desc by second header", async () => {
562 const sort = await loader.getHarness(MatSortHarness);
563 const jobsTable = await loader.getHarness(
564 MatTableHarness.with({ selector: "#policiesTable" })
566 const firstHeader = (await sort.getSortHeaders())[1];
567 expect(await firstHeader.getSortDirection()).toBe("");
569 await firstHeader.click();
570 expect(await firstHeader.getSortDirection()).toBe("asc");
571 let policyRows = await jobsTable.getRows();
572 policyRows = await jobsTable.getRows();
573 expect(await policyRows[0].getCellTextByColumnName()).toEqual(
577 await firstHeader.click();
578 expect(await firstHeader.getSortDirection()).toBe("desc");
579 policyRows = await jobsTable.getRows();
581 await policyRows[policyRows.length - 1].getCellTextByColumnName()
582 ).toEqual(expectedPolicy1Row);
585 it("should sort table asc and desc by third header", async () => {
586 const sort = await loader.getHarness(MatSortHarness);
587 const jobsTable = await loader.getHarness(
588 MatTableHarness.with({ selector: "#policiesTable" })
590 const firstHeader = (await sort.getSortHeaders())[2];
591 expect(await firstHeader.getSortDirection()).toBe("");
593 await firstHeader.click();
594 expect(await firstHeader.getSortDirection()).toBe("asc");
595 let policyRows = await jobsTable.getRows();
596 policyRows = await jobsTable.getRows();
597 expect(await policyRows[0].getCellTextByColumnName()).toEqual(
601 await firstHeader.click();
602 expect(await firstHeader.getSortDirection()).toBe("desc");
603 policyRows = await jobsTable.getRows();
605 await policyRows[policyRows.length - 1].getCellTextByColumnName()
606 ).toEqual(expectedPolicy1Row);
609 it("should sort table asc and desc by fourth header", async () => {
610 const sort = await loader.getHarness(MatSortHarness);
611 const jobsTable = await loader.getHarness(
612 MatTableHarness.with({ selector: "#policiesTable" })
614 const firstHeader = (await sort.getSortHeaders())[3];
615 expect(await firstHeader.getSortDirection()).toBe("");
617 await firstHeader.click();
618 expect(await firstHeader.getSortDirection()).toBe("asc");
619 let policyRows = await jobsTable.getRows();
620 policyRows = await jobsTable.getRows();
621 expect(await policyRows[0].getCellTextByColumnName()).toEqual(
625 await firstHeader.click();
626 expect(await firstHeader.getSortDirection()).toBe("desc");
627 policyRows = await jobsTable.getRows();
629 await policyRows[policyRows.length - 1].getCellTextByColumnName()
630 ).toEqual(expectedPolicy1Row);
635 function compileAndGetComponents() {
636 TestBed.compileComponents();
638 hostFixture = TestBed.createComponent(PolicyInstanceComponentHostComponent);
639 hostComponent = hostFixture.componentInstance;
640 componentUnderTest = hostFixture.debugElement.query(
641 By.directive(PolicyInstanceComponent)
643 hostFixture.detectChanges();
644 loader = TestbedHarnessEnvironment.loader(hostFixture);
645 return { hostFixture, hostComponent, componentUnderTest, loader };
649 function setupDialogRefSpy(returnValue: boolean = true) {
650 const afterClosedObservable = new Observable((observer) => {
651 observer.next(returnValue);
654 const dialogRefSpy = jasmine.createSpyObj("MatDialogRef", ["afterClosed"]);
655 dialogRefSpy.afterClosed.and.returnValue(afterClosedObservable);
659 function toLocalTime(utcTime: string): string {
660 const date = new Date(utcTime);
661 const toutc = date.toUTCString();
662 return new Date(toutc + " UTC").toLocaleString();