diff --git a/Example/example-app/Package.swift b/Example/example-app/Package.swift index 0c0ef22230153b65f531bbdb29f044158041a35f..72fd8b73e72ed93e7d71bcba0f0e74d6040f933a 100644 --- a/Example/example-app/Package.swift +++ b/Example/example-app/Package.swift @@ -144,6 +144,10 @@ let package = Package( name: "ComposableArchitecture", package: "swift-composable-architecture" ), + .product( + name: "ElixxirDAppsSDK", + package: "elixxir-dapps-sdk-swift" + ), ], swiftSettings: swiftSettings ), diff --git a/Example/example-app/Sources/AppFeature/App.swift b/Example/example-app/Sources/AppFeature/App.swift index 16d9b87a7ff78c79866c82132d612bf5d68427d3..37007c70eb7168a0f217baf3e7cc0f51f2522ea3 100644 --- a/Example/example-app/Sources/AppFeature/App.swift +++ b/Example/example-app/Sources/AppFeature/App.swift @@ -23,6 +23,7 @@ struct App: SwiftUI.App { extension AppEnvironment { static func live() -> AppEnvironment { let clientSubject = CurrentValueSubject<Client?, Never>(nil) + let identitySubject = CurrentValueSubject<Identity?, Never>(nil) let mainScheduler = DispatchQueue.main.eraseToAnyScheduler() let bgScheduler = DispatchQueue( label: "xx.network.dApps.ExampleApp.bg", @@ -48,7 +49,12 @@ extension AppEnvironment { mainScheduler: mainScheduler, makeId: UUID.init, error: ErrorEnvironment(), - myIdentity: MyIdentityEnvironment() + myIdentity: MyIdentityEnvironment( + getClient: { clientSubject.value }, + observeIdentity: { identitySubject.eraseToAnyPublisher() }, + bgScheduler: bgScheduler, + mainScheduler: mainScheduler + ) ) ) } diff --git a/Example/example-app/Sources/MyIdentityFeature/MyIdentityFeature.swift b/Example/example-app/Sources/MyIdentityFeature/MyIdentityFeature.swift index fb4c7200466290ea5f950eaaa65605f1948042ca..4fe79ffdddde683f4898e05793fd3892964574a6 100644 --- a/Example/example-app/Sources/MyIdentityFeature/MyIdentityFeature.swift +++ b/Example/example-app/Sources/MyIdentityFeature/MyIdentityFeature.swift @@ -1,4 +1,6 @@ +import Combine import ComposableArchitecture +import ElixxirDAppsSDK public struct MyIdentityState: Equatable { public init( @@ -8,18 +10,67 @@ public struct MyIdentityState: Equatable { } public var id: UUID + public var identity: Identity? } -public enum MyIdentityAction: Equatable {} +public enum MyIdentityAction: Equatable { + case viewDidLoad + case observeMyIdentity + case didUpdateMyIdentity(Identity?) +} public struct MyIdentityEnvironment { - public init() {} + public init( + getClient: @escaping () -> Client?, + observeIdentity: @escaping () -> AnyPublisher<Identity?, Never>, + bgScheduler: AnySchedulerOf<DispatchQueue>, + mainScheduler: AnySchedulerOf<DispatchQueue> + ) { + self.getClient = getClient + self.observeIdentity = observeIdentity + self.bgScheduler = bgScheduler + self.mainScheduler = mainScheduler + } + + public var getClient: () -> Client? + public var observeIdentity: () -> AnyPublisher<Identity?, Never> + public var bgScheduler: AnySchedulerOf<DispatchQueue> + public var mainScheduler: AnySchedulerOf<DispatchQueue> } -public let myIdentityReducer = Reducer<MyIdentityState, MyIdentityAction, MyIdentityEnvironment>.empty +public let myIdentityReducer = Reducer<MyIdentityState, MyIdentityAction, MyIdentityEnvironment> +{ state, action, env in + switch action { + case .viewDidLoad: + return .merge([ + .init(value: .observeMyIdentity), + ]) + + case .observeMyIdentity: + struct EffectId: Hashable { + let id: UUID + } + return env.observeIdentity() + .removeDuplicates() + .map(MyIdentityAction.didUpdateMyIdentity) + .subscribe(on: env.bgScheduler) + .receive(on: env.mainScheduler) + .eraseToEffect() + .cancellable(id: EffectId(id: state.id), cancelInFlight: true) + + case .didUpdateMyIdentity(let identity): + state.identity = identity + return .none + } +} #if DEBUG extension MyIdentityEnvironment { - public static let failing = MyIdentityEnvironment() + public static let failing = MyIdentityEnvironment( + getClient: { fatalError() }, + observeIdentity: { fatalError() }, + bgScheduler: .failing, + mainScheduler: .failing + ) } #endif diff --git a/Example/example-app/Sources/MyIdentityFeature/MyIdentityView.swift b/Example/example-app/Sources/MyIdentityFeature/MyIdentityView.swift index c50941b6a987a909aa2440541c69f6ad133ce82a..62cea2bbfa8095805777cdb74b91e8e460637575 100644 --- a/Example/example-app/Sources/MyIdentityFeature/MyIdentityView.swift +++ b/Example/example-app/Sources/MyIdentityFeature/MyIdentityView.swift @@ -15,6 +15,9 @@ public struct MyIdentityView: View { public var body: some View { WithViewStore(store.scope(state: ViewState.init)) { viewStore in Text("MyIdentityView") + .task { + viewStore.send(.viewDidLoad) + } } } } diff --git a/Example/example-app/Tests/MyIdentityFeatureTests/MyIdentityFeatureTests.swift b/Example/example-app/Tests/MyIdentityFeatureTests/MyIdentityFeatureTests.swift index f4a6a410a779bdc85e3c8742289d87012399b353..d3fcdc08fcfc6637c74824f2f44223894e9fcc3d 100644 --- a/Example/example-app/Tests/MyIdentityFeatureTests/MyIdentityFeatureTests.swift +++ b/Example/example-app/Tests/MyIdentityFeatureTests/MyIdentityFeatureTests.swift @@ -1,9 +1,57 @@ +import Combine import ComposableArchitecture +import ElixxirDAppsSDK import XCTest @testable import MyIdentityFeature final class MyIdentityFeatureTests: XCTestCase { - func testExample() { - XCTAssert(true) + func testViewDidLoad() { + let myIdentitySubject = PassthroughSubject<Identity?, Never>() + let bgScheduler = DispatchQueue.test + let mainScheduler = DispatchQueue.test + + var env = MyIdentityEnvironment.failing + env.observeIdentity = { myIdentitySubject.eraseToAnyPublisher() } + env.bgScheduler = bgScheduler.eraseToAnyScheduler() + env.mainScheduler = mainScheduler.eraseToAnyScheduler() + + let store = TestStore( + initialState: MyIdentityState(id: UUID()), + reducer: myIdentityReducer, + environment: env + ) + + store.send(.viewDidLoad) + store.receive(.observeMyIdentity) + + bgScheduler.advance() + let identity = Identity.stub() + myIdentitySubject.send(identity) + mainScheduler.advance() + + store.receive(.didUpdateMyIdentity(identity)) { + $0.identity = identity + } + + myIdentitySubject.send(nil) + mainScheduler.advance() + + store.receive(.didUpdateMyIdentity(nil)) { + $0.identity = nil + } + + myIdentitySubject.send(completion: .finished) + mainScheduler.advance() + } +} + +private extension Identity { + static func stub() -> Identity { + Identity( + id: "\(Int.random(in: 100...999))".data(using: .utf8)!, + rsaPrivatePem: "\(Int.random(in: 100...999))".data(using: .utf8)!, + salt: "\(Int.random(in: 100...999))".data(using: .utf8)!, + dhKeyPrivate: "\(Int.random(in: 100...999))".data(using: .utf8)! + ) } }