@meshconnect/uwc-core 0.2.13 → 0.2.15

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.
@@ -0,0 +1,428 @@
1
+ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'
2
+ import { UniversalWalletConnector } from './universal-wallet-connector'
3
+ import type {
4
+ Network,
5
+ WalletMetadata,
6
+ WalletConnectConfig,
7
+ TransactionRequest
8
+ } from '@meshconnect/uwc-types'
9
+
10
+ const mockInjectedConnector = {
11
+ getAvailableWallets: vi.fn().mockResolvedValue([])
12
+ }
13
+
14
+ const mockWalletConnectConnector = {
15
+ getAvailableWallets: vi.fn().mockResolvedValue([])
16
+ }
17
+
18
+ const mockSessionManager = {
19
+ getSession: vi.fn().mockReturnValue({
20
+ connectionMode: null,
21
+ activeWallet: null,
22
+ activeNetwork: null,
23
+ activeAddress: null,
24
+ availableNetworks: [],
25
+ availableAddresses: [],
26
+ activeWalletCapabilities: null
27
+ })
28
+ }
29
+
30
+ const mockEventManager = {
31
+ subscribe: vi.fn().mockReturnValue(() => {}),
32
+ notify: vi.fn()
33
+ }
34
+
35
+ const mockConnectionService = {
36
+ getWallets: vi.fn().mockReturnValue([]),
37
+ getNetworks: vi.fn().mockReturnValue([]),
38
+ connect: vi.fn(),
39
+ disconnect: vi.fn(),
40
+ getConnectionURI: vi.fn(),
41
+ updateWallets: vi.fn()
42
+ }
43
+
44
+ const mockNetworkSwitchService = {
45
+ switchNetwork: vi.fn(),
46
+ getLoadingState: vi.fn().mockReturnValue(false)
47
+ }
48
+
49
+ const mockSignatureService = {
50
+ signMessage: vi.fn()
51
+ }
52
+
53
+ const mockTransactionService = {
54
+ sendTransaction: vi.fn()
55
+ }
56
+
57
+ // Mock the dependencies
58
+ vi.mock('@meshconnect/uwc-injected-connector', () => ({
59
+ InjectedConnector: vi.fn(() => mockInjectedConnector)
60
+ }))
61
+
62
+ vi.mock('@meshconnect/uwc-wallet-connect-connector', () => ({
63
+ WalletConnectConnector: vi.fn(() => mockWalletConnectConnector)
64
+ }))
65
+
66
+ vi.mock('@meshconnect/uwc-bridge-child', () => ({
67
+ BridgeChild: vi.fn()
68
+ }))
69
+
70
+ vi.mock('./managers/session-manager', () => ({
71
+ SessionManager: vi.fn(() => mockSessionManager)
72
+ }))
73
+
74
+ vi.mock('./managers/event-manager', () => ({
75
+ EventManager: vi.fn(() => mockEventManager)
76
+ }))
77
+
78
+ vi.mock('./services/connection-service', () => ({
79
+ ConnectionService: vi.fn(() => mockConnectionService)
80
+ }))
81
+
82
+ vi.mock('./services/network-switch-service', () => ({
83
+ NetworkSwitchService: vi.fn(() => mockNetworkSwitchService)
84
+ }))
85
+
86
+ vi.mock('./services/signature-service', () => ({
87
+ SignatureService: vi.fn(() => mockSignatureService)
88
+ }))
89
+
90
+ vi.mock('./services/transaction-service', () => ({
91
+ TransactionService: vi.fn(() => mockTransactionService)
92
+ }))
93
+
94
+ vi.mock('./utils/network-rpc-utils', () => ({
95
+ createNetworkRpcMap: vi.fn().mockReturnValue(new Map())
96
+ }))
97
+
98
+ describe('UniversalWalletConnector', () => {
99
+ let mockNetworks: Network[]
100
+ let mockWallets: WalletMetadata[]
101
+ let mockWalletConnectConfig: WalletConnectConfig
102
+
103
+ beforeEach(() => {
104
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
105
+ ;(UniversalWalletConnector as any).instanceCount = 0
106
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
107
+ ;(UniversalWalletConnector as any).hasWarnedAboutMultipleInstances = false
108
+
109
+ mockNetworks = [
110
+ {
111
+ id: 'eip155:1',
112
+ internalId: 1,
113
+ name: 'Ethereum Mainnet',
114
+ namespace: 'eip155',
115
+ logoUrl: 'https://example.com/eth.png',
116
+ rpcUrls: {
117
+ default: {
118
+ http: ['https://mainnet.infura.io/v3/test']
119
+ }
120
+ },
121
+ blockExplorers: {
122
+ default: {
123
+ name: 'Etherscan',
124
+ url: 'https://etherscan.io'
125
+ }
126
+ },
127
+ networkType: 'evm',
128
+ nativeCurrency: {
129
+ name: 'Ether',
130
+ symbol: 'ETH',
131
+ decimals: 18
132
+ }
133
+ }
134
+ ]
135
+
136
+ mockWallets = [
137
+ {
138
+ id: 'metamask',
139
+ name: 'MetaMask',
140
+ metadata: {},
141
+ extensionInjectedProvider: {
142
+ supportedNetworkIds: ['eip155:1'] as const,
143
+ namespaceMetaData: {
144
+ eip155: {
145
+ eip155Name: 'MetaMask',
146
+ injectedId: 'io.metamask',
147
+ supportsAddingNetworks: true,
148
+ requiresUserApprovalOnNetworkSwitch: false
149
+ }
150
+ },
151
+ requiresUserApprovalOnNamespaceSwitch: false
152
+ }
153
+ }
154
+ ]
155
+
156
+ mockWalletConnectConfig = {
157
+ projectId: 'test-project-id',
158
+ metadata: {
159
+ name: 'Test App',
160
+ description: 'Test App Description',
161
+ url: 'https://test.com',
162
+ icons: ['https://test.com/icon.png']
163
+ }
164
+ }
165
+
166
+ // Clear all mocks
167
+ vi.clearAllMocks()
168
+
169
+ // Reset mock return values for methods that are called in the constructor
170
+ mockInjectedConnector.getAvailableWallets.mockResolvedValue([])
171
+ mockWalletConnectConnector.getAvailableWallets.mockResolvedValue([])
172
+ mockConnectionService.getWallets.mockReturnValue(mockWallets)
173
+ mockConnectionService.getNetworks.mockReturnValue(mockNetworks)
174
+ mockSessionManager.getSession.mockReturnValue({
175
+ connectionMode: null,
176
+ activeWallet: null,
177
+ activeNetwork: null,
178
+ activeAddress: null,
179
+ availableNetworks: [],
180
+ availableAddresses: [],
181
+ activeWalletCapabilities: null
182
+ })
183
+ })
184
+
185
+ afterEach(() => {
186
+ vi.restoreAllMocks()
187
+ })
188
+
189
+ describe('constructor', () => {
190
+ it('should initialize with required parameters', () => {
191
+ const connector = new UniversalWalletConnector(mockNetworks, mockWallets)
192
+
193
+ expect(connector).toBeInstanceOf(UniversalWalletConnector)
194
+ })
195
+
196
+ it('should initialize with WalletConnect config when provided', () => {
197
+ const connector = new UniversalWalletConnector(
198
+ mockNetworks,
199
+ mockWallets,
200
+ false,
201
+ mockWalletConnectConfig
202
+ )
203
+
204
+ expect(connector).toBeInstanceOf(UniversalWalletConnector)
205
+ })
206
+
207
+ it('should throw error when networks are not provided', () => {
208
+ expect(() => {
209
+ // @ts-expect-error Testing error case with null networks
210
+ new UniversalWalletConnector(null, mockWallets)
211
+ }).toThrow(
212
+ 'Universal Wallet Connector must be initialized with at least one network'
213
+ )
214
+ })
215
+
216
+ it('should throw error when wallets are not provided', () => {
217
+ expect(() => {
218
+ // @ts-expect-error Testing error case with null wallets
219
+ new UniversalWalletConnector(mockNetworks, null)
220
+ }).toThrow(
221
+ 'Universal Wallet Connector must be initialized with at least one wallet'
222
+ )
223
+ })
224
+
225
+ it('should warn about multiple instances', () => {
226
+ const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
227
+
228
+ new UniversalWalletConnector(mockNetworks, mockWallets)
229
+ new UniversalWalletConnector(mockNetworks, mockWallets)
230
+
231
+ expect(consoleSpy).toHaveBeenCalledWith(
232
+ expect.stringContaining(
233
+ 'WARNING: Multiple instances of UniversalWalletConnector detected'
234
+ )
235
+ )
236
+
237
+ consoleSpy.mockRestore()
238
+ })
239
+ })
240
+
241
+ describe('isReady', () => {
242
+ it('should return true after detection is complete', async () => {
243
+ const connector = new UniversalWalletConnector(mockNetworks, mockWallets)
244
+
245
+ // Wait for async initialization to complete
246
+ await new Promise(resolve => setTimeout(resolve, 0))
247
+
248
+ expect(connector.isReady()).toBe(true)
249
+ })
250
+ })
251
+
252
+ describe('isConnectionModeAvailable', () => {
253
+ it('should return false for unknown wallet', () => {
254
+ const connector = new UniversalWalletConnector(mockNetworks, mockWallets)
255
+
256
+ expect(
257
+ connector.isConnectionModeAvailable('injected', 'unknown-wallet')
258
+ ).toBe(false)
259
+ })
260
+
261
+ it('should return false for WalletConnect when connector not available', () => {
262
+ const connector = new UniversalWalletConnector(mockNetworks, mockWallets)
263
+
264
+ expect(
265
+ connector.isConnectionModeAvailable('walletConnect', 'metamask')
266
+ ).toBe(false)
267
+ })
268
+
269
+ it('should return true for WalletConnect when connector and provider available', () => {
270
+ const walletWithWalletConnect = {
271
+ ...mockWallets[0],
272
+ walletConnectProvider: {
273
+ supportedNetworkIds: ['eip155:1'] as const,
274
+ deeplinks: {
275
+ universal: 'https://metamask.app',
276
+ native: 'metamask://'
277
+ }
278
+ }
279
+ } as WalletMetadata
280
+
281
+ const connector = new UniversalWalletConnector(
282
+ mockNetworks,
283
+ [walletWithWalletConnect],
284
+ false,
285
+ mockWalletConnectConfig
286
+ )
287
+
288
+ expect(
289
+ connector.isConnectionModeAvailable('walletConnect', 'metamask')
290
+ ).toBe(true)
291
+ })
292
+
293
+ it('should return false for injected when no provider', () => {
294
+ const walletWithoutProvider = {
295
+ ...mockWallets[0],
296
+ extensionInjectedProvider: undefined
297
+ }
298
+
299
+ const connector = new UniversalWalletConnector(mockNetworks, [
300
+ walletWithoutProvider
301
+ ])
302
+
303
+ expect(connector.isConnectionModeAvailable('injected', 'metamask')).toBe(
304
+ false
305
+ )
306
+ })
307
+
308
+ it('should return false for injected when no installed namespace', () => {
309
+ const walletWithoutInstalledNamespace = {
310
+ ...mockWallets[0],
311
+ extensionInjectedProvider: {
312
+ supportedNetworkIds: ['eip155:1'] as const,
313
+ namespaceMetaData: {
314
+ eip155: {
315
+ eip155Name: 'MetaMask',
316
+ injectedId: 'io.metamask',
317
+ supportsAddingNetworks: true,
318
+ requiresUserApprovalOnNetworkSwitch: false,
319
+ installed: false
320
+ }
321
+ },
322
+ requiresUserApprovalOnNamespaceSwitch: false
323
+ }
324
+ } as WalletMetadata
325
+
326
+ const connector = new UniversalWalletConnector(mockNetworks, [
327
+ walletWithoutInstalledNamespace
328
+ ])
329
+
330
+ expect(connector.isConnectionModeAvailable('injected', 'metamask')).toBe(
331
+ false
332
+ )
333
+ })
334
+
335
+ it('should return true for injected when namespace is installed', () => {
336
+ const walletWithInstalledNamespace = {
337
+ ...mockWallets[0],
338
+ extensionInjectedProvider: {
339
+ supportedNetworkIds: ['eip155:1'] as const,
340
+ namespaceMetaData: {
341
+ eip155: {
342
+ eip155Name: 'MetaMask',
343
+ injectedId: 'io.metamask',
344
+ supportsAddingNetworks: true,
345
+ requiresUserApprovalOnNetworkSwitch: false,
346
+ installed: true
347
+ }
348
+ },
349
+ requiresUserApprovalOnNamespaceSwitch: false
350
+ }
351
+ } as WalletMetadata
352
+
353
+ const connector = new UniversalWalletConnector(mockNetworks, [
354
+ walletWithInstalledNamespace
355
+ ])
356
+
357
+ expect(connector.isConnectionModeAvailable('injected', 'metamask')).toBe(
358
+ true
359
+ )
360
+ })
361
+
362
+ it('should return false for unknown connection mode', () => {
363
+ const connector = new UniversalWalletConnector(mockNetworks, mockWallets)
364
+
365
+ expect(
366
+ // @ts-expect-error Testing unknown connection mode
367
+ connector.isConnectionModeAvailable('unknown', 'metamask')
368
+ ).toBe(false)
369
+ })
370
+ })
371
+
372
+ describe('basic functionality', () => {
373
+ it('should have all required methods', () => {
374
+ const connector = new UniversalWalletConnector(mockNetworks, mockWallets)
375
+
376
+ expect(typeof connector.getSession).toBe('function')
377
+ expect(typeof connector.isReady).toBe('function')
378
+ expect(typeof connector.subscribe).toBe('function')
379
+ expect(typeof connector.getState).toBe('function')
380
+ expect(typeof connector.getWallets).toBe('function')
381
+ expect(typeof connector.getNetworks).toBe('function')
382
+ expect(typeof connector.connect).toBe('function')
383
+ expect(typeof connector.getConnectionURI).toBe('function')
384
+ expect(typeof connector.disconnect).toBe('function')
385
+ expect(typeof connector.switchNetwork).toBe('function')
386
+ expect(typeof connector.getNetworkSwitchLoadingState).toBe('function')
387
+ expect(typeof connector.signMessage).toBe('function')
388
+ expect(typeof connector.sendTransaction).toBe('function')
389
+ expect(typeof connector.isConnectionModeAvailable).toBe('function')
390
+ expect(typeof connector.getActiveWalletCapabilities).toBe('function')
391
+ })
392
+ })
393
+
394
+ describe('error handling', () => {
395
+ it('should handle signMessage errors gracefully', async () => {
396
+ const connector = new UniversalWalletConnector(mockNetworks, mockWallets)
397
+
398
+ // This should not throw an error, but should handle the case where session manager is not properly mocked
399
+ await expect(connector.signMessage('test')).rejects.toThrow()
400
+ })
401
+
402
+ it('should handle sendTransaction errors gracefully', async () => {
403
+ const connector = new UniversalWalletConnector(mockNetworks, mockWallets)
404
+
405
+ // This should not throw an error, but should handle the case where session manager is not properly mocked
406
+ await expect(
407
+ connector.sendTransaction({
408
+ to: '0x123',
409
+ value: '0x0',
410
+ data: '0x',
411
+ gas: '0x5208'
412
+ } as unknown as TransactionRequest)
413
+ ).rejects.toThrow()
414
+ })
415
+ })
416
+
417
+ describe('wallet detection', () => {
418
+ it('should handle wallet detection gracefully', async () => {
419
+ const connector = new UniversalWalletConnector(mockNetworks, mockWallets)
420
+
421
+ // Wait for async initialization to complete
422
+ await new Promise(resolve => setTimeout(resolve, 0))
423
+
424
+ // Should complete without throwing
425
+ expect(connector.isReady()).toBe(true)
426
+ })
427
+ })
428
+ })