1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-19 09:43:23 +00:00
Files
browser/libs/vault/src/components/copy-cipher-field.directive.spec.ts
Nick Krantz b4120e0e3f [PM-22134] Migrate list views to CipherListView from the SDK (#15174)
* add `CipherViewLike` and utilities to handle `CipherView` and `CipherViewLike`

* migrate libs needed for web vault to support `CipherViewLike`

* migrate web vault components to support

* add  for CipherView.  will have to be later

* fetch full CipherView for copying a password

* have only the cipher service utilize SDK migration flag

- This keeps feature flag logic away from the component
- Also cuts down on what is needed for other platforms

* strongly type CipherView for AC vault

- Probably temporary before migration of the AC vault to `CipherListView` SDK

* fix build icon tests by being more gracious with the uri structure

* migrate desktop components to CipherListViews$

* consume card from sdk

* add browser implementation for `CipherListView`

* update copy message for single copiable items

* refactor `getCipherViewLikeLogin` to `getLogin`

* refactor `getCipherViewLikeCard` to `getCard`

* add `hasFido2Credentials` helper

* add decryption failure to cipher like utils

* add todo with ticket

* fix decryption failure typing

* fix copy card messages

* fix addition of organizations and collections for `PopupCipherViewLike`

- accessors were being lost

* refactor to getters to fix re-rendering bug

* fix decryption failure helper

* fix sorting functions for `CipherViewLike`

* formatting

* add `CipherViewLikeUtils` tests

* refactor "copiable" to "copyable" to match SDK

* use `hasOldAttachments` from cipherlistview

* fix typing

* update SDK version

* add feature flag for cipher list view work

* use `CipherViewLikeUtils` for copyable values rather than referring to the cipher directly

* update restricted item type to support CipherViewLike

* add cipher support to `CipherViewLikeUtils`

* update `isCipherListView` check

* refactor CipherLike to a separate type

* refactor `getFullCipherView` into the cipher service

* add optional chaining for `uriChecksum`

* set empty array for decrypted CipherListView

* migrate nudge service to use `cipherListViews`

* update web vault to not depend on `cipherViews$`

* update popup list filters to use `CipherListView`

* fix storybook

* fix tests

* accept undefined as a MY VAULT filter value for cipher list views

* use `LoginUriView` for uri logic (#15530)

* filter out null ciphers from the `_allDecryptedCiphers$` (#15539)

* use `launchUri` to avoid any unexpected behavior in URIs - this appends `http://` when missing
2025-07-17 14:55:32 -05:00

229 lines
7.2 KiB
TypeScript

import { mock } from "jest-mock-extended";
import { of } from "rxjs";
import { Account, AccountService } from "@bitwarden/common/auth/abstractions/account.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 { BitIconButtonComponent, MenuItemDirective } from "@bitwarden/components";
import { CopyCipherFieldService } from "@bitwarden/vault";
import { CopyCipherFieldDirective } from "./copy-cipher-field.directive";
describe("CopyCipherFieldDirective", () => {
const copyFieldService = {
copy: jest.fn().mockResolvedValue(null),
totpAllowed: jest.fn().mockResolvedValue(true),
};
let mockAccountService: AccountService;
let mockCipherService: CipherService;
let copyCipherFieldDirective: CopyCipherFieldDirective;
beforeEach(() => {
copyFieldService.copy.mockClear();
copyFieldService.totpAllowed.mockClear();
mockAccountService = mock<AccountService>();
mockAccountService.activeAccount$ = of({ id: "test-account-id" } as Account);
mockCipherService = mock<CipherService>();
copyCipherFieldDirective = new CopyCipherFieldDirective(
copyFieldService as unknown as CopyCipherFieldService,
mockAccountService,
mockCipherService,
);
copyCipherFieldDirective.cipher = new CipherView();
copyCipherFieldDirective.cipher.type = CipherType.Login;
});
describe("disabled state", () => {
it("should be enabled when the field is available", async () => {
copyCipherFieldDirective.action = "username";
(copyCipherFieldDirective.cipher as CipherView).login.username = "test-username";
await copyCipherFieldDirective.ngOnChanges();
expect(copyCipherFieldDirective["disabled"]).toBe(null);
});
it("should be disabled when the field is not available", async () => {
// create empty cipher
copyCipherFieldDirective.cipher = new CipherView();
copyCipherFieldDirective.cipher.type = CipherType.Login;
copyCipherFieldDirective.action = "username";
await copyCipherFieldDirective.ngOnChanges();
expect(copyCipherFieldDirective["disabled"]).toBe(true);
});
it("updates icon button disabled state", async () => {
const iconButton = {
disabled: {
set: jest.fn(),
},
};
copyCipherFieldDirective = new CopyCipherFieldDirective(
copyFieldService as unknown as CopyCipherFieldService,
mockAccountService,
mockCipherService,
undefined,
iconButton as unknown as BitIconButtonComponent,
);
copyCipherFieldDirective.action = "password";
copyCipherFieldDirective.cipher = new CipherView();
copyCipherFieldDirective.cipher.type = CipherType.Login;
await copyCipherFieldDirective.ngOnChanges();
expect(iconButton.disabled.set).toHaveBeenCalledWith(true);
});
it("updates menuItemDirective disabled state", async () => {
const menuItemDirective = {
disabled: false,
};
copyCipherFieldDirective = new CopyCipherFieldDirective(
copyFieldService as unknown as CopyCipherFieldService,
mockAccountService,
mockCipherService,
menuItemDirective as unknown as MenuItemDirective,
);
copyCipherFieldDirective.action = "totp";
await copyCipherFieldDirective.ngOnChanges();
expect(menuItemDirective.disabled).toBe(true);
});
});
describe("login", () => {
beforeEach(() => {
const cipher = copyCipherFieldDirective.cipher as CipherView;
cipher.type = CipherType.Login;
cipher.login.username = "test-username";
cipher.login.password = "test-password";
cipher.login.totp = "test-totp";
});
it.each([
["username", "test-username"],
["password", "test-password"],
["totp", "test-totp"],
])("copies %s field from login to clipboard", async (action, value) => {
copyCipherFieldDirective.action = action as CopyCipherFieldDirective["action"];
await copyCipherFieldDirective.copy();
expect(copyFieldService.copy).toHaveBeenCalledWith(
value,
action,
copyCipherFieldDirective.cipher,
);
});
});
describe("identity", () => {
beforeEach(() => {
const cipher = copyCipherFieldDirective.cipher as CipherView;
cipher.type = CipherType.Identity;
cipher.identity.username = "test-username";
cipher.identity.email = "test-email";
cipher.identity.phone = "test-phone";
cipher.identity.address1 = "test-address-1";
});
it.each([
["username", "test-username"],
["email", "test-email"],
["phone", "test-phone"],
["address", "test-address-1"],
])("copies %s field from identity to clipboard", async (action, value) => {
copyCipherFieldDirective.action = action as CopyCipherFieldDirective["action"];
await copyCipherFieldDirective.copy();
expect(copyFieldService.copy).toHaveBeenCalledWith(
value,
action,
copyCipherFieldDirective.cipher,
);
});
});
describe("card", () => {
beforeEach(() => {
const cipher = copyCipherFieldDirective.cipher as CipherView;
cipher.type = CipherType.Card;
cipher.card.number = "test-card-number";
cipher.card.code = "test-card-code";
});
it.each([
["cardNumber", "test-card-number"],
["securityCode", "test-card-code"],
])("copies %s field from card to clipboard", async (action, value) => {
copyCipherFieldDirective.action = action as CopyCipherFieldDirective["action"];
await copyCipherFieldDirective.copy();
expect(copyFieldService.copy).toHaveBeenCalledWith(
value,
action,
copyCipherFieldDirective.cipher,
);
});
});
describe("secure note", () => {
beforeEach(() => {
const cipher = copyCipherFieldDirective.cipher as CipherView;
cipher.type = CipherType.SecureNote;
cipher.notes = "test-secure-note";
});
it("copies secure note field to clipboard", async () => {
copyCipherFieldDirective.action = "secureNote";
await copyCipherFieldDirective.copy();
expect(copyFieldService.copy).toHaveBeenCalledWith(
"test-secure-note",
"secureNote",
copyCipherFieldDirective.cipher,
);
});
});
describe("ssh key", () => {
beforeEach(() => {
const cipher = copyCipherFieldDirective.cipher as CipherView;
cipher.type = CipherType.SshKey;
cipher.sshKey.privateKey = "test-private-key";
cipher.sshKey.publicKey = "test-public-key";
cipher.sshKey.keyFingerprint = "test-key-fingerprint";
});
it.each([
["privateKey", "test-private-key"],
["publicKey", "test-public-key"],
["keyFingerprint", "test-key-fingerprint"],
])("copies %s field from ssh key to clipboard", async (action, value) => {
copyCipherFieldDirective.action = action as CopyCipherFieldDirective["action"];
await copyCipherFieldDirective.copy();
expect(copyFieldService.copy).toHaveBeenCalledWith(
value,
action,
copyCipherFieldDirective.cipher,
);
});
});
});