1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-23 12:53:44 +00:00

Desktop Autotype add service unit tests (#17678)

This commit is contained in:
neuronull
2026-01-07 08:54:28 -07:00
committed by GitHub
parent 9ff3540406
commit b644cca91e
2 changed files with 759 additions and 2 deletions

View File

@@ -0,0 +1,400 @@
/* eslint-disable @typescript-eslint/no-unsafe-function-type */
import { TestBed } from "@angular/core/testing";
import { ipcMain, globalShortcut } from "electron";
import { autotype } from "@bitwarden/desktop-napi";
import { LogService } from "@bitwarden/logging";
import { WindowMain } from "../../main/window.main";
import { AutotypeConfig } from "../models/autotype-config";
import { AutotypeMatchError } from "../models/autotype-errors";
import { AutotypeVaultData } from "../models/autotype-vault-data";
import { AUTOTYPE_IPC_CHANNELS } from "../models/ipc-channels";
import { AutotypeKeyboardShortcut } from "../models/main-autotype-keyboard-shortcut";
import { MainDesktopAutotypeService } from "./main-desktop-autotype.service";
// Mock electron modules
jest.mock("electron", () => ({
ipcMain: {
on: jest.fn(),
removeAllListeners: jest.fn(),
},
globalShortcut: {
register: jest.fn(),
unregister: jest.fn(),
isRegistered: jest.fn(),
},
}));
// Mock desktop-napi
jest.mock("@bitwarden/desktop-napi", () => ({
autotype: {
getForegroundWindowTitle: jest.fn(),
typeInput: jest.fn(),
},
}));
// Mock AutotypeKeyboardShortcut
jest.mock("../models/main-autotype-keyboard-shortcut", () => ({
AutotypeKeyboardShortcut: jest.fn().mockImplementation(() => ({
set: jest.fn().mockReturnValue(true),
getElectronFormat: jest.fn().mockReturnValue("Control+Alt+B"),
getArrayFormat: jest.fn().mockReturnValue(["Control", "Alt", "B"]),
})),
}));
describe("MainDesktopAutotypeService", () => {
let service: MainDesktopAutotypeService;
let mockLogService: jest.Mocked<LogService>;
let mockWindowMain: jest.Mocked<WindowMain>;
let ipcHandlers: Map<string, Function>;
beforeEach(() => {
// Track IPC handlers
ipcHandlers = new Map();
(ipcMain.on as jest.Mock).mockImplementation((channel: string, handler: Function) => {
ipcHandlers.set(channel, handler);
});
// Mock LogService
mockLogService = {
debug: jest.fn(),
error: jest.fn(),
info: jest.fn(),
warning: jest.fn(),
} as any;
// Mock WindowMain with webContents
mockWindowMain = {
win: {
webContents: {
send: jest.fn(),
},
},
} as any;
// Reset all mocks
jest.clearAllMocks();
(globalShortcut.isRegistered as jest.Mock).mockReturnValue(false);
(globalShortcut.register as jest.Mock).mockReturnValue(true);
TestBed.configureTestingModule({
providers: [
{ provide: LogService, useValue: mockLogService },
{ provide: WindowMain, useValue: mockWindowMain },
],
});
// Create service manually since it doesn't use Angular DI
service = new MainDesktopAutotypeService(mockLogService, mockWindowMain);
});
afterEach(() => {
ipcHandlers.clear(); // Clear handler map
service.dispose();
});
describe("constructor", () => {
it("should create service", () => {
expect(service).toBeTruthy();
});
it("should initialize keyboard shortcut", () => {
expect(service.autotypeKeyboardShortcut).toBeDefined();
});
it("should register IPC handlers", () => {
expect(ipcMain.on).toHaveBeenCalledWith(AUTOTYPE_IPC_CHANNELS.TOGGLE, expect.any(Function));
expect(ipcMain.on).toHaveBeenCalledWith(
AUTOTYPE_IPC_CHANNELS.CONFIGURE,
expect.any(Function),
);
expect(ipcMain.on).toHaveBeenCalledWith(AUTOTYPE_IPC_CHANNELS.EXECUTE, expect.any(Function));
expect(ipcMain.on).toHaveBeenCalledWith(
"autofill.completeAutotypeError",
expect.any(Function),
);
});
});
describe("TOGGLE handler", () => {
it("should enable autotype when toggle is true", () => {
const toggleHandler = ipcHandlers.get(AUTOTYPE_IPC_CHANNELS.TOGGLE);
toggleHandler({}, true);
expect(globalShortcut.register).toHaveBeenCalled();
expect(mockLogService.debug).toHaveBeenCalledWith("Autotype enabled.");
});
it("should disable autotype when toggle is false", () => {
(globalShortcut.isRegistered as jest.Mock).mockReturnValue(true);
const toggleHandler = ipcHandlers.get(AUTOTYPE_IPC_CHANNELS.TOGGLE);
toggleHandler({}, false);
expect(globalShortcut.unregister).toHaveBeenCalled();
expect(mockLogService.debug).toHaveBeenCalledWith("Autotype disabled.");
});
});
describe("CONFIGURE handler", () => {
it("should update keyboard shortcut with valid configuration", () => {
const config: AutotypeConfig = {
keyboardShortcut: ["Control", "Alt", "A"],
};
const mockNewShortcut = {
set: jest.fn().mockReturnValue(true),
getElectronFormat: jest.fn().mockReturnValue("Control+Alt+A"),
getArrayFormat: jest.fn().mockReturnValue(["Control", "Alt", "A"]),
};
(AutotypeKeyboardShortcut as jest.Mock).mockReturnValue(mockNewShortcut);
const configureHandler = ipcHandlers.get(AUTOTYPE_IPC_CHANNELS.CONFIGURE);
configureHandler({}, config);
expect(mockNewShortcut.set).toHaveBeenCalledWith(config.keyboardShortcut);
});
it("should log error with invalid keyboard shortcut", () => {
const config: AutotypeConfig = {
keyboardShortcut: ["Invalid", "Keys"],
};
const mockNewShortcut = {
set: jest.fn().mockReturnValue(false),
getElectronFormat: jest.fn(),
getArrayFormat: jest.fn(),
};
(AutotypeKeyboardShortcut as jest.Mock).mockReturnValue(mockNewShortcut);
const configureHandler = ipcHandlers.get(AUTOTYPE_IPC_CHANNELS.CONFIGURE);
configureHandler({}, config);
expect(mockLogService.error).toHaveBeenCalledWith(
"Configure autotype failed: the keyboard shortcut is invalid.",
);
});
it("should register new shortcut if one already registered", () => {
(globalShortcut.isRegistered as jest.Mock)
.mockReturnValueOnce(true)
.mockReturnValueOnce(true)
.mockReturnValueOnce(false);
const config: AutotypeConfig = {
keyboardShortcut: ["Control", "Alt", "B"],
};
const mockNewShortcut = {
set: jest.fn().mockReturnValue(true),
getElectronFormat: jest.fn().mockReturnValue("Control+Alt+B"),
getArrayFormat: jest.fn().mockReturnValue(["Control", "Alt", "B"]),
};
(AutotypeKeyboardShortcut as jest.Mock).mockReturnValue(mockNewShortcut);
const configureHandler = ipcHandlers.get(AUTOTYPE_IPC_CHANNELS.CONFIGURE);
configureHandler({}, config);
expect(globalShortcut.unregister).toHaveBeenCalled();
expect(globalShortcut.register).toHaveBeenCalled();
});
it("should not change shortcut if it is the same", () => {
const config: AutotypeConfig = {
keyboardShortcut: ["Control", "Alt", "B"],
};
jest
.spyOn(service.autotypeKeyboardShortcut, "getElectronFormat")
.mockReturnValue("Control+Alt+B");
(globalShortcut.isRegistered as jest.Mock).mockReturnValue(true);
const configureHandler = ipcHandlers.get(AUTOTYPE_IPC_CHANNELS.CONFIGURE);
configureHandler({}, config);
expect(mockLogService.debug).toHaveBeenCalledWith(
"setKeyboardShortcut() called but shortcut is not different from current.",
);
});
});
describe("EXECUTE handler", () => {
it("should execute autotype with valid vault data", async () => {
const vaultData: AutotypeVaultData = {
username: "testuser",
password: "testpass",
};
jest
.spyOn(service.autotypeKeyboardShortcut, "getArrayFormat")
.mockReturnValue(["Control", "Alt", "B"]);
const executeHandler = ipcHandlers.get(AUTOTYPE_IPC_CHANNELS.EXECUTE);
await executeHandler({}, vaultData);
expect(autotype.typeInput).toHaveBeenCalledWith(expect.any(Array), ["Control", "Alt", "B"]);
});
it("should not execute autotype with empty username", () => {
const vaultData: AutotypeVaultData = {
username: "",
password: "testpass",
};
const executeHandler = ipcHandlers.get(AUTOTYPE_IPC_CHANNELS.EXECUTE);
executeHandler({}, vaultData);
expect(autotype.typeInput).not.toHaveBeenCalled();
});
it("should not execute autotype with empty password", () => {
const vaultData: AutotypeVaultData = {
username: "testuser",
password: "",
};
const executeHandler = ipcHandlers.get(AUTOTYPE_IPC_CHANNELS.EXECUTE);
executeHandler({}, vaultData);
expect(autotype.typeInput).not.toHaveBeenCalled();
});
it("should format input with tab separator", () => {
const mockNewShortcut = {
set: jest.fn().mockReturnValue(true),
getElectronFormat: jest.fn().mockReturnValue("Control+Alt+B"),
getArrayFormat: jest.fn().mockReturnValue(["Control", "Alt", "B"]),
};
(AutotypeKeyboardShortcut as jest.Mock).mockReturnValue(mockNewShortcut);
const vaultData: AutotypeVaultData = {
username: "user",
password: "pass",
};
const executeHandler = ipcHandlers.get(AUTOTYPE_IPC_CHANNELS.EXECUTE);
executeHandler({}, vaultData);
// Verify the input array contains char codes for "user\tpass"
const expectedPattern = "user\tpass";
const expectedArray = Array.from(expectedPattern).map((c) => c.charCodeAt(0));
expect(autotype.typeInput).toHaveBeenCalledWith(expectedArray, ["Control", "Alt", "B"]);
});
});
describe("completeAutotypeError handler", () => {
it("should log autotype match errors", () => {
const matchError: AutotypeMatchError = {
windowTitle: "Test Window",
errorMessage: "No matching vault item",
};
const errorHandler = ipcHandlers.get("autofill.completeAutotypeError");
errorHandler({}, matchError);
expect(mockLogService.debug).toHaveBeenCalledWith(
"autofill.completeAutotypeError",
"No match for window: Test Window",
);
expect(mockLogService.error).toHaveBeenCalledWith(
"autofill.completeAutotypeError",
"No matching vault item",
);
});
});
describe("disableAutotype", () => {
it("should unregister shortcut if registered", () => {
(globalShortcut.isRegistered as jest.Mock).mockReturnValue(true);
service.disableAutotype();
expect(globalShortcut.unregister).toHaveBeenCalled();
expect(mockLogService.debug).toHaveBeenCalledWith("Autotype disabled.");
});
it("should log debug message if shortcut not registered", () => {
(globalShortcut.isRegistered as jest.Mock).mockReturnValue(false);
service.disableAutotype();
expect(globalShortcut.unregister).not.toHaveBeenCalled();
expect(mockLogService.debug).toHaveBeenCalledWith(
"Autotype is not registered, implicitly disabled.",
);
});
});
describe("dispose", () => {
it("should remove all IPC listeners", () => {
service.dispose();
expect(ipcMain.removeAllListeners).toHaveBeenCalledWith(AUTOTYPE_IPC_CHANNELS.TOGGLE);
expect(ipcMain.removeAllListeners).toHaveBeenCalledWith(AUTOTYPE_IPC_CHANNELS.CONFIGURE);
expect(ipcMain.removeAllListeners).toHaveBeenCalledWith(AUTOTYPE_IPC_CHANNELS.EXECUTE);
});
it("should disable autotype", () => {
(globalShortcut.isRegistered as jest.Mock).mockReturnValue(true);
service.dispose();
expect(globalShortcut.unregister).toHaveBeenCalled();
});
});
describe("enableAutotype (via TOGGLE handler)", () => {
it("should register global shortcut", () => {
const toggleHandler = ipcHandlers.get(AUTOTYPE_IPC_CHANNELS.TOGGLE);
toggleHandler({}, true);
expect(globalShortcut.register).toHaveBeenCalledWith("Control+Alt+B", expect.any(Function));
});
it("should not register if already registered", () => {
(globalShortcut.isRegistered as jest.Mock).mockReturnValue(true);
const toggleHandler = ipcHandlers.get(AUTOTYPE_IPC_CHANNELS.TOGGLE);
toggleHandler({}, true);
expect(globalShortcut.register).not.toHaveBeenCalled();
expect(mockLogService.debug).toHaveBeenCalledWith(
"Autotype is already enabled with this keyboard shortcut: Control+Alt+B",
);
});
it("should log error if registration fails", () => {
(globalShortcut.register as jest.Mock).mockReturnValue(false);
const toggleHandler = ipcHandlers.get(AUTOTYPE_IPC_CHANNELS.TOGGLE);
toggleHandler({}, true);
expect(mockLogService.error).toHaveBeenCalledWith("Failed to enable Autotype.");
});
it("should send window title to renderer on shortcut activation", () => {
(autotype.getForegroundWindowTitle as jest.Mock).mockReturnValue("Notepad");
const toggleHandler = ipcHandlers.get(AUTOTYPE_IPC_CHANNELS.TOGGLE);
toggleHandler({}, true);
// Get the registered callback
const registeredCallback = (globalShortcut.register as jest.Mock).mock.calls[0][1];
registeredCallback();
expect(autotype.getForegroundWindowTitle).toHaveBeenCalled();
expect(mockWindowMain.win.webContents.send).toHaveBeenCalledWith(
AUTOTYPE_IPC_CHANNELS.LISTEN,
{ windowTitle: "Notepad" },
);
});
});
});

View File

@@ -1,6 +1,363 @@
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { TestBed } from "@angular/core/testing";
import { BehaviorSubject } from "rxjs";
import { getAutotypeVaultData } from "./desktop-autotype.service";
import { AccountService } from "@bitwarden/common/auth/abstractions/account.service";
import { AuthService } from "@bitwarden/common/auth/abstractions/auth.service";
import { AuthenticationStatus } from "@bitwarden/common/auth/enums/authentication-status";
import { BillingAccountProfileStateService } from "@bitwarden/common/billing/abstractions";
import { DeviceType } from "@bitwarden/common/enums";
import { ConfigService } from "@bitwarden/common/platform/abstractions/config/config.service";
import { PlatformUtilsService } from "@bitwarden/common/platform/abstractions/platform-utils.service";
import { GlobalStateProvider } from "@bitwarden/common/platform/state";
import { CipherService } from "@bitwarden/common/vault/abstractions/cipher.service";
import { CipherView } from "@bitwarden/common/vault/models/view/cipher.view";
import { LogService } from "@bitwarden/logging";
import { DesktopAutotypeDefaultSettingPolicy } from "./desktop-autotype-policy.service";
import { DesktopAutotypeService, getAutotypeVaultData } from "./desktop-autotype.service";
describe("DesktopAutotypeService", () => {
let service: DesktopAutotypeService;
// Mock dependencies
let mockAccountService: jest.Mocked<AccountService>;
let mockAuthService: jest.Mocked<AuthService>;
let mockCipherService: jest.Mocked<CipherService>;
let mockConfigService: jest.Mocked<ConfigService>;
let mockGlobalStateProvider: jest.Mocked<GlobalStateProvider>;
let mockPlatformUtilsService: jest.Mocked<PlatformUtilsService>;
let mockBillingAccountProfileStateService: jest.Mocked<BillingAccountProfileStateService>;
let mockDesktopAutotypePolicy: jest.Mocked<DesktopAutotypeDefaultSettingPolicy>;
let mockLogService: jest.Mocked<LogService>;
// Mock GlobalState objects
let mockAutotypeEnabledState: any;
let mockAutotypeKeyboardShortcutState: any;
// BehaviorSubjects for reactive state
let autotypeEnabledSubject: BehaviorSubject<boolean | null>;
let autotypeKeyboardShortcutSubject: BehaviorSubject<string[]>;
let activeAccountSubject: BehaviorSubject<any>;
let activeAccountStatusSubject: BehaviorSubject<AuthenticationStatus>;
let hasPremiumSubject: BehaviorSubject<boolean>;
let featureFlagSubject: BehaviorSubject<boolean>;
let autotypeDefaultPolicySubject: BehaviorSubject<boolean>;
let cipherViewsSubject: BehaviorSubject<any[]>;
beforeEach(() => {
// Initialize BehaviorSubjects
autotypeEnabledSubject = new BehaviorSubject<boolean | null>(null);
autotypeKeyboardShortcutSubject = new BehaviorSubject<string[]>(["Control", "Shift", "B"]);
activeAccountSubject = new BehaviorSubject<any>({ id: "user-123" });
activeAccountStatusSubject = new BehaviorSubject<AuthenticationStatus>(
AuthenticationStatus.Unlocked,
);
hasPremiumSubject = new BehaviorSubject<boolean>(true);
featureFlagSubject = new BehaviorSubject<boolean>(true);
autotypeDefaultPolicySubject = new BehaviorSubject<boolean>(false);
cipherViewsSubject = new BehaviorSubject<any[]>([]);
// Mock GlobalState objects
mockAutotypeEnabledState = {
state$: autotypeEnabledSubject.asObservable(),
update: jest.fn().mockImplementation(async (configureState, options) => {
const newState = configureState(autotypeEnabledSubject.value, null);
// Handle shouldUpdate option
if (options?.shouldUpdate && !options.shouldUpdate(autotypeEnabledSubject.value)) {
return autotypeEnabledSubject.value;
}
autotypeEnabledSubject.next(newState);
return newState;
}),
};
mockAutotypeKeyboardShortcutState = {
state$: autotypeKeyboardShortcutSubject.asObservable(),
update: jest.fn().mockImplementation(async (configureState) => {
const newState = configureState(autotypeKeyboardShortcutSubject.value, null);
autotypeKeyboardShortcutSubject.next(newState);
return newState;
}),
};
// Mock GlobalStateProvider
mockGlobalStateProvider = {
get: jest.fn().mockImplementation((keyDef) => {
if (keyDef.key === "autotypeEnabled") {
return mockAutotypeEnabledState;
}
if (keyDef.key === "autotypeKeyboardShortcut") {
return mockAutotypeKeyboardShortcutState;
}
}),
} as any;
// Mock AccountService
mockAccountService = {
activeAccount$: activeAccountSubject.asObservable(),
} as any;
// Mock AuthService
mockAuthService = {
activeAccountStatus$: activeAccountStatusSubject.asObservable(),
} as any;
// Mock CipherService
mockCipherService = {
cipherViews$: jest.fn().mockReturnValue(cipherViewsSubject.asObservable()),
} as any;
// Mock ConfigService
mockConfigService = {
getFeatureFlag$: jest.fn().mockReturnValue(featureFlagSubject.asObservable()),
} as any;
// Mock PlatformUtilsService
mockPlatformUtilsService = {
getDevice: jest.fn().mockReturnValue(DeviceType.WindowsDesktop),
} as any;
// Mock BillingAccountProfileStateService
mockBillingAccountProfileStateService = {
hasPremiumFromAnySource$: jest.fn().mockReturnValue(hasPremiumSubject.asObservable()),
} as any;
// Mock DesktopAutotypeDefaultSettingPolicy
mockDesktopAutotypePolicy = {
autotypeDefaultSetting$: autotypeDefaultPolicySubject.asObservable(),
} as any;
// Mock LogService
mockLogService = {
error: jest.fn(),
info: jest.fn(),
debug: jest.fn(),
} as any;
// Mock ipc (global)
global.ipc = {
autofill: {
listenAutotypeRequest: jest.fn(),
configureAutotype: jest.fn(),
toggleAutotype: jest.fn(),
},
} as any;
TestBed.configureTestingModule({
providers: [
DesktopAutotypeService,
{ provide: AccountService, useValue: mockAccountService },
{ provide: AuthService, useValue: mockAuthService },
{ provide: CipherService, useValue: mockCipherService },
{ provide: ConfigService, useValue: mockConfigService },
{ provide: GlobalStateProvider, useValue: mockGlobalStateProvider },
{ provide: PlatformUtilsService, useValue: mockPlatformUtilsService },
{
provide: BillingAccountProfileStateService,
useValue: mockBillingAccountProfileStateService,
},
{ provide: DesktopAutotypeDefaultSettingPolicy, useValue: mockDesktopAutotypePolicy },
{ provide: LogService, useValue: mockLogService },
],
});
service = TestBed.inject(DesktopAutotypeService);
});
afterEach(() => {
jest.clearAllMocks();
service.ngOnDestroy();
});
describe("constructor", () => {
it("should create service", () => {
expect(service).toBeTruthy();
});
it("should initialize observables", () => {
expect(service.autotypeEnabledUserSetting$).toBeDefined();
expect(service.autotypeKeyboardShortcut$).toBeDefined();
});
});
describe("init", () => {
it("should register autotype request listener on Windows", async () => {
await service.init();
expect(global.ipc.autofill.listenAutotypeRequest).toHaveBeenCalled();
});
it("should not initialize on non-Windows platforms", async () => {
mockPlatformUtilsService.getDevice.mockReturnValue(DeviceType.MacOsDesktop);
await service.init();
expect(global.ipc.autofill.listenAutotypeRequest).not.toHaveBeenCalled();
});
it("should configure autotype when keyboard shortcut changes", async () => {
await service.init();
// Allow observables to emit
await new Promise((resolve) => setTimeout(resolve, 0));
expect(global.ipc.autofill.configureAutotype).toHaveBeenCalled();
});
it("should toggle autotype when feature enabled state changes", async () => {
autotypeEnabledSubject.next(true);
await service.init();
// Allow observables to emit
await new Promise((resolve) => setTimeout(resolve, 0));
expect(global.ipc.autofill.toggleAutotype).toHaveBeenCalled();
});
it("should enable autotype when policy is true and user setting is null", async () => {
autotypeEnabledSubject.next(null);
autotypeDefaultPolicySubject.next(true);
await service.init();
// Allow observables to emit
await new Promise((resolve) => setTimeout(resolve, 0));
expect(mockAutotypeEnabledState.update).toHaveBeenCalled();
expect(autotypeEnabledSubject.value).toBe(true);
});
});
describe("setAutotypeEnabledState", () => {
it("should update autotype enabled state", async () => {
await service.setAutotypeEnabledState(true);
expect(mockAutotypeEnabledState.update).toHaveBeenCalled();
expect(autotypeEnabledSubject.value).toBe(true);
});
it("should not update if value has not changed", async () => {
autotypeEnabledSubject.next(true);
await service.setAutotypeEnabledState(true);
// Update was called but shouldUpdate prevented the change
expect(mockAutotypeEnabledState.update).toHaveBeenCalled();
expect(autotypeEnabledSubject.value).toBe(true);
});
});
describe("setAutotypeKeyboardShortcutState", () => {
it("should update keyboard shortcut state", async () => {
const newShortcut = ["Control", "Alt", "A"];
await service.setAutotypeKeyboardShortcutState(newShortcut);
expect(mockAutotypeKeyboardShortcutState.update).toHaveBeenCalled();
expect(autotypeKeyboardShortcutSubject.value).toEqual(newShortcut);
});
});
describe("matchCiphersToWindowTitle", () => {
it("should match ciphers with matching apptitle URIs", async () => {
const mockCiphers = [
{
login: {
username: "user1",
password: "pass1",
uris: [{ uri: "apptitle://notepad" }],
},
deletedDate: null,
},
{
login: {
username: "user2",
password: "pass2",
uris: [{ uri: "apptitle://chrome" }],
},
deletedDate: null,
},
];
cipherViewsSubject.next(mockCiphers);
const result = await service.matchCiphersToWindowTitle("Notepad - Document.txt");
expect(result).toHaveLength(1);
expect(result[0].login.username).toBe("user1");
});
it("should filter out deleted ciphers", async () => {
const mockCiphers = [
{
login: {
username: "user1",
password: "pass1",
uris: [{ uri: "apptitle://notepad" }],
},
deletedDate: new Date(),
},
];
cipherViewsSubject.next(mockCiphers);
const result = await service.matchCiphersToWindowTitle("Notepad");
expect(result).toHaveLength(0);
});
it("should filter out ciphers without username or password", async () => {
const mockCiphers = [
{
login: {
username: null,
password: "pass1",
uris: [{ uri: "apptitle://notepad" }],
},
deletedDate: null,
},
];
cipherViewsSubject.next(mockCiphers);
const result = await service.matchCiphersToWindowTitle("Notepad");
expect(result).toHaveLength(0);
});
it("should perform case-insensitive matching", async () => {
const mockCiphers = [
{
login: {
username: "user1",
password: "pass1",
uris: [{ uri: "apptitle://NOTEPAD" }],
},
deletedDate: null,
},
];
cipherViewsSubject.next(mockCiphers);
const result = await service.matchCiphersToWindowTitle("notepad - document.txt");
expect(result).toHaveLength(1);
});
});
describe("ngOnDestroy", () => {
it("should complete destroy subject", () => {
const destroySpy = jest.spyOn(service["destroy$"], "complete");
service.ngOnDestroy();
expect(destroySpy).toHaveBeenCalled();
});
});
});
describe("getAutotypeVaultData", () => {
it("should return vault data when cipher has username and password", () => {