From 91467de31976692f07f772274ed223776fc18fbf Mon Sep 17 00:00:00 2001 From: Andrey Chukavin Date: Tue, 21 May 2024 20:25:28 +0900 Subject: [PATCH 1/4] IOS-6610 Indexation helper --- Tangem/App/Services/Common/AppSettings.swift | 3 + .../UserWalletNameIndexationHelper.swift | 97 ++++++++++++++++ .../CommonUserWalletRepository.swift | 30 ++++- Tangem/Modules/Main/MainViewModel.swift | 14 ++- .../UserWalletNameIndexationTests.swift | 104 ++++++++++++++++++ 5 files changed, 245 insertions(+), 3 deletions(-) create mode 100644 Tangem/App/Services/UserWalletNameIndexationHelper/UserWalletNameIndexationHelper.swift create mode 100644 TangemTests/UserWalletNameIndexationTests.swift 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 0e7131c8e4..f7dc99ce2c 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 { @@ -480,7 +480,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) { @@ -511,6 +512,31 @@ class CommonUserWalletRepository: UserWalletRepository { initializeServices(for: selectedModel) } + private func migrateNamesIfNeeded(_ wallets: inout [StoredUserWallet]) { + if AppSettings.shared.didMigrateUserWalletNames { + return + } + + AppSettings.shared.didMigrateUserWalletNames = true + + 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) + } + } + private func sendEvent(_ event: UserWalletRepositoryEvent) { eventSubject.send(event) } diff --git a/Tangem/Modules/Main/MainViewModel.swift b/Tangem/Modules/Main/MainViewModel.swift index 9d65d2b9bd..0faf171a3a 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/TangemTests/UserWalletNameIndexationTests.swift b/TangemTests/UserWalletNameIndexationTests.swift new file mode 100644 index 0000000000..21f297c793 --- /dev/null +++ b/TangemTests/UserWalletNameIndexationTests.swift @@ -0,0 +1,104 @@ +// +// 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 { + private var existingNameTestCases: [(String, String)] { + [ + ("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"), + ] + } + + private var newNameTestCases: [(String, String)] { + [ + ("Wallet", "Wallet 5"), + ("Note", "Note 4"), + ("Twin", "Twin 5"), + ("Start2Coin", "Start2Coin 2"), + ("Wallet 2.0", "Wallet 2.0 4"), + ("Tangem Card", "Tangem Card 2"), + ] + } + + func testUserWalletNameIndexation() { + let numberOfShuffleTests = 10 + + for testNumber in 1 ... numberOfShuffleTests { + var generator = SeededNumberGenerator(seed: testNumber, length: existingNameTestCases.count) + XCTAssertNotNil(generator) + + let existingNames = existingNameTestCases.map(\.0).shuffled(using: &generator!) + let expectedNamesAfterMigration = existingNameTestCases.map(\.1).sorted() + + let nameMigrationHelper = UserWalletNameIndexationHelper(mode: .migration, names: existingNames) + + let migratedNames = existingNames + .map { name in + nameMigrationHelper.suggestedName(name) + } + .sorted() + XCTAssertEqual(migratedNames, expectedNamesAfterMigration) + + 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) + } + } + } +} + +private class SeededNumberGenerator: RandomNumberGenerator { + private let values: [UInt64] + private var index: Int = 0 + + init?(seed: Int, length: Int) { + guard length >= 1 else { return nil } + + srand48(seed) + + values = (1 ... length) + .map { _ in + let randomValue = drand48() + return UInt64(randomValue * Double(UInt64.max - 1)) + } + } + + func next() -> UInt64 { + let value = values[index] + if index < values.count - 1 { + index += 1 + } else { + index = 0 + } + return value + } +} From 988136f44cc38f527e27fd38a64bcf84c5c68b33 Mon Sep 17 00:00:00 2001 From: Andrey Chukavin Date: Wed, 22 May 2024 21:31:26 +0900 Subject: [PATCH 2/4] IOS-6610 Fixed conflicts --- .../CommonUserWalletRepository.swift | 7 +++++++ Tangem/Common/Storage/StorageType.swift | 1 + TangemApp.xcodeproj/project.pbxproj | 16 ++++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/Tangem/App/Services/UserWalletRepository/CommonUserWalletRepository.swift b/Tangem/App/Services/UserWalletRepository/CommonUserWalletRepository.swift index f7dc99ce2c..eb17f4a000 100644 --- a/Tangem/App/Services/UserWalletRepository/CommonUserWalletRepository.swift +++ b/Tangem/App/Services/UserWalletRepository/CommonUserWalletRepository.swift @@ -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: 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/TangemApp.xcodeproj/project.pbxproj b/TangemApp.xcodeproj/project.pbxproj index d72842f2bd..19af89c3c1 100644 --- a/TangemApp.xcodeproj/project.pbxproj +++ b/TangemApp.xcodeproj/project.pbxproj @@ -849,6 +849,8 @@ DAAA3C722B3D813C0002DBB0 /* SendAddressService.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAAA3C702B3D813C0002DBB0 /* SendAddressService.swift */; }; DAAA3C732B3D813C0002DBB0 /* SendAddressServiceFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAAA3C712B3D813C0002DBB0 /* SendAddressServiceFactory.swift */; }; DAAEC38D2BF31A8800184D7C /* SendFeatureProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAAEC38C2BF31A8800184D7C /* SendFeatureProvider.swift */; }; + DAAF69482BFE1C0400179697 /* UserWalletNameIndexationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAAF69462BFE1C0400179697 /* UserWalletNameIndexationHelper.swift */; }; + DAAF694A2BFE1CFA00179697 /* UserWalletNameIndexationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAAF69492BFE1CFA00179697 /* UserWalletNameIndexationTests.swift */; }; DAB19D482B2C72A100DD08C4 /* AddCustomTokenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAB19D432B2C72A100DD08C4 /* AddCustomTokenCoordinator.swift */; }; DAB19D492B2C72A100DD08C4 /* AddCustomTokenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAB19D442B2C72A100DD08C4 /* AddCustomTokenViewModel.swift */; }; DAB19D4A2B2C72A100DD08C4 /* AddCustomTokenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAB19D452B2C72A100DD08C4 /* AddCustomTokenView.swift */; }; @@ -2434,6 +2436,8 @@ DAAA3C702B3D813C0002DBB0 /* SendAddressService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendAddressService.swift; sourceTree = ""; }; DAAA3C712B3D813C0002DBB0 /* SendAddressServiceFactory.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SendAddressServiceFactory.swift; sourceTree = ""; }; DAAEC38C2BF31A8800184D7C /* SendFeatureProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendFeatureProvider.swift; sourceTree = ""; }; + DAAF69462BFE1C0400179697 /* UserWalletNameIndexationHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserWalletNameIndexationHelper.swift; sourceTree = ""; }; + DAAF69492BFE1CFA00179697 /* UserWalletNameIndexationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserWalletNameIndexationTests.swift; sourceTree = ""; }; DAB19D432B2C72A100DD08C4 /* AddCustomTokenCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddCustomTokenCoordinator.swift; sourceTree = ""; }; DAB19D442B2C72A100DD08C4 /* AddCustomTokenViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddCustomTokenViewModel.swift; sourceTree = ""; }; DAB19D452B2C72A100DD08C4 /* AddCustomTokenView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddCustomTokenView.swift; sourceTree = ""; }; @@ -3419,6 +3423,7 @@ 5D3F77DB24BF56DC00E8695B /* TangemTests */ = { isa = PBXGroup; children = ( + DAAF69492BFE1CFA00179697 /* UserWalletNameIndexationTests.swift */, 0A1992A52B5FBDF800344312 /* CurrencyTests.swift */, 5D3F77DC24BF56DC00E8695B /* TangemTests.swift */, DC4BF174298A4B810052EC19 /* IncomingActionsTests.swift */, @@ -5910,6 +5915,14 @@ path = UserWalletStorageAggreement; sourceTree = ""; }; + DAAF69472BFE1C0400179697 /* UserWalletNameIndexationHelper */ = { + isa = PBXGroup; + children = ( + DAAF69462BFE1C0400179697 /* UserWalletNameIndexationHelper.swift */, + ); + path = UserWalletNameIndexationHelper; + sourceTree = ""; + }; DAB5E331297E8CBF00A4E9CF /* Rounding */ = { isa = PBXGroup; children = ( @@ -6146,6 +6159,7 @@ DC0A57FF28243D050031BECC /* ExchangeService */, DC0A57E62822AB8C0031BECC /* PersistentStorage */, DC0A57E52822AA240031BECC /* UserWalletRepository */, + DAAF69472BFE1C0400179697 /* UserWalletNameIndexationHelper */, DC0A57E02822A5A10031BECC /* TangemSdk */, DC0A5848282C07750031BECC /* WalletConnect */, DC0A57F52822BDFB0031BECC /* CardImageLoader */, @@ -9291,6 +9305,7 @@ DC3B49F62893E51B00C207EA /* UserWalletConfig.swift in Sources */, DADBECE32BBE910C007E7885 /* CustomFeeServiceInputOutput.swift in Sources */, B6C4D7322B1A71480084AF1E /* BottomScrollableSheet+Environment.swift in Sources */, + DAAF69482BFE1C0400179697 /* UserWalletNameIndexationHelper.swift in Sources */, B03156AA2A8636CB001D9646 /* TokenItemViewState+WalletModelState.swift in Sources */, 5D242A5F273C1F24008F79B5 /* Publisher+.swift in Sources */, B0A0E7972BF48CAF0059FD5C /* VisaAppUtilities.swift in Sources */, @@ -9827,6 +9842,7 @@ B03AC9932BD8DB26000CBC50 /* APIListTests.swift in Sources */, DC4BF175298A4B810052EC19 /* IncomingActionsTests.swift in Sources */, 0A1992A62B5FBDF800344312 /* CurrencyTests.swift in Sources */, + DAAF694A2BFE1CFA00179697 /* UserWalletNameIndexationTests.swift in Sources */, EF6221B3292B5FCD00D1D4A0 /* DecimalNumberFormatterTests.swift in Sources */, DC7AC96A292BE2C900580668 /* TangemTests.swift in Sources */, ); From dc129310b0fdc9eb75d89949aa84267390ce42e8 Mon Sep 17 00:00:00 2001 From: Andrey Chukavin Date: Thu, 30 May 2024 12:52:35 +0900 Subject: [PATCH 3/4] IOS-6610 Refactoring --- .../UserWalletRepository/CommonUserWalletRepository.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tangem/App/Services/UserWalletRepository/CommonUserWalletRepository.swift b/Tangem/App/Services/UserWalletRepository/CommonUserWalletRepository.swift index eb17f4a000..adfa25e73a 100644 --- a/Tangem/App/Services/UserWalletRepository/CommonUserWalletRepository.swift +++ b/Tangem/App/Services/UserWalletRepository/CommonUserWalletRepository.swift @@ -524,8 +524,6 @@ class CommonUserWalletRepository: UserWalletRepository { return } - AppSettings.shared.didMigrateUserWalletNames = true - let oldNames = wallets.map(\.name) let helper = UserWalletNameIndexationHelper(mode: .migration, names: oldNames) @@ -542,6 +540,8 @@ class CommonUserWalletRepository: UserWalletRepository { if didChangeNames { UserWalletRepositoryUtil().saveUserWallets(wallets) } + + AppSettings.shared.didMigrateUserWalletNames = true } private func sendEvent(_ event: UserWalletRepositoryEvent) { From 8bded771bb094f105383a8c45af7bf7ab5d8c506 Mon Sep 17 00:00:00 2001 From: Andrey Chukavin Date: Thu, 30 May 2024 23:42:43 +0900 Subject: [PATCH 4/4] IOS-6610 Refactored test cases --- .../UserWalletNameIndexationTests.swift | 400 +++++++++++++++--- 1 file changed, 335 insertions(+), 65 deletions(-) diff --git a/TangemTests/UserWalletNameIndexationTests.swift b/TangemTests/UserWalletNameIndexationTests.swift index 21f297c793..a3db87802b 100644 --- a/TangemTests/UserWalletNameIndexationTests.swift +++ b/TangemTests/UserWalletNameIndexationTests.swift @@ -10,50 +10,11 @@ import XCTest @testable import Tangem class UserWalletNameIndexationTests: XCTestCase { - private var existingNameTestCases: [(String, String)] { - [ - ("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"), - ] - } - - private var newNameTestCases: [(String, String)] { - [ - ("Wallet", "Wallet 5"), - ("Note", "Note 4"), - ("Twin", "Twin 5"), - ("Start2Coin", "Start2Coin 2"), - ("Wallet 2.0", "Wallet 2.0 4"), - ("Tangem Card", "Tangem Card 2"), - ] - } - func testUserWalletNameIndexation() { - let numberOfShuffleTests = 10 - - for testNumber in 1 ... numberOfShuffleTests { - var generator = SeededNumberGenerator(seed: testNumber, length: existingNameTestCases.count) - XCTAssertNotNil(generator) - - let existingNames = existingNameTestCases.map(\.0).shuffled(using: &generator!) - let expectedNamesAfterMigration = existingNameTestCases.map(\.1).sorted() + 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) @@ -64,6 +25,7 @@ class UserWalletNameIndexationTests: XCTestCase { .sorted() XCTAssertEqual(migratedNames, expectedNamesAfterMigration) + let newNameTestCases = testCaseSet.newNameTestCases for newNameTestCase in newNameTestCases { XCTAssertEqual(nameMigrationHelper.suggestedName(newNameTestCase.0), newNameTestCase.1) } @@ -76,29 +38,337 @@ class UserWalletNameIndexationTests: XCTestCase { } } -private class SeededNumberGenerator: RandomNumberGenerator { - private let values: [UInt64] - private var index: Int = 0 - - init?(seed: Int, length: Int) { - guard length >= 1 else { return nil } - - srand48(seed) - - values = (1 ... length) - .map { _ in - let randomValue = drand48() - return UInt64(randomValue * Double(UInt64.max - 1)) - } +extension UserWalletNameIndexationTests { + struct TestCasesSet { + let existingNamesTestCases: [(String, String)] + let newNameTestCases: [(String, String)] } +} - func next() -> UInt64 { - let value = values[index] - if index < values.count - 1 { - index += 1 - } else { - index = 0 - } - return value +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"), + ] + ), + ] } }