Merge pull request #21 from zcash/appNavigation/loadingScreen
Loading Screen + Tests
This commit is contained in:
commit
9004a124cd
|
@ -35,6 +35,12 @@
|
|||
0D4E7A1026B364180058B01E /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0D4E7A0F26B364180058B01E /* Preview Assets.xcassets */; };
|
||||
0D4E7A1B26B364180058B01E /* secantTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D4E7A1A26B364180058B01E /* secantTests.swift */; };
|
||||
0D4E7A2626B364180058B01E /* secantUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D4E7A2526B364180058B01E /* secantUITests.swift */; };
|
||||
0D5D16F526E24CCF00AD33D1 /* AppError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D5D16F426E24CCF00AD33D1 /* AppError.swift */; };
|
||||
0D864A0526E1546000A61879 /* LoadingScreenTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D864A0426E1546000A61879 /* LoadingScreenTests.swift */; };
|
||||
0D864A0926E154FD00A61879 /* InitFailedScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D864A0726E154FD00A61879 /* InitFailedScreen.swift */; };
|
||||
0D864A0A26E154FD00A61879 /* InitFailedScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D864A0826E154FD00A61879 /* InitFailedScreenViewModel.swift */; };
|
||||
0D864A0E26E1583000A61879 /* LoadingScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D864A0C26E1583000A61879 /* LoadingScreen.swift */; };
|
||||
0D864A0F26E1583000A61879 /* LoadingScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0D864A0D26E1583000A61879 /* LoadingScreenViewModel.swift */; };
|
||||
0DA13C8F26C15D1D00E3B610 /* WelcomeScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DA13C8D26C15D1D00E3B610 /* WelcomeScreen.swift */; };
|
||||
0DA13C9026C15D1D00E3B610 /* WelcomeScreenViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DA13C8E26C15D1D00E3B610 /* WelcomeScreenViewModel.swift */; };
|
||||
0DA13C9326C15E2F00E3B610 /* PlainButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0DA13C9226C15E2F00E3B610 /* PlainButton.swift */; };
|
||||
|
@ -99,6 +105,12 @@
|
|||
0D4E7A2126B364180058B01E /* secantUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = secantUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
0D4E7A2526B364180058B01E /* secantUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = secantUITests.swift; sourceTree = "<group>"; };
|
||||
0D4E7A2726B364180058B01E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
0D5D16F426E24CCF00AD33D1 /* AppError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppError.swift; sourceTree = "<group>"; };
|
||||
0D864A0426E1546000A61879 /* LoadingScreenTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingScreenTests.swift; sourceTree = "<group>"; };
|
||||
0D864A0726E154FD00A61879 /* InitFailedScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitFailedScreen.swift; sourceTree = "<group>"; };
|
||||
0D864A0826E154FD00A61879 /* InitFailedScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitFailedScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
0D864A0C26E1583000A61879 /* LoadingScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingScreen.swift; sourceTree = "<group>"; };
|
||||
0D864A0D26E1583000A61879 /* LoadingScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoadingScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
0DA13C8D26C15D1D00E3B610 /* WelcomeScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeScreen.swift; sourceTree = "<group>"; };
|
||||
0DA13C8E26C15D1D00E3B610 /* WelcomeScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
0DA13C9226C15E2F00E3B610 /* PlainButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlainButton.swift; sourceTree = "<group>"; };
|
||||
|
@ -166,6 +178,8 @@
|
|||
0D1922EB26BDD9A500052649 /* Screens */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0D864A0B26E1580700A61879 /* Loading */,
|
||||
0D864A0626E154D100A61879 /* Error */,
|
||||
0D32282F26C5874B00262533 /* Balance */,
|
||||
0D32282A26C586E700262533 /* Send ZEC */,
|
||||
0D32282526C586C600262533 /* Request ZEC */,
|
||||
|
@ -282,6 +296,7 @@
|
|||
0D4E7A0726B364170058B01E /* secant */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0D5D16F326E24CB900AD33D1 /* App Errors */,
|
||||
0D2ACE8126C2C71100D62E3C /* Utilities */,
|
||||
0D2ACE7E26C2C65E00D62E3C /* Fonts */,
|
||||
0DA13CA326C1960A00E3B610 /* Models */,
|
||||
|
@ -313,6 +328,7 @@
|
|||
children = (
|
||||
0D4E7A1A26B364180058B01E /* secantTests.swift */,
|
||||
0D4E7A1C26B364180058B01E /* Info.plist */,
|
||||
0D864A0426E1546000A61879 /* LoadingScreenTests.swift */,
|
||||
);
|
||||
path = secantTests;
|
||||
sourceTree = "<group>";
|
||||
|
@ -326,6 +342,32 @@
|
|||
path = secantUITests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0D5D16F326E24CB900AD33D1 /* App Errors */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0D5D16F426E24CCF00AD33D1 /* AppError.swift */,
|
||||
);
|
||||
path = "App Errors";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0D864A0626E154D100A61879 /* Error */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0D864A0726E154FD00A61879 /* InitFailedScreen.swift */,
|
||||
0D864A0826E154FD00A61879 /* InitFailedScreenViewModel.swift */,
|
||||
);
|
||||
path = Error;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0D864A0B26E1580700A61879 /* Loading */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0D864A0C26E1583000A61879 /* LoadingScreen.swift */,
|
||||
0D864A0D26E1583000A61879 /* LoadingScreenViewModel.swift */,
|
||||
);
|
||||
path = Loading;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0DA13C8C26C15CBE00E3B610 /* Welcome Screen */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
@ -538,10 +580,13 @@
|
|||
0DA13C9D26C1942100E3B610 /* BackupWalletScreenViewModel.swift in Sources */,
|
||||
0D354A0B26D5A9D000315F45 /* MnemonicSeedPhraseHandling.swift in Sources */,
|
||||
0D32281E26C5867D00262533 /* ScanQrScreen.swift in Sources */,
|
||||
0D864A0E26E1583000A61879 /* LoadingScreen.swift in Sources */,
|
||||
0DA13C9C26C1942100E3B610 /* BackupWalletScreen.swift in Sources */,
|
||||
0DA13C9826C186FF00E3B610 /* RestoreWalletScreenViewModel.swift in Sources */,
|
||||
0D32283326C5877A00262533 /* BalanceScreenViewModel.swift in Sources */,
|
||||
0D5D16F526E24CCF00AD33D1 /* AppError.swift in Sources */,
|
||||
0D32282326C586A800262533 /* HistoryScreen.swift in Sources */,
|
||||
0D864A0A26E154FD00A61879 /* InitFailedScreenViewModel.swift in Sources */,
|
||||
0DA13CA526C1963000E3B610 /* Balance.swift in Sources */,
|
||||
0D1922F826BDEB3500052649 /* MockServices.swift in Sources */,
|
||||
0D4E7A0B26B364170058B01E /* ContentView.swift in Sources */,
|
||||
|
@ -550,7 +595,9 @@
|
|||
0DA13C9726C186FF00E3B610 /* RestoreWalletScreen.swift in Sources */,
|
||||
0D1922EA26BDD96A00052649 /* ViewModel.swift in Sources */,
|
||||
0D4E7A0926B364170058B01E /* SecantApp.swift in Sources */,
|
||||
0D864A0926E154FD00A61879 /* InitFailedScreen.swift in Sources */,
|
||||
0D32281A26C5864B00262533 /* ProfileScreenViewModel.swift in Sources */,
|
||||
0D864A0F26E1583000A61879 /* LoadingScreenViewModel.swift in Sources */,
|
||||
0DA13CA126C1955600E3B610 /* HomeScreen.swift in Sources */,
|
||||
0DA13C9026C15D1D00E3B610 /* WelcomeScreenViewModel.swift in Sources */,
|
||||
0DA13C8F26C15D1D00E3B610 /* WelcomeScreen.swift in Sources */,
|
||||
|
@ -570,6 +617,7 @@
|
|||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0D864A0526E1546000A61879 /* LoadingScreenTests.swift in Sources */,
|
||||
0D4E7A1B26B364180058B01E /* secantTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
|
@ -28,8 +28,7 @@
|
|||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES"
|
||||
onlyGenerateCoverageForSpecifiedTargets = "YES"
|
||||
localizableStringsDataGatheringEnabled = "YES">
|
||||
onlyGenerateCoverageForSpecifiedTargets = "YES">
|
||||
<CodeCoverageTargets>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
//
|
||||
// AppError.swift
|
||||
// secant-testnet
|
||||
//
|
||||
// Created by Francisco Gindre on 9/3/21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum AppError: Error {
|
||||
case failedToInitialize(Error)
|
||||
}
|
|
@ -12,7 +12,7 @@ protocol KeyStoring {
|
|||
func exportBirthday() throws -> BlockHeight
|
||||
func importPhrase(bip39 phrase: String) throws
|
||||
func exportPhrase() throws -> String
|
||||
|
||||
func areKeysPresent() throws -> Bool
|
||||
/**
|
||||
Use carefully: Deletes the seed phrase from the keychain
|
||||
*/
|
||||
|
@ -34,4 +34,5 @@ protocol KeyStoring {
|
|||
enum KeyStoringError: Error {
|
||||
case alreadyImported
|
||||
case uninitializedWallet
|
||||
case storageError(Error)
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ enum AppRouterScreen {
|
|||
case appLoading
|
||||
case createRestoreWallet
|
||||
case home
|
||||
case loadingFailed
|
||||
}
|
||||
|
||||
class AppRouter: Router {
|
||||
|
@ -39,7 +40,14 @@ class AppRouter: Router {
|
|||
}
|
||||
|
||||
@ViewBuilder func loadingScreen() -> some View {
|
||||
Text("Loading")
|
||||
LoadingScreen(
|
||||
viewModel: LoadingScreenViewModel(services: self.services),
|
||||
router: self
|
||||
)
|
||||
}
|
||||
|
||||
@ViewBuilder func loadingFailedScreen() -> some View {
|
||||
Text("loading failed")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,15 +56,6 @@ struct AppRouterView: View {
|
|||
|
||||
var body: some View {
|
||||
viewForScreen(router.screen)
|
||||
.onAppear {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
|
||||
if router.services.keyStorage.keysPresent {
|
||||
router.screen = .home
|
||||
} else {
|
||||
router.screen = .createRestoreWallet
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ViewBuilder func viewForScreen(_ screen: AppRouterScreen) -> some View {
|
||||
|
@ -64,6 +63,22 @@ struct AppRouterView: View {
|
|||
case .appLoading: router.loadingScreen()
|
||||
case .createRestoreWallet: router.createNew()
|
||||
case .home: router.home()
|
||||
case .loadingFailed: router.loadingFailedScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension AppRouter: LoadingScreenRouter {
|
||||
func proceedToWelcome() {
|
||||
self.screen = .createRestoreWallet
|
||||
}
|
||||
|
||||
func proceedToHome() {
|
||||
self.screen = .home
|
||||
}
|
||||
|
||||
// TODO: handle Errors
|
||||
func failWithError() {
|
||||
self.screen = .loadingFailed
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// InitFailedScreen.swift
|
||||
// secant-testnet
|
||||
//
|
||||
// Created by Francisco Gindre on 9/2/21.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
protocol InitFailedScreenRouter: AnyObject {
|
||||
}
|
||||
|
||||
struct InitFailedScreen: View {
|
||||
@State var router: InitFailedScreenRouter?
|
||||
|
||||
@ObservedObject var viewModel: InitFailedScreenViewModel
|
||||
|
||||
var body: some View {
|
||||
Text("Hello, World!")
|
||||
}
|
||||
}
|
||||
|
||||
struct InitFailedScreenPreviews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
InitFailedScreen(viewModel: InitFailedScreenViewModel(services: MockServices()))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
//
|
||||
// InitFailedScreenViewModel.swift
|
||||
// secant-testnet
|
||||
//
|
||||
// Created by Francisco Gindre on 9/2/21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
class InitFailedScreenViewModel: BaseViewModel<Services>, ObservableObject {}
|
|
@ -0,0 +1,67 @@
|
|||
//
|
||||
// LoadingScreen.swift
|
||||
// secant-testnet
|
||||
//
|
||||
// Created by Francisco Gindre on 9/2/21.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
protocol LoadingScreenRouter: AnyObject {
|
||||
func proceedToHome()
|
||||
func failWithError()
|
||||
func proceedToWelcome()
|
||||
}
|
||||
|
||||
struct LoadingScreen: View {
|
||||
@StateObject var viewModel: LoadingScreenViewModel
|
||||
|
||||
@State var router: LoadingScreenRouter?
|
||||
|
||||
var body: some View {
|
||||
Text("Loading")
|
||||
.onReceive(
|
||||
viewModel.$loadingResult,
|
||||
perform: { result in
|
||||
guard
|
||||
let loadingResult = result,
|
||||
let router = self.router
|
||||
else { return }
|
||||
|
||||
viewModel.callRouter(router, with: loadingResult)
|
||||
}
|
||||
)
|
||||
.onAppear {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 4) {
|
||||
viewModel.loadAsync()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Routing
|
||||
|
||||
extension LoadingScreenViewModel {
|
||||
func callRouter(
|
||||
_ router: LoadingScreenRouter,
|
||||
with loadingResult: Result<LoadingScreenViewModel.LoadingResult, Error>
|
||||
) {
|
||||
switch loadingResult {
|
||||
case .success(let result):
|
||||
switch result {
|
||||
case .credentialsFound:
|
||||
router.proceedToHome()
|
||||
case .newWallet:
|
||||
router.proceedToWelcome()
|
||||
}
|
||||
case .failure:
|
||||
router.failWithError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct LoadingScreenPreviews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
LoadingScreen(viewModel: LoadingScreenViewModel(services: MockServices()))
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// LoadingScreenViewModel.swift
|
||||
// secant-testnet
|
||||
//
|
||||
// Created by Francisco Gindre on 9/2/21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
class LoadingScreenViewModel: BaseViewModel<Services>, ObservableObject {
|
||||
enum LoadingResult {
|
||||
case newWallet
|
||||
case credentialsFound
|
||||
}
|
||||
|
||||
@Published var loadingResult: Result<LoadingResult, Error>?
|
||||
|
||||
func loadAsync () {
|
||||
// TODO: Make a special queue for the app
|
||||
DispatchQueue.global(qos: .userInitiated)
|
||||
.async { [weak self] in
|
||||
guard let result = self?.load() else { return }
|
||||
DispatchQueue.main.async {
|
||||
self?.loadingResult = result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal func load() -> Result<LoadingResult, Error> {
|
||||
do {
|
||||
return (try services.keyStorage.areKeysPresent()) ? .success(.credentialsFound) : .success(.newWallet)
|
||||
} catch {
|
||||
return .failure(error)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,24 +4,16 @@
|
|||
//
|
||||
// Created by Francisco Gindre on 8/6/21.
|
||||
//
|
||||
|
||||
// TODO: Move this to diferent Target when real functionality is developed.
|
||||
import Foundation
|
||||
|
||||
// swiftlint:disable line_length
|
||||
class MockServices: Services {
|
||||
var networkProvider: ZcashNetworkProvider {
|
||||
MockNetworkProvider()
|
||||
}
|
||||
|
||||
var seedHandler: MnemonicSeedPhraseHandling {
|
||||
MockMnemonicPhraseHandling()
|
||||
}
|
||||
|
||||
var keyStorage: KeyStoring {
|
||||
MockKeyStoring()
|
||||
}
|
||||
|
||||
init() {}
|
||||
var networkProvider: ZcashNetworkProvider = MockNetworkProvider()
|
||||
|
||||
var seedHandler: MnemonicSeedPhraseHandling = MockMnemonicPhraseHandling()
|
||||
|
||||
var keyStorage: KeyStoring = MockKeyStoring()
|
||||
}
|
||||
|
||||
class MockNetworkProvider: ZcashNetworkProvider {
|
||||
|
@ -65,10 +57,76 @@ class MockMnemonicPhraseHandling: MnemonicSeedPhraseHandling {
|
|||
func isValid(mnemonic: String) throws {}
|
||||
}
|
||||
|
||||
class KeysPresentStub: KeyStoring {
|
||||
init(returnBlock: @escaping () throws -> Bool) {
|
||||
self.returnBlock = returnBlock
|
||||
}
|
||||
var returnBlock: () throws -> Bool
|
||||
var called = false
|
||||
func areKeysPresent() throws -> Bool {
|
||||
called = true
|
||||
return try returnBlock()
|
||||
}
|
||||
|
||||
var birthday: BlockHeight?
|
||||
var phrase: String?
|
||||
func importBirthday(_ height: BlockHeight) throws {
|
||||
guard birthday == nil else {
|
||||
throw KeyStoringError.alreadyImported
|
||||
}
|
||||
birthday = height
|
||||
}
|
||||
|
||||
func exportBirthday() throws -> BlockHeight {
|
||||
guard let birthday = birthday else {
|
||||
throw KeyStoringError.uninitializedWallet
|
||||
}
|
||||
return birthday
|
||||
}
|
||||
|
||||
func importPhrase(bip39 phrase: String) throws {
|
||||
guard self.phrase == nil else {
|
||||
throw KeyStoringError.alreadyImported
|
||||
}
|
||||
self.phrase = phrase
|
||||
}
|
||||
|
||||
func exportPhrase() throws -> String {
|
||||
guard let phrase = self.phrase else {
|
||||
throw KeyStoringError.uninitializedWallet
|
||||
}
|
||||
return phrase
|
||||
}
|
||||
|
||||
var keysPresent: Bool {
|
||||
return self.phrase != nil && self.birthday != nil
|
||||
}
|
||||
|
||||
func nukePhrase() {
|
||||
self.phrase = nil
|
||||
}
|
||||
|
||||
func nukeBirthday() {
|
||||
self.birthday = nil
|
||||
}
|
||||
|
||||
func nukeWallet() {
|
||||
nukePhrase()
|
||||
nukeBirthday()
|
||||
}
|
||||
}
|
||||
class MockKeyStoring: KeyStoring {
|
||||
var birthday: BlockHeight?
|
||||
var phrase: String?
|
||||
|
||||
var keysPresent: Bool {
|
||||
return self.phrase != nil && self.birthday != nil
|
||||
}
|
||||
|
||||
func areKeysPresent() throws -> Bool {
|
||||
false
|
||||
}
|
||||
|
||||
func importBirthday(_ height: BlockHeight) throws {
|
||||
guard birthday == nil else {
|
||||
throw KeyStoringError.alreadyImported
|
||||
|
@ -101,10 +159,6 @@ class MockKeyStoring: KeyStoring {
|
|||
return phrase
|
||||
}
|
||||
|
||||
var keysPresent: Bool {
|
||||
return self.phrase != nil && self.birthday != nil
|
||||
}
|
||||
|
||||
func nukePhrase() {
|
||||
self.phrase = nil
|
||||
}
|
||||
|
|
|
@ -0,0 +1,239 @@
|
|||
//
|
||||
// AppRouterNavigationTests.swift
|
||||
// secantTests
|
||||
//
|
||||
// Created by Francisco Gindre on 9/2/21.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import secant_testnet
|
||||
import Foundation
|
||||
import Combine
|
||||
class LoadingScreenTests: XCTestCase {
|
||||
var cancellables: [AnyCancellable] = []
|
||||
|
||||
// MARK: LoadingScreenViewModel Tests
|
||||
|
||||
func testCredentialsFoundIsPublishedWhenCredentialsArePresent() throws {
|
||||
let mockServices = MockServices()
|
||||
let stub = KeysPresentStub(returnBlock: {
|
||||
true
|
||||
})
|
||||
mockServices.keyStorage = stub
|
||||
let loadingViewModel = LoadingScreenViewModel(services: mockServices)
|
||||
// swiftlint:disable:next line_length
|
||||
let testExpectation = XCTestExpectation(description: "LoadingViewModel Publishes .credentialsFound when credentials are present and there's no failure")
|
||||
let expected = LoadingScreenViewModel.LoadingResult.credentialsFound
|
||||
|
||||
loadingViewModel.$loadingResult
|
||||
.dropFirst()
|
||||
.sink { loadingResult in
|
||||
testExpectation.fulfill()
|
||||
|
||||
XCTAssertTrue(stub.called)
|
||||
switch loadingResult {
|
||||
case .success(let result):
|
||||
XCTAssertEqual(result, expected)
|
||||
case .failure(let error):
|
||||
XCTFail("found error \(error.localizedDescription)")
|
||||
case .none:
|
||||
XCTFail("found None when expected a value")
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
loadingViewModel.loadAsync()
|
||||
wait(for: [testExpectation], timeout: 0.1)
|
||||
}
|
||||
|
||||
func testNewWalletLoadingResultPublishedWhenNoCredentialsFound() throws {
|
||||
let mockServices = MockServices()
|
||||
let stub = KeysPresentStub(returnBlock: {
|
||||
false
|
||||
})
|
||||
mockServices.keyStorage = stub
|
||||
let loadingViewModel = LoadingScreenViewModel(services: mockServices)
|
||||
let testExpectation = XCTestExpectation(
|
||||
description: "LoadingViewModel Publishes .newWallet when no credentials are present and there's no failure"
|
||||
)
|
||||
let expected = LoadingScreenViewModel.LoadingResult.newWallet
|
||||
|
||||
loadingViewModel.$loadingResult
|
||||
.dropFirst()
|
||||
.sink { loadingResult in
|
||||
testExpectation.fulfill()
|
||||
|
||||
XCTAssertTrue(stub.called)
|
||||
switch loadingResult {
|
||||
case .success(let result):
|
||||
XCTAssertEqual(result, expected)
|
||||
case .failure(let error):
|
||||
XCTFail("found error \(error.localizedDescription)")
|
||||
case .none:
|
||||
XCTFail("found None when expected a value")
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
loadingViewModel.loadAsync()
|
||||
wait(for: [testExpectation], timeout: 0.1)
|
||||
}
|
||||
|
||||
func testFailureIsPublishedWhenWalletFailsToInitialize() throws {
|
||||
let mockServices = MockServices()
|
||||
let stub = KeysPresentStub(returnBlock: {
|
||||
throw KeyStoringError.alreadyImported
|
||||
})
|
||||
mockServices.keyStorage = stub
|
||||
let loadingViewModel = LoadingScreenViewModel(services: mockServices)
|
||||
let testExpectation = XCTestExpectation(description: "LoadingViewModel Publishes .failure when there's a failure")
|
||||
|
||||
loadingViewModel.$loadingResult
|
||||
.dropFirst()
|
||||
.sink { loadingResult in
|
||||
testExpectation.fulfill()
|
||||
|
||||
XCTAssertTrue(stub.called)
|
||||
switch loadingResult {
|
||||
case .success(let result):
|
||||
XCTFail("found result: \(result) but expected a failure")
|
||||
case .failure:
|
||||
XCTAssertTrue(true) // fails when expected
|
||||
case .none:
|
||||
XCTFail("found None when expected a failure")
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
loadingViewModel.loadAsync()
|
||||
wait(for: [testExpectation], timeout: 0.1)
|
||||
}
|
||||
|
||||
func testNewWalletLoadingResultReturnedWhenCredentialsAreNotPresent() throws {
|
||||
let mockServices = MockServices()
|
||||
let stub = KeysPresentStub(returnBlock: {
|
||||
false
|
||||
})
|
||||
mockServices.keyStorage = stub
|
||||
let loadingViewModel = LoadingScreenViewModel(services: mockServices)
|
||||
|
||||
let expected = LoadingScreenViewModel.LoadingResult.newWallet
|
||||
let result = loadingViewModel.load()
|
||||
|
||||
XCTAssertTrue(stub.called)
|
||||
switch result {
|
||||
case .failure(let error):
|
||||
XCTFail("found error \(error.localizedDescription)")
|
||||
case .success(let res):
|
||||
XCTAssertEqual(expected, res)
|
||||
}
|
||||
}
|
||||
|
||||
func testCredentialsFoundReturnedWhenCredentialsArePresent() throws {
|
||||
let mockServices = MockServices()
|
||||
let stub = KeysPresentStub(returnBlock: {
|
||||
true
|
||||
})
|
||||
mockServices.keyStorage = stub
|
||||
let loadingViewModel = LoadingScreenViewModel(services: mockServices)
|
||||
|
||||
let expected = LoadingScreenViewModel.LoadingResult.credentialsFound
|
||||
let result = loadingViewModel.load()
|
||||
|
||||
XCTAssertTrue(stub.called)
|
||||
switch result {
|
||||
case .failure(let error):
|
||||
XCTFail("found error \(error.localizedDescription)")
|
||||
case .success(let res):
|
||||
XCTAssertEqual(expected, res)
|
||||
}
|
||||
}
|
||||
|
||||
func testLoadReturnsErrorWhenLoadingFails() throws {
|
||||
let mockServices = MockServices()
|
||||
let stub = KeysPresentStub(returnBlock: {
|
||||
throw KeyStoringError.uninitializedWallet
|
||||
})
|
||||
mockServices.keyStorage = stub
|
||||
let loadingViewModel = LoadingScreenViewModel(services: mockServices)
|
||||
|
||||
let result = loadingViewModel.load()
|
||||
XCTAssertTrue(stub.called)
|
||||
switch result {
|
||||
case .failure:
|
||||
XCTAssertTrue(true)
|
||||
case .success(let res):
|
||||
XCTFail("case succeeded when testing failure - result: \(res)")
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: LoadingScreen View Tests
|
||||
|
||||
func testProceedToHomeIsCalledWhenCredentialsAreFound() throws {
|
||||
let loadingViewModel = LoadingScreenViewModelHelper.loadingViewModelWith {
|
||||
true
|
||||
}
|
||||
|
||||
let spyRouter = LoadingScreenRouterSpy(fulfillment: {
|
||||
})
|
||||
|
||||
loadingViewModel.callRouter(spyRouter, with: loadingViewModel.load())
|
||||
XCTAssertTrue(spyRouter.proceedToHomeCalled)
|
||||
}
|
||||
|
||||
func testProceedToWelcomeIsCalledWhenCredentialsAreNotFound() throws {
|
||||
let loadingViewModel = LoadingScreenViewModelHelper.loadingViewModelWith {
|
||||
false
|
||||
}
|
||||
|
||||
let spyRouter = LoadingScreenRouterSpy(fulfillment: {
|
||||
})
|
||||
|
||||
loadingViewModel.callRouter(spyRouter, with: loadingViewModel.load())
|
||||
XCTAssertTrue(spyRouter.proceedToWelcomeCalled)
|
||||
}
|
||||
|
||||
func testFailWithErrorIsCalledWhenKeyStoringFails() throws {
|
||||
let loadingViewModel = LoadingScreenViewModelHelper.loadingViewModelWith {
|
||||
throw KeyStoringError.alreadyImported
|
||||
}
|
||||
|
||||
let spyRouter = LoadingScreenRouterSpy(fulfillment: {
|
||||
})
|
||||
|
||||
loadingViewModel.callRouter(spyRouter, with: loadingViewModel.load())
|
||||
XCTAssertTrue(spyRouter.failWithErrorCalled)
|
||||
}
|
||||
}
|
||||
|
||||
class LoadingScreenRouterSpy: LoadingScreenRouter {
|
||||
var fulfillmentBlock: () -> Void
|
||||
var proceedToHomeCalled = false
|
||||
var failWithErrorCalled = false
|
||||
var proceedToWelcomeCalled = false
|
||||
|
||||
init(fulfillment: @escaping () -> Void) {
|
||||
self.fulfillmentBlock = fulfillment
|
||||
}
|
||||
|
||||
func proceedToHome() {
|
||||
proceedToHomeCalled = true
|
||||
fulfillmentBlock()
|
||||
}
|
||||
|
||||
func failWithError() {
|
||||
failWithErrorCalled = true
|
||||
fulfillmentBlock()
|
||||
}
|
||||
|
||||
func proceedToWelcome() {
|
||||
proceedToWelcomeCalled = true
|
||||
fulfillmentBlock()
|
||||
}
|
||||
}
|
||||
|
||||
enum LoadingScreenViewModelHelper {
|
||||
static func loadingViewModelWith(keysPresentStubBlock: @escaping () throws -> Bool) -> LoadingScreenViewModel {
|
||||
let mockServices = MockServices()
|
||||
let stub = KeysPresentStub(returnBlock: keysPresentStubBlock)
|
||||
mockServices.keyStorage = stub
|
||||
return LoadingScreenViewModel(services: mockServices)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue