diff --git a/ios/PolkadotVault.xcodeproj/project.pbxproj b/ios/PolkadotVault.xcodeproj/project.pbxproj index fad8c82553..9a03bc8963 100644 --- a/ios/PolkadotVault.xcodeproj/project.pbxproj +++ b/ios/PolkadotVault.xcodeproj/project.pbxproj @@ -169,6 +169,9 @@ 6D8045D928D0761E00237F8C /* QRCodeAddressFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8045D828D0761E00237F8C /* QRCodeAddressFooterView.swift */; }; 6D8045DC28D0840F00237F8C /* MKeyDetails+Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8045DB28D0840F00237F8C /* MKeyDetails+Helpers.swift */; }; 6D8045DE28D087D400237F8C /* QRCodeRootFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D8045DD28D087D400237F8C /* QRCodeRootFooterView.swift */; }; + 6D80EB4B2B4E7034009C544B /* NetworkSettingDetailsActionModalViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D80EB4A2B4E7034009C544B /* NetworkSettingDetailsActionModalViewModelTests.swift */; }; + 6D80EB522B4EB0B8009C544B /* MSufficientCryptoReady+Generate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D80EB512B4EB0B8009C544B /* MSufficientCryptoReady+Generate.swift */; }; + 6D80EB542B4EB0C8009C544B /* MAddressCard+Generate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D80EB532B4EB0C8009C544B /* MAddressCard+Generate.swift */; }; 6D80EB492B4CF582009C544B /* BananaSplitService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D80EB482B4CF582009C544B /* BananaSplitService.swift */; }; 6D84442428D3424F0072FBAC /* HiddenScrollContainerModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D84442328D3424F0072FBAC /* HiddenScrollContainerModifier.swift */; }; 6D850BDE292F7B4D00BA9017 /* EnterPasswordModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D850BDD292F7B4D00BA9017 /* EnterPasswordModal.swift */; }; @@ -225,6 +228,7 @@ 6DAFCB042B0AEF6800DDD165 /* PasswordProtectionStatePublisherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DAFCB032B0AEF6800DDD165 /* PasswordProtectionStatePublisherTests.swift */; }; 6DB2E7C12B4BBAF7002387DE /* SettingsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB2E7C02B4BBAF7002387DE /* SettingsViewModelTests.swift */; }; 6DB2E7C52B4BBC24002387DE /* BackupSelectKeyViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB2E7C42B4BBC24002387DE /* BackupSelectKeyViewModelTests.swift */; }; + 6DB2E7C82B4BBEB0002387DE /* NetworkSelectionSettingsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB2E7C72B4BBEB0002387DE /* NetworkSelectionSettingsViewModelTests.swift */; }; 6DB2E7CA2B4BBF6E002387DE /* MmNetwork+Generate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB2E7C92B4BBF6E002387DE /* MmNetwork+Generate.swift */; }; 6DB2E7CC2B4BBF78002387DE /* MNetworkDetails+Generate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB2E7CB2B4BBF78002387DE /* MNetworkDetails+Generate.swift */; }; 6DB39AA42A4579E0004B8FAA /* AddDerivedKeysView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6DB39AA32A4579E0004B8FAA /* AddDerivedKeysView.swift */; }; @@ -553,6 +557,9 @@ 6D8045D828D0761E00237F8C /* QRCodeAddressFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeAddressFooterView.swift; sourceTree = ""; }; 6D8045DB28D0840F00237F8C /* MKeyDetails+Helpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MKeyDetails+Helpers.swift"; sourceTree = ""; }; 6D8045DD28D087D400237F8C /* QRCodeRootFooterView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QRCodeRootFooterView.swift; sourceTree = ""; }; + 6D80EB4A2B4E7034009C544B /* NetworkSettingDetailsActionModalViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkSettingDetailsActionModalViewModelTests.swift; sourceTree = ""; }; + 6D80EB512B4EB0B8009C544B /* MSufficientCryptoReady+Generate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MSufficientCryptoReady+Generate.swift"; sourceTree = ""; }; + 6D80EB532B4EB0C8009C544B /* MAddressCard+Generate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MAddressCard+Generate.swift"; sourceTree = ""; }; 6D80EB482B4CF582009C544B /* BananaSplitService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BananaSplitService.swift; sourceTree = ""; }; 6D84442328D3424F0072FBAC /* HiddenScrollContainerModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HiddenScrollContainerModifier.swift; sourceTree = ""; }; 6D850BDD292F7B4D00BA9017 /* EnterPasswordModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnterPasswordModal.swift; sourceTree = ""; }; @@ -608,6 +615,7 @@ 6DAFCB032B0AEF6800DDD165 /* PasswordProtectionStatePublisherTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasswordProtectionStatePublisherTests.swift; sourceTree = ""; }; 6DB2E7C02B4BBAF7002387DE /* SettingsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModelTests.swift; sourceTree = ""; }; 6DB2E7C42B4BBC24002387DE /* BackupSelectKeyViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupSelectKeyViewModelTests.swift; sourceTree = ""; }; + 6DB2E7C72B4BBEB0002387DE /* NetworkSelectionSettingsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkSelectionSettingsViewModelTests.swift; sourceTree = ""; }; 6DB2E7C92B4BBF6E002387DE /* MmNetwork+Generate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MmNetwork+Generate.swift"; sourceTree = ""; }; 6DB2E7CB2B4BBF78002387DE /* MNetworkDetails+Generate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MNetworkDetails+Generate.swift"; sourceTree = ""; }; 6DB39AA32A4579E0004B8FAA /* AddDerivedKeysView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddDerivedKeysView.swift; sourceTree = ""; }; @@ -1569,6 +1577,8 @@ 6D57DC51289D68B800005C63 /* ActionResult+Generate.swift */, 6DB2E7C92B4BBF6E002387DE /* MmNetwork+Generate.swift */, 6DB2E7CB2B4BBF78002387DE /* MNetworkDetails+Generate.swift */, + 6D80EB512B4EB0B8009C544B /* MSufficientCryptoReady+Generate.swift */, + 6D80EB532B4EB0C8009C544B /* MAddressCard+Generate.swift */, ); path = "Models+Generate"; sourceTree = ""; @@ -1728,6 +1738,8 @@ 6DB2E7C62B4BBEA0002387DE /* NetworkSelectionSettings */ = { isa = PBXGroup; children = ( + 6DB2E7C72B4BBEB0002387DE /* NetworkSelectionSettingsViewModelTests.swift */, + 6D80EB4A2B4E7034009C544B /* NetworkSettingDetailsActionModalViewModelTests.swift */, ); path = NetworkSelectionSettings; sourceTree = ""; @@ -2151,7 +2163,7 @@ attributes = { BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 1250; - LastUpgradeCheck = 1500; + LastUpgradeCheck = 1510; TargetAttributes = { 2DE72BBD26A588C7002BB752 = { CreatedOnToolsVersion = 12.5.1; @@ -2639,6 +2651,7 @@ 6DE48E8E2B1F0B96003094D5 /* AutoMockable+B.generated.swift in Sources */, 6DE48E992B1F14A0003094D5 /* QRCodeAddressFooterViewModel+Generate.swift in Sources */, 6DE48E832B1F0B96003094D5 /* AutoMockable+C.generated.swift in Sources */, + 6D80EB542B4EB0C8009C544B /* MAddressCard+Generate.swift in Sources */, 6D8AF88228BCC4D100CF0AB2 /* AccessControlProvidingAssemblerTests.swift in Sources */, 6DE48E8D2B1F0B96003094D5 /* AutoMockable+S.generated.swift in Sources */, 6DE48E872B1F0B96003094D5 /* AutoMockable+E.generated.swift in Sources */, @@ -2646,6 +2659,7 @@ 6DE48E8A2B1F0B96003094D5 /* AutoMockable+V.generated.swift in Sources */, 6DB2E7CA2B4BBF6E002387DE /* MmNetwork+Generate.swift in Sources */, 6D686B9C2B45B36A007B7642 /* DevicePasscodeAuthenticatorTests.swift in Sources */, + 6D80EB4B2B4E7034009C544B /* NetworkSettingDetailsActionModalViewModelTests.swift in Sources */, 6D57DC54289D6CE900005C63 /* NavigationTests.swift in Sources */, 6DE48E8F2B1F0B96003094D5 /* AutoMockable+Y.generated.swift in Sources */, 6DAFCB022B0AEE4900DDD165 /* ApplicationStatePublisherTests.swift in Sources */, @@ -2662,6 +2676,7 @@ 6DE48E812B1F0B96003094D5 /* AutoMockable+G.generated.swift in Sources */, 6D57DC52289D68B800005C63 /* ActionResult+Generate.swift in Sources */, 6DAFCAFD2B0AE87300DDD165 /* RuntimePropertiesProviderTests.swift in Sources */, + 6DB2E7C82B4BBEB0002387DE /* NetworkSelectionSettingsViewModelTests.swift in Sources */, 6DBD2202289A8E1F005D539B /* ErrorMock.swift in Sources */, 6DC2EDF92B11961800298F00 /* DateFormatterTests.swift in Sources */, 6DDD38B72B1346C8000D2B62 /* KeyDetailsPublicKeyViewModelTests.swift in Sources */, @@ -2673,6 +2688,7 @@ 6DAFCAF82B0A360600DDD165 /* CameraPermissionHandlerTests.swift in Sources */, 6DE48E952B1F0B96003094D5 /* AutoMockable+Q.generated.swift in Sources */, 6DE48E802B1F0B96003094D5 /* AutoMockable+P.generated.swift in Sources */, + 6D80EB522B4EB0B8009C544B /* MSufficientCryptoReady+Generate.swift in Sources */, 6DE48E8C2B1F0B96003094D5 /* AutoMockable+R.generated.swift in Sources */, 6D5801E5289937BA006C41D8 /* ConnectivityMonitoringAssemblerTests.swift in Sources */, 6DE07B102B450EB7001AF54C /* OnboardingMediatorTests.swift in Sources */, diff --git a/ios/PolkadotVault.xcodeproj/xcshareddata/xcschemes/PolkadotVault-Dev.xcscheme b/ios/PolkadotVault.xcodeproj/xcshareddata/xcschemes/PolkadotVault-Dev.xcscheme index f8ff8f9974..5a8186381f 100644 --- a/ios/PolkadotVault.xcodeproj/xcshareddata/xcschemes/PolkadotVault-Dev.xcscheme +++ b/ios/PolkadotVault.xcodeproj/xcshareddata/xcschemes/PolkadotVault-Dev.xcscheme @@ -1,6 +1,6 @@ { dismissRequest.eraseToAnyPublisher() } private let dismissRequest = PassthroughSubject() @@ -345,7 +347,7 @@ extension NetworkSettingsDetails { @Published var specSignType: SpecSignType! @Published var isPresentingSignSpecList: Bool = false @Published var isShowingQRScanner: Bool = false - var snackbarViewModel: SnackbarViewModel = .init(title: "") + @Published var snackbarViewModel: SnackbarViewModel = .init(title: "") @Published var isSnackbarPresented: Bool = false @Published var isPresentingError: Bool = false @Published var presentableError: ErrorBottomModalViewModel = .alertError(message: "") @@ -353,7 +355,7 @@ extension NetworkSettingsDetails { init( networkKey: String, networkDetails: MNetworkDetails, - networkDetailsService: ManageNetworkDetailsService = ManageNetworkDetailsService(), + networkDetailsService: ManageNetworkDetailsServicing = ManageNetworkDetailsService(), onCompletion: @escaping (OnCompletionAction) -> Void ) { self.networkKey = networkKey @@ -467,10 +469,12 @@ private extension NetworkSettingsDetails.ViewModel { func listenToNavigationUpdates() { guard cancelBag.subscriptions.isEmpty else { return } - $isPresentingSignSpecList.sink { [weak self] isPresentingSignSpecList in - guard let self, !isPresentingSignSpecList else { return } - updateView() - }.store(in: cancelBag) + $isPresentingSignSpecList + .dropFirst() + .sink { [weak self] isPresentingSignSpecList in + guard let self, !isPresentingSignSpecList else { return } + updateView() + }.store(in: cancelBag) } } diff --git a/ios/PolkadotVault/Screens/Settings/Subviews/Networks/NetworkSettingsDetailsActionModal.swift b/ios/PolkadotVault/Screens/Settings/Subviews/Networks/NetworkSettingsDetailsActionModal.swift index 48a4c29827..36ede28333 100644 --- a/ios/PolkadotVault/Screens/Settings/Subviews/Networks/NetworkSettingsDetailsActionModal.swift +++ b/ios/PolkadotVault/Screens/Settings/Subviews/Networks/NetworkSettingsDetailsActionModal.swift @@ -8,32 +8,29 @@ import SwiftUI struct NetworkSettingsDetailsActionModal: View { - @State private var animateBackground: Bool = false - @Binding var isShowingActionSheet: Bool - @Binding var shouldPresentRemoveNetworkConfirmation: Bool - @Binding var shouldSignSpecs: Bool + @StateObject var viewModel: ViewModel var body: some View { FullScreenRoundedModal( - backgroundTapAction: { animateDismissal() }, - animateBackground: $animateBackground, + backgroundTapAction: { viewModel.dismissActionSheet() }, + animateBackground: $viewModel.animateBackground, content: { VStack(alignment: .leading, spacing: 0) { // Sign Specs ActionSheetButton( - action: { animateDismissal { shouldSignSpecs = true } }, + action: { viewModel.toggleSignSpecs() }, icon: Image(.signSpecs), text: Localizable.Settings.NetworkDetails.More.Action.sign.key ) // Remove Network ActionSheetButton( - action: { animateDismissal { shouldPresentRemoveNetworkConfirmation = true } }, + action: { viewModel.toggleRemoveNetworkConfirmation() }, icon: Image(.delete), text: Localizable.Settings.NetworkDetails.More.Action.delete.key, style: .destructive ) ActionButton( - action: { animateDismissal() }, + action: { viewModel.dismissActionSheet() }, text: Localizable.LogsList.More.Action.cancel.key, style: .emptySecondary() ) @@ -44,14 +41,63 @@ struct NetworkSettingsDetailsActionModal: View { } ) } +} - private func animateDismissal(_ completion: @escaping () -> Void = {}) { - Animations.chainAnimation( - animateBackground.toggle(), - delayedAnimationClosure: { - isShowingActionSheet = false - completion() - }() - ) +extension NetworkSettingsDetailsActionModal { + final class ViewModel: ObservableObject { + @Published var animateBackground: Bool = false + @Binding var isPresented: Bool + @Binding var shouldPresentRemoveNetworkConfirmation: Bool + @Binding var shouldSignSpecs: Bool + + init( + isPresented: Binding, + shouldPresentRemoveNetworkConfirmation: Binding, + shouldSignSpecs: Binding + ) { + _isPresented = isPresented + _shouldPresentRemoveNetworkConfirmation = shouldPresentRemoveNetworkConfirmation + _shouldSignSpecs = shouldSignSpecs + } + + func toggleSignSpecs() { + shouldSignSpecs = true + animateDismissal() + } + + func toggleRemoveNetworkConfirmation() { + shouldPresentRemoveNetworkConfirmation = true + animateDismissal() + } + + func dismissActionSheet() { + animateDismissal() + } + + func animateDismissal() { + Animations.chainAnimation( + animateBackground.toggle(), + // swiftformat:disable all + delayedAnimationClosure: self.hide() + ) + } + + private func hide() { + isPresented = false + } } } + +#if DEBUG + struct NetworkSettingsDetailsActionModal_Previews: PreviewProvider { + static var previews: some View { + NetworkSettingsDetailsActionModal( + viewModel: .init( + isPresented: .constant(true), + shouldPresentRemoveNetworkConfirmation: .constant(false), + shouldSignSpecs: .constant(false) + ) + ) + } + } +#endif diff --git a/ios/PolkadotVaultTests/Models+Generate/MAddressCard+Generate.swift b/ios/PolkadotVaultTests/Models+Generate/MAddressCard+Generate.swift new file mode 100644 index 0000000000..fafbbc4510 --- /dev/null +++ b/ios/PolkadotVaultTests/Models+Generate/MAddressCard+Generate.swift @@ -0,0 +1,41 @@ +// +// MAddressCard+Generate.swift +// PolkadotVaultTests +// +// Created by Krzysztof Rodak on 10/01/2024. +// + +import Foundation +@testable import PolkadotVault + +extension MAddressCard { + static func generate( + base58: String = "defaultBase58", + addressKey: String = "defaultAddressKey", + address: Address = .generate() + ) -> MAddressCard { + MAddressCard( + base58: base58, + addressKey: addressKey, + address: address + ) + } +} + +extension Address { + static func generate( + path: String = "//polkadot//0", + hasPwd: Bool = false, + identicon: Identicon = Identicon.generate(), + seedName: String = "Main Key Set", + secretExposed: Bool = false + ) -> Address { + Address( + path: path, + hasPwd: hasPwd, + identicon: identicon, + seedName: seedName, + secretExposed: secretExposed + ) + } +} diff --git a/ios/PolkadotVaultTests/Models+Generate/MSufficientCryptoReady+Generate.swift b/ios/PolkadotVaultTests/Models+Generate/MSufficientCryptoReady+Generate.swift new file mode 100644 index 0000000000..6bdd4b5dd8 --- /dev/null +++ b/ios/PolkadotVaultTests/Models+Generate/MSufficientCryptoReady+Generate.swift @@ -0,0 +1,49 @@ +// +// MSufficientCryptoReady+Generate.swift +// PolkadotVaultTests +// +// Created by Krzysztof Rodak on 10/01/2024. +// + +import Foundation +@testable import PolkadotVault + +extension MSufficientCryptoReady { + static func generate( + authorInfo: MAddressCard = MAddressCard.generate(), + sufficient: [UInt8] = [1, 2, 3], + content: MscContent = MscContent.addSpecs(f: .generate()), + networkLogo: String? = "defaultNetworkLogo" + ) -> MSufficientCryptoReady { + MSufficientCryptoReady( + authorInfo: authorInfo, + sufficient: sufficient, + content: content, + networkLogo: networkLogo + ) + } +} + +extension MSignSufficientCrypto { + static func generate( + identities: [MRawKey] = [MRawKey.generate()] + ) -> MSignSufficientCrypto { + MSignSufficientCrypto(identities: identities) + } +} + +extension MRawKey { + static func generate( + address: Address = Address.generate(), + addressKey: String = "defaultAddressKey", + publicKey: String = "defaultPublicKey", + networkLogo: String = "defaultNetworkLogo" + ) -> MRawKey { + MRawKey( + address: address, + addressKey: addressKey, + publicKey: publicKey, + networkLogo: networkLogo + ) + } +} diff --git a/ios/PolkadotVaultTests/Screens/Settings/Subview/NetworkSelectionSettings/NetworkSelectionSettingsViewModelTests.swift b/ios/PolkadotVaultTests/Screens/Settings/Subview/NetworkSelectionSettings/NetworkSelectionSettingsViewModelTests.swift new file mode 100644 index 0000000000..02df26f124 --- /dev/null +++ b/ios/PolkadotVaultTests/Screens/Settings/Subview/NetworkSelectionSettings/NetworkSelectionSettingsViewModelTests.swift @@ -0,0 +1,138 @@ +// +// NetworkSelectionSettingsViewModelTests.swift +// PolkadotVaultTests +// +// Created by Krzysztof Rodak on 11/01/2024. +// + +import Combine +@testable import PolkadotVault +import XCTest + +final class NetworkSelectionSettingsViewModelTests: XCTestCase { + private var viewModel: NetworkSelectionSettings.ViewModel! + private var getManagedNetworksServiceMock: GetManagedNetworksServicingMock! + private var manageNetworkDetailsServiceMock: ManageNetworkDetailsServicingMock! + + override func setUp() { + super.setUp() + getManagedNetworksServiceMock = GetManagedNetworksServicingMock() + manageNetworkDetailsServiceMock = ManageNetworkDetailsServicingMock() + viewModel = NetworkSelectionSettings.ViewModel( + service: getManagedNetworksServiceMock, + networkDetailsService: manageNetworkDetailsServiceMock + ) + } + + override func tearDown() { + viewModel = nil + getManagedNetworksServiceMock = nil + manageNetworkDetailsServiceMock = nil + super.tearDown() + } + + func testOnTapNetwork_FetchesAndPresentsDetailsOnSuccess() { + // Given + let networkKey = "networkKey" + let mockNetwork = MmNetwork.generate(key: networkKey) + let mockDetails = MNetworkDetails.generate() + + // When + viewModel.onTap(mockNetwork) + manageNetworkDetailsServiceMock.getNetworkDetailsReceivedCompletion.first?(.success(mockDetails)) + + // Then + XCTAssertEqual(manageNetworkDetailsServiceMock.getNetworkDetailsCallsCount, 1) + XCTAssertEqual(viewModel.selectedDetails, mockDetails) + XCTAssertTrue(viewModel.isPresentingDetails) + } + + func testOnTapNetwork_PresentsErrorOnFailure() { + // Given + let networkKey = "networkKey" + let mockNetwork = MmNetwork.generate(key: networkKey) + let mockError = ServiceError(message: "Error occurred") + let expectedPresentableError: ErrorBottomModalViewModel = .alertError(message: mockError.localizedDescription) + + // When + viewModel.onTap(mockNetwork) + manageNetworkDetailsServiceMock.getNetworkDetailsReceivedCompletion.first?(.failure(mockError)) + + // Then + XCTAssertEqual(manageNetworkDetailsServiceMock.getNetworkDetailsCallsCount, 1) + XCTAssertEqual(viewModel.presentableError, expectedPresentableError) + XCTAssertTrue(viewModel.isPresentingError) + } + + func testOnInit_SuccessfulNetworkFetch() { + // Given + let mockNetworks = [MmNetwork.generate(key: "key1"), MmNetwork.generate(key: "key2")] + + // When + getManagedNetworksServiceMock.getNetworksReceivedCompletion.first?(.success(mockNetworks)) + + // Then + XCTAssertEqual(getManagedNetworksServiceMock.getNetworksCallsCount, 1) + XCTAssertEqual(viewModel.networks, mockNetworks) + } + + func testOnInit_FailureInNetworkFetch() { + // Given + let mockError = ServiceError(message: "Error occurred") + let expectedPresentableError: ErrorBottomModalViewModel = .alertError(message: mockError.localizedDescription) + + // When + getManagedNetworksServiceMock.getNetworksReceivedCompletion.first?(.failure(mockError)) + + // Then + XCTAssertEqual(getManagedNetworksServiceMock.getNetworksCallsCount, 1) + XCTAssertEqual(viewModel.presentableError, expectedPresentableError) + XCTAssertTrue(viewModel.isPresentingError) + } + + func testOnQRScannerDismiss_UpdatesNetworks() { + // Given + let mockNetworks = [MmNetwork.generate(key: "key1"), MmNetwork.generate(key: "key2")] + let updatedMockNetworks = [MmNetwork.generate(key: "updatedKey")] + getManagedNetworksServiceMock.getNetworksReceivedCompletion.first?(.success(mockNetworks)) + + // When + viewModel.onQRScannerDismiss() + getManagedNetworksServiceMock.getNetworksReceivedCompletion.last?(.success(updatedMockNetworks)) + + // Then + XCTAssertEqual(viewModel.networks, updatedMockNetworks) + XCTAssertEqual(getManagedNetworksServiceMock.getNetworksCallsCount, 2) + } + + func testOnNetworkDetailsCompletion_DisplaysSnackbarOnNetworkDeleted() { + // Given + let networkTitle = "Test Network" + + // When + viewModel.onNetworkDetailsCompletion(.networkDeleted(networkTitle)) + + // Then + XCTAssertEqual( + viewModel.snackbarViewModel.title, + Localizable.Settings.NetworkDetails.DeleteNetwork.Label.confirmation(networkTitle) + ) + XCTAssertTrue(viewModel.isSnackbarPresented) + } + + func testOnIsPresentingDetailsChange_UpdatesNetworks() { + // Given + let mockNetworks = [MmNetwork.generate(key: "key1"), MmNetwork.generate(key: "key2")] + let updatedMockNetworks = [MmNetwork.generate(key: "updatedKey")] + getManagedNetworksServiceMock.getNetworksReceivedCompletion.first?(.success(mockNetworks)) + viewModel.isPresentingDetails = true + + // When + viewModel.isPresentingDetails = false + getManagedNetworksServiceMock.getNetworksReceivedCompletion.last?(.success(updatedMockNetworks)) + + // Then + XCTAssertEqual(viewModel.networks, updatedMockNetworks) + XCTAssertEqual(getManagedNetworksServiceMock.getNetworksCallsCount, 2) + } +} diff --git a/ios/PolkadotVaultTests/Screens/Settings/Subview/NetworkSelectionSettings/NetworkSettingDetailsActionModalViewModelTests.swift b/ios/PolkadotVaultTests/Screens/Settings/Subview/NetworkSelectionSettings/NetworkSettingDetailsActionModalViewModelTests.swift new file mode 100644 index 0000000000..e13088ab1f --- /dev/null +++ b/ios/PolkadotVaultTests/Screens/Settings/Subview/NetworkSelectionSettings/NetworkSettingDetailsActionModalViewModelTests.swift @@ -0,0 +1,92 @@ +// +// NetworkSettingDetailsActionModalViewModelTests.swift +// PolkadotVaultTests +// +// Created by Krzysztof Rodak on 10/01/2024. +// + +import Foundation +@testable import PolkadotVault +import SwiftUI +import XCTest + +final class NetworkSettingsDetailsActionModalViewModelTests: XCTestCase { + private var isPresented: Bool = false + private var shouldPresentRemoveNetworkConfirmation: Bool = false + private var shouldSignSpecs: Bool = false + + private func createBinding(for keyPath: WritableKeyPath) + -> Binding { + let defaultValue = self[keyPath: keyPath] + return .init(get: { [weak self] in + self?[keyPath: keyPath] ?? defaultValue + }, set: { [weak self] in + self?[keyPath: keyPath] = $0 + }) + } + + func testToggleSignSpecs() { + // Given + let viewModel = NetworkSettingsDetailsActionModal.ViewModel( + isPresented: createBinding(for: \.isPresented), + shouldPresentRemoveNetworkConfirmation: createBinding(for: \.shouldPresentRemoveNetworkConfirmation), + shouldSignSpecs: createBinding(for: \.shouldSignSpecs) + ) + let expectation = expectation(description: "AnimationDelay") + + // When + viewModel.toggleSignSpecs() + + // Then + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + XCTAssertTrue(self.shouldSignSpecs) + XCTAssertFalse(self.isPresented) + expectation.fulfill() + } + + waitForExpectations(timeout: 2, handler: nil) + } + + func testToggleRemoveNetworkConfirmation() { + // Given + let viewModel = NetworkSettingsDetailsActionModal.ViewModel( + isPresented: createBinding(for: \.isPresented), + shouldPresentRemoveNetworkConfirmation: createBinding(for: \.shouldPresentRemoveNetworkConfirmation), + shouldSignSpecs: createBinding(for: \.shouldSignSpecs) + ) + let expectation = expectation(description: "AnimationDelay") + + // When + viewModel.toggleRemoveNetworkConfirmation() + + // Then + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + XCTAssertTrue(self.shouldPresentRemoveNetworkConfirmation) + XCTAssertFalse(self.isPresented) + expectation.fulfill() + } + + waitForExpectations(timeout: 2, handler: nil) + } + + func testDismissActionSheet() { + // Given + let viewModel = NetworkSettingsDetailsActionModal.ViewModel( + isPresented: createBinding(for: \.isPresented), + shouldPresentRemoveNetworkConfirmation: createBinding(for: \.shouldPresentRemoveNetworkConfirmation), + shouldSignSpecs: createBinding(for: \.shouldSignSpecs) + ) + let expectation = expectation(description: "AnimationDelay") + + // When + viewModel.dismissActionSheet() + + // Then + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + XCTAssertFalse(self.isPresented) + expectation.fulfill() + } + + waitForExpectations(timeout: 2, handler: nil) + } +}