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] 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