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 "@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";
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 =
190 hostFixture.debugElement.nativeElement.querySelector("div");
191 expect(instancesHeading.innerText).toContain("Number of instances: 2");
193 const createButton: MatButtonHarness = await loader.getHarness(
194 MatButtonHarness.with({ selector: "#createButton" })
196 expect(createButton).toBeTruthy();
198 hostFixture.debugElement.nativeElement.querySelector("#createIcon");
199 expect(createIcon.innerText).toContain("add_box");
201 const refreshButton: MatButtonHarness = await loader.getHarness(
202 MatButtonHarness.with({ selector: "#refreshButton" })
204 expect(refreshButton).toBeTruthy();
206 hostFixture.debugElement.nativeElement.querySelector("#refreshIcon");
207 expect(refreshIcon.innerText).toContain("refresh");
209 const policiesTable = await loader.getHarness(
210 MatTableHarness.with({ selector: "#policiesTable" })
212 expect(policiesTable).toBeTruthy();
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);
219 spyOn(componentUnderTest, "getPolicyInstances");
221 const createButton: MatButtonHarness = await loader.getHarness(
222 MatButtonHarness.with({ selector: "#createButton" })
224 await createButton.click();
226 expect(dialogSpy.open).toHaveBeenCalledWith(
227 PolicyInstanceDialogComponent,
234 panelClass: "dark-theme",
236 createSchema: policyTypeSchema,
244 expect(componentUnderTest.getPolicyInstances).toHaveBeenCalled();
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);
251 spyOn(componentUnderTest, "getPolicyInstances");
253 const editButton: MatButtonHarness = await loader.getHarness(
254 MatButtonHarness.with({ selector: "#policy1EditButton" })
256 await editButton.click();
258 expect(dialogSpy.open).toHaveBeenCalledWith(
259 PolicyInstanceDialogComponent,
266 panelClass: "dark-theme",
268 createSchema: policyTypeSchema,
269 instanceId: "policy1",
276 expect(componentUnderTest.getPolicyInstances).toHaveBeenCalled();
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);
283 spyOn(componentUnderTest, "getPolicyInstances");
285 const editButton: MatButtonHarness = await loader.getHarness(
286 MatButtonHarness.with({ selector: "#policy1EditButton" })
288 await editButton.click();
290 expect(componentUnderTest.getPolicyInstances).not.toHaveBeenCalled();
293 it("should open instance dialog when clicking in any policy cell in table", async () => {
294 spyOn(componentUnderTest, "modifyInstance");
296 const policiesTable = await loader.getHarness(
297 MatTableHarness.with({ selector: "#policiesTable" })
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();
309 // Totally unnecessary call just to make the framework count the number of calls to the spy correctly!
310 await policiesTable.getRows();
312 expect(componentUnderTest.modifyInstance).toHaveBeenCalledTimes(4);
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));
321 spyOn(componentUnderTest, "getPolicyInstances");
322 const deleteButton: MatButtonHarness = await loader.getHarness(
323 MatButtonHarness.with({ selector: "#policy1DeleteButton" })
325 await deleteButton.click();
327 expect(confirmServiceSpy.openConfirmDialog).toHaveBeenCalledWith(
329 "Are you sure you want to delete this policy instance?"
331 expect(policyServiceSpy.deletePolicy).toHaveBeenCalledWith("policy1");
332 expect(notificationServiceSpy.success).toHaveBeenCalledWith(
335 expect(componentUnderTest.getPolicyInstances).toHaveBeenCalled();
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);
342 const deleteButton: MatButtonHarness = await loader.getHarness(
343 MatButtonHarness.with({ selector: "#policy1DeleteButton" })
345 await deleteButton.click();
347 expect(policyServiceSpy.deletePolicy).not.toHaveBeenCalled();
350 it("should refresh table", async () => {
351 spyOn(componentUnderTest, "getPolicyInstances");
353 const refreshButton: MatButtonHarness = await loader.getHarness(
354 MatButtonHarness.with({ selector: "#refreshButton" })
356 await refreshButton.click();
358 expect(componentUnderTest.getPolicyInstances).toHaveBeenCalled();
362 describe("no instances", () => {
364 policyServiceSpy.getPolicyInstancesByType.and.returnValue(
367 } as PolicyInstances)
370 compileAndGetComponents();
373 it("should display message of no instances", async () => {
374 const policiesTable = await loader.getHarness(
375 MatTableHarness.with({ selector: "#policiesTable" })
377 const footerRows = await policiesTable.getFooterRows();
378 const footerRow = footerRows[0];
379 const footerRowHost = await footerRow.host();
381 expect(await footerRowHost.hasClass("display-none")).toBeFalsy();
382 const footerTexts = await footerRow.getCellTextByColumnName();
383 expect(footerTexts["noRecordsFound"]).toEqual("No records found.");
387 describe("#policiesTable", () => {
388 const expectedPolicy1Row = {
389 instanceId: "policy1",
392 lastModified: toLocalTime(lastModifiedTime),
393 action: "editdelete",
397 policyServiceSpy.getPolicyInstancesByType.and.returnValue(
400 policyServiceSpy.getPolicyInstance.and.callFake(function (
403 return of(policyIdToInstanceMap[policyId]);
405 policyServiceSpy.getPolicyStatus.and.callFake(function (
408 return of(policyIdToStatusMap[policyId]);
411 compileAndGetComponents();
414 it("should contain correct headings", async () => {
415 const policiesTable = await loader.getHarness(
416 MatTableHarness.with({ selector: "#policiesTable" })
418 const headerRow = (await policiesTable.getHeaderRows())[0];
419 const headers = await headerRow.getCellTextByColumnName();
421 expect(headers).toEqual({
422 instanceId: "Instance",
425 lastModified: "Last modified",
430 it("should contain data after initialization", async () => {
431 const expectedJobRows = [
434 instanceId: "policy2",
437 lastModified: toLocalTime(lastModifiedTime),
438 action: "editdelete",
441 const policiesTable = await loader.getHarness(
442 MatTableHarness.with({ selector: "#policiesTable" })
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));
452 // No message about no entries
453 const footerRows = await policiesTable.getFooterRows();
454 const footerRow = await footerRows[0];
455 const footerRowHost = await footerRow.host();
457 expect(await footerRowHost.hasClass("display-none")).toBeTruthy();
460 it("should have filtering for all four policy data headings", async () => {
461 const policiesTable = await loader.getHarness(
462 MatTableHarness.with({ selector: "#policiesTable" })
465 const idFilterInput = await loader.getHarness(
466 MatInputHarness.with({ selector: "#policyInstanceIdFilter" })
468 await idFilterInput.setValue("1");
469 const policyRows = await policiesTable.getRows();
470 expect(policyRows.length).toEqual(1);
471 expect(await policyRows[0].getCellTextByColumnName()).toEqual(
475 const targetFilterInput = await loader.getHarness(
476 MatInputHarness.with({ selector: "#policyInstanceTargetFilter" })
478 expect(targetFilterInput).toBeTruthy();
480 const ownerFilterInput = await loader.getHarness(
481 MatInputHarness.with({ selector: "#policyInstanceOwnerFilter" })
483 expect(ownerFilterInput).toBeTruthy();
485 const lastModifiedFilterInput = await loader.getHarness(
486 MatInputHarness.with({ selector: "#policyInstanceLastModifiedFilter" })
488 expect(lastModifiedFilterInput).toBeTruthy();
491 it("should not sort when click in filter inputs", async () => {
492 spyOn(componentUnderTest, "stopSort").and.callThrough();
494 const idFilterInputDiv =
495 hostFixture.debugElement.nativeElement.querySelector("#idSortStop");
496 idFilterInputDiv.click();
498 const targetFilterInputDiv =
499 hostFixture.debugElement.nativeElement.querySelector("#targetSortStop");
500 targetFilterInputDiv.click();
502 const ownerFilterInputDiv =
503 hostFixture.debugElement.nativeElement.querySelector("#ownerSortStop");
504 ownerFilterInputDiv.click();
506 const lastModifiedFilterInputDiv =
507 hostFixture.debugElement.nativeElement.querySelector(
508 "#lastModifiedSortStop"
510 lastModifiedFilterInputDiv.click();
512 expect(componentUnderTest.stopSort).toHaveBeenCalledTimes(4);
514 const eventSpy = jasmine.createSpyObj("any", ["stopPropagation"]);
515 componentUnderTest.stopSort(eventSpy);
516 expect(eventSpy.stopPropagation).toHaveBeenCalled();
519 describe("#truncate data", () => {
520 it("should verify that data is correctly truncated when needed", async () => {
521 policyServiceSpy.getPolicyInstancesByType.and.returnValue(
524 policyServiceSpy.getPolicyInstance.and.callFake(function (
527 return of(policyIdToInstanceMap[policyId]);
529 policyServiceSpy.getPolicyStatus.and.callFake(function (
532 return of(policyIdToStatusMap[policyId]);
534 compileAndGetComponents();
535 componentUnderTest.slice = 1;
536 componentUnderTest.ngOnInit();
538 const policiesTable = await loader.getHarness(
539 MatTableHarness.with({ selector: "#policiesTable" })
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));
547 expect(componentUnderTest.truncated).toBeTruthy();
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);
557 await headers[0].click();
558 expect(await headers[0].isActive()).toBe(true);
559 expect(await headers[0].getSortDirection()).toBe("asc");
561 await headers[0].click();
562 expect(await headers[0].getSortDirection()).toBe("desc");
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" })
570 const firstHeader = (await sort.getSortHeaders())[0];
571 expect(await firstHeader.getSortDirection()).toBe("");
573 await firstHeader.click();
574 expect(await firstHeader.getSortDirection()).toBe("asc");
575 let policyRows = await policyTable.getRows();
576 expect(await policyRows[0].getCellTextByColumnName()).toEqual(
580 await firstHeader.click();
581 expect(await firstHeader.getSortDirection()).toBe("desc");
582 policyRows = await policyTable.getRows();
584 await policyRows[policyRows.length - 1].getCellTextByColumnName()
585 ).toEqual(expectedPolicy1Row);
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" })
593 const firstHeader = (await sort.getSortHeaders())[1];
594 expect(await firstHeader.getSortDirection()).toBe("");
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(
604 await firstHeader.click();
605 expect(await firstHeader.getSortDirection()).toBe("desc");
606 policyRows = await jobsTable.getRows();
608 await policyRows[policyRows.length - 1].getCellTextByColumnName()
609 ).toEqual(expectedPolicy1Row);
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" })
617 const firstHeader = (await sort.getSortHeaders())[2];
618 expect(await firstHeader.getSortDirection()).toBe("");
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(
628 await firstHeader.click();
629 expect(await firstHeader.getSortDirection()).toBe("desc");
630 policyRows = await jobsTable.getRows();
632 await policyRows[policyRows.length - 1].getCellTextByColumnName()
633 ).toEqual(expectedPolicy1Row);
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" })
641 const firstHeader = (await sort.getSortHeaders())[3];
642 expect(await firstHeader.getSortDirection()).toBe("");
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(
652 await firstHeader.click();
653 expect(await firstHeader.getSortDirection()).toBe("desc");
654 policyRows = await jobsTable.getRows();
656 await policyRows[policyRows.length - 1].getCellTextByColumnName()
657 ).toEqual(expectedPolicy1Row);
662 function compileAndGetComponents() {
663 TestBed.compileComponents();
665 hostFixture = TestBed.createComponent(PolicyInstanceComponentHostComponent);
666 hostComponent = hostFixture.componentInstance;
667 componentUnderTest = hostFixture.debugElement.query(
668 By.directive(PolicyInstanceComponent)
670 hostFixture.detectChanges();
671 loader = TestbedHarnessEnvironment.loader(hostFixture);
672 return { hostFixture, hostComponent, componentUnderTest, loader };
676 function setupDialogRefSpy(returnValue: boolean = true) {
677 const afterClosedObservable = new Observable((observer) => {
678 observer.next(returnValue);
681 const dialogRefSpy = jasmine.createSpyObj("MatDialogRef", ["afterClosed"]);
682 dialogRefSpy.afterClosed.and.returnValue(afterClosedObservable);
686 function toLocalTime(utcTime: string): string {
687 const date = new Date(utcTime);
688 const toutc = date.toUTCString();
689 return new Date(toutc + " UTC").toLocaleString();