import type Transport from "@ledgerhq/hw-transport"; import type { Account } from "@ledgerhq/types-live"; import type { CryptoCurrency } from "@ledgerhq/types-cryptoassets"; import { createAction, getViewKeyExec, Request, State, type ViewKeyProgress } from "./index"; import { viewKeyResolver } from "../../setup"; const mockViewKeyResolver = jest.mocked(viewKeyResolver); jest.mock("../../setup", () => ({ viewKeyResolver: jest.fn(), })); jest.mock("@ledgerhq/logs", () => ({ log: jest.fn(), })); describe("getViewKey", () => { beforeEach(() => { jest.clearAllMocks(); }); describe("getViewKeyExec", () => { const mockTransport = {} as Transport; const mockCurrency = { id: "aleo" } as CryptoCurrency; const mockAccount1 = { id: "account1", freshAddressPath: "44'/1234'/0'/0/0", } as Account; const mockAccount2 = { id: "account2", freshAddressPath: "44'/1234'/1'/0/0", } as Account; it("retrieves view key for single account", done => { const viewKey = "viewkey123"; mockViewKeyResolver.mockResolvedValue({ path: mockAccount1.freshAddressPath, viewKey }); const request: Request = { currency: mockCurrency, selectedAccounts: [mockAccount1], }; getViewKeyExec(mockTransport, request).subscribe({ next: progress => { expect(progress).toEqual({ viewKeys: { account1: viewKey }, completed: 1, total: 1, }); }, complete: () => { expect(mockViewKeyResolver).toHaveBeenCalledWith(mockTransport, { path: mockAccount1.freshAddressPath, currency: mockCurrency, }); done(); }, error: done, }); }); it("retrieves view keys for multiple accounts with progress tracking", done => { mockViewKeyResolver .mockResolvedValueOnce({ path: mockAccount1.freshAddressPath, viewKey: "key1" }) .mockResolvedValueOnce({ path: mockAccount2.freshAddressPath, viewKey: "key2" }); const request: Request = { currency: mockCurrency, selectedAccounts: [mockAccount1, mockAccount2], }; const progressUpdates: ViewKeyProgress[] = []; getViewKeyExec(mockTransport, request).subscribe({ next: progress => progressUpdates.push(progress), complete: () => { expect(progressUpdates).toEqual([ { viewKeys: { account1: "key1" }, completed: 1, total: 2 }, { viewKeys: { account1: "key1", account2: "key2" }, completed: 2, total: 2 }, ]); expect(mockViewKeyResolver).toHaveBeenCalledTimes(2); done(); }, error: done, }); }); it("propagates error when viewKeyResolver fails", done => { const error = new Error("Hardware wallet error"); mockViewKeyResolver.mockRejectedValue(error); const request: Request = { currency: mockCurrency, selectedAccounts: [mockAccount1], }; getViewKeyExec(mockTransport, request).subscribe({ error: (err: Error) => { expect(err).toBe(error); done(); }, }); }); it("stops processing on first error", done => { mockViewKeyResolver .mockResolvedValueOnce({ path: mockAccount1.freshAddressPath, viewKey: "key1" }) .mockRejectedValueOnce(new Error("Failed")); const request: Request = { currency: mockCurrency, selectedAccounts: [mockAccount1, mockAccount2], }; let emissionCount = 0; getViewKeyExec(mockTransport, request).subscribe({ next: () => emissionCount++, error: () => { expect(emissionCount).toBe(1); done(); }, }); }); it("throws when currency is missing", () => { const request: Request = { selectedAccounts: [mockAccount1], }; expect(() => { getViewKeyExec(mockTransport, request); }).toThrow("currency is required"); }); it("throws when selectedAccounts is empty", () => { const request: Request = { currency: mockCurrency, selectedAccounts: [], }; expect(() => { getViewKeyExec(mockTransport, request); }).toThrow("selectedAccounts cannot be empty"); }); }); describe("createAction", () => { const mockConnectAppExec = jest.fn(); const mockGetViewKey = jest.fn(); it("should return useHook and mapResult functions", () => { const action = createAction(mockConnectAppExec, mockGetViewKey); expect(action).toEqual({ useHook: expect.any(Function), mapResult: expect.any(Function), }); }); it("should map state.result correctly", () => { const action = createAction(mockConnectAppExec, mockGetViewKey); const mockState = { result: { acc1: "viewkey1", acc2: "viewkey2" }, error: null, sharePending: false, shareProgress: { completed: 2, total: 2, viewKeys: {} }, } as unknown as State; expect(action.mapResult(mockState)).toEqual(mockState.result); }); }); });