1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-28 02:23:25 +00:00
Files
browser/apps/web/src/app/vault/components/vault-items/vault-cipher-row.component.spec.ts
Nick Krantz 9a22907e27 [PM-30296] Assign to Collections for Archived Ciphers (#18223)
* allow for archived ciphers to be assigned to a collection via the more options menu

* reference `userId$` directly
2026-01-15 11:08:18 -06:00

187 lines
6.5 KiB
TypeScript

import { OverlayContainer } from "@angular/cdk/overlay";
import { CommonModule } from "@angular/common";
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { RouterModule } from "@angular/router";
import { mock } from "jest-mock-extended";
import { BehaviorSubject } from "rxjs";
import { JslibModule } from "@bitwarden/angular/jslib.module";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { DomainSettingsService } from "@bitwarden/common/autofill/services/domain-settings.service";
import { EnvironmentService } from "@bitwarden/common/platform/abstractions/environment.service";
import { I18nService } from "@bitwarden/common/platform/abstractions/i18n.service";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherType } from "@bitwarden/common/vault/enums";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { LoginView } from "@bitwarden/common/vault/models/view/login.view";
import { CipherViewLike } from "@bitwarden/common/vault/utils/cipher-view-like-utils";
import { IconButtonModule, MenuModule } from "@bitwarden/components";
import { CopyCipherFieldDirective, CopyCipherFieldService } from "@bitwarden/vault";
import { OrganizationNameBadgeComponent } from "../../individual-vault/organization-badge/organization-name-badge.component";
import { VaultCipherRowComponent } from "./vault-cipher-row.component";
// eslint-disable-next-line no-console
const originalError = console.error;
// eslint-disable-next-line no-console
console.error = (...args) => {
if (
typeof args[0] === "object" &&
(args[0] as Error).message.includes("Could not parse CSS stylesheet")
) {
// Opening the overlay container in tests causes stylesheets to be parsed,
// which can lead to JSDOM unable to parse CSS errors. These can be ignored safely.
return;
}
originalError(...args);
};
describe("VaultCipherRowComponent", () => {
let component: VaultCipherRowComponent<CipherViewLike>;
let fixture: ComponentFixture<VaultCipherRowComponent<CipherViewLike>>;
let overlayContainer: OverlayContainer;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [VaultCipherRowComponent, OrganizationNameBadgeComponent],
imports: [
CommonModule,
RouterModule.forRoot([]),
MenuModule,
IconButtonModule,
JslibModule,
CopyCipherFieldDirective,
],
providers: [
{ provide: I18nService, useValue: { t: (key: string) => key } },
{
provide: EnvironmentService,
useValue: { environment$: new BehaviorSubject({}).asObservable() },
},
{
provide: DomainSettingsService,
useValue: { showFavicons$: new BehaviorSubject(false).asObservable() },
},
{ provide: CopyCipherFieldService, useValue: mock<CopyCipherFieldService>() },
{ provide: AccountService, useValue: mock<AccountService>() },
{ provide: CipherService, useValue: mock<CipherService>() },
],
}).compileComponents();
fixture = TestBed.createComponent(VaultCipherRowComponent);
component = fixture.componentInstance;
fixture.componentRef.setInput("archiveEnabled", false);
overlayContainer = TestBed.inject(OverlayContainer);
});
afterEach(() => {
overlayContainer?.ngOnDestroy();
});
afterAll(() => {
// eslint-disable-next-line no-console
console.error = originalError;
});
describe("copy password visibility", () => {
let loginCipher: CipherView;
beforeEach(() => {
loginCipher = new CipherView();
loginCipher.id = "cipher-1";
loginCipher.name = "Test Login";
loginCipher.type = CipherType.Login;
loginCipher.login = new LoginView();
loginCipher.login.password = "test-password";
loginCipher.organizationId = undefined;
loginCipher.deletedDate = null;
loginCipher.archivedDate = null;
component.cipher = loginCipher;
component.disabled = false;
});
const openMenuAndGetContent = (): string => {
fixture.detectChanges();
const menuTrigger = fixture.nativeElement.querySelector(
'button[biticonbutton="bwi-ellipsis-v"]',
) as HTMLButtonElement;
expect(menuTrigger).toBeTruthy();
menuTrigger.click();
fixture.detectChanges();
return overlayContainer.getContainerElement().innerHTML;
};
it("renders copy password button in menu when viewPassword is true", () => {
component.cipher.viewPassword = true;
const overlayContent = openMenuAndGetContent();
expect(overlayContent).toContain('appcopyfield="password"');
expect(overlayContent).toContain("copyPassword");
});
it("does not render copy password button in menu when viewPassword is false", () => {
component.cipher.viewPassword = false;
const overlayContent = openMenuAndGetContent();
expect(overlayContent).not.toContain('appcopyfield="password"');
});
it("does not render copy password button in menu when viewPassword is undefined", () => {
component.cipher.viewPassword = undefined;
const overlayContent = openMenuAndGetContent();
expect(overlayContent).not.toContain('appcopyfield="password"');
});
});
describe("showAssignToCollections", () => {
let archivedCipher: CipherView;
beforeEach(() => {
archivedCipher = new CipherView();
archivedCipher.id = "cipher-1";
archivedCipher.name = "Test Cipher";
archivedCipher.type = CipherType.Login;
archivedCipher.organizationId = "org-1";
archivedCipher.deletedDate = null;
archivedCipher.archivedDate = new Date();
component.cipher = archivedCipher;
component.organizations = [{ id: "org-1" } as any];
component.canAssignCollections = true;
component.disabled = false;
});
it("returns true when cipher is archived and conditions are met", () => {
expect(component["showAssignToCollections"]).toBe(true);
});
it("returns false when cipher is deleted", () => {
archivedCipher.deletedDate = new Date();
expect(component["showAssignToCollections"]).toBe(false);
});
it("returns false when user cannot assign collections", () => {
component.canAssignCollections = false;
expect(component["showAssignToCollections"]).toBe(false);
});
it("returns false when there are no organizations", () => {
component.organizations = [];
expect(component["showAssignToCollections"]).toBeFalsy();
});
});
});