diff --git a/Tangem/App/Models/UserWallet/StoredUserWallet.swift b/Tangem/App/Models/UserWallet/StoredUserWallet.swift index 14d727f750..e96b87953e 100644 --- a/Tangem/App/Models/UserWallet/StoredUserWallet.swift +++ b/Tangem/App/Models/UserWallet/StoredUserWallet.swift @@ -66,3 +66,9 @@ extension StoredUserWallet: Decodable { } } } + +protocol NameableWallet { + var name: String { get set } +} + +extension StoredUserWallet: NameableWallet {} diff --git a/Tangem/App/Services/Common/AppSettings.swift b/Tangem/App/Services/Common/AppSettings.swift index ed457b39e9..9e7cbca59d 100644 --- a/Tangem/App/Services/Common/AppSettings.swift +++ b/Tangem/App/Services/Common/AppSettings.swift @@ -75,6 +75,9 @@ class AppSettings { @AppStorageCompat(StorageType.forcedDemoCardId) var forcedDemoCardId: String? = nil + @AppStorageCompat(StorageType.didMigrateUserWalletNames) + var didMigrateUserWalletNames: Bool = false + static let shared: AppSettings = .init() private init() {} diff --git a/Tangem/App/Services/UserWalletNameIndexationHelper/UserWalletNameIndexationHelper.swift b/Tangem/App/Services/UserWalletNameIndexationHelper/UserWalletNameIndexationHelper.swift new file mode 100644 index 0000000000..02284e3a33 --- /dev/null +++ b/Tangem/App/Services/UserWalletNameIndexationHelper/UserWalletNameIndexationHelper.swift @@ -0,0 +1,111 @@ +// +// UserWalletNameIndexationHelper.swift +// Tangem +// +// Created by Andrey Chukavin on 17.05.2024. +// Copyright © 2024 Tangem AG. All rights reserved. +// + +import Foundation + +final class UserWalletNameIndexationHelper { + private var existingNames: Set + + private init(existingNames: [String]) { + self.existingNames = Set(existingNames).filter { NameComponents(from: $0) != nil } + } + + static func migratedWallets(_ wallets: [T]) -> [T]? { + var wallets = wallets + let helper = UserWalletNameIndexationHelper(existingNames: wallets.map(\.name)) + + var didChangeNames = false + for (index, wallet) in wallets.enumerated() { + let suggestedName = helper.suggestedName(for: wallet.name) + if wallet.name != suggestedName { + var wallet = wallet + wallet.name = suggestedName + wallets[index] = wallet + didChangeNames = true + } + } + + return didChangeNames ? wallets : nil + } + + static func suggestedName(_ rawName: String, names: [String]) -> String { + if NameComponents(from: rawName) != nil { + return rawName + } + + let indicesByNameTemplate = names.reduce(into: [String: Set]()) { dict, name in + guard let nameComponents = NameComponents(from: name) else { + dict[name, default: []].insert(1) + return + } + + dict[nameComponents.template, default: []].insert(nameComponents.index) + } + + let nameTemplate = rawName.trimmingCharacters(in: .whitespaces) + let nameIndex = indicesByNameTemplate.nextIndex(for: nameTemplate) + + if nameIndex == 1 { + return nameTemplate + } else { + return "\(nameTemplate) \(nameIndex)" + } + } + + private func suggestedName(for rawName: String) -> String { + let name = Self.suggestedName(rawName, names: Array(existingNames)) + existingNames.insert(name) + return name + } +} + +private extension UserWalletNameIndexationHelper { + struct NameComponents { + static let nameComponentsRegex = try! NSRegularExpression(pattern: "^(.+)(\\s+\\d+)$") + + let template: String + let index: Int + + init?(from rawName: String) { + let name = rawName.trimmingCharacters(in: .whitespaces) + let range = NSRange(location: 0, length: name.count) + + guard + let match = Self.nameComponentsRegex.matches(in: name, range: range).first, + match.numberOfRanges == 3, + let templateRange = Range(match.range(at: 1), in: name), + let indexRange = Range(match.range(at: 2), in: name), + let index = Int(String(name[indexRange]).trimmingCharacters(in: .whitespaces)) + else { + return nil + } + + template = String(name[templateRange]) + self.index = index + } + } +} + +private extension [String: Set] { + func nextIndex(for nameTemplate: String) -> Int { + let indices = self[nameTemplate, default: []] + + if indices.isEmpty { + return 1 + } + + for i in 1 ... indices.count { + if !indices.contains(i) { + return i + } + } + + let defaultIndex = indices.count + 1 + return defaultIndex + } +} diff --git a/Tangem/App/Services/UserWalletRepository/CommonUserWalletRepository.swift b/Tangem/App/Services/UserWalletRepository/CommonUserWalletRepository.swift index 9216fb1931..d6a8645bee 100644 --- a/Tangem/App/Services/UserWalletRepository/CommonUserWalletRepository.swift +++ b/Tangem/App/Services/UserWalletRepository/CommonUserWalletRepository.swift @@ -94,13 +94,18 @@ class CommonUserWalletRepository: UserWalletRepository { failedCardScanTracker.resetCounter() sendEvent(.scan(isScanning: false)) - let cardInfo = response.getCardInfo() + var cardInfo = response.getCardInfo() updateAssociatedCard(for: cardInfo) resetServices() initializeAnalyticsContext(with: cardInfo) let config = UserWalletConfigFactory(cardInfo).makeConfig() Analytics.endLoggingCardScan() + cardInfo.name = UserWalletNameIndexationHelper.suggestedName( + config.cardName, + names: models.map(\.name) + ) + let userWalletModel = CommonUserWalletModel(cardInfo: cardInfo) if let userWalletModel { initializeServices(for: userWalletModel) @@ -489,7 +494,8 @@ class CommonUserWalletRepository: UserWalletRepository { } private func loadModels() { - let savedUserWallets = savedUserWallets(withSensitiveData: true) + var savedUserWallets = savedUserWallets(withSensitiveData: true) + migrateNamesIfNeeded(&savedUserWallets) models = savedUserWallets.map { userWalletStorageItem in if let userWallet = CommonUserWalletModel(userWallet: userWalletStorageItem) { @@ -520,6 +526,18 @@ class CommonUserWalletRepository: UserWalletRepository { initializeServices(for: selectedModel) } + private func migrateNamesIfNeeded(_ wallets: inout [StoredUserWallet]) { + guard !AppSettings.shared.didMigrateUserWalletNames else { + return + } + + if let migratedWallets = UserWalletNameIndexationHelper.migratedWallets(wallets) { + UserWalletRepositoryUtil().saveUserWallets(migratedWallets) + } + + AppSettings.shared.didMigrateUserWalletNames = true + } + private func sendEvent(_ event: UserWalletRepositoryEvent) { eventSubject.send(event) } diff --git a/Tangem/Common/Storage/StorageType.swift b/Tangem/Common/Storage/StorageType.swift index dc76408c2d..654477a434 100644 --- a/Tangem/Common/Storage/StorageType.swift +++ b/Tangem/Common/Storage/StorageType.swift @@ -40,4 +40,5 @@ enum StorageType: String { case pendingBackups = "pending_backups" case pendingBackupsCurrentID = "pending_backups_current_id" case forcedDemoCardId = "forced_demo_card_id" + case didMigrateUserWalletNames = "did_migrate_user_wallet_names" } diff --git a/Tangem/Modules/Main/MainViewModel.swift b/Tangem/Modules/Main/MainViewModel.swift index 44236818bd..4000412bd7 100644 --- a/Tangem/Modules/Main/MainViewModel.swift +++ b/Tangem/Modules/Main/MainViewModel.swift @@ -40,6 +40,8 @@ final class MainViewModel: ObservableObject { private var pendingUserWalletIdsToUpdate: Set = [] private var pendingUserWalletModelsToAdd: [UserWalletModel] = [] private var shouldRecreatePagesAfterAddingPendingWalletModels = false + private var walletNameFieldValidator: AlertFieldValidator? + private var shouldDelayBottomSheetVisibility = true private var isLoggingOut = false @@ -176,10 +178,21 @@ final class MainViewModel: ObservableObject { guard let userWalletModel = userWalletRepository.selectedModel else { return } + let otherWalletNames = userWalletRepository.models.compactMap { model -> String? in + guard model.userWalletId != userWalletModel.userWalletId else { return nil } + + return model.name + } + + walletNameFieldValidator = AlertFieldValidator { input in + !(otherWalletNames.contains(input) || input.isEmpty) + } + let alert = AlertBuilder.makeAlertControllerWithTextField( title: Localization.userWalletListRenamePopupTitle, fieldPlaceholder: Localization.userWalletListRenamePopupPlaceholder, - fieldText: userWalletModel.name + fieldText: userWalletModel.name, + fieldValidator: walletNameFieldValidator ) { newName in guard userWalletModel.name != newName else { return } diff --git a/TangemApp.xcodeproj/project.pbxproj b/TangemApp.xcodeproj/project.pbxproj index 5e864507ae..2b2f481066 100644 --- a/TangemApp.xcodeproj/project.pbxproj +++ b/TangemApp.xcodeproj/project.pbxproj @@ -978,6 +978,8 @@ DA60DD7D2B34572300AC1B79 /* QRScannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA60DD7C2B34572300AC1B79 /* QRScannerView.swift */; }; DA6445262BB17C8100865F7B /* SendDecimalNumberTextFieldPrefixSuffixOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA6445252BB17C8100865F7B /* SendDecimalNumberTextFieldPrefixSuffixOptions.swift */; }; DA64C36D27CCAD7E005B5E9A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = DA64C36F27CCAD7E005B5E9A /* InfoPlist.strings */; }; + DA64DAC92C08BE71000CBC1E /* UserWalletNameIndexationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA64DAC72C08BE71000CBC1E /* UserWalletNameIndexationHelper.swift */; }; + DA64DACB2C08BE7B000CBC1E /* UserWalletNameIndexationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA64DACA2C08BE7B000CBC1E /* UserWalletNameIndexationTests.swift */; }; DA682F962A29D08F0083CA79 /* PromotionNetworkModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA682F952A29D08F0083CA79 /* PromotionNetworkModels.swift */; }; DA6915A62A9CE2BD00DB4339 /* Tokens.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = DA6915A52A9CE2BD00DB4339 /* Tokens.xcassets */; }; DA6C7522292224E00070EEFD /* UserWalletEncryptionKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA6C7521292224E00070EEFD /* UserWalletEncryptionKey.swift */; }; @@ -2959,6 +2961,8 @@ DA6445252BB17C8100865F7B /* SendDecimalNumberTextFieldPrefixSuffixOptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendDecimalNumberTextFieldPrefixSuffixOptions.swift; sourceTree = ""; }; DA64C36E27CCAD7E005B5E9A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; DA64C37027CCAD7F005B5E9A /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/InfoPlist.strings; sourceTree = ""; }; + DA64DAC72C08BE71000CBC1E /* UserWalletNameIndexationHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserWalletNameIndexationHelper.swift; sourceTree = ""; }; + DA64DACA2C08BE7B000CBC1E /* UserWalletNameIndexationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserWalletNameIndexationTests.swift; sourceTree = ""; }; DA682F952A29D08F0083CA79 /* PromotionNetworkModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromotionNetworkModels.swift; sourceTree = ""; }; DA6915A52A9CE2BD00DB4339 /* Tokens.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Tokens.xcassets; sourceTree = ""; }; DA69F557298BB3AC004A5A93 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-Hant"; path = "zh-Hant.lproj/Localizable.stringsdict"; sourceTree = ""; }; @@ -4425,6 +4429,7 @@ 5D3F77DB24BF56DC00E8695B /* TangemTests */ = { isa = PBXGroup; children = ( + DA64DACA2C08BE7B000CBC1E /* UserWalletNameIndexationTests.swift */, DCA54D8E2C05E0BA00FBF99F /* PriceChangeFormatterTests.swift */, 0A1992A52B5FBDF800344312 /* CurrencyTests.swift */, 5D3F77DC24BF56DC00E8695B /* TangemTests.swift */, @@ -7195,6 +7200,14 @@ path = AddressIconView; sourceTree = ""; }; + DA64DAC82C08BE71000CBC1E /* UserWalletNameIndexationHelper */ = { + isa = PBXGroup; + children = ( + DA64DAC72C08BE71000CBC1E /* UserWalletNameIndexationHelper.swift */, + ); + path = UserWalletNameIndexationHelper; + sourceTree = ""; + }; DA7046152AAA180900808FA8 /* Charts */ = { isa = PBXGroup; children = ( @@ -7444,6 +7457,7 @@ DC0A57DA2822A4DC0031BECC /* Services */ = { isa = PBXGroup; children = ( + DA64DAC82C08BE71000CBC1E /* UserWalletNameIndexationHelper */, DC9632642C514335009B250A /* TokenFinder */, DC592B912C41A7EA005AB9A7 /* Staking */, B62C65C62C38D04F00189AD4 /* PushNotifications */, @@ -10960,6 +10974,7 @@ EFB7521B2C985F1100CB614C /* CommonFeeIncludedCalculator.swift in Sources */, DC70C01F2B95F196002205BA /* KeysDerivingMock.swift in Sources */, EFAD02472A824E6700E9B959 /* TransactionViewModel.swift in Sources */, + DA64DAC92C08BE71000CBC1E /* UserWalletNameIndexationHelper.swift in Sources */, EF729B722AF3E86300D80205 /* ExpressProvidersSelectorViewModel.swift in Sources */, DCEC71F52B8683FB00B3847E /* BackupValidator.swift in Sources */, 5D51037927C539B60025EA55 /* ABIError.swift in Sources */, @@ -12323,6 +12338,7 @@ 0A1992A62B5FBDF800344312 /* CurrencyTests.swift in Sources */, EF6221B3292B5FCD00D1D4A0 /* DecimalNumberFormatterTests.swift in Sources */, DC7AC96A292BE2C900580668 /* TangemTests.swift in Sources */, + DA64DACB2C08BE7B000CBC1E /* UserWalletNameIndexationTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/TangemTests/UserWalletNameIndexationTests.swift b/TangemTests/UserWalletNameIndexationTests.swift new file mode 100644 index 0000000000..fddf2f173a --- /dev/null +++ b/TangemTests/UserWalletNameIndexationTests.swift @@ -0,0 +1,370 @@ +// +// UserWalletNameIndexationTests.swift +// TangemTests +// +// Created by Andrey Chukavin on 17.05.2024. +// Copyright © 2024 Tangem AG. All rights reserved. +// + +import XCTest +@testable import Tangem + +class UserWalletNameIndexationTests: XCTestCase { + func testUserWalletNameIndexation() throws { + for testCaseSet in testCaseSets { + let existingNamesTestCases = testCaseSet.existingNamesTestCases + let existingNames = existingNamesTestCases.map(\.0) + let expectedNamesAfterMigration = existingNamesTestCases.map(\.1).sorted() + + let wallets = existingNames.map(WalletMock.init) + let migratedWallets = try XCTUnwrap(UserWalletNameIndexationHelper.migratedWallets(wallets)) + let migratedNames = migratedWallets.map(\.name).sorted() + + XCTAssertEqual(migratedNames, expectedNamesAfterMigration) + + for newNameTestCase in testCaseSet.newNameTestCases { + let name = UserWalletNameIndexationHelper.suggestedName(newNameTestCase.0, names: migratedNames) + XCTAssertEqual(name, newNameTestCase.1) + } + } + } +} + +extension UserWalletNameIndexationTests { + struct TestCasesSet { + let existingNamesTestCases: [(String, String)] + let newNameTestCases: [(String, String)] + } + + struct WalletMock: NameableWallet { + var name: String + } +} + +extension UserWalletNameIndexationTests { + var testCaseSets: [TestCasesSet] { + [ + // 🚀 The original set, neatly organized + TestCasesSet( + existingNamesTestCases: [ + ("Wallet 2", /* -> */ "Wallet 2"), + ("Wallet", /* -> */ "Wallet"), + ("Wallet 2", /* -> */ "Wallet 2"), + ("Wallet", /* -> */ "Wallet 3"), + ("Wallet", /* -> */ "Wallet 4"), + ("Note", /* -> */ "Note"), + ("Note", /* -> */ "Note 2"), + ("Note", /* -> */ "Note 3"), + ("Twin 1", /* -> */ "Twin 1"), + ("Twin", /* -> */ "Twin 2"), + ("Twin 3", /* -> */ "Twin 3"), + ("Twin", /* -> */ "Twin 4"), + ("Start2Coin 1", /* -> */ "Start2Coin 1"), + ("Start2Coin 1", /* -> */ "Start2Coin 1"), + ("Start2Coin 1", /* -> */ "Start2Coin 1"), + ("Tangem Card", /* -> */ "Tangem Card"), + ("Wallet 2.0", /* -> */ "Wallet 2.0"), + ("Wallet 2.0", /* -> */ "Wallet 2.0 2"), + ("Wallet 2.0", /* -> */ "Wallet 2.0 3"), + ], + newNameTestCases: [ + ("Wallet", "Wallet 5"), + ("Note", "Note 4"), + ("Twin", "Twin 5"), + ("Start2Coin", "Start2Coin 2"), + ("Wallet 2.0", "Wallet 2.0 4"), + ("Tangem Card", "Tangem Card 2"), + ] + ), + // 🔀 Randomized set + TestCasesSet( + existingNamesTestCases: [ + ("Tangem Card", /* -> */ "Tangem Card"), + ("Wallet", /* -> */ "Wallet 4"), + ("Wallet", /* -> */ "Wallet"), + ("Wallet 2", /* -> */ "Wallet 2"), + ("Start2Coin 1", /* -> */ "Start2Coin 1"), + ("Twin", /* -> */ "Twin 4"), + ("Note", /* -> */ "Note"), + ("Start2Coin 1", /* -> */ "Start2Coin 1"), + ("Note", /* -> */ "Note 2"), + ("Wallet 2.0", /* -> */ "Wallet 2.0"), + ("Twin 3", /* -> */ "Twin 3"), + ("Wallet 2", /* -> */ "Wallet 2"), + ("Wallet", /* -> */ "Wallet 3"), + ("Wallet 2.0", /* -> */ "Wallet 2.0 2"), + ("Note", /* -> */ "Note 3"), + ("Wallet 2.0", /* -> */ "Wallet 2.0 3"), + ("Twin 1", /* -> */ "Twin 1"), + ("Twin", /* -> */ "Twin 2"), + ("Start2Coin 1", /* -> */ "Start2Coin 1"), + ], + newNameTestCases: [ + ("Start2Coin", "Start2Coin 2"), + ("Wallet 2.0", "Wallet 2.0 4"), + ("Wallet", "Wallet 5"), + ("Twin", "Twin 5"), + ("Note", "Note 4"), + ("Tangem Card", "Tangem Card 2"), + ] + ), + // 🔀 Randomized set + TestCasesSet( + existingNamesTestCases: [ + ("Start2Coin 1", /* -> */ "Start2Coin 1"), + ("Wallet", /* -> */ "Wallet 3"), + ("Start2Coin 1", /* -> */ "Start2Coin 1"), + ("Twin 3", /* -> */ "Twin 3"), + ("Wallet 2.0", /* -> */ "Wallet 2.0 2"), + ("Wallet 2.0", /* -> */ "Wallet 2.0"), + ("Tangem Card", /* -> */ "Tangem Card"), + ("Note", /* -> */ "Note 3"), + ("Wallet 2.0", /* -> */ "Wallet 2.0 3"), + ("Twin", /* -> */ "Twin 2"), + ("Wallet", /* -> */ "Wallet 4"), + ("Start2Coin 1", /* -> */ "Start2Coin 1"), + ("Wallet 2", /* -> */ "Wallet 2"), + ("Wallet 2", /* -> */ "Wallet 2"), + ("Twin", /* -> */ "Twin 4"), + ("Twin 1", /* -> */ "Twin 1"), + ("Wallet", /* -> */ "Wallet"), + ("Note", /* -> */ "Note 2"), + ("Note", /* -> */ "Note"), + ], + newNameTestCases: [ + ("Wallet 2.0", "Wallet 2.0 4"), + ("Twin", "Twin 5"), + ("Tangem Card", "Tangem Card 2"), + ("Wallet", "Wallet 5"), + ("Note", "Note 4"), + ("Start2Coin", "Start2Coin 2"), + ] + ), + + // 🔀 Randomized set + TestCasesSet( + existingNamesTestCases: [ + ("Wallet", /* -> */ "Wallet 4"), + ("Wallet 2.0", /* -> */ "Wallet 2.0 3"), + ("Wallet 2", /* -> */ "Wallet 2"), + ("Wallet 2", /* -> */ "Wallet 2"), + ("Twin 3", /* -> */ "Twin 3"), + ("Wallet", /* -> */ "Wallet 3"), + ("Tangem Card", /* -> */ "Tangem Card"), + ("Note", /* -> */ "Note 2"), + ("Twin 1", /* -> */ "Twin 1"), + ("Start2Coin 1", /* -> */ "Start2Coin 1"), + ("Note", /* -> */ "Note"), + ("Wallet", /* -> */ "Wallet"), + ("Start2Coin 1", /* -> */ "Start2Coin 1"), + ("Wallet 2.0", /* -> */ "Wallet 2.0 2"), + ("Twin", /* -> */ "Twin 4"), + ("Twin", /* -> */ "Twin 2"), + ("Start2Coin 1", /* -> */ "Start2Coin 1"), + ("Wallet 2.0", /* -> */ "Wallet 2.0"), + ("Note", /* -> */ "Note 3"), + ], + newNameTestCases: [ + ("Twin", "Twin 5"), + ("Note", "Note 4"), + ("Wallet", "Wallet 5"), + ("Start2Coin", "Start2Coin 2"), + ("Tangem Card", "Tangem Card 2"), + ("Wallet 2.0", "Wallet 2.0 4"), + ] + ), + // 🔀 Randomized set + TestCasesSet( + existingNamesTestCases: [ + ("Start2Coin 1", /* -> */ "Start2Coin 1"), + ("Note", /* -> */ "Note 2"), + ("Wallet 2.0", /* -> */ "Wallet 2.0 3"), + ("Twin", /* -> */ "Twin 2"), + ("Wallet 2.0", /* -> */ "Wallet 2.0"), + ("Tangem Card", /* -> */ "Tangem Card"), + ("Twin", /* -> */ "Twin 4"), + ("Note", /* -> */ "Note"), + ("Wallet", /* -> */ "Wallet 3"), + ("Wallet 2", /* -> */ "Wallet 2"), + ("Wallet", /* -> */ "Wallet 4"), + ("Start2Coin 1", /* -> */ "Start2Coin 1"), + ("Twin 3", /* -> */ "Twin 3"), + ("Twin 1", /* -> */ "Twin 1"), + ("Wallet", /* -> */ "Wallet"), + ("Wallet 2.0", /* -> */ "Wallet 2.0 2"), + ("Start2Coin 1", /* -> */ "Start2Coin 1"), + ("Note", /* -> */ "Note 3"), + ("Wallet 2", /* -> */ "Wallet 2"), + ], + newNameTestCases: [ + ("Note", "Note 4"), + ("Wallet", "Wallet 5"), + ("Wallet 2.0", "Wallet 2.0 4"), + ("Tangem Card", "Tangem Card 2"), + ("Start2Coin", "Start2Coin 2"), + ("Twin", "Twin 5"), + ] + ), + // 🔀 Randomized set + TestCasesSet( + existingNamesTestCases: [ + ("Wallet 2", /* -> */ "Wallet 2"), + ("Wallet", /* -> */ "Wallet"), + ("Tangem Card", /* -> */ "Tangem Card"), + ("Wallet 2", /* -> */ "Wallet 2"), + ("Wallet", /* -> */ "Wallet 4"), + ("Start2Coin 1", /* -> */ "Start2Coin 1"), + ("Wallet 2.0", /* -> */ "Wallet 2.0 2"), + ("Twin", /* -> */ "Twin 2"), + ("Wallet 2.0", /* -> */ "Wallet 2.0"), + ("Wallet", /* -> */ "Wallet 3"), + ("Note", /* -> */ "Note"), + ("Twin 1", /* -> */ "Twin 1"), + ("Start2Coin 1", /* -> */ "Start2Coin 1"), + ("Start2Coin 1", /* -> */ "Start2Coin 1"), + ("Wallet 2.0", /* -> */ "Wallet 2.0 3"), + ("Note", /* -> */ "Note 2"), + ("Twin 3", /* -> */ "Twin 3"), + ("Twin", /* -> */ "Twin 4"), + ("Note", /* -> */ "Note 3"), + ], + newNameTestCases: [ + ("Twin", "Twin 5"), + ("Note", "Note 4"), + ("Wallet", "Wallet 5"), + ("Tangem Card", "Tangem Card 2"), + ("Start2Coin", "Start2Coin 2"), + ("Wallet 2.0", "Wallet 2.0 4"), + ] + ), + // 🔀 Randomized set + TestCasesSet( + existingNamesTestCases: [ + ("Start2Coin 1", /* -> */ "Start2Coin 1"), + ("Wallet", /* -> */ "Wallet"), + ("Twin", /* -> */ "Twin 2"), + ("Wallet", /* -> */ "Wallet 4"), + ("Twin", /* -> */ "Twin 4"), + ("Note", /* -> */ "Note 3"), + ("Wallet 2", /* -> */ "Wallet 2"), + ("Start2Coin 1", /* -> */ "Start2Coin 1"), + ("Twin 3", /* -> */ "Twin 3"), + ("Wallet", /* -> */ "Wallet 3"), + ("Wallet 2", /* -> */ "Wallet 2"), + ("Twin 1", /* -> */ "Twin 1"), + ("Wallet 2.0", /* -> */ "Wallet 2.0 2"), + ("Wallet 2.0", /* -> */ "Wallet 2.0"), + ("Tangem Card", /* -> */ "Tangem Card"), + ("Start2Coin 1", /* -> */ "Start2Coin 1"), + ("Note", /* -> */ "Note 2"), + ("Wallet 2.0", /* -> */ "Wallet 2.0 3"), + ("Note", /* -> */ "Note"), + ], + newNameTestCases: [ + ("Wallet 2.0", "Wallet 2.0 4"), + ("Twin", "Twin 5"), + ("Note", "Note 4"), + ("Tangem Card", "Tangem Card 2"), + ("Start2Coin", "Start2Coin 2"), + ("Wallet", "Wallet 5"), + ] + ), + // 🔀 Randomized set + TestCasesSet( + existingNamesTestCases: [ + ("Wallet", /* -> */ "Wallet 3"), + ("Note", /* -> */ "Note 3"), + ("Wallet 2", /* -> */ "Wallet 2"), + ("Wallet 2.0", /* -> */ "Wallet 2.0"), + ("Wallet 2.0", /* -> */ "Wallet 2.0 3"), + ("Note", /* -> */ "Note"), + ("Note", /* -> */ "Note 2"), + ("Start2Coin 1", /* -> */ "Start2Coin 1"), + ("Wallet", /* -> */ "Wallet 4"), + ("Start2Coin 1", /* -> */ "Start2Coin 1"), + ("Twin", /* -> */ "Twin 4"), + ("Twin", /* -> */ "Twin 2"), + ("Wallet 2", /* -> */ "Wallet 2"), + ("Start2Coin 1", /* -> */ "Start2Coin 1"), + ("Wallet 2.0", /* -> */ "Wallet 2.0 2"), + ("Tangem Card", /* -> */ "Tangem Card"), + ("Twin 3", /* -> */ "Twin 3"), + ("Twin 1", /* -> */ "Twin 1"), + ("Wallet", /* -> */ "Wallet"), + ], + newNameTestCases: [ + ("Tangem Card", "Tangem Card 2"), + ("Wallet", "Wallet 5"), + ("Wallet 2.0", "Wallet 2.0 4"), + ("Twin", "Twin 5"), + ("Note", "Note 4"), + ("Start2Coin", "Start2Coin 2"), + ] + ), + // 🔀 Randomized set + TestCasesSet( + existingNamesTestCases: [ + ("Wallet 2.0", /* -> */ "Wallet 2.0 3"), + ("Wallet", /* -> */ "Wallet 3"), + ("Note", /* -> */ "Note"), + ("Twin", /* -> */ "Twin 4"), + ("Note", /* -> */ "Note 2"), + ("Twin 1", /* -> */ "Twin 1"), + ("Wallet", /* -> */ "Wallet"), + ("Note", /* -> */ "Note 3"), + ("Wallet 2", /* -> */ "Wallet 2"), + ("Wallet 2.0", /* -> */ "Wallet 2.0 2"), + ("Twin 3", /* -> */ "Twin 3"), + ("Start2Coin 1", /* -> */ "Start2Coin 1"), + ("Start2Coin 1", /* -> */ "Start2Coin 1"), + ("Tangem Card", /* -> */ "Tangem Card"), + ("Wallet 2", /* -> */ "Wallet 2"), + ("Wallet", /* -> */ "Wallet 4"), + ("Start2Coin 1", /* -> */ "Start2Coin 1"), + ("Twin", /* -> */ "Twin 2"), + ("Wallet 2.0", /* -> */ "Wallet 2.0"), + ], + newNameTestCases: [ + ("Tangem Card", "Tangem Card 2"), + ("Wallet", "Wallet 5"), + ("Wallet 2.0", "Wallet 2.0 4"), + ("Twin", "Twin 5"), + ("Note", "Note 4"), + ("Start2Coin", "Start2Coin 2"), + ] + ), + // 🔀 Randomized set + TestCasesSet( + existingNamesTestCases: [ + ("Twin 1", /* -> */ "Twin 1"), + ("Tangem Card", /* -> */ "Tangem Card"), + ("Note", /* -> */ "Note 3"), + ("Twin", /* -> */ "Twin 2"), + ("Note", /* -> */ "Note"), + ("Twin", /* -> */ "Twin 4"), + ("Twin 3", /* -> */ "Twin 3"), + ("Wallet 2.0", /* -> */ "Wallet 2.0"), + ("Wallet", /* -> */ "Wallet 3"), + ("Start2Coin 1", /* -> */ "Start2Coin 1"), + ("Wallet 2", /* -> */ "Wallet 2"), + ("Note", /* -> */ "Note 2"), + ("Start2Coin 1", /* -> */ "Start2Coin 1"), + ("Wallet", /* -> */ "Wallet"), + ("Wallet 2.0", /* -> */ "Wallet 2.0 2"), + ("Wallet", /* -> */ "Wallet 4"), + ("Start2Coin 1", /* -> */ "Start2Coin 1"), + ("Wallet 2.0", /* -> */ "Wallet 2.0 3"), + ("Wallet 2", /* -> */ "Wallet 2"), + ], + newNameTestCases: [ + ("Wallet 2.0", "Wallet 2.0 4"), + ("Wallet", "Wallet 5"), + ("Start2Coin", "Start2Coin 2"), + ("Twin", "Twin 5"), + ("Tangem Card", "Tangem Card 2"), + ("Note", "Note 4"), + ] + ), + ] + } +}