mirror of
https://github.com/bitwarden/browser
synced 2025-12-19 09:43:23 +00:00
* 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
229 lines
7.2 KiB
TypeScript
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,
|
|
);
|
|
});
|
|
});
|
|
});
|