From a537f0f3074a678140627f213feb48e93fc0e587 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Wed, 31 Aug 2022 13:25:15 +0100 Subject: [PATCH 01/10] Update dependencies --- .../xcshareddata/swiftpm/Package.resolved | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Examples/xx-messenger/XXMessenger.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/xx-messenger/XXMessenger.xcworkspace/xcshareddata/swiftpm/Package.resolved index c330aa94..b9909a30 100644 --- a/Examples/xx-messenger/XXMessenger.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Examples/xx-messenger/XXMessenger.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/combine-schedulers", "state" : { - "revision" : "8fee20f993e64bbbf22bc3e3f444758ac2d05692", - "version" : "0.7.2" + "revision" : "9e42b4b0453da417a44daa17174103e7d1c5be07", + "version" : "0.7.3" } }, { @@ -59,8 +59,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-composable-architecture.git", "state" : { - "revision" : "108e3a536fcebb16c4f247ef92c2d7326baf9fe3", - "version" : "0.39.0" + "revision" : "a518935116b2bada7234f47073159b433d432af1", + "version" : "0.39.1" } }, { @@ -68,8 +68,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/darrarski/swift-composable-presentation.git", "state" : { - "revision" : "1f4d17fae1f7ed41cbed17929083190fd9a78ee6", - "version" : "0.5.2" + "revision" : "bdb7df9476cf29e8379fc50aa03848dd6c8033d9", + "version" : "0.5.3" } }, { -- GitLab From f1f148bbd7fd50d483e94fe500de06dcf327b811 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Wed, 31 Aug 2022 13:32:29 +0100 Subject: [PATCH 02/10] Add missing imports --- Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift | 1 + Examples/xx-messenger/Sources/LaunchFeature/LaunchFeature.swift | 1 + 2 files changed, 2 insertions(+) diff --git a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift index 6b61b099..499bed78 100644 --- a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift +++ b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift @@ -1,4 +1,5 @@ import ComposableArchitecture +import Foundation import XXClient import XXMessengerClient diff --git a/Examples/xx-messenger/Sources/LaunchFeature/LaunchFeature.swift b/Examples/xx-messenger/Sources/LaunchFeature/LaunchFeature.swift index 39e3efdf..a7bf89ce 100644 --- a/Examples/xx-messenger/Sources/LaunchFeature/LaunchFeature.swift +++ b/Examples/xx-messenger/Sources/LaunchFeature/LaunchFeature.swift @@ -2,6 +2,7 @@ import AppCore import Combine import ComposableArchitecture import ComposablePresentation +import Foundation import RegisterFeature import RestoreFeature import WelcomeFeature -- GitLab From 4197d79d240a0398ff1ed021f6289396f09d73c3 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Wed, 31 Aug 2022 14:55:30 +0100 Subject: [PATCH 03/10] Move app launch logic to AppFeature --- Examples/xx-messenger/Package.swift | 2 - .../AppFeature/AppEnvironment+Live.swift | 32 ++-- .../Sources/AppFeature/AppFeature.swift | 99 +++++++++-- .../Sources/AppFeature/AppView.swift | 97 +++++++++-- .../AppFeatureTests/AppFeatureTests.swift | 160 +++++++++++++++++- 5 files changed, 333 insertions(+), 57 deletions(-) diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift index 5eb7bede..b91c4340 100644 --- a/Examples/xx-messenger/Package.swift +++ b/Examples/xx-messenger/Package.swift @@ -69,8 +69,6 @@ let package = Package( dependencies: [ .target(name: "AppCore"), .target(name: "HomeFeature"), - .target(name: "LaunchFeature"), - .target(name: "RegisterFeature"), .target(name: "RestoreFeature"), .target(name: "WelcomeFeature"), .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift index ff97f803..eec6f199 100644 --- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift +++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift @@ -1,7 +1,6 @@ import AppCore import Foundation import HomeFeature -import LaunchFeature import RegisterFeature import RestoreFeature import WelcomeFeature @@ -17,31 +16,20 @@ extension AppEnvironment { let bgQueue = DispatchQueue.global(qos: .background).eraseToAnyScheduler() return AppEnvironment( - launch: { - LaunchEnvironment( - dbManager: dbManager, + dbManager: dbManager, + messenger: messenger, + mainQueue: mainQueue, + bgQueue: bgQueue, + welcome: { + WelcomeEnvironment( messenger: messenger, mainQueue: mainQueue, - bgQueue: bgQueue, - welcome: { - WelcomeEnvironment( - messenger: messenger, - mainQueue: mainQueue, - bgQueue: bgQueue - ) - }, - restore: { - RestoreEnvironment() - }, - register: { - RegisterEnvironment( - messenger: messenger, - mainQueue: mainQueue, - bgQueue: bgQueue - ) - } + bgQueue: bgQueue ) }, + restore: { + RestoreEnvironment() + }, home: { HomeEnvironment( messenger: messenger, diff --git a/Examples/xx-messenger/Sources/AppFeature/AppFeature.swift b/Examples/xx-messenger/Sources/AppFeature/AppFeature.swift index 18927c0b..535d69fc 100644 --- a/Examples/xx-messenger/Sources/AppFeature/AppFeature.swift +++ b/Examples/xx-messenger/Sources/AppFeature/AppFeature.swift @@ -1,21 +1,33 @@ +import AppCore +import Combine import ComposableArchitecture import ComposablePresentation +import Foundation import HomeFeature -import LaunchFeature +import RestoreFeature +import WelcomeFeature +import XXMessengerClient struct AppState: Equatable { enum Screen: Equatable { - case launch(LaunchState) + case loading + case welcome(WelcomeState) + case restore(RestoreState) case home(HomeState) + case failure(String) } - var screen: Screen = .launch(LaunchState()) + @BindableState var screen: Screen = .loading } extension AppState.Screen { - var asLaunch: LaunchState? { - get { (/AppState.Screen.launch).extract(from: self) } - set { if let newValue = newValue { self = .launch(newValue) } } + var asWelcome: WelcomeState? { + get { (/AppState.Screen.welcome).extract(from: self) } + set { if let newValue = newValue { self = .welcome(newValue) } } + } + var asRestore: RestoreState? { + get { (/AppState.Screen.restore).extract(from: self) } + set { if let state = newValue { self = .restore(state) } } } var asHome: HomeState? { get { (/AppState.Screen.home).extract(from: self) } @@ -23,19 +35,32 @@ extension AppState.Screen { } } -enum AppAction: Equatable { +enum AppAction: Equatable, BindableAction { + case start + case binding(BindingAction<AppState>) + case welcome(WelcomeAction) + case restore(RestoreAction) case home(HomeAction) - case launch(LaunchAction) } struct AppEnvironment { - var launch: () -> LaunchEnvironment + var dbManager: DBManager + var messenger: Messenger + var mainQueue: AnySchedulerOf<DispatchQueue> + var bgQueue: AnySchedulerOf<DispatchQueue> + var welcome: () -> WelcomeEnvironment + var restore: () -> RestoreEnvironment var home: () -> HomeEnvironment } extension AppEnvironment { static let unimplemented = AppEnvironment( - launch: { .unimplemented }, + dbManager: .unimplemented, + messenger: .unimplemented, + mainQueue: .unimplemented, + bgQueue: .unimplemented, + welcome: { .unimplemented }, + restore: { .unimplemented }, home: { .unimplemented } ) } @@ -43,20 +68,60 @@ extension AppEnvironment { let appReducer = Reducer<AppState, AppAction, AppEnvironment> { state, action, env in switch action { - case .launch(.finished): - state.screen = .home(HomeState()) + case .start, .welcome(.finished), .restore(.finished): + state.screen = .loading + return .run { subscriber in + do { + if env.dbManager.hasDB() == false { + try env.dbManager.makeDB() + } + + if env.messenger.isLoaded() == false { + if env.messenger.isCreated() == false { + subscriber.send(.set(\.$screen, .welcome(WelcomeState()))) + subscriber.send(completion: .finished) + return AnyCancellable {} + } + try env.messenger.load() + } + + subscriber.send(.set(\.$screen, .home(HomeState()))) + } catch { + subscriber.send(.set(\.$screen, .failure(error.localizedDescription))) + } + subscriber.send(completion: .finished) + return AnyCancellable {} + } + .subscribe(on: env.bgQueue) + .receive(on: env.mainQueue) + .eraseToEffect() + + case .welcome(.restoreTapped): + state.screen = .restore(RestoreState()) return .none - case .launch(_), .home(_): + case .welcome(.failed(let failure)): + state.screen = .failure(failure) + return .none + + case .binding(_), .welcome(_), .restore(_), .home(_): return .none } } +.binding() +.presenting( + welcomeReducer, + state: .keyPath(\.screen.asWelcome), + id: .notNil(), + action: /AppAction.welcome, + environment: { $0.welcome() } +) .presenting( - launchReducer, - state: .keyPath(\.screen.asLaunch), + restoreReducer, + state: .keyPath(\.screen.asRestore), id: .notNil(), - action: /AppAction.launch, - environment: { $0.launch() } + action: /AppAction.restore, + environment: { $0.restore() } ) .presenting( homeReducer, diff --git a/Examples/xx-messenger/Sources/AppFeature/AppView.swift b/Examples/xx-messenger/Sources/AppFeature/AppView.swift index 317560a3..2af6d352 100644 --- a/Examples/xx-messenger/Sources/AppFeature/AppView.swift +++ b/Examples/xx-messenger/Sources/AppFeature/AppView.swift @@ -1,19 +1,26 @@ import ComposableArchitecture -import SwiftUI import HomeFeature -import LaunchFeature +import RestoreFeature +import SwiftUI +import WelcomeFeature struct AppView: View { let store: Store<AppState, AppAction> enum ViewState: Equatable { - case launch + case loading + case welcome + case restore case home + case failure(String) init(_ state: AppState) { switch state.screen { - case .launch(_): self = .launch + case .loading: self = .loading + case .welcome(_): self = .welcome + case .restore(_): self = .restore case .home(_): self = .home + case .failure(let failure): self = .failure(failure) } } } @@ -21,20 +28,53 @@ struct AppView: View { var body: some View { WithViewStore(store.scope(state: ViewState.init)) { viewStore in ZStack { - SwitchStore(store.scope(state: \.screen)) { - CaseLet( - state: /AppState.Screen.launch, - action: AppAction.launch, + switch viewStore.state { + case .loading: + ProgressView { + Text("Loading") + } + .controlSize(.large) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .transition(.opacity) + + case .welcome: + IfLetStore( + store.scope( + state: { (/AppState.Screen.welcome).extract(from: $0.screen) }, + action: AppAction.welcome + ), + then: { store in + WelcomeView(store: store) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .transition(.asymmetric( + insertion: .move(edge: .trailing), + removal: .opacity + )) + } + ) + + case .restore: + IfLetStore( + store.scope( + state: { (/AppState.Screen.restore).extract(from: $0.screen) }, + action: AppAction.restore + ), then: { store in - LaunchView(store: store) + RestoreView(store: store) .frame(maxWidth: .infinity, maxHeight: .infinity) - .transition(.opacity) + .transition(.asymmetric( + insertion: .move(edge: .trailing), + removal: .opacity + )) } ) - CaseLet( - state: /AppState.Screen.home, - action: AppAction.home, + case .home: + IfLetStore( + store.scope( + state: { (/AppState.Screen.home).extract(from: $0.screen) }, + action: AppAction.home + ), then: { store in HomeView(store: store) .frame(maxWidth: .infinity, maxHeight: .infinity) @@ -44,9 +84,40 @@ struct AppView: View { )) } ) + + case .failure(let failure): + NavigationView { + VStack(spacing: 0) { + ScrollView { + Text(failure) + .frame(maxWidth: .infinity, alignment: .leading) + .padding() + } + + Divider() + + Button { + viewStore.send(.start) + } label: { + Text("Retry") + .frame(maxWidth: .infinity) + } + .buttonStyle(.borderedProminent) + .controlSize(.large) + .padding() + } + .navigationTitle("Error") + } + .navigationViewStyle(.stack) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .transition(.asymmetric( + insertion: .move(edge: .trailing), + removal: .opacity + )) } } .animation(.default, value: viewStore.state) + .task { viewStore.send(.start) } } } } diff --git a/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift b/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift index 6563da86..166b6325 100644 --- a/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift +++ b/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift @@ -1,19 +1,173 @@ import ComposableArchitecture import HomeFeature +import RestoreFeature +import WelcomeFeature import XCTest @testable import AppFeature -@MainActor final class AppFeatureTests: XCTestCase { - func testLaunchFinished() async throws { + func testStartWithoutMessengerCreated() { let store = TestStore( initialState: AppState(), reducer: appReducer, environment: .unimplemented ) - await store.send(.launch(.finished)) { + let mainQueue = DispatchQueue.test + let bgQueue = DispatchQueue.test + var didMakeDB = 0 + + store.environment.mainQueue = mainQueue.eraseToAnyScheduler() + store.environment.bgQueue = bgQueue.eraseToAnyScheduler() + store.environment.dbManager.hasDB.run = { false } + store.environment.dbManager.makeDB.run = { didMakeDB += 1 } + store.environment.messenger.isLoaded.run = { false } + store.environment.messenger.isCreated.run = { false } + + store.send(.start) + + bgQueue.advance() + + XCTAssertNoDifference(didMakeDB, 1) + + mainQueue.advance() + + store.receive(.set(\.$screen, .welcome(WelcomeState()))) { + $0.screen = .welcome(WelcomeState()) + } + } + + func testStartWithMessengerCreated() { + let store = TestStore( + initialState: AppState(), + reducer: appReducer, + environment: .unimplemented + ) + + let mainQueue = DispatchQueue.test + let bgQueue = DispatchQueue.test + var didMakeDB = 0 + var messengerDidLoad = 0 + + store.environment.mainQueue = mainQueue.eraseToAnyScheduler() + store.environment.bgQueue = bgQueue.eraseToAnyScheduler() + store.environment.dbManager.hasDB.run = { false } + store.environment.dbManager.makeDB.run = { didMakeDB += 1 } + store.environment.messenger.isLoaded.run = { false } + store.environment.messenger.isCreated.run = { true } + store.environment.messenger.load.run = { messengerDidLoad += 1 } + + store.send(.start) + + bgQueue.advance() + + XCTAssertNoDifference(didMakeDB, 1) + XCTAssertNoDifference(messengerDidLoad, 1) + + mainQueue.advance() + + store.receive(.set(\.$screen, .home(HomeState()))) { + $0.screen = .home(HomeState()) + } + } + + func testWelcomeFinished() { + let store = TestStore( + initialState: AppState( + screen: .welcome(WelcomeState()) + ), + reducer: appReducer, + environment: .unimplemented + ) + + let mainQueue = DispatchQueue.test + let bgQueue = DispatchQueue.test + var messengerDidLoad = 0 + + store.environment.mainQueue = mainQueue.eraseToAnyScheduler() + store.environment.bgQueue = bgQueue.eraseToAnyScheduler() + store.environment.dbManager.hasDB.run = { true } + store.environment.messenger.isLoaded.run = { false } + store.environment.messenger.isCreated.run = { true } + store.environment.messenger.load.run = { messengerDidLoad += 1 } + + store.send(.welcome(.finished)) { + $0.screen = .loading + } + + bgQueue.advance() + + XCTAssertNoDifference(messengerDidLoad, 1) + + mainQueue.advance() + + store.receive(.set(\.$screen, .home(HomeState()))) { + $0.screen = .home(HomeState()) + } + } + + func testRestoreFinished() { + let store = TestStore( + initialState: AppState( + screen: .restore(RestoreState()) + ), + reducer: appReducer, + environment: .unimplemented + ) + + let mainQueue = DispatchQueue.test + let bgQueue = DispatchQueue.test + var messengerDidLoad = 0 + + store.environment.mainQueue = mainQueue.eraseToAnyScheduler() + store.environment.bgQueue = bgQueue.eraseToAnyScheduler() + store.environment.dbManager.hasDB.run = { true } + store.environment.messenger.isLoaded.run = { false } + store.environment.messenger.isCreated.run = { true } + store.environment.messenger.load.run = { messengerDidLoad += 1 } + + store.send(.restore(.finished)) { + $0.screen = .loading + } + + bgQueue.advance() + + XCTAssertNoDifference(messengerDidLoad, 1) + + mainQueue.advance() + + store.receive(.set(\.$screen, .home(HomeState()))) { $0.screen = .home(HomeState()) } } + + func testWelcomeRestoreTapped() { + let store = TestStore( + initialState: AppState( + screen: .welcome(WelcomeState()) + ), + reducer: appReducer, + environment: .unimplemented + ) + + store.send(.welcome(.restoreTapped)) { + $0.screen = .restore(RestoreState()) + } + } + + func testWelcomeFailed() { + let store = TestStore( + initialState: AppState( + screen: .welcome(WelcomeState()) + ), + reducer: appReducer, + environment: .unimplemented + ) + + let failure = "Something went wrong" + + store.send(.welcome(.failed(failure))) { + $0.screen = .failure(failure) + } + } } -- GitLab From 8014ffc3be3e89de0e0e7981a95bf1fd6c9dabb7 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Wed, 31 Aug 2022 15:41:41 +0100 Subject: [PATCH 04/10] Register from Home --- Examples/xx-messenger/Package.swift | 3 + .../AppFeature/AppEnvironment+Live.swift | 9 +- .../Sources/HomeFeature/HomeFeature.swift | 74 +++++- .../Sources/HomeFeature/HomeView.swift | 39 ++- .../HomeFeatureTests/HomeFeatureTests.swift | 222 +++++++++++++++++- 5 files changed, 338 insertions(+), 9 deletions(-) diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift index b91c4340..4ed6cfd8 100644 --- a/Examples/xx-messenger/Package.swift +++ b/Examples/xx-messenger/Package.swift @@ -69,6 +69,7 @@ let package = Package( dependencies: [ .target(name: "AppCore"), .target(name: "HomeFeature"), + .target(name: "RegisterFeature"), .target(name: "RestoreFeature"), .target(name: "WelcomeFeature"), .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), @@ -89,7 +90,9 @@ let package = Package( name: "HomeFeature", dependencies: [ .target(name: "AppCore"), + .target(name: "RegisterFeature"), .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), + .product(name: "ComposablePresentation", package: "swift-composable-presentation"), .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"), ], swiftSettings: swiftSettings diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift index eec6f199..d3cc134a 100644 --- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift +++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift @@ -34,7 +34,14 @@ extension AppEnvironment { HomeEnvironment( messenger: messenger, mainQueue: mainQueue, - bgQueue: bgQueue + bgQueue: bgQueue, + register: { + RegisterEnvironment( + messenger: messenger, + mainQueue: mainQueue, + bgQueue: bgQueue + ) + } ) } ) diff --git a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift index 499bed78..29abfb6b 100644 --- a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift +++ b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift @@ -1,37 +1,58 @@ +import Combine import ComposableArchitecture +import ComposablePresentation import Foundation +import RegisterFeature import XXClient import XXMessengerClient public struct HomeState: Equatable { - public init() {} + public init( + username: String? = nil, + failure: String? = nil, + register: RegisterState? = nil + ) { + self.username = username + self.failure = failure + self.register = register + } + + @BindableState public var username: String? + @BindableState public var failure: String? + @BindableState public var register: RegisterState? } -public enum HomeAction: Equatable { +public enum HomeAction: Equatable, BindableAction { case start + case binding(BindingAction<HomeState>) + case register(RegisterAction) } public struct HomeEnvironment { public init( messenger: Messenger, mainQueue: AnySchedulerOf<DispatchQueue>, - bgQueue: AnySchedulerOf<DispatchQueue> + bgQueue: AnySchedulerOf<DispatchQueue>, + register: @escaping () -> RegisterEnvironment ) { self.messenger = messenger self.mainQueue = mainQueue self.bgQueue = bgQueue + self.register = register } public var messenger: Messenger public var mainQueue: AnySchedulerOf<DispatchQueue> public var bgQueue: AnySchedulerOf<DispatchQueue> + public var register: () -> RegisterEnvironment } extension HomeEnvironment { public static let unimplemented = HomeEnvironment( messenger: .unimplemented, mainQueue: .unimplemented, - bgQueue: .unimplemented + bgQueue: .unimplemented, + register: { .unimplemented } ) } @@ -39,6 +60,51 @@ public let homeReducer = Reducer<HomeState, HomeAction, HomeEnvironment> { state, action, env in switch action { case .start: + return .run { subscriber in + do { + try env.messenger.start() + + if env.messenger.isConnected() == false { + try env.messenger.connect() + } + + if env.messenger.isLoggedIn() == false { + if try env.messenger.isRegistered() == false { + subscriber.send(.set(\.$register, RegisterState())) + subscriber.send(completion: .finished) + return AnyCancellable {} + } + try env.messenger.logIn() + } + + if let contact = env.messenger.e2e()?.getContact(), + let facts = try? contact.getFacts(), + let username = facts.first(where: { $0.type == 0 })?.fact { + subscriber.send(.set(\.$username, username)) + } + } catch { + subscriber.send(.set(\.$failure, error.localizedDescription)) + } + subscriber.send(completion: .finished) + return AnyCancellable {} + } + .subscribe(on: env.bgQueue) + .receive(on: env.mainQueue) + .eraseToEffect() + + case .register(.finished): + state.register = nil + return Effect(value: .start) + + case .binding(_), .register(_): return .none } } +.binding() +.presenting( + registerReducer, + state: .keyPath(\.register), + id: .notNil(), + action: /HomeAction.register, + environment: { $0.register() } +) diff --git a/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift index 0bda29bd..e40bb1db 100644 --- a/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift +++ b/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift @@ -1,4 +1,6 @@ import ComposableArchitecture +import ComposablePresentation +import RegisterFeature import SwiftUI public struct HomeView: View { @@ -9,19 +11,54 @@ public struct HomeView: View { let store: Store<HomeState, HomeAction> struct ViewState: Equatable { - init(state: HomeState) {} + var username: String? + var failure: String? + + init(state: HomeState) { + username = state.username + failure = state.failure + } } public var body: some View { WithViewStore(store.scope(state: ViewState.init)) { viewStore in NavigationView { Form { + if let username = viewStore.username { + Section { + Text(username) + } header: { + Text("Username") + } + } + if let failure = viewStore.failure { + Section { + Text(failure) + Button { + viewStore.send(.start) + } label: { + Text("Retry") + } + } header: { + Text("Error") + } + } } .navigationTitle("Home") } .navigationViewStyle(.stack) .task { viewStore.send(.start) } + .fullScreenCover( + store.scope( + state: \.register, + action: HomeAction.register + ), + onDismiss: { + viewStore.send(.set(\.$register, nil)) + }, + content: RegisterView.init(store:) + ) } } } diff --git a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift index 21a25bb6..efdd867f 100644 --- a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift +++ b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift @@ -1,16 +1,232 @@ import ComposableArchitecture +import RegisterFeature import XCTest +import XXClient +import XXMessengerClient @testable import HomeFeature -@MainActor final class HomeFeatureTests: XCTestCase { - func testStart() async throws { + func testStartUnregistered() { let store = TestStore( initialState: HomeState(), reducer: homeReducer, environment: .unimplemented ) - await store.send(.start) + let bgQueue = DispatchQueue.test + let mainQueue = DispatchQueue.test + var messengerDidStartWithTimeout: [Int] = [] + var messengerDidConnect = 0 + + store.environment.bgQueue = bgQueue.eraseToAnyScheduler() + store.environment.mainQueue = mainQueue.eraseToAnyScheduler() + store.environment.messenger.start.run = { messengerDidStartWithTimeout.append($0) } + store.environment.messenger.isConnected.run = { false } + store.environment.messenger.connect.run = { messengerDidConnect += 1 } + store.environment.messenger.isLoggedIn.run = { false } + store.environment.messenger.isRegistered.run = { false } + + store.send(.start) + + bgQueue.advance() + + XCTAssertNoDifference(messengerDidStartWithTimeout, [30_000]) + XCTAssertNoDifference(messengerDidConnect, 1) + + mainQueue.advance() + + store.receive(.set(\.$register, RegisterState())) { + $0.register = RegisterState() + } + } + + func testStartRegistered() { + let store = TestStore( + initialState: HomeState(), + reducer: homeReducer, + environment: .unimplemented + ) + + let username = "test_username" + let bgQueue = DispatchQueue.test + let mainQueue = DispatchQueue.test + var messengerDidStartWithTimeout: [Int] = [] + var messengerDidConnect = 0 + var messengerDidLogIn = 0 + + store.environment.bgQueue = bgQueue.eraseToAnyScheduler() + store.environment.mainQueue = mainQueue.eraseToAnyScheduler() + store.environment.messenger.start.run = { messengerDidStartWithTimeout.append($0) } + store.environment.messenger.isConnected.run = { false } + store.environment.messenger.connect.run = { messengerDidConnect += 1 } + store.environment.messenger.isLoggedIn.run = { false } + store.environment.messenger.isRegistered.run = { true } + store.environment.messenger.logIn.run = { messengerDidLogIn += 1 } + store.environment.messenger.e2e.get = { + var e2e = E2E.unimplemented + e2e.getContact.run = { + var contact = Contact.unimplemented(Data()) + contact.getFactsFromContact.run = { _ in [Fact(fact: username, type: 0)] } + return contact + } + return e2e + } + + store.send(.start) + + bgQueue.advance() + + XCTAssertNoDifference(messengerDidStartWithTimeout, [30_000]) + XCTAssertNoDifference(messengerDidConnect, 1) + XCTAssertNoDifference(messengerDidLogIn, 1) + + mainQueue.advance() + + store.receive(.set(\.$username, username)) { + $0.username = username + } + } + + func testRegisterFinished() { + let store = TestStore( + initialState: HomeState( + register: RegisterState() + ), + reducer: homeReducer, + environment: .unimplemented + ) + + let username = "test_username" + let bgQueue = DispatchQueue.test + let mainQueue = DispatchQueue.test + var messengerDidStartWithTimeout: [Int] = [] + var messengerDidLogIn = 0 + + store.environment.bgQueue = bgQueue.eraseToAnyScheduler() + store.environment.mainQueue = mainQueue.eraseToAnyScheduler() + store.environment.messenger.start.run = { messengerDidStartWithTimeout.append($0) } + store.environment.messenger.isConnected.run = { true } + store.environment.messenger.isLoggedIn.run = { false } + store.environment.messenger.isRegistered.run = { true } + store.environment.messenger.logIn.run = { messengerDidLogIn += 1 } + store.environment.messenger.e2e.get = { + var e2e = E2E.unimplemented + e2e.getContact.run = { + var contact = Contact.unimplemented(Data()) + contact.getFactsFromContact.run = { _ in [Fact(fact: username, type: 0)] } + return contact + } + return e2e + } + + store.send(.register(.finished)) { + $0.register = nil + } + + store.receive(.start) + + bgQueue.advance() + + XCTAssertNoDifference(messengerDidStartWithTimeout, [30_000]) + XCTAssertNoDifference(messengerDidLogIn, 1) + + mainQueue.advance() + + store.receive(.set(\.$username, username)) { + $0.username = username + } + } + + func testStartMessengerStartFailure() { + let store = TestStore( + initialState: HomeState(), + reducer: homeReducer, + environment: .unimplemented + ) + + struct Failure: Error {} + let error = Failure() + + store.environment.bgQueue = .immediate + store.environment.mainQueue = .immediate + store.environment.messenger.start.run = { _ in throw error } + + store.send(.start) + + store.receive(.set(\.$failure, error.localizedDescription)) { + $0.failure = error.localizedDescription + } + } + + func testStartMessengerConnectFailure() { + let store = TestStore( + initialState: HomeState(), + reducer: homeReducer, + environment: .unimplemented + ) + + struct Failure: Error {} + let error = Failure() + + store.environment.bgQueue = .immediate + store.environment.mainQueue = .immediate + store.environment.messenger.start.run = { _ in } + store.environment.messenger.isConnected.run = { false } + store.environment.messenger.connect.run = { throw error } + + store.send(.start) + + store.receive(.set(\.$failure, error.localizedDescription)) { + $0.failure = error.localizedDescription + } + } + + func testStartMessengerIsRegisteredFailure() { + let store = TestStore( + initialState: HomeState(), + reducer: homeReducer, + environment: .unimplemented + ) + + struct Failure: Error {} + let error = Failure() + + store.environment.bgQueue = .immediate + store.environment.mainQueue = .immediate + store.environment.messenger.start.run = { _ in } + store.environment.messenger.isConnected.run = { true } + store.environment.messenger.isLoggedIn.run = { false } + store.environment.messenger.isRegistered.run = { throw error } + + store.send(.start) + + store.receive(.set(\.$failure, error.localizedDescription)) { + $0.failure = error.localizedDescription + } + } + + func testStartMessengerLogInFailure() { + let store = TestStore( + initialState: HomeState(), + reducer: homeReducer, + environment: .unimplemented + ) + + struct Failure: Error {} + let error = Failure() + + store.environment.bgQueue = .immediate + store.environment.mainQueue = .immediate + store.environment.messenger.start.run = { _ in } + store.environment.messenger.isConnected.run = { true } + store.environment.messenger.isLoggedIn.run = { false } + store.environment.messenger.isRegistered.run = { true } + store.environment.messenger.logIn.run = { throw error } + + store.send(.start) + + store.receive(.set(\.$failure, error.localizedDescription)) { + $0.failure = error.localizedDescription + } } } -- GitLab From 98b9898ba931f002aef449c511e95a6f0a3f9c38 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Wed, 31 Aug 2022 15:46:39 +0100 Subject: [PATCH 05/10] Add missing tests --- .../AppFeatureTests/AppFeatureTests.swift | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift b/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift index 166b6325..5f055492 100644 --- a/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift +++ b/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift @@ -170,4 +170,50 @@ final class AppFeatureTests: XCTestCase { $0.screen = .failure(failure) } } + + func testStartDatabaseMakeFailure() { + let store = TestStore( + initialState: AppState(), + reducer: appReducer, + environment: .unimplemented + ) + + struct Failure: Error {} + let error = Failure() + + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.dbManager.hasDB.run = { false } + store.environment.dbManager.makeDB.run = { throw error } + + store.send(.start) + + store.receive(.set(\.$screen, .failure(error.localizedDescription))) { + $0.screen = .failure(error.localizedDescription) + } + } + + func testStartMessengerLoadFailure() { + let store = TestStore( + initialState: AppState(), + reducer: appReducer, + environment: .unimplemented + ) + + struct Failure: Error {} + let error = Failure() + + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.dbManager.hasDB.run = { true } + store.environment.messenger.isLoaded.run = { false } + store.environment.messenger.isCreated.run = { true } + store.environment.messenger.load.run = { throw error } + + store.send(.start) + + store.receive(.set(\.$screen, .failure(error.localizedDescription))) { + $0.screen = .failure(error.localizedDescription) + } + } } -- GitLab From d883eb9c917f5be62db1db70612ff98ef643631b Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Wed, 31 Aug 2022 15:47:38 +0100 Subject: [PATCH 06/10] Remove LaunchFeature --- Examples/xx-messenger/Package.swift | 21 -- .../LaunchFeature/LaunchErrorView.swift | 44 --- .../Sources/LaunchFeature/LaunchFeature.swift | 173 --------- .../Sources/LaunchFeature/LaunchView.swift | 118 ------- .../LaunchFeatureTests.swift | 327 ------------------ 5 files changed, 683 deletions(-) delete mode 100644 Examples/xx-messenger/Sources/LaunchFeature/LaunchErrorView.swift delete mode 100644 Examples/xx-messenger/Sources/LaunchFeature/LaunchFeature.swift delete mode 100644 Examples/xx-messenger/Sources/LaunchFeature/LaunchView.swift delete mode 100644 Examples/xx-messenger/Tests/LaunchFeatureTests/LaunchFeatureTests.swift diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift index 4ed6cfd8..f7e0d039 100644 --- a/Examples/xx-messenger/Package.swift +++ b/Examples/xx-messenger/Package.swift @@ -21,7 +21,6 @@ let package = Package( .library(name: "AppCore", targets: ["AppCore"]), .library(name: "AppFeature", targets: ["AppFeature"]), .library(name: "HomeFeature", targets: ["HomeFeature"]), - .library(name: "LaunchFeature", targets: ["LaunchFeature"]), .library(name: "RegisterFeature", targets: ["RegisterFeature"]), .library(name: "RestoreFeature", targets: ["RestoreFeature"]), .library(name: "WelcomeFeature", targets: ["WelcomeFeature"]), @@ -104,26 +103,6 @@ let package = Package( ], swiftSettings: swiftSettings ), - .target( - name: "LaunchFeature", - dependencies: [ - .target(name: "AppCore"), - .target(name: "RegisterFeature"), - .target(name: "RestoreFeature"), - .target(name: "WelcomeFeature"), - .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), - .product(name: "ComposablePresentation", package: "swift-composable-presentation"), - .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"), - ], - swiftSettings: swiftSettings - ), - .testTarget( - name: "LaunchFeatureTests", - dependencies: [ - .target(name: "LaunchFeature"), - ], - swiftSettings: swiftSettings - ), .target( name: "RegisterFeature", dependencies: [ diff --git a/Examples/xx-messenger/Sources/LaunchFeature/LaunchErrorView.swift b/Examples/xx-messenger/Sources/LaunchFeature/LaunchErrorView.swift deleted file mode 100644 index 893492c0..00000000 --- a/Examples/xx-messenger/Sources/LaunchFeature/LaunchErrorView.swift +++ /dev/null @@ -1,44 +0,0 @@ -import ComposableArchitecture -import SwiftUI - -struct LaunchErrorView: View { - var failure: String - var onRetry: () -> Void - - var body: some View { - NavigationView { - VStack(spacing: 0) { - ScrollView { - Text(failure) - .frame(maxWidth: .infinity, alignment: .leading) - .padding() - } - - Divider() - - Button { - onRetry() - } label: { - Text("Retry") - .frame(maxWidth: .infinity) - } - .buttonStyle(.borderedProminent) - .controlSize(.large) - .padding() - } - .navigationTitle("Error") - } - .navigationViewStyle(.stack) - } -} - -#if DEBUG -struct LaunchErrorView_Previews: PreviewProvider { - static var previews: some View { - LaunchErrorView( - failure: "Something went wrong...", - onRetry: {} - ) - } -} -#endif diff --git a/Examples/xx-messenger/Sources/LaunchFeature/LaunchFeature.swift b/Examples/xx-messenger/Sources/LaunchFeature/LaunchFeature.swift deleted file mode 100644 index a7bf89ce..00000000 --- a/Examples/xx-messenger/Sources/LaunchFeature/LaunchFeature.swift +++ /dev/null @@ -1,173 +0,0 @@ -import AppCore -import Combine -import ComposableArchitecture -import ComposablePresentation -import Foundation -import RegisterFeature -import RestoreFeature -import WelcomeFeature -import XXMessengerClient -import XXModels - -public struct LaunchState: Equatable { - public enum Screen: Equatable { - case loading - case welcome(WelcomeState) - case restore(RestoreState) - case register(RegisterState) - case failure(String) - } - - public init( - screen: Screen = .loading - ) { - self.screen = screen - } - - @BindableState public var screen: Screen -} - -extension LaunchState.Screen { - var asWelcome: WelcomeState? { - get { (/LaunchState.Screen.welcome).extract(from: self) } - set { if let state = newValue { self = .welcome(state) } } - } - var asRestore: RestoreState? { - get { (/LaunchState.Screen.restore).extract(from: self) } - set { if let state = newValue { self = .restore(state) } } - } - var asRegister: RegisterState? { - get { (/LaunchState.Screen.register).extract(from: self) } - set { if let state = newValue { self = .register(state) } } - } -} - -public enum LaunchAction: Equatable, BindableAction { - case start - case finished - case binding(BindingAction<LaunchState>) - case welcome(WelcomeAction) - case restore(RestoreAction) - case register(RegisterAction) -} - -public struct LaunchEnvironment { - public init( - dbManager: DBManager, - messenger: Messenger, - mainQueue: AnySchedulerOf<DispatchQueue>, - bgQueue: AnySchedulerOf<DispatchQueue>, - welcome: @escaping () -> WelcomeEnvironment, - restore: @escaping () -> RestoreEnvironment, - register: @escaping () -> RegisterEnvironment - ) { - self.dbManager = dbManager - self.messenger = messenger - self.mainQueue = mainQueue - self.bgQueue = bgQueue - self.welcome = welcome - self.restore = restore - self.register = register - } - - public var dbManager: DBManager - public var messenger: Messenger - public var mainQueue: AnySchedulerOf<DispatchQueue> - public var bgQueue: AnySchedulerOf<DispatchQueue> - public var welcome: () -> WelcomeEnvironment - public var restore: () -> RestoreEnvironment - public var register: () -> RegisterEnvironment -} - -extension LaunchEnvironment { - public static let unimplemented = LaunchEnvironment( - dbManager: .unimplemented, - messenger: .unimplemented, - mainQueue: .unimplemented, - bgQueue: .unimplemented, - welcome: { .unimplemented }, - restore: { .unimplemented }, - register: { .unimplemented } - ) -} - -public let launchReducer = Reducer<LaunchState, LaunchAction, LaunchEnvironment> -{ state, action, env in - switch action { - case .start, .welcome(.finished), .restore(.finished), .register(.finished): - state.screen = .loading - return .future { fulfill in - do { - if env.dbManager.hasDB() == false { - try env.dbManager.makeDB() - } - - if env.messenger.isLoaded() == false { - if env.messenger.isCreated() == false { - fulfill(.success(.set(\.$screen, .welcome(WelcomeState())))) - return - } - try env.messenger.load() - } - - try env.messenger.start() - - if env.messenger.isConnected() == false { - try env.messenger.connect() - } - - if env.messenger.isLoggedIn() == false { - if try env.messenger.isRegistered() == false { - fulfill(.success(.set(\.$screen, .register(RegisterState())))) - return - } - try env.messenger.logIn() - } - - fulfill(.success(.finished)) - } - catch { - fulfill(.success(.set(\.$screen, .failure(error.localizedDescription)))) - } - } - .subscribe(on: env.bgQueue) - .receive(on: env.mainQueue) - .eraseToEffect() - - case .finished: - return .none - - case .welcome(.restoreTapped): - state.screen = .restore(RestoreState()) - return .none - - case .welcome(.failed(let failure)): - state.screen = .failure(failure) - return .none - - case .binding(_), .welcome(_), .restore(_), .register(_): - return .none - } -} -.binding() -.presenting( - welcomeReducer, - state: .keyPath(\.screen.asWelcome), - id: .notNil(), - action: /LaunchAction.welcome, - environment: { $0.welcome() } -) -.presenting( - restoreReducer, - state: .keyPath(\.screen.asRestore), - id: .notNil(), - action: /LaunchAction.restore, - environment: { $0.restore() } -) -.presenting( - registerReducer, - state: .keyPath(\.screen.asRegister), - id: .notNil(), - action: /LaunchAction.register, - environment: { $0.register() } -) diff --git a/Examples/xx-messenger/Sources/LaunchFeature/LaunchView.swift b/Examples/xx-messenger/Sources/LaunchFeature/LaunchView.swift deleted file mode 100644 index ed110302..00000000 --- a/Examples/xx-messenger/Sources/LaunchFeature/LaunchView.swift +++ /dev/null @@ -1,118 +0,0 @@ -import ComposableArchitecture -import RegisterFeature -import RestoreFeature -import SwiftUI -import WelcomeFeature - -public struct LaunchView: View { - public init(store: Store<LaunchState, LaunchAction>) { - self.store = store - } - - struct ViewState: Equatable { - enum Screen: Equatable { - case loading - case welcome - case restore - case register - case failure(String) - } - - init(_ state: LaunchState) { - switch state.screen { - case .loading: screen = .loading - case .welcome(_): screen = .welcome - case .restore(_): screen = .restore - case .register(_): screen = .register - case .failure(let failure): screen = .failure(failure) - } - } - - var screen: Screen - } - - let store: Store<LaunchState, LaunchAction> - - public var body: some View { - WithViewStore(store.scope(state: ViewState.init)) { viewStore in - ZStack { - switch viewStore.screen { - case .loading: - ProgressView { - Text("Loading") - } - .controlSize(.large) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .transition(.opacity) - - case .welcome: - IfLetStore( - store.scope( - state: { (/LaunchState.Screen.welcome).extract(from: $0.screen) }, - action: LaunchAction.welcome - ), - then: WelcomeView.init(store:) - ) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .transition(.asymmetric( - insertion: .move(edge: .trailing), - removal: .opacity - )) - - case .restore: - IfLetStore( - store.scope( - state: { (/LaunchState.Screen.restore).extract(from: $0.screen) }, - action: LaunchAction.restore - ), - then: RestoreView.init(store:) - ) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .transition(.asymmetric( - insertion: .move(edge: .trailing), - removal: .opacity - )) - - case .register: - IfLetStore( - store.scope( - state: { (/LaunchState.Screen.register).extract(from: $0.screen) }, - action: LaunchAction.register - ), - then: RegisterView.init(store:) - ) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .transition(.asymmetric( - insertion: .move(edge: .trailing), - removal: .opacity - )) - - case .failure(let failure): - LaunchErrorView( - failure: failure, - onRetry: { viewStore.send(.start) } - ) - .frame(maxWidth: .infinity, maxHeight: .infinity) - .transition(.asymmetric( - insertion: .move(edge: .trailing), - removal: .opacity - )) - } - } - .animation(.default, value: viewStore.screen) - .task { viewStore.send(.start) } - } - } -} - -#if DEBUG -public struct LaunchView_Previews: PreviewProvider { - public static var previews: some View { - LaunchView(store: Store( - initialState: LaunchState(), - reducer: .empty, - environment: () - )) - } -} -#endif diff --git a/Examples/xx-messenger/Tests/LaunchFeatureTests/LaunchFeatureTests.swift b/Examples/xx-messenger/Tests/LaunchFeatureTests/LaunchFeatureTests.swift deleted file mode 100644 index b2b3f856..00000000 --- a/Examples/xx-messenger/Tests/LaunchFeatureTests/LaunchFeatureTests.swift +++ /dev/null @@ -1,327 +0,0 @@ -import AppCore -import ComposableArchitecture -import RegisterFeature -import RestoreFeature -import WelcomeFeature -import XCTest -import XXModels -@testable import LaunchFeature - -@MainActor -final class LaunchFeatureTests: XCTestCase { - func testStart() { - let store = TestStore( - initialState: LaunchState(), - reducer: launchReducer, - environment: .unimplemented - ) - - let mainQueue = DispatchQueue.test - let bgQueue = DispatchQueue.test - var didMakeDB = 0 - var messengerDidLoad = 0 - var messengerDidStart = 0 - var messengerDidConnect = 0 - var messengerDidLogIn = 0 - - store.environment.mainQueue = mainQueue.eraseToAnyScheduler() - store.environment.bgQueue = bgQueue.eraseToAnyScheduler() - store.environment.dbManager.hasDB.run = { false } - store.environment.dbManager.makeDB.run = { didMakeDB += 1 } - store.environment.messenger.isLoaded.run = { false } - store.environment.messenger.isCreated.run = { true } - store.environment.messenger.load.run = { messengerDidLoad += 1 } - store.environment.messenger.start.run = { _ in messengerDidStart += 1 } - store.environment.messenger.isConnected.run = { false } - store.environment.messenger.connect.run = { messengerDidConnect += 1 } - store.environment.messenger.isLoggedIn.run = { false } - store.environment.messenger.isRegistered.run = { true } - store.environment.messenger.logIn.run = { messengerDidLogIn += 1 } - - store.send(.start) - - bgQueue.advance() - - XCTAssertNoDifference(didMakeDB, 1) - XCTAssertNoDifference(messengerDidLoad, 1) - XCTAssertNoDifference(messengerDidStart, 1) - XCTAssertNoDifference(messengerDidConnect, 1) - XCTAssertNoDifference(messengerDidLogIn, 1) - - mainQueue.advance() - - store.receive(.finished) - } - - func testStartWithoutMessengerCreated() { - let store = TestStore( - initialState: LaunchState(), - reducer: launchReducer, - environment: .unimplemented - ) - - let mainQueue = DispatchQueue.test - let bgQueue = DispatchQueue.test - - store.environment.mainQueue = mainQueue.eraseToAnyScheduler() - store.environment.bgQueue = bgQueue.eraseToAnyScheduler() - store.environment.dbManager.hasDB.run = { true } - store.environment.messenger.isLoaded.run = { false } - store.environment.messenger.isCreated.run = { false } - - store.send(.start) - - bgQueue.advance() - mainQueue.advance() - - store.receive(.set(\.$screen, .welcome(WelcomeState()))) { - $0.screen = .welcome(WelcomeState()) - } - } - - func testStartUnregistered() { - let store = TestStore( - initialState: LaunchState(), - reducer: launchReducer, - environment: .unimplemented - ) - - let mainQueue = DispatchQueue.test - let bgQueue = DispatchQueue.test - - store.environment.mainQueue = mainQueue.eraseToAnyScheduler() - store.environment.bgQueue = bgQueue.eraseToAnyScheduler() - store.environment.dbManager.hasDB.run = { true } - store.environment.messenger.isLoaded.run = { true } - store.environment.messenger.start.run = { _ in } - store.environment.messenger.isConnected.run = { true } - store.environment.messenger.isLoggedIn.run = { false } - store.environment.messenger.isRegistered.run = { false } - - store.send(.start) - - bgQueue.advance() - mainQueue.advance() - - store.receive(.set(\.$screen, .register(RegisterState()))) { - $0.screen = .register(RegisterState()) - } - } - - func testStartMakeDBFailure() { - let store = TestStore( - initialState: LaunchState(), - reducer: launchReducer, - environment: .unimplemented - ) - - let mainQueue = DispatchQueue.test - let bgQueue = DispatchQueue.test - struct Error: Swift.Error {} - let error = Error() - - store.environment.mainQueue = mainQueue.eraseToAnyScheduler() - store.environment.bgQueue = bgQueue.eraseToAnyScheduler() - store.environment.dbManager.hasDB.run = { false } - store.environment.dbManager.makeDB.run = { throw error } - - store.send(.start) - - bgQueue.advance() - mainQueue.advance() - - store.receive(.set(\.$screen, .failure(error.localizedDescription))) { - $0.screen = .failure(error.localizedDescription) - } - } - - func testStartMessengerLoadFailure() { - let store = TestStore( - initialState: LaunchState(), - reducer: launchReducer, - environment: .unimplemented - ) - - let mainQueue = DispatchQueue.test - let bgQueue = DispatchQueue.test - struct Error: Swift.Error {} - let error = Error() - - store.environment.mainQueue = mainQueue.eraseToAnyScheduler() - store.environment.bgQueue = bgQueue.eraseToAnyScheduler() - store.environment.dbManager.hasDB.run = { true } - store.environment.messenger.isLoaded.run = { false } - store.environment.messenger.isCreated.run = { true } - store.environment.messenger.load.run = { throw error } - - store.send(.start) - - bgQueue.advance() - mainQueue.advance() - - store.receive(.set(\.$screen, .failure(error.localizedDescription))) { - $0.screen = .failure(error.localizedDescription) - } - } - - func testStartMessengerStartFailure() { - let store = TestStore( - initialState: LaunchState(), - reducer: launchReducer, - environment: .unimplemented - ) - - let mainQueue = DispatchQueue.test - let bgQueue = DispatchQueue.test - struct Error: Swift.Error {} - let error = Error() - - store.environment.mainQueue = mainQueue.eraseToAnyScheduler() - store.environment.bgQueue = bgQueue.eraseToAnyScheduler() - store.environment.dbManager.hasDB.run = { true } - store.environment.messenger.isLoaded.run = { true } - store.environment.messenger.start.run = { _ in throw error } - - store.send(.start) - - bgQueue.advance() - mainQueue.advance() - - store.receive(.set(\.$screen, .failure(error.localizedDescription))) { - $0.screen = .failure(error.localizedDescription) - } - } - - func testStartMessengerConnectFailure() { - let store = TestStore( - initialState: LaunchState(), - reducer: launchReducer, - environment: .unimplemented - ) - - let mainQueue = DispatchQueue.test - let bgQueue = DispatchQueue.test - struct Error: Swift.Error {} - let error = Error() - - store.environment.mainQueue = mainQueue.eraseToAnyScheduler() - store.environment.bgQueue = bgQueue.eraseToAnyScheduler() - store.environment.dbManager.hasDB.run = { true } - store.environment.messenger.isLoaded.run = { true } - store.environment.messenger.start.run = { _ in } - store.environment.messenger.isConnected.run = { false } - store.environment.messenger.connect.run = { throw error } - - store.send(.start) - - bgQueue.advance() - mainQueue.advance() - - store.receive(.set(\.$screen, .failure(error.localizedDescription))) { - $0.screen = .failure(error.localizedDescription) - } - } - - func testStartMessengerIsRegisteredFailure() { - let store = TestStore( - initialState: LaunchState(), - reducer: launchReducer, - environment: .unimplemented - ) - - let mainQueue = DispatchQueue.test - let bgQueue = DispatchQueue.test - struct Error: Swift.Error {} - let error = Error() - - store.environment.mainQueue = mainQueue.eraseToAnyScheduler() - store.environment.bgQueue = bgQueue.eraseToAnyScheduler() - store.environment.dbManager.hasDB.run = { true } - store.environment.messenger.isLoaded.run = { true } - store.environment.messenger.start.run = { _ in } - store.environment.messenger.isConnected.run = { true } - store.environment.messenger.isLoggedIn.run = { false } - store.environment.messenger.isRegistered.run = { throw error } - - store.send(.start) - - bgQueue.advance() - mainQueue.advance() - - store.receive(.set(\.$screen, .failure(error.localizedDescription))) { - $0.screen = .failure(error.localizedDescription) - } - } - - func testStartMessengerLogInFailure() { - let store = TestStore( - initialState: LaunchState(), - reducer: launchReducer, - environment: .unimplemented - ) - - let mainQueue = DispatchQueue.test - let bgQueue = DispatchQueue.test - struct Error: Swift.Error {} - let error = Error() - - store.environment.mainQueue = mainQueue.eraseToAnyScheduler() - store.environment.bgQueue = bgQueue.eraseToAnyScheduler() - store.environment.dbManager.hasDB.run = { true } - store.environment.messenger.isLoaded.run = { true } - store.environment.messenger.start.run = { _ in } - store.environment.messenger.isConnected.run = { true } - store.environment.messenger.isLoggedIn.run = { false } - store.environment.messenger.isRegistered.run = { true } - store.environment.messenger.logIn.run = { throw error } - - store.send(.start) - - bgQueue.advance() - mainQueue.advance() - - store.receive(.set(\.$screen, .failure(error.localizedDescription))) { - $0.screen = .failure(error.localizedDescription) - } - } - - func testWelcomeRestoreTapped() { - let store = TestStore( - initialState: LaunchState( - screen: .welcome(WelcomeState()) - ), - reducer: launchReducer, - environment: .unimplemented - ) - - store.send(.welcome(.restoreTapped)) { - $0.screen = .restore(RestoreState()) - } - } - - func testWelcomeFailed() { - let store = TestStore( - initialState: LaunchState( - screen: .welcome(WelcomeState()) - ), - reducer: launchReducer, - environment: .unimplemented - ) - - let failure = "Something went wrong" - - store.send(.welcome(.failed(failure))) { - $0.screen = .failure(failure) - } - } - - func testFinished() { - let store = TestStore( - initialState: LaunchState(), - reducer: launchReducer, - environment: .unimplemented - ) - - store.send(.finished) - } -} -- GitLab From e39a275f2dd9d69a337c24297a0204da7c66dce9 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Wed, 31 Aug 2022 16:00:33 +0100 Subject: [PATCH 07/10] Update GitLab CI config --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9e87f241..855b6566 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -22,5 +22,7 @@ test-examples-ios: tags: - ios script: + - for ip in $(dig @8.8.8.8 github.com +short); do ssh-keyscan github.com,$ip; ssh-keyscan $ip; done 2>/dev/null >> ~/.ssh/known_hosts + - for ip in $(dig @8.8.8.8 git.xx.network +short); do ssh-keyscan git.xx.network,$ip; ssh-keyscan $ip; done 2>/dev/null >> ~/.ssh/known_hosts - ./run-tests.sh examples-ios retry: 1 -- GitLab From 16e19de3b3746e8a5af85a21dbbcd9b187107c6c Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Wed, 31 Aug 2022 16:03:31 +0100 Subject: [PATCH 08/10] Update build schemes --- .../xcschemes/LaunchFeature.xcscheme | 78 ------------------- .../xcschemes/XXMessenger.xcscheme | 10 --- 2 files changed, 88 deletions(-) delete mode 100644 Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/LaunchFeature.xcscheme diff --git a/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/LaunchFeature.xcscheme b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/LaunchFeature.xcscheme deleted file mode 100644 index 0b5b716c..00000000 --- a/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/LaunchFeature.xcscheme +++ /dev/null @@ -1,78 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<Scheme - LastUpgradeVersion = "1340" - version = "1.3"> - <BuildAction - parallelizeBuildables = "YES" - buildImplicitDependencies = "YES"> - <BuildActionEntries> - <BuildActionEntry - buildForTesting = "YES" - buildForRunning = "YES" - buildForProfiling = "YES" - buildForArchiving = "YES" - buildForAnalyzing = "YES"> - <BuildableReference - BuildableIdentifier = "primary" - BlueprintIdentifier = "LaunchFeature" - BuildableName = "LaunchFeature" - BlueprintName = "LaunchFeature" - ReferencedContainer = "container:"> - </BuildableReference> - </BuildActionEntry> - </BuildActionEntries> - </BuildAction> - <TestAction - buildConfiguration = "Debug" - selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" - selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES" - codeCoverageEnabled = "YES"> - <Testables> - <TestableReference - skipped = "NO"> - <BuildableReference - BuildableIdentifier = "primary" - BlueprintIdentifier = "LaunchFeatureTests" - BuildableName = "LaunchFeatureTests" - BlueprintName = "LaunchFeatureTests" - ReferencedContainer = "container:"> - </BuildableReference> - </TestableReference> - </Testables> - </TestAction> - <LaunchAction - buildConfiguration = "Debug" - selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" - selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - launchStyle = "0" - useCustomWorkingDirectory = "NO" - ignoresPersistentStateOnLaunch = "NO" - debugDocumentVersioning = "YES" - debugServiceExtension = "internal" - allowLocationSimulation = "YES"> - </LaunchAction> - <ProfileAction - buildConfiguration = "Release" - shouldUseLaunchSchemeArgsEnv = "YES" - savedToolIdentifier = "" - useCustomWorkingDirectory = "NO" - debugDocumentVersioning = "YES"> - <MacroExpansion> - <BuildableReference - BuildableIdentifier = "primary" - BlueprintIdentifier = "LaunchFeature" - BuildableName = "LaunchFeature" - BlueprintName = "LaunchFeature" - ReferencedContainer = "container:"> - </BuildableReference> - </MacroExpansion> - </ProfileAction> - <AnalyzeAction - buildConfiguration = "Debug"> - </AnalyzeAction> - <ArchiveAction - buildConfiguration = "Release" - revealArchiveInOrganizer = "YES"> - </ArchiveAction> -</Scheme> diff --git a/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme b/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme index 9e83a72f..669debfc 100644 --- a/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme +++ b/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme @@ -59,16 +59,6 @@ ReferencedContainer = "container:.."> </BuildableReference> </TestableReference> - <TestableReference - skipped = "NO"> - <BuildableReference - BuildableIdentifier = "primary" - BlueprintIdentifier = "LaunchFeatureTests" - BuildableName = "LaunchFeatureTests" - BlueprintName = "LaunchFeatureTests" - ReferencedContainer = "container:.."> - </BuildableReference> - </TestableReference> <TestableReference skipped = "NO"> <BuildableReference -- GitLab From 4b4193260d4418fb9cee348a59593b26fe72dbc7 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Wed, 31 Aug 2022 16:12:56 +0100 Subject: [PATCH 09/10] Update run-tests script --- run-tests.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/run-tests.sh b/run-tests.sh index 1f9d60c4..1a87f661 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -1,9 +1,6 @@ #!/bin/sh set -e -echo "\n\033[1;32m▶ Xcode version\033[0m" -xcodebuild -version - if [ "$1" = "macos" ]; then echo "\n\033[1;32m▶ Running package tests on macOS...\033[0m" -- GitLab From 3dc278e6b26c8bcf70353ef4b92b87d3c805b31c Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Wed, 31 Aug 2022 16:13:22 +0100 Subject: [PATCH 10/10] Update GitLab CI config --- .gitlab-ci.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 855b6566..10f45cd3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,3 +1,8 @@ +before_script: + - for ip in $(dig @8.8.8.8 github.com +short); do ssh-keyscan github.com,$ip; ssh-keyscan $ip; done 2>/dev/null >> ~/.ssh/known_hosts + - for ip in $(dig @8.8.8.8 git.xx.network +short); do ssh-keyscan git.xx.network,$ip; ssh-keyscan $ip; done 2>/dev/null >> ~/.ssh/known_hosts + - xcodebuild -version + stages: - test @@ -22,7 +27,5 @@ test-examples-ios: tags: - ios script: - - for ip in $(dig @8.8.8.8 github.com +short); do ssh-keyscan github.com,$ip; ssh-keyscan $ip; done 2>/dev/null >> ~/.ssh/known_hosts - - for ip in $(dig @8.8.8.8 git.xx.network +short); do ssh-keyscan git.xx.network,$ip; ssh-keyscan $ip; done 2>/dev/null >> ~/.ssh/known_hosts - ./run-tests.sh examples-ios retry: 1 -- GitLab