diff --git a/ios/PolkadotVault.xcodeproj/project.pbxproj b/ios/PolkadotVault.xcodeproj/project.pbxproj index 746e540ac2..39921c3dfd 100644 --- a/ios/PolkadotVault.xcodeproj/project.pbxproj +++ b/ios/PolkadotVault.xcodeproj/project.pbxproj @@ -163,7 +163,9 @@ 6D77863C2B626FD2009C8E73 /* CreateKeysForNetworksViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D77863B2B626FD2009C8E73 /* CreateKeysForNetworksViewModelTests.swift */; }; 6D77863F2B62FF6F009C8E73 /* EnterKeySetNameViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D77863E2B62FF6F009C8E73 /* EnterKeySetNameViewModelTests.swift */; }; 6D7786412B630116009C8E73 /* MNewSeedBackup+Generate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D7786402B630116009C8E73 /* MNewSeedBackup+Generate.swift */; }; + 6D7786442B630619009C8E73 /* RecoverKeySetNameViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D7786432B630619009C8E73 /* RecoverKeySetNameViewModelTests.swift */; }; 6D7786462B631262009C8E73 /* CreateKeySetSeedPhraseViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D7786452B631262009C8E73 /* CreateKeySetSeedPhraseViewModelTests.swift */; }; + 6D7786482B631739009C8E73 /* RecoverKeySetSeedPhraseViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D7786472B631739009C8E73 /* RecoverKeySetSeedPhraseViewModelTests.swift */; }; 6D77F31F296D0C5600044C7C /* CreateKeyNetworkSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D77F31E296D0C5600044C7C /* CreateKeyNetworkSelectionView.swift */; }; 6D77F321296D0D4600044C7C /* GetManagedNetworksService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D77F320296D0D4600044C7C /* GetManagedNetworksService.swift */; }; 6D77F323296D4D8900044C7C /* CreateDerivedKeyService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D77F322296D4D8900044C7C /* CreateDerivedKeyService.swift */; }; @@ -233,6 +235,7 @@ 6DAB52EB2B5E71FC005FDBA8 /* MLog+Generate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DAB52EA2B5E71FC005FDBA8 /* MLog+Generate.swift */; }; 6DAB52ED2B5E75E7005FDBA8 /* MTransactionCardSet+Generate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DAB52EC2B5E75E7005FDBA8 /* MTransactionCardSet+Generate.swift */; }; 6DAB52EF2B5E81BB005FDBA8 /* LogNoteModalViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DAB52EE2B5E81BB005FDBA8 /* LogNoteModalViewModelTests.swift */; }; + 6DAB52F22B5E832F005FDBA8 /* NoKeySetsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DAB52F12B5E832F005FDBA8 /* NoKeySetsViewModelTests.swift */; }; 6DAFCAF82B0A360600DDD165 /* CameraPermissionHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DAFCAF72B0A360600DDD165 /* CameraPermissionHandlerTests.swift */; }; 6DAFCAFA2B0AE5C000DDD165 /* KeychainAccessAdapterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DAFCAF92B0AE5C000DDD165 /* KeychainAccessAdapterTests.swift */; }; 6DAFCAFD2B0AE87300DDD165 /* RuntimePropertiesProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DAFCAFC2B0AE87300DDD165 /* RuntimePropertiesProviderTests.swift */; }; @@ -569,7 +572,9 @@ 6D77863B2B626FD2009C8E73 /* CreateKeysForNetworksViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateKeysForNetworksViewModelTests.swift; sourceTree = ""; }; 6D77863E2B62FF6F009C8E73 /* EnterKeySetNameViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnterKeySetNameViewModelTests.swift; sourceTree = ""; }; 6D7786402B630116009C8E73 /* MNewSeedBackup+Generate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MNewSeedBackup+Generate.swift"; sourceTree = ""; }; + 6D7786432B630619009C8E73 /* RecoverKeySetNameViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoverKeySetNameViewModelTests.swift; sourceTree = ""; }; 6D7786452B631262009C8E73 /* CreateKeySetSeedPhraseViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateKeySetSeedPhraseViewModelTests.swift; sourceTree = ""; }; + 6D7786472B631739009C8E73 /* RecoverKeySetSeedPhraseViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecoverKeySetSeedPhraseViewModelTests.swift; sourceTree = ""; }; 6D77F31E296D0C5600044C7C /* CreateKeyNetworkSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateKeyNetworkSelectionView.swift; sourceTree = ""; }; 6D77F320296D0D4600044C7C /* GetManagedNetworksService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GetManagedNetworksService.swift; sourceTree = ""; }; 6D77F322296D4D8900044C7C /* CreateDerivedKeyService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateDerivedKeyService.swift; sourceTree = ""; }; @@ -638,6 +643,7 @@ 6DAB52EA2B5E71FC005FDBA8 /* MLog+Generate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MLog+Generate.swift"; sourceTree = ""; }; 6DAB52EC2B5E75E7005FDBA8 /* MTransactionCardSet+Generate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MTransactionCardSet+Generate.swift"; sourceTree = ""; }; 6DAB52EE2B5E81BB005FDBA8 /* LogNoteModalViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogNoteModalViewModelTests.swift; sourceTree = ""; }; + 6DAB52F12B5E832F005FDBA8 /* NoKeySetsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoKeySetsViewModelTests.swift; sourceTree = ""; }; 6DAFCAF72B0A360600DDD165 /* CameraPermissionHandlerTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraPermissionHandlerTests.swift; sourceTree = ""; }; 6DAFCAF92B0AE5C000DDD165 /* KeychainAccessAdapterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainAccessAdapterTests.swift; sourceTree = ""; }; 6DAFCAFC2B0AE87300DDD165 /* RuntimePropertiesProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimePropertiesProviderTests.swift; sourceTree = ""; }; @@ -1551,6 +1557,15 @@ path = NewKeySet; sourceTree = ""; }; + 6D7786422B63060B009C8E73 /* RecoverKeySet */ = { + isa = PBXGroup; + children = ( + 6D7786432B630619009C8E73 /* RecoverKeySetNameViewModelTests.swift */, + 6D7786472B631739009C8E73 /* RecoverKeySetSeedPhraseViewModelTests.swift */, + ); + path = RecoverKeySet; + sourceTree = ""; + }; 6D77F31D296D0C4000044C7C /* DerivedKey */ = { isa = PBXGroup; children = ( @@ -1790,6 +1805,14 @@ path = Logs; sourceTree = ""; }; + 6DAB52F02B5E831B005FDBA8 /* NoKeySets */ = { + isa = PBXGroup; + children = ( + 6DAB52F12B5E832F005FDBA8 /* NoKeySetsViewModelTests.swift */, + ); + path = NoKeySets; + sourceTree = ""; + }; 6DAFCAF62B0A360600DDD165 /* Camera */ = { isa = PBXGroup; children = ( @@ -1972,6 +1995,7 @@ isa = PBXGroup; children = ( 6D7786392B626FB4009C8E73 /* CreateKey */, + 6DAB52F02B5E831B005FDBA8 /* NoKeySets */, 6DAB52E72B5E7182005FDBA8 /* Logs */, 6DB2E7BF2B4BBAEA002387DE /* Settings */, 6DDD38B52B1346C0000D2B62 /* PublicKey */, @@ -2768,9 +2792,11 @@ 6DB2E7CA2B4BBF6E002387DE /* MmNetwork+Generate.swift in Sources */, 6D686B9C2B45B36A007B7642 /* DevicePasscodeAuthenticatorTests.swift in Sources */, 6D80EB4B2B4E7034009C544B /* NetworkSettingDetailsActionModalViewModelTests.swift in Sources */, + 6DAB52F22B5E832F005FDBA8 /* NoKeySetsViewModelTests.swift in Sources */, 6D7786462B631262009C8E73 /* CreateKeySetSeedPhraseViewModelTests.swift in Sources */, 6D57DC54289D6CE900005C63 /* NavigationTests.swift in Sources */, 6DE48E8F2B1F0B96003094D5 /* AutoMockable+Y.generated.swift in Sources */, + 6D7786442B630619009C8E73 /* RecoverKeySetNameViewModelTests.swift in Sources */, 6DAFCB022B0AEE4900DDD165 /* ApplicationStatePublisherTests.swift in Sources */, 6DE48E842B1F0B96003094D5 /* AutoMockable+K.generated.swift in Sources */, 6DAB52EB2B5E71FC005FDBA8 /* MLog+Generate.swift in Sources */, @@ -2784,6 +2810,7 @@ 6DDD38B22B11C3C2000D2B62 /* SeedsMediatorTests.swift in Sources */, 6DB2E7C52B4BBC24002387DE /* BackupSelectKeyViewModelTests.swift in Sources */, 6D7786412B630116009C8E73 /* MNewSeedBackup+Generate.swift in Sources */, + 6D7786482B631739009C8E73 /* RecoverKeySetSeedPhraseViewModelTests.swift in Sources */, 6DE48E812B1F0B96003094D5 /* AutoMockable+G.generated.swift in Sources */, 6D57DC52289D68B800005C63 /* ActionResult+Generate.swift in Sources */, 6DAFCAFD2B0AE87300DDD165 /* RuntimePropertiesProviderTests.swift in Sources */, diff --git a/ios/PolkadotVault/Screens/CreateKey/RecoverKeySet/RecoverKeySetSeedPhraseView.swift b/ios/PolkadotVault/Screens/CreateKey/RecoverKeySet/RecoverKeySetSeedPhraseView.swift index 19f6e3fc6a..9e5ffa759a 100644 --- a/ios/PolkadotVault/Screens/CreateKey/RecoverKeySet/RecoverKeySetSeedPhraseView.swift +++ b/ios/PolkadotVault/Screens/CreateKey/RecoverKeySet/RecoverKeySetSeedPhraseView.swift @@ -217,16 +217,16 @@ extension RecoverKeySetSeedPhraseView { extension RecoverKeySetSeedPhraseView { final class ViewModel: ObservableObject { - private enum Constants { + enum Constants { static let invisibleNonEmptyCharacter = "\u{200B}" } private let seedsMediator: SeedsMediating private let textInput = TextInput() private var shouldSkipUpdate = false - private let service: RecoverKeySetService + private let service: RecoverKeySetServicing private let onCompletion: (CreateKeysForNetworksView.OnCompletionAction) -> Void - private let seedName: String + let seedName: String @Binding var isPresented: Bool @Published var isPresentingDetails: Bool = false @Published var isValidSeedPhrase: Bool = false @@ -235,7 +235,7 @@ extension RecoverKeySetSeedPhraseView { @Published var guesses: [String] = [] @Published var keyboardHeight: CGFloat = 0.0 - private var seedPhraseDraft: [String] = [] { + var seedPhraseDraft: [String] = [] { didSet { regenerateGrid() validateSeedPhrase() @@ -244,7 +244,7 @@ extension RecoverKeySetSeedPhraseView { } } - private var seedPhrase: String { + var seedPhrase: String { seedPhraseDraft.joined(separator: " ") } @@ -255,7 +255,7 @@ extension RecoverKeySetSeedPhraseView { seedName: String, isPresented: Binding, seedsMediator: SeedsMediating = ServiceLocator.seedsMediator, - service: RecoverKeySetService = RecoverKeySetService(), + service: RecoverKeySetServicing = RecoverKeySetService(), onCompletion: @escaping (CreateKeysForNetworksView.OnCompletionAction) -> Void ) { self.seedName = seedName @@ -274,28 +274,18 @@ extension RecoverKeySetSeedPhraseView { seedPhraseDraft.append(guess) } - private func updateGuesses(_ userInput: String) { - service.updateGuessWords(userInput: userInput) { result in - switch result { - case let .success(guesses): - self.guesses = guesses - case let .failure(error): - self.presentableError = .alertError(message: error.localizedDescription) - self.isPresentingError = true - } - } - } - func onUserInput(_ word: String) { guard !shouldSkipUpdate else { return } + defer { shouldSkipUpdate = false } shouldSkipUpdate = true // User input is empty and invisible character was deleted // This means that backspace was tapped, we should delete last saved word if word.isEmpty { seedPhraseDraft = Array(seedPhraseDraft.dropLast(1)) - userInput = Constants.invisibleNonEmptyCharacter - // User added " " while typing, we should check guess words or delete whitespace - } else if word.hasSuffix(" ") { + return + } + // User added " " while typing, we should check guess words or delete whitespace + if word.hasSuffix(" ") { let exactWord = String(word.dropFirst().dropLast(1)) // If there is a match, add this word and clear user input if guesses.contains(exactWord) { @@ -304,11 +294,10 @@ extension RecoverKeySetSeedPhraseView { } else { userInput = exactWord } - // User just added new character, generate new guesses - } else { - updateGuesses(String(word.dropFirst())) + return } - shouldSkipUpdate = false + // User just added new character, generate new guesses + updateGuesses(String(word.dropFirst())) } func onDoneTap() { @@ -324,32 +313,39 @@ extension RecoverKeySetSeedPhraseView { onCompletion: onCompletion ) } + } +} - private func validateSeedPhrase() { - service.validate(seedPhrase: seedPhrase) { result in - switch result { - case let .success(isValid): - self.isValidSeedPhrase = isValid - case let .failure(error): - self.presentableError = .alertError(message: error.localizedDescription) - self.isPresentingError = true - } +private extension RecoverKeySetSeedPhraseView.ViewModel { + func updateGuesses(_ userInput: String) { + service.updateGuessWords(userInput: userInput) { result in + switch result { + case let .success(guesses): + self.guesses = guesses + case let .failure(error): + self.presentableError = .alertError(message: error.localizedDescription) + self.isPresentingError = true } } } -} -private extension RecoverKeySetSeedPhraseView.ViewModel { func regenerateGrid() { var updatedGrid: [RecoverKeySetSeedPhraseView.GridElement] = seedPhraseDraft.enumerated() .map { .seedPhraseElement(.init(position: String($0.offset + 1), word: $0.element)) } updatedGrid.append(.input(textInput)) seedPhraseGrid = updatedGrid } -} -private extension MRecoverSeedPhrase { - func draftPhrase() -> String { - draft.joined(separator: " ") + func validateSeedPhrase() { + service.validate(seedPhrase: seedPhrase) { result in + switch result { + case let .success(isValid): + self.isValidSeedPhrase = isValid + case let .failure(error): + self.isValidSeedPhrase = false + self.presentableError = .alertError(message: error.localizedDescription) + self.isPresentingError = true + } + } } } diff --git a/ios/PolkadotVaultTests/Screens/CreateKey/RecoverKeySet/RecoverKeySetNameViewModelTests.swift b/ios/PolkadotVaultTests/Screens/CreateKey/RecoverKeySet/RecoverKeySetNameViewModelTests.swift new file mode 100644 index 0000000000..7c36ecd2bc --- /dev/null +++ b/ios/PolkadotVaultTests/Screens/CreateKey/RecoverKeySet/RecoverKeySetNameViewModelTests.swift @@ -0,0 +1,117 @@ +// +// RecoverKeySetNameViewModelTests.swift +// PolkadotVaultTests +// +// Created by Krzysztof Rodak on 29/01/2024. +// + +import Foundation +@testable import PolkadotVault +import SwiftUI +import XCTest + +final class RecoverKeySetNameViewModelTests: XCTestCase { + private var viewModel: RecoverKeySetNameView.ViewModel! + private var seedsMediatorMock: SeedsMediatingMock! + private var isPresented: Bool = false + private var onCompletionExecuted: Bool = false + + override func setUp() { + super.setUp() + seedsMediatorMock = SeedsMediatingMock() + isPresented = false + onCompletionExecuted = false + viewModel = RecoverKeySetNameView.ViewModel( + seedsMediator: seedsMediatorMock, + isPresented: Binding(get: { self.isPresented }, set: { self.isPresented = $0 }), + onCompletion: { _ in self.onCompletionExecuted = true } + ) + } + + override func tearDown() { + viewModel = nil + seedsMediatorMock = nil + super.tearDown() + } + + func testInitialSetup() { + // Then + XCTAssertTrue(viewModel.seedName.isEmpty) + XCTAssertFalse(viewModel.isPresentingDetails) + } + + func testOnBackTap() { + // When + viewModel.onBackTap() + + // Then + XCTAssertFalse(isPresented) + } + + func testOnNextTap() { + // When + viewModel.onNextTap() + + // Then + XCTAssertTrue(viewModel.isPresentingDetails) + } + + func testIsActionAvailable() { + // Given + viewModel.seedName = "ValidSeedName" + seedsMediatorMock.checkSeedCollisionSeedNameReturnValue = false + + // When + let result = viewModel.isActionAvailable() + + // Then + XCTAssertTrue(result) + } + + func testIsActionNotAvailableDueToEmptySeedName() { + // Given + viewModel.seedName = "" + + // When + let result = viewModel.isActionAvailable() + + // Then + XCTAssertFalse(result) + } + + func testIsActionNotAvailableDueToSeedNameCollision() { + // Given + viewModel.seedName = "CollidingSeedName" + seedsMediatorMock.checkSeedCollisionSeedNameReturnValue = true + + // When + let result = viewModel.isActionAvailable() + + // Then + XCTAssertFalse(result) + } + + func testOnSubmitTap_ActionAvailable() { + // Given + viewModel.seedName = "ValidSeedName" + seedsMediatorMock.checkSeedCollisionSeedNameReturnValue = false + + // When + viewModel.onSubmitTap() + + // Then + XCTAssertTrue(viewModel.isPresentingDetails) + } + + func testOnSubmitTap_ActionNotAvailable() { + // Given + viewModel.seedName = "CollidingSeedName" + seedsMediatorMock.checkSeedCollisionSeedNameReturnValue = true + + // When + viewModel.onSubmitTap() + + // Then + XCTAssertFalse(viewModel.isPresentingDetails) + } +} diff --git a/ios/PolkadotVaultTests/Screens/CreateKey/RecoverKeySet/RecoverKeySetSeedPhraseViewModelTests.swift b/ios/PolkadotVaultTests/Screens/CreateKey/RecoverKeySet/RecoverKeySetSeedPhraseViewModelTests.swift new file mode 100644 index 0000000000..1e26f75ffe --- /dev/null +++ b/ios/PolkadotVaultTests/Screens/CreateKey/RecoverKeySet/RecoverKeySetSeedPhraseViewModelTests.swift @@ -0,0 +1,228 @@ +// +// RecoverKeySetSeedPhraseViewModelTests.swift +// PolkadotVaultTests +// +// Created by Krzysztof Rodak on 30/01/2024. +// + +import Foundation +@testable import PolkadotVault +import SwiftUI +import XCTest + +final class RecoverKeySetSeedPhraseViewModelTests: XCTestCase { + private var viewModel: RecoverKeySetSeedPhraseView.ViewModel! + private var serviceMock: RecoverKeySetServicingMock! + private var seedsMediatorMock: SeedsMediatingMock! + private var isPresented: Bool = false + private var onCompletionExecuted: Bool = false + + override func setUp() { + super.setUp() + serviceMock = RecoverKeySetServicingMock() + seedsMediatorMock = SeedsMediatingMock() + isPresented = false + onCompletionExecuted = false + viewModel = RecoverKeySetSeedPhraseView.ViewModel( + seedName: "testSeed", + isPresented: Binding(get: { self.isPresented }, set: { self.isPresented = $0 }), + seedsMediator: seedsMediatorMock, + service: serviceMock, + onCompletion: { _ in self.onCompletionExecuted = true } + ) + } + + override func tearDown() { + viewModel = nil + serviceMock = nil + seedsMediatorMock = nil + super.tearDown() + } + + func testInitialSetup() { + // Then + XCTAssertFalse(viewModel.isPresentingDetails) + XCTAssertFalse(viewModel.isValidSeedPhrase) + XCTAssertEqual(viewModel.userInput, RecoverKeySetSeedPhraseView.ViewModel.Constants.invisibleNonEmptyCharacter) + } + + func testOnAppear_requestGuesses() { + // When + viewModel.onAppear() + + // Then + XCTAssertEqual(serviceMock.updateGuessWordsUserInputCallsCount, 1) + XCTAssertEqual(serviceMock.updateGuessWordsUserInputReceivedUserInput, [""]) + } + + func testOnAppear_updatesGuesses_onSuccess() { + // Given + let guesses = ["abc", "bcd"] + + // When + viewModel.onAppear() + serviceMock.updateGuessWordsUserInputReceivedCompletion.first?(.success(guesses)) + + // Then + XCTAssertEqual(viewModel.guesses, guesses) + } + + func testOnAppear_updatesGuesses_onFailure() { + // Given + let error = ServiceError(message: "Error") + let expectedErrorMessage = ErrorBottomModalViewModel.alertError(message: error.localizedDescription) + + // When + viewModel.onAppear() + serviceMock.updateGuessWordsUserInputReceivedCompletion.first?(.failure(error)) + + // Then + XCTAssert(viewModel.isPresentingError) + XCTAssertEqual(viewModel.presentableError, expectedErrorMessage) + } + + func testOnGuessTap_AddsGuessToSeedPhraseDraft() { + // Given + let guess = "testGuess" + + // When + viewModel.onGuessTap(guess) + + // Then + XCTAssertTrue(viewModel.seedPhraseDraft.contains(guess)) + } + + func testOnUserInput_whenUserInputEmpty_deletesLastWordFromDraftAndUpdatesUserInput_updatesGuesses() { + // Given + let userInput = "" + let currentPhraseDraft = ["abc", "bcd"] + let expectedPhraseDraft = ["abc"] + let expectedUserInput = RecoverKeySetSeedPhraseView.ViewModel.Constants.invisibleNonEmptyCharacter + viewModel.seedPhraseDraft = currentPhraseDraft + + // When + viewModel.onUserInput(userInput) + + // Then + XCTAssertEqual(viewModel.seedPhraseDraft, expectedPhraseDraft) + XCTAssertEqual(viewModel.userInput, expectedUserInput) + XCTAssertEqual(serviceMock.updateGuessWordsUserInputCallsCount, 2) + XCTAssertEqual(serviceMock.updateGuessWordsUserInputReceivedUserInput, ["", ""]) + } + + func testOnUserInput_whenUserInputEndsWithWhitespace_whenGuessExist_guessIsAdded_guessesAreUpdated() { + // Given + let userInput = "\(RecoverKeySetSeedPhraseView.ViewModel.Constants.invisibleNonEmptyCharacter)abc " + let expectedUserInput = RecoverKeySetSeedPhraseView.ViewModel.Constants.invisibleNonEmptyCharacter + let expectedPhraseDraft = ["abc"] + viewModel.guesses = ["abc"] + viewModel.seedPhraseDraft = [] + + // When + viewModel.onUserInput(userInput) + + // Then + XCTAssertEqual(viewModel.seedPhraseDraft, expectedPhraseDraft) + XCTAssertEqual(viewModel.userInput, expectedUserInput) + XCTAssertEqual(serviceMock.updateGuessWordsUserInputCallsCount, 2) + XCTAssertEqual(serviceMock.updateGuessWordsUserInputReceivedUserInput, ["", ""]) + } + + func testOnUserInput_whenUserInputEndsWithWhitespace_whenGuessDoesNotExist_whitespaceIsDeleted_guessesAreNotUpdated( + ) { + // Given + let userInput = "\(RecoverKeySetSeedPhraseView.ViewModel.Constants.invisibleNonEmptyCharacter)abc " + let expectedUserInput = "abc" + viewModel.guesses = ["bcd"] + viewModel.seedPhraseDraft = [] + + // When + viewModel.onUserInput(userInput) + + // Then + XCTAssertEqual(viewModel.seedPhraseDraft, []) + XCTAssertEqual(viewModel.userInput, expectedUserInput) + XCTAssertEqual(serviceMock.updateGuessWordsUserInputCallsCount, 1) + } + + func testOnUserInput_whenUserInputIsAnotherCharacter_guessesAreUpdated() { + // Given + let userInput = "\(RecoverKeySetSeedPhraseView.ViewModel.Constants.invisibleNonEmptyCharacter)a" + viewModel.seedPhraseDraft = [] + + // When + viewModel.onUserInput(userInput) + + // Then + XCTAssertEqual(viewModel.seedPhraseDraft, []) + XCTAssertEqual(serviceMock.updateGuessWordsUserInputCallsCount, 2) + XCTAssertEqual(serviceMock.updateGuessWordsUserInputReceivedUserInput, ["", "a"]) + } + + func testOnUserInput_whenSeedPhraseDraftIsUpdated_seedPhraseIsValidated() { + // Given + let userInput = "\(RecoverKeySetSeedPhraseView.ViewModel.Constants.invisibleNonEmptyCharacter)abc " + let expectedSeedPhrase = "bcd abc" + viewModel.guesses = ["abc"] + viewModel.seedPhraseDraft = ["bcd"] + + // When + viewModel.onUserInput(userInput) + + // Then + XCTAssertEqual(viewModel.seedPhrase, expectedSeedPhrase) + XCTAssertEqual(serviceMock.validateSeedPhraseCallsCount, 2) + XCTAssertEqual(serviceMock.validateSeedPhraseReceivedSeedPhrase, ["bcd", expectedSeedPhrase]) + } + + func testOnUserInput_whenValidation_whenSuccess_isValidIsUpdated() { + // Given + let userInput = "\(RecoverKeySetSeedPhraseView.ViewModel.Constants.invisibleNonEmptyCharacter)abc " + let expectedSeedPhrase = "bcd abc" + viewModel.isValidSeedPhrase = false + viewModel.guesses = ["abc"] + viewModel.seedPhraseDraft = ["bcd"] + + // When + viewModel.onUserInput(userInput) + serviceMock.validateSeedPhraseReceivedCompletion.first?(.success(true)) + + // Then + XCTAssert(viewModel.isValidSeedPhrase) + } + + func testOnUserInput_whenValidation_whenFailure_errorIsPresented_isValidIsSetToFalse() { + // Given + let error = ServiceError(message: "Error") + let expectedErrorMessage = ErrorBottomModalViewModel.alertError(message: error.localizedDescription) + let userInput = "\(RecoverKeySetSeedPhraseView.ViewModel.Constants.invisibleNonEmptyCharacter)abc " + viewModel.isValidSeedPhrase = true + viewModel.guesses = ["abc"] + viewModel.seedPhraseDraft = ["bcd"] + + // When + viewModel.onUserInput(userInput) + serviceMock.validateSeedPhraseReceivedCompletion.first?(.failure(error)) + + // Then + XCTAssert(viewModel.isPresentingError) + XCTAssertEqual(viewModel.presentableError, expectedErrorMessage) + } + + func testOnDoneTap_SetsIsPresentingDetails() { + // When + viewModel.onDoneTap() + + // Then + XCTAssertTrue(viewModel.isPresentingDetails) + } + + func testCreateDerivedKeys_ReturnsExpectedViewModel() { + // When + let derivedViewModel = viewModel.createDerivedKeys() + + // Then + XCTAssertEqual(derivedViewModel.seedName, viewModel.seedName) + XCTAssertEqual(derivedViewModel.seedPhrase, viewModel.seedPhrase) + } +} diff --git a/ios/PolkadotVaultTests/Screens/NoKeySets/NoKeySetsViewModelTests.swift b/ios/PolkadotVaultTests/Screens/NoKeySets/NoKeySetsViewModelTests.swift new file mode 100644 index 0000000000..96ee5028ad --- /dev/null +++ b/ios/PolkadotVaultTests/Screens/NoKeySets/NoKeySetsViewModelTests.swift @@ -0,0 +1,62 @@ +// +// NoKeySetsViewModelTests.swift +// PolkadotVaultTests +// +// Created by Krzysztof Rodak on 31/01/2024. +// + +import Combine +import Foundation +@testable import PolkadotVault +import XCTest + +final class NoKeySetsViewModelTests: XCTestCase { + private var viewModel: NoKeySetsView.ViewModel! + private var onCompletionActionExecuted: CreateKeysForNetworksView.OnCompletionAction? + + override func setUp() { + super.setUp() + viewModel = NoKeySetsView.ViewModel { [weak self] action in + self?.onCompletionActionExecuted = action + } + } + + override func tearDown() { + viewModel = nil + onCompletionActionExecuted = nil + super.tearDown() + } + + func testInit_InitialState() { + // Then + XCTAssertFalse(viewModel.isPresentingAddKeySet) + XCTAssertFalse(viewModel.isPresentingRecoverKeySet) + } + + func testOnAddTap_SetsIsPresentingAddKeySetToTrue() { + // When + viewModel.onAddTap() + + // Then + XCTAssertTrue(viewModel.isPresentingAddKeySet) + } + + func testOnRecoverTap_SetsIsPresentingRecoverKeySetToTrue() { + // When + viewModel.onRecoverTap() + + // Then + XCTAssertTrue(viewModel.isPresentingRecoverKeySet) + } + + func testOnKeySetAddCompletion_ExecutesOnCompletion() { + // Given + let completionAction = CreateKeysForNetworksView.OnCompletionAction.createKeySet(seedName: "seedName") + + // When + viewModel.onKeySetAddCompletion(completionAction) + + // Then + XCTAssertEqual(onCompletionActionExecuted, completionAction) + } +}