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..befd7cdb0a --- /dev/null +++ b/Tangem/App/Services/UserWalletNameIndexationHelper/UserWalletNameIndexationHelper.swift @@ -0,0 +1,97 @@ +// +// UserWalletNameIndexationHelper.swift +// Tangem +// +// Created by Andrey Chukavin on 17.05.2024. +// Copyright © 2024 Tangem AG. All rights reserved. +// + +import Foundation + +class UserWalletNameIndexationHelper { + private var indexesByNameTemplate: [String: [Int]] = [:] + private let nameComponentsRegex = try! NSRegularExpression(pattern: "^(.+)(\\s+\\d+)$") + + init(mode: Mode, names: [String]) { + for name in names { + guard let nameComponents = nameComponents(from: name) else { + if mode == .newName { + addIndex(1, for: name) + } + continue + } + + let indexesByNameTemplate = indexesByNameTemplate[nameComponents.template] ?? [] + if !indexesByNameTemplate.contains(nameComponents.index) { + addIndex(nameComponents.index, for: nameComponents.template) + } + } + } + + func suggestedName(_ rawName: String) -> String { + if let _ = nameComponents(from: rawName) { + return rawName + } + + let nameTemplate = rawName.trimmingCharacters(in: .whitespaces) + let nameIndex = nextIndex(for: nameTemplate) + + addIndex(nameIndex, for: nameTemplate) + + if nameIndex == 1 { + return nameTemplate + } else { + return "\(nameTemplate) \(nameIndex)" + } + } + + private func addIndex(_ index: Int, for nameTemplate: String) { + let newIndexes = (indexesByNameTemplate[nameTemplate] ?? []) + [index] + indexesByNameTemplate[nameTemplate] = newIndexes.sorted() + } + + private func nextIndex(for nameTemplate: String) -> Int { + let indexes = indexesByNameTemplate[nameTemplate] ?? [] + + for i in 1 ... 100 { + if !indexes.contains(i) { + return i + } + } + + let defaultIndex = indexes.count + 1 + return defaultIndex + } + + private func nameComponents(from rawName: String) -> NameComponents? { + let name = rawName.trimmingCharacters(in: .whitespaces) + let range = NSRange(location: 0, length: name.count) + + guard + let match = 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 + } + + let template = String(name[templateRange]) + return NameComponents(template: template, index: index) + } +} + +extension UserWalletNameIndexationHelper { + enum Mode { + case migration + case newName + } +} + +private extension UserWalletNameIndexationHelper { + struct NameComponents { + let template: String + let index: Int + } +} diff --git a/Tangem/App/Services/UserWalletRepository/CommonUserWalletRepository.swift b/Tangem/App/Services/UserWalletRepository/CommonUserWalletRepository.swift index b1ccfbb3c7..1672f528e2 100644 --- a/Tangem/App/Services/UserWalletRepository/CommonUserWalletRepository.swift +++ b/Tangem/App/Services/UserWalletRepository/CommonUserWalletRepository.swift @@ -100,7 +100,7 @@ class CommonUserWalletRepository: UserWalletRepository { let config = UserWalletConfigFactory(cardInfo).makeConfig() Analytics.endLoggingCardScan() - cardInfo.name = config.cardName + cardInfo.name = suggestedUserWalletName(defaultName: config.cardName) let userWalletModel = CommonUserWalletModel(cardInfo: cardInfo) if let userWalletModel { @@ -148,6 +148,13 @@ class CommonUserWalletRepository: UserWalletRepository { .eraseToAnyPublisher() } + private func suggestedUserWalletName(defaultName: String) -> String { + let otherNames = models.map(\.name) + let helper = UserWalletNameIndexationHelper(mode: .newName, names: otherNames) + + return helper.suggestedName(defaultName) + } + func unlock(with method: UserWalletRepositoryUnlockMethod, completion: @escaping (UserWalletRepositoryResult?) -> Void) { switch method { case .biometry: @@ -489,7 +496,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 +528,31 @@ class CommonUserWalletRepository: UserWalletRepository { initializeServices(for: selectedModel) } + private func migrateNamesIfNeeded(_ wallets: inout [StoredUserWallet]) { + if AppSettings.shared.didMigrateUserWalletNames { + return + } + + let oldNames = wallets.map(\.name) + let helper = UserWalletNameIndexationHelper(mode: .migration, names: oldNames) + + var didChangeNames = true + for i in 0 ..< wallets.count { + let oldName = wallets[i].name + let newName = helper.suggestedName(oldName) + if newName != oldName { + wallets[i].name = newName + didChangeNames = true + } + } + + if didChangeNames { + UserWalletRepositoryUtil().saveUserWallets(wallets) + } + + 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 bfc904dc60..2aa51e898c 100644 --- a/Tangem/Modules/Main/MainViewModel.swift +++ b/Tangem/Modules/Main/MainViewModel.swift @@ -41,6 +41,7 @@ final class MainViewModel: ObservableObject { private var pendingUserWalletIdsToUpdate: Set = [] private var pendingUserWalletModelsToAdd: [UserWalletModel] = [] private var shouldRecreatePagesAfterAddingPendingWalletModels = false + private var walletNameFieldValidator: AlertFieldValidator? private var isLoggingOut = false @@ -155,10 +156,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) + } + 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 c224aae2a7..727de8fb51 100644 --- a/TangemApp.xcodeproj/project.pbxproj +++ b/TangemApp.xcodeproj/project.pbxproj @@ -824,6 +824,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 */; }; @@ -2527,6 +2529,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 = ""; }; @@ -3637,6 +3641,7 @@ 5D3F77DB24BF56DC00E8695B /* TangemTests */ = { isa = PBXGroup; children = ( + DA64DACA2C08BE7B000CBC1E /* UserWalletNameIndexationTests.swift */, DCA54D8E2C05E0BA00FBF99F /* PriceChangeFormatterTests.swift */, 0A1992A52B5FBDF800344312 /* CurrencyTests.swift */, 5D3F77DC24BF56DC00E8695B /* TangemTests.swift */, @@ -6125,6 +6130,14 @@ path = AddressIconView; sourceTree = ""; }; + DA64DAC82C08BE71000CBC1E /* UserWalletNameIndexationHelper */ = { + isa = PBXGroup; + children = ( + DA64DAC72C08BE71000CBC1E /* UserWalletNameIndexationHelper.swift */, + ); + path = UserWalletNameIndexationHelper; + sourceTree = ""; + }; DA7046152AAA180900808FA8 /* Charts */ = { isa = PBXGroup; children = ( @@ -6390,6 +6403,7 @@ DC0A57DA2822A4DC0031BECC /* Services */ = { isa = PBXGroup; children = ( + DA64DAC82C08BE71000CBC1E /* UserWalletNameIndexationHelper */, B098F3972C0474AD00F79989 /* ManageTokens */, DAC5A5272BBEB5A4003CFC1D /* KeyboardVisibilityService */, B675C35D2BAC8A0E007BE1DC /* AccountHealthCheck */, @@ -9329,6 +9343,7 @@ B62EE31C2B28977B006BA412 /* RateAppResponse.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 */, @@ -10465,6 +10480,7 @@ EF6221B3292B5FCD00D1D4A0 /* DecimalNumberFormatterTests.swift in Sources */, DC7AC96A292BE2C900580668 /* TangemTests.swift in Sources */, DA151FBE2C058B6E00BB208D /* SendCryptoValueFormatterTests.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..a3db87802b --- /dev/null +++ b/TangemTests/UserWalletNameIndexationTests.swift @@ -0,0 +1,374 @@ +// +// 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() { + for testCaseSet in testCaseSets { + let existingNamesTestCases = testCaseSet.existingNamesTestCases + let existingNames = existingNamesTestCases.map(\.0) + let expectedNamesAfterMigration = existingNamesTestCases.map(\.1).sorted() + + let nameMigrationHelper = UserWalletNameIndexationHelper(mode: .migration, names: existingNames) + + let migratedNames = existingNames + .map { name in + nameMigrationHelper.suggestedName(name) + } + .sorted() + XCTAssertEqual(migratedNames, expectedNamesAfterMigration) + + let newNameTestCases = testCaseSet.newNameTestCases + for newNameTestCase in newNameTestCases { + XCTAssertEqual(nameMigrationHelper.suggestedName(newNameTestCase.0), newNameTestCase.1) + } + + let newNameHelper = UserWalletNameIndexationHelper(mode: .newName, names: migratedNames) + for newNameTestCase in newNameTestCases { + XCTAssertEqual(newNameHelper.suggestedName(newNameTestCase.0), newNameTestCase.1) + } + } + } +} + +extension UserWalletNameIndexationTests { + struct TestCasesSet { + let existingNamesTestCases: [(String, String)] + let newNameTestCases: [(String, 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"), + ] + ), + ] + } +}