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",
119 policyServiceSpy.getPolicyInstancesByType.and.returnValue(
122 policyServiceSpy.getPolicyInstance.and.callFake(function (
125 return of(policyIdToInstanceMap[policyId]);
127 policyServiceSpy.getPolicyStatus.and.callFake(function (policyId: string) {
128 return of(policyIdToStatusMap[policyId]);
131 dialogSpy = jasmine.createSpyObj("MatDialog", ["open"]);
132 notificationServiceSpy = jasmine.createSpyObj("NotificationService", [
136 confirmServiceSpy = jasmine.createSpyObj("ConfirmDialogService", [
140 await TestBed.configureTestingModule({
142 BrowserAnimationsModule,
148 ToastrModule.forRoot(),
150 schemas: [CUSTOM_ELEMENTS_SCHEMA],
152 PolicyInstanceComponent,
153 PolicyInstanceComponentHostComponent,
156 { provide: PolicyService, useValue: policyServiceSpy },
157 { provide: MatDialog, useValue: dialogSpy },
158 { provide: NotificationService, useValue: notificationServiceSpy },
159 { provide: ConfirmDialogService, useValue: confirmServiceSpy },
162 }).compileComponents();
164 hostFixture = TestBed.createComponent(PolicyInstanceComponentHostComponent);
165 hostComponent = hostFixture.componentInstance;
166 componentUnderTest = hostFixture.debugElement.query(
167 By.directive(PolicyInstanceComponent)
169 hostFixture.detectChanges();
170 loader = TestbedHarnessEnvironment.loader(hostFixture);
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(PolicyInstanceDialogComponent, {
235 panelClass: "dark-theme",
237 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(PolicyInstanceDialogComponent, {
264 panelClass: "dark-theme",
266 createSchema: policyTypeSchema,
267 instanceId: "policy1",
273 expect(componentUnderTest.getPolicyInstances).toHaveBeenCalled();
276 it("should open dialog to edit policy and not refresh policies when dialog closed wihtout submit", async () => {
277 const dialogRefSpy = setupDialogRefSpy(false);
278 dialogSpy.open.and.returnValue(dialogRefSpy);
280 spyOn(componentUnderTest, "getPolicyInstances");
282 const editButton: MatButtonHarness = await loader.getHarness(
283 MatButtonHarness.with({ selector: "#policy1EditButton" })
285 await editButton.click();
287 expect(componentUnderTest.getPolicyInstances).not.toHaveBeenCalled();
290 it("should open instance dialog when clicking in any policy cell in table", async () => {
291 spyOn(componentUnderTest, "modifyInstance");
293 const policiesTable = await loader.getHarness(
294 MatTableHarness.with({ selector: "#policiesTable" })
296 const firstRow = (await policiesTable.getRows())[0];
297 const idCell = (await firstRow.getCells())[0];
298 (await idCell.host()).click();
299 const ownerCell = (await firstRow.getCells())[1];
300 (await ownerCell.host()).click();
301 const serviceCell = (await firstRow.getCells())[2];
302 (await serviceCell.host()).click();
303 const lastModifiedCell = (await firstRow.getCells())[3];
304 (await lastModifiedCell.host()).click();
306 // Totally unnecessary call just to make the bloody framework count the number of calls to the spy correctly!
307 await policiesTable.getRows();
309 expect(componentUnderTest.modifyInstance).toHaveBeenCalledTimes(4);
312 it("should open dialog asking for delete and delete when ok response and refresh table afterwards", async () => {
313 const dialogRefSpy = setupDialogRefSpy();
314 confirmServiceSpy.openConfirmDialog.and.returnValue(dialogRefSpy);
315 const createResponse = { status: 204 } as HttpResponse<Object>;
316 policyServiceSpy.deletePolicy.and.returnValue(of(createResponse));
318 spyOn(componentUnderTest, "getPolicyInstances");
319 const deleteButton: MatButtonHarness = await loader.getHarness(
320 MatButtonHarness.with({ selector: "#policy1DeleteButton" })
322 await deleteButton.click();
324 expect(confirmServiceSpy.openConfirmDialog).toHaveBeenCalledWith(
325 "Are you sure you want to delete this policy instance?"
327 expect(policyServiceSpy.deletePolicy).toHaveBeenCalledWith("policy1");
328 expect(notificationServiceSpy.success).toHaveBeenCalledWith(
331 expect(componentUnderTest.getPolicyInstances).toHaveBeenCalled();
334 it("should open dialog asking for delete and not delete whith Cancel as response", async () => {
335 const dialogRefSpy = setupDialogRefSpy(false);
336 confirmServiceSpy.openConfirmDialog.and.returnValue(dialogRefSpy);
338 const deleteButton: MatButtonHarness = await loader.getHarness(
339 MatButtonHarness.with({ selector: "#policy1DeleteButton" })
341 await deleteButton.click();
343 expect(policyServiceSpy.deletePolicy).not.toHaveBeenCalled();
346 it("should refresh table", async () => {
347 spyOn(componentUnderTest, "getPolicyInstances");
349 const refreshButton: MatButtonHarness = await loader.getHarness(
350 MatButtonHarness.with({ selector: "#refreshButton" })
352 await refreshButton.click();
354 expect(componentUnderTest.getPolicyInstances).toHaveBeenCalled();
357 describe("#policiesTable", () => {
358 const expectedPolicy1Row = {
359 instanceId: "policy1",
362 lastModified: toLocalTime(lastModifiedTime),
363 action: "editdelete",
366 it("should contain correct headings", async () => {
367 const policiesTable = await loader.getHarness(
368 MatTableHarness.with({ selector: "#policiesTable" })
370 const headerRow = (await policiesTable.getHeaderRows())[0];
371 const headers = await headerRow.getCellTextByColumnName();
373 expect(headers).toEqual({
374 instanceId: "Instance",
377 lastModified: "Last modified",
382 it("should contain data after initialization", async () => {
383 const expectedJobRows = [
386 instanceId: "policy2",
389 lastModified: toLocalTime(lastModifiedTime),
390 action: "editdelete",
393 const policiesTable = await loader.getHarness(
394 MatTableHarness.with({ selector: "#policiesTable" })
396 const policyRows = await policiesTable.getRows();
397 expect(policyRows.length).toEqual(2);
398 policyRows.forEach((row) => {
399 row.getCellTextByColumnName().then((values) => {
400 expect(expectedJobRows).toContain(jasmine.objectContaining(values));
405 it("should have filtering for all four policy data headings", async () => {
406 const policiesTable = await loader.getHarness(
407 MatTableHarness.with({ selector: "#policiesTable" })
410 const idFilterInput = await loader.getHarness(
411 MatInputHarness.with({ selector: "#policyInstanceIdFilter" })
413 await idFilterInput.setValue("1");
414 const policyRows = await policiesTable.getRows();
415 expect(policyRows.length).toEqual(1);
416 expect(await policyRows[0].getCellTextByColumnName()).toEqual(
420 const targetFilterInput = await loader.getHarness(
421 MatInputHarness.with({ selector: "#policyInstanceTargetFilter" })
423 expect(targetFilterInput).toBeTruthy();
425 const ownerFilterInput = await loader.getHarness(
426 MatInputHarness.with({ selector: "#policyInstanceOwnerFilter" })
428 expect(ownerFilterInput).toBeTruthy();
430 const lastModifiedFilterInput = await loader.getHarness(
431 MatInputHarness.with({ selector: "#policyInstanceLastModifiedFilter" })
433 expect(lastModifiedFilterInput).toBeTruthy();
436 it("should not sort when click in filter inputs", async () => {
437 spyOn(componentUnderTest, "stopSort").and.callThrough();
439 const idFilterInputDiv = hostFixture.debugElement.nativeElement.querySelector(
442 idFilterInputDiv.click();
444 const targetFilterInputDiv = hostFixture.debugElement.nativeElement.querySelector(
447 targetFilterInputDiv.click();
449 const ownerFilterInputDiv = hostFixture.debugElement.nativeElement.querySelector(
452 ownerFilterInputDiv.click();
454 const lastModifiedFilterInputDiv = hostFixture.debugElement.nativeElement.querySelector(
455 "#lastModifiedSortStop"
457 lastModifiedFilterInputDiv.click();
459 expect(componentUnderTest.stopSort).toHaveBeenCalledTimes(4);
461 const eventSpy = jasmine.createSpyObj("any", ["stopPropagation"]);
462 componentUnderTest.stopSort(eventSpy);
463 expect(eventSpy.stopPropagation).toHaveBeenCalled();
466 describe("#sorting", () => {
467 it("should verify sort functionality on the table", async () => {
468 const sort = await loader.getHarness(MatSortHarness);
469 const headers = await sort.getSortHeaders({ sortDirection: "" });
470 expect(headers.length).toBe(4);
472 await headers[0].click();
473 expect(await headers[0].isActive()).toBe(true);
474 expect(await headers[0].getSortDirection()).toBe("asc");
476 await headers[0].click();
477 expect(await headers[0].getSortDirection()).toBe("desc");
480 it("should sort table asc and desc by first header", async () => {
481 const sort = await loader.getHarness(MatSortHarness);
482 const policyTable = await loader.getHarness(
483 MatTableHarness.with({ selector: "#policiesTable" })
485 const firstHeader = (await sort.getSortHeaders())[0];
486 expect(await firstHeader.getSortDirection()).toBe("");
488 await firstHeader.click();
489 expect(await firstHeader.getSortDirection()).toBe("asc");
490 let policyRows = await policyTable.getRows();
491 expect(await policyRows[0].getCellTextByColumnName()).toEqual(
495 await firstHeader.click();
496 expect(await firstHeader.getSortDirection()).toBe("desc");
497 policyRows = await policyTable.getRows();
499 await policyRows[policyRows.length - 1].getCellTextByColumnName()
500 ).toEqual(expectedPolicy1Row);
503 it("should sort table asc and desc by second header", async () => {
504 const sort = await loader.getHarness(MatSortHarness);
505 const jobsTable = await loader.getHarness(
506 MatTableHarness.with({ selector: "#policiesTable" })
508 const firstHeader = (await sort.getSortHeaders())[1];
509 expect(await firstHeader.getSortDirection()).toBe("");
511 await firstHeader.click();
512 expect(await firstHeader.getSortDirection()).toBe("asc");
513 let policyRows = await jobsTable.getRows();
514 policyRows = await jobsTable.getRows();
515 expect(await policyRows[0].getCellTextByColumnName()).toEqual(
519 await firstHeader.click();
520 expect(await firstHeader.getSortDirection()).toBe("desc");
521 policyRows = await jobsTable.getRows();
523 await policyRows[policyRows.length - 1].getCellTextByColumnName()
524 ).toEqual(expectedPolicy1Row);
527 it("should sort table asc and desc by third header", async () => {
528 const sort = await loader.getHarness(MatSortHarness);
529 const jobsTable = await loader.getHarness(
530 MatTableHarness.with({ selector: "#policiesTable" })
532 const firstHeader = (await sort.getSortHeaders())[2];
533 expect(await firstHeader.getSortDirection()).toBe("");
535 await firstHeader.click();
536 expect(await firstHeader.getSortDirection()).toBe("asc");
537 let policyRows = await jobsTable.getRows();
538 policyRows = await jobsTable.getRows();
539 expect(await policyRows[0].getCellTextByColumnName()).toEqual(
543 await firstHeader.click();
544 expect(await firstHeader.getSortDirection()).toBe("desc");
545 policyRows = await jobsTable.getRows();
547 await policyRows[policyRows.length - 1].getCellTextByColumnName()
548 ).toEqual(expectedPolicy1Row);
551 it("should sort table asc and desc by fourth header", async () => {
552 const sort = await loader.getHarness(MatSortHarness);
553 const jobsTable = await loader.getHarness(
554 MatTableHarness.with({ selector: "#policiesTable" })
556 const firstHeader = (await sort.getSortHeaders())[3];
557 expect(await firstHeader.getSortDirection()).toBe("");
559 await firstHeader.click();
560 expect(await firstHeader.getSortDirection()).toBe("asc");
561 let policyRows = await jobsTable.getRows();
562 policyRows = await jobsTable.getRows();
563 expect(await policyRows[0].getCellTextByColumnName()).toEqual(
567 await firstHeader.click();
568 expect(await firstHeader.getSortDirection()).toBe("desc");
569 policyRows = await jobsTable.getRows();
571 await policyRows[policyRows.length - 1].getCellTextByColumnName()
572 ).toEqual(expectedPolicy1Row);
578 function setupDialogRefSpy(returnValue: boolean = true) {
579 const afterClosedObservable = new Observable((observer) => {
580 observer.next(returnValue);
583 const dialogRefSpy = jasmine.createSpyObj("MatDialogRef", ["afterClosed"]);
584 dialogRefSpy.afterClosed.and.returnValue(afterClosedObservable);
588 function toLocalTime(utcTime: string): string {
589 const date = new Date(utcTime);
590 const toutc = date.toUTCString();
591 return new Date(toutc + " UTC").toLocaleString();