@ledgerhq/live-common
Version:
Common ground for the Ledger Live apps
174 lines (145 loc) • 5.16 kB
text/typescript
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);
});
});
});