import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' import type { WalletMetadata } from '@meshconnect/uwc-types' // Mock window object const mockWindow = { UWCBridgeChildInitialized: false, tronWallets: undefined as any, tronLink: undefined as any, tokenpocket: undefined as any } // Mock global window Object.defineProperty(globalThis, 'window', { value: mockWindow, writable: true }) describe('Tron Discovery', () => { beforeEach(() => { vi.clearAllMocks() vi.useFakeTimers() // Reset window properties mockWindow.UWCBridgeChildInitialized = false mockWindow.tronWallets = undefined mockWindow.tronLink = undefined mockWindow.tokenpocket = undefined // Clear module cache to ensure fresh imports vi.resetModules() }) afterEach(() => { vi.useRealTimers() vi.restoreAllMocks() }) const createMockTronProvider = (address?: string) => ({ ready: true, request: vi.fn(), tronWeb: { defaultAddress: { base58: address || 'TTestAddress123', hex: '0x1234' }, trx: { sign: vi.fn(), signMessageV2: vi.fn(), sendRawTransaction: vi.fn() }, transactionBuilder: { sendTrx: vi.fn(), triggerSmartContract: vi.fn() }, toHex: vi.fn() } }) const createExpectedWallets = (injectedIds: string[]): WalletMetadata[] => injectedIds.map(id => ({ id: id.split('.')[0], name: id.split('.')[0], metadata: {}, extensionInjectedProvider: { supportedNetworkIds: ['tron:0x2b6653dc'] as const, namespaceMetaData: { tron: { injectedId: id } }, requiresUserApprovalOnNamespaceSwitch: false } })) describe('getTronWallets', () => { it('should return empty array when no expected wallets provided', async () => { const { getTronWallets } = await import('./tron-discovery') const result = await getTronWallets() expect(result).toEqual([]) }) it('should return empty array when expected wallets have no tron injectedIds', async () => { const { getTronWallets } = await import('./tron-discovery') const wallets: WalletMetadata[] = [ { id: 'metamask', name: 'MetaMask', metadata: {}, extensionInjectedProvider: { supportedNetworkIds: ['eip155:1'] as const, namespaceMetaData: { eip155: { eip155Name: 'MetaMask', injectedId: 'isMetaMask', supportsAddingNetworks: true, requiresUserApprovalOnNetworkSwitch: false } }, requiresUserApprovalOnNamespaceSwitch: false } } ] const result = await getTronWallets(wallets) expect(result).toEqual([]) }) it('should discover native tron provider at window.tronLink', async () => { const mockProvider = createMockTronProvider() mockWindow.tronLink = mockProvider const { getTronWallets } = await import('./tron-discovery') const wallets = createExpectedWallets(['tronLink']) const result = await getTronWallets(wallets) expect(result).toHaveLength(1) expect(result[0].uuid).toBe('tron-tronlink') expect(result[0].name).toBe('tronLink') expect(result[0].injectedId).toBe('tronLink') expect(result[0].provider).toBe(mockProvider) }) it('should discover nested tron provider (e.g. tokenpocket.tron)', async () => { const mockProvider = createMockTronProvider() mockWindow.tokenpocket = { tron: mockProvider } const { getTronWallets } = await import('./tron-discovery') const wallets = createExpectedWallets(['tokenpocket.tron']) const result = await getTronWallets(wallets) expect(result).toHaveLength(1) expect(result[0].uuid).toBe('tron-tokenpocket-tron') expect(result[0].name).toBe('tokenpocket') expect(result[0].injectedId).toBe('tokenpocket.tron') }) it('should discover multiple tron providers', async () => { const mockProvider1 = createMockTronProvider() const mockProvider2 = createMockTronProvider() mockWindow.tronLink = mockProvider1 mockWindow.tokenpocket = { tron: mockProvider2 } const { getTronWallets } = await import('./tron-discovery') const wallets = createExpectedWallets(['tronLink', 'tokenpocket.tron']) const result = await getTronWallets(wallets) expect(result).toHaveLength(2) }) it('should return empty array when provider is not found at injected path', async () => { // tronLink not set on window const { getTronWallets } = await import('./tron-discovery') const wallets = createExpectedWallets(['tronLink']) const result = await getTronWallets(wallets) expect(result).toEqual([]) }) it('should return empty array when provider has no valid tronWeb', async () => { // Set a provider without tronWeb.trx mockWindow.tronLink = { ready: true, tronWeb: { notTrx: true } } const { getTronWallets } = await import('./tron-discovery') const wallets = createExpectedWallets(['tronLink']) const result = await getTronWallets(wallets) expect(result).toEqual([]) }) it('should return found wallets and skip missing provider paths', async () => { // Only tronLink exists, tokenpocket is not injected const mockProvider = createMockTronProvider() mockWindow.tronLink = mockProvider const { getTronWallets } = await import('./tron-discovery') const wallets = createExpectedWallets(['tronLink', 'tokenpocket.tron']) const result = await getTronWallets(wallets) // Returns what was found, skips missing paths expect(result).toHaveLength(1) expect(result[0].uuid).toBe('tron-tronlink') }) it('should deduplicate wallets for same injectedId', async () => { const mockProvider = createMockTronProvider() mockWindow.tronLink = mockProvider const { getTronWallets } = await import('./tron-discovery') // Two expected wallets pointing to same injectedId const wallets: WalletMetadata[] = [ ...createExpectedWallets(['tronLink']), ...createExpectedWallets(['tronLink']) ] const result = await getTronWallets(wallets) // discoverTronProviders deduplicates by injectedId expect(result).toHaveLength(1) expect(result[0].uuid).toBe('tron-tronlink') }) }) describe('Bridge wallet polling', () => { it('should return bridge wallets when UWCBridgeChildInitialized is true', async () => { mockWindow.UWCBridgeChildInitialized = true const mockBridgeWallets = [ { uuid: 'tron-tronlink', name: 'tronLink', injectedId: 'tronLink', provider: createMockTronProvider() } ] mockWindow.tronWallets = mockBridgeWallets const { getTronWallets } = await import('./tron-discovery') const result = await getTronWallets() expect(result).toEqual(mockBridgeWallets) }) it('should return empty array when bridge polling times out', async () => { mockWindow.UWCBridgeChildInitialized = true mockWindow.tronWallets = undefined const { getTronWallets } = await import('./tron-discovery') const resultPromise = getTronWallets() // Fast-forward past the bridge poll budget (BRIDGE_WALLET_POLL_MS = 5300ms) await vi.advanceTimersByTimeAsync(5400) const result = await resultPromise expect(result).toEqual([]) }) it('should handle invalid bridge wallets (non-array)', async () => { mockWindow.UWCBridgeChildInitialized = true mockWindow.tronWallets = 'invalid' as any const { getTronWallets } = await import('./tron-discovery') const resultPromise = getTronWallets() await vi.advanceTimersByTimeAsync(5400) const result = await resultPromise expect(result).toEqual([]) }) it('should prefer bridge wallets over native when in bridge mode', async () => { mockWindow.UWCBridgeChildInitialized = true const bridgeWallet = { uuid: 'tron-tronlink', name: 'tronLink', injectedId: 'tronLink', provider: createMockTronProvider('BridgeAddress') } mockWindow.tronWallets = [bridgeWallet] mockWindow.tronLink = createMockTronProvider('NativeAddress') const { getTronWallets } = await import('./tron-discovery') const wallets = createExpectedWallets(['tronLink']) const result = await getTronWallets(wallets) expect(result).toHaveLength(1) expect(result[0]).toEqual(bridgeWallet) }) it('should not fall back to native discovery when in bridge mode', async () => { mockWindow.UWCBridgeChildInitialized = true mockWindow.tronWallets = [] mockWindow.tronLink = createMockTronProvider() const { getTronWallets } = await import('./tron-discovery') const wallets = createExpectedWallets(['tronLink']) // Bridge returns empty array, should NOT fall through to native // because native discovery can't work in an iframe const result = await getTronWallets(wallets) expect(result).toEqual([]) }) it('should return empty array when bridge is initialized but no wallets found', async () => { mockWindow.UWCBridgeChildInitialized = true mockWindow.tronWallets = undefined const { getTronWallets } = await import('./tron-discovery') const resultPromise = getTronWallets(createExpectedWallets(['tronLink'])) // Fast-forward past the bridge poll budget (BRIDGE_WALLET_POLL_MS = 5300ms) await vi.advanceTimersByTimeAsync(5400) const result = await resultPromise // In bridge mode, if polling finds nothing, returns empty expect(result).toEqual([]) }) it('should use native discovery when not in bridge mode', async () => { mockWindow.UWCBridgeChildInitialized = false mockWindow.tronLink = createMockTronProvider() const { getTronWallets } = await import('./tron-discovery') const wallets = createExpectedWallets(['tronLink']) const result = await getTronWallets(wallets) expect(result).toHaveLength(1) expect(result[0].uuid).toBe('tron-tronlink') expect(result[0].injectedId).toBe('tronLink') }) it('picks up a wallet the child writes inside the grace window (t≈5050ms)', async () => { // The child can marshal on its final tick, writing window.tronWallets just // after its own 5000ms budget. The consumer's BRIDGE_WALLET_POLL_MS (5300ms) // grace must still observe it — an equal 5000ms deadline would miss it. mockWindow.UWCBridgeChildInitialized = true const bridgeWallet = { uuid: 'tron-tronlink', name: 'tronLink', injectedId: 'tronLink', provider: createMockTronProvider() } setTimeout(() => { mockWindow.tronWallets = [bridgeWallet] }, 5050) const { getTronWallets } = await import('./tron-discovery') const resultPromise = getTronWallets() await vi.advanceTimersByTimeAsync(5200) const result = await resultPromise expect(result).toEqual([bridgeWallet]) }) }) describe('error handling', () => { it('should return empty array if discovery throws', async () => { // Force an error by creating a problematic expectedWallets const { getTronWallets } = await import('./tron-discovery') // Even if something goes wrong internally, should return [] const result = await getTronWallets([]) expect(result).toEqual([]) }) }) })