From 4e7a48099bbac92d4f645e79e9b3f85ee6ead09d Mon Sep 17 00:00:00 2001 From: Nuo Xu Date: Mon, 8 Apr 2024 13:58:38 -0400 Subject: [PATCH 1/4] add a debug option to enable bitcoin testnet --- .../Brave Wallet/BraveWalletDebugMenu.swift | 24 + .../Settings/SettingsViewController.swift | 18 + .../Crypto/Accounts/Add/AddAccountView.swift | 11 +- .../Crypto/Stores/AccountsStore.swift | 4 +- .../Crypto/Stores/DepositTokenStore.swift | 4 +- .../Crypto/Stores/KeyringStore.swift | 23 + .../BraveWallet/Crypto/Stores/NFTStore.swift | 2 +- .../Crypto/Stores/NetworkStore.swift | 6 +- .../Crypto/Stores/PortfolioStore.swift | 2 +- .../Extensions/KeyringServiceExtensions.swift | 11 + .../Extensions/RpcServiceExtensions.swift | 5 + .../BraveWallet/WalletPreferences.swift | 6 + .../BraveWallet/WalletUserAssetManager.swift | 2 +- .../BraveWalletTests/AccountsStoreTests.swift | 248 +++++- .../PortfolioStoreTests.swift | 777 +++++++++++++++++- 15 files changed, 1104 insertions(+), 39 deletions(-) create mode 100644 ios/brave-ios/Sources/Brave/Frontend/Settings/Debug/Brave Wallet/BraveWalletDebugMenu.swift diff --git a/ios/brave-ios/Sources/Brave/Frontend/Settings/Debug/Brave Wallet/BraveWalletDebugMenu.swift b/ios/brave-ios/Sources/Brave/Frontend/Settings/Debug/Brave Wallet/BraveWalletDebugMenu.swift new file mode 100644 index 000000000000..e747c6a16768 --- /dev/null +++ b/ios/brave-ios/Sources/Brave/Frontend/Settings/Debug/Brave Wallet/BraveWalletDebugMenu.swift @@ -0,0 +1,24 @@ +// Copyright 2021 The Brave Authors. All rights reserved. +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +import BraveUI +import SwiftUI +import BraveWallet +import Preferences + +struct BraveWalletDebugMenu: View { + + @ObservedObject var enableBitcoinTestnet = Preferences.Wallet.isBitcoinTestnetEnabled + + var body: some View { + List { + Section { + Toggle("Enable Bitcoin Testnet", isOn: $enableBitcoinTestnet.value) + .toggleStyle(SwitchToggleStyle(tint: .accentColor)) + } + } + .listBackgroundColor(Color(UIColor.braveGroupedBackground)) + } +} diff --git a/ios/brave-ios/Sources/Brave/Frontend/Settings/SettingsViewController.swift b/ios/brave-ios/Sources/Brave/Frontend/Settings/SettingsViewController.swift index 289410ddf16c..441c771e1b19 100644 --- a/ios/brave-ios/Sources/Brave/Frontend/Settings/SettingsViewController.swift +++ b/ios/brave-ios/Sources/Brave/Frontend/Settings/SettingsViewController.swift @@ -98,6 +98,9 @@ class SettingsViewController: TableViewController { self.cryptoStore = cryptoStore self.ipfsAPI = braveCore.ipfsAPI + self.keyringStore?.setupObservers() + self.cryptoStore?.setupObservers() + super.init(style: .insetGrouped) UIImageView.appearance(whenContainedInInstancesOf: [SettingsViewController.self]).tintColor = @@ -163,6 +166,13 @@ class SettingsViewController: TableViewController { navigationController?.pushViewController(hostingController, animated: true) } + private func displayBraveWalletDebugMenu() { + let hostingController = + UIHostingController(rootView: BraveWalletDebugMenu()) + + navigationController?.pushViewController(hostingController, animated: true) + } + /// The function for refreshing VPN status for menu /// - Parameter notification: NEVPNStatusDidChange @objc private func vpnConfigChanged(notification: NSNotification) { @@ -1062,6 +1072,14 @@ class SettingsViewController: TableViewController { accessory: .disclosureIndicator, cellClass: MultilineValue1Cell.self ), + Row( + text: "View Brave Wallet Debug Menu", + selection: { [unowned self] in + self.displayBraveWalletDebugMenu() + }, + accessory: .disclosureIndicator, + cellClass: MultilineValue1Cell.self + ), Row( text: "Consolidate Privacy Report Data", detailText: diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Accounts/Add/AddAccountView.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Accounts/Add/AddAccountView.swift index d12ce6abccac..9b175eb0032e 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Accounts/Add/AddAccountView.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Accounts/Add/AddAccountView.swift @@ -8,6 +8,7 @@ import BraveUI import DesignSystem import Strings import SwiftUI +import Preferences struct AddAccountView: View { @ObservedObject var keyringStore: KeyringStore @@ -419,8 +420,14 @@ struct AddAccountView: View { private var isKeyringSelectionRequired: Bool { guard !selectedCoinNetworks.isEmpty else { return false } - return (selectedCoin == .fil || selectedCoin == .btc) - || (preSelectedCoin == .fil || preSelectedCoin == .btc) + if selectedCoin == .fil || preSelectedCoin == .fil { + return true + } else if (selectedCoin == .btc || preSelectedCoin == .btc) + && Preferences.Wallet.isBitcoinTestnetEnabled.value + { + return true + } + return false } } diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountsStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountsStore.swift index d0fd91a8eb87..c4552ab8e61a 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountsStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountsStore.swift @@ -108,7 +108,7 @@ class AccountsStore: ObservableObject, WalletObserverStore { } let tokens = allTokensPerNetwork.flatMap(\.tokens) - var allAccounts = await keyringService.allAccounts().accounts + var allAccounts = await keyringService.allAccounts(checkBTCTestnet: true) var accountDetails = buildAccountDetails(accounts: allAccounts, tokens: tokens) self.primaryAccounts = accountDetails @@ -123,7 +123,7 @@ class AccountsStore: ObservableObject, WalletObserverStore { ) // if new accounts added while balances were being fetched. - allAccounts = await keyringService.allAccounts().accounts + allAccounts = await keyringService.allAccounts(checkBTCTestnet: true) accountDetails = buildAccountDetails(accounts: allAccounts, tokens: tokens) self.primaryAccounts = accountDetails diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/DepositTokenStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/DepositTokenStore.swift index 99f1375775d0..baf8bb972d14 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/DepositTokenStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/DepositTokenStore.swift @@ -113,9 +113,7 @@ class DepositTokenStore: ObservableObject, WalletObserverStore { accounts: allAccounts.filter({ $0.coin == .btc }) ) } - self.allNetworks = await rpcService.allNetworksForSupportedCoins( - respectTestnetPreference: true - ) + self.allNetworks = await rpcService.allNetworksForSupportedCoins() self.update() } } diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/KeyringStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/KeyringStore.swift index db7501c8b373..4ca36d7a0155 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/KeyringStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/KeyringStore.swift @@ -194,6 +194,9 @@ public class KeyringStore: ObservableObject, WalletObserverStore { self.keychain = keychain setupObservers() + + Preferences.Wallet.isBitcoinTestnetEnabled.observe(from: self) + updateInfo() Task { @MainActor in @@ -655,3 +658,23 @@ public class KeyringStore: ObservableObject, WalletObserverStore { keychain.getPasswordFromKeychain(key: Self.passwordKeychainKey) } } + +extension KeyringStore: PreferencesObserver { + public func preferencesDidChange(for key: String) { + if Preferences.Wallet.isBitcoinTestnetEnabled.value == false { + // user disabled Bitcoin Testnet + Task { @MainActor in + let allAccounts = await keyringService.allAccounts() + if let currentlySelectedAccount = allAccounts.selectedAccount, + currentlySelectedAccount.keyringId == .bitcoin84Testnet, + let firstAvailableAccount = allAccounts.accounts.first(where: { + $0.keyringId != currentlySelectedAccount.keyringId + }) + { + // we need to switch to the first available account + setSelectedAccount(to: firstAvailableAccount) + } + } + } + } +} diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NFTStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NFTStore.swift index 1872da9fbbdf..a1bec1043394 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NFTStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NFTStore.swift @@ -293,7 +293,7 @@ public class NFTStore: ObservableObject, WalletObserverStore { let isLocked = await keyringService.isLocked() guard !isLocked else { return } // `update() will be called after unlock` - self.allAccounts = await keyringService.allAccounts().accounts + self.allAccounts = await keyringService.allAccounts(checkBTCTestnet: true) .filter { account in WalletConstants.supportedCoinTypes().contains(account.coin) } diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NetworkStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NetworkStore.swift index 098be2da49a0..b45e36996495 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NetworkStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NetworkStore.swift @@ -383,7 +383,11 @@ extension Array where Element == BraveWallet.NetworkInfo { /// Returns the known test networks in Self. var testNetworks: [BraveWallet.NetworkInfo] { filter { - WalletConstants.supportedTestNetworkChainIds.contains($0.chainId) + if !Preferences.Wallet.isBitcoinTestnetEnabled.value && + $0.chainId == BraveWallet.BitcoinTestnet { + return false + } + return WalletConstants.supportedTestNetworkChainIds.contains($0.chainId) } } } diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/PortfolioStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/PortfolioStore.swift index efe3b537ad6c..8e21f4f02397 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/PortfolioStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/PortfolioStore.swift @@ -437,7 +437,7 @@ public class PortfolioStore: ObservableObject, WalletObserverStore { guard !isLocked else { return } // `update() will be called after unlock` self.isLoadingBalances = true - self.allAccounts = await keyringService.allAccounts().accounts + self.allAccounts = await keyringService.allAccounts(checkBTCTestnet: true) .filter { account in WalletConstants.supportedCoinTypes().contains(account.coin) } diff --git a/ios/brave-ios/Sources/BraveWallet/Extensions/KeyringServiceExtensions.swift b/ios/brave-ios/Sources/BraveWallet/Extensions/KeyringServiceExtensions.swift index 5f90c58d5c27..47b7152e9c7b 100644 --- a/ios/brave-ios/Sources/BraveWallet/Extensions/KeyringServiceExtensions.swift +++ b/ios/brave-ios/Sources/BraveWallet/Extensions/KeyringServiceExtensions.swift @@ -6,6 +6,7 @@ import BraveCore import Foundation import OrderedCollections +import Preferences extension BraveWalletKeyringService { @@ -19,4 +20,14 @@ extension BraveWalletKeyringService { let allAccountsForKeyring = await allAccounts().accounts.filter { $0.keyringId == keyringId } return !allAccountsForKeyring.isEmpty } + + /// Return a list of all accounts with checking if Bitcoin testnet is enabled + /// The list of account will not include Bitcoin Testnet Accounts if Bitcoin testnet is disabled. + func allAccounts(checkBTCTestnet: Bool) async -> [BraveWallet.AccountInfo] { + var accounts = await self.allAccounts().accounts + if checkBTCTestnet, !Preferences.Wallet.isBitcoinTestnetEnabled.value { + accounts = accounts.filter({ $0.keyringId != BraveWallet.KeyringId.bitcoin84Testnet }) + } + return accounts + } } diff --git a/ios/brave-ios/Sources/BraveWallet/Extensions/RpcServiceExtensions.swift b/ios/brave-ios/Sources/BraveWallet/Extensions/RpcServiceExtensions.swift index a088ff10ef2a..3c3ad4f28fde 100644 --- a/ios/brave-ios/Sources/BraveWallet/Extensions/RpcServiceExtensions.swift +++ b/ios/brave-ios/Sources/BraveWallet/Extensions/RpcServiceExtensions.swift @@ -388,6 +388,11 @@ extension BraveWalletJsonRpcService { $0 == network.chainId }) } + if Preferences.Wallet.showTestNetworks.value && respectTestnetPreference + && network.chainId == BraveWallet.BitcoinTestnet + { // if testnets are enabled, we still need to check if bitcoin testnet is enabled + return Preferences.Wallet.isBitcoinTestnetEnabled.value + } return true } return allChains.sorted { lhs, rhs in diff --git a/ios/brave-ios/Sources/BraveWallet/WalletPreferences.swift b/ios/brave-ios/Sources/BraveWallet/WalletPreferences.swift index cdae1ab09363..d0ec247879da 100644 --- a/ios/brave-ios/Sources/BraveWallet/WalletPreferences.swift +++ b/ios/brave-ios/Sources/BraveWallet/WalletPreferences.swift @@ -158,6 +158,12 @@ extension Preferences { key: "wallet.show-wallet-is-onboarding-completed", default: false ) + + /// Used for Debug section for anyone wants to test with Bitcoin Testnet network or account + public static let isBitcoinTestnetEnabled = Option( + key: "wallet.is-bitcoin-testnet-enabled", + default: false + ) } } diff --git a/ios/brave-ios/Sources/BraveWallet/WalletUserAssetManager.swift b/ios/brave-ios/Sources/BraveWallet/WalletUserAssetManager.swift index 1cb0cc8010ca..094cf5df9efe 100644 --- a/ios/brave-ios/Sources/BraveWallet/WalletUserAssetManager.swift +++ b/ios/brave-ios/Sources/BraveWallet/WalletUserAssetManager.swift @@ -378,7 +378,7 @@ public class WalletUserAssetManager: WalletUserAssetManagerType, WalletObserverS public func refreshBalances(_ completion: (() -> Void)? = nil) { refreshBalanceTask?.cancel() refreshBalanceTask = Task { @MainActor in - let accounts = await keyringService.allAccounts().accounts + let accounts = await keyringService.allAccounts(checkBTCTestnet: true) let allNetworks = await rpcService.allNetworksForSupportedCoins() let allUserAssets: [NetworkAssets] = self.getAllUserAssetsInNetworkAssets( networks: allNetworks, diff --git a/ios/brave-ios/Tests/BraveWalletTests/AccountsStoreTests.swift b/ios/brave-ios/Tests/BraveWalletTests/AccountsStoreTests.swift index bbce22fdc706..57d736575d38 100644 --- a/ios/brave-ios/Tests/BraveWalletTests/AccountsStoreTests.swift +++ b/ios/brave-ios/Tests/BraveWalletTests/AccountsStoreTests.swift @@ -12,6 +12,12 @@ import XCTest @MainActor class AccountsStoreTests: XCTestCase { + override class func tearDown() { + super.tearDown() + Preferences.Wallet.showTestNetworks.reset() + Preferences.Wallet.isBitcoinTestnetEnabled.reset() + } + private var cancellables: Set = .init() let ethAccount1: BraveWallet.AccountInfo = .mockEthAccount @@ -98,6 +104,243 @@ import XCTest func testUpdate() async { Preferences.Wallet.showTestNetworks.value = true + Preferences.Wallet.isBitcoinTestnetEnabled.value = false + let ethBalanceWei = + formatter.weiString( + from: mockETHBalanceAccount1, + radix: .hex, + decimals: Int(BraveWallet.NetworkInfo.mockMainnet.nativeToken.decimals) + ) ?? "" + let usdcAccount1BalanceWei = + formatter.weiString( + from: mockUSDCBalanceAccount1, + radix: .hex, + decimals: Int(BraveWallet.BlockchainToken.mockUSDCToken.decimals) + ) ?? "" + let usdcAccount2BalanceWei = + formatter.weiString( + from: mockUSDCBalanceAccount2, + radix: .hex, + decimals: Int(BraveWallet.BlockchainToken.mockUSDCToken.decimals) + ) ?? "" + let mockSOLLamportBalance: UInt64 = 3_876_535_000 // ~3.8765 SOL + let mockFilBalanceInWei = + formatter.weiString( + from: mockFILBalanceAccount1, + radix: .decimal, + decimals: Int(BraveWallet.NetworkInfo.mockFilecoinMainnet.nativeToken.decimals) + ) ?? "" + let mockFilTestnetBalanceInWei = + formatter.weiString( + from: mockFILBalanceAccount1, + radix: .decimal, + decimals: Int(BraveWallet.NetworkInfo.mockFilecoinTestnet.nativeToken.decimals) + ) ?? "" + let mockBtcBalanceInWei = + formatter.weiString( + from: mockBTCBalanceAccount1, + radix: .decimal, + decimals: Int(BraveWallet.NetworkInfo.mockBitcoinMainnet.nativeToken.decimals) + ) ?? "" + let mockBtcTestnetBalanceInWei = + formatter.weiString( + from: mockBTCTestnetBalanceAccount1, + radix: .decimal, + decimals: Int(BraveWallet.NetworkInfo.mockBitcoinTestnet.nativeToken.decimals) + ) ?? "" + + let keyringService = BraveWallet.TestKeyringService() + keyringService._addObserver = { _ in } + keyringService._allAccounts = { + $0( + .init( + accounts: [ + self.ethAccount1, self.ethAccount2, + self.solAccount1, self.filAccount1, + self.filTestnetAccount, + self.btcAccount1, self.btcTestnetAccount, + ], + selectedAccount: self.ethAccount1, + ethDappSelectedAccount: self.ethAccount1, + solDappSelectedAccount: self.solAccount1 + ) + ) + } + let rpcService = BraveWallet.TestJsonRpcService() + rpcService._addObserver = { _ in } + rpcService._allNetworks = { coin, completion in + completion( + [ + .mockMainnet, + .mockSolana, + .mockFilecoinMainnet, + .mockFilecoinTestnet, + .mockBitcoinMainnet, + .mockBitcoinTestnet, + ].filter { $0.coin == coin } + ) + } + // TODO: Update balance test once bitcoin balance fetching is integrated + // https://github.com/brave/brave-browser/issues/36966 + rpcService._balance = { accountAddress, coin, chainId, completion in + if coin == .eth, + chainId == BraveWallet.MainnetChainId, + accountAddress == self.ethAccount1.address + { + completion(ethBalanceWei, .success, "") + } else if coin == .fil, + chainId == BraveWallet.FilecoinMainnet, + accountAddress == self.filAccount1.address + { // .fil + completion(mockFilBalanceInWei, .success, "") + } else if coin == .fil, + chainId == BraveWallet.FilecoinTestnet, + accountAddress == self.filTestnetAccount.address + { + completion(mockFilTestnetBalanceInWei, .success, "") + } else if coin == .btc, + chainId == BraveWallet.BitcoinMainnet + { + completion(mockBtcBalanceInWei, .success, "") + } else if coin == .btc, + chainId == BraveWallet.BitcoinTestnet + { + completion(mockBtcTestnetBalanceInWei, .success, "") + } else { + completion("", .internalError, "") + } + } + rpcService._erc20TokenBalance = { contractAddress, accountAddress, _, completion in + // usdc balance + if accountAddress == self.ethAccount1.address { + completion(usdcAccount1BalanceWei, .success, "") + } else if accountAddress == self.ethAccount2.address { + completion(usdcAccount2BalanceWei, .success, "") + } + } + rpcService._erc721TokenBalance = { _, _, _, _, completion in + completion("", .internalError, "") + } + rpcService._solanaBalance = { accountAddress, chainId, completion in + if accountAddress == self.solAccount1.address { + completion(mockSOLLamportBalance, .success, "") + } else { + completion(0, .success, "") + } + } + + let walletService = BraveWallet.TestBraveWalletService() + walletService._addObserver = { _ in } + + let assetRatioService = BraveWallet.TestAssetRatioService() + assetRatioService._price = { priceIds, _, _, completion in + completion( + true, + [ + self.mockETHAssetPrice, + self.mockUSDCAssetPrice, + self.mockSOLAssetPrice, + self.mockFILAssetPrice, + self.mockBTCAssetPrice, + ] + ) + } + + let userAssetManager = TestableWalletUserAssetManager() + userAssetManager._getAllUserAssetsInNetworkAssets = { networks, _ in + [ + NetworkAssets( + network: .mockMainnet, + tokens: self.ethMainnetTokens, + sortOrder: 0 + ), + NetworkAssets( + network: .mockSolana, + tokens: self.solMainnetTokens, + sortOrder: 1 + ), + NetworkAssets( + network: .mockFilecoinMainnet, + tokens: self.filMainnetTokens, + sortOrder: 2 + ), + NetworkAssets( + network: .mockFilecoinTestnet, + tokens: self.filTestnetTokens, + sortOrder: 3 + ), + NetworkAssets( + network: .mockBitcoinMainnet, + tokens: self.btcMainnetTokens, + sortOrder: 4 + ), + NetworkAssets( + network: .mockBitcoinTestnet, + tokens: self.btcTestnetTokens, + sortOrder: 5 + ), + ].filter { networkAsset in + networks.contains(where: { $0.chainId == networkAsset.network.chainId }) + } + } + + let store = AccountsStore( + keyringService: keyringService, + rpcService: rpcService, + walletService: walletService, + assetRatioService: assetRatioService, + userAssetManager: userAssetManager + ) + + let updateExpectation = expectation(description: "update") + store.$primaryAccounts + .dropFirst() // initial + .collect(2) // with accounts & tokens, with balances & prices loaded + .sink { accountDetails in + defer { updateExpectation.fulfill() } + guard let accountDetails = accountDetails.last else { + XCTFail("Expected account details models") + return + } + XCTAssertEqual(accountDetails.count, 6) // bitcoin testnet is default disabled + + XCTAssertEqual(accountDetails[safe: 0]?.account, self.ethAccount1) + XCTAssertEqual(accountDetails[safe: 0]?.tokensWithBalance, self.ethMainnetTokens) + XCTAssertEqual(accountDetails[safe: 0]?.totalBalanceFiat, "$2,741.78") + + XCTAssertEqual(accountDetails[safe: 1]?.account, self.ethAccount2) + XCTAssertEqual(accountDetails[safe: 1]?.tokensWithBalance.count, 1) // usdc only + XCTAssertEqual( + accountDetails[safe: 1]?.tokensWithBalance[safe: 0], + self.ethMainnetTokens[safe: 1] + ) // usdc + XCTAssertEqual(accountDetails[safe: 1]?.totalBalanceFiat, "$0.01") + + XCTAssertEqual(accountDetails[safe: 2]?.account, self.solAccount1) + XCTAssertEqual(accountDetails[safe: 2]?.tokensWithBalance, self.solMainnetTokens) + XCTAssertEqual(accountDetails[safe: 2]?.totalBalanceFiat, "$775.30") + + XCTAssertEqual(accountDetails[safe: 3]?.account, self.filAccount1) + XCTAssertEqual(accountDetails[safe: 3]?.tokensWithBalance, self.filMainnetTokens) + XCTAssertEqual(accountDetails[safe: 3]?.totalBalanceFiat, "$4.00") + + XCTAssertEqual(accountDetails[safe: 4]?.account, self.filTestnetAccount) + XCTAssertEqual(accountDetails[safe: 4]?.tokensWithBalance, self.filTestnetTokens) + XCTAssertEqual(accountDetails[safe: 4]?.totalBalanceFiat, "$4.00") + + XCTAssertEqual(accountDetails[safe: 5]?.account, self.btcAccount1) + XCTAssertEqual(accountDetails[safe: 5]?.tokensWithBalance, []) // BTC 0 value + XCTAssertEqual(accountDetails[safe: 5]?.totalBalanceFiat, "$0.00") + }.store(in: &cancellables) + + store.update() + + await fulfillment(of: [updateExpectation], timeout: 1) + } + + func testUpdateWithBitcoinTestnet() async { + Preferences.Wallet.showTestNetworks.value = true + Preferences.Wallet.isBitcoinTestnetEnabled.value = true let ethBalanceWei = formatter.weiString( from: mockETHBalanceAccount1, @@ -334,9 +577,4 @@ import XCTest await fulfillment(of: [updateExpectation], timeout: 1) } - - override class func tearDown() { - super.tearDown() - Preferences.Wallet.showTestNetworks.reset() - } } diff --git a/ios/brave-ios/Tests/BraveWalletTests/PortfolioStoreTests.swift b/ios/brave-ios/Tests/BraveWalletTests/PortfolioStoreTests.swift index 92c7f897599e..38c2a8be10c9 100644 --- a/ios/brave-ios/Tests/BraveWalletTests/PortfolioStoreTests.swift +++ b/ios/brave-ios/Tests/BraveWalletTests/PortfolioStoreTests.swift @@ -31,6 +31,7 @@ import XCTest Preferences.Wallet.isHidingSmallBalancesFilter.reset() Preferences.Wallet.nonSelectedAccountsFilter.reset() Preferences.Wallet.nonSelectedNetworksFilter.reset() + Preferences.Wallet.isBitcoinTestnetEnabled.reset() } // Accounts @@ -432,7 +433,7 @@ import XCTest return } - // ETH on Ethereum mainnet, SOL on Solana mainnet, USDC on Ethereum mainnet, ETH on Goerli, FIL on Filecoin mainnet, FIL on Filecoin testnet, BTC on Bitcoin mainnet, BTC on Bitcoin testnet + // ETH on Ethereum mainnet, SOL on Solana mainnet, USDC on Ethereum mainnet, ETH on Goerli, FIL on Filecoin mainnet, FIL on Filecoin testnet, BTC on Bitcoin mainnet. No BTC on Bitcoin testnet XCTAssertEqual(group.assets.count, 5) // ETH Mainnet (value ~= $2741.7510399999996) XCTAssertEqual( @@ -526,7 +527,7 @@ import XCTest // ETH Goerli (value = 0), hidden because test networks not selected by default // FIL Testnet (value = $400.00), hidden because test networks not selected by default - // FIL Testnet (value = 0.00), hidden because test networks not selected by default + // BIT Testnet (value = 0.00), hidden because Bitcoin testnet is disabled by default XCTAssertNil(group.assets[safe: 5]) } .store(in: &cancellables) @@ -636,7 +637,115 @@ import XCTest XCTFail("Unexpected test result") return } - // USDC on Ethereum mainnet, SOL on Solana mainnet, ETH on Ethereum mainnet, FIL on Filecoin mainnet, FIL on Filecoin testnet, BTC on Bitcoin mainnet and BTC on Bitcoin testnet + // USDC on Ethereum mainnet, SOL on Solana mainnet, ETH on Ethereum mainnet, FIL on Filecoin mainnet, FIL on Filecoin testnet, BTC on Bitcoin mainnet. No BTC on Bitcoin testnet since Bitcoin tesnet is disabled by default + XCTAssertEqual(group.assets.count, 7) + // BTC mainnet (value = $0) + XCTAssertEqual( + group.assets[safe: 0]?.token.symbol, + BraveWallet.BlockchainToken.mockBTCToken.symbol + ) + XCTAssertEqual(group.assets[safe: 0]?.quantity, String(format: "%.04f", 0)) + // ETH Goerli (value = $0) + XCTAssertEqual( + group.assets[safe: 1]?.token.symbol, + BraveWallet.BlockchainToken.previewToken.symbol + ) + XCTAssertEqual(group.assets[safe: 1]?.quantity, String(format: "%.04f", 0)) + // USDC (value = $0.04) + XCTAssertEqual( + group.assets[safe: 2]?.token.symbol, + BraveWallet.BlockchainToken.mockUSDCToken.symbol + ) + XCTAssertEqual( + group.assets[safe: 2]?.quantity, + String(format: "%.04f", self.mockUSDCBalanceAccount1 + self.mockUSDCBalanceAccount2) + ) + // FIL (value = $4.00) on filecoin mainnet + XCTAssertEqual( + group.assets[safe: 3]?.token.symbol, + BraveWallet.BlockchainToken.mockFilToken.symbol + ) + XCTAssertEqual( + group.assets[safe: 3]?.quantity, + String(format: "%.04f", self.mockFILBalanceAccount1) + ) + // FIL (value = $400.00) on filecoin testnet + XCTAssertEqual( + group.assets[safe: 4]?.token.symbol, + BraveWallet.BlockchainToken.mockFilToken.symbol + ) + XCTAssertEqual( + group.assets[safe: 4]?.quantity, + String(format: "%.04f", self.mockFILBalanceTestnet) + ) + // SOL (value = $775.3) + XCTAssertEqual( + group.assets[safe: 5]?.token.symbol, + BraveWallet.BlockchainToken.mockSolToken.symbol + ) + XCTAssertEqual( + group.assets[safe: 5]?.quantity, + String(format: "%.04f", self.mockSOLBalance) + ) + // ETH Mainnet (value ~= $2741.7510399999996) + XCTAssertEqual( + group.assets[safe: 6]?.token.symbol, + BraveWallet.BlockchainToken.previewToken.symbol + ) + XCTAssertEqual( + group.assets[safe: 6]?.quantity, + String(format: "%.04f", self.mockETHBalanceAccount1) + ) + }.store(in: &cancellables) + + isLocked = false + // change sort to ascending + store.saveFilters( + .init( + groupBy: store.filters.groupBy, + sortOrder: .valueAsc, + isHidingSmallBalances: store.filters.isHidingSmallBalances, + isHidingUnownedNFTs: store.filters.isHidingUnownedNFTs, + isShowingNFTNetworkLogo: store.filters.isShowingNFTNetworkLogo, + accounts: [ + ethAccount1, ethAccount2, solAccount1, solAccount2, filAccount1, filAccount2, + filTestnetAccount, btcAccount1, btcAccount2, btcTestnetAccount, + ].map { + .init(isSelected: true, model: $0) + }, + networks: [ + ethNetwork, goerliNetwork, solNetwork, filMainnet, filTestnet, btcMainnet, btcTestnet, + ].map { + .init(isSelected: true, model: $0) + } + ) + ) + await fulfillment(of: [sortExpectation], timeout: 1) + cancellables.removeAll() + } + + /// Test `assetGroups` will be sorted to from smallest to highest fiat value when `sortOrder` filter is `valueAsc`. + func testFilterSortWithBitcoinTestnet() async { + Preferences.Wallet.showTestNetworks.value = true + Preferences.Wallet.isBitcoinTestnetEnabled.value = true + let store = setupStore() + let sortExpectation = expectation(description: "update-sortOrder") + store.$assetGroups + .dropFirst() + .collect(2) + .sink { assetGroups in + defer { sortExpectation.fulfill() } + XCTAssertEqual(assetGroups.count, 2) // empty (no balance, price, history), populated + guard let lastUpdatedAssetGroups = assetGroups.last else { + XCTFail("Unexpected test result") + return + } + XCTAssertEqual(lastUpdatedAssetGroups.count, 1) // grouping by .none, so only 1 group + guard let group = lastUpdatedAssetGroups.first else { + XCTFail("Unexpected test result") + return + } + // USDC on Ethereum mainnet, SOL on Solana mainnet, ETH on Ethereum mainnet, FIL on Filecoin mainnet, FIL on Filecoin testnet, BTC on Bitcoin mainnet. No BTC on Bitcoin testnet since Bitcoin tesnet is disabled by default XCTAssertEqual(group.assets.count, 8) // BTC mainnet (value = $0) XCTAssertEqual( @@ -801,12 +910,12 @@ import XCTest isShowingNFTNetworkLogo: store.filters.isShowingNFTNetworkLogo, accounts: [ ethAccount1, ethAccount2, solAccount1, solAccount2, filAccount1, filAccount2, - filTestnetAccount, btcAccount1, btcAccount2, btcTestnetAccount, + filTestnetAccount, btcAccount1, btcAccount2, ].map { .init(isSelected: true, model: $0) }, networks: [ - ethNetwork, goerliNetwork, solNetwork, filMainnet, filTestnet, btcMainnet, btcTestnet, + ethNetwork, goerliNetwork, solNetwork, filMainnet, filTestnet, btcMainnet, ].map { .init(isSelected: true, model: $0) } @@ -821,6 +930,111 @@ import XCTest Preferences.Wallet.showTestNetworks.value = true let store = setupStore() let accountsExpectation = expectation(description: "update-accounts") + store.$assetGroups + .dropFirst() + .collect(2) + .sink { assetGroups in + defer { accountsExpectation.fulfill() } + XCTAssertEqual(assetGroups.count, 2) // empty (no balance, price, history), populated + guard let lastUpdatedAssetGroups = assetGroups.last else { + XCTFail("Unexpected test result") + return + } + XCTAssertEqual(lastUpdatedAssetGroups.count, 1) // grouping by .none, so only 1 group + guard let group = lastUpdatedAssetGroups.first else { + XCTFail("Unexpected test result") + return + } + // ETH on Ethereum mainnet, SOL on Solana mainnet, USDC on Ethereum mainnet, ETH on Goerli, FIL on mainnet and testnet, BTC on mainnet but testnet + XCTAssertEqual(group.assets.count, 7) + // ETH Mainnet (value ~= $2741.7510399999996) + XCTAssertEqual( + group.assets[safe: 0]?.token.symbol, + BraveWallet.BlockchainToken.previewToken.symbol + ) + XCTAssertEqual( + group.assets[safe: 0]?.quantity, + String(format: "%.04f", self.mockETHBalanceAccount1) + ) + // SOL (value = $775.3) + XCTAssertEqual( + group.assets[safe: 1]?.token.symbol, + BraveWallet.BlockchainToken.mockSolToken.symbol + ) + XCTAssertEqual( + group.assets[safe: 1]?.quantity, + String(format: "%.04f", self.mockSOLBalance) + ) + // FIL (value = $400) on testnet + XCTAssertEqual( + group.assets[safe: 2]?.token.symbol, + BraveWallet.BlockchainToken.mockFilToken.symbol + ) + XCTAssertEqual( + group.assets[safe: 2]?.quantity, + String(format: "%.04f", self.mockFILBalanceTestnet) + ) + // FIL (value = $4) on mainnet + XCTAssertEqual( + group.assets[safe: 3]?.token.symbol, + BraveWallet.BlockchainToken.mockFilToken.symbol + ) + XCTAssertEqual( + group.assets[safe: 3]?.quantity, + String(format: "%.04f", self.mockFILBalanceAccount1) + ) + // USDC (value = $0.03, ethAccount2 hidden!) + XCTAssertEqual( + group.assets[safe: 4]?.token.symbol, + BraveWallet.BlockchainToken.mockUSDCToken.symbol + ) + XCTAssertEqual( + group.assets[safe: 4]?.quantity, + String(format: "%.04f", self.mockUSDCBalanceAccount1) + ) // verify account 2 hidden + // BTC mainnet (value = $0) + XCTAssertEqual( + group.assets[safe: 5]?.token.symbol, + self.btcMainnet.nativeToken.symbol + ) + // ETH Goerli (value = $0) + XCTAssertEqual( + group.assets[safe: 6]?.token.symbol, + self.goerliNetwork.nativeToken.symbol + ) + XCTAssertEqual(group.assets[safe: 6]?.quantity, String(format: "%.04f", 0)) + }.store(in: &cancellables) + isLocked = false + store.saveFilters( + .init( + groupBy: store.filters.groupBy, + sortOrder: .valueDesc, + isHidingSmallBalances: false, + isHidingUnownedNFTs: store.filters.isHidingUnownedNFTs, + isShowingNFTNetworkLogo: store.filters.isShowingNFTNetworkLogo, + accounts: [ + ethAccount1, ethAccount2, solAccount1, solAccount2, filAccount1, filAccount2, + filTestnetAccount, btcAccount1, btcAccount2, + ].map { // deselect ethAccount2 + .init(isSelected: $0 != ethAccount2, model: $0) + }, + networks: [ + ethNetwork, goerliNetwork, solNetwork, filMainnet, filTestnet, btcMainnet, + ].map { + .init(isSelected: true, model: $0) + } + ) + ) + await fulfillment(of: [accountsExpectation], timeout: 1) + cancellables.removeAll() + } + + /// Test `assetGroups` will be filtered by accounts when `accounts` filter is has de-selected accounts. + func testFilterAccountsWithBitcoinTesnet() async { + Preferences.Wallet.showTestNetworks.value = true + Preferences.Wallet.isBitcoinTestnetEnabled.value = true + let store = setupStore() + let accountsExpectation = expectation(description: "update-accounts") store.$assetGroups .dropFirst() .collect(2) @@ -1042,8 +1256,8 @@ import XCTest XCTFail("Unexpected test result") return } - // grouping by .account; eth has 2 accounts, sol has 2 accounts, fil has 3 accounts, btc has 3 accounts - XCTAssertEqual(lastUpdatedAssetGroups.count, 10) + // grouping by .account; eth has 2 accounts, sol has 2 accounts, fil has 3 accounts, btc has 2 accounts(no testnet) + XCTAssertEqual(lastUpdatedAssetGroups.count, 9) guard let ethAccount1Group = lastUpdatedAssetGroups[safe: 0], let solAccount1Group = lastUpdatedAssetGroups[safe: 1], let filTestnetAccountGroup = lastUpdatedAssetGroups[safe: 2], @@ -1052,8 +1266,7 @@ import XCTest let solAccount2Group = lastUpdatedAssetGroups[safe: 5], let btcAccount1Group = lastUpdatedAssetGroups[safe: 6], let btcAccount2Group = lastUpdatedAssetGroups[safe: 7], - let btcTestnetAccountGroup = lastUpdatedAssetGroups[safe: 8], - let filAccount2Group = lastUpdatedAssetGroups[safe: 9] + let filAccount2Group = lastUpdatedAssetGroups[safe: 8] else { XCTFail("Unexpected test result") return @@ -1174,15 +1387,6 @@ import XCTest ) XCTAssertEqual(btcAccount2Group.assets[safe: 0]?.quantity, String(format: "%.04f", 0)) - XCTAssertEqual(btcTestnetAccountGroup.groupType, .account(self.btcTestnetAccount)) - XCTAssertEqual(btcTestnetAccountGroup.assets.count, 1) - // BTC (value = $0) - XCTAssertEqual( - btcTestnetAccountGroup.assets[safe: 0]?.token.symbol, - BraveWallet.BlockchainToken.mockBTCToken.symbol - ) - XCTAssertEqual(btcTestnetAccountGroup.assets[safe: 0]?.quantity, String(format: "%.04f", 0)) - XCTAssertEqual(filAccount2Group.groupType, .account(self.filAccount2)) XCTAssertEqual(filAccount2Group.assets.count, 1) // FIL (value = $0) @@ -1208,12 +1412,12 @@ import XCTest isShowingNFTNetworkLogo: store.filters.isShowingNFTNetworkLogo, accounts: [ ethAccount1, ethAccount2, solAccount1, solAccount2, filAccount1, filAccount2, - filTestnetAccount, btcAccount1, btcAccount2, btcTestnetAccount, + filTestnetAccount, btcAccount1, btcAccount2, ].map { .init(isSelected: true, model: $0) }, networks: [ - ethNetwork, goerliNetwork, solNetwork, filMainnet, filTestnet, btcMainnet, btcTestnet, + ethNetwork, goerliNetwork, solNetwork, filMainnet, filTestnet, btcMainnet, ].map { .init(isSelected: true, model: $0) } @@ -1326,10 +1530,537 @@ import XCTest await fulfillment(of: [accountsExpectation], timeout: 1) } - /// Test `assetGroups` will be grouped by network when `GroupBy` filter is assigned `.network`. - /// Additionally, test de-selecting/hiding one of the available networks. - func testGroupByNetworks() async { + /// Test `assetGroups` will be grouped by account when `GroupBy` filter is assigned `.account`. + /// Additionally, test de-selecting/hiding one of the available accounts. + func testGroupByAccountsWithBitcoinTestnet() async { + Preferences.Wallet.showTestNetworks.value = true + Preferences.Wallet.isBitcoinTestnetEnabled.value = true + let store = setupStore() + let assetGroupsExpectation = expectation(description: "update-assetGroups") + XCTAssertTrue(store.assetGroups.isEmpty) // Initial state + store.$assetGroups + .dropFirst() + .collect(2) + .sink { assetGroups in + defer { assetGroupsExpectation.fulfill() } + XCTAssertEqual(assetGroups.count, 2) // empty (no balance, price, history), populated + guard let lastUpdatedAssetGroups = assetGroups.last else { + XCTFail("Unexpected test result") + return + } + // grouping by .account; eth has 2 accounts, sol has 2 accounts, fil has 3 accounts, btc has 3 accounts + XCTAssertEqual(lastUpdatedAssetGroups.count, 10) + guard let ethAccount1Group = lastUpdatedAssetGroups[safe: 0], + let solAccount1Group = lastUpdatedAssetGroups[safe: 1], + let filTestnetAccountGroup = lastUpdatedAssetGroups[safe: 2], + let filAccount1Group = lastUpdatedAssetGroups[safe: 3], + let ethAccount2Group = lastUpdatedAssetGroups[safe: 4], + let solAccount2Group = lastUpdatedAssetGroups[safe: 5], + let btcAccount1Group = lastUpdatedAssetGroups[safe: 6], + let btcAccount2Group = lastUpdatedAssetGroups[safe: 7], + let btcTestnetAccountGroup = lastUpdatedAssetGroups[safe: 8], + let filAccount2Group = lastUpdatedAssetGroups[safe: 9] + else { + XCTFail("Unexpected test result") + return + } + + XCTAssertEqual(ethAccount1Group.groupType, .account(self.ethAccount1)) + XCTAssertEqual(ethAccount1Group.assets.count, 3) // ETH Mainnet, USDC, ETH Goerli + // ETH Mainnet (value ~= $2741.7510399999996) + XCTAssertEqual( + ethAccount1Group.assets[safe: 0]?.token.symbol, + BraveWallet.BlockchainToken.previewToken.symbol + ) + XCTAssertEqual( + ethAccount1Group.assets[safe: 0]?.quantity, + String(format: "%.04f", self.mockETHBalanceAccount1) + ) + // USDC (value = $0.03) + XCTAssertEqual( + ethAccount1Group.assets[safe: 1]?.token.symbol, + BraveWallet.BlockchainToken.mockUSDCToken.symbol + ) + XCTAssertEqual( + ethAccount1Group.assets[safe: 1]?.quantity, + String(format: "%.04f", self.mockUSDCBalanceAccount1) + ) + // ETH Goerli (value = $0) + XCTAssertEqual( + ethAccount1Group.assets[safe: 2]?.token.symbol, + self.goerliNetwork.nativeToken.symbol + ) + XCTAssertEqual(ethAccount1Group.assets[safe: 2]?.quantity, String(format: "%.04f", 0)) + + XCTAssertEqual(solAccount1Group.groupType, .account(self.solAccount1)) + XCTAssertEqual(solAccount1Group.assets.count, 1) + // SOL (value = $775.3) + XCTAssertEqual( + solAccount1Group.assets[safe: 0]?.token.symbol, + BraveWallet.BlockchainToken.mockSolToken.symbol + ) + XCTAssertEqual( + solAccount1Group.assets[safe: 0]?.quantity, + String(format: "%.04f", self.mockSOLBalance) + ) + + XCTAssertEqual(filTestnetAccountGroup.groupType, .account(self.filTestnetAccount)) + XCTAssertEqual(filTestnetAccountGroup.assets.count, 1) + // FIL (value = $400) + XCTAssertEqual( + filTestnetAccountGroup.assets[safe: 0]?.token.symbol, + BraveWallet.BlockchainToken.mockFilToken.symbol + ) + XCTAssertEqual( + filTestnetAccountGroup.assets[safe: 0]?.quantity, + String(format: "%.04f", self.mockFILBalanceTestnet) + ) + + XCTAssertEqual(filAccount1Group.groupType, .account(self.filAccount1)) + XCTAssertEqual(filAccount1Group.assets.count, 1) + // FIL (value = $4) + XCTAssertEqual( + filAccount1Group.assets[safe: 0]?.token.symbol, + BraveWallet.BlockchainToken.mockFilToken.symbol + ) + XCTAssertEqual( + filAccount1Group.assets[safe: 0]?.quantity, + String(format: "%.04f", self.mockFILBalanceAccount1) + ) + + XCTAssertEqual(ethAccount2Group.groupType, .account(self.ethAccount2)) + XCTAssertEqual(ethAccount2Group.assets.count, 3) // ETH Mainnet, USDC, ETH Goerli + // USDC (value $0.01) + XCTAssertEqual( + ethAccount2Group.assets[safe: 0]?.token.symbol, + BraveWallet.BlockchainToken.mockUSDCToken.symbol + ) + XCTAssertEqual( + ethAccount2Group.assets[safe: 0]?.quantity, + String(format: "%.04f", self.mockUSDCBalanceAccount2) + ) + // ETH Mainnet (value = $0) + XCTAssertEqual( + ethAccount2Group.assets[safe: 1]?.token.symbol, + BraveWallet.BlockchainToken.previewToken.symbol + ) + XCTAssertEqual(ethAccount2Group.assets[safe: 1]?.quantity, String(format: "%.04f", 0)) + // ETH Goerli (value = $0) + XCTAssertEqual( + ethAccount2Group.assets[safe: 2]?.token.symbol, + self.goerliNetwork.nativeToken.symbol + ) + XCTAssertEqual(ethAccount2Group.assets[safe: 2]?.quantity, String(format: "%.04f", 0)) + + XCTAssertEqual(solAccount2Group.groupType, .account(self.solAccount2)) + XCTAssertEqual(solAccount2Group.assets.count, 1) + // SOL (value = $0) + XCTAssertEqual( + solAccount2Group.assets[safe: 0]?.token.symbol, + BraveWallet.BlockchainToken.mockSolToken.symbol + ) + XCTAssertEqual(solAccount2Group.assets[safe: 0]?.quantity, String(format: "%.04f", 0)) + + XCTAssertEqual(btcAccount1Group.assets[safe: 0]?.quantity, String(format: "%.04f", 0)) + XCTAssertEqual(btcAccount1Group.groupType, .account(self.btcAccount1)) + XCTAssertEqual(btcAccount1Group.assets.count, 1) + // BTC (value = $0) + XCTAssertEqual( + btcAccount1Group.assets[safe: 0]?.token.symbol, + BraveWallet.BlockchainToken.mockBTCToken.symbol + ) + + XCTAssertEqual(btcAccount2Group.assets[safe: 0]?.quantity, String(format: "%.04f", 0)) + XCTAssertEqual(btcAccount2Group.groupType, .account(self.btcAccount2)) + XCTAssertEqual(btcAccount2Group.assets.count, 1) + // BTC (value = $0) + XCTAssertEqual( + btcAccount2Group.assets[safe: 0]?.token.symbol, + BraveWallet.BlockchainToken.mockBTCToken.symbol + ) + XCTAssertEqual(btcAccount2Group.assets[safe: 0]?.quantity, String(format: "%.04f", 0)) + + XCTAssertEqual(btcTestnetAccountGroup.groupType, .account(self.btcTestnetAccount)) + XCTAssertEqual(btcTestnetAccountGroup.assets.count, 1) + // BTC (value = $0) + XCTAssertEqual( + btcTestnetAccountGroup.assets[safe: 0]?.token.symbol, + BraveWallet.BlockchainToken.mockBTCToken.symbol + ) + XCTAssertEqual(btcTestnetAccountGroup.assets[safe: 0]?.quantity, String(format: "%.04f", 0)) + + XCTAssertEqual(filAccount2Group.groupType, .account(self.filAccount2)) + XCTAssertEqual(filAccount2Group.assets.count, 1) + // FIL (value = $0) + XCTAssertEqual( + filAccount2Group.assets[safe: 0]?.token.symbol, + BraveWallet.BlockchainToken.mockFilToken.symbol + ) + + // Verify NFTs not used in Portfolio #7945 + let noAssetsAreNFTs = lastUpdatedAssetGroups.flatMap(\.assets).allSatisfy({ + !($0.token.isNft || $0.token.isErc721) + }) + XCTAssertTrue(noAssetsAreNFTs) + } + .store(in: &cancellables) + isLocked = false + store.saveFilters( + .init( + groupBy: .accounts, + sortOrder: store.filters.sortOrder, + isHidingSmallBalances: store.filters.isHidingSmallBalances, + isHidingUnownedNFTs: store.filters.isHidingUnownedNFTs, + isShowingNFTNetworkLogo: store.filters.isShowingNFTNetworkLogo, + accounts: [ + ethAccount1, ethAccount2, solAccount1, solAccount2, filAccount1, filAccount2, + filTestnetAccount, btcAccount1, btcAccount2, btcTestnetAccount, + ].map { + .init(isSelected: true, model: $0) + }, + networks: [ + ethNetwork, goerliNetwork, solNetwork, filMainnet, filTestnet, btcMainnet, btcTestnet, + ].map { + .init(isSelected: true, model: $0) + } + ) + ) + await fulfillment(of: [assetGroupsExpectation], timeout: 1) + cancellables.removeAll() + // test hiding an account & hiding groups with small balances + let accountsExpectation = expectation(description: "update-accounts") + store.$assetGroups + .dropFirst() + .collect(2) + .sink { assetGroups in + defer { accountsExpectation.fulfill() } + XCTAssertEqual(assetGroups.count, 2) // empty (no balance, price, history), populated + guard let lastUpdatedAssetGroups = assetGroups.last else { + XCTFail("Unexpected test result") + return + } + // grouping by .account; 1 for each of the 2 accounts selected accounts + XCTAssertEqual(lastUpdatedAssetGroups.count, 4) + guard let ethAccount1Group = lastUpdatedAssetGroups[safe: 0], + let solAccountGroup = lastUpdatedAssetGroups[safe: 1], + let filTestnetAccountGroup = lastUpdatedAssetGroups[safe: 2], + let filAccount1Group = lastUpdatedAssetGroups[safe: 3] + else { + XCTFail("Unexpected test result") + return + } + XCTAssertEqual(ethAccount1Group.groupType, .account(self.ethAccount1)) + // ETH Mainnet (USDC, ETH Goerli hidden for small balance) + XCTAssertEqual(ethAccount1Group.assets.count, 1) + // ETH Mainnet (value ~= $2741.7510399999996) + XCTAssertEqual( + ethAccount1Group.assets[safe: 0]?.token.symbol, + BraveWallet.BlockchainToken.previewToken.symbol + ) + XCTAssertEqual( + ethAccount1Group.assets[safe: 0]?.quantity, + String(format: "%.04f", self.mockETHBalanceAccount1) + ) + // USDC (value $0.03) + XCTAssertNil(ethAccount1Group.assets[safe: 1]) + + XCTAssertEqual(solAccountGroup.groupType, .account(.mockSolAccount)) + XCTAssertEqual(solAccountGroup.assets.count, 1) + // SOL (value = $775.3) + XCTAssertEqual( + solAccountGroup.assets[safe: 0]?.token.symbol, + BraveWallet.BlockchainToken.mockSolToken.symbol + ) + XCTAssertEqual( + solAccountGroup.assets[safe: 0]?.quantity, + String(format: "%.04f", self.mockSOLBalance) + ) + + XCTAssertEqual(filTestnetAccountGroup.groupType, .account(self.filTestnetAccount)) + XCTAssertEqual(filTestnetAccountGroup.assets.count, 1) + // FIL (value = $400) + XCTAssertEqual( + filTestnetAccountGroup.assets[safe: 0]?.token.symbol, + BraveWallet.BlockchainToken.mockFilToken.symbol + ) + XCTAssertEqual( + filTestnetAccountGroup.assets[safe: 0]?.quantity, + String(format: "%.04f", self.mockFILBalanceTestnet) + ) + + XCTAssertEqual(filAccount1Group.groupType, .account(self.filAccount1)) + XCTAssertEqual(filAccount1Group.assets.count, 1) + // FIL (value = $4) + XCTAssertEqual( + filAccount1Group.assets[safe: 0]?.token.symbol, + BraveWallet.BlockchainToken.mockFilToken.symbol + ) + XCTAssertEqual( + filAccount1Group.assets[safe: 0]?.quantity, + String(format: "%.04f", self.mockFILBalanceAccount1) + ) + + // ethAccount2 hidden as it's de-selected, solAccount2 hidden for small balance, filAccount2 hidden for small balance, btcAccount1/btcAccount2/btcTestAccount hidden for small balance + XCTAssertNil(lastUpdatedAssetGroups[safe: 4]) + XCTAssertNil(lastUpdatedAssetGroups[safe: 5]) + XCTAssertNil(lastUpdatedAssetGroups[safe: 6]) + XCTAssertNil(lastUpdatedAssetGroups[safe: 7]) + XCTAssertNil(lastUpdatedAssetGroups[safe: 8]) + XCTAssertNil(lastUpdatedAssetGroups[safe: 9]) + } + .store(in: &cancellables) + store.saveFilters( + .init( + groupBy: .accounts, + sortOrder: store.filters.sortOrder, + isHidingSmallBalances: true, + isHidingUnownedNFTs: store.filters.isHidingUnownedNFTs, + isShowingNFTNetworkLogo: store.filters.isShowingNFTNetworkLogo, + accounts: [ + ethAccount1, ethAccount2, solAccount1, solAccount2, filAccount1, filAccount2, + filTestnetAccount, btcAccount1, btcAccount2, btcTestnetAccount, + ].map { + .init(isSelected: $0 != ethAccount2, model: $0) + }, + networks: [ + ethNetwork, goerliNetwork, solNetwork, filMainnet, filTestnet, btcMainnet, btcTestnet, + ].map { + .init(isSelected: true, model: $0) + } + ) + ) + await fulfillment(of: [accountsExpectation], timeout: 1) + } + + /// Test `assetGroups` will be grouped by network when `GroupBy` filter is assigned `.network`. + /// Additionally, test de-selecting/hiding one of the available networks. + func testGroupByNetworks() async { + Preferences.Wallet.showTestNetworks.value = true + let store = setupStore() + let assetGroupsExpectation = expectation(description: "update-assetGroups") + XCTAssertTrue(store.assetGroups.isEmpty) // Initial state + store.$assetGroups + .dropFirst() + .collect(2) + .sink { assetGroups in + defer { assetGroupsExpectation.fulfill() } + XCTAssertEqual(assetGroups.count, 2) // empty (no balance, price, history), populated + guard let lastUpdatedAssetGroups = assetGroups.last else { + XCTFail("Unexpected test result") + return + } + // grouping by .network; 1 for each of the 2 networks. Bitcoin testnet is disabled + // network groups order should be the same as the order of all networks in `Filters` + XCTAssertEqual(lastUpdatedAssetGroups.count, 6) + guard let ethMainnetGroup = lastUpdatedAssetGroups[safe: 0], + let solMainnetGroup = lastUpdatedAssetGroups[safe: 1], + let filTestnetGroup = lastUpdatedAssetGroups[safe: 2], + let filMainnetGroup = lastUpdatedAssetGroups[safe: 3], + let btcMainnetGroup = lastUpdatedAssetGroups[safe: 4], + let ethGoerliGroup = lastUpdatedAssetGroups[safe: 5] + else { + XCTFail("Unexpected test result") + return + } + XCTAssertEqual(ethMainnetGroup.groupType, .network(.mockMainnet)) + XCTAssertEqual(ethMainnetGroup.assets.count, 2) // ETH Mainnet, USDC + // ETH (value ~= $2741.7510399999996) + XCTAssertEqual( + ethMainnetGroup.assets[safe: 0]?.token.symbol, + BraveWallet.BlockchainToken.previewToken.symbol + ) + XCTAssertEqual( + ethMainnetGroup.assets[safe: 0]?.quantity, + String(format: "%.04f", self.mockETHBalanceAccount1) + ) + // USDC (value = $0.04) + XCTAssertEqual( + ethMainnetGroup.assets[safe: 1]?.token.symbol, + BraveWallet.BlockchainToken.mockUSDCToken.symbol + ) + XCTAssertEqual( + ethMainnetGroup.assets[safe: 1]?.quantity, + String(format: "%.04f", self.mockUSDCBalanceAccount1 + self.mockUSDCBalanceAccount2) + ) + + XCTAssertEqual(solMainnetGroup.groupType, .network(.mockSolana)) + XCTAssertEqual(solMainnetGroup.assets.count, 1) // SOL + // SOL (value = $775.3) + XCTAssertEqual( + solMainnetGroup.assets[safe: 0]?.token.symbol, + BraveWallet.BlockchainToken.mockSolToken.symbol + ) + XCTAssertEqual( + solMainnetGroup.assets[safe: 0]?.quantity, + String(format: "%.04f", self.mockSOLBalance) + ) + + XCTAssertEqual(filTestnetGroup.groupType, .network(.mockFilecoinTestnet)) + XCTAssertEqual(filTestnetGroup.assets.count, 1) // FIL on testnet + // FIL (value = $400) + XCTAssertEqual( + filTestnetGroup.assets[safe: 0]?.token.symbol, + BraveWallet.BlockchainToken.mockFilToken.symbol + ) + XCTAssertEqual( + filTestnetGroup.assets[safe: 0]?.quantity, + String(format: "%.04f", self.mockFILBalanceTestnet) + ) + + XCTAssertEqual(filMainnetGroup.groupType, .network(.mockFilecoinMainnet)) + XCTAssertEqual(filMainnetGroup.assets.count, 1) // FIL on mainnet + // FIL (value = $4) + XCTAssertEqual( + filMainnetGroup.assets[safe: 0]?.token.symbol, + BraveWallet.BlockchainToken.mockFilToken.symbol + ) + XCTAssertEqual( + filMainnetGroup.assets[safe: 0]?.quantity, + String(format: "%.04f", self.mockFILBalanceAccount1) + ) + + XCTAssertEqual(ethGoerliGroup.groupType, .network(.mockGoerli)) + XCTAssertEqual(ethGoerliGroup.assets.count, 1) // ETH Goerli + // ETH Goerli (value = $0) + XCTAssertEqual( + ethGoerliGroup.assets[safe: 0]?.token.symbol, + BraveWallet.BlockchainToken.previewToken.symbol + ) + XCTAssertEqual(ethGoerliGroup.assets[safe: 0]?.quantity, String(format: "%.04f", 0)) + + XCTAssertEqual(btcMainnetGroup.groupType, .network(self.btcMainnet)) + XCTAssertEqual(btcMainnetGroup.assets.count, 1) + // BTC mainnet (value = $0) + XCTAssertEqual( + btcMainnetGroup.assets[safe: 0]?.token.symbol, + BraveWallet.BlockchainToken.mockBTCToken.symbol + ) + XCTAssertEqual(btcMainnetGroup.assets[safe: 0]?.quantity, String(format: "%.04f", 0)) + + // Verify NFTs not used in Portfolio #7945 + let noAssetsAreNFTs = lastUpdatedAssetGroups.flatMap(\.assets).allSatisfy({ + !($0.token.isNft || $0.token.isErc721) + }) + XCTAssertTrue(noAssetsAreNFTs) + } + .store(in: &cancellables) + isLocked = false + store.saveFilters( + .init( + groupBy: .networks, + sortOrder: store.filters.sortOrder, + isHidingSmallBalances: store.filters.isHidingSmallBalances, + isHidingUnownedNFTs: store.filters.isHidingUnownedNFTs, + isShowingNFTNetworkLogo: store.filters.isShowingNFTNetworkLogo, + accounts: [ + ethAccount1, ethAccount2, solAccount1, solAccount2, filAccount1, filAccount2, + filTestnetAccount, btcAccount1, btcAccount2, + ].map { + .init(isSelected: true, model: $0) + }, + networks: [ + ethNetwork, goerliNetwork, solNetwork, filMainnet, filTestnet, btcMainnet, + ].map { + .init(isSelected: true, model: $0) + } + ) + ) + await fulfillment(of: [assetGroupsExpectation], timeout: 1) + cancellables.removeAll() + // test hiding a network & hiding groups with small balances + let networksExpectation = expectation(description: "update-networks") + store.$assetGroups + .dropFirst() + .collect(2) + .sink { assetGroups in + defer { networksExpectation.fulfill() } + XCTAssertEqual(assetGroups.count, 2) // empty (no balance, price, history), populated + guard let lastUpdatedAssetGroups = assetGroups.last else { + XCTFail("Unexpected test result") + return + } + // grouping by .network; 1 group for Solana network, 1 group for Filecoin mainnet, 1 group for Filecoin testnet + XCTAssertEqual(lastUpdatedAssetGroups.count, 3) + guard let solMainnetGroup = lastUpdatedAssetGroups[safe: 0], + let filTestnetGroup = lastUpdatedAssetGroups[safe: 1], + let filMainnetGroup = lastUpdatedAssetGroups[safe: 2] + else { + XCTFail("Unexpected test result") + return + } + XCTAssertEqual(solMainnetGroup.groupType, .network(.mockSolana)) + XCTAssertEqual(solMainnetGroup.assets.count, 1) // SOL + // SOL (value = $775.3) + XCTAssertEqual( + solMainnetGroup.assets[safe: 0]?.token.symbol, + BraveWallet.BlockchainToken.mockSolToken.symbol + ) + XCTAssertEqual( + solMainnetGroup.assets[safe: 0]?.quantity, + String(format: "%.04f", self.mockSOLBalance) + ) + + XCTAssertEqual(filTestnetGroup.groupType, .network(.mockFilecoinTestnet)) + XCTAssertEqual(filTestnetGroup.assets.count, 1) // FIL + // FIL (value = $400) + XCTAssertEqual( + filTestnetGroup.assets[safe: 0]?.token.symbol, + BraveWallet.BlockchainToken.mockFilToken.symbol + ) + XCTAssertEqual( + filTestnetGroup.assets[safe: 0]?.quantity, + String(format: "%.04f", self.mockFILBalanceTestnet) + ) + + XCTAssertEqual(filMainnetGroup.groupType, .network(.mockFilecoinMainnet)) + XCTAssertEqual(filMainnetGroup.assets.count, 1) // FIL + // FIL (value = $4) + XCTAssertEqual( + filMainnetGroup.assets[safe: 0]?.token.symbol, + BraveWallet.BlockchainToken.mockFilToken.symbol + ) + XCTAssertEqual( + filMainnetGroup.assets[safe: 0]?.quantity, + String(format: "%.04f", self.mockFILBalanceAccount1) + ) + // eth mainnet group hidden as network de-selected + XCTAssertNil(lastUpdatedAssetGroups[safe: 3]) + // goerli network group hidden for small balance + XCTAssertNil(lastUpdatedAssetGroups[safe: 4]) + // Bitcoin network group hidden for small balance + XCTAssertNil(lastUpdatedAssetGroups[safe: 5]) + } + .store(in: &cancellables) + store.saveFilters( + .init( + groupBy: .networks, + sortOrder: store.filters.sortOrder, + isHidingSmallBalances: true, + isHidingUnownedNFTs: store.filters.isHidingUnownedNFTs, + isShowingNFTNetworkLogo: store.filters.isShowingNFTNetworkLogo, + accounts: [ + ethAccount1, ethAccount2, solAccount1, solAccount2, filAccount1, filAccount2, + filTestnetAccount, btcAccount1, btcAccount2, + ].map { + .init(isSelected: true, model: $0) + }, + networks: [ + ethNetwork, goerliNetwork, solNetwork, filMainnet, filTestnet, btcMainnet, + ].map { + // hide ethNetwork + .init(isSelected: $0 != ethNetwork, model: $0) + } + ) + ) + await fulfillment(of: [networksExpectation], timeout: 1) + cancellables.removeAll() + } + + /// Test `assetGroups` will be grouped by network when `GroupBy` filter is assigned `.network`. + /// Additionally, test de-selecting/hiding one of the available networks. + func testGroupByNetworksWithBitcoinTestnet() async { Preferences.Wallet.showTestNetworks.value = true + Preferences.Wallet.isBitcoinTestnetEnabled.value = true let store = setupStore() let assetGroupsExpectation = expectation(description: "update-assetGroups") XCTAssertTrue(store.assetGroups.isEmpty) // Initial state From fe5161e2c999a51ebd524e2ff5c24e28bc067a0f Mon Sep 17 00:00:00 2001 From: Nuo Xu Date: Mon, 8 Apr 2024 17:39:38 -0400 Subject: [PATCH 2/4] consolidate some unit tests to avoid duplicated code. --- .../Brave Wallet/BraveWalletDebugMenu.swift | 2 +- .../Crypto/Accounts/Add/AddAccountView.swift | 2 +- .../Crypto/Stores/NetworkStore.swift | 5 +- .../BraveWalletTests/AccountsStoreTests.swift | 254 +---- .../PortfolioStoreTests.swift | 915 +++--------------- 5 files changed, 141 insertions(+), 1037 deletions(-) diff --git a/ios/brave-ios/Sources/Brave/Frontend/Settings/Debug/Brave Wallet/BraveWalletDebugMenu.swift b/ios/brave-ios/Sources/Brave/Frontend/Settings/Debug/Brave Wallet/BraveWalletDebugMenu.swift index e747c6a16768..9a24ba6bdc0b 100644 --- a/ios/brave-ios/Sources/Brave/Frontend/Settings/Debug/Brave Wallet/BraveWalletDebugMenu.swift +++ b/ios/brave-ios/Sources/Brave/Frontend/Settings/Debug/Brave Wallet/BraveWalletDebugMenu.swift @@ -4,9 +4,9 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. import BraveUI -import SwiftUI import BraveWallet import Preferences +import SwiftUI struct BraveWalletDebugMenu: View { diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Accounts/Add/AddAccountView.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Accounts/Add/AddAccountView.swift index 9b175eb0032e..9df962bc6204 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Accounts/Add/AddAccountView.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Accounts/Add/AddAccountView.swift @@ -6,9 +6,9 @@ import BraveCore import BraveUI import DesignSystem +import Preferences import Strings import SwiftUI -import Preferences struct AddAccountView: View { @ObservedObject var keyringStore: KeyringStore diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NetworkStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NetworkStore.swift index b45e36996495..f06554755df5 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NetworkStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NetworkStore.swift @@ -383,8 +383,9 @@ extension Array where Element == BraveWallet.NetworkInfo { /// Returns the known test networks in Self. var testNetworks: [BraveWallet.NetworkInfo] { filter { - if !Preferences.Wallet.isBitcoinTestnetEnabled.value && - $0.chainId == BraveWallet.BitcoinTestnet { + if !Preferences.Wallet.isBitcoinTestnetEnabled.value + && $0.chainId == BraveWallet.BitcoinTestnet + { return false } return WalletConstants.supportedTestNetworkChainIds.contains($0.chainId) diff --git a/ios/brave-ios/Tests/BraveWalletTests/AccountsStoreTests.swift b/ios/brave-ios/Tests/BraveWalletTests/AccountsStoreTests.swift index 57d736575d38..52acad8ea83a 100644 --- a/ios/brave-ios/Tests/BraveWalletTests/AccountsStoreTests.swift +++ b/ios/brave-ios/Tests/BraveWalletTests/AccountsStoreTests.swift @@ -102,9 +102,9 @@ import XCTest let formatter = WeiFormatter(decimalFormatStyle: .decimals(precision: 18)) - func testUpdate() async { + func testUpdate(with bitcoinTestnetEnabled: Bool) async { Preferences.Wallet.showTestNetworks.value = true - Preferences.Wallet.isBitcoinTestnetEnabled.value = false + Preferences.Wallet.isBitcoinTestnetEnabled.value = bitcoinTestnetEnabled let ethBalanceWei = formatter.weiString( from: mockETHBalanceAccount1, @@ -302,7 +302,8 @@ import XCTest XCTFail("Expected account details models") return } - XCTAssertEqual(accountDetails.count, 6) // bitcoin testnet is default disabled + let accountNumber = bitcoinTestnetEnabled ? 7 : 6 + XCTAssertEqual(accountDetails.count, accountNumber) XCTAssertEqual(accountDetails[safe: 0]?.account, self.ethAccount1) XCTAssertEqual(accountDetails[safe: 0]?.tokensWithBalance, self.ethMainnetTokens) @@ -331,250 +332,21 @@ import XCTest XCTAssertEqual(accountDetails[safe: 5]?.account, self.btcAccount1) XCTAssertEqual(accountDetails[safe: 5]?.tokensWithBalance, []) // BTC 0 value XCTAssertEqual(accountDetails[safe: 5]?.totalBalanceFiat, "$0.00") - }.store(in: &cancellables) - - store.update() - - await fulfillment(of: [updateExpectation], timeout: 1) - } - - func testUpdateWithBitcoinTestnet() async { - Preferences.Wallet.showTestNetworks.value = true - Preferences.Wallet.isBitcoinTestnetEnabled.value = true - let ethBalanceWei = - formatter.weiString( - from: mockETHBalanceAccount1, - radix: .hex, - decimals: Int(BraveWallet.NetworkInfo.mockMainnet.nativeToken.decimals) - ) ?? "" - let usdcAccount1BalanceWei = - formatter.weiString( - from: mockUSDCBalanceAccount1, - radix: .hex, - decimals: Int(BraveWallet.BlockchainToken.mockUSDCToken.decimals) - ) ?? "" - let usdcAccount2BalanceWei = - formatter.weiString( - from: mockUSDCBalanceAccount2, - radix: .hex, - decimals: Int(BraveWallet.BlockchainToken.mockUSDCToken.decimals) - ) ?? "" - let mockSOLLamportBalance: UInt64 = 3_876_535_000 // ~3.8765 SOL - let mockFilBalanceInWei = - formatter.weiString( - from: mockFILBalanceAccount1, - radix: .decimal, - decimals: Int(BraveWallet.NetworkInfo.mockFilecoinMainnet.nativeToken.decimals) - ) ?? "" - let mockFilTestnetBalanceInWei = - formatter.weiString( - from: mockFILBalanceAccount1, - radix: .decimal, - decimals: Int(BraveWallet.NetworkInfo.mockFilecoinTestnet.nativeToken.decimals) - ) ?? "" - let mockBtcBalanceInWei = - formatter.weiString( - from: mockBTCBalanceAccount1, - radix: .decimal, - decimals: Int(BraveWallet.NetworkInfo.mockBitcoinMainnet.nativeToken.decimals) - ) ?? "" - let mockBtcTestnetBalanceInWei = - formatter.weiString( - from: mockBTCTestnetBalanceAccount1, - radix: .decimal, - decimals: Int(BraveWallet.NetworkInfo.mockBitcoinTestnet.nativeToken.decimals) - ) ?? "" - - let keyringService = BraveWallet.TestKeyringService() - keyringService._addObserver = { _ in } - keyringService._allAccounts = { - $0( - .init( - accounts: [ - self.ethAccount1, self.ethAccount2, - self.solAccount1, self.filAccount1, - self.filTestnetAccount, - self.btcAccount1, self.btcTestnetAccount, - ], - selectedAccount: self.ethAccount1, - ethDappSelectedAccount: self.ethAccount1, - solDappSelectedAccount: self.solAccount1 - ) - ) - } - let rpcService = BraveWallet.TestJsonRpcService() - rpcService._addObserver = { _ in } - rpcService._allNetworks = { coin, completion in - completion( - [ - .mockMainnet, - .mockSolana, - .mockFilecoinMainnet, - .mockFilecoinTestnet, - .mockBitcoinMainnet, - .mockBitcoinTestnet, - ].filter { $0.coin == coin } - ) - } - // TODO: Update balance test once bitcoin balance fetching is integrated - // https://github.com/brave/brave-browser/issues/36966 - rpcService._balance = { accountAddress, coin, chainId, completion in - if coin == .eth, - chainId == BraveWallet.MainnetChainId, - accountAddress == self.ethAccount1.address - { - completion(ethBalanceWei, .success, "") - } else if coin == .fil, - chainId == BraveWallet.FilecoinMainnet, - accountAddress == self.filAccount1.address - { // .fil - completion(mockFilBalanceInWei, .success, "") - } else if coin == .fil, - chainId == BraveWallet.FilecoinTestnet, - accountAddress == self.filTestnetAccount.address - { - completion(mockFilTestnetBalanceInWei, .success, "") - } else if coin == .btc, - chainId == BraveWallet.BitcoinMainnet - { - completion(mockBtcBalanceInWei, .success, "") - } else if coin == .btc, - chainId == BraveWallet.BitcoinTestnet - { - completion(mockBtcTestnetBalanceInWei, .success, "") - } else { - completion("", .internalError, "") - } - } - rpcService._erc20TokenBalance = { contractAddress, accountAddress, _, completion in - // usdc balance - if accountAddress == self.ethAccount1.address { - completion(usdcAccount1BalanceWei, .success, "") - } else if accountAddress == self.ethAccount2.address { - completion(usdcAccount2BalanceWei, .success, "") - } - } - rpcService._erc721TokenBalance = { _, _, _, _, completion in - completion("", .internalError, "") - } - rpcService._solanaBalance = { accountAddress, chainId, completion in - if accountAddress == self.solAccount1.address { - completion(mockSOLLamportBalance, .success, "") - } else { - completion(0, .success, "") - } - } - - let walletService = BraveWallet.TestBraveWalletService() - walletService._addObserver = { _ in } - - let assetRatioService = BraveWallet.TestAssetRatioService() - assetRatioService._price = { priceIds, _, _, completion in - completion( - true, - [ - self.mockETHAssetPrice, - self.mockUSDCAssetPrice, - self.mockSOLAssetPrice, - self.mockFILAssetPrice, - self.mockBTCAssetPrice, - ] - ) - } - - let userAssetManager = TestableWalletUserAssetManager() - userAssetManager._getAllUserAssetsInNetworkAssets = { networks, _ in - [ - NetworkAssets( - network: .mockMainnet, - tokens: self.ethMainnetTokens, - sortOrder: 0 - ), - NetworkAssets( - network: .mockSolana, - tokens: self.solMainnetTokens, - sortOrder: 1 - ), - NetworkAssets( - network: .mockFilecoinMainnet, - tokens: self.filMainnetTokens, - sortOrder: 2 - ), - NetworkAssets( - network: .mockFilecoinTestnet, - tokens: self.filTestnetTokens, - sortOrder: 3 - ), - NetworkAssets( - network: .mockBitcoinMainnet, - tokens: self.btcMainnetTokens, - sortOrder: 4 - ), - NetworkAssets( - network: .mockBitcoinTestnet, - tokens: self.btcTestnetTokens, - sortOrder: 5 - ), - ].filter { networkAsset in - networks.contains(where: { $0.chainId == networkAsset.network.chainId }) - } - } - - let store = AccountsStore( - keyringService: keyringService, - rpcService: rpcService, - walletService: walletService, - assetRatioService: assetRatioService, - userAssetManager: userAssetManager - ) - let updateExpectation = expectation(description: "update") - store.$primaryAccounts - .dropFirst() // initial - .collect(2) // with accounts & tokens, with balances & prices loaded - .sink { accountDetails in - defer { updateExpectation.fulfill() } - guard let accountDetails = accountDetails.last else { - XCTFail("Expected account details models") - return + if bitcoinTestnetEnabled { + XCTAssertEqual(accountDetails[safe: 6]?.account, self.btcTestnetAccount) + XCTAssertEqual(accountDetails[safe: 6]?.tokensWithBalance, []) // BTC 0 value + XCTAssertEqual(accountDetails[safe: 6]?.totalBalanceFiat, "$0.00") } - XCTAssertEqual(accountDetails.count, 7) - - XCTAssertEqual(accountDetails[safe: 0]?.account, self.ethAccount1) - XCTAssertEqual(accountDetails[safe: 0]?.tokensWithBalance, self.ethMainnetTokens) - XCTAssertEqual(accountDetails[safe: 0]?.totalBalanceFiat, "$2,741.78") - - XCTAssertEqual(accountDetails[safe: 1]?.account, self.ethAccount2) - XCTAssertEqual(accountDetails[safe: 1]?.tokensWithBalance.count, 1) // usdc only - XCTAssertEqual( - accountDetails[safe: 1]?.tokensWithBalance[safe: 0], - self.ethMainnetTokens[safe: 1] - ) // usdc - XCTAssertEqual(accountDetails[safe: 1]?.totalBalanceFiat, "$0.01") - - XCTAssertEqual(accountDetails[safe: 2]?.account, self.solAccount1) - XCTAssertEqual(accountDetails[safe: 2]?.tokensWithBalance, self.solMainnetTokens) - XCTAssertEqual(accountDetails[safe: 2]?.totalBalanceFiat, "$775.30") - - XCTAssertEqual(accountDetails[safe: 3]?.account, self.filAccount1) - XCTAssertEqual(accountDetails[safe: 3]?.tokensWithBalance, self.filMainnetTokens) - XCTAssertEqual(accountDetails[safe: 3]?.totalBalanceFiat, "$4.00") - - XCTAssertEqual(accountDetails[safe: 4]?.account, self.filTestnetAccount) - XCTAssertEqual(accountDetails[safe: 4]?.tokensWithBalance, self.filTestnetTokens) - XCTAssertEqual(accountDetails[safe: 4]?.totalBalanceFiat, "$4.00") - - XCTAssertEqual(accountDetails[safe: 5]?.account, self.btcAccount1) - XCTAssertEqual(accountDetails[safe: 5]?.tokensWithBalance, []) // BTC 0 value - XCTAssertEqual(accountDetails[safe: 5]?.totalBalanceFiat, "$0.00") - - XCTAssertEqual(accountDetails[safe: 6]?.account, self.btcTestnetAccount) - XCTAssertEqual(accountDetails[safe: 6]?.tokensWithBalance, []) // BTC 0 value - XCTAssertEqual(accountDetails[safe: 6]?.totalBalanceFiat, "$0.00") }.store(in: &cancellables) store.update() await fulfillment(of: [updateExpectation], timeout: 1) } + + func testUpdate() async { + await testUpdate(with: false) + await testUpdate(with: true) + } } diff --git a/ios/brave-ios/Tests/BraveWalletTests/PortfolioStoreTests.swift b/ios/brave-ios/Tests/BraveWalletTests/PortfolioStoreTests.swift index 38c2a8be10c9..0ced3737dad7 100644 --- a/ios/brave-ios/Tests/BraveWalletTests/PortfolioStoreTests.swift +++ b/ios/brave-ios/Tests/BraveWalletTests/PortfolioStoreTests.swift @@ -618,8 +618,9 @@ import XCTest } /// Test `assetGroups` will be sorted to from smallest to highest fiat value when `sortOrder` filter is `valueAsc`. - func testFilterSort() async { + func testFilterSort(with bitcoinTestnetEnabled: Bool) async { Preferences.Wallet.showTestNetworks.value = true + Preferences.Wallet.isBitcoinTestnetEnabled.value = bitcoinTestnetEnabled let store = setupStore() let sortExpectation = expectation(description: "update-sortOrder") store.$assetGroups @@ -638,62 +639,79 @@ import XCTest return } // USDC on Ethereum mainnet, SOL on Solana mainnet, ETH on Ethereum mainnet, FIL on Filecoin mainnet, FIL on Filecoin testnet, BTC on Bitcoin mainnet. No BTC on Bitcoin testnet since Bitcoin tesnet is disabled by default - XCTAssertEqual(group.assets.count, 7) + let assetGroupNumber = bitcoinTestnetEnabled ? 8 : 7 + XCTAssertEqual(group.assets.count, assetGroupNumber) // BTC mainnet (value = $0) XCTAssertEqual( group.assets[safe: 0]?.token.symbol, BraveWallet.BlockchainToken.mockBTCToken.symbol ) XCTAssertEqual(group.assets[safe: 0]?.quantity, String(format: "%.04f", 0)) + var offset = 0 + if bitcoinTestnetEnabled { + offset += 1 + // BTC testnet (value = $0) + XCTAssertEqual( + group.assets[safe: 1]?.token.symbol, + BraveWallet.BlockchainToken.mockBTCToken.symbol + ) + XCTAssertEqual(group.assets[safe: 1]?.quantity, String(format: "%.04f", 0)) + } // ETH Goerli (value = $0) + offset += 1 XCTAssertEqual( - group.assets[safe: 1]?.token.symbol, + group.assets[safe: offset]?.token.symbol, BraveWallet.BlockchainToken.previewToken.symbol ) - XCTAssertEqual(group.assets[safe: 1]?.quantity, String(format: "%.04f", 0)) + XCTAssertEqual(group.assets[safe: offset]?.quantity, String(format: "%.04f", 0)) // USDC (value = $0.04) + offset += 1 XCTAssertEqual( - group.assets[safe: 2]?.token.symbol, + group.assets[safe: offset]?.token.symbol, BraveWallet.BlockchainToken.mockUSDCToken.symbol ) XCTAssertEqual( - group.assets[safe: 2]?.quantity, + group.assets[safe: offset]?.quantity, String(format: "%.04f", self.mockUSDCBalanceAccount1 + self.mockUSDCBalanceAccount2) ) // FIL (value = $4.00) on filecoin mainnet + offset += 1 XCTAssertEqual( - group.assets[safe: 3]?.token.symbol, + group.assets[safe: offset]?.token.symbol, BraveWallet.BlockchainToken.mockFilToken.symbol ) XCTAssertEqual( - group.assets[safe: 3]?.quantity, + group.assets[safe: offset]?.quantity, String(format: "%.04f", self.mockFILBalanceAccount1) ) // FIL (value = $400.00) on filecoin testnet + offset += 1 XCTAssertEqual( - group.assets[safe: 4]?.token.symbol, + group.assets[safe: offset]?.token.symbol, BraveWallet.BlockchainToken.mockFilToken.symbol ) XCTAssertEqual( - group.assets[safe: 4]?.quantity, + group.assets[safe: offset]?.quantity, String(format: "%.04f", self.mockFILBalanceTestnet) ) // SOL (value = $775.3) + offset += 1 XCTAssertEqual( - group.assets[safe: 5]?.token.symbol, + group.assets[safe: offset]?.token.symbol, BraveWallet.BlockchainToken.mockSolToken.symbol ) XCTAssertEqual( - group.assets[safe: 5]?.quantity, + group.assets[safe: offset]?.quantity, String(format: "%.04f", self.mockSOLBalance) ) // ETH Mainnet (value ~= $2741.7510399999996) + offset += 1 XCTAssertEqual( - group.assets[safe: 6]?.token.symbol, + group.assets[safe: offset]?.token.symbol, BraveWallet.BlockchainToken.previewToken.symbol ) XCTAssertEqual( - group.assets[safe: 6]?.quantity, + group.assets[safe: offset]?.quantity, String(format: "%.04f", self.mockETHBalanceAccount1) ) }.store(in: &cancellables) @@ -724,118 +742,9 @@ import XCTest cancellables.removeAll() } - /// Test `assetGroups` will be sorted to from smallest to highest fiat value when `sortOrder` filter is `valueAsc`. - func testFilterSortWithBitcoinTestnet() async { - Preferences.Wallet.showTestNetworks.value = true - Preferences.Wallet.isBitcoinTestnetEnabled.value = true - let store = setupStore() - let sortExpectation = expectation(description: "update-sortOrder") - store.$assetGroups - .dropFirst() - .collect(2) - .sink { assetGroups in - defer { sortExpectation.fulfill() } - XCTAssertEqual(assetGroups.count, 2) // empty (no balance, price, history), populated - guard let lastUpdatedAssetGroups = assetGroups.last else { - XCTFail("Unexpected test result") - return - } - XCTAssertEqual(lastUpdatedAssetGroups.count, 1) // grouping by .none, so only 1 group - guard let group = lastUpdatedAssetGroups.first else { - XCTFail("Unexpected test result") - return - } - // USDC on Ethereum mainnet, SOL on Solana mainnet, ETH on Ethereum mainnet, FIL on Filecoin mainnet, FIL on Filecoin testnet, BTC on Bitcoin mainnet. No BTC on Bitcoin testnet since Bitcoin tesnet is disabled by default - XCTAssertEqual(group.assets.count, 8) - // BTC mainnet (value = $0) - XCTAssertEqual( - group.assets[safe: 0]?.token.symbol, - BraveWallet.BlockchainToken.mockBTCToken.symbol - ) - XCTAssertEqual(group.assets[safe: 0]?.quantity, String(format: "%.04f", 0)) - // BTC testnet (value = $0) - XCTAssertEqual( - group.assets[safe: 1]?.token.symbol, - BraveWallet.BlockchainToken.mockBTCToken.symbol - ) - XCTAssertEqual(group.assets[safe: 1]?.quantity, String(format: "%.04f", 0)) - // ETH Goerli (value = $0) - XCTAssertEqual( - group.assets[safe: 2]?.token.symbol, - BraveWallet.BlockchainToken.previewToken.symbol - ) - XCTAssertEqual(group.assets[safe: 2]?.quantity, String(format: "%.04f", 0)) - // USDC (value = $0.04) - XCTAssertEqual( - group.assets[safe: 3]?.token.symbol, - BraveWallet.BlockchainToken.mockUSDCToken.symbol - ) - XCTAssertEqual( - group.assets[safe: 3]?.quantity, - String(format: "%.04f", self.mockUSDCBalanceAccount1 + self.mockUSDCBalanceAccount2) - ) - // FIL (value = $4.00) on filecoin mainnet - XCTAssertEqual( - group.assets[safe: 4]?.token.symbol, - BraveWallet.BlockchainToken.mockFilToken.symbol - ) - XCTAssertEqual( - group.assets[safe: 4]?.quantity, - String(format: "%.04f", self.mockFILBalanceAccount1) - ) - // FIL (value = $400.00) on filecoin testnet - XCTAssertEqual( - group.assets[safe: 5]?.token.symbol, - BraveWallet.BlockchainToken.mockFilToken.symbol - ) - XCTAssertEqual( - group.assets[safe: 5]?.quantity, - String(format: "%.04f", self.mockFILBalanceTestnet) - ) - // SOL (value = $775.3) - XCTAssertEqual( - group.assets[safe: 6]?.token.symbol, - BraveWallet.BlockchainToken.mockSolToken.symbol - ) - XCTAssertEqual( - group.assets[safe: 6]?.quantity, - String(format: "%.04f", self.mockSOLBalance) - ) - // ETH Mainnet (value ~= $2741.7510399999996) - XCTAssertEqual( - group.assets[safe: 7]?.token.symbol, - BraveWallet.BlockchainToken.previewToken.symbol - ) - XCTAssertEqual( - group.assets[safe: 7]?.quantity, - String(format: "%.04f", self.mockETHBalanceAccount1) - ) - }.store(in: &cancellables) - - isLocked = false - // change sort to ascending - store.saveFilters( - .init( - groupBy: store.filters.groupBy, - sortOrder: .valueAsc, - isHidingSmallBalances: store.filters.isHidingSmallBalances, - isHidingUnownedNFTs: store.filters.isHidingUnownedNFTs, - isShowingNFTNetworkLogo: store.filters.isShowingNFTNetworkLogo, - accounts: [ - ethAccount1, ethAccount2, solAccount1, solAccount2, filAccount1, filAccount2, - filTestnetAccount, btcAccount1, btcAccount2, btcTestnetAccount, - ].map { - .init(isSelected: true, model: $0) - }, - networks: [ - ethNetwork, goerliNetwork, solNetwork, filMainnet, filTestnet, btcMainnet, btcTestnet, - ].map { - .init(isSelected: true, model: $0) - } - ) - ) - await fulfillment(of: [sortExpectation], timeout: 1) - cancellables.removeAll() + func testFilterSort() async { + await testFilterSort(with: false) + await testFilterSort(with: true) } /// Test `assetGroups` will be filtered to remove small balances when `hideSmallBalances` filter is true. @@ -926,113 +835,9 @@ import XCTest } /// Test `assetGroups` will be filtered by accounts when `accounts` filter is has de-selected accounts. - func testFilterAccounts() async { - Preferences.Wallet.showTestNetworks.value = true - let store = setupStore() - let accountsExpectation = expectation(description: "update-accounts") - store.$assetGroups - .dropFirst() - .collect(2) - .sink { assetGroups in - defer { accountsExpectation.fulfill() } - XCTAssertEqual(assetGroups.count, 2) // empty (no balance, price, history), populated - guard let lastUpdatedAssetGroups = assetGroups.last else { - XCTFail("Unexpected test result") - return - } - XCTAssertEqual(lastUpdatedAssetGroups.count, 1) // grouping by .none, so only 1 group - guard let group = lastUpdatedAssetGroups.first else { - XCTFail("Unexpected test result") - return - } - // ETH on Ethereum mainnet, SOL on Solana mainnet, USDC on Ethereum mainnet, ETH on Goerli, FIL on mainnet and testnet, BTC on mainnet but testnet - XCTAssertEqual(group.assets.count, 7) - // ETH Mainnet (value ~= $2741.7510399999996) - XCTAssertEqual( - group.assets[safe: 0]?.token.symbol, - BraveWallet.BlockchainToken.previewToken.symbol - ) - XCTAssertEqual( - group.assets[safe: 0]?.quantity, - String(format: "%.04f", self.mockETHBalanceAccount1) - ) - // SOL (value = $775.3) - XCTAssertEqual( - group.assets[safe: 1]?.token.symbol, - BraveWallet.BlockchainToken.mockSolToken.symbol - ) - XCTAssertEqual( - group.assets[safe: 1]?.quantity, - String(format: "%.04f", self.mockSOLBalance) - ) - // FIL (value = $400) on testnet - XCTAssertEqual( - group.assets[safe: 2]?.token.symbol, - BraveWallet.BlockchainToken.mockFilToken.symbol - ) - XCTAssertEqual( - group.assets[safe: 2]?.quantity, - String(format: "%.04f", self.mockFILBalanceTestnet) - ) - // FIL (value = $4) on mainnet - XCTAssertEqual( - group.assets[safe: 3]?.token.symbol, - BraveWallet.BlockchainToken.mockFilToken.symbol - ) - XCTAssertEqual( - group.assets[safe: 3]?.quantity, - String(format: "%.04f", self.mockFILBalanceAccount1) - ) - // USDC (value = $0.03, ethAccount2 hidden!) - XCTAssertEqual( - group.assets[safe: 4]?.token.symbol, - BraveWallet.BlockchainToken.mockUSDCToken.symbol - ) - XCTAssertEqual( - group.assets[safe: 4]?.quantity, - String(format: "%.04f", self.mockUSDCBalanceAccount1) - ) // verify account 2 hidden - // BTC mainnet (value = $0) - XCTAssertEqual( - group.assets[safe: 5]?.token.symbol, - self.btcMainnet.nativeToken.symbol - ) - // ETH Goerli (value = $0) - XCTAssertEqual( - group.assets[safe: 6]?.token.symbol, - self.goerliNetwork.nativeToken.symbol - ) - XCTAssertEqual(group.assets[safe: 6]?.quantity, String(format: "%.04f", 0)) - }.store(in: &cancellables) - isLocked = false - store.saveFilters( - .init( - groupBy: store.filters.groupBy, - sortOrder: .valueDesc, - isHidingSmallBalances: false, - isHidingUnownedNFTs: store.filters.isHidingUnownedNFTs, - isShowingNFTNetworkLogo: store.filters.isShowingNFTNetworkLogo, - accounts: [ - ethAccount1, ethAccount2, solAccount1, solAccount2, filAccount1, filAccount2, - filTestnetAccount, btcAccount1, btcAccount2, - ].map { // deselect ethAccount2 - .init(isSelected: $0 != ethAccount2, model: $0) - }, - networks: [ - ethNetwork, goerliNetwork, solNetwork, filMainnet, filTestnet, btcMainnet, - ].map { - .init(isSelected: true, model: $0) - } - ) - ) - await fulfillment(of: [accountsExpectation], timeout: 1) - cancellables.removeAll() - } - - /// Test `assetGroups` will be filtered by accounts when `accounts` filter is has de-selected accounts. - func testFilterAccountsWithBitcoinTesnet() async { + func testFilterAccounts(with bitcoinTesnetEnabled: Bool) async { Preferences.Wallet.showTestNetworks.value = true - Preferences.Wallet.isBitcoinTestnetEnabled.value = true + Preferences.Wallet.isBitcoinTestnetEnabled.value = bitcoinTesnetEnabled let store = setupStore() let accountsExpectation = expectation(description: "update-accounts") store.$assetGroups @@ -1051,7 +856,8 @@ import XCTest return } // ETH on Ethereum mainnet, SOL on Solana mainnet, USDC on Ethereum mainnet, ETH on Goerli, FIL on mainnet and testnet, BTC on mainnet and testnet - XCTAssertEqual(group.assets.count, 8) + let assetGroupNumber = bitcoinTesnetEnabled ? 8 : 7 + XCTAssertEqual(group.assets.count, bitcoinTesnetEnabled) // ETH Mainnet (value ~= $2741.7510399999996) XCTAssertEqual( group.assets[safe: 0]?.token.symbol, @@ -1102,17 +908,21 @@ import XCTest group.assets[safe: 5]?.token.symbol, self.btcMainnet.nativeToken.symbol ) - // BTC testnet (value = $0) - XCTAssertEqual( - group.assets[safe: 6]?.token.symbol, - self.btcMainnet.nativeToken.symbol - ) + var goerliOffset = 6 + if bitcoinTesnetEnabled { + // BTC testnet (value = $0) + XCTAssertEqual( + group.assets[safe: 6]?.token.symbol, + self.btcMainnet.nativeToken.symbol + ) + var goerliOffset = 7 + } // ETH Goerli (value = $0) XCTAssertEqual( - group.assets[safe: 7]?.token.symbol, + group.assets[safe: goerliOffset]?.token.symbol, self.goerliNetwork.nativeToken.symbol ) - XCTAssertEqual(group.assets[safe: 7]?.quantity, String(format: "%.04f", 0)) + XCTAssertEqual(group.assets[safe: goerliOffset]?.quantity, String(format: "%.04f", 0)) }.store(in: &cancellables) isLocked = false store.saveFilters( @@ -1139,6 +949,11 @@ import XCTest cancellables.removeAll() } + func testFilterAccounts() async { + await testFilterSort(with: false) + await testFilterSort(with: true) + } + /// Test `assetGroups` will be filtered by network when `networks` filter is has de-selected networks. func testFilterNetworks() async { Preferences.Wallet.showTestNetworks.value = true @@ -1241,8 +1056,9 @@ import XCTest /// Test `assetGroups` will be grouped by account when `GroupBy` filter is assigned `.account`. /// Additionally, test de-selecting/hiding one of the available accounts. - func testGroupByAccounts() async { + func testGroupByAccounts(with bitcoinTestnetEnabled: Bool) async { Preferences.Wallet.showTestNetworks.value = true + Preferences.Wallet.isBitcoinTestnetEnabled.value = bitcoinTestnetEnabled let store = setupStore() let assetGroupsExpectation = expectation(description: "update-assetGroups") XCTAssertTrue(store.assetGroups.isEmpty) // Initial state @@ -1256,8 +1072,11 @@ import XCTest XCTFail("Unexpected test result") return } - // grouping by .account; eth has 2 accounts, sol has 2 accounts, fil has 3 accounts, btc has 2 accounts(no testnet) - XCTAssertEqual(lastUpdatedAssetGroups.count, 9) + // grouping by .account; eth has 2 accounts, sol has 2 accounts, fil has 3 accounts, btc has 3 accounts (one tesnet) + let assetGroupNumber = bitcoinTestnetEnabled ? 10 : 9 + XCTAssertEqual(lastUpdatedAssetGroups.count, assetGroupNumber) + + var filAccount2Offset = bitcoinTestnetEnabled ? 9 : 8 guard let ethAccount1Group = lastUpdatedAssetGroups[safe: 0], let solAccount1Group = lastUpdatedAssetGroups[safe: 1], let filTestnetAccountGroup = lastUpdatedAssetGroups[safe: 2], @@ -1266,12 +1085,32 @@ import XCTest let solAccount2Group = lastUpdatedAssetGroups[safe: 5], let btcAccount1Group = lastUpdatedAssetGroups[safe: 6], let btcAccount2Group = lastUpdatedAssetGroups[safe: 7], - let filAccount2Group = lastUpdatedAssetGroups[safe: 8] + let filAccount2Group = lastUpdatedAssetGroups[safe: filAccount2Offset] else { XCTFail("Unexpected test result") return } + if bitcoinTestnetEnabled { + guard let btcTestnetAccountGroup = lastUpdatedAssetGroups[safe: 8] + else { + XCTFail("Unexpected test result") + return + } + + XCTAssertEqual(btcTestnetAccountGroup.groupType, .account(self.btcTestnetAccount)) + XCTAssertEqual(btcTestnetAccountGroup.assets.count, 1) + // BTC (value = $0) + XCTAssertEqual( + btcTestnetAccountGroup.assets[safe: 0]?.token.symbol, + BraveWallet.BlockchainToken.mockBTCToken.symbol + ) + XCTAssertEqual( + btcTestnetAccountGroup.assets[safe: 0]?.quantity, + String(format: "%.04f", 0) + ) + } + XCTAssertEqual(ethAccount1Group.groupType, .account(self.ethAccount1)) XCTAssertEqual(ethAccount1Group.assets.count, 3) // ETH Mainnet, USDC, ETH Goerli // ETH Mainnet (value ~= $2741.7510399999996) @@ -1412,12 +1251,12 @@ import XCTest isShowingNFTNetworkLogo: store.filters.isShowingNFTNetworkLogo, accounts: [ ethAccount1, ethAccount2, solAccount1, solAccount2, filAccount1, filAccount2, - filTestnetAccount, btcAccount1, btcAccount2, + filTestnetAccount, btcAccount1, btcAccount2, btcTestnetAccount, ].map { .init(isSelected: true, model: $0) }, networks: [ - ethNetwork, goerliNetwork, solNetwork, filMainnet, filTestnet, btcMainnet, + ethNetwork, goerliNetwork, solNetwork, filMainnet, filTestnet, btcMainnet, btcTestnet, ].map { .init(isSelected: true, model: $0) } @@ -1530,11 +1369,16 @@ import XCTest await fulfillment(of: [accountsExpectation], timeout: 1) } - /// Test `assetGroups` will be grouped by account when `GroupBy` filter is assigned `.account`. - /// Additionally, test de-selecting/hiding one of the available accounts. - func testGroupByAccountsWithBitcoinTestnet() async { + func testGroupByAccounts() async { + await testGroupByAccounts(with: false) + await testGroupByAccounts(with: true) + } + + /// Test `assetGroups` will be grouped by network when `GroupBy` filter is assigned `.network`. + /// Additionally, test de-selecting/hiding one of the available networks. + func testGroupByNetworks(with bitcoinTestnetEnabled: Bool) async { Preferences.Wallet.showTestNetworks.value = true - Preferences.Wallet.isBitcoinTestnetEnabled.value = true + Preferences.Wallet.isBitcoinTestnetEnabled.value = bitcoinTestnetEnabled let store = setupStore() let assetGroupsExpectation = expectation(description: "update-assetGroups") XCTAssertTrue(store.assetGroups.isEmpty) // Initial state @@ -1548,546 +1392,37 @@ import XCTest XCTFail("Unexpected test result") return } - // grouping by .account; eth has 2 accounts, sol has 2 accounts, fil has 3 accounts, btc has 3 accounts - XCTAssertEqual(lastUpdatedAssetGroups.count, 10) - guard let ethAccount1Group = lastUpdatedAssetGroups[safe: 0], - let solAccount1Group = lastUpdatedAssetGroups[safe: 1], - let filTestnetAccountGroup = lastUpdatedAssetGroups[safe: 2], - let filAccount1Group = lastUpdatedAssetGroups[safe: 3], - let ethAccount2Group = lastUpdatedAssetGroups[safe: 4], - let solAccount2Group = lastUpdatedAssetGroups[safe: 5], - let btcAccount1Group = lastUpdatedAssetGroups[safe: 6], - let btcAccount2Group = lastUpdatedAssetGroups[safe: 7], - let btcTestnetAccountGroup = lastUpdatedAssetGroups[safe: 8], - let filAccount2Group = lastUpdatedAssetGroups[safe: 9] + // grouping by .network; 1 for each of the 2 networks (Bitcoin testnet can be disabled) + // network groups order should be the same as the order of all networks in `Filters` + let assetGroupNumber = bitcoinTestnetEnabled ? 7 : 6 + XCTAssertEqual(lastUpdatedAssetGroups.count, assetGroupNumber) + guard let ethMainnetGroup = lastUpdatedAssetGroups[safe: 0], + let solMainnetGroup = lastUpdatedAssetGroups[safe: 1], + let filTestnetGroup = lastUpdatedAssetGroups[safe: 2], + let filMainnetGroup = lastUpdatedAssetGroups[safe: 3], + let btcMainnetGroup = lastUpdatedAssetGroups[safe: 4], + let ethGoerliGroup = lastUpdatedAssetGroups[safe: 5] else { XCTFail("Unexpected test result") return } - XCTAssertEqual(ethAccount1Group.groupType, .account(self.ethAccount1)) - XCTAssertEqual(ethAccount1Group.assets.count, 3) // ETH Mainnet, USDC, ETH Goerli - // ETH Mainnet (value ~= $2741.7510399999996) - XCTAssertEqual( - ethAccount1Group.assets[safe: 0]?.token.symbol, - BraveWallet.BlockchainToken.previewToken.symbol - ) - XCTAssertEqual( - ethAccount1Group.assets[safe: 0]?.quantity, - String(format: "%.04f", self.mockETHBalanceAccount1) - ) - // USDC (value = $0.03) - XCTAssertEqual( - ethAccount1Group.assets[safe: 1]?.token.symbol, - BraveWallet.BlockchainToken.mockUSDCToken.symbol - ) - XCTAssertEqual( - ethAccount1Group.assets[safe: 1]?.quantity, - String(format: "%.04f", self.mockUSDCBalanceAccount1) - ) - // ETH Goerli (value = $0) - XCTAssertEqual( - ethAccount1Group.assets[safe: 2]?.token.symbol, - self.goerliNetwork.nativeToken.symbol - ) - XCTAssertEqual(ethAccount1Group.assets[safe: 2]?.quantity, String(format: "%.04f", 0)) - - XCTAssertEqual(solAccount1Group.groupType, .account(self.solAccount1)) - XCTAssertEqual(solAccount1Group.assets.count, 1) - // SOL (value = $775.3) - XCTAssertEqual( - solAccount1Group.assets[safe: 0]?.token.symbol, - BraveWallet.BlockchainToken.mockSolToken.symbol - ) - XCTAssertEqual( - solAccount1Group.assets[safe: 0]?.quantity, - String(format: "%.04f", self.mockSOLBalance) - ) - - XCTAssertEqual(filTestnetAccountGroup.groupType, .account(self.filTestnetAccount)) - XCTAssertEqual(filTestnetAccountGroup.assets.count, 1) - // FIL (value = $400) - XCTAssertEqual( - filTestnetAccountGroup.assets[safe: 0]?.token.symbol, - BraveWallet.BlockchainToken.mockFilToken.symbol - ) - XCTAssertEqual( - filTestnetAccountGroup.assets[safe: 0]?.quantity, - String(format: "%.04f", self.mockFILBalanceTestnet) - ) - - XCTAssertEqual(filAccount1Group.groupType, .account(self.filAccount1)) - XCTAssertEqual(filAccount1Group.assets.count, 1) - // FIL (value = $4) - XCTAssertEqual( - filAccount1Group.assets[safe: 0]?.token.symbol, - BraveWallet.BlockchainToken.mockFilToken.symbol - ) - XCTAssertEqual( - filAccount1Group.assets[safe: 0]?.quantity, - String(format: "%.04f", self.mockFILBalanceAccount1) - ) - - XCTAssertEqual(ethAccount2Group.groupType, .account(self.ethAccount2)) - XCTAssertEqual(ethAccount2Group.assets.count, 3) // ETH Mainnet, USDC, ETH Goerli - // USDC (value $0.01) - XCTAssertEqual( - ethAccount2Group.assets[safe: 0]?.token.symbol, - BraveWallet.BlockchainToken.mockUSDCToken.symbol - ) - XCTAssertEqual( - ethAccount2Group.assets[safe: 0]?.quantity, - String(format: "%.04f", self.mockUSDCBalanceAccount2) - ) - // ETH Mainnet (value = $0) - XCTAssertEqual( - ethAccount2Group.assets[safe: 1]?.token.symbol, - BraveWallet.BlockchainToken.previewToken.symbol - ) - XCTAssertEqual(ethAccount2Group.assets[safe: 1]?.quantity, String(format: "%.04f", 0)) - // ETH Goerli (value = $0) - XCTAssertEqual( - ethAccount2Group.assets[safe: 2]?.token.symbol, - self.goerliNetwork.nativeToken.symbol - ) - XCTAssertEqual(ethAccount2Group.assets[safe: 2]?.quantity, String(format: "%.04f", 0)) - - XCTAssertEqual(solAccount2Group.groupType, .account(self.solAccount2)) - XCTAssertEqual(solAccount2Group.assets.count, 1) - // SOL (value = $0) - XCTAssertEqual( - solAccount2Group.assets[safe: 0]?.token.symbol, - BraveWallet.BlockchainToken.mockSolToken.symbol - ) - XCTAssertEqual(solAccount2Group.assets[safe: 0]?.quantity, String(format: "%.04f", 0)) - - XCTAssertEqual(btcAccount1Group.assets[safe: 0]?.quantity, String(format: "%.04f", 0)) - XCTAssertEqual(btcAccount1Group.groupType, .account(self.btcAccount1)) - XCTAssertEqual(btcAccount1Group.assets.count, 1) - // BTC (value = $0) - XCTAssertEqual( - btcAccount1Group.assets[safe: 0]?.token.symbol, - BraveWallet.BlockchainToken.mockBTCToken.symbol - ) - - XCTAssertEqual(btcAccount2Group.assets[safe: 0]?.quantity, String(format: "%.04f", 0)) - XCTAssertEqual(btcAccount2Group.groupType, .account(self.btcAccount2)) - XCTAssertEqual(btcAccount2Group.assets.count, 1) - // BTC (value = $0) - XCTAssertEqual( - btcAccount2Group.assets[safe: 0]?.token.symbol, - BraveWallet.BlockchainToken.mockBTCToken.symbol - ) - XCTAssertEqual(btcAccount2Group.assets[safe: 0]?.quantity, String(format: "%.04f", 0)) - - XCTAssertEqual(btcTestnetAccountGroup.groupType, .account(self.btcTestnetAccount)) - XCTAssertEqual(btcTestnetAccountGroup.assets.count, 1) - // BTC (value = $0) - XCTAssertEqual( - btcTestnetAccountGroup.assets[safe: 0]?.token.symbol, - BraveWallet.BlockchainToken.mockBTCToken.symbol - ) - XCTAssertEqual(btcTestnetAccountGroup.assets[safe: 0]?.quantity, String(format: "%.04f", 0)) - - XCTAssertEqual(filAccount2Group.groupType, .account(self.filAccount2)) - XCTAssertEqual(filAccount2Group.assets.count, 1) - // FIL (value = $0) - XCTAssertEqual( - filAccount2Group.assets[safe: 0]?.token.symbol, - BraveWallet.BlockchainToken.mockFilToken.symbol - ) - - // Verify NFTs not used in Portfolio #7945 - let noAssetsAreNFTs = lastUpdatedAssetGroups.flatMap(\.assets).allSatisfy({ - !($0.token.isNft || $0.token.isErc721) - }) - XCTAssertTrue(noAssetsAreNFTs) - } - .store(in: &cancellables) - isLocked = false - store.saveFilters( - .init( - groupBy: .accounts, - sortOrder: store.filters.sortOrder, - isHidingSmallBalances: store.filters.isHidingSmallBalances, - isHidingUnownedNFTs: store.filters.isHidingUnownedNFTs, - isShowingNFTNetworkLogo: store.filters.isShowingNFTNetworkLogo, - accounts: [ - ethAccount1, ethAccount2, solAccount1, solAccount2, filAccount1, filAccount2, - filTestnetAccount, btcAccount1, btcAccount2, btcTestnetAccount, - ].map { - .init(isSelected: true, model: $0) - }, - networks: [ - ethNetwork, goerliNetwork, solNetwork, filMainnet, filTestnet, btcMainnet, btcTestnet, - ].map { - .init(isSelected: true, model: $0) - } - ) - ) - await fulfillment(of: [assetGroupsExpectation], timeout: 1) - cancellables.removeAll() - // test hiding an account & hiding groups with small balances - let accountsExpectation = expectation(description: "update-accounts") - store.$assetGroups - .dropFirst() - .collect(2) - .sink { assetGroups in - defer { accountsExpectation.fulfill() } - XCTAssertEqual(assetGroups.count, 2) // empty (no balance, price, history), populated - guard let lastUpdatedAssetGroups = assetGroups.last else { - XCTFail("Unexpected test result") - return - } - // grouping by .account; 1 for each of the 2 accounts selected accounts - XCTAssertEqual(lastUpdatedAssetGroups.count, 4) - guard let ethAccount1Group = lastUpdatedAssetGroups[safe: 0], - let solAccountGroup = lastUpdatedAssetGroups[safe: 1], - let filTestnetAccountGroup = lastUpdatedAssetGroups[safe: 2], - let filAccount1Group = lastUpdatedAssetGroups[safe: 3] - else { - XCTFail("Unexpected test result") - return - } - XCTAssertEqual(ethAccount1Group.groupType, .account(self.ethAccount1)) - // ETH Mainnet (USDC, ETH Goerli hidden for small balance) - XCTAssertEqual(ethAccount1Group.assets.count, 1) - // ETH Mainnet (value ~= $2741.7510399999996) - XCTAssertEqual( - ethAccount1Group.assets[safe: 0]?.token.symbol, - BraveWallet.BlockchainToken.previewToken.symbol - ) - XCTAssertEqual( - ethAccount1Group.assets[safe: 0]?.quantity, - String(format: "%.04f", self.mockETHBalanceAccount1) - ) - // USDC (value $0.03) - XCTAssertNil(ethAccount1Group.assets[safe: 1]) - - XCTAssertEqual(solAccountGroup.groupType, .account(.mockSolAccount)) - XCTAssertEqual(solAccountGroup.assets.count, 1) - // SOL (value = $775.3) - XCTAssertEqual( - solAccountGroup.assets[safe: 0]?.token.symbol, - BraveWallet.BlockchainToken.mockSolToken.symbol - ) - XCTAssertEqual( - solAccountGroup.assets[safe: 0]?.quantity, - String(format: "%.04f", self.mockSOLBalance) - ) - - XCTAssertEqual(filTestnetAccountGroup.groupType, .account(self.filTestnetAccount)) - XCTAssertEqual(filTestnetAccountGroup.assets.count, 1) - // FIL (value = $400) - XCTAssertEqual( - filTestnetAccountGroup.assets[safe: 0]?.token.symbol, - BraveWallet.BlockchainToken.mockFilToken.symbol - ) - XCTAssertEqual( - filTestnetAccountGroup.assets[safe: 0]?.quantity, - String(format: "%.04f", self.mockFILBalanceTestnet) - ) - - XCTAssertEqual(filAccount1Group.groupType, .account(self.filAccount1)) - XCTAssertEqual(filAccount1Group.assets.count, 1) - // FIL (value = $4) - XCTAssertEqual( - filAccount1Group.assets[safe: 0]?.token.symbol, - BraveWallet.BlockchainToken.mockFilToken.symbol - ) - XCTAssertEqual( - filAccount1Group.assets[safe: 0]?.quantity, - String(format: "%.04f", self.mockFILBalanceAccount1) - ) - - // ethAccount2 hidden as it's de-selected, solAccount2 hidden for small balance, filAccount2 hidden for small balance, btcAccount1/btcAccount2/btcTestAccount hidden for small balance - XCTAssertNil(lastUpdatedAssetGroups[safe: 4]) - XCTAssertNil(lastUpdatedAssetGroups[safe: 5]) - XCTAssertNil(lastUpdatedAssetGroups[safe: 6]) - XCTAssertNil(lastUpdatedAssetGroups[safe: 7]) - XCTAssertNil(lastUpdatedAssetGroups[safe: 8]) - XCTAssertNil(lastUpdatedAssetGroups[safe: 9]) - } - .store(in: &cancellables) - store.saveFilters( - .init( - groupBy: .accounts, - sortOrder: store.filters.sortOrder, - isHidingSmallBalances: true, - isHidingUnownedNFTs: store.filters.isHidingUnownedNFTs, - isShowingNFTNetworkLogo: store.filters.isShowingNFTNetworkLogo, - accounts: [ - ethAccount1, ethAccount2, solAccount1, solAccount2, filAccount1, filAccount2, - filTestnetAccount, btcAccount1, btcAccount2, btcTestnetAccount, - ].map { - .init(isSelected: $0 != ethAccount2, model: $0) - }, - networks: [ - ethNetwork, goerliNetwork, solNetwork, filMainnet, filTestnet, btcMainnet, btcTestnet, - ].map { - .init(isSelected: true, model: $0) - } - ) - ) - await fulfillment(of: [accountsExpectation], timeout: 1) - } - - /// Test `assetGroups` will be grouped by network when `GroupBy` filter is assigned `.network`. - /// Additionally, test de-selecting/hiding one of the available networks. - func testGroupByNetworks() async { - Preferences.Wallet.showTestNetworks.value = true - let store = setupStore() - let assetGroupsExpectation = expectation(description: "update-assetGroups") - XCTAssertTrue(store.assetGroups.isEmpty) // Initial state - store.$assetGroups - .dropFirst() - .collect(2) - .sink { assetGroups in - defer { assetGroupsExpectation.fulfill() } - XCTAssertEqual(assetGroups.count, 2) // empty (no balance, price, history), populated - guard let lastUpdatedAssetGroups = assetGroups.last else { - XCTFail("Unexpected test result") - return - } - // grouping by .network; 1 for each of the 2 networks. Bitcoin testnet is disabled - // network groups order should be the same as the order of all networks in `Filters` - XCTAssertEqual(lastUpdatedAssetGroups.count, 6) - guard let ethMainnetGroup = lastUpdatedAssetGroups[safe: 0], - let solMainnetGroup = lastUpdatedAssetGroups[safe: 1], - let filTestnetGroup = lastUpdatedAssetGroups[safe: 2], - let filMainnetGroup = lastUpdatedAssetGroups[safe: 3], - let btcMainnetGroup = lastUpdatedAssetGroups[safe: 4], - let ethGoerliGroup = lastUpdatedAssetGroups[safe: 5] - else { - XCTFail("Unexpected test result") - return - } - XCTAssertEqual(ethMainnetGroup.groupType, .network(.mockMainnet)) - XCTAssertEqual(ethMainnetGroup.assets.count, 2) // ETH Mainnet, USDC - // ETH (value ~= $2741.7510399999996) - XCTAssertEqual( - ethMainnetGroup.assets[safe: 0]?.token.symbol, - BraveWallet.BlockchainToken.previewToken.symbol - ) - XCTAssertEqual( - ethMainnetGroup.assets[safe: 0]?.quantity, - String(format: "%.04f", self.mockETHBalanceAccount1) - ) - // USDC (value = $0.04) - XCTAssertEqual( - ethMainnetGroup.assets[safe: 1]?.token.symbol, - BraveWallet.BlockchainToken.mockUSDCToken.symbol - ) - XCTAssertEqual( - ethMainnetGroup.assets[safe: 1]?.quantity, - String(format: "%.04f", self.mockUSDCBalanceAccount1 + self.mockUSDCBalanceAccount2) - ) - - XCTAssertEqual(solMainnetGroup.groupType, .network(.mockSolana)) - XCTAssertEqual(solMainnetGroup.assets.count, 1) // SOL - // SOL (value = $775.3) - XCTAssertEqual( - solMainnetGroup.assets[safe: 0]?.token.symbol, - BraveWallet.BlockchainToken.mockSolToken.symbol - ) - XCTAssertEqual( - solMainnetGroup.assets[safe: 0]?.quantity, - String(format: "%.04f", self.mockSOLBalance) - ) - - XCTAssertEqual(filTestnetGroup.groupType, .network(.mockFilecoinTestnet)) - XCTAssertEqual(filTestnetGroup.assets.count, 1) // FIL on testnet - // FIL (value = $400) - XCTAssertEqual( - filTestnetGroup.assets[safe: 0]?.token.symbol, - BraveWallet.BlockchainToken.mockFilToken.symbol - ) - XCTAssertEqual( - filTestnetGroup.assets[safe: 0]?.quantity, - String(format: "%.04f", self.mockFILBalanceTestnet) - ) - - XCTAssertEqual(filMainnetGroup.groupType, .network(.mockFilecoinMainnet)) - XCTAssertEqual(filMainnetGroup.assets.count, 1) // FIL on mainnet - // FIL (value = $4) - XCTAssertEqual( - filMainnetGroup.assets[safe: 0]?.token.symbol, - BraveWallet.BlockchainToken.mockFilToken.symbol - ) - XCTAssertEqual( - filMainnetGroup.assets[safe: 0]?.quantity, - String(format: "%.04f", self.mockFILBalanceAccount1) - ) - - XCTAssertEqual(ethGoerliGroup.groupType, .network(.mockGoerli)) - XCTAssertEqual(ethGoerliGroup.assets.count, 1) // ETH Goerli - // ETH Goerli (value = $0) - XCTAssertEqual( - ethGoerliGroup.assets[safe: 0]?.token.symbol, - BraveWallet.BlockchainToken.previewToken.symbol - ) - XCTAssertEqual(ethGoerliGroup.assets[safe: 0]?.quantity, String(format: "%.04f", 0)) - - XCTAssertEqual(btcMainnetGroup.groupType, .network(self.btcMainnet)) - XCTAssertEqual(btcMainnetGroup.assets.count, 1) - // BTC mainnet (value = $0) - XCTAssertEqual( - btcMainnetGroup.assets[safe: 0]?.token.symbol, - BraveWallet.BlockchainToken.mockBTCToken.symbol - ) - XCTAssertEqual(btcMainnetGroup.assets[safe: 0]?.quantity, String(format: "%.04f", 0)) - - // Verify NFTs not used in Portfolio #7945 - let noAssetsAreNFTs = lastUpdatedAssetGroups.flatMap(\.assets).allSatisfy({ - !($0.token.isNft || $0.token.isErc721) - }) - XCTAssertTrue(noAssetsAreNFTs) - } - .store(in: &cancellables) - isLocked = false - store.saveFilters( - .init( - groupBy: .networks, - sortOrder: store.filters.sortOrder, - isHidingSmallBalances: store.filters.isHidingSmallBalances, - isHidingUnownedNFTs: store.filters.isHidingUnownedNFTs, - isShowingNFTNetworkLogo: store.filters.isShowingNFTNetworkLogo, - accounts: [ - ethAccount1, ethAccount2, solAccount1, solAccount2, filAccount1, filAccount2, - filTestnetAccount, btcAccount1, btcAccount2, - ].map { - .init(isSelected: true, model: $0) - }, - networks: [ - ethNetwork, goerliNetwork, solNetwork, filMainnet, filTestnet, btcMainnet, - ].map { - .init(isSelected: true, model: $0) - } - ) - ) - await fulfillment(of: [assetGroupsExpectation], timeout: 1) - cancellables.removeAll() - // test hiding a network & hiding groups with small balances - let networksExpectation = expectation(description: "update-networks") - store.$assetGroups - .dropFirst() - .collect(2) - .sink { assetGroups in - defer { networksExpectation.fulfill() } - XCTAssertEqual(assetGroups.count, 2) // empty (no balance, price, history), populated - guard let lastUpdatedAssetGroups = assetGroups.last else { - XCTFail("Unexpected test result") - return - } - // grouping by .network; 1 group for Solana network, 1 group for Filecoin mainnet, 1 group for Filecoin testnet - XCTAssertEqual(lastUpdatedAssetGroups.count, 3) - guard let solMainnetGroup = lastUpdatedAssetGroups[safe: 0], - let filTestnetGroup = lastUpdatedAssetGroups[safe: 1], - let filMainnetGroup = lastUpdatedAssetGroups[safe: 2] - else { - XCTFail("Unexpected test result") - return - } - XCTAssertEqual(solMainnetGroup.groupType, .network(.mockSolana)) - XCTAssertEqual(solMainnetGroup.assets.count, 1) // SOL - // SOL (value = $775.3) - XCTAssertEqual( - solMainnetGroup.assets[safe: 0]?.token.symbol, - BraveWallet.BlockchainToken.mockSolToken.symbol - ) - XCTAssertEqual( - solMainnetGroup.assets[safe: 0]?.quantity, - String(format: "%.04f", self.mockSOLBalance) - ) - - XCTAssertEqual(filTestnetGroup.groupType, .network(.mockFilecoinTestnet)) - XCTAssertEqual(filTestnetGroup.assets.count, 1) // FIL - // FIL (value = $400) - XCTAssertEqual( - filTestnetGroup.assets[safe: 0]?.token.symbol, - BraveWallet.BlockchainToken.mockFilToken.symbol - ) - XCTAssertEqual( - filTestnetGroup.assets[safe: 0]?.quantity, - String(format: "%.04f", self.mockFILBalanceTestnet) - ) - - XCTAssertEqual(filMainnetGroup.groupType, .network(.mockFilecoinMainnet)) - XCTAssertEqual(filMainnetGroup.assets.count, 1) // FIL - // FIL (value = $4) - XCTAssertEqual( - filMainnetGroup.assets[safe: 0]?.token.symbol, - BraveWallet.BlockchainToken.mockFilToken.symbol - ) - XCTAssertEqual( - filMainnetGroup.assets[safe: 0]?.quantity, - String(format: "%.04f", self.mockFILBalanceAccount1) - ) - // eth mainnet group hidden as network de-selected - XCTAssertNil(lastUpdatedAssetGroups[safe: 3]) - // goerli network group hidden for small balance - XCTAssertNil(lastUpdatedAssetGroups[safe: 4]) - // Bitcoin network group hidden for small balance - XCTAssertNil(lastUpdatedAssetGroups[safe: 5]) - } - .store(in: &cancellables) - store.saveFilters( - .init( - groupBy: .networks, - sortOrder: store.filters.sortOrder, - isHidingSmallBalances: true, - isHidingUnownedNFTs: store.filters.isHidingUnownedNFTs, - isShowingNFTNetworkLogo: store.filters.isShowingNFTNetworkLogo, - accounts: [ - ethAccount1, ethAccount2, solAccount1, solAccount2, filAccount1, filAccount2, - filTestnetAccount, btcAccount1, btcAccount2, - ].map { - .init(isSelected: true, model: $0) - }, - networks: [ - ethNetwork, goerliNetwork, solNetwork, filMainnet, filTestnet, btcMainnet, - ].map { - // hide ethNetwork - .init(isSelected: $0 != ethNetwork, model: $0) + if bitcoinTestnetEnabled { + guard let btcTestnetGroup = lastUpdatedAssetGroups[safe: 6] + else { + XCTFail("Unexpected test result") + return + } + XCTAssertEqual(btcTestnetGroup.groupType, .network(self.btcTestnet)) + XCTAssertEqual(btcTestnetGroup.assets.count, 1) + // BTC testnet (value = $0) + XCTAssertEqual( + btcTestnetGroup.assets[safe: 0]?.token.symbol, + BraveWallet.BlockchainToken.mockBTCToken.symbol + ) + XCTAssertEqual(btcTestnetGroup.assets[safe: 0]?.quantity, String(format: "%.04f", 0)) } - ) - ) - await fulfillment(of: [networksExpectation], timeout: 1) - cancellables.removeAll() - } - /// Test `assetGroups` will be grouped by network when `GroupBy` filter is assigned `.network`. - /// Additionally, test de-selecting/hiding one of the available networks. - func testGroupByNetworksWithBitcoinTestnet() async { - Preferences.Wallet.showTestNetworks.value = true - Preferences.Wallet.isBitcoinTestnetEnabled.value = true - let store = setupStore() - let assetGroupsExpectation = expectation(description: "update-assetGroups") - XCTAssertTrue(store.assetGroups.isEmpty) // Initial state - store.$assetGroups - .dropFirst() - .collect(2) - .sink { assetGroups in - defer { assetGroupsExpectation.fulfill() } - XCTAssertEqual(assetGroups.count, 2) // empty (no balance, price, history), populated - guard let lastUpdatedAssetGroups = assetGroups.last else { - XCTFail("Unexpected test result") - return - } - // grouping by .network; 1 for each of the 2 networks - // network groups order should be the same as the order of all networks in `Filters` - XCTAssertEqual(lastUpdatedAssetGroups.count, 7) - guard let ethMainnetGroup = lastUpdatedAssetGroups[safe: 0], - let solMainnetGroup = lastUpdatedAssetGroups[safe: 1], - let filTestnetGroup = lastUpdatedAssetGroups[safe: 2], - let filMainnetGroup = lastUpdatedAssetGroups[safe: 3], - let btcMainnetGroup = lastUpdatedAssetGroups[safe: 4], - let ethGoerliGroup = lastUpdatedAssetGroups[safe: 5], - let btcTestnetGroup = lastUpdatedAssetGroups[safe: 6] - else { - XCTFail("Unexpected test result") - return - } XCTAssertEqual(ethMainnetGroup.groupType, .network(.mockMainnet)) XCTAssertEqual(ethMainnetGroup.assets.count, 2) // ETH Mainnet, USDC // ETH (value ~= $2741.7510399999996) @@ -2163,15 +1498,6 @@ import XCTest ) XCTAssertEqual(btcMainnetGroup.assets[safe: 0]?.quantity, String(format: "%.04f", 0)) - XCTAssertEqual(btcTestnetGroup.groupType, .network(self.btcTestnet)) - XCTAssertEqual(btcTestnetGroup.assets.count, 1) - // BTC testnet (value = $0) - XCTAssertEqual( - btcTestnetGroup.assets[safe: 0]?.token.symbol, - BraveWallet.BlockchainToken.mockBTCToken.symbol - ) - XCTAssertEqual(btcTestnetGroup.assets[safe: 0]?.quantity, String(format: "%.04f", 0)) - // Verify NFTs not used in Portfolio #7945 let noAssetsAreNFTs = lastUpdatedAssetGroups.flatMap(\.assets).allSatisfy({ !($0.token.isNft || $0.token.isErc721) @@ -2291,6 +1617,11 @@ import XCTest await fulfillment(of: [networksExpectation], timeout: 1) cancellables.removeAll() } + + func testGroupByNetworks() async { + await testGroupByNetworks(with: false) + await testGroupByNetworks(with: true) + } } extension BraveWallet.BlockchainToken { From ea4c7d698a1804b088d06397f6f414422cb415f8 Mon Sep 17 00:00:00 2001 From: Nuo Xu Date: Mon, 8 Apr 2024 23:38:43 -0400 Subject: [PATCH 3/4] fix unit tests --- .../BraveWalletTests/AccountsStoreTests.swift | 9 ++-- .../PortfolioStoreTests.swift | 49 ++++++++++++------- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/ios/brave-ios/Tests/BraveWalletTests/AccountsStoreTests.swift b/ios/brave-ios/Tests/BraveWalletTests/AccountsStoreTests.swift index 52acad8ea83a..5ca69406b4e9 100644 --- a/ios/brave-ios/Tests/BraveWalletTests/AccountsStoreTests.swift +++ b/ios/brave-ios/Tests/BraveWalletTests/AccountsStoreTests.swift @@ -102,7 +102,7 @@ import XCTest let formatter = WeiFormatter(decimalFormatStyle: .decimals(precision: 18)) - func testUpdate(with bitcoinTestnetEnabled: Bool) async { + func updateHelper(bitcoinTestnetEnabled: Bool) async { Preferences.Wallet.showTestNetworks.value = true Preferences.Wallet.isBitcoinTestnetEnabled.value = bitcoinTestnetEnabled let ethBalanceWei = @@ -346,7 +346,10 @@ import XCTest } func testUpdate() async { - await testUpdate(with: false) - await testUpdate(with: true) + await updateHelper(bitcoinTestnetEnabled: false) + } + + func testUpdateBitcoinTestnet() async { + await updateHelper(bitcoinTestnetEnabled: true) } } diff --git a/ios/brave-ios/Tests/BraveWalletTests/PortfolioStoreTests.swift b/ios/brave-ios/Tests/BraveWalletTests/PortfolioStoreTests.swift index 0ced3737dad7..79a194a4f20f 100644 --- a/ios/brave-ios/Tests/BraveWalletTests/PortfolioStoreTests.swift +++ b/ios/brave-ios/Tests/BraveWalletTests/PortfolioStoreTests.swift @@ -618,7 +618,7 @@ import XCTest } /// Test `assetGroups` will be sorted to from smallest to highest fiat value when `sortOrder` filter is `valueAsc`. - func testFilterSort(with bitcoinTestnetEnabled: Bool) async { + func filterSortHelper(bitcoinTestnetEnabled: Bool) async { Preferences.Wallet.showTestNetworks.value = true Preferences.Wallet.isBitcoinTestnetEnabled.value = bitcoinTestnetEnabled let store = setupStore() @@ -743,8 +743,11 @@ import XCTest } func testFilterSort() async { - await testFilterSort(with: false) - await testFilterSort(with: true) + await filterSortHelper(bitcoinTestnetEnabled: false) + } + + func testFilterSortBitcoinTestnet() async { + await filterSortHelper(bitcoinTestnetEnabled: true) } /// Test `assetGroups` will be filtered to remove small balances when `hideSmallBalances` filter is true. @@ -835,9 +838,10 @@ import XCTest } /// Test `assetGroups` will be filtered by accounts when `accounts` filter is has de-selected accounts. - func testFilterAccounts(with bitcoinTesnetEnabled: Bool) async { + func filterAccountsHelper(bitcoinTestnetEnabled: Bool) async { Preferences.Wallet.showTestNetworks.value = true - Preferences.Wallet.isBitcoinTestnetEnabled.value = bitcoinTesnetEnabled + // test without bitcoin testnet enabled + Preferences.Wallet.isBitcoinTestnetEnabled.value = bitcoinTestnetEnabled let store = setupStore() let accountsExpectation = expectation(description: "update-accounts") store.$assetGroups @@ -856,8 +860,8 @@ import XCTest return } // ETH on Ethereum mainnet, SOL on Solana mainnet, USDC on Ethereum mainnet, ETH on Goerli, FIL on mainnet and testnet, BTC on mainnet and testnet - let assetGroupNumber = bitcoinTesnetEnabled ? 8 : 7 - XCTAssertEqual(group.assets.count, bitcoinTesnetEnabled) + let assetGroupNumber: Int = bitcoinTestnetEnabled ? 8 : 7 + XCTAssertEqual(group.assets.count, assetGroupNumber) // ETH Mainnet (value ~= $2741.7510399999996) XCTAssertEqual( group.assets[safe: 0]?.token.symbol, @@ -908,14 +912,14 @@ import XCTest group.assets[safe: 5]?.token.symbol, self.btcMainnet.nativeToken.symbol ) - var goerliOffset = 6 - if bitcoinTesnetEnabled { + var goerliOffset: Int = 6 + if bitcoinTestnetEnabled { // BTC testnet (value = $0) XCTAssertEqual( group.assets[safe: 6]?.token.symbol, self.btcMainnet.nativeToken.symbol ) - var goerliOffset = 7 + goerliOffset = 7 } // ETH Goerli (value = $0) XCTAssertEqual( @@ -950,8 +954,11 @@ import XCTest } func testFilterAccounts() async { - await testFilterSort(with: false) - await testFilterSort(with: true) + await filterAccountsHelper(bitcoinTestnetEnabled: false) + } + + func testFilterAccountsWithBitcoinTestnet() async { + await filterAccountsHelper(bitcoinTestnetEnabled: true) } /// Test `assetGroups` will be filtered by network when `networks` filter is has de-selected networks. @@ -1056,7 +1063,7 @@ import XCTest /// Test `assetGroups` will be grouped by account when `GroupBy` filter is assigned `.account`. /// Additionally, test de-selecting/hiding one of the available accounts. - func testGroupByAccounts(with bitcoinTestnetEnabled: Bool) async { + func groupByAccountsHelper(bitcoinTestnetEnabled: Bool) async { Preferences.Wallet.showTestNetworks.value = true Preferences.Wallet.isBitcoinTestnetEnabled.value = bitcoinTestnetEnabled let store = setupStore() @@ -1370,13 +1377,16 @@ import XCTest } func testGroupByAccounts() async { - await testGroupByAccounts(with: false) - await testGroupByAccounts(with: true) + await groupByAccountsHelper(bitcoinTestnetEnabled: false) + } + + func testGroupByAccountsBitcoinTestnet() async { + await groupByAccountsHelper(bitcoinTestnetEnabled: true) } /// Test `assetGroups` will be grouped by network when `GroupBy` filter is assigned `.network`. /// Additionally, test de-selecting/hiding one of the available networks. - func testGroupByNetworks(with bitcoinTestnetEnabled: Bool) async { + func groupByNetworksHelper(bitcoinTestnetEnabled: Bool) async { Preferences.Wallet.showTestNetworks.value = true Preferences.Wallet.isBitcoinTestnetEnabled.value = bitcoinTestnetEnabled let store = setupStore() @@ -1619,8 +1629,11 @@ import XCTest } func testGroupByNetworks() async { - await testGroupByNetworks(with: false) - await testGroupByNetworks(with: true) + await groupByNetworksHelper(bitcoinTestnetEnabled: false) + } + + func testGroupByNetworkBitcoinTestnet() async { + await groupByNetworksHelper(bitcoinTestnetEnabled: true) } } From 8193cb9c02fb3fbabeac81084bb564414ca2c7e6 Mon Sep 17 00:00:00 2001 From: Nuo Xu Date: Tue, 9 Apr 2024 17:31:55 -0400 Subject: [PATCH 4/4] address review comments --- .../Settings/Debug/Brave Wallet/BraveWalletDebugMenu.swift | 2 ++ .../Brave/Frontend/Settings/SettingsViewController.swift | 3 --- .../Sources/BraveWallet/Crypto/Stores/AccountsStore.swift | 4 ++-- .../Sources/BraveWallet/Crypto/Stores/NFTStore.swift | 2 +- .../Sources/BraveWallet/Crypto/Stores/PortfolioStore.swift | 2 +- .../BraveWallet/Extensions/KeyringServiceExtensions.swift | 2 +- .../Sources/BraveWallet/WalletUserAssetManager.swift | 2 +- 7 files changed, 8 insertions(+), 9 deletions(-) diff --git a/ios/brave-ios/Sources/Brave/Frontend/Settings/Debug/Brave Wallet/BraveWalletDebugMenu.swift b/ios/brave-ios/Sources/Brave/Frontend/Settings/Debug/Brave Wallet/BraveWalletDebugMenu.swift index 9a24ba6bdc0b..bc51d5065cc4 100644 --- a/ios/brave-ios/Sources/Brave/Frontend/Settings/Debug/Brave Wallet/BraveWalletDebugMenu.swift +++ b/ios/brave-ios/Sources/Brave/Frontend/Settings/Debug/Brave Wallet/BraveWalletDebugMenu.swift @@ -20,5 +20,7 @@ struct BraveWalletDebugMenu: View { } } .listBackgroundColor(Color(UIColor.braveGroupedBackground)) + .navigationTitle("Brave Wallet Debug") + .navigationBarTitleDisplayMode(.inline) } } diff --git a/ios/brave-ios/Sources/Brave/Frontend/Settings/SettingsViewController.swift b/ios/brave-ios/Sources/Brave/Frontend/Settings/SettingsViewController.swift index 441c771e1b19..d544089cb073 100644 --- a/ios/brave-ios/Sources/Brave/Frontend/Settings/SettingsViewController.swift +++ b/ios/brave-ios/Sources/Brave/Frontend/Settings/SettingsViewController.swift @@ -98,9 +98,6 @@ class SettingsViewController: TableViewController { self.cryptoStore = cryptoStore self.ipfsAPI = braveCore.ipfsAPI - self.keyringStore?.setupObservers() - self.cryptoStore?.setupObservers() - super.init(style: .insetGrouped) UIImageView.appearance(whenContainedInInstancesOf: [SettingsViewController.self]).tintColor = diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountsStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountsStore.swift index c4552ab8e61a..ae6d66bfd8c6 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountsStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/AccountsStore.swift @@ -108,7 +108,7 @@ class AccountsStore: ObservableObject, WalletObserverStore { } let tokens = allTokensPerNetwork.flatMap(\.tokens) - var allAccounts = await keyringService.allAccounts(checkBTCTestnet: true) + var allAccounts = await keyringService.allAccountInfos() var accountDetails = buildAccountDetails(accounts: allAccounts, tokens: tokens) self.primaryAccounts = accountDetails @@ -123,7 +123,7 @@ class AccountsStore: ObservableObject, WalletObserverStore { ) // if new accounts added while balances were being fetched. - allAccounts = await keyringService.allAccounts(checkBTCTestnet: true) + allAccounts = await keyringService.allAccountInfos() accountDetails = buildAccountDetails(accounts: allAccounts, tokens: tokens) self.primaryAccounts = accountDetails diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NFTStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NFTStore.swift index a1bec1043394..03c1eeef3e11 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NFTStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/NFTStore.swift @@ -293,7 +293,7 @@ public class NFTStore: ObservableObject, WalletObserverStore { let isLocked = await keyringService.isLocked() guard !isLocked else { return } // `update() will be called after unlock` - self.allAccounts = await keyringService.allAccounts(checkBTCTestnet: true) + self.allAccounts = await keyringService.allAccountInfos() .filter { account in WalletConstants.supportedCoinTypes().contains(account.coin) } diff --git a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/PortfolioStore.swift b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/PortfolioStore.swift index 8e21f4f02397..6bab7848da0f 100644 --- a/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/PortfolioStore.swift +++ b/ios/brave-ios/Sources/BraveWallet/Crypto/Stores/PortfolioStore.swift @@ -437,7 +437,7 @@ public class PortfolioStore: ObservableObject, WalletObserverStore { guard !isLocked else { return } // `update() will be called after unlock` self.isLoadingBalances = true - self.allAccounts = await keyringService.allAccounts(checkBTCTestnet: true) + self.allAccounts = await keyringService.allAccountInfos() .filter { account in WalletConstants.supportedCoinTypes().contains(account.coin) } diff --git a/ios/brave-ios/Sources/BraveWallet/Extensions/KeyringServiceExtensions.swift b/ios/brave-ios/Sources/BraveWallet/Extensions/KeyringServiceExtensions.swift index 47b7152e9c7b..b4018a94e045 100644 --- a/ios/brave-ios/Sources/BraveWallet/Extensions/KeyringServiceExtensions.swift +++ b/ios/brave-ios/Sources/BraveWallet/Extensions/KeyringServiceExtensions.swift @@ -23,7 +23,7 @@ extension BraveWalletKeyringService { /// Return a list of all accounts with checking if Bitcoin testnet is enabled /// The list of account will not include Bitcoin Testnet Accounts if Bitcoin testnet is disabled. - func allAccounts(checkBTCTestnet: Bool) async -> [BraveWallet.AccountInfo] { + func allAccountInfos(checkBTCTestnet: Bool = true) async -> [BraveWallet.AccountInfo] { var accounts = await self.allAccounts().accounts if checkBTCTestnet, !Preferences.Wallet.isBitcoinTestnetEnabled.value { accounts = accounts.filter({ $0.keyringId != BraveWallet.KeyringId.bitcoin84Testnet }) diff --git a/ios/brave-ios/Sources/BraveWallet/WalletUserAssetManager.swift b/ios/brave-ios/Sources/BraveWallet/WalletUserAssetManager.swift index 094cf5df9efe..8851444e4b29 100644 --- a/ios/brave-ios/Sources/BraveWallet/WalletUserAssetManager.swift +++ b/ios/brave-ios/Sources/BraveWallet/WalletUserAssetManager.swift @@ -378,7 +378,7 @@ public class WalletUserAssetManager: WalletUserAssetManagerType, WalletObserverS public func refreshBalances(_ completion: (() -> Void)? = nil) { refreshBalanceTask?.cancel() refreshBalanceTask = Task { @MainActor in - let accounts = await keyringService.allAccounts(checkBTCTestnet: true) + let accounts = await keyringService.allAccountInfos() let allNetworks = await rpcService.allNetworksForSupportedCoins() let allUserAssets: [NetworkAssets] = self.getAllUserAssetsInNetworkAssets( networks: allNetworks,