-
Notifications
You must be signed in to change notification settings - Fork 33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
IOS-6610 add a mechanism for automatic indexation of wallets #3322
base: develop
Are you sure you want to change the base?
Changes from 3 commits
91467de
6034d4a
988136f
c14f65f
dc12931
ae2db03
8bded77
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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]) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Давай сделаем утилиту stateless, без mode. Просто набор методов и немного подрефачим для простоты публичные методы
Приватные
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ради простоты и тестов не хочется передавать сюда модели, поэтому сделал на строках. Передавать массив строк, получать его обратно и задавать имена массиву моделей выглядело ненадежно, поэтому сделал на скалярах There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. это комментарий не к тестам же There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Мой тоже |
||
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 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. если уже есть индекс его пропускаем, на дублирование не обращаем внимания? Может обсуждали где-то? в описании задачи не нашел There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Показывал Лехе результат миграции, он одобрил There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wallet, Wallet, Wallet2 превратятся в Wallet2, Wallet3, Wallet2 ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wallet -> Wallet (потому что такого нет) В первом куске тестов есть подобное |
||
("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() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Давай еще пожалуйста тесты упростим, чтобы было в стиле
Все на хардкодах, без рандомных числе и тп There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Тесты фактически на хардкодах, там рандом с сидом There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. тесты чем тупее написаны, тем лучше) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. В этих тестах ничего особо умного нет. Это тоже самое что 10 захардкоженных тесткейсов. Если так сильно режет глаза могу на 10 массивов заменить There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Дело не в режет. Беглого взгляда на тесты недостаточно, чтобы понять что тестируется и какой ожидаемый результат. Не хочется тратить время на ревью еще и тестов There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Отрефакторил |
||
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 | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indexes -> indices
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Что не так с indexes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Чаще используется форма indices, в том числе Apple, это не крит в этой задаче
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Я бы предложил оставить как есть чтобы не переусложнять