@reactive-dot/wallet-mimir
Version:
Mimir adapter for ReactiveDOT
218 lines (181 loc) • 5.99 kB
text/typescript
import { MimirWallet } from "./mimir-wallet.js";
import { MimirPAPISigner } from "@mimirdev/papi-signer";
import {
type Address,
BaseError,
Storage as WalletStorage,
} from "@reactive-dot/core";
import { firstValueFrom } from "rxjs";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("@mimirdev/papi-signer");
let wallet: MimirWallet;
beforeEach(() => {
const inMemorySimpleStorage = {
items: new Map(),
getItem(key: string) {
return this.items.get(key) ?? null;
},
removeItem(key: string) {
this.items.delete(key);
},
setItem(key: string, value: string) {
this.items.set(key, value);
},
};
const inMemoryStorage = new WalletStorage({
prefix: "@reactive-dot",
storage: inMemorySimpleStorage,
});
wallet = new MimirWallet({
originName: "test-origin",
storage: inMemoryStorage,
});
vi.clearAllMocks();
});
afterEach(() => {
wallet.disconnect();
});
describe("constructor", () => {
it("should create instance with correct id and name", () => {
expect(wallet.id).toBe("mimir");
expect(wallet.name).toBe("Mimir");
});
});
describe("connect", () => {
it("should connect successfully", async () => {
const mockSigner = {
enable: vi.fn().mockResolvedValue({ result: true }),
getAccounts: vi.fn().mockResolvedValue([]),
};
vi.mocked(MimirPAPISigner).mockImplementation(
vi.fn(
class {
constructor() {
Object.assign(this, mockSigner);
}
} as unknown as typeof MimirPAPISigner,
),
);
await wallet.connect();
expect(mockSigner.enable).toHaveBeenCalledWith("test-origin");
expect(await firstValueFrom(wallet.connected$)).toBeTruthy();
});
it("should throw error on failed connection", async () => {
const mockSigner = {
enable: vi.fn().mockResolvedValue({ result: false }),
};
vi.mocked(MimirPAPISigner).mockImplementation(
vi.fn(
class {
constructor() {
Object.assign(this, mockSigner);
}
} as unknown as typeof MimirPAPISigner,
),
);
await expect(wallet.connect()).rejects.toThrow(BaseError);
});
});
describe("$accounts", () => {
it("should emit an empty array when not connected", async () => {
const emittedAccounts = await firstValueFrom(wallet.accounts$);
expect(emittedAccounts).toEqual([]);
});
it("should emit updated accounts when subscribeAccounts callback is triggered", async () => {
// Prepare a controlled subscribeAccounts callback.
let accountsCallback: ((accounts: unknown[]) => void) | undefined;
const subscribeAccountsMock = vi.fn((cb: (accounts: unknown[]) => void) => {
accountsCallback = cb;
// Return a dummy unsubscribe function.
return () => {};
});
const mockSigner = {
enable: vi.fn().mockResolvedValue({ result: true }),
getAccounts: vi.fn().mockResolvedValue([]),
subscribeAccounts: subscribeAccountsMock,
getPolkadotSigner: vi
.fn()
.mockImplementation((address: Address) => ({ address })),
};
vi.mocked(MimirPAPISigner).mockImplementation(
vi.fn(
class {
constructor() {
Object.assign(this, mockSigner);
}
} as unknown as typeof MimirPAPISigner,
),
);
await wallet.connect();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let emittedAccounts: any[] = [];
const subscription = wallet.accounts$.subscribe((accounts) => {
emittedAccounts = accounts;
});
// Ensure subscribeAccounts was called.
expect(subscribeAccountsMock).toHaveBeenCalled();
// Trigger the subscription callback with mock account data.
const mockAccountsData = [{ address: "account1" }, { address: "account2" }];
if (accountsCallback) {
accountsCallback(mockAccountsData);
}
// Allow for asynchronous propagation.
await new Promise((resolve) => setTimeout(resolve, 0));
expect(emittedAccounts.length).toBe(2);
expect(emittedAccounts[0]?.address).toBe("account1");
expect(emittedAccounts[0]?.id).toBe("0");
expect(emittedAccounts[1]?.address).toBe("account2");
expect(emittedAccounts[1]?.id).toBe("1");
subscription.unsubscribe();
});
});
describe("getAccounts", () => {
it("should throw error when not connected", async () => {
await expect(wallet.getAccounts()).rejects.toThrow(
"Mimir is not connected",
);
});
it("should return accounts when connected", async () => {
const mockAccounts = [{ address: "test-address" }];
const mockSigner = {
enable: vi.fn().mockResolvedValue({ result: true }),
getAccounts: vi.fn().mockResolvedValue(mockAccounts),
getPolkadotSigner: vi.fn().mockReturnValue({}),
};
vi.mocked(MimirPAPISigner).mockImplementation(
vi.fn(
class {
constructor() {
Object.assign(this, mockSigner);
}
} as unknown as typeof MimirPAPISigner,
),
);
await wallet.connect();
const accounts = await wallet.getAccounts();
expect(accounts[0]?.address).toBe("test-address");
expect(accounts[0]?.id).toBe("0");
});
});
describe("initialize", () => {
it("should connect if previously connected", async () => {
const mockSigner = {
enable: vi.fn().mockResolvedValue({ result: true }),
getAccounts: vi.fn().mockResolvedValue([]),
};
vi.mocked(MimirPAPISigner).mockImplementation(
vi.fn(
class {
constructor() {
Object.assign(this, mockSigner);
}
} as unknown as typeof MimirPAPISigner,
),
);
// @ts-expect-error using protected method for testing
wallet.storage.setItem("connected", JSON.stringify(true));
const connectSpy = vi.spyOn(wallet, "connect");
await wallet.initialize();
expect(connectSpy).toHaveBeenCalled();
});
});