diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9e87f241d5ee8ea1c5511bdca11093bb0e0ce5c4..10f45cd3ff6b3f9359973783412c08b883c300c3 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 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 0b5b716cb194f784d36c6cfe45d705107c4e6549..0000000000000000000000000000000000000000 --- 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/Package.swift b/Examples/xx-messenger/Package.swift index 5eb7bede849b86baf218f969361cf911bd4a5907..f7e0d0399b2b67ef32c8bece58cfb102e76864a2 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"]), @@ -69,7 +68,6 @@ let package = Package( dependencies: [ .target(name: "AppCore"), .target(name: "HomeFeature"), - .target(name: "LaunchFeature"), .target(name: "RegisterFeature"), .target(name: "RestoreFeature"), .target(name: "WelcomeFeature"), @@ -89,27 +87,9 @@ let package = Package( ), .target( name: "HomeFeature", - dependencies: [ - .target(name: "AppCore"), - .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), - .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"), - ], - swiftSettings: swiftSettings - ), - .testTarget( - name: "HomeFeatureTests", - dependencies: [ - .target(name: "HomeFeature"), - ], - 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"), @@ -117,9 +97,9 @@ let package = Package( swiftSettings: swiftSettings ), .testTarget( - name: "LaunchFeatureTests", + name: "HomeFeatureTests", dependencies: [ - .target(name: "LaunchFeature"), + .target(name: "HomeFeature"), ], swiftSettings: swiftSettings ), 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 9e83a72f3d20c49c9a5fec35e35ca64500fe5ebe..669debfc2ca857cae6de176b19f3caeb579e56e6 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 diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift index ff97f80333da243e0dd39008f16d58587a094463..d3cc134a7dbca79b4d9e75472eb26697f08a455b 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,22 +16,25 @@ 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 + ) + }, + restore: { + RestoreEnvironment() + }, + home: { + HomeEnvironment( messenger: messenger, mainQueue: mainQueue, bgQueue: bgQueue, - welcome: { - WelcomeEnvironment( - messenger: messenger, - mainQueue: mainQueue, - bgQueue: bgQueue - ) - }, - restore: { - RestoreEnvironment() - }, register: { RegisterEnvironment( messenger: messenger, @@ -41,13 +43,6 @@ extension AppEnvironment { ) } ) - }, - home: { - HomeEnvironment( - messenger: messenger, - mainQueue: mainQueue, - bgQueue: bgQueue - ) } ) } diff --git a/Examples/xx-messenger/Sources/AppFeature/AppFeature.swift b/Examples/xx-messenger/Sources/AppFeature/AppFeature.swift index 18927c0bc21af48ba57317be8b38c7c80ca27a0b..535d69fcbc3044d7698040497d5eba6020d5e426 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 317560a3f749bd2b2eaa923318c7c4f4fc66fb1c..2af6d3521c4037f54c34400acd51103c9429451d 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/Sources/HomeFeature/HomeFeature.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift index 6b61b0997c784eafc891d11cf46a15a22ba4549d..29abfb6ba9d977e0e87b0cb31bf7eca167b15886 100644 --- a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift +++ b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift @@ -1,36 +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 } ) } @@ -38,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 0bda29bd8c2ea48b125337dd5a487e63334794f0..e40bb1db3982e1673ca2c77d6b8b0e72fa697da9 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/Sources/LaunchFeature/LaunchErrorView.swift b/Examples/xx-messenger/Sources/LaunchFeature/LaunchErrorView.swift deleted file mode 100644 index 893492c0b9670af7389e341713527346eb6e4d26..0000000000000000000000000000000000000000 --- 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 39e3efdf0c70780ccd3f2343f17a0fc138fdab57..0000000000000000000000000000000000000000 --- a/Examples/xx-messenger/Sources/LaunchFeature/LaunchFeature.swift +++ /dev/null @@ -1,172 +0,0 @@ -import AppCore -import Combine -import ComposableArchitecture -import ComposablePresentation -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 ed110302f45ff390664f9af2e939571c9bbb2646..0000000000000000000000000000000000000000 --- 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/AppFeatureTests/AppFeatureTests.swift b/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift index 6563da86bb1d74e25b4cb0c551c7314c20542792..5f0554922106e525c5f6c151c958b602dd8f5552 100644 --- a/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift +++ b/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift @@ -1,19 +1,219 @@ 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) + } + } + + 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) + } + } } diff --git a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift index 21a25bb641274dced7d7dd83ebc49f073ada4420..efdd867ff247317e7dd6dc379f38edfbc5ba7b3b 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 + } } } diff --git a/Examples/xx-messenger/Tests/LaunchFeatureTests/LaunchFeatureTests.swift b/Examples/xx-messenger/Tests/LaunchFeatureTests/LaunchFeatureTests.swift deleted file mode 100644 index b2b3f856f79260ef652664f9c9892382296ede95..0000000000000000000000000000000000000000 --- 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) - } -} diff --git a/Examples/xx-messenger/XXMessenger.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/xx-messenger/XXMessenger.xcworkspace/xcshareddata/swiftpm/Package.resolved index c330aa9448492ecb8f33d3b05ee260fe3897d417..b9909a307686e48727475bb07420e0efc4cd13cd 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" } }, { diff --git a/run-tests.sh b/run-tests.sh index 1f9d60c4075a3a06dd1bce6e6f0fb82fc647530a..1a87f661803af51200586db33dac341407cc7e78 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"