diff --git a/Example/example-app/.swiftpm/xcode/xcshareddata/xcschemes/MyContactFeature.xcscheme b/Example/example-app/.swiftpm/xcode/xcshareddata/xcschemes/MyContactFeature.xcscheme deleted file mode 100644 index 60c61fd3b79f6c748a322027d0ca61e8f4e8df28..0000000000000000000000000000000000000000 --- a/Example/example-app/.swiftpm/xcode/xcshareddata/xcschemes/MyContactFeature.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 = "MyContactFeature" - BuildableName = "MyContactFeature" - BlueprintName = "MyContactFeature" - 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 = "MyContactFeatureTests" - BuildableName = "MyContactFeatureTests" - BlueprintName = "MyContactFeatureTests" - 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 = "MyContactFeature" - BuildableName = "MyContactFeature" - BlueprintName = "MyContactFeature" - ReferencedContainer = "container:"> - </BuildableReference> - </MacroExpansion> - </ProfileAction> - <AnalyzeAction - buildConfiguration = "Debug"> - </AnalyzeAction> - <ArchiveAction - buildConfiguration = "Release" - revealArchiveInOrganizer = "YES"> - </ArchiveAction> -</Scheme> diff --git a/Example/example-app/.swiftpm/xcode/xcshareddata/xcschemes/MyIdentityFeature.xcscheme b/Example/example-app/.swiftpm/xcode/xcshareddata/xcschemes/MyIdentityFeature.xcscheme deleted file mode 100644 index e69b1d44aa7ba292ef94bb8db602faa9111304f6..0000000000000000000000000000000000000000 --- a/Example/example-app/.swiftpm/xcode/xcshareddata/xcschemes/MyIdentityFeature.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 = "MyIdentityFeature" - BuildableName = "MyIdentityFeature" - BlueprintName = "MyIdentityFeature" - 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 = "MyIdentityFeatureTests" - BuildableName = "MyIdentityFeatureTests" - BlueprintName = "MyIdentityFeatureTests" - 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 = "MyIdentityFeature" - BuildableName = "MyIdentityFeature" - BlueprintName = "MyIdentityFeature" - ReferencedContainer = "container:"> - </BuildableReference> - </MacroExpansion> - </ProfileAction> - <AnalyzeAction - buildConfiguration = "Debug"> - </AnalyzeAction> - <ArchiveAction - buildConfiguration = "Release" - revealArchiveInOrganizer = "YES"> - </ArchiveAction> -</Scheme> diff --git a/Example/example-app/Package.swift b/Example/example-app/Package.swift index 6aa4531627631a16dff789a4acbef611711dd361..0c4051afd5c8d97996be45242276539f93b4c88c 100644 --- a/Example/example-app/Package.swift +++ b/Example/example-app/Package.swift @@ -1,226 +1,180 @@ // swift-tools-version: 5.6 - import PackageDescription -let swiftSettings: [SwiftSetting] = [ - .unsafeFlags( - [ - "-Xfrontend", - "-debug-time-function-bodies", - "-Xfrontend", - "-debug-time-expression-type-checking", - ], - .when(configuration: .debug) - ), -] +// MARK: - Helpers -let package = Package( - name: "example-app", - platforms: [ - .iOS(.v15), - ], - products: [ - .library( - name: "AppFeature", - targets: ["AppFeature"] - ), - .library( - name: "ErrorFeature", - targets: ["ErrorFeature"] - ), - .library( - name: "LandingFeature", - targets: ["LandingFeature"] - ), - .library( - name: "MyContactFeature", - targets: ["MyContactFeature"] - ), - .library( - name: "MyIdentityFeature", - targets: ["MyIdentityFeature"] - ), - .library( - name: "SessionFeature", - targets: ["SessionFeature"] - ), - ], - dependencies: [ - .package(path: "../../"), // elixxir-dapps-sdk-swift - .package( - url: "https://github.com/pointfreeco/swift-composable-architecture.git", - .upToNextMajor(from: "0.35.0") - ), - .package( - url: "https://github.com/darrarski/swift-composable-presentation.git", - .upToNextMajor(from: "0.5.2") - ), - .package( - url: "https://github.com/kishikawakatsumi/KeychainAccess.git", - .upToNextMajor(from: "4.2.2") - ), - ], - targets: [ - .target( - name: "AppFeature", - dependencies: [ - .target(name: "ErrorFeature"), - .target(name: "LandingFeature"), - .target(name: "MyContactFeature"), - .target(name: "MyIdentityFeature"), - .target(name: "SessionFeature"), - .product( - name: "ElixxirDAppsSDK", - package: "elixxir-dapps-sdk-swift" - ), - .product( - name: "ComposableArchitecture", - package: "swift-composable-architecture" - ), - .product( - name: "ComposablePresentation", - package: "swift-composable-presentation" - ), - .product( - name: "KeychainAccess", - package: "KeychainAccess" - ), - ], - swiftSettings: swiftSettings - ), - .testTarget( - name: "AppFeatureTests", - dependencies: [ - .target(name: "AppFeature"), - ], - swiftSettings: swiftSettings - ), - .target( - name: "ErrorFeature", - dependencies: [ - .product( - name: "ComposableArchitecture", - package: "swift-composable-architecture" - ), - .product( - name: "ElixxirDAppsSDK", - package: "elixxir-dapps-sdk-swift" - ), - ], - swiftSettings: swiftSettings - ), - .testTarget( - name: "ErrorFeatureTests", - dependencies: [ - .target(name: "ErrorFeature"), - ], - swiftSettings: swiftSettings - ), - .target( - name: "LandingFeature", - dependencies: [ - .target(name: "ErrorFeature"), - .product( - name: "ComposableArchitecture", - package: "swift-composable-architecture" - ), - .product( - name: "ComposablePresentation", - package: "swift-composable-presentation" - ), - .product( - name: "ElixxirDAppsSDK", - package: "elixxir-dapps-sdk-swift" - ), - ], - swiftSettings: swiftSettings - ), - .testTarget( - name: "LandingFeatureTests", - dependencies: [ - .target(name: "LandingFeature"), - ], - swiftSettings: swiftSettings - ), - .target( - name: "MyContactFeature", - dependencies: [ - .target(name: "ErrorFeature"), - .product( - name: "ComposableArchitecture", - package: "swift-composable-architecture" - ), - .product( - name: "ComposablePresentation", - package: "swift-composable-presentation" - ), - .product( - name: "ElixxirDAppsSDK", - package: "elixxir-dapps-sdk-swift" - ), - ], - swiftSettings: swiftSettings - ), - .testTarget( - name: "MyContactFeatureTests", - dependencies: [ - .target(name: "MyContactFeature"), - ], - swiftSettings: swiftSettings - ), - .target( - name: "MyIdentityFeature", - dependencies: [ - .target(name: "ErrorFeature"), - .product( - name: "ComposableArchitecture", - package: "swift-composable-architecture" - ), - .product( - name: "ComposablePresentation", - package: "swift-composable-presentation" - ), - .product( - name: "ElixxirDAppsSDK", - package: "elixxir-dapps-sdk-swift" - ), - ], - swiftSettings: swiftSettings - ), - .testTarget( - name: "MyIdentityFeatureTests", - dependencies: [ - .target(name: "MyIdentityFeature"), - ], - swiftSettings: swiftSettings - ), - .target( - name: "SessionFeature", - dependencies: [ - .target(name: "ErrorFeature"), - .target(name: "MyContactFeature"), - .target(name: "MyIdentityFeature"), - .product( - name: "ComposableArchitecture", - package: "swift-composable-architecture" - ), - .product( - name: "ComposablePresentation", - package: "swift-composable-presentation" - ), - .product( - name: "ElixxirDAppsSDK", - package: "elixxir-dapps-sdk-swift" +struct Feature { + var product: Product + var targets: [Target] + var targetDependency: Target.Dependency + + static func library( + name: String, + dependencies: [Target.Dependency] = [], + testDependencies: [Target.Dependency] = [], + swiftSettings: [SwiftSetting] = [ + .unsafeFlags( + [ + "-Xfrontend", + "-debug-time-function-bodies", + "-Xfrontend", + "-debug-time-expression-type-checking", + ], + .when(configuration: .debug) + ), + ] + ) -> Feature { + .init( + product: .library(name: name, targets: [name]), + targets: [ + .target( + name: name, + dependencies: dependencies, + swiftSettings: swiftSettings + ), + .testTarget( + name: "\(name)Tests", + dependencies: [.target(name: name)] + testDependencies, + swiftSettings: swiftSettings ), ], - swiftSettings: swiftSettings - ), - .testTarget( - name: "SessionFeatureTests", - dependencies: [ - .target(name: "SessionFeature"), - ], - swiftSettings: swiftSettings - ), + targetDependency: .target(name: name) + ) + } +} + +struct Dependency { + var packageDependency: Package.Dependency + var targetDependency: Target.Dependency + + static func local( + path: String, + name: String, + package: String + ) -> Dependency { + .init( + packageDependency: .package(path: path), + targetDependency: .product(name: name, package: package) + ) + } + + static func external( + url: String, + version: Range<Version>, + name: String, + package: String + ) -> Dependency { + .init( + packageDependency: .package(url: url, version), + targetDependency: .product(name: name, package: package) + ) + } +} + +// MARK: - Manifest + +extension Dependency { + static let all: [Dependency] = [ + .composableArchitecture, + .composablePresentation, + .elixxirDAppsSDK, + .keychainAccess, + .xcTestDynamicOverlay, ] + + static let composableArchitecture = Dependency.external( + url: "https://github.com/pointfreeco/swift-composable-architecture.git", + version: .upToNextMajor(from: "0.38.3"), + name: "ComposableArchitecture", + package: "swift-composable-architecture" + ) + + static let composablePresentation = Dependency.external( + url: "https://github.com/darrarski/swift-composable-presentation.git", + version: .upToNextMajor(from: "0.5.2"), + name: "ComposablePresentation", + package: "swift-composable-presentation" + ) + + static let elixxirDAppsSDK = Dependency.local( + path: "../../", + name: "ElixxirDAppsSDK", + package: "elixxir-dapps-sdk-swift" + ) + + static let keychainAccess = Dependency.external( + url: "https://github.com/kishikawakatsumi/KeychainAccess.git", + version: .upToNextMajor(from: "4.2.2"), + name: "KeychainAccess", + package: "KeychainAccess" + ) + + static let xcTestDynamicOverlay = Dependency.external( + url: "https://github.com/pointfreeco/xctest-dynamic-overlay.git", + version: .upToNextMajor(from: "0.3.3"), + name: "XCTestDynamicOverlay", + package: "xctest-dynamic-overlay" + ) +} + +extension Feature { + static let all: [Feature] = [ + .app, + .error, + .landing, + .session, + ] + + static let app = Feature.library( + name: "AppFeature", + dependencies: [ + Feature.error.targetDependency, + Feature.landing.targetDependency, + Feature.session.targetDependency, + Dependency.composableArchitecture.targetDependency, + Dependency.composablePresentation.targetDependency, + Dependency.elixxirDAppsSDK.targetDependency, + Dependency.keychainAccess.targetDependency, + Dependency.xcTestDynamicOverlay.targetDependency, + ] + ) + + static let error = Feature.library( + name: "ErrorFeature", + dependencies: [ + Dependency.composableArchitecture.targetDependency, + Dependency.elixxirDAppsSDK.targetDependency, + Dependency.xcTestDynamicOverlay.targetDependency, + ] + ) + + static let landing = Feature.library( + name: "LandingFeature", + dependencies: [ + Feature.error.targetDependency, + Dependency.composableArchitecture.targetDependency, + Dependency.composablePresentation.targetDependency, + Dependency.elixxirDAppsSDK.targetDependency, + Dependency.xcTestDynamicOverlay.targetDependency, + ] + ) + + static let session = Feature.library( + name: "SessionFeature", + dependencies: [ + Feature.error.targetDependency, + Dependency.composableArchitecture.targetDependency, + Dependency.composablePresentation.targetDependency, + Dependency.elixxirDAppsSDK.targetDependency, + Dependency.xcTestDynamicOverlay.targetDependency, + ] + ) +} + +let package = Package( + name: "example-app", + platforms: [.iOS(.v15)], + products: Feature.all.map(\.product), + dependencies: Dependency.all.map(\.packageDependency), + targets: Feature.all.flatMap(\.targets) ) diff --git a/Example/example-app/Sources/AppFeature/App.swift b/Example/example-app/Sources/AppFeature/App.swift index 65f4728edaf4f763712e3e9ef425e923135ba2ef..2ad3db435adb6e6ee674b09f5d8878cb4d8bea6e 100644 --- a/Example/example-app/Sources/AppFeature/App.swift +++ b/Example/example-app/Sources/AppFeature/App.swift @@ -3,8 +3,6 @@ import ComposableArchitecture import ElixxirDAppsSDK import ErrorFeature import LandingFeature -import MyContactFeature -import MyIdentityFeature import SessionFeature import SwiftUI @@ -23,9 +21,7 @@ struct App: SwiftUI.App { extension AppEnvironment { static func live() -> AppEnvironment { - let clientSubject = CurrentValueSubject<Client?, Never>(nil) - let identitySubject = CurrentValueSubject<Identity?, Never>(nil) - let contactSubject = CurrentValueSubject<Data?, Never>(nil) + let cmixSubject = CurrentValueSubject<Cmix?, Never>(nil) let mainScheduler = DispatchQueue.main.eraseToAnyScheduler() let bgScheduler = DispatchQueue( label: "xx.network.dApps.ExampleApp.bg", @@ -34,40 +30,22 @@ extension AppEnvironment { return AppEnvironment( makeId: UUID.init, - hasClient: clientSubject.map { $0 != nil }.eraseToAnyPublisher(), + hasCmix: { cmixSubject.map { $0 != nil }.eraseToAnyPublisher() }, mainScheduler: mainScheduler, landing: LandingEnvironment( - clientStorage: .live( + cmixManager: .live( passwordStorage: .keychain ), - setClient: { clientSubject.send($0) }, + setCmix: { cmixSubject.value = $0 }, bgScheduler: bgScheduler, mainScheduler: mainScheduler, error: ErrorEnvironment() ), session: SessionEnvironment( - getClient: { clientSubject.value }, + getCmix: { cmixSubject.value }, bgScheduler: bgScheduler, mainScheduler: mainScheduler, - makeId: UUID.init, - error: ErrorEnvironment(), - myIdentity: MyIdentityEnvironment( - getClient: { clientSubject.value }, - observeIdentity: { identitySubject.eraseToAnyPublisher() }, - updateIdentity: { identitySubject.value = $0 }, - bgScheduler: bgScheduler, - mainScheduler: mainScheduler, - error: ErrorEnvironment() - ), - myContact: MyContactEnvironment( - getClient: { clientSubject.value }, - getIdentity: { identitySubject.value }, - observeContact: { contactSubject.eraseToAnyPublisher() }, - updateContact: { contactSubject.value = $0 }, - bgScheduler: bgScheduler, - mainScheduler: mainScheduler, - error: ErrorEnvironment() - ) + error: ErrorEnvironment() ) ) } diff --git a/Example/example-app/Sources/AppFeature/AppFeature.swift b/Example/example-app/Sources/AppFeature/AppFeature.swift index 347c13b151a70c09e0188a4e3cd5f4ac690d4c08..f9ce028759020d50a8853ebc55aff84530e0a710 100644 --- a/Example/example-app/Sources/AppFeature/AppFeature.swift +++ b/Example/example-app/Sources/AppFeature/AppFeature.swift @@ -3,6 +3,7 @@ import ComposableArchitecture import ComposablePresentation import LandingFeature import SessionFeature +import XCTestDynamicOverlay struct AppState: Equatable { enum Scene: Equatable { @@ -40,14 +41,14 @@ extension AppState.Scene { enum AppAction: Equatable { case viewDidLoad - case clientDidChange(hasClient: Bool) + case cmixDidChange(hasCmix: Bool) case landing(LandingAction) case session(SessionAction) } struct AppEnvironment { var makeId: () -> UUID - var hasClient: AnyPublisher<Bool, Never> + var hasCmix: () -> AnyPublisher<Bool, Never> var mainScheduler: AnySchedulerOf<DispatchQueue> var landing: LandingEnvironment var session: SessionEnvironment @@ -55,19 +56,18 @@ struct AppEnvironment { let appReducer = Reducer<AppState, AppAction, AppEnvironment> { state, action, env in + enum HasCmixEffectId {} + switch action { case .viewDidLoad: - struct HasClientEffectId: Hashable { - var id: UUID - } - return env.hasClient + return env.hasCmix() .removeDuplicates() - .map(AppAction.clientDidChange(hasClient:)) + .map(AppAction.cmixDidChange(hasCmix:)) .receive(on: env.mainScheduler) .eraseToEffect() - .cancellable(id: HasClientEffectId(id: state.id), cancelInFlight: true) + .cancellable(id: HasCmixEffectId.self, cancelInFlight: true) - case .clientDidChange(let hasClient): + case .cmixDidChange(let hasClient): if hasClient { let sessionState = state.scene.asSession ?? SessionState(id: env.makeId()) state.scene = .session(sessionState) @@ -96,14 +96,12 @@ let appReducer = Reducer<AppState, AppAction, AppEnvironment> environment: \.session ) -#if DEBUG extension AppEnvironment { - static let failing = AppEnvironment( - makeId: { fatalError() }, - hasClient: Empty().eraseToAnyPublisher(), - mainScheduler: .failing, - landing: .failing, - session: .failing + static let unimplemented = AppEnvironment( + makeId: XCTUnimplemented("\(Self.self).makeId"), + hasCmix: XCTUnimplemented("\(Self.self).hasCmix"), + mainScheduler: .unimplemented, + landing: .unimplemented, + session: .unimplemented ) } -#endif diff --git a/Example/example-app/Sources/ErrorFeature/ErrorFeature.swift b/Example/example-app/Sources/ErrorFeature/ErrorFeature.swift index dc7d8b69db96f23837fd62a352dbe8d4f0f2e2f0..eb1671e8424a7db0ee5ccb12c8fef9289d1277da 100644 --- a/Example/example-app/Sources/ErrorFeature/ErrorFeature.swift +++ b/Example/example-app/Sources/ErrorFeature/ErrorFeature.swift @@ -1,5 +1,5 @@ import ComposableArchitecture -import SwiftUI +import XCTestDynamicOverlay public struct ErrorState: Equatable { public init(error: NSError) { @@ -17,8 +17,6 @@ public struct ErrorEnvironment { public let errorReducer = Reducer<ErrorState, ErrorAction, ErrorEnvironment>.empty -#if DEBUG extension ErrorEnvironment { - public static let failing = ErrorEnvironment() + public static let unimplemented = ErrorEnvironment() } -#endif diff --git a/Example/example-app/Sources/LandingFeature/LandingFeature.swift b/Example/example-app/Sources/LandingFeature/LandingFeature.swift index 511b1107b0afed85ddcbf2a9a03c3b99841a055b..f8fa2d9db3a439f112d2b47edd39914d587f1b37 100644 --- a/Example/example-app/Sources/LandingFeature/LandingFeature.swift +++ b/Example/example-app/Sources/LandingFeature/LandingFeature.swift @@ -2,58 +2,59 @@ import Combine import ComposableArchitecture import ElixxirDAppsSDK import ErrorFeature +import XCTestDynamicOverlay public struct LandingState: Equatable { public init( id: UUID, - hasStoredClient: Bool = false, - isMakingClient: Bool = false, - isRemovingClient: Bool = false, + hasStoredCmix: Bool = false, + isMakingCmix: Bool = false, + isRemovingCmix: Bool = false, error: ErrorState? = nil ) { self.id = id - self.hasStoredClient = hasStoredClient - self.isMakingClient = isMakingClient - self.isRemovingClient = isRemovingClient + self.hasStoredCmix = hasStoredCmix + self.isMakingCmix = isMakingCmix + self.isRemovingCmix = isRemovingCmix self.error = error } var id: UUID - var hasStoredClient: Bool - var isMakingClient: Bool - var isRemovingClient: Bool + var hasStoredCmix: Bool + var isMakingCmix: Bool + var isRemovingCmix: Bool var error: ErrorState? } public enum LandingAction: Equatable { case viewDidLoad - case makeClient - case didMakeClient - case didFailMakingClient(NSError) - case removeStoredClient - case didRemoveStoredClient - case didFailRemovingStoredClient(NSError) + case makeCmix + case didMakeCmix + case didFailMakingCmix(NSError) + case removeStoredCmix + case didRemoveStoredCmix + case didFailRemovingStoredCmix(NSError) case didDismissError case error(ErrorAction) } public struct LandingEnvironment { public init( - clientStorage: ClientStorage, - setClient: @escaping (Client) -> Void, + cmixManager: CmixManager, + setCmix: @escaping (Cmix) -> Void, bgScheduler: AnySchedulerOf<DispatchQueue>, mainScheduler: AnySchedulerOf<DispatchQueue>, error: ErrorEnvironment ) { - self.clientStorage = clientStorage - self.setClient = setClient + self.cmixManager = cmixManager + self.setCmix = setCmix self.bgScheduler = bgScheduler self.mainScheduler = mainScheduler self.error = error } - public var clientStorage: ClientStorage - public var setClient: (Client) -> Void + public var cmixManager: CmixManager + public var setCmix: (Cmix) -> Void public var bgScheduler: AnySchedulerOf<DispatchQueue> public var mainScheduler: AnySchedulerOf<DispatchQueue> public var error: ErrorEnvironment @@ -63,60 +64,60 @@ public let landingReducer = Reducer<LandingState, LandingAction, LandingEnvironm { state, action, env in switch action { case .viewDidLoad: - state.hasStoredClient = env.clientStorage.hasStoredClient() + state.hasStoredCmix = env.cmixManager.hasStorage() return .none - case .makeClient: - state.isMakingClient = true + case .makeCmix: + state.isMakingCmix = true return Effect.future { fulfill in do { - if env.clientStorage.hasStoredClient() { - env.setClient(try env.clientStorage.loadClient()) + if env.cmixManager.hasStorage() { + env.setCmix(try env.cmixManager.load()) } else { - env.setClient(try env.clientStorage.createClient()) + env.setCmix(try env.cmixManager.create()) } - fulfill(.success(.didMakeClient)) + fulfill(.success(.didMakeCmix)) } catch { - fulfill(.success(.didFailMakingClient(error as NSError))) + fulfill(.success(.didFailMakingCmix(error as NSError))) } } .subscribe(on: env.bgScheduler) .receive(on: env.mainScheduler) .eraseToEffect() - case .didMakeClient: - state.isMakingClient = false - state.hasStoredClient = env.clientStorage.hasStoredClient() + case .didMakeCmix: + state.isMakingCmix = false + state.hasStoredCmix = env.cmixManager.hasStorage() return .none - case .didFailMakingClient(let error): - state.isMakingClient = false - state.hasStoredClient = env.clientStorage.hasStoredClient() + case .didFailMakingCmix(let error): + state.isMakingCmix = false + state.hasStoredCmix = env.cmixManager.hasStorage() state.error = ErrorState(error: error) return .none - case .removeStoredClient: - state.isRemovingClient = true + case .removeStoredCmix: + state.isRemovingCmix = true return Effect.future { fulfill in do { - try env.clientStorage.removeClient() - fulfill(.success(.didRemoveStoredClient)) + try env.cmixManager.remove() + fulfill(.success(.didRemoveStoredCmix)) } catch { - fulfill(.success(.didFailRemovingStoredClient(error as NSError))) + fulfill(.success(.didFailRemovingStoredCmix(error as NSError))) } } .subscribe(on: env.bgScheduler) .receive(on: env.mainScheduler) .eraseToEffect() - case .didRemoveStoredClient: - state.isRemovingClient = false - state.hasStoredClient = env.clientStorage.hasStoredClient() + case .didRemoveStoredCmix: + state.isRemovingCmix = false + state.hasStoredCmix = env.cmixManager.hasStorage() return .none - case .didFailRemovingStoredClient(let error): - state.isRemovingClient = false - state.hasStoredClient = env.clientStorage.hasStoredClient() + case .didFailRemovingStoredCmix(let error): + state.isRemovingCmix = false + state.hasStoredCmix = env.cmixManager.hasStorage() state.error = ErrorState(error: error) return .none @@ -133,14 +134,12 @@ public let landingReducer = Reducer<LandingState, LandingAction, LandingEnvironm environment: \.error ) -#if DEBUG extension LandingEnvironment { - public static let failing = LandingEnvironment( - clientStorage: .failing, - setClient: { _ in fatalError() }, - bgScheduler: .failing, - mainScheduler: .failing, - error: .failing + public static let unimplemented = LandingEnvironment( + cmixManager: .unimplemented, + setCmix: XCTUnimplemented("\(Self.self).setCmix"), + bgScheduler: .unimplemented, + mainScheduler: .unimplemented, + error: .unimplemented ) } -#endif diff --git a/Example/example-app/Sources/LandingFeature/LandingView.swift b/Example/example-app/Sources/LandingFeature/LandingView.swift index 4a45669a3538af966d54876187964d4697a59ee5..f52aeebb80bc75f503060b642ffaf97b9c843f27 100644 --- a/Example/example-app/Sources/LandingFeature/LandingView.swift +++ b/Example/example-app/Sources/LandingFeature/LandingView.swift @@ -11,19 +11,19 @@ public struct LandingView: View { let store: Store<LandingState, LandingAction> struct ViewState: Equatable { - let hasStoredClient: Bool - let isMakingClient: Bool - let isRemovingClient: Bool + let hasStoredCmix: Bool + let isMakingCmix: Bool + let isRemovingCmix: Bool init(state: LandingState) { - hasStoredClient = state.hasStoredClient - isMakingClient = state.isMakingClient - isRemovingClient = state.isRemovingClient + hasStoredCmix = state.hasStoredCmix + isMakingCmix = state.isMakingCmix + isRemovingCmix = state.isRemovingCmix } var isLoading: Bool { - isMakingClient || - isRemovingClient + isMakingCmix || + isRemovingCmix } } @@ -31,25 +31,25 @@ public struct LandingView: View { WithViewStore(store.scope(state: ViewState.init)) { viewStore in Form { Button { - viewStore.send(.makeClient) + viewStore.send(.makeCmix) } label: { HStack { - Text(viewStore.hasStoredClient ? "Load stored client" : "Create new client") + Text(viewStore.hasStoredCmix ? "Load stored cMix" : "Create new cMix") Spacer() - if viewStore.isMakingClient { + if viewStore.isMakingCmix { ProgressView() } } } - if viewStore.hasStoredClient { + if viewStore.hasStoredCmix { Button(role: .destructive) { - viewStore.send(.removeStoredClient) + viewStore.send(.removeStoredCmix) } label: { HStack { - Text("Remove stored client") + Text("Remove stored cMix") Spacer() - if viewStore.isRemovingClient { + if viewStore.isRemovingCmix { ProgressView() } } diff --git a/Example/example-app/Sources/MyContactFeature/MyContactFeature.swift b/Example/example-app/Sources/MyContactFeature/MyContactFeature.swift deleted file mode 100644 index 317b646f1f7b5897f1cd4b4829f41cd8e67fd259..0000000000000000000000000000000000000000 --- a/Example/example-app/Sources/MyContactFeature/MyContactFeature.swift +++ /dev/null @@ -1,138 +0,0 @@ -import Combine -import ComposableArchitecture -import ComposablePresentation -import ElixxirDAppsSDK -import ErrorFeature - -public struct MyContactState: Equatable { - public init( - id: UUID, - contact: Data? = nil, - isMakingContact: Bool = false, - error: ErrorState? = nil - ) { - self.id = id - self.contact = contact - self.isMakingContact = isMakingContact - self.error = error - } - - public var id: UUID - public var contact: Data? - public var isMakingContact: Bool - public var error: ErrorState? -} - -public enum MyContactAction: Equatable { - case viewDidLoad - case observeMyContact - case didUpdateMyContact(Data?) - case makeContact - case didFinishMakingContact(NSError?) - case didDismissError - case error(ErrorAction) -} - -public struct MyContactEnvironment { - public init( - getClient: @escaping () -> Client?, - getIdentity: @escaping () -> Identity?, - observeContact: @escaping () -> AnyPublisher<Data?, Never>, - updateContact: @escaping (Data?) -> Void, - bgScheduler: AnySchedulerOf<DispatchQueue>, - mainScheduler: AnySchedulerOf<DispatchQueue>, - error: ErrorEnvironment - ) { - self.getClient = getClient - self.getIdentity = getIdentity - self.observeContact = observeContact - self.updateContact = updateContact - self.bgScheduler = bgScheduler - self.mainScheduler = mainScheduler - self.error = error - } - - public var getClient: () -> Client? - public var getIdentity: () -> Identity? - public var observeContact: () -> AnyPublisher<Data?, Never> - public var updateContact: (Data?) -> Void - public var bgScheduler: AnySchedulerOf<DispatchQueue> - public var mainScheduler: AnySchedulerOf<DispatchQueue> - public var error: ErrorEnvironment -} - -public let myContactReducer = Reducer<MyContactState, MyContactAction, MyContactEnvironment> -{ state, action, env in - switch action { - case .viewDidLoad: - return .merge([ - .init(value: .observeMyContact), - ]) - - case .observeMyContact: - struct EffectId: Hashable { - let id: UUID - } - return env.observeContact() - .removeDuplicates() - .map(MyContactAction.didUpdateMyContact) - .subscribe(on: env.bgScheduler) - .receive(on: env.mainScheduler) - .eraseToEffect() - .cancellable(id: EffectId(id: state.id), cancelInFlight: true) - - case .didUpdateMyContact(let contact): - state.contact = contact - return .none - - case .makeContact: - state.isMakingContact = true - return Effect.future { fulfill in - guard let identity = env.getIdentity() else { - fulfill(.success(.didFinishMakingContact(NoIdentityError() as NSError))) - return - } - do { - env.updateContact(try env.getClient()?.makeContactFromIdentity(identity: identity)) - fulfill(.success(.didFinishMakingContact(nil))) - } catch { - fulfill(.success(.didFinishMakingContact(error as NSError))) - } - } - .subscribe(on: env.bgScheduler) - .receive(on: env.mainScheduler) - .eraseToEffect() - - case .didFinishMakingContact(let error): - state.isMakingContact = false - if let error = error { - state.error = ErrorState(error: error) - } - return .none - - case .didDismissError: - state.error = nil - return .none - - case .error(_): - return .none - } -} - -public struct NoIdentityError: Error, LocalizedError { - public init() {} -} - -#if DEBUG -extension MyContactEnvironment { - public static let failing = MyContactEnvironment( - getClient: { fatalError() }, - getIdentity: { fatalError() }, - observeContact: { fatalError() }, - updateContact: { _ in fatalError() }, - bgScheduler: .failing, - mainScheduler: .failing, - error: .failing - ) -} -#endif diff --git a/Example/example-app/Sources/MyContactFeature/MyContactView.swift b/Example/example-app/Sources/MyContactFeature/MyContactView.swift deleted file mode 100644 index 88f9d4b8fa84272d7ef3695fed506410d4010aa1..0000000000000000000000000000000000000000 --- a/Example/example-app/Sources/MyContactFeature/MyContactView.swift +++ /dev/null @@ -1,90 +0,0 @@ -import ComposableArchitecture -import ComposablePresentation -import ElixxirDAppsSDK -import ErrorFeature -import SwiftUI - -public struct MyContactView: View { - public init(store: Store<MyContactState, MyContactAction>) { - self.store = store - } - - let store: Store<MyContactState, MyContactAction> - - struct ViewState: Equatable { - let contact: Data? - let isMakingContact: Bool - - init(state: MyContactState) { - contact = state.contact - isMakingContact = state.isMakingContact - } - - var isLoading: Bool { - isMakingContact - } - } - - public var body: some View { - WithViewStore(store.scope(state: ViewState.init)) { viewStore in - Form { - Section { - Text(string(for: viewStore.contact)) - .textSelection(.enabled) - } - - Section { - Button { - viewStore.send(.makeContact) - } label: { - HStack { - Text("Make contact from identity") - Spacer() - if viewStore.isMakingContact { - ProgressView() - } - } - } - } - .disabled(viewStore.isLoading) - } - .navigationTitle("My contact") - .navigationBarBackButtonHidden(viewStore.isLoading) - .task { - viewStore.send(.viewDidLoad) - } - .sheet( - store.scope( - state: \.error, - action: MyContactAction.error - ), - onDismiss: { - viewStore.send(.didDismissError) - }, - content: ErrorView.init(store:) - ) - } - } - - func string(for contact: Data?) -> String { - guard let contact = contact else { - return "No contact" - } - return String(data: contact, encoding: .utf8) ?? "Decoding error" - } -} - -#if DEBUG -public struct MyContactView_Previews: PreviewProvider { - public static var previews: some View { - NavigationView { - MyContactView(store: .init( - initialState: .init(id: UUID()), - reducer: .empty, - environment: () - )) - } - .navigationViewStyle(.stack) - } -} -#endif diff --git a/Example/example-app/Sources/MyIdentityFeature/MyIdentityFeature.swift b/Example/example-app/Sources/MyIdentityFeature/MyIdentityFeature.swift deleted file mode 100644 index df4559d5cb56cb3f47c0ad79c57bbe5c0296a3bb..0000000000000000000000000000000000000000 --- a/Example/example-app/Sources/MyIdentityFeature/MyIdentityFeature.swift +++ /dev/null @@ -1,129 +0,0 @@ -import Combine -import ComposableArchitecture -import ComposablePresentation -import ElixxirDAppsSDK -import ErrorFeature - -public struct MyIdentityState: Equatable { - public init( - id: UUID, - identity: Identity? = nil, - isMakingIdentity: Bool = false, - error: ErrorState? = nil - ) { - self.id = id - self.isMakingIdentity = isMakingIdentity - self.error = error - } - - public var id: UUID - public var identity: Identity? - public var isMakingIdentity: Bool - public var error: ErrorState? -} - -public enum MyIdentityAction: Equatable { - case viewDidLoad - case observeMyIdentity - case didUpdateMyIdentity(Identity?) - case makeIdentity - case didFinishMakingIdentity(NSError?) - case didDismissError - case error(ErrorAction) -} - -public struct MyIdentityEnvironment { - public init( - getClient: @escaping () -> Client?, - observeIdentity: @escaping () -> AnyPublisher<Identity?, Never>, - updateIdentity: @escaping (Identity?) -> Void, - bgScheduler: AnySchedulerOf<DispatchQueue>, - mainScheduler: AnySchedulerOf<DispatchQueue>, - error: ErrorEnvironment - ) { - self.getClient = getClient - self.observeIdentity = observeIdentity - self.updateIdentity = updateIdentity - self.bgScheduler = bgScheduler - self.mainScheduler = mainScheduler - self.error = error - } - - public var getClient: () -> Client? - public var observeIdentity: () -> AnyPublisher<Identity?, Never> - public var updateIdentity: (Identity?) -> Void - public var bgScheduler: AnySchedulerOf<DispatchQueue> - public var mainScheduler: AnySchedulerOf<DispatchQueue> - public var error: ErrorEnvironment -} - -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 - - case .makeIdentity: - state.isMakingIdentity = true - return Effect.future { fulfill in - do { - env.updateIdentity(try env.getClient()?.makeIdentity()) - fulfill(.success(.didFinishMakingIdentity(nil))) - } catch { - fulfill(.success(.didFinishMakingIdentity(error as NSError))) - } - } - .subscribe(on: env.bgScheduler) - .receive(on: env.mainScheduler) - .eraseToEffect() - - case .didDismissError: - state.error = nil - return .none - - case .didFinishMakingIdentity(let error): - state.isMakingIdentity = false - if let error = error { - state.error = ErrorState(error: error) - } - return .none - } -} -.presenting( - errorReducer, - state: .keyPath(\.error), - id: .keyPath(\.?.error), - action: /MyIdentityAction.error, - environment: \.error -) - -#if DEBUG -extension MyIdentityEnvironment { - public static let failing = MyIdentityEnvironment( - getClient: { fatalError() }, - observeIdentity: { fatalError() }, - updateIdentity: { _ in fatalError() }, - bgScheduler: .failing, - mainScheduler: .failing, - error: .failing - ) -} -#endif diff --git a/Example/example-app/Sources/MyIdentityFeature/MyIdentityView.swift b/Example/example-app/Sources/MyIdentityFeature/MyIdentityView.swift deleted file mode 100644 index 61e09c1355755a7f1955e91ad7054d78f3689f5a..0000000000000000000000000000000000000000 --- a/Example/example-app/Sources/MyIdentityFeature/MyIdentityView.swift +++ /dev/null @@ -1,97 +0,0 @@ -import ComposableArchitecture -import ComposablePresentation -import ElixxirDAppsSDK -import ErrorFeature -import SwiftUI - -public struct MyIdentityView: View { - public init(store: Store<MyIdentityState, MyIdentityAction>) { - self.store = store - } - - let store: Store<MyIdentityState, MyIdentityAction> - - struct ViewState: Equatable { - let identity: Identity? - let isMakingIdentity: Bool - - init(state: MyIdentityState) { - identity = state.identity - isMakingIdentity = state.isMakingIdentity - } - - var isLoading: Bool { - isMakingIdentity - } - } - - public var body: some View { - WithViewStore(store.scope(state: ViewState.init)) { viewStore in - Form { - Section { - Text(string(for: viewStore.identity)) - .textSelection(.enabled) - } - - Section { - Button { - viewStore.send(.makeIdentity) - } label: { - HStack { - Text("Make new identity") - Spacer() - if viewStore.isMakingIdentity { - ProgressView() - } - } - } - } - .disabled(viewStore.isLoading) - } - .navigationTitle("My identity") - .navigationBarBackButtonHidden(viewStore.isLoading) - .task { - viewStore.send(.viewDidLoad) - } - .sheet( - store.scope( - state: \.error, - action: MyIdentityAction.error - ), - onDismiss: { - viewStore.send(.didDismissError) - }, - content: ErrorView.init(store:) - ) - } - } - - func string(for identity: Identity?) -> String { - guard let identity = identity else { - return "No identity" - } - let encoder = JSONEncoder() - encoder.outputFormatting = .prettyPrinted - do { - let data = try encoder.encode(identity) - return String(data: data, encoding: .utf8) ?? "Decoding error" - } catch { - return "Decoding error: \(error)" - } - } -} - -#if DEBUG -public struct MyIdentityView_Previews: PreviewProvider { - public static var previews: some View { - NavigationView { - MyIdentityView(store: .init( - initialState: .init(id: UUID()), - reducer: .empty, - environment: () - )) - } - .navigationViewStyle(.stack) - } -} -#endif diff --git a/Example/example-app/Sources/SessionFeature/NetworkFollowerStatusView.swift b/Example/example-app/Sources/SessionFeature/NetworkFollowerStatusView.swift index 5d1ba1dee39fba281819f0865cfaecd73bae6038..dbb06c4f6646859cbfe4e1a5b05343d68e10f2a6 100644 --- a/Example/example-app/Sources/SessionFeature/NetworkFollowerStatusView.swift +++ b/Example/example-app/Sources/SessionFeature/NetworkFollowerStatusView.swift @@ -9,9 +9,6 @@ struct NetworkFollowerStatusView: View { case .stopped: Label("Stopped", systemImage: "stop.fill") - case .starting: - Label("Starting...", systemImage: "play") - case .running: Label("Running", systemImage: "play.fill") @@ -32,7 +29,6 @@ struct NetworkFollowerStatusView_Previews: PreviewProvider { static var previews: some View { Group { NetworkFollowerStatusView(status: .stopped) - NetworkFollowerStatusView(status: .starting) NetworkFollowerStatusView(status: .running) NetworkFollowerStatusView(status: .stopping) NetworkFollowerStatusView(status: .unknown(code: -1)) diff --git a/Example/example-app/Sources/SessionFeature/SessionFeature.swift b/Example/example-app/Sources/SessionFeature/SessionFeature.swift index 5b1005a9a632e62978cc8a66294566e2f1ba4069..a5bd6f9cb07099ea08483bed50941b371566f59f 100644 --- a/Example/example-app/Sources/SessionFeature/SessionFeature.swift +++ b/Example/example-app/Sources/SessionFeature/SessionFeature.swift @@ -2,32 +2,25 @@ import Combine import ComposableArchitecture import ElixxirDAppsSDK import ErrorFeature -import MyContactFeature -import MyIdentityFeature +import XCTestDynamicOverlay public struct SessionState: Equatable { public init( id: UUID, networkFollowerStatus: NetworkFollowerStatus? = nil, isNetworkHealthy: Bool? = nil, - error: ErrorState? = nil, - myIdentity: MyIdentityState? = nil, - myContact: MyContactState? = nil + error: ErrorState? = nil ) { self.id = id self.networkFollowerStatus = networkFollowerStatus self.isNetworkHealthy = isNetworkHealthy self.error = error - self.myIdentity = myIdentity - self.myContact = myContact } public var id: UUID public var networkFollowerStatus: NetworkFollowerStatus? public var isNetworkHealthy: Bool? public var error: ErrorState? - public var myIdentity: MyIdentityState? - public var myContact: MyContactState? } public enum SessionAction: Equatable { @@ -39,41 +32,26 @@ public enum SessionAction: Equatable { case monitorNetworkHealth(Bool) case didUpdateNetworkHealth(Bool?) case didDismissError - case presentMyIdentity - case didDismissMyIdentity - case presentMyContact - case didDismissMyContact case error(ErrorAction) - case myIdentity(MyIdentityAction) - case myContact(MyContactAction) } public struct SessionEnvironment { public init( - getClient: @escaping () -> Client?, + getCmix: @escaping () -> Cmix?, bgScheduler: AnySchedulerOf<DispatchQueue>, mainScheduler: AnySchedulerOf<DispatchQueue>, - makeId: @escaping () -> UUID, - error: ErrorEnvironment, - myIdentity: MyIdentityEnvironment, - myContact: MyContactEnvironment + error: ErrorEnvironment ) { - self.getClient = getClient + self.getCmix = getCmix self.bgScheduler = bgScheduler self.mainScheduler = mainScheduler - self.makeId = makeId self.error = error - self.myIdentity = myIdentity - self.myContact = myContact } - public var getClient: () -> Client? + public var getCmix: () -> Cmix? public var bgScheduler: AnySchedulerOf<DispatchQueue> public var mainScheduler: AnySchedulerOf<DispatchQueue> - public var makeId: () -> UUID public var error: ErrorEnvironment - public var myIdentity: MyIdentityEnvironment - public var myContact: MyContactEnvironment } public let sessionReducer = Reducer<SessionState, SessionAction, SessionEnvironment> @@ -87,7 +65,7 @@ public let sessionReducer = Reducer<SessionState, SessionAction, SessionEnvironm case .updateNetworkFollowerStatus: return Effect.future { fulfill in - let status = env.getClient()?.networkFollower.status() + let status = env.getCmix()?.networkFollowerStatus() fulfill(.success(.didUpdateNetworkFollowerStatus(status))) } .subscribe(on: env.bgScheduler) @@ -99,18 +77,17 @@ public let sessionReducer = Reducer<SessionState, SessionAction, SessionEnvironm return .none case .runNetworkFollower(let start): - state.networkFollowerStatus = start ? .starting : .stopping return Effect.run { subscriber in do { if start { - try env.getClient()?.networkFollower.start(timeoutMS: 30_000) + try env.getCmix()?.startNetworkFollower(timeoutMS: 30_000) } else { - try env.getClient()?.networkFollower.stop() + try env.getCmix()?.stopNetworkFollower() } } catch { subscriber.send(.networkFollowerDidFail(error as NSError)) } - let status = env.getClient()?.networkFollower.status() + let status = env.getCmix()?.networkFollowerStatus() subscriber.send(.didUpdateNetworkFollowerStatus(status)) subscriber.send(completion: .finished) return AnyCancellable {} @@ -130,9 +107,10 @@ public let sessionReducer = Reducer<SessionState, SessionAction, SessionEnvironm let effectId = MonitorEffectId(id: state.id) if start { return Effect.run { subscriber in - var cancellable = env.getClient()?.monitorNetworkHealth { isHealthy in + let callback = HealthCallback { isHealthy in subscriber.send(.didUpdateNetworkHealth(isHealthy)) } + let cancellable = env.getCmix()?.addHealthCallback(callback) return AnyCancellable { cancellable?.cancel() } @@ -155,27 +133,7 @@ public let sessionReducer = Reducer<SessionState, SessionAction, SessionEnvironm state.error = nil return .none - case .presentMyIdentity: - if state.myIdentity == nil { - state.myIdentity = MyIdentityState(id: env.makeId()) - } - return .none - - case .didDismissMyIdentity: - state.myIdentity = nil - return .none - - case .presentMyContact: - if state.myContact == nil { - state.myContact = MyContactState(id: env.makeId()) - } - return .none - - case .didDismissMyContact: - state.myContact = nil - return .none - - case .error(_), .myIdentity(_), .myContact(_): + case .error(_): return .none } } @@ -186,31 +144,12 @@ public let sessionReducer = Reducer<SessionState, SessionAction, SessionEnvironm action: /SessionAction.error, environment: \.error ) -.presenting( - myIdentityReducer, - state: .keyPath(\.myIdentity), - id: .keyPath(\.?.id), - action: /SessionAction.myIdentity, - environment: \.myIdentity -) -.presenting( - myContactReducer, - state: .keyPath(\.myContact), - id: .keyPath(\.?.id), - action: /SessionAction.myContact, - environment: \.myContact -) -#if DEBUG extension SessionEnvironment { - public static let failing = SessionEnvironment( - getClient: { .failing }, - bgScheduler: .failing, - mainScheduler: .failing, - makeId: { fatalError() }, - error: .failing, - myIdentity: .failing, - myContact: .failing + public static let unimplemented = SessionEnvironment( + getCmix: XCTUnimplemented("\(Self.self).getCmix"), + bgScheduler: .unimplemented, + mainScheduler: .unimplemented, + error: .unimplemented ) } -#endif diff --git a/Example/example-app/Sources/SessionFeature/SessionView.swift b/Example/example-app/Sources/SessionFeature/SessionView.swift index ea14d91ff94358572a9462647044186b3c47ab6a..395cfcb160067fedc31ec3ddd5200aa6960864e2 100644 --- a/Example/example-app/Sources/SessionFeature/SessionView.swift +++ b/Example/example-app/Sources/SessionFeature/SessionView.swift @@ -2,8 +2,6 @@ import ComposableArchitecture import ComposablePresentation import ElixxirDAppsSDK import ErrorFeature -import MyContactFeature -import MyIdentityFeature import SwiftUI public struct SessionView: View { @@ -51,28 +49,6 @@ public struct SessionView: View { } header: { Text("Network health") } - - Section { - Button { - viewStore.send(.presentMyIdentity) - } label: { - HStack { - Text("My identity") - Spacer() - Image(systemName: "chevron.forward") - } - } - - Button { - viewStore.send(.presentMyContact) - } label: { - HStack { - Text("My contact") - Spacer() - Image(systemName: "chevron.forward") - } - } - } } .navigationTitle("Session") .task { @@ -88,30 +64,6 @@ public struct SessionView: View { }, content: ErrorView.init(store:) ) - .background( - NavigationLinkWithStore( - store.scope( - state: \.myIdentity, - action: SessionAction.myIdentity - ), - onDeactivate: { - viewStore.send(.didDismissMyIdentity) - }, - destination: MyIdentityView.init(store:) - ) - ) - .background( - NavigationLinkWithStore( - store.scope( - state: \.myContact, - action: SessionAction.myContact - ), - onDeactivate: { - viewStore.send(.didDismissMyContact) - }, - destination: MyContactView.init(store:) - ) - ) } } } diff --git a/Example/example-app/Tests/AppFeatureTests/AppFeatureTests.swift b/Example/example-app/Tests/AppFeatureTests/AppFeatureTests.swift index ce5891ed1d1e147abdca7390420e2888e82321a5..24abccd89477d96029c36a768a735d5f3bc59828 100644 --- a/Example/example-app/Tests/AppFeatureTests/AppFeatureTests.swift +++ b/Example/example-app/Tests/AppFeatureTests/AppFeatureTests.swift @@ -8,45 +8,44 @@ import XCTest final class AppFeatureTests: XCTestCase { func testViewDidLoad() throws { let newId = UUID() - let hasClient = PassthroughSubject<Bool, Never>() + let hasCmix = PassthroughSubject<Bool, Never>() let mainScheduler = DispatchQueue.test - var env = AppEnvironment.failing - env.makeId = { newId } - env.hasClient = hasClient.eraseToAnyPublisher() - env.mainScheduler = mainScheduler.eraseToAnyScheduler() - let store = TestStore( initialState: AppState(), reducer: appReducer, - environment: env + environment: .unimplemented ) + store.environment.makeId = { newId } + store.environment.hasCmix = { hasCmix.eraseToAnyPublisher() } + store.environment.mainScheduler = mainScheduler.eraseToAnyScheduler() + store.send(.viewDidLoad) - hasClient.send(false) + hasCmix.send(false) mainScheduler.advance() - store.receive(.clientDidChange(hasClient: false)) + store.receive(.cmixDidChange(hasCmix: false)) - hasClient.send(true) + hasCmix.send(true) mainScheduler.advance() - store.receive(.clientDidChange(hasClient: true)) { + store.receive(.cmixDidChange(hasCmix: true)) { $0.scene = .session(SessionState(id: newId)) } - hasClient.send(true) + hasCmix.send(true) mainScheduler.advance() - hasClient.send(false) + hasCmix.send(false) mainScheduler.advance() - store.receive(.clientDidChange(hasClient: false)) { + store.receive(.cmixDidChange(hasCmix: false)) { $0.scene = .landing(LandingState(id: newId)) } - hasClient.send(completion: .finished) + hasCmix.send(completion: .finished) mainScheduler.advance() } } diff --git a/Example/example-app/Tests/LandingFeatureTests/LandingFeatureTests.swift b/Example/example-app/Tests/LandingFeatureTests/LandingFeatureTests.swift index c5b56f988cd96cf0f8a8c9e03ab2afad9ef885cc..cb52fb7b3b50c0e1cf39d8e6af5e509c1988686b 100644 --- a/Example/example-app/Tests/LandingFeatureTests/LandingFeatureTests.swift +++ b/Example/example-app/Tests/LandingFeatureTests/LandingFeatureTests.swift @@ -5,183 +5,177 @@ import XCTest final class LandingFeatureTests: XCTestCase { func testViewDidLoad() throws { - var env = LandingEnvironment.failing - env.clientStorage.hasStoredClient = { true } - let store = TestStore( initialState: LandingState(id: UUID()), reducer: landingReducer, - environment: env + environment: .unimplemented ) + store.environment.cmixManager.hasStorage.run = { true } + store.send(.viewDidLoad) { - $0.hasStoredClient = true + $0.hasStoredCmix = true } } - func testCreateClient() { - var hasStoredClient = false - var didSetClient = false + func testCreateCmix() { + var hasStoredCmix = false + var didSetCmix = false let bgScheduler = DispatchQueue.test let mainScheduler = DispatchQueue.test - var env = LandingEnvironment.failing - env.clientStorage.hasStoredClient = { hasStoredClient } - env.clientStorage.createClient = { .failing } - env.setClient = { _ in didSetClient = true } - env.bgScheduler = bgScheduler.eraseToAnyScheduler() - env.mainScheduler = mainScheduler.eraseToAnyScheduler() - let store = TestStore( initialState: LandingState(id: UUID()), reducer: landingReducer, - environment: env + environment: .unimplemented ) - store.send(.makeClient) { - $0.isMakingClient = true + store.environment.cmixManager.hasStorage.run = { hasStoredCmix } + store.environment.cmixManager.create.run = { .unimplemented } + store.environment.setCmix = { _ in didSetCmix = true } + store.environment.bgScheduler = bgScheduler.eraseToAnyScheduler() + store.environment.mainScheduler = mainScheduler.eraseToAnyScheduler() + + store.send(.makeCmix) { + $0.isMakingCmix = true } bgScheduler.advance() - XCTAssertTrue(didSetClient) + XCTAssertTrue(didSetCmix) - hasStoredClient = true + hasStoredCmix = true mainScheduler.advance() - store.receive(.didMakeClient) { - $0.isMakingClient = false - $0.hasStoredClient = true + store.receive(.didMakeCmix) { + $0.isMakingCmix = false + $0.hasStoredCmix = true } } - func testLoadStoredClient() { - var didSetClient = false + func testLoadStoredCmix() { + var didSetCmix = false let bgScheduler = DispatchQueue.test let mainScheduler = DispatchQueue.test - var env = LandingEnvironment.failing - env.clientStorage.hasStoredClient = { true } - env.clientStorage.loadClient = { .failing } - env.setClient = { _ in didSetClient = true } - env.bgScheduler = bgScheduler.eraseToAnyScheduler() - env.mainScheduler = mainScheduler.eraseToAnyScheduler() - let store = TestStore( initialState: LandingState(id: UUID()), reducer: landingReducer, - environment: env + environment: .unimplemented ) - store.send(.makeClient) { - $0.isMakingClient = true + store.environment.cmixManager.hasStorage.run = { true } + store.environment.cmixManager.load.run = { .unimplemented } + store.environment.setCmix = { _ in didSetCmix = true } + store.environment.bgScheduler = bgScheduler.eraseToAnyScheduler() + store.environment.mainScheduler = mainScheduler.eraseToAnyScheduler() + + store.send(.makeCmix) { + $0.isMakingCmix = true } bgScheduler.advance() - XCTAssertTrue(didSetClient) + XCTAssertTrue(didSetCmix) mainScheduler.advance() - store.receive(.didMakeClient) { - $0.isMakingClient = false - $0.hasStoredClient = true + store.receive(.didMakeCmix) { + $0.isMakingCmix = false + $0.hasStoredCmix = true } } - func testMakeClientFailure() { + func testMakeCmixFailure() { let error = NSError(domain: "test", code: 1234) let bgScheduler = DispatchQueue.test let mainScheduler = DispatchQueue.test - var env = LandingEnvironment.failing - env.clientStorage.hasStoredClient = { false } - env.clientStorage.createClient = { throw error } - env.bgScheduler = bgScheduler.eraseToAnyScheduler() - env.mainScheduler = mainScheduler.eraseToAnyScheduler() - let store = TestStore( initialState: LandingState(id: UUID()), reducer: landingReducer, - environment: env + environment: .unimplemented ) - store.send(.makeClient) { - $0.isMakingClient = true + store.environment.cmixManager.hasStorage.run = { false } + store.environment.cmixManager.create.run = { throw error } + store.environment.bgScheduler = bgScheduler.eraseToAnyScheduler() + store.environment.mainScheduler = mainScheduler.eraseToAnyScheduler() + + store.send(.makeCmix) { + $0.isMakingCmix = true } bgScheduler.advance() mainScheduler.advance() - store.receive(.didFailMakingClient(error)) { - $0.isMakingClient = false - $0.hasStoredClient = false + store.receive(.didFailMakingCmix(error)) { + $0.isMakingCmix = false + $0.hasStoredCmix = false $0.error = ErrorState(error: error) } } - func testRemoveStoredClient() { - var hasStoredClient = true - var didRemoveClient = false + func testRemoveStoredCmix() { + var hasStoredCmix = true + var didRemoveCmix = false let bgScheduler = DispatchQueue.test let mainScheduler = DispatchQueue.test - var env = LandingEnvironment.failing - env.clientStorage.hasStoredClient = { hasStoredClient } - env.clientStorage.removeClient = { didRemoveClient = true } - env.bgScheduler = bgScheduler.eraseToAnyScheduler() - env.mainScheduler = mainScheduler.eraseToAnyScheduler() - let store = TestStore( initialState: LandingState(id: UUID()), reducer: landingReducer, - environment: env + environment: .unimplemented ) - store.send(.removeStoredClient) { - $0.isRemovingClient = true + store.environment.cmixManager.hasStorage.run = { hasStoredCmix } + store.environment.cmixManager.remove.run = { didRemoveCmix = true } + store.environment.bgScheduler = bgScheduler.eraseToAnyScheduler() + store.environment.mainScheduler = mainScheduler.eraseToAnyScheduler() + + store.send(.removeStoredCmix) { + $0.isRemovingCmix = true } bgScheduler.advance() - XCTAssertTrue(didRemoveClient) + XCTAssertTrue(didRemoveCmix) - hasStoredClient = false + hasStoredCmix = false mainScheduler.advance() - store.receive(.didRemoveStoredClient) { - $0.isRemovingClient = false - $0.hasStoredClient = false + store.receive(.didRemoveStoredCmix) { + $0.isRemovingCmix = false + $0.hasStoredCmix = false } } - func testRemoveStoredClientFailure() { + func testRemoveStoredCmixFailure() { let error = NSError(domain: "test", code: 1234) let bgScheduler = DispatchQueue.test let mainScheduler = DispatchQueue.test - var env = LandingEnvironment.failing - env.clientStorage.hasStoredClient = { true } - env.clientStorage.removeClient = { throw error } - env.bgScheduler = bgScheduler.eraseToAnyScheduler() - env.mainScheduler = mainScheduler.eraseToAnyScheduler() - let store = TestStore( initialState: LandingState(id: UUID()), reducer: landingReducer, - environment: env + environment: .unimplemented ) - store.send(.removeStoredClient) { - $0.isRemovingClient = true + store.environment.cmixManager.hasStorage.run = { true } + store.environment.cmixManager.remove.run = { throw error } + store.environment.bgScheduler = bgScheduler.eraseToAnyScheduler() + store.environment.mainScheduler = mainScheduler.eraseToAnyScheduler() + + store.send(.removeStoredCmix) { + $0.isRemovingCmix = true } bgScheduler.advance() mainScheduler.advance() - store.receive(.didFailRemovingStoredClient(error)) { - $0.isRemovingClient = false - $0.hasStoredClient = true + store.receive(.didFailRemovingStoredCmix(error)) { + $0.isRemovingCmix = false + $0.hasStoredCmix = true $0.error = ErrorState(error: error) } } diff --git a/Example/example-app/Tests/MyContactFeatureTests/MyContactFeatureTests.swift b/Example/example-app/Tests/MyContactFeatureTests/MyContactFeatureTests.swift deleted file mode 100644 index 862be08da221c884a14875b3ceb86010788073f4..0000000000000000000000000000000000000000 --- a/Example/example-app/Tests/MyContactFeatureTests/MyContactFeatureTests.swift +++ /dev/null @@ -1,174 +0,0 @@ -import Combine -import ComposableArchitecture -import CustomDump -import ElixxirDAppsSDK -import ErrorFeature -import XCTest -@testable import MyContactFeature - -final class MyContactFeatureTests: XCTestCase { - func testViewDidLoad() { - let myContactSubject = PassthroughSubject<Data?, Never>() - let bgScheduler = DispatchQueue.test - let mainScheduler = DispatchQueue.test - - var env = MyContactEnvironment.failing - env.observeContact = { myContactSubject.eraseToAnyPublisher() } - env.bgScheduler = bgScheduler.eraseToAnyScheduler() - env.mainScheduler = mainScheduler.eraseToAnyScheduler() - - let store = TestStore( - initialState: MyContactState(id: UUID()), - reducer: myContactReducer, - environment: env - ) - - store.send(.viewDidLoad) - store.receive(.observeMyContact) - - bgScheduler.advance() - let contact = "\(Int.random(in: 100...999))".data(using: .utf8)! - myContactSubject.send(contact) - mainScheduler.advance() - - store.receive(.didUpdateMyContact(contact)) { - $0.contact = contact - } - - myContactSubject.send(nil) - mainScheduler.advance() - - store.receive(.didUpdateMyContact(nil)) { - $0.contact = nil - } - - myContactSubject.send(completion: .finished) - mainScheduler.advance() - } - - func testMakeContact() { - let identity = Identity.stub() - let newContact = "\(Int.random(in: 100...999))".data(using: .utf8)! - var didMakeContactFromIdentity = [Identity]() - var didUpdateContact = [Data?]() - let bgScheduler = DispatchQueue.test - let mainScheduler = DispatchQueue.test - - var env = MyContactEnvironment.failing - env.getClient = { - var client = Client.failing - client.makeContactFromIdentity.get = { identity in - didMakeContactFromIdentity.append(identity) - return newContact - } - return client - } - env.updateContact = { didUpdateContact.append($0) } - env.getIdentity = { identity } - env.bgScheduler = bgScheduler.eraseToAnyScheduler() - env.mainScheduler = mainScheduler.eraseToAnyScheduler() - - let store = TestStore( - initialState: MyContactState(id: UUID()), - reducer: myContactReducer, - environment: env - ) - - store.send(.makeContact) { - $0.isMakingContact = true - } - - bgScheduler.advance() - - XCTAssertNoDifference(didMakeContactFromIdentity, [identity]) - XCTAssertNoDifference(didUpdateContact, [newContact]) - - mainScheduler.advance() - - store.receive(.didFinishMakingContact(nil)) { - $0.isMakingContact = false - } - } - - func testMakeContactWithoutIdentity() { - let error = NoIdentityError() as NSError - let bgScheduler = DispatchQueue.test - let mainScheduler = DispatchQueue.test - - var env = MyContactEnvironment.failing - env.getIdentity = { nil } - env.bgScheduler = bgScheduler.eraseToAnyScheduler() - env.mainScheduler = mainScheduler.eraseToAnyScheduler() - - let store = TestStore( - initialState: MyContactState(id: UUID()), - reducer: myContactReducer, - environment: env - ) - - store.send(.makeContact) { - $0.isMakingContact = true - } - - bgScheduler.advance() - mainScheduler.advance() - - store.receive(.didFinishMakingContact(error)) { - $0.isMakingContact = false - $0.error = ErrorState(error: error) - } - - store.send(.didDismissError) { - $0.error = nil - } - } - - func testMakeContactFailure() { - let error = NSError(domain: "test", code: 1234) - let bgScheduler = DispatchQueue.test - let mainScheduler = DispatchQueue.test - - var env = MyContactEnvironment.failing - env.getClient = { - var client = Client.failing - client.makeContactFromIdentity.get = { _ in throw error } - return client - } - env.getIdentity = { .stub() } - env.bgScheduler = bgScheduler.eraseToAnyScheduler() - env.mainScheduler = mainScheduler.eraseToAnyScheduler() - - let store = TestStore( - initialState: MyContactState(id: UUID()), - reducer: myContactReducer, - environment: env - ) - - store.send(.makeContact) { - $0.isMakingContact = true - } - - bgScheduler.advance() - mainScheduler.advance() - - store.receive(.didFinishMakingContact(error)) { - $0.isMakingContact = false - $0.error = ErrorState(error: error) - } - - store.send(.didDismissError) { - $0.error = nil - } - } -} - -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)! - ) - } -} diff --git a/Example/example-app/Tests/MyIdentityFeatureTests/MyIdentityFeatureTests.swift b/Example/example-app/Tests/MyIdentityFeatureTests/MyIdentityFeatureTests.swift deleted file mode 100644 index b426cc0fbd26c49bc14b462db938b1eb27afb712..0000000000000000000000000000000000000000 --- a/Example/example-app/Tests/MyIdentityFeatureTests/MyIdentityFeatureTests.swift +++ /dev/null @@ -1,133 +0,0 @@ -import Combine -import ComposableArchitecture -import CustomDump -import ElixxirDAppsSDK -import ErrorFeature -import XCTest -@testable import MyIdentityFeature - -final class MyIdentityFeatureTests: XCTestCase { - 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() - } - - func testMakeIdentity() { - let newIdentity = Identity.stub() - var didUpdateIdentity = [Identity?]() - let bgScheduler = DispatchQueue.test - let mainScheduler = DispatchQueue.test - - var env = MyIdentityEnvironment.failing - env.getClient = { - var client = Client.failing - client.makeIdentity.make = { newIdentity } - return client - } - env.updateIdentity = { didUpdateIdentity.append($0) } - env.bgScheduler = bgScheduler.eraseToAnyScheduler() - env.mainScheduler = mainScheduler.eraseToAnyScheduler() - - let store = TestStore( - initialState: MyIdentityState(id: UUID()), - reducer: myIdentityReducer, - environment: env - ) - - store.send(.makeIdentity) { - $0.isMakingIdentity = true - } - - bgScheduler.advance() - - XCTAssertNoDifference(didUpdateIdentity, [newIdentity]) - - mainScheduler.advance() - - store.receive(.didFinishMakingIdentity(nil)) { - $0.isMakingIdentity = false - } - } - - func testMakeIdentityFailure() { - let error = NSError(domain: "test", code: 1234) - let bgScheduler = DispatchQueue.test - let mainScheduler = DispatchQueue.test - - var env = MyIdentityEnvironment.failing - env.getClient = { - var client = Client.failing - client.makeIdentity.make = { throw error } - return client - } - env.bgScheduler = bgScheduler.eraseToAnyScheduler() - env.mainScheduler = mainScheduler.eraseToAnyScheduler() - - let store = TestStore( - initialState: MyIdentityState(id: UUID()), - reducer: myIdentityReducer, - environment: env - ) - - store.send(.makeIdentity) { - $0.isMakingIdentity = true - } - - bgScheduler.advance() - mainScheduler.advance() - - store.receive(.didFinishMakingIdentity(error)) { - $0.isMakingIdentity = false - $0.error = ErrorState(error: error) - } - - store.send(.didDismissError) { - $0.error = nil - } - } -} - -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)! - ) - } -} diff --git a/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift b/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift index 5c1aa3babd922cf042c0df855604dceeae7c36f7..2b3250c6b0b2db94c409cdf60804da306b12b4b4 100644 --- a/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift +++ b/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift @@ -1,8 +1,6 @@ import ComposableArchitecture import ElixxirDAppsSDK import ErrorFeature -import MyContactFeature -import MyIdentityFeature import XCTest @testable import SessionFeature @@ -11,31 +9,30 @@ final class SessionFeatureTests: XCTestCase { var networkFollowerStatus: NetworkFollowerStatus! var didStartMonitoringNetworkHealth = 0 var didStopMonitoringNetworkHealth = 0 - var networkHealthCallback: ((Bool) -> Void)! + var networkHealthCallback: HealthCallback! let bgScheduler = DispatchQueue.test let mainScheduler = DispatchQueue.test - var env = SessionEnvironment.failing - env.getClient = { - var client = Client.failing - client.networkFollower.status.status = { networkFollowerStatus } - client.monitorNetworkHealth.listen = { callback in + let store = TestStore( + initialState: SessionState(id: UUID()), + reducer: sessionReducer, + environment: .unimplemented + ) + + store.environment.getCmix = { + var cmix = Cmix.unimplemented + cmix.networkFollowerStatus.run = { networkFollowerStatus } + cmix.addHealthCallback.run = { callback in networkHealthCallback = callback didStartMonitoringNetworkHealth += 1 return Cancellable { didStopMonitoringNetworkHealth += 1 } } - return client + return cmix } - env.bgScheduler = bgScheduler.eraseToAnyScheduler() - env.mainScheduler = mainScheduler.eraseToAnyScheduler() - - let store = TestStore( - initialState: SessionState(id: UUID()), - reducer: sessionReducer, - environment: env - ) + store.environment.bgScheduler = bgScheduler.eraseToAnyScheduler() + store.environment.mainScheduler = mainScheduler.eraseToAnyScheduler() store.send(.viewDidLoad) @@ -53,7 +50,7 @@ final class SessionFeatureTests: XCTestCase { XCTAssertEqual(didStartMonitoringNetworkHealth, 1) XCTAssertEqual(didStopMonitoringNetworkHealth, 0) - networkHealthCallback(true) + networkHealthCallback.handle(true) bgScheduler.advance() mainScheduler.advance() @@ -77,35 +74,30 @@ final class SessionFeatureTests: XCTestCase { let bgScheduler = DispatchQueue.test let mainScheduler = DispatchQueue.test - var env = SessionEnvironment.failing - env.getClient = { - var client = Client.failing - client.networkFollower.status.status = { - networkFollowerStatus - } - client.networkFollower.start.start = { + let store = TestStore( + initialState: SessionState(id: UUID()), + reducer: sessionReducer, + environment: .unimplemented + ) + + store.environment.getCmix = { + var cmix = Cmix.unimplemented + cmix.networkFollowerStatus.run = { networkFollowerStatus } + cmix.startNetworkFollower.run = { didStartNetworkFollowerWithTimeout.append($0) if let error = networkFollowerStartError { throw error } } - client.networkFollower.stop.stop = { + cmix.stopNetworkFollower.run = { didStopNetworkFollower += 1 } - return client + return cmix } - env.bgScheduler = bgScheduler.eraseToAnyScheduler() - env.mainScheduler = mainScheduler.eraseToAnyScheduler() + store.environment.bgScheduler = bgScheduler.eraseToAnyScheduler() + store.environment.mainScheduler = mainScheduler.eraseToAnyScheduler() - let store = TestStore( - initialState: SessionState(id: UUID()), - reducer: sessionReducer, - environment: env - ) - - store.send(.runNetworkFollower(true)) { - $0.networkFollowerStatus = .starting - } + store.send(.runNetworkFollower(true)) networkFollowerStatus = .running bgScheduler.advance() @@ -118,9 +110,7 @@ final class SessionFeatureTests: XCTestCase { $0.networkFollowerStatus = .running } - store.send(.runNetworkFollower(false)) { - $0.networkFollowerStatus = .stopping - } + store.send(.runNetworkFollower(false)) networkFollowerStatus = .stopped bgScheduler.advance() @@ -133,9 +123,7 @@ final class SessionFeatureTests: XCTestCase { $0.networkFollowerStatus = .stopped } - store.send(.runNetworkFollower(true)) { - $0.networkFollowerStatus = .starting - } + store.send(.runNetworkFollower(true)) networkFollowerStartError = NSError(domain: "test", code: 1234) networkFollowerStatus = .stopped @@ -149,54 +137,10 @@ final class SessionFeatureTests: XCTestCase { $0.error = ErrorState(error: networkFollowerStartError!) } - store.receive(.didUpdateNetworkFollowerStatus(.stopped)) { - $0.networkFollowerStatus = .stopped - } + store.receive(.didUpdateNetworkFollowerStatus(.stopped)) store.send(.didDismissError) { $0.error = nil } } - - func testPresentingMyIdentity() { - let newId = UUID() - - var env = SessionEnvironment.failing - env.makeId = { newId } - - let store = TestStore( - initialState: SessionState(id: UUID()), - reducer: sessionReducer, - environment: env - ) - - store.send(.presentMyIdentity) { - $0.myIdentity = MyIdentityState(id: newId) - } - - store.send(.didDismissMyIdentity) { - $0.myIdentity = nil - } - } - - func testPresentingMyContact() { - let newId = UUID() - - var env = SessionEnvironment.failing - env.makeId = { newId } - - let store = TestStore( - initialState: SessionState(id: UUID()), - reducer: sessionReducer, - environment: env - ) - - store.send(.presentMyContact) { - $0.myContact = MyContactState(id: newId) - } - - store.send(.didDismissMyContact) { - $0.myContact = nil - } - } }