From b25a039eda9e200f44c8a2e6dfdafd5da5cf5e9e Mon Sep 17 00:00:00 2001 From: voommen-livefront Date: Thu, 24 Jul 2025 09:32:17 -0500 Subject: [PATCH] PM-23826 added unit tests to the connect dialog --- .../integration-card.component.ts | 1 + .../connect-dialog.component.spec.ts | 156 ++++++++++++++++++ .../connect-dialog.component.ts | 5 +- 3 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-dialog/connect-dialog/connect-dialog.component.spec.ts diff --git a/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component.ts b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component.ts index aa4497db1a2..7c93f8a3dec 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-card/integration-card.component.ts @@ -116,6 +116,7 @@ export class IntegrationCardComponent implements AfterViewInit, OnDestroy { const result = await lastValueFrom(dialog.closed); + // for now we just log the result // eslint-disable-next-line no-console console.log(`Dialog closed with result: ${JSON.stringify(result)}`); } diff --git a/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-dialog/connect-dialog/connect-dialog.component.spec.ts b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-dialog/connect-dialog/connect-dialog.component.spec.ts new file mode 100644 index 00000000000..13e19c39cb9 --- /dev/null +++ b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-dialog/connect-dialog/connect-dialog.component.spec.ts @@ -0,0 +1,156 @@ +import { ComponentFixture, TestBed } from "@angular/core/testing"; +import { FormBuilder, ReactiveFormsModule } from "@angular/forms"; +import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; +import { mock } from "jest-mock-extended"; + +import { IntegrationType } from "@bitwarden/common/enums"; +import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service"; +import { DIALOG_DATA, DialogConfig, DialogRef, DialogService } from "@bitwarden/components"; +import { I18nPipe } from "@bitwarden/ui-common"; +import { SharedModule } from "@bitwarden/web-vault/app/shared"; + +import { Integration } from "../../models"; + +import { + ConnectDialogComponent, + ConnectDialogParams, + ConnectDialogResult, + openCrowdstrikeConnectDialog, +} from "./connect-dialog.component"; + +beforeAll(() => { + // Mock element.animate for jsdom + // the animate function is not available in jsdom, so we provide a mock implementation + // This is necessary for tests that rely on animations + // This mock does not perform any actual animations, it just provides a structure that allows tests + // to run without throwing errors related to missing animate function + if (!HTMLElement.prototype.animate) { + HTMLElement.prototype.animate = function () { + return { + play: () => {}, + pause: () => {}, + finish: () => {}, + cancel: () => {}, + reverse: () => {}, + addEventListener: () => {}, + removeEventListener: () => {}, + dispatchEvent: () => false, + onfinish: null, + oncancel: null, + startTime: 0, + currentTime: 0, + playbackRate: 1, + playState: "idle", + replaceState: "active", + effect: null, + finished: Promise.resolve(), + id: "", + remove: () => {}, + timeline: null, + ready: Promise.resolve(), + } as unknown as Animation; + }; + } +}); + +describe("ConnectDialogComponent", () => { + let component: ConnectDialogComponent; + let fixture: ComponentFixture; + let dialogRefMock = mock>(); + const mockI18nService = mock(); + + const integrationMock: Integration = { + name: "Test Integration", + image: "test-image.png", + linkURL: "https://example.com", + imageDarkMode: "test-image-dark.png", + newBadgeExpiration: "2024-12-31", + description: "Test Description", + isConnected: false, + canSetupConnection: true, + type: IntegrationType.EVENT, + } as Integration; + const connectInfo: ConnectDialogParams = { settings: integrationMock }; + + beforeEach(async () => { + dialogRefMock = mock>(); + + await TestBed.configureTestingModule({ + imports: [ReactiveFormsModule, SharedModule, BrowserAnimationsModule], + providers: [ + FormBuilder, + { provide: DIALOG_DATA, useValue: connectInfo }, + { provide: DialogRef, useValue: dialogRefMock }, + { provide: I18nPipe, useValue: mock() }, + { provide: I18nService, useValue: mockI18nService }, + ], + }).compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ConnectDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + mockI18nService.t.mockImplementation((key) => key); + }); + + it("should create the component", () => { + expect(component).toBeTruthy(); + }); + + it("should initialize form with empty values", () => { + expect(component.formGroup.value).toEqual({ + url: "", + bearerToken: "", + index: "", + }); + }); + + it("should have required validators for all fields", () => { + component.formGroup.setValue({ url: "", bearerToken: "", index: "" }); + expect(component.formGroup.valid).toBeFalsy(); + + component.formGroup.setValue({ url: "https://test.com", bearerToken: "token", index: "1" }); + expect(component.formGroup.valid).toBeTruthy(); + }); + + it("should invalidate url if not matching pattern", () => { + component.formGroup.setValue({ url: "ftp://test.com", bearerToken: "token", index: "1" }); + expect(component.formGroup.valid).toBeFalsy(); + + component.formGroup.setValue({ url: "https://test.com", bearerToken: "token", index: "1" }); + expect(component.formGroup.valid).toBeTruthy(); + }); + + it("should call dialogRef.close with correct result on submit", async () => { + component.formGroup.setValue({ + url: "https://test.com", + bearerToken: "token", + index: "1", + }); + + await component.submit(); + + expect(dialogRefMock.close).toHaveBeenCalledWith({ + integrationSettings: integrationMock, + url: "https://test.com", + bearerToken: "token", + index: "1", + success: true, + error: null, + }); + }); +}); + +describe("openCrowdstrikeConnectDialog", () => { + it("should call dialogService.open with correct params", () => { + const dialogServiceMock = mock(); + const config: DialogConfig> = { + data: { settings: { name: "Test" } as Integration }, + } as any; + + openCrowdstrikeConnectDialog(dialogServiceMock, config); + + expect(dialogServiceMock.open).toHaveBeenCalledWith(ConnectDialogComponent, config); + }); +}); diff --git a/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-dialog/connect-dialog/connect-dialog.component.ts b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-dialog/connect-dialog/connect-dialog.component.ts index edd00f02998..cac15737f69 100644 --- a/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-dialog/connect-dialog/connect-dialog.component.ts +++ b/apps/web/src/app/admin-console/organizations/shared/components/integrations/integration-dialog/connect-dialog/connect-dialog.component.ts @@ -25,7 +25,7 @@ export interface ConnectDialogResult { }) export class ConnectDialogComponent { loading = false; - protected formGroup = this.formBuilder.group({ + formGroup = this.formBuilder.group({ url: ["", [Validators.required, Validators.pattern("https?://.+")]], bearerToken: ["", Validators.required], index: ["", Validators.required], @@ -37,7 +37,7 @@ export class ConnectDialogComponent { private dialogRef: DialogRef, ) {} - protected submit = async (): Promise => { + submit = async (): Promise => { const formJson = this.formGroup.getRawValue(); // eslint-disable-next-line no-console @@ -52,6 +52,7 @@ export class ConnectDialogComponent { error: null, }; + // for now, we just log the result // eslint-disable-next-line no-console console.log(`Dialog closed with result: ${JSON.stringify(result)}`);