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(
326 "Are you sure you want to delete this policy instance?"
328 expect(policyServiceSpy.deletePolicy).toHaveBeenCalledWith("policy1");
329 expect(notificationServiceSpy.success).toHaveBeenCalledWith(
332 expect(componentUnderTest.getPolicyInstances).toHaveBeenCalled();
335 it("should open dialog asking for delete and not delete whith Cancel as response", async () => {
336 const dialogRefSpy = setupDialogRefSpy(false);
337 confirmServiceSpy.openConfirmDialog.and.returnValue(dialogRefSpy);
339 const deleteButton: MatButtonHarness = await loader.getHarness(
340 MatButtonHarness.with({ selector: "#policy1DeleteButton" })
342 await deleteButton.click();
344 expect(policyServiceSpy.deletePolicy).not.toHaveBeenCalled();
347 it("should refresh table", async () => {
348 spyOn(componentUnderTest, "getPolicyInstances");
350 const refreshButton: MatButtonHarness = await loader.getHarness(
351 MatButtonHarness.with({ selector: "#refreshButton" })
353 await refreshButton.click();
355 expect(componentUnderTest.getPolicyInstances).toHaveBeenCalled();
358 describe("#policiesTable", () => {
359 const expectedPolicy1Row = {
360 instanceId: "policy1",
363 lastModified: toLocalTime(lastModifiedTime),
364 action: "editdelete",
367 it("should contain correct headings", async () => {
368 const policiesTable = await loader.getHarness(
369 MatTableHarness.with({ selector: "#policiesTable" })
371 const headerRow = (await policiesTable.getHeaderRows())[0];
372 const headers = await headerRow.getCellTextByColumnName();
374 expect(headers).toEqual({
375 instanceId: "Instance",
378 lastModified: "Last modified",
383 it("should contain data after initialization", async () => {
384 const expectedJobRows = [
387 instanceId: "policy2",
390 lastModified: toLocalTime(lastModifiedTime),
391 action: "editdelete",
394 const policiesTable = await loader.getHarness(
395 MatTableHarness.with({ selector: "#policiesTable" })
397 const policyRows = await policiesTable.getRows();
398 expect(policyRows.length).toEqual(2);
399 policyRows.forEach((row) => {
400 row.getCellTextByColumnName().then((values) => {
401 expect(expectedJobRows).toContain(jasmine.objectContaining(values));
406 it("should have filtering for all four policy data headings", async () => {
407 const policiesTable = await loader.getHarness(
408 MatTableHarness.with({ selector: "#policiesTable" })
411 const idFilterInput = await loader.getHarness(
412 MatInputHarness.with({ selector: "#policyInstanceIdFilter" })
414 await idFilterInput.setValue("1");
415 const policyRows = await policiesTable.getRows();
416 expect(policyRows.length).toEqual(1);
417 expect(await policyRows[0].getCellTextByColumnName()).toEqual(
421 const targetFilterInput = await loader.getHarness(
422 MatInputHarness.with({ selector: "#policyInstanceTargetFilter" })
424 expect(targetFilterInput).toBeTruthy();
426 const ownerFilterInput = await loader.getHarness(
427 MatInputHarness.with({ selector: "#policyInstanceOwnerFilter" })
429 expect(ownerFilterInput).toBeTruthy();
431 const lastModifiedFilterInput = await loader.getHarness(
432 MatInputHarness.with({ selector: "#policyInstanceLastModifiedFilter" })
434 expect(lastModifiedFilterInput).toBeTruthy();
437 it("should not sort when click in filter inputs", async () => {
438 spyOn(componentUnderTest, "stopSort").and.callThrough();
440 const idFilterInputDiv = hostFixture.debugElement.nativeElement.querySelector(
443 idFilterInputDiv.click();
445 const targetFilterInputDiv = hostFixture.debugElement.nativeElement.querySelector(
448 targetFilterInputDiv.click();
450 const ownerFilterInputDiv = hostFixture.debugElement.nativeElement.querySelector(
453 ownerFilterInputDiv.click();
455 const lastModifiedFilterInputDiv = hostFixture.debugElement.nativeElement.querySelector(
456 "#lastModifiedSortStop"
458 lastModifiedFilterInputDiv.click();
460 expect(componentUnderTest.stopSort).toHaveBeenCalledTimes(4);
462 const eventSpy = jasmine.createSpyObj("any", ["stopPropagation"]);
463 componentUnderTest.stopSort(eventSpy);
464 expect(eventSpy.stopPropagation).toHaveBeenCalled();
467 describe("#sorting", () => {
468 it("should verify sort functionality on the table", async () => {
469 const sort = await loader.getHarness(MatSortHarness);
470 const headers = await sort.getSortHeaders({ sortDirection: "" });
471 expect(headers.length).toBe(4);
473 await headers[0].click();
474 expect(await headers[0].isActive()).toBe(true);
475 expect(await headers[0].getSortDirection()).toBe("asc");
477 await headers[0].click();
478 expect(await headers[0].getSortDirection()).toBe("desc");
481 it("should sort table asc and desc by first header", async () => {
482 const sort = await loader.getHarness(MatSortHarness);
483 const policyTable = await loader.getHarness(
484 MatTableHarness.with({ selector: "#policiesTable" })
486 const firstHeader = (await sort.getSortHeaders())[0];
487 expect(await firstHeader.getSortDirection()).toBe("");
489 await firstHeader.click();
490 expect(await firstHeader.getSortDirection()).toBe("asc");
491 let policyRows = await policyTable.getRows();
492 expect(await policyRows[0].getCellTextByColumnName()).toEqual(
496 await firstHeader.click();
497 expect(await firstHeader.getSortDirection()).toBe("desc");
498 policyRows = await policyTable.getRows();
500 await policyRows[policyRows.length - 1].getCellTextByColumnName()
501 ).toEqual(expectedPolicy1Row);
504 it("should sort table asc and desc by second header", async () => {
505 const sort = await loader.getHarness(MatSortHarness);
506 const jobsTable = await loader.getHarness(
507 MatTableHarness.with({ selector: "#policiesTable" })
509 const firstHeader = (await sort.getSortHeaders())[1];
510 expect(await firstHeader.getSortDirection()).toBe("");
512 await firstHeader.click();
513 expect(await firstHeader.getSortDirection()).toBe("asc");
514 let policyRows = await jobsTable.getRows();
515 policyRows = await jobsTable.getRows();
516 expect(await policyRows[0].getCellTextByColumnName()).toEqual(
520 await firstHeader.click();
521 expect(await firstHeader.getSortDirection()).toBe("desc");
522 policyRows = await jobsTable.getRows();
524 await policyRows[policyRows.length - 1].getCellTextByColumnName()
525 ).toEqual(expectedPolicy1Row);
528 it("should sort table asc and desc by third header", async () => {
529 const sort = await loader.getHarness(MatSortHarness);
530 const jobsTable = await loader.getHarness(
531 MatTableHarness.with({ selector: "#policiesTable" })
533 const firstHeader = (await sort.getSortHeaders())[2];
534 expect(await firstHeader.getSortDirection()).toBe("");
536 await firstHeader.click();
537 expect(await firstHeader.getSortDirection()).toBe("asc");
538 let policyRows = await jobsTable.getRows();
539 policyRows = await jobsTable.getRows();
540 expect(await policyRows[0].getCellTextByColumnName()).toEqual(
544 await firstHeader.click();
545 expect(await firstHeader.getSortDirection()).toBe("desc");
546 policyRows = await jobsTable.getRows();
548 await policyRows[policyRows.length - 1].getCellTextByColumnName()
549 ).toEqual(expectedPolicy1Row);
552 it("should sort table asc and desc by fourth header", async () => {
553 const sort = await loader.getHarness(MatSortHarness);
554 const jobsTable = await loader.getHarness(
555 MatTableHarness.with({ selector: "#policiesTable" })
557 const firstHeader = (await sort.getSortHeaders())[3];
558 expect(await firstHeader.getSortDirection()).toBe("");
560 await firstHeader.click();
561 expect(await firstHeader.getSortDirection()).toBe("asc");
562 let policyRows = await jobsTable.getRows();
563 policyRows = await jobsTable.getRows();
564 expect(await policyRows[0].getCellTextByColumnName()).toEqual(
568 await firstHeader.click();
569 expect(await firstHeader.getSortDirection()).toBe("desc");
570 policyRows = await jobsTable.getRows();
572 await policyRows[policyRows.length - 1].getCellTextByColumnName()
573 ).toEqual(expectedPolicy1Row);
579 function setupDialogRefSpy(returnValue: boolean = true) {
580 const afterClosedObservable = new Observable((observer) => {
581 observer.next(returnValue);
584 const dialogRefSpy = jasmine.createSpyObj("MatDialogRef", ["afterClosed"]);
585 dialogRefSpy.afterClosed.and.returnValue(afterClosedObservable);
589 function toLocalTime(utcTime: string): string {
590 const date = new Date(utcTime);
591 const toutc = date.toUTCString();
592 return new Date(toutc + " UTC").toLocaleString();