@tomo-inc/inject-providers 0.0.16 → 0.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +19 -11
- package/dist/index.js +19 -11
- package/package.json +3 -2
- package/project.json +1 -1
- package/src/__tests__/btc-provider.test.ts +523 -0
- package/src/__tests__/dapp-info.test.ts +603 -0
- package/src/__tests__/dogecoin-provider.test.ts +288 -0
- package/src/__tests__/dom.test.ts +146 -0
- package/src/__tests__/evm-provider.test.ts +1362 -0
- package/src/__tests__/evm-shim.test.ts +114 -0
- package/src/__tests__/evm-utils.test.ts +258 -0
- package/src/__tests__/index.test.ts +26 -0
- package/src/__tests__/messages.test.ts +139 -0
- package/src/__tests__/ready-promise.test.ts +114 -0
- package/src/__tests__/solana-provider.test.ts +375 -0
- package/src/__tests__/solana-utils.test.ts +60 -0
- package/src/__tests__/tron-provider.test.ts +288 -0
- package/src/__tests__/utils.test.ts +78 -0
- package/src/evm/metamask.ts +1 -1
- package/src/solana/phantom.ts +9 -7
- package/src/utils/dapp-info.ts +13 -8
- package/src/utils/dom.ts +2 -1
- package/vitest.config.ts +27 -0
|
@@ -0,0 +1,1362 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import messages from "../evm/messages";
|
|
3
|
+
import { EvmProvider, initializeEvmProvider, setGlobalProvider } from "../evm/metamask";
|
|
4
|
+
import { EMITTED_NOTIFICATIONS } from "../evm/utils";
|
|
5
|
+
import { IProductInfo } from "../types";
|
|
6
|
+
import * as utils from "../utils/index";
|
|
7
|
+
|
|
8
|
+
describe("EvmProvider", () => {
|
|
9
|
+
let provider: EvmProvider;
|
|
10
|
+
let mockSendRequest: (chainType: string, data: any) => void;
|
|
11
|
+
let mockOnResponse: any;
|
|
12
|
+
let productInfo: IProductInfo;
|
|
13
|
+
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
mockSendRequest = vi.fn() as any;
|
|
16
|
+
mockOnResponse = vi.fn().mockResolvedValue({ data: null });
|
|
17
|
+
|
|
18
|
+
productInfo = {
|
|
19
|
+
name: "Test Wallet",
|
|
20
|
+
rdns: "com.test.wallet",
|
|
21
|
+
icon: "test-icon.png",
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Mock getDappInfo to prevent initialization errors
|
|
25
|
+
vi.spyOn(utils, "getDappInfo").mockResolvedValue({
|
|
26
|
+
origin: "https://test.com",
|
|
27
|
+
title: "Test",
|
|
28
|
+
desc: "Test desc",
|
|
29
|
+
favicon: "",
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// Mock document.body to prevent innerText errors
|
|
33
|
+
const bodyElement = document.createElement("body");
|
|
34
|
+
Object.defineProperty(bodyElement, "innerText", {
|
|
35
|
+
value: "",
|
|
36
|
+
writable: true,
|
|
37
|
+
configurable: true,
|
|
38
|
+
});
|
|
39
|
+
Object.defineProperty(document, "body", {
|
|
40
|
+
value: bodyElement,
|
|
41
|
+
writable: true,
|
|
42
|
+
configurable: true,
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
afterEach(() => {
|
|
47
|
+
vi.restoreAllMocks();
|
|
48
|
+
// Clean up window.ethereum
|
|
49
|
+
delete (window as any).ethereum;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe("constructor", () => {
|
|
53
|
+
it("should create EvmProvider instance with valid options", () => {
|
|
54
|
+
provider = new EvmProvider(productInfo, {
|
|
55
|
+
sendRequest: mockSendRequest,
|
|
56
|
+
onResponse: mockOnResponse,
|
|
57
|
+
logger: console,
|
|
58
|
+
maxEventListeners: 100,
|
|
59
|
+
shouldSendMetadata: true,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
expect(provider).toBeInstanceOf(EvmProvider);
|
|
63
|
+
expect(provider.name).toBe("Test Wallet");
|
|
64
|
+
expect(provider.icon).toBe("test-icon.png");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("should throw error for invalid maxEventListeners", () => {
|
|
68
|
+
expect(() => {
|
|
69
|
+
new EvmProvider(productInfo, {
|
|
70
|
+
sendRequest: mockSendRequest,
|
|
71
|
+
onResponse: mockOnResponse,
|
|
72
|
+
maxEventListeners: "invalid" as any,
|
|
73
|
+
shouldSendMetadata: true,
|
|
74
|
+
});
|
|
75
|
+
}).toThrow(messages.errors.invalidOptions("invalid", true));
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("should throw error for invalid shouldSendMetadata", () => {
|
|
79
|
+
expect(() => {
|
|
80
|
+
new EvmProvider(productInfo, {
|
|
81
|
+
sendRequest: mockSendRequest,
|
|
82
|
+
onResponse: mockOnResponse,
|
|
83
|
+
maxEventListeners: 100,
|
|
84
|
+
shouldSendMetadata: "invalid" as any,
|
|
85
|
+
});
|
|
86
|
+
}).toThrow(messages.errors.invalidOptions(100, "invalid"));
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("should use default logger if not provided", () => {
|
|
90
|
+
provider = new EvmProvider(productInfo, {
|
|
91
|
+
sendRequest: mockSendRequest,
|
|
92
|
+
onResponse: mockOnResponse,
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
expect(provider).toBeInstanceOf(EvmProvider);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe("request", () => {
|
|
100
|
+
beforeEach(() => {
|
|
101
|
+
provider = new EvmProvider(productInfo, {
|
|
102
|
+
sendRequest: mockSendRequest,
|
|
103
|
+
onResponse: mockOnResponse,
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("should throw error if method is not provided", async () => {
|
|
108
|
+
await expect(provider.request({ method: "" } as any)).rejects.toThrow();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("should throw error if method is not a string", async () => {
|
|
112
|
+
await expect(provider.request({ method: 123 } as any)).rejects.toThrow();
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("should throw error if args is not an object", async () => {
|
|
116
|
+
await expect(provider.request(null as any)).rejects.toThrow();
|
|
117
|
+
await expect(provider.request([] as any)).rejects.toThrow();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("should validate wallet_addEthereumChain params", async () => {
|
|
121
|
+
await expect(
|
|
122
|
+
provider.request({
|
|
123
|
+
method: "wallet_addEthereumChain",
|
|
124
|
+
params: [],
|
|
125
|
+
}),
|
|
126
|
+
).rejects.toThrow();
|
|
127
|
+
|
|
128
|
+
await expect(
|
|
129
|
+
provider.request({
|
|
130
|
+
method: "wallet_addEthereumChain",
|
|
131
|
+
params: [{}],
|
|
132
|
+
}),
|
|
133
|
+
).rejects.toThrow();
|
|
134
|
+
|
|
135
|
+
await expect(
|
|
136
|
+
provider.request({
|
|
137
|
+
method: "wallet_addEthereumChain",
|
|
138
|
+
params: [{ chainName: "Test" }],
|
|
139
|
+
}),
|
|
140
|
+
).rejects.toThrow();
|
|
141
|
+
|
|
142
|
+
mockOnResponse.mockResolvedValue({
|
|
143
|
+
data: true,
|
|
144
|
+
method: "wallet_addEthereumChain",
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
await provider.request({
|
|
148
|
+
method: "wallet_addEthereumChain",
|
|
149
|
+
params: [
|
|
150
|
+
{
|
|
151
|
+
chainName: "Test Chain",
|
|
152
|
+
rpcUrls: ["https://test.com"],
|
|
153
|
+
chainId: "0x1",
|
|
154
|
+
nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 },
|
|
155
|
+
},
|
|
156
|
+
],
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
expect(mockSendRequest).toHaveBeenCalled();
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("should validate personal_sign params", async () => {
|
|
163
|
+
await expect(
|
|
164
|
+
provider.request({
|
|
165
|
+
method: "personal_sign",
|
|
166
|
+
params: [],
|
|
167
|
+
}),
|
|
168
|
+
).rejects.toThrow();
|
|
169
|
+
|
|
170
|
+
await expect(
|
|
171
|
+
provider.request({
|
|
172
|
+
method: "personal_sign",
|
|
173
|
+
params: ["0x123"],
|
|
174
|
+
}),
|
|
175
|
+
).rejects.toThrow();
|
|
176
|
+
|
|
177
|
+
await expect(
|
|
178
|
+
provider.request({
|
|
179
|
+
method: "personal_sign",
|
|
180
|
+
params: [null, "0x123"],
|
|
181
|
+
}),
|
|
182
|
+
).rejects.toThrow();
|
|
183
|
+
|
|
184
|
+
mockOnResponse.mockResolvedValue({
|
|
185
|
+
data: "0xsignature",
|
|
186
|
+
method: "personal_sign",
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
await provider.request({
|
|
190
|
+
method: "personal_sign",
|
|
191
|
+
params: ["0x123", "0x456"],
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
expect(mockSendRequest).toHaveBeenCalled();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("should validate wallet_watchAsset params", async () => {
|
|
198
|
+
await expect(
|
|
199
|
+
provider.request({
|
|
200
|
+
method: "wallet_watchAsset",
|
|
201
|
+
params: { type: "ERC20" },
|
|
202
|
+
}),
|
|
203
|
+
).rejects.toThrow();
|
|
204
|
+
|
|
205
|
+
await expect(
|
|
206
|
+
provider.request({
|
|
207
|
+
method: "wallet_watchAsset",
|
|
208
|
+
params: {
|
|
209
|
+
type: "ERC20",
|
|
210
|
+
options: {},
|
|
211
|
+
},
|
|
212
|
+
}),
|
|
213
|
+
).rejects.toThrow();
|
|
214
|
+
|
|
215
|
+
await expect(
|
|
216
|
+
provider.request({
|
|
217
|
+
method: "wallet_watchAsset",
|
|
218
|
+
params: {
|
|
219
|
+
type: "ERC20",
|
|
220
|
+
options: { address: "0x123" },
|
|
221
|
+
},
|
|
222
|
+
}),
|
|
223
|
+
).rejects.toThrow();
|
|
224
|
+
|
|
225
|
+
mockOnResponse.mockResolvedValue({
|
|
226
|
+
data: true,
|
|
227
|
+
method: "wallet_watchAsset",
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
await provider.request({
|
|
231
|
+
method: "wallet_watchAsset",
|
|
232
|
+
params: {
|
|
233
|
+
type: "ERC20",
|
|
234
|
+
options: {
|
|
235
|
+
address: "0x123",
|
|
236
|
+
symbol: "TST",
|
|
237
|
+
decimals: 18,
|
|
238
|
+
},
|
|
239
|
+
},
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
expect(mockSendRequest).toHaveBeenCalled();
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("should validate params type", async () => {
|
|
246
|
+
await expect(
|
|
247
|
+
provider.request({
|
|
248
|
+
method: "eth_getBalance",
|
|
249
|
+
params: "invalid" as any,
|
|
250
|
+
}),
|
|
251
|
+
).rejects.toThrow();
|
|
252
|
+
|
|
253
|
+
await expect(
|
|
254
|
+
provider.request({
|
|
255
|
+
method: "eth_getBalance",
|
|
256
|
+
params: null as any,
|
|
257
|
+
}),
|
|
258
|
+
).rejects.toThrow();
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it("should handle _wallet_getProviderState response", async () => {
|
|
262
|
+
const accounts = ["0x123", "0x456"];
|
|
263
|
+
mockOnResponse.mockResolvedValue({
|
|
264
|
+
data: { accounts },
|
|
265
|
+
method: "_wallet_getProviderState",
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
const accountsChangedSpy = vi.fn();
|
|
269
|
+
provider.on("accountsChanged", accountsChangedSpy);
|
|
270
|
+
|
|
271
|
+
await provider.request({
|
|
272
|
+
method: "_wallet_getProviderState",
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// Wait for async operations
|
|
276
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
277
|
+
|
|
278
|
+
expect(mockSendRequest).toHaveBeenCalled();
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it("should handle wallet_revokePermissions response", async () => {
|
|
282
|
+
mockOnResponse.mockResolvedValue({
|
|
283
|
+
data: true,
|
|
284
|
+
method: "wallet_revokePermissions",
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
const accountsChangedSpy = vi.fn();
|
|
288
|
+
provider.on("accountsChanged", accountsChangedSpy);
|
|
289
|
+
|
|
290
|
+
await provider.request({
|
|
291
|
+
method: "wallet_revokePermissions",
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
// Wait for async operations
|
|
295
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
296
|
+
|
|
297
|
+
expect(mockSendRequest).toHaveBeenCalled();
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
it("should handle connect methods response", async () => {
|
|
301
|
+
const accounts = ["0x123"];
|
|
302
|
+
mockOnResponse.mockResolvedValue({
|
|
303
|
+
data: accounts,
|
|
304
|
+
method: "eth_requestAccounts",
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
const accountsChangedSpy = vi.fn();
|
|
308
|
+
provider.on("accountsChanged", accountsChangedSpy);
|
|
309
|
+
|
|
310
|
+
await provider.request({
|
|
311
|
+
method: "eth_requestAccounts",
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// Wait for async operations
|
|
315
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
316
|
+
|
|
317
|
+
expect(mockSendRequest).toHaveBeenCalled();
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it("should handle wallet_switchEthereumChain response", async () => {
|
|
321
|
+
const chainId = "0x5";
|
|
322
|
+
mockOnResponse.mockResolvedValue({
|
|
323
|
+
data: chainId,
|
|
324
|
+
method: "wallet_switchEthereumChain",
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
const chainChangedSpy = vi.fn();
|
|
328
|
+
provider.on("chainChanged", chainChangedSpy);
|
|
329
|
+
|
|
330
|
+
await provider.request({
|
|
331
|
+
method: "wallet_switchEthereumChain",
|
|
332
|
+
params: [{ chainId }],
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// Wait for async operations
|
|
336
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
337
|
+
|
|
338
|
+
expect(mockSendRequest).toHaveBeenCalled();
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it("should handle eth_chainId response", async () => {
|
|
342
|
+
const chainId = "0x1";
|
|
343
|
+
mockOnResponse.mockResolvedValue({
|
|
344
|
+
data: chainId,
|
|
345
|
+
method: "eth_chainId",
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
const chainChangedSpy = vi.fn();
|
|
349
|
+
provider.on("chainChanged", chainChangedSpy);
|
|
350
|
+
|
|
351
|
+
await provider.request({
|
|
352
|
+
method: "eth_chainId",
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
// Wait for async operations
|
|
356
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
357
|
+
|
|
358
|
+
expect(mockSendRequest).toHaveBeenCalled();
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
describe("connect", () => {
|
|
363
|
+
beforeEach(() => {
|
|
364
|
+
provider = new EvmProvider(productInfo, {
|
|
365
|
+
sendRequest: mockSendRequest,
|
|
366
|
+
onResponse: mockOnResponse,
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it("should emit connect event when accounts are returned", async () => {
|
|
371
|
+
const connectSpy = vi.spyOn(provider, "emit");
|
|
372
|
+
// Mock request to return accounts
|
|
373
|
+
vi.spyOn(provider, "request").mockResolvedValue(["0x123"] as any);
|
|
374
|
+
|
|
375
|
+
await provider.connect();
|
|
376
|
+
|
|
377
|
+
expect(connectSpy).toHaveBeenCalledWith("connect", {});
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
describe("disconnect", () => {
|
|
382
|
+
beforeEach(() => {
|
|
383
|
+
provider = new EvmProvider(productInfo, {
|
|
384
|
+
sendRequest: mockSendRequest,
|
|
385
|
+
onResponse: mockOnResponse,
|
|
386
|
+
});
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
it("should emit disconnect event when disconnected", async () => {
|
|
390
|
+
const disconnectSpy = vi.spyOn(provider, "emit");
|
|
391
|
+
// Mock request to return disconnected state
|
|
392
|
+
vi.spyOn(provider, "request").mockResolvedValue({ isConnected: false } as any);
|
|
393
|
+
|
|
394
|
+
await provider.disconnect();
|
|
395
|
+
|
|
396
|
+
expect(disconnectSpy).toHaveBeenCalledWith("disconnect", null);
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
describe("isConnected", () => {
|
|
401
|
+
beforeEach(() => {
|
|
402
|
+
provider = new EvmProvider(productInfo, {
|
|
403
|
+
sendRequest: mockSendRequest,
|
|
404
|
+
onResponse: mockOnResponse,
|
|
405
|
+
});
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
it("should return false when not connected", () => {
|
|
409
|
+
expect(provider.isConnected()).toBe(false);
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
it("should return true when connected", async () => {
|
|
413
|
+
// Trigger connect by emitting connect event
|
|
414
|
+
provider.emit("connect", { chainId: "0x1" });
|
|
415
|
+
expect(provider.isConnected()).toBe(true);
|
|
416
|
+
});
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
describe("sendAsync", () => {
|
|
420
|
+
beforeEach(() => {
|
|
421
|
+
provider = new EvmProvider(productInfo, {
|
|
422
|
+
sendRequest: mockSendRequest,
|
|
423
|
+
onResponse: mockOnResponse,
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
it("should call _rpcRequest with callback", () => {
|
|
428
|
+
const callback = vi.fn();
|
|
429
|
+
const payload = {
|
|
430
|
+
jsonrpc: "2.0" as const,
|
|
431
|
+
id: 1,
|
|
432
|
+
method: "eth_getBalance",
|
|
433
|
+
params: ["0x123"],
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
// Mock _rpcEngine.handle
|
|
437
|
+
const mockHandle = vi.fn();
|
|
438
|
+
(provider as any)._rpcEngine = {
|
|
439
|
+
handle: mockHandle,
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
provider.sendAsync(payload, callback);
|
|
443
|
+
|
|
444
|
+
expect(mockHandle).toHaveBeenCalledWith(payload, callback);
|
|
445
|
+
});
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
describe("event listeners", () => {
|
|
449
|
+
beforeEach(() => {
|
|
450
|
+
provider = new EvmProvider(productInfo, {
|
|
451
|
+
sendRequest: mockSendRequest,
|
|
452
|
+
onResponse: mockOnResponse,
|
|
453
|
+
logger: {
|
|
454
|
+
log: vi.fn(),
|
|
455
|
+
warn: vi.fn(),
|
|
456
|
+
error: vi.fn(),
|
|
457
|
+
debug: vi.fn(),
|
|
458
|
+
info: vi.fn(),
|
|
459
|
+
trace: vi.fn(),
|
|
460
|
+
},
|
|
461
|
+
});
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it("should warn on deprecated addListener", () => {
|
|
465
|
+
const listener = vi.fn();
|
|
466
|
+
const warnSpy = vi.spyOn((provider as any)._log, "warn");
|
|
467
|
+
|
|
468
|
+
provider.addListener("close", listener);
|
|
469
|
+
|
|
470
|
+
expect(warnSpy).toHaveBeenCalled();
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
it("should warn on deprecated once", () => {
|
|
474
|
+
const listener = vi.fn();
|
|
475
|
+
const warnSpy = vi.spyOn((provider as any)._log, "warn");
|
|
476
|
+
|
|
477
|
+
provider.once("data", listener);
|
|
478
|
+
|
|
479
|
+
expect(warnSpy).toHaveBeenCalled();
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
it("should warn on deprecated prependListener", () => {
|
|
483
|
+
const listener = vi.fn();
|
|
484
|
+
const warnSpy = vi.spyOn((provider as any)._log, "warn");
|
|
485
|
+
|
|
486
|
+
provider.prependListener("networkChanged", listener);
|
|
487
|
+
|
|
488
|
+
expect(warnSpy).toHaveBeenCalled();
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
it("should warn on deprecated prependOnceListener", () => {
|
|
492
|
+
const listener = vi.fn();
|
|
493
|
+
const warnSpy = vi.spyOn((provider as any)._log, "warn");
|
|
494
|
+
|
|
495
|
+
provider.prependOnceListener("notification", listener);
|
|
496
|
+
|
|
497
|
+
expect(warnSpy).toHaveBeenCalled();
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
it("should not warn twice for same event", () => {
|
|
501
|
+
const listener = vi.fn();
|
|
502
|
+
const warnSpy = vi.spyOn((provider as any)._log, "warn");
|
|
503
|
+
|
|
504
|
+
provider.addListener("close", listener);
|
|
505
|
+
provider.addListener("close", listener);
|
|
506
|
+
|
|
507
|
+
expect(warnSpy).toHaveBeenCalledTimes(1);
|
|
508
|
+
});
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
describe("JSON-RPC notifications", () => {
|
|
512
|
+
beforeEach(() => {
|
|
513
|
+
provider = new EvmProvider(productInfo, {
|
|
514
|
+
sendRequest: mockSendRequest,
|
|
515
|
+
onResponse: mockOnResponse,
|
|
516
|
+
});
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
it("should handle wallet_accountsChanged notification", async () => {
|
|
520
|
+
// Wait for initialization to complete
|
|
521
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
522
|
+
|
|
523
|
+
const accountsChangedSpy = vi.fn();
|
|
524
|
+
provider.on("accountsChanged", accountsChangedSpy);
|
|
525
|
+
|
|
526
|
+
// Access the internal jsonRpcConnection through _rpcEngine
|
|
527
|
+
const rpcEngine = (provider as any)._rpcEngine;
|
|
528
|
+
const middlewares = (rpcEngine as any)._middleware || [];
|
|
529
|
+
const streamMiddleware = middlewares.find((m: any) => m && m.stream);
|
|
530
|
+
|
|
531
|
+
if (streamMiddleware && streamMiddleware.events) {
|
|
532
|
+
streamMiddleware.events.emit("notification", {
|
|
533
|
+
method: "wallet_accountsChanged",
|
|
534
|
+
params: [["0x123", "0x456"]],
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
538
|
+
expect(accountsChangedSpy).toHaveBeenCalled();
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
it("should handle wallet_unlockStateChanged notification", async () => {
|
|
543
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
544
|
+
|
|
545
|
+
const accountsChangedSpy = vi.fn();
|
|
546
|
+
provider.on("accountsChanged", accountsChangedSpy);
|
|
547
|
+
|
|
548
|
+
const rpcEngine = (provider as any)._rpcEngine;
|
|
549
|
+
const middlewares = (rpcEngine as any)._middleware || [];
|
|
550
|
+
const streamMiddleware = middlewares.find((m: any) => m && m.stream);
|
|
551
|
+
|
|
552
|
+
if (streamMiddleware && streamMiddleware.events) {
|
|
553
|
+
streamMiddleware.events.emit("notification", {
|
|
554
|
+
method: "wallet_unlockStateChanged",
|
|
555
|
+
params: [{ accounts: ["0x123"], isUnlocked: true }],
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
559
|
+
}
|
|
560
|
+
});
|
|
561
|
+
|
|
562
|
+
it("should handle wallet_chainChanged notification", async () => {
|
|
563
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
564
|
+
|
|
565
|
+
const chainChangedSpy = vi.fn();
|
|
566
|
+
provider.on("chainChanged", chainChangedSpy);
|
|
567
|
+
|
|
568
|
+
const rpcEngine = (provider as any)._rpcEngine;
|
|
569
|
+
const middlewares = (rpcEngine as any)._middleware || [];
|
|
570
|
+
const streamMiddleware = middlewares.find((m: any) => m && m.stream);
|
|
571
|
+
|
|
572
|
+
if (streamMiddleware && streamMiddleware.events) {
|
|
573
|
+
streamMiddleware.events.emit("notification", {
|
|
574
|
+
method: "wallet_chainChanged",
|
|
575
|
+
params: [{ chainId: "0x5" }],
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
it("should handle EMITTED_NOTIFICATIONS", async () => {
|
|
583
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
584
|
+
|
|
585
|
+
const dataSpy = vi.fn();
|
|
586
|
+
const messageSpy = vi.fn();
|
|
587
|
+
const notificationSpy = vi.fn();
|
|
588
|
+
|
|
589
|
+
provider.on("data", dataSpy);
|
|
590
|
+
provider.on("message", messageSpy);
|
|
591
|
+
provider.on("notification", notificationSpy);
|
|
592
|
+
|
|
593
|
+
const rpcEngine = (provider as any)._rpcEngine;
|
|
594
|
+
const middlewares = (rpcEngine as any)._middleware || [];
|
|
595
|
+
const streamMiddleware = middlewares.find((m: any) => m && m.stream);
|
|
596
|
+
|
|
597
|
+
if (streamMiddleware && streamMiddleware.events) {
|
|
598
|
+
const notificationMethod = EMITTED_NOTIFICATIONS[0];
|
|
599
|
+
streamMiddleware.events.emit("notification", {
|
|
600
|
+
method: notificationMethod,
|
|
601
|
+
params: { result: "test" },
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
it("should handle WALLET_STREAM_FAILURE", async () => {
|
|
609
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
610
|
+
|
|
611
|
+
const consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
612
|
+
|
|
613
|
+
const rpcEngine = (provider as any)._rpcEngine;
|
|
614
|
+
const middlewares = (rpcEngine as any)._middleware || [];
|
|
615
|
+
const streamMiddleware = middlewares.find((m: any) => m && m.stream);
|
|
616
|
+
|
|
617
|
+
if (streamMiddleware && streamMiddleware.events) {
|
|
618
|
+
streamMiddleware.events.emit("notification", {
|
|
619
|
+
method: "WALLET_STREAM_FAILURE",
|
|
620
|
+
params: {},
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
consoleErrorSpy.mockRestore();
|
|
627
|
+
});
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
describe("subscribeWalletEventsCallback", () => {
|
|
631
|
+
beforeEach(async () => {
|
|
632
|
+
// Mock the initial state request
|
|
633
|
+
mockOnResponse.mockResolvedValueOnce({
|
|
634
|
+
data: {
|
|
635
|
+
accounts: [],
|
|
636
|
+
chainId: "0x1",
|
|
637
|
+
isUnlocked: false,
|
|
638
|
+
isConnected: false,
|
|
639
|
+
},
|
|
640
|
+
method: "wallet_getProviderState",
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
provider = new EvmProvider(productInfo, {
|
|
644
|
+
sendRequest: mockSendRequest,
|
|
645
|
+
onResponse: mockOnResponse,
|
|
646
|
+
});
|
|
647
|
+
// Wait for _initializeState to complete and set up window.addEventListener
|
|
648
|
+
await new Promise((resolve) => {
|
|
649
|
+
provider.once("_initialized", resolve);
|
|
650
|
+
// Fallback timeout
|
|
651
|
+
setTimeout(resolve, 500);
|
|
652
|
+
});
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
it("should handle accountsChanged message", async () => {
|
|
656
|
+
const accountsChangedSpy = vi.fn();
|
|
657
|
+
provider.on("accountsChanged", accountsChangedSpy);
|
|
658
|
+
|
|
659
|
+
// Simulate window message event
|
|
660
|
+
// Note: chainType is "evm" (lowercase) as defined by ChainTypes.EVM
|
|
661
|
+
const event = new MessageEvent("message", {
|
|
662
|
+
data: {
|
|
663
|
+
type: "subscribeWalletEvents",
|
|
664
|
+
method: "accountsChanged",
|
|
665
|
+
data: {
|
|
666
|
+
evm: [{ address: "0x123" }, { address: "0x456" }],
|
|
667
|
+
},
|
|
668
|
+
},
|
|
669
|
+
});
|
|
670
|
+
|
|
671
|
+
window.dispatchEvent(event);
|
|
672
|
+
|
|
673
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
674
|
+
|
|
675
|
+
expect(accountsChangedSpy).toHaveBeenCalled();
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
it("should handle chainChanged message", async () => {
|
|
679
|
+
const chainChangedSpy = vi.fn();
|
|
680
|
+
provider.on("chainChanged", chainChangedSpy);
|
|
681
|
+
|
|
682
|
+
// Note: chainType is "evm" (lowercase), so type should start with "evm:"
|
|
683
|
+
const event = new MessageEvent("message", {
|
|
684
|
+
data: {
|
|
685
|
+
type: "subscribeWalletEvents",
|
|
686
|
+
method: "chainChanged",
|
|
687
|
+
data: {
|
|
688
|
+
chainId: "5",
|
|
689
|
+
type: "evm:5",
|
|
690
|
+
},
|
|
691
|
+
},
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
window.dispatchEvent(event);
|
|
695
|
+
|
|
696
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
697
|
+
|
|
698
|
+
expect(chainChangedSpy).toHaveBeenCalled();
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
it("should ignore non-subscribeWalletEvents messages", () => {
|
|
702
|
+
const accountsChangedSpy = vi.fn();
|
|
703
|
+
provider.on("accountsChanged", accountsChangedSpy);
|
|
704
|
+
|
|
705
|
+
const event = new MessageEvent("message", {
|
|
706
|
+
data: {
|
|
707
|
+
type: "other",
|
|
708
|
+
method: "accountsChanged",
|
|
709
|
+
},
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
window.dispatchEvent(event);
|
|
713
|
+
|
|
714
|
+
expect(accountsChangedSpy).not.toHaveBeenCalled();
|
|
715
|
+
});
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
describe("keepAlive", () => {
|
|
719
|
+
let keepAliveMockSendRequest: ReturnType<typeof vi.fn>;
|
|
720
|
+
let keepAliveMockOnResponse: ReturnType<typeof vi.fn>;
|
|
721
|
+
let keepAliveSpy: ReturnType<typeof vi.fn>;
|
|
722
|
+
|
|
723
|
+
beforeEach(async () => {
|
|
724
|
+
vi.useFakeTimers();
|
|
725
|
+
keepAliveMockSendRequest = vi.fn();
|
|
726
|
+
|
|
727
|
+
// Mock keepAlive response - always resolve to allow testing
|
|
728
|
+
keepAliveMockOnResponse = vi.fn().mockImplementation((args: any) => {
|
|
729
|
+
if (args?.method === "keepAlive") {
|
|
730
|
+
return Promise.resolve({
|
|
731
|
+
data: true,
|
|
732
|
+
method: "keepAlive",
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
// For other methods, return default
|
|
736
|
+
return Promise.resolve({ data: null });
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
// Mock initial state request
|
|
740
|
+
keepAliveMockOnResponse.mockResolvedValueOnce({
|
|
741
|
+
data: {
|
|
742
|
+
accounts: [],
|
|
743
|
+
chainId: "0x1",
|
|
744
|
+
isUnlocked: false,
|
|
745
|
+
isConnected: false,
|
|
746
|
+
},
|
|
747
|
+
method: "wallet_getProviderState",
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
provider = new EvmProvider(productInfo, {
|
|
751
|
+
sendRequest: keepAliveMockSendRequest as any,
|
|
752
|
+
onResponse: keepAliveMockOnResponse,
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
// Spy on keepAlive method to track calls
|
|
756
|
+
keepAliveSpy = vi.spyOn(provider as any, "keepAlive");
|
|
757
|
+
|
|
758
|
+
// Wait for initialization to complete
|
|
759
|
+
await new Promise((resolve) => {
|
|
760
|
+
provider.once("_initialized", resolve);
|
|
761
|
+
// Process pending timers
|
|
762
|
+
vi.runAllTicks();
|
|
763
|
+
});
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
afterEach(() => {
|
|
767
|
+
vi.useRealTimers();
|
|
768
|
+
keepAliveSpy.mockRestore();
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
it("should call keepAlive periodically", async () => {
|
|
772
|
+
// Clear previous calls
|
|
773
|
+
keepAliveMockSendRequest.mockClear();
|
|
774
|
+
keepAliveSpy.mockClear();
|
|
775
|
+
|
|
776
|
+
// Fast-forward time to trigger keepAlive (1000ms interval)
|
|
777
|
+
// Advance by 1500ms to trigger at least one keepAlive call
|
|
778
|
+
vi.advanceTimersByTime(1500);
|
|
779
|
+
|
|
780
|
+
// Process only the current tick's timers, not all future timers
|
|
781
|
+
// This prevents infinite loop
|
|
782
|
+
await vi.runAllTicks();
|
|
783
|
+
|
|
784
|
+
// Verify keepAlive was called
|
|
785
|
+
expect(keepAliveSpy).toHaveBeenCalled();
|
|
786
|
+
|
|
787
|
+
// Verify sendRequest was called with keepAlive method
|
|
788
|
+
const keepAliveCalls = keepAliveMockSendRequest.mock.calls.filter((call) => call[1]?.method === "keepAlive");
|
|
789
|
+
expect(keepAliveCalls.length).toBeGreaterThan(0);
|
|
790
|
+
});
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
describe("_rpcRequest", () => {
|
|
794
|
+
beforeEach(() => {
|
|
795
|
+
provider = new EvmProvider(productInfo, {
|
|
796
|
+
sendRequest: mockSendRequest,
|
|
797
|
+
onResponse: mockOnResponse,
|
|
798
|
+
});
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
it("should handle single payload", () => {
|
|
802
|
+
const callback = vi.fn();
|
|
803
|
+
const payload: any = {
|
|
804
|
+
method: "eth_getBalance",
|
|
805
|
+
params: ["0x123"],
|
|
806
|
+
};
|
|
807
|
+
|
|
808
|
+
const mockHandle = vi.fn();
|
|
809
|
+
(provider as any)._rpcEngine = {
|
|
810
|
+
handle: mockHandle,
|
|
811
|
+
};
|
|
812
|
+
|
|
813
|
+
(provider as any)._rpcRequest(payload, callback);
|
|
814
|
+
|
|
815
|
+
expect(mockHandle).toHaveBeenCalled();
|
|
816
|
+
expect(payload.jsonrpc).toBe("2.0");
|
|
817
|
+
});
|
|
818
|
+
|
|
819
|
+
it("should handle payload with existing jsonrpc", () => {
|
|
820
|
+
const callback = vi.fn();
|
|
821
|
+
const payload = {
|
|
822
|
+
jsonrpc: "2.0",
|
|
823
|
+
method: "eth_getBalance",
|
|
824
|
+
params: ["0x123"],
|
|
825
|
+
};
|
|
826
|
+
|
|
827
|
+
const mockHandle = vi.fn();
|
|
828
|
+
(provider as any)._rpcEngine = {
|
|
829
|
+
handle: mockHandle,
|
|
830
|
+
};
|
|
831
|
+
|
|
832
|
+
(provider as any)._rpcRequest(payload, callback);
|
|
833
|
+
|
|
834
|
+
expect(mockHandle).toHaveBeenCalled();
|
|
835
|
+
});
|
|
836
|
+
|
|
837
|
+
it("should handle eth_accounts method", () => {
|
|
838
|
+
const callback = vi.fn();
|
|
839
|
+
const payload = {
|
|
840
|
+
method: "eth_accounts",
|
|
841
|
+
params: [],
|
|
842
|
+
};
|
|
843
|
+
|
|
844
|
+
const mockHandle = vi.fn((p, cb) => {
|
|
845
|
+
cb(null, { result: ["0x123"] });
|
|
846
|
+
});
|
|
847
|
+
(provider as any)._rpcEngine = {
|
|
848
|
+
handle: mockHandle,
|
|
849
|
+
};
|
|
850
|
+
|
|
851
|
+
const accountsChangedSpy = vi.fn();
|
|
852
|
+
provider.on("accountsChanged", accountsChangedSpy);
|
|
853
|
+
|
|
854
|
+
(provider as any)._rpcRequest(payload, callback);
|
|
855
|
+
|
|
856
|
+
expect(mockHandle).toHaveBeenCalled();
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
it("should handle eth_requestAccounts method", () => {
|
|
860
|
+
const callback = vi.fn();
|
|
861
|
+
const payload = {
|
|
862
|
+
method: "eth_requestAccounts",
|
|
863
|
+
params: [],
|
|
864
|
+
};
|
|
865
|
+
|
|
866
|
+
const mockHandle = vi.fn((p, cb) => {
|
|
867
|
+
cb(null, { result: ["0x123"] });
|
|
868
|
+
});
|
|
869
|
+
(provider as any)._rpcEngine = {
|
|
870
|
+
handle: mockHandle,
|
|
871
|
+
};
|
|
872
|
+
|
|
873
|
+
const accountsChangedSpy = vi.fn();
|
|
874
|
+
provider.on("accountsChanged", accountsChangedSpy);
|
|
875
|
+
|
|
876
|
+
(provider as any)._rpcRequest(payload, callback);
|
|
877
|
+
|
|
878
|
+
expect(mockHandle).toHaveBeenCalled();
|
|
879
|
+
});
|
|
880
|
+
|
|
881
|
+
it("should handle array payload", () => {
|
|
882
|
+
const callback = vi.fn();
|
|
883
|
+
const payload = [
|
|
884
|
+
{
|
|
885
|
+
jsonrpc: "2.0",
|
|
886
|
+
method: "eth_getBalance",
|
|
887
|
+
params: ["0x123"],
|
|
888
|
+
},
|
|
889
|
+
];
|
|
890
|
+
|
|
891
|
+
const mockHandle = vi.fn();
|
|
892
|
+
(provider as any)._rpcEngine = {
|
|
893
|
+
handle: mockHandle,
|
|
894
|
+
};
|
|
895
|
+
|
|
896
|
+
(provider as any)._rpcRequest(payload, callback);
|
|
897
|
+
|
|
898
|
+
expect(mockHandle).toHaveBeenCalled();
|
|
899
|
+
});
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
describe("_handleConnect", () => {
|
|
903
|
+
beforeEach(() => {
|
|
904
|
+
provider = new EvmProvider(productInfo, {
|
|
905
|
+
sendRequest: mockSendRequest,
|
|
906
|
+
onResponse: mockOnResponse,
|
|
907
|
+
});
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
it("should emit connect event when not connected", () => {
|
|
911
|
+
const connectSpy = vi.fn();
|
|
912
|
+
provider.on("connect", connectSpy);
|
|
913
|
+
|
|
914
|
+
(provider as any)._handleConnect("0x1");
|
|
915
|
+
|
|
916
|
+
expect(connectSpy).toHaveBeenCalledWith({ chainId: "0x1" });
|
|
917
|
+
expect(provider.isConnected()).toBe(true);
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
it("should not emit connect event when already connected", () => {
|
|
921
|
+
provider.emit("connect", { chainId: "0x1" });
|
|
922
|
+
const connectSpy = vi.fn();
|
|
923
|
+
provider.on("connect", connectSpy);
|
|
924
|
+
|
|
925
|
+
(provider as any)._handleConnect("0x2");
|
|
926
|
+
|
|
927
|
+
// Should not emit again
|
|
928
|
+
expect(connectSpy).not.toHaveBeenCalled();
|
|
929
|
+
});
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
describe("_handleDisconnect", () => {
|
|
933
|
+
beforeEach(() => {
|
|
934
|
+
provider = new EvmProvider(productInfo, {
|
|
935
|
+
sendRequest: mockSendRequest,
|
|
936
|
+
onResponse: mockOnResponse,
|
|
937
|
+
});
|
|
938
|
+
});
|
|
939
|
+
|
|
940
|
+
it("should handle recoverable disconnect", () => {
|
|
941
|
+
provider.emit("connect", { chainId: "0x1" });
|
|
942
|
+
const disconnectSpy = vi.fn();
|
|
943
|
+
const closeSpy = vi.fn();
|
|
944
|
+
|
|
945
|
+
provider.on("disconnect", disconnectSpy);
|
|
946
|
+
provider.on("close", closeSpy);
|
|
947
|
+
|
|
948
|
+
(provider as any)._handleDisconnect(true, "Test error");
|
|
949
|
+
|
|
950
|
+
expect(disconnectSpy).toHaveBeenCalled();
|
|
951
|
+
expect(closeSpy).toHaveBeenCalled();
|
|
952
|
+
expect(provider.isConnected()).toBe(false);
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
it("should handle permanent disconnect", () => {
|
|
956
|
+
provider.emit("connect", { chainId: "0x1" });
|
|
957
|
+
provider.selectedAddress = "0x123";
|
|
958
|
+
provider.chainId = "0x1";
|
|
959
|
+
provider.networkVersion = "1";
|
|
960
|
+
(provider as any)._state.accounts = ["0x123"];
|
|
961
|
+
|
|
962
|
+
const disconnectSpy = vi.fn();
|
|
963
|
+
provider.on("disconnect", disconnectSpy);
|
|
964
|
+
|
|
965
|
+
(provider as any)._handleDisconnect(false, "Permanent error");
|
|
966
|
+
|
|
967
|
+
expect(disconnectSpy).toHaveBeenCalled();
|
|
968
|
+
expect(provider.chainId).toBeNull();
|
|
969
|
+
expect(provider.networkVersion).toBeNull();
|
|
970
|
+
expect(provider.selectedAddress).toBeNull();
|
|
971
|
+
expect((provider as any)._state.accounts).toBeNull();
|
|
972
|
+
expect((provider as any)._state.isPermanentlyDisconnected).toBe(true);
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
it("should handle disconnect when not connected", () => {
|
|
976
|
+
const disconnectSpy = vi.fn();
|
|
977
|
+
provider.on("disconnect", disconnectSpy);
|
|
978
|
+
|
|
979
|
+
(provider as any)._handleDisconnect(false);
|
|
980
|
+
|
|
981
|
+
expect(disconnectSpy).toHaveBeenCalled();
|
|
982
|
+
});
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
describe("_handleChainChanged", () => {
|
|
986
|
+
beforeEach(() => {
|
|
987
|
+
provider = new EvmProvider(productInfo, {
|
|
988
|
+
sendRequest: mockSendRequest,
|
|
989
|
+
onResponse: mockOnResponse,
|
|
990
|
+
});
|
|
991
|
+
});
|
|
992
|
+
|
|
993
|
+
it("should handle valid chain change", async () => {
|
|
994
|
+
// Wait for initialization
|
|
995
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
996
|
+
|
|
997
|
+
const chainChangedSpy = vi.fn();
|
|
998
|
+
provider.on("chainChanged", chainChangedSpy);
|
|
999
|
+
|
|
1000
|
+
(provider as any)._handleChainChanged({ chainId: "0x5", isConnected: true });
|
|
1001
|
+
|
|
1002
|
+
expect(provider.chainId).toBe("0x5");
|
|
1003
|
+
expect(chainChangedSpy).toHaveBeenCalledWith("0x5");
|
|
1004
|
+
});
|
|
1005
|
+
|
|
1006
|
+
it("should not emit if chainId is the same", async () => {
|
|
1007
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1008
|
+
|
|
1009
|
+
provider.chainId = "0x1";
|
|
1010
|
+
const chainChangedSpy = vi.fn();
|
|
1011
|
+
provider.on("chainChanged", chainChangedSpy);
|
|
1012
|
+
|
|
1013
|
+
(provider as any)._handleChainChanged({ chainId: "0x1", isConnected: true });
|
|
1014
|
+
|
|
1015
|
+
expect(chainChangedSpy).not.toHaveBeenCalled();
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
it("should handle invalid chainId", () => {
|
|
1019
|
+
const chainChangedSpy = vi.fn();
|
|
1020
|
+
provider.on("chainChanged", chainChangedSpy);
|
|
1021
|
+
|
|
1022
|
+
(provider as any)._handleChainChanged({ chainId: "invalid", isConnected: true });
|
|
1023
|
+
|
|
1024
|
+
expect(chainChangedSpy).not.toHaveBeenCalled();
|
|
1025
|
+
});
|
|
1026
|
+
|
|
1027
|
+
it("should handle disconnect when isConnected is false", () => {
|
|
1028
|
+
// First set provider as connected
|
|
1029
|
+
provider.emit("connect", { chainId: "0x1" });
|
|
1030
|
+
expect(provider.isConnected()).toBe(true);
|
|
1031
|
+
|
|
1032
|
+
const disconnectSpy = vi.fn();
|
|
1033
|
+
provider.on("disconnect", disconnectSpy);
|
|
1034
|
+
|
|
1035
|
+
(provider as any)._handleChainChanged({ chainId: "0x1", isConnected: false });
|
|
1036
|
+
|
|
1037
|
+
expect(disconnectSpy).toHaveBeenCalled();
|
|
1038
|
+
});
|
|
1039
|
+
});
|
|
1040
|
+
|
|
1041
|
+
describe("_handleAccountsChanged", () => {
|
|
1042
|
+
beforeEach(() => {
|
|
1043
|
+
provider = new EvmProvider(productInfo, {
|
|
1044
|
+
sendRequest: mockSendRequest,
|
|
1045
|
+
onResponse: mockOnResponse,
|
|
1046
|
+
});
|
|
1047
|
+
});
|
|
1048
|
+
|
|
1049
|
+
it("should handle valid accounts change", async () => {
|
|
1050
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1051
|
+
|
|
1052
|
+
const accountsChangedSpy = vi.fn();
|
|
1053
|
+
provider.on("accountsChanged", accountsChangedSpy);
|
|
1054
|
+
|
|
1055
|
+
(provider as any)._handleAccountsChanged(["0x123", "0x456"]);
|
|
1056
|
+
|
|
1057
|
+
expect(provider.selectedAddress).toBe("0x123");
|
|
1058
|
+
expect(accountsChangedSpy).toHaveBeenCalledWith(["0x123", "0x456"]);
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
it("should handle empty accounts", async () => {
|
|
1062
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1063
|
+
|
|
1064
|
+
provider.selectedAddress = "0x123";
|
|
1065
|
+
const accountsChangedSpy = vi.fn();
|
|
1066
|
+
provider.on("accountsChanged", accountsChangedSpy);
|
|
1067
|
+
|
|
1068
|
+
(provider as any)._handleAccountsChanged([]);
|
|
1069
|
+
|
|
1070
|
+
expect(provider.selectedAddress).toBeNull();
|
|
1071
|
+
expect(accountsChangedSpy).toHaveBeenCalledWith([]);
|
|
1072
|
+
});
|
|
1073
|
+
|
|
1074
|
+
it("should handle invalid accounts parameter", () => {
|
|
1075
|
+
const errorSpy = vi.spyOn((provider as any)._log, "error");
|
|
1076
|
+
|
|
1077
|
+
(provider as any)._handleAccountsChanged("invalid" as any);
|
|
1078
|
+
|
|
1079
|
+
expect(errorSpy).toHaveBeenCalled();
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1082
|
+
it("should handle non-string account", () => {
|
|
1083
|
+
const errorSpy = vi.spyOn((provider as any)._log, "error");
|
|
1084
|
+
|
|
1085
|
+
(provider as any)._handleAccountsChanged([123 as any, "0x123"]);
|
|
1086
|
+
|
|
1087
|
+
expect(errorSpy).toHaveBeenCalled();
|
|
1088
|
+
});
|
|
1089
|
+
|
|
1090
|
+
it("should not emit if accounts haven't changed", async () => {
|
|
1091
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1092
|
+
|
|
1093
|
+
// Set accounts using the same array reference to ensure comparison works
|
|
1094
|
+
const accounts = ["0x123"];
|
|
1095
|
+
(provider as any)._state.accounts = accounts;
|
|
1096
|
+
const accountsChangedSpy = vi.fn();
|
|
1097
|
+
provider.on("accountsChanged", accountsChangedSpy);
|
|
1098
|
+
|
|
1099
|
+
// Use the same array reference
|
|
1100
|
+
(provider as any)._handleAccountsChanged(accounts);
|
|
1101
|
+
|
|
1102
|
+
expect(accountsChangedSpy).not.toHaveBeenCalled();
|
|
1103
|
+
});
|
|
1104
|
+
});
|
|
1105
|
+
|
|
1106
|
+
describe("_handleUnlockStateChanged", () => {
|
|
1107
|
+
beforeEach(() => {
|
|
1108
|
+
provider = new EvmProvider(productInfo, {
|
|
1109
|
+
sendRequest: mockSendRequest,
|
|
1110
|
+
onResponse: mockOnResponse,
|
|
1111
|
+
});
|
|
1112
|
+
});
|
|
1113
|
+
|
|
1114
|
+
it("should handle unlock state change", () => {
|
|
1115
|
+
const accountsChangedSpy = vi.fn();
|
|
1116
|
+
provider.on("accountsChanged", accountsChangedSpy);
|
|
1117
|
+
|
|
1118
|
+
(provider as any)._handleUnlockStateChanged({
|
|
1119
|
+
accounts: ["0x123"],
|
|
1120
|
+
isUnlocked: true,
|
|
1121
|
+
});
|
|
1122
|
+
|
|
1123
|
+
expect((provider as any)._state.isUnlocked).toBe(true);
|
|
1124
|
+
expect(accountsChangedSpy).toHaveBeenCalled();
|
|
1125
|
+
});
|
|
1126
|
+
|
|
1127
|
+
it("should handle invalid isUnlocked parameter", () => {
|
|
1128
|
+
const errorSpy = vi.spyOn((provider as any)._log, "error");
|
|
1129
|
+
|
|
1130
|
+
(provider as any)._handleUnlockStateChanged({
|
|
1131
|
+
isUnlocked: "invalid" as any,
|
|
1132
|
+
});
|
|
1133
|
+
|
|
1134
|
+
expect(errorSpy).toHaveBeenCalled();
|
|
1135
|
+
});
|
|
1136
|
+
|
|
1137
|
+
it("should not change if isUnlocked is the same", () => {
|
|
1138
|
+
(provider as any)._state.isUnlocked = false;
|
|
1139
|
+
const accountsChangedSpy = vi.fn();
|
|
1140
|
+
provider.on("accountsChanged", accountsChangedSpy);
|
|
1141
|
+
|
|
1142
|
+
(provider as any)._handleUnlockStateChanged({
|
|
1143
|
+
isUnlocked: false,
|
|
1144
|
+
});
|
|
1145
|
+
|
|
1146
|
+
// Should not call accountsChanged if unlock state didn't change
|
|
1147
|
+
expect((provider as any)._state.isUnlocked).toBe(false);
|
|
1148
|
+
});
|
|
1149
|
+
});
|
|
1150
|
+
|
|
1151
|
+
describe("send", () => {
|
|
1152
|
+
beforeEach(() => {
|
|
1153
|
+
provider = new EvmProvider(productInfo, {
|
|
1154
|
+
sendRequest: mockSendRequest,
|
|
1155
|
+
onResponse: mockOnResponse,
|
|
1156
|
+
logger: {
|
|
1157
|
+
log: vi.fn(),
|
|
1158
|
+
warn: vi.fn(),
|
|
1159
|
+
error: vi.fn(),
|
|
1160
|
+
debug: vi.fn(),
|
|
1161
|
+
info: vi.fn(),
|
|
1162
|
+
trace: vi.fn(),
|
|
1163
|
+
},
|
|
1164
|
+
});
|
|
1165
|
+
});
|
|
1166
|
+
|
|
1167
|
+
it("should warn on first send call", () => {
|
|
1168
|
+
const warnSpy = vi.spyOn((provider as any)._log, "warn");
|
|
1169
|
+
|
|
1170
|
+
const mockHandle = vi.fn();
|
|
1171
|
+
(provider as any)._rpcEngine = {
|
|
1172
|
+
handle: mockHandle,
|
|
1173
|
+
};
|
|
1174
|
+
|
|
1175
|
+
provider.send("eth_getBalance", ["0x123"]);
|
|
1176
|
+
|
|
1177
|
+
expect(warnSpy).toHaveBeenCalled();
|
|
1178
|
+
});
|
|
1179
|
+
|
|
1180
|
+
it("should handle string method with params", async () => {
|
|
1181
|
+
const mockHandle = vi.fn((payload, callback) => {
|
|
1182
|
+
callback(null, { result: "0x1000" });
|
|
1183
|
+
});
|
|
1184
|
+
(provider as any)._rpcEngine = {
|
|
1185
|
+
handle: mockHandle,
|
|
1186
|
+
};
|
|
1187
|
+
|
|
1188
|
+
const result = await provider.send("eth_getBalance", ["0x123"]);
|
|
1189
|
+
|
|
1190
|
+
expect(mockHandle).toHaveBeenCalled();
|
|
1191
|
+
expect(result).toBeDefined();
|
|
1192
|
+
});
|
|
1193
|
+
|
|
1194
|
+
it("should handle payload object with callback", () => {
|
|
1195
|
+
const callback = vi.fn();
|
|
1196
|
+
const payload = {
|
|
1197
|
+
jsonrpc: "2.0" as const,
|
|
1198
|
+
id: 1,
|
|
1199
|
+
method: "eth_getBalance",
|
|
1200
|
+
params: ["0x123"],
|
|
1201
|
+
};
|
|
1202
|
+
|
|
1203
|
+
const mockHandle = vi.fn();
|
|
1204
|
+
(provider as any)._rpcEngine = {
|
|
1205
|
+
handle: mockHandle,
|
|
1206
|
+
};
|
|
1207
|
+
|
|
1208
|
+
provider.send(payload, callback);
|
|
1209
|
+
|
|
1210
|
+
expect(mockHandle).toHaveBeenCalled();
|
|
1211
|
+
});
|
|
1212
|
+
|
|
1213
|
+
it("should handle sync methods", () => {
|
|
1214
|
+
provider.selectedAddress = "0x123";
|
|
1215
|
+
|
|
1216
|
+
const result = provider.send({
|
|
1217
|
+
jsonrpc: "2.0" as const,
|
|
1218
|
+
id: 1,
|
|
1219
|
+
method: "eth_accounts",
|
|
1220
|
+
} as any) as any;
|
|
1221
|
+
|
|
1222
|
+
expect(result.result).toEqual(["0x123"]);
|
|
1223
|
+
});
|
|
1224
|
+
|
|
1225
|
+
it("should handle eth_coinbase sync", () => {
|
|
1226
|
+
provider.selectedAddress = "0x123";
|
|
1227
|
+
|
|
1228
|
+
const result = provider.send({
|
|
1229
|
+
jsonrpc: "2.0" as const,
|
|
1230
|
+
id: 1,
|
|
1231
|
+
method: "eth_coinbase",
|
|
1232
|
+
} as any) as any;
|
|
1233
|
+
|
|
1234
|
+
expect(result.result).toBe("0x123");
|
|
1235
|
+
});
|
|
1236
|
+
|
|
1237
|
+
it("should handle eth_uninstallFilter sync", () => {
|
|
1238
|
+
const mockHandle = vi.fn();
|
|
1239
|
+
(provider as any)._rpcEngine = {
|
|
1240
|
+
handle: mockHandle,
|
|
1241
|
+
};
|
|
1242
|
+
|
|
1243
|
+
const result = provider.send({
|
|
1244
|
+
jsonrpc: "2.0" as const,
|
|
1245
|
+
id: 1,
|
|
1246
|
+
method: "eth_uninstallFilter",
|
|
1247
|
+
params: ["0x123"],
|
|
1248
|
+
} as any) as any;
|
|
1249
|
+
|
|
1250
|
+
expect(result.result).toBe(true);
|
|
1251
|
+
expect(mockHandle).toHaveBeenCalled();
|
|
1252
|
+
});
|
|
1253
|
+
|
|
1254
|
+
it("should handle net_version sync", () => {
|
|
1255
|
+
provider.networkVersion = "1";
|
|
1256
|
+
|
|
1257
|
+
const result = provider.send({
|
|
1258
|
+
jsonrpc: "2.0" as const,
|
|
1259
|
+
id: 1,
|
|
1260
|
+
method: "net_version",
|
|
1261
|
+
} as any) as any;
|
|
1262
|
+
|
|
1263
|
+
expect(result.result).toBe("1");
|
|
1264
|
+
});
|
|
1265
|
+
|
|
1266
|
+
it("should throw error for unsupported sync method", () => {
|
|
1267
|
+
expect(() => {
|
|
1268
|
+
provider.send({
|
|
1269
|
+
jsonrpc: "2.0",
|
|
1270
|
+
id: 1,
|
|
1271
|
+
method: "unsupported_method",
|
|
1272
|
+
} as any);
|
|
1273
|
+
}).toThrow();
|
|
1274
|
+
});
|
|
1275
|
+
});
|
|
1276
|
+
|
|
1277
|
+
describe("enable", () => {
|
|
1278
|
+
beforeEach(() => {
|
|
1279
|
+
provider = new EvmProvider(productInfo, {
|
|
1280
|
+
sendRequest: mockSendRequest,
|
|
1281
|
+
onResponse: mockOnResponse,
|
|
1282
|
+
logger: {
|
|
1283
|
+
log: vi.fn(),
|
|
1284
|
+
warn: vi.fn(),
|
|
1285
|
+
error: vi.fn(),
|
|
1286
|
+
debug: vi.fn(),
|
|
1287
|
+
info: vi.fn(),
|
|
1288
|
+
trace: vi.fn(),
|
|
1289
|
+
},
|
|
1290
|
+
});
|
|
1291
|
+
});
|
|
1292
|
+
|
|
1293
|
+
it("should call connect", async () => {
|
|
1294
|
+
const connectSpy = vi.spyOn(provider, "connect").mockResolvedValue(["0x123"]);
|
|
1295
|
+
|
|
1296
|
+
await provider.enable();
|
|
1297
|
+
|
|
1298
|
+
expect(connectSpy).toHaveBeenCalled();
|
|
1299
|
+
});
|
|
1300
|
+
});
|
|
1301
|
+
|
|
1302
|
+
describe("setConnectedStatus", () => {
|
|
1303
|
+
beforeEach(() => {
|
|
1304
|
+
provider = new EvmProvider(productInfo, {
|
|
1305
|
+
sendRequest: mockSendRequest,
|
|
1306
|
+
onResponse: mockOnResponse,
|
|
1307
|
+
});
|
|
1308
|
+
});
|
|
1309
|
+
|
|
1310
|
+
it("should set connected status and accounts", () => {
|
|
1311
|
+
provider.setConnectedStatus({
|
|
1312
|
+
connected: true,
|
|
1313
|
+
address: ["0x123", "0x456"],
|
|
1314
|
+
});
|
|
1315
|
+
|
|
1316
|
+
expect((provider as any)._state.isConnected).toBe(true);
|
|
1317
|
+
expect((provider as any)._state.accounts).toEqual(["0x123", "0x456"]);
|
|
1318
|
+
});
|
|
1319
|
+
});
|
|
1320
|
+
|
|
1321
|
+
describe("initializeEvmProvider", () => {
|
|
1322
|
+
it("should create provider with Proxy", () => {
|
|
1323
|
+
const provider = initializeEvmProvider(productInfo, {
|
|
1324
|
+
sendRequest: mockSendRequest,
|
|
1325
|
+
onResponse: mockOnResponse,
|
|
1326
|
+
shouldSetOnWindow: false,
|
|
1327
|
+
});
|
|
1328
|
+
|
|
1329
|
+
expect(provider).toBeDefined();
|
|
1330
|
+
// Test Proxy deleteProperty
|
|
1331
|
+
delete (provider as any).testProperty;
|
|
1332
|
+
expect(true).toBe(true); // Proxy should allow deletion
|
|
1333
|
+
});
|
|
1334
|
+
|
|
1335
|
+
it("should set provider on window when shouldSetOnWindow is true", () => {
|
|
1336
|
+
initializeEvmProvider(productInfo, {
|
|
1337
|
+
sendRequest: mockSendRequest,
|
|
1338
|
+
onResponse: mockOnResponse,
|
|
1339
|
+
shouldSetOnWindow: true,
|
|
1340
|
+
});
|
|
1341
|
+
|
|
1342
|
+
expect((window as any).ethereum).toBeDefined();
|
|
1343
|
+
});
|
|
1344
|
+
});
|
|
1345
|
+
|
|
1346
|
+
describe("setGlobalProvider", () => {
|
|
1347
|
+
it("should set provider on window and dispatch event", () => {
|
|
1348
|
+
const provider = new EvmProvider(productInfo, {
|
|
1349
|
+
sendRequest: mockSendRequest,
|
|
1350
|
+
onResponse: mockOnResponse,
|
|
1351
|
+
});
|
|
1352
|
+
|
|
1353
|
+
const eventSpy = vi.fn();
|
|
1354
|
+
window.addEventListener("ethereum#initialized", eventSpy);
|
|
1355
|
+
|
|
1356
|
+
setGlobalProvider(provider);
|
|
1357
|
+
|
|
1358
|
+
expect((window as any).ethereum).toBe(provider);
|
|
1359
|
+
expect(eventSpy).toHaveBeenCalled();
|
|
1360
|
+
});
|
|
1361
|
+
});
|
|
1362
|
+
});
|