Skip to content

Commit

Permalink
test(wallet-walletconnect): WalletConnect (#470)
Browse files Browse the repository at this point in the history
  • Loading branch information
tien authored Feb 11, 2025
1 parent 79493a3 commit 744f678
Show file tree
Hide file tree
Showing 4 changed files with 309 additions and 2 deletions.
6 changes: 4 additions & 2 deletions packages/wallet-walletconnect/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"scripts": {
"dev": "tsc --build --watch",
"build": "rm -rf build && tsc --build",
"lint": "eslint src"
"lint": "eslint src",
"test": "vitest"
},
"dependencies": {
"@reactive-dot/core": "workspace:^",
Expand All @@ -40,6 +41,7 @@
"@tsconfig/recommended": "^1.0.8",
"@tsconfig/strictest": "^2.0.5",
"eslint": "^9.20.0",
"typescript": "^5.7.3"
"typescript": "^5.7.3",
"vitest": "^3.0.5"
}
}
301 changes: 301 additions & 0 deletions packages/wallet-walletconnect/src/walletconnect.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
import { WalletConnect } from "./walletconnect.js";
import { ReactiveDotError } from "@reactive-dot/core";
import { WalletConnectModal } from "@walletconnect/modal";
import { UniversalProvider } from "@walletconnect/universal-provider";
import { firstValueFrom } from "rxjs";
import { describe, it, expect, vi, beforeEach } from "vitest";

vi.mock("@walletconnect/universal-provider", () => {
const mockedProvider = {
init: vi.fn(),
session: undefined,
client: {
connect: vi.fn(),
request: vi.fn(),
disconnect: vi.fn(),
},
disconnect: vi.fn(),
};

return {
UniversalProvider: {
init: vi.fn(() => Promise.resolve(mockedProvider)),
},
};
});

vi.mock("@walletconnect/modal", () => {
return {
WalletConnectModal: vi.fn().mockImplementation(() => ({
openModal: vi.fn(),
closeModal: vi.fn(),
subscribeModal: vi.fn(),
})),
};
});

let walletConnect: WalletConnect;
let mockProvider: Awaited<ReturnType<typeof UniversalProvider.init>>;

beforeEach(async () => {
mockProvider = {
init: vi.fn(),
session: undefined,
client: {
connect: vi.fn(),
request: vi.fn(),
disconnect: vi.fn(),
},
disconnect: vi.fn(),
} as unknown as Awaited<ReturnType<typeof UniversalProvider.init>>;

vi.mocked(UniversalProvider.init).mockResolvedValue(mockProvider);

walletConnect = new WalletConnect({
projectId: "test-project-id",
providerOptions: {
relayUrl: "test-relay-url",
},
modalOptions: {},
chainIds: ["polkadot:91b171bb158e2d3848fa23a9f1c25182"],
optionalChainIds: ["polkadot:91b171bb158e2d3848fa23a9f1c25182"],
});

await walletConnect.initialize();
});

it("should initialize the WalletConnect instance", async () => {
expect(UniversalProvider.init).toHaveBeenCalledWith({
projectId: "test-project-id",
relayUrl: "test-relay-url",
});
});

it("should emit true to connected$ when a session exists", async () => {
mockProvider.session = {
topic: "test-topic",
namespaces: {},
} as unknown as NonNullable<typeof mockProvider.session>;

vi.mocked(UniversalProvider.init).mockResolvedValue(mockProvider);

await walletConnect.initialize();

const connected = await firstValueFrom(walletConnect.connected$);

expect(connected).toBe(true);
});

describe("initiateConnectionHandshake", () => {
it("should throw an error if the provider client is undefined", async () => {
// @ts-expect-error this is actually possible
mockProvider.client = undefined;

await expect(
walletConnect.initiateConnectionHandshake(),
).rejects.toThrowError(ReactiveDotError);
});

it("should throw an error if neither chainIds nor optionalChainIds are provided", async () => {
walletConnect = new WalletConnect({
projectId: "test-project-id",
providerOptions: {
relayUrl: "test-relay-url",
},
modalOptions: {},
chainIds: [],
optionalChainIds: [],
});

await walletConnect.initialize();

await expect(
walletConnect.initiateConnectionHandshake(),
).rejects.toThrowError(ReactiveDotError);
});

it("should call connect on the client with the correct parameters", async () => {
const mockConnectResult = {
uri: "test-uri",
approval: vi.fn().mockResolvedValue({}),
};

vi.mocked(mockProvider.client.connect).mockResolvedValue(mockConnectResult);

await walletConnect.initiateConnectionHandshake();

expect(mockProvider.client.connect).toHaveBeenCalledWith({
requiredNamespaces: {
polkadot: {
methods: ["polkadot_signTransaction", "polkadot_signMessage"],
chains: ["polkadot:91b171bb158e2d3848fa23a9f1c25182"],
events: ['chainChanged", "accountsChanged'],
},
},
optionalNamespaces: {
polkadot: {
methods: ["polkadot_signTransaction", "polkadot_signMessage"],
chains: ["polkadot:91b171bb158e2d3848fa23a9f1c25182"],
events: ['chainChanged", "accountsChanged'],
},
},
});
});

it("should throw an error if no URI is provided by the connection", async () => {
const mockConnectResult = {
approval: vi.fn().mockResolvedValue({}),
};

vi.mocked(mockProvider.client.connect).mockResolvedValue(mockConnectResult);

await expect(
walletConnect.initiateConnectionHandshake(),
).rejects.toThrowError(ReactiveDotError);
});

it("should return the URI and a promise that resolves when the session is settled", async () => {
const mockConnectResult = {
uri: "test-uri",
approval: vi.fn().mockResolvedValue({}),
};

vi.mocked(mockProvider.client.connect).mockResolvedValue(mockConnectResult);

const result = await walletConnect.initiateConnectionHandshake();

expect(result.uri).toBe("test-uri");
await expect(result.settled).resolves.toBeUndefined();
});
});

describe("connect", () => {
it("should open the modal with the URI returned by initiateConnectionHandshake", async () => {
const mockConnectResult = {
uri: "test-uri",
approval: vi.fn().mockResolvedValue({}),
};

vi.mocked(mockProvider.client.connect).mockResolvedValue(mockConnectResult);

const modal = {
openModal: vi.fn(),
closeModal: vi.fn(),
subscribeModal: vi.fn().mockImplementation(() => () => {}),
};

vi.mocked(WalletConnectModal).mockImplementation(() => modal);

await walletConnect.connect();

expect(modal.openModal).toHaveBeenCalledWith({ uri: "test-uri" });
});

it("should close the modal after a successful connection", async () => {
const mockConnectResult = {
uri: "test-uri",
approval: vi.fn().mockResolvedValue({}),
};

vi.mocked(mockProvider.client.connect).mockResolvedValue(mockConnectResult);

const modal = {
openModal: vi.fn(),
closeModal: vi.fn(),
subscribeModal: vi.fn().mockImplementation(() => () => {}),
};

vi.mocked(WalletConnectModal).mockImplementation(() => modal);

// Simulate a successful connection by resolving the settled promise immediately
vi.mocked(mockProvider.client.connect).mockResolvedValue({
uri: "test-uri",
approval: vi.fn().mockResolvedValue({}),
});

await walletConnect.connect();

expect(modal.closeModal).toHaveBeenCalled();
});

it("should throw an error if the modal is closed before the connection is established", async () => {
const mockConnectResult = {
uri: "test-uri",
approval: vi
.fn()
.mockReturnValue(
new Promise((resolve) => setTimeout(() => resolve({}), 1000)),
),
};

vi.mocked(mockProvider.client.connect).mockResolvedValue(mockConnectResult);

const modal = {
openModal: vi.fn(),
closeModal: vi.fn(),
subscribeModal: vi
.fn()
.mockImplementation(
(callback: (args: { open: boolean }) => unknown) => {
// Simulate the modal closing immediately
callback({ open: false });
return () => {};
},
),
};

vi.mocked(WalletConnectModal).mockImplementation(() => modal);

await expect(walletConnect.connect()).rejects.toThrowError(
"Modal was closed",
);
});
});

describe("disconnect", () => {
it("should call disconnect on the provider and clear the session", async () => {
await walletConnect.disconnect();

expect(mockProvider.disconnect).toHaveBeenCalled();
});
});

describe("accounts$", () => {
it("should emit an empty array when there is no session", async () => {
const accounts = await firstValueFrom(walletConnect.accounts$);
expect(accounts).toEqual([]);
});

it("should map the session accounts to PolkadotSignerAccount objects", async () => {
vi.mocked(mockProvider.client.request).mockResolvedValue({
signature: "0xsignature",
});

mockProvider.session = {
topic: "test-topic",
namespaces: {
polkadot: {
accounts: [
"polkadot:91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3:5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
],
methods: [],
events: [],
},
},
} as unknown as NonNullable<typeof mockProvider.session>;

vi.mocked(UniversalProvider.init).mockResolvedValue(mockProvider);

await walletConnect.initialize();

const accounts = await firstValueFrom(walletConnect.accounts$);

expect(accounts).toHaveLength(1);
expect(accounts[0]?.id).toBe(
"polkadot:91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3:5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
);
expect(accounts[0]?.genesisHash).toBe(
"91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3",
);
});
});
3 changes: 3 additions & 0 deletions packages/wallet-walletconnect/vitest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { defineProject } from "vitest/config";

export default defineProject({});
1 change: 1 addition & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5143,6 +5143,7 @@ __metadata:
"@walletconnect/universal-provider": "npm:^2.18.0"
eslint: "npm:^9.20.0"
typescript: "npm:^5.7.3"
vitest: "npm:^3.0.5"
languageName: unknown
linkType: soft

Expand Down

0 comments on commit 744f678

Please sign in to comment.