diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift index 411e8e2dcea75f6f5d6996ce29ae025403a13541..44fec1e27b0a8a9d5a0865013098b3d9bf6d189a 100644 --- a/Examples/xx-messenger/Package.swift +++ b/Examples/xx-messenger/Package.swift @@ -128,6 +128,7 @@ let package = Package( .target( name: "BackupFeature", dependencies: [ + .target(name: "AppCore"), .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"), ], diff --git a/Examples/xx-messenger/Sources/BackupFeature/Alerts.swift b/Examples/xx-messenger/Sources/BackupFeature/Alerts.swift index 2bd95f770ff5ce1be9e808284d1d50f3d111af1d..e261adeba5b042c931817ffa7f0d4a6d849a2671 100644 --- a/Examples/xx-messenger/Sources/BackupFeature/Alerts.swift +++ b/Examples/xx-messenger/Sources/BackupFeature/Alerts.swift @@ -1,8 +1,8 @@ import ComposableArchitecture -extension AlertState where Action == BackupAction { - public static func error(_ error: Error) -> AlertState<BackupAction> { - AlertState( +extension AlertState where Action == BackupComponent.Action { + public static func error(_ error: Error) -> AlertState<BackupComponent.Action> { + AlertState<BackupComponent.Action>( title: TextState("Error"), message: TextState(error.localizedDescription) ) diff --git a/Examples/xx-messenger/Sources/BackupFeature/BackupComponent.swift b/Examples/xx-messenger/Sources/BackupFeature/BackupComponent.swift new file mode 100644 index 0000000000000000000000000000000000000000..af3e3c34a5728a73a084c307b918fab128cce7b8 --- /dev/null +++ b/Examples/xx-messenger/Sources/BackupFeature/BackupComponent.swift @@ -0,0 +1,209 @@ +import AppCore +import Combine +import ComposableArchitecture +import Foundation +import XXClient +import XXMessengerClient + +public struct BackupComponent: ReducerProtocol { + public struct State: Equatable { + public enum Field: String, Hashable { + case passphrase + } + + public enum Error: String, Swift.Error, Equatable { + case contactUsernameMissing + } + + public init( + isRunning: Bool = false, + isStarting: Bool = false, + isResuming: Bool = false, + isStopping: Bool = false, + backup: BackupStorage.Backup? = nil, + alert: AlertState<Action>? = nil, + focusedField: Field? = nil, + passphrase: String = "", + isExporting: Bool = false, + exportData: Data? = nil + ) { + self.isRunning = isRunning + self.isStarting = isStarting + self.isResuming = isResuming + self.isStopping = isStopping + self.backup = backup + self.alert = alert + self.focusedField = focusedField + self.passphrase = passphrase + self.isExporting = isExporting + self.exportData = exportData + } + + public var isRunning: Bool + public var isStarting: Bool + public var isResuming: Bool + public var isStopping: Bool + public var backup: BackupStorage.Backup? + public var alert: AlertState<Action>? + @BindableState public var focusedField: Field? + @BindableState public var passphrase: String + @BindableState public var isExporting: Bool + public var exportData: Data? + } + + public enum Action: Equatable, BindableAction { + case task + case cancelTask + case startTapped + case resumeTapped + case stopTapped + case exportTapped + case alertDismissed + case backupUpdated(BackupStorage.Backup?) + case didStart(failure: NSError?) + case didResume(failure: NSError?) + case didStop(failure: NSError?) + case didExport(failure: NSError?) + case binding(BindingAction<State>) + } + + public init() {} + + @Dependency(\.app.messenger) var messenger: Messenger + @Dependency(\.app.backupStorage) var backupStorage: BackupStorage + @Dependency(\.app.mainQueue) var mainQueue: AnySchedulerOf<DispatchQueue> + @Dependency(\.app.bgQueue) var bgQueue: AnySchedulerOf<DispatchQueue> + + public var body: some ReducerProtocol<State, Action> { + BindingReducer() + Reduce { state, action in + enum TaskEffectId {} + + switch action { + case .task: + state.isRunning = messenger.isBackupRunning() + return Effect.run { subscriber in + subscriber.send(.backupUpdated(backupStorage.stored())) + let cancellable = backupStorage.observe { backup in + subscriber.send(.backupUpdated(backup)) + } + return AnyCancellable { cancellable.cancel() } + } + .subscribe(on: bgQueue) + .receive(on: mainQueue) + .eraseToEffect() + .cancellable(id: TaskEffectId.self, cancelInFlight: true) + + case .cancelTask: + return .cancel(id: TaskEffectId.self) + + case .startTapped: + state.isStarting = true + state.focusedField = nil + return Effect.run { [state] subscriber in + do { + let contact = try messenger.myContact(includeFacts: .types([.username])) + guard let username = try contact.getFact(.username)?.value else { + throw State.Error.contactUsernameMissing + } + try messenger.startBackup( + password: state.passphrase, + params: BackupParams(username: username) + ) + subscriber.send(.didStart(failure: nil)) + } catch { + subscriber.send(.didStart(failure: error as NSError)) + } + subscriber.send(completion: .finished) + return AnyCancellable {} + } + .subscribe(on: bgQueue) + .receive(on: mainQueue) + .eraseToEffect() + + case .resumeTapped: + state.isResuming = true + return Effect.run { subscriber in + do { + try messenger.resumeBackup() + subscriber.send(.didResume(failure: nil)) + } catch { + subscriber.send(.didResume(failure: error as NSError)) + } + subscriber.send(completion: .finished) + return AnyCancellable {} + } + .subscribe(on: bgQueue) + .receive(on: mainQueue) + .eraseToEffect() + + case .stopTapped: + state.isStopping = true + return Effect.run { subscriber in + do { + try messenger.stopBackup() + try backupStorage.remove() + subscriber.send(.didStop(failure: nil)) + } catch { + subscriber.send(.didStop(failure: error as NSError)) + } + subscriber.send(completion: .finished) + return AnyCancellable {} + } + .subscribe(on: bgQueue) + .receive(on: mainQueue) + .eraseToEffect() + + case .exportTapped: + state.isExporting = true + state.exportData = state.backup?.data + return .none + + case .alertDismissed: + state.alert = nil + return .none + + case .backupUpdated(let backup): + state.backup = backup + return .none + + case .didStart(let failure): + state.isRunning = messenger.isBackupRunning() + state.isStarting = false + if let failure { + state.alert = .error(failure) + } else { + state.passphrase = "" + } + return .none + + case .didResume(let failure): + state.isRunning = messenger.isBackupRunning() + state.isResuming = false + if let failure { + state.alert = .error(failure) + } + return .none + + case .didStop(let failure): + state.isRunning = messenger.isBackupRunning() + state.isStopping = false + if let failure { + state.alert = .error(failure) + } + return .none + + case .didExport(let failure): + state.isExporting = false + state.exportData = nil + if let failure { + state.alert = .error(failure) + } + return .none + + case .binding(_): + return .none + } + } + } +} diff --git a/Examples/xx-messenger/Sources/BackupFeature/BackupFeature.swift b/Examples/xx-messenger/Sources/BackupFeature/BackupFeature.swift deleted file mode 100644 index 8010f17d1d0727c4621841dd124a93dcda0e0b5d..0000000000000000000000000000000000000000 --- a/Examples/xx-messenger/Sources/BackupFeature/BackupFeature.swift +++ /dev/null @@ -1,228 +0,0 @@ -import Combine -import ComposableArchitecture -import Foundation -import XXClient -import XXMessengerClient - -public struct BackupState: Equatable { - public enum Field: String, Hashable { - case passphrase - } - - public enum Error: String, Swift.Error, Equatable { - case contactUsernameMissing - } - - public init( - isRunning: Bool = false, - isStarting: Bool = false, - isResuming: Bool = false, - isStopping: Bool = false, - backup: BackupStorage.Backup? = nil, - alert: AlertState<BackupAction>? = nil, - focusedField: Field? = nil, - passphrase: String = "", - isExporting: Bool = false, - exportData: Data? = nil - ) { - self.isRunning = isRunning - self.isStarting = isStarting - self.isResuming = isResuming - self.isStopping = isStopping - self.backup = backup - self.alert = alert - self.focusedField = focusedField - self.passphrase = passphrase - self.isExporting = isExporting - self.exportData = exportData - } - - public var isRunning: Bool - public var isStarting: Bool - public var isResuming: Bool - public var isStopping: Bool - public var backup: BackupStorage.Backup? - public var alert: AlertState<BackupAction>? - @BindableState public var focusedField: Field? - @BindableState public var passphrase: String - @BindableState public var isExporting: Bool - public var exportData: Data? -} - -public enum BackupAction: Equatable, BindableAction { - case task - case cancelTask - case startTapped - case resumeTapped - case stopTapped - case exportTapped - case alertDismissed - case backupUpdated(BackupStorage.Backup?) - case didStart(failure: NSError?) - case didResume(failure: NSError?) - case didStop(failure: NSError?) - case didExport(failure: NSError?) - case binding(BindingAction<BackupState>) -} - -public struct BackupEnvironment { - public init( - messenger: Messenger, - backupStorage: BackupStorage, - mainQueue: AnySchedulerOf<DispatchQueue>, - bgQueue: AnySchedulerOf<DispatchQueue> - ) { - self.messenger = messenger - self.backupStorage = backupStorage - self.mainQueue = mainQueue - self.bgQueue = bgQueue - } - - public var messenger: Messenger - public var backupStorage: BackupStorage - public var mainQueue: AnySchedulerOf<DispatchQueue> - public var bgQueue: AnySchedulerOf<DispatchQueue> -} - -#if DEBUG -extension BackupEnvironment { - public static let unimplemented = BackupEnvironment( - messenger: .unimplemented, - backupStorage: .unimplemented, - mainQueue: .unimplemented, - bgQueue: .unimplemented - ) -} -#endif - -public let backupReducer = Reducer<BackupState, BackupAction, BackupEnvironment> -{ state, action, env in - enum TaskEffectId {} - - switch action { - case .task: - state.isRunning = env.messenger.isBackupRunning() - return Effect.run { subscriber in - subscriber.send(.backupUpdated(env.backupStorage.stored())) - let cancellable = env.backupStorage.observe { backup in - subscriber.send(.backupUpdated(backup)) - } - return AnyCancellable { cancellable.cancel() } - } - .subscribe(on: env.bgQueue) - .receive(on: env.mainQueue) - .eraseToEffect() - .cancellable(id: TaskEffectId.self, cancelInFlight: true) - - case .cancelTask: - return .cancel(id: TaskEffectId.self) - - case .startTapped: - state.isStarting = true - state.focusedField = nil - return Effect.run { [state] subscriber in - do { - let contact = try env.messenger.myContact(includeFacts: .types([.username])) - guard let username = try contact.getFact(.username)?.value else { - throw BackupState.Error.contactUsernameMissing - } - try env.messenger.startBackup( - password: state.passphrase, - params: BackupParams(username: username) - ) - subscriber.send(.didStart(failure: nil)) - } catch { - subscriber.send(.didStart(failure: error as NSError)) - } - subscriber.send(completion: .finished) - return AnyCancellable {} - } - .subscribe(on: env.bgQueue) - .receive(on: env.mainQueue) - .eraseToEffect() - - case .resumeTapped: - state.isResuming = true - return Effect.run { subscriber in - do { - try env.messenger.resumeBackup() - subscriber.send(.didResume(failure: nil)) - } catch { - subscriber.send(.didResume(failure: error as NSError)) - } - subscriber.send(completion: .finished) - return AnyCancellable {} - } - .subscribe(on: env.bgQueue) - .receive(on: env.mainQueue) - .eraseToEffect() - - case .stopTapped: - state.isStopping = true - return Effect.run { subscriber in - do { - try env.messenger.stopBackup() - try env.backupStorage.remove() - subscriber.send(.didStop(failure: nil)) - } catch { - subscriber.send(.didStop(failure: error as NSError)) - } - subscriber.send(completion: .finished) - return AnyCancellable {} - } - .subscribe(on: env.bgQueue) - .receive(on: env.mainQueue) - .eraseToEffect() - - case .exportTapped: - state.isExporting = true - state.exportData = state.backup?.data - return .none - - case .alertDismissed: - state.alert = nil - return .none - - case .backupUpdated(let backup): - state.backup = backup - return .none - - case .didStart(let failure): - state.isRunning = env.messenger.isBackupRunning() - state.isStarting = false - if let failure { - state.alert = .error(failure) - } else { - state.passphrase = "" - } - return .none - - case .didResume(let failure): - state.isRunning = env.messenger.isBackupRunning() - state.isResuming = false - if let failure { - state.alert = .error(failure) - } - return .none - - case .didStop(let failure): - state.isRunning = env.messenger.isBackupRunning() - state.isStopping = false - if let failure { - state.alert = .error(failure) - } - return .none - - case .didExport(let failure): - state.isExporting = false - state.exportData = nil - if let failure { - state.alert = .error(failure) - } - return .none - - case .binding(_): - return .none - } -} -.binding() diff --git a/Examples/xx-messenger/Sources/BackupFeature/BackupView.swift b/Examples/xx-messenger/Sources/BackupFeature/BackupView.swift index 89510b2fbf2c993afa1438fd62408dc925d1c30e..78805e66acd18a8200d461f803243673d509bdac 100644 --- a/Examples/xx-messenger/Sources/BackupFeature/BackupView.swift +++ b/Examples/xx-messenger/Sources/BackupFeature/BackupView.swift @@ -3,12 +3,12 @@ import SwiftUI import UniformTypeIdentifiers public struct BackupView: View { - public init(store: Store<BackupState, BackupAction>) { + public init(store: StoreOf<BackupComponent>) { self.store = store } - let store: Store<BackupState, BackupAction> - @FocusState var focusedField: BackupState.Field? + let store: StoreOf<BackupComponent> + @FocusState var focusedField: BackupComponent.State.Field? struct ViewState: Equatable { struct Backup: Equatable { @@ -16,7 +16,7 @@ public struct BackupView: View { var size: Int } - init(state: BackupState) { + init(state: BackupComponent.State) { isRunning = state.isRunning isStarting = state.isStarting isResuming = state.isResuming @@ -36,7 +36,7 @@ public struct BackupView: View { var isStopping: Bool var isLoading: Bool { isStarting || isResuming || isStopping } var backup: Backup? - var focusedField: BackupState.Field? + var focusedField: BackupComponent.State.Field? var passphrase: String var isExporting: Bool var exportData: Data? @@ -67,7 +67,7 @@ public struct BackupView: View { } @ViewBuilder func newBackupSection( - _ viewStore: ViewStore<ViewState, BackupAction> + _ viewStore: ViewStore<ViewState, BackupComponent.Action> ) -> some View { Section { SecureField( @@ -103,7 +103,7 @@ public struct BackupView: View { } @ViewBuilder func backupSection( - _ viewStore: ViewStore<ViewState, BackupAction> + _ viewStore: ViewStore<ViewState, BackupComponent.Action> ) -> some View { Section { backupView(viewStore) @@ -115,7 +115,7 @@ public struct BackupView: View { } @ViewBuilder func backupView( - _ viewStore: ViewStore<ViewState, BackupAction> + _ viewStore: ViewStore<ViewState, BackupComponent.Action> ) -> some View { if let backup = viewStore.backup { HStack { @@ -165,7 +165,7 @@ public struct BackupView: View { } @ViewBuilder func stopView( - _ viewStore: ViewStore<ViewState, BackupAction> + _ viewStore: ViewStore<ViewState, BackupComponent.Action> ) -> some View { if viewStore.isRunning { Button { @@ -185,7 +185,7 @@ public struct BackupView: View { } @ViewBuilder func resumeView( - _ viewStore: ViewStore<ViewState, BackupAction> + _ viewStore: ViewStore<ViewState, BackupComponent.Action> ) -> some View { if !viewStore.isRunning, viewStore.backup != nil { Button { @@ -240,9 +240,8 @@ public struct BackupView_Previews: PreviewProvider { public static var previews: some View { NavigationView { BackupView(store: Store( - initialState: BackupState(), - reducer: .empty, - environment: () + initialState: BackupComponent.State(), + reducer: EmptyReducer() )) } } diff --git a/Examples/xx-messenger/Tests/BackupFeatureTests/BackupFeatureTests.swift b/Examples/xx-messenger/Tests/BackupFeatureTests/BackupComponentTests.swift similarity index 70% rename from Examples/xx-messenger/Tests/BackupFeatureTests/BackupFeatureTests.swift rename to Examples/xx-messenger/Tests/BackupFeatureTests/BackupComponentTests.swift index d0c69eb87e2efea29f635fce84656b04d2e35cba..e409a1b9241f2a342f77fc3dca23174ca06c1c15 100644 --- a/Examples/xx-messenger/Tests/BackupFeatureTests/BackupFeatureTests.swift +++ b/Examples/xx-messenger/Tests/BackupFeatureTests/BackupComponentTests.swift @@ -14,19 +14,18 @@ final class BackupFeatureTests: XCTestCase { ) let store = TestStore( - initialState: BackupState(), - reducer: backupReducer, - environment: .unimplemented + initialState: BackupComponent.State(), + reducer: BackupComponent() ) - store.environment.mainQueue = .immediate - store.environment.bgQueue = .immediate - store.environment.messenger.isBackupRunning.run = { + store.dependencies.app.mainQueue = .immediate + store.dependencies.app.bgQueue = .immediate + store.dependencies.app.messenger.isBackupRunning.run = { isBackupRunning.removeFirst() } - store.environment.backupStorage.stored = { + store.dependencies.app.backupStorage.stored = { storedBackup } - store.environment.backupStorage.observe = { + store.dependencies.app.backupStorage.observe = { let id = UUID() observers[id] = $0 return Cancellable { observers[id] = nil } @@ -68,22 +67,21 @@ final class BackupFeatureTests: XCTestCase { let passphrase = "backup-password" let store = TestStore( - initialState: BackupState(), - reducer: backupReducer, - environment: .unimplemented + initialState: BackupComponent.State(), + reducer: BackupComponent() ) - store.environment.mainQueue = .immediate - store.environment.bgQueue = .immediate - store.environment.messenger.myContact.run = { includeFacts in + store.dependencies.app.mainQueue = .immediate + store.dependencies.app.bgQueue = .immediate + store.dependencies.app.messenger.myContact.run = { includeFacts in actions.append(.didGetMyContact(includingFacts: includeFacts)) var contact = Contact.unimplemented("contact-data".data(using: .utf8)!) contact.getFactsFromContact.run = { _ in [Fact(type: .username, value: username)] } return contact } - store.environment.messenger.startBackup.run = { passphrase, params in + store.dependencies.app.messenger.startBackup.run = { passphrase, params in actions.append(.didStartBackup(passphrase: passphrase, params: params)) } - store.environment.messenger.isBackupRunning.run = { + store.dependencies.app.messenger.isBackupRunning.run = { isBackupRunning.removeFirst() } @@ -124,20 +122,19 @@ final class BackupFeatureTests: XCTestCase { var isBackupRunning: [Bool] = [false] let store = TestStore( - initialState: BackupState( + initialState: BackupComponent.State( passphrase: "1234" ), - reducer: backupReducer, - environment: .unimplemented + reducer: BackupComponent() ) - store.environment.mainQueue = .immediate - store.environment.bgQueue = .immediate - store.environment.messenger.myContact.run = { _ in + store.dependencies.app.mainQueue = .immediate + store.dependencies.app.bgQueue = .immediate + store.dependencies.app.messenger.myContact.run = { _ in var contact = Contact.unimplemented("contact-data".data(using: .utf8)!) contact.getFactsFromContact.run = { _ in [] } return contact } - store.environment.messenger.isBackupRunning.run = { + store.dependencies.app.messenger.isBackupRunning.run = { isBackupRunning.removeFirst() } @@ -145,7 +142,7 @@ final class BackupFeatureTests: XCTestCase { $0.isStarting = true } - let failure = BackupState.Error.contactUsernameMissing + let failure = BackupComponent.State.Error.contactUsernameMissing store.receive(.didStart(failure: failure as NSError)) { $0.isRunning = false $0.isStarting = false @@ -159,16 +156,15 @@ final class BackupFeatureTests: XCTestCase { var isBackupRunning: [Bool] = [false] let store = TestStore( - initialState: BackupState( + initialState: BackupComponent.State( passphrase: "1234" ), - reducer: backupReducer, - environment: .unimplemented + reducer: BackupComponent() ) - store.environment.mainQueue = .immediate - store.environment.bgQueue = .immediate - store.environment.messenger.myContact.run = { _ in throw failure } - store.environment.messenger.isBackupRunning.run = { + store.dependencies.app.mainQueue = .immediate + store.dependencies.app.bgQueue = .immediate + store.dependencies.app.messenger.myContact.run = { _ in throw failure } + store.dependencies.app.messenger.isBackupRunning.run = { isBackupRunning.removeFirst() } @@ -189,23 +185,22 @@ final class BackupFeatureTests: XCTestCase { var isBackupRunning: [Bool] = [false] let store = TestStore( - initialState: BackupState( + initialState: BackupComponent.State( passphrase: "1234" ), - reducer: backupReducer, - environment: .unimplemented + reducer: BackupComponent() ) - store.environment.mainQueue = .immediate - store.environment.bgQueue = .immediate - store.environment.messenger.myContact.run = { _ in + store.dependencies.app.mainQueue = .immediate + store.dependencies.app.bgQueue = .immediate + store.dependencies.app.messenger.myContact.run = { _ in var contact = Contact.unimplemented("data".data(using: .utf8)!) contact.getFactsFromContact.run = { _ in [Fact(type: .username, value: "username")] } return contact } - store.environment.messenger.startBackup.run = { _, _ in + store.dependencies.app.messenger.startBackup.run = { _, _ in throw failure } - store.environment.messenger.isBackupRunning.run = { + store.dependencies.app.messenger.isBackupRunning.run = { isBackupRunning.removeFirst() } @@ -225,16 +220,15 @@ final class BackupFeatureTests: XCTestCase { var isBackupRunning: [Bool] = [true] let store = TestStore( - initialState: BackupState(), - reducer: backupReducer, - environment: .unimplemented + initialState: BackupComponent.State(), + reducer: BackupComponent() ) - store.environment.mainQueue = .immediate - store.environment.bgQueue = .immediate - store.environment.messenger.resumeBackup.run = { + store.dependencies.app.mainQueue = .immediate + store.dependencies.app.bgQueue = .immediate + store.dependencies.app.messenger.resumeBackup.run = { actions.append(.didResumeBackup) } - store.environment.messenger.isBackupRunning.run = { + store.dependencies.app.messenger.isBackupRunning.run = { isBackupRunning.removeFirst() } @@ -260,16 +254,15 @@ final class BackupFeatureTests: XCTestCase { var isBackupRunning: [Bool] = [false] let store = TestStore( - initialState: BackupState(), - reducer: backupReducer, - environment: .unimplemented + initialState: BackupComponent.State(), + reducer: BackupComponent() ) - store.environment.mainQueue = .immediate - store.environment.bgQueue = .immediate - store.environment.messenger.resumeBackup.run = { + store.dependencies.app.mainQueue = .immediate + store.dependencies.app.bgQueue = .immediate + store.dependencies.app.messenger.resumeBackup.run = { throw failure } - store.environment.messenger.isBackupRunning.run = { + store.dependencies.app.messenger.isBackupRunning.run = { isBackupRunning.removeFirst() } @@ -289,19 +282,18 @@ final class BackupFeatureTests: XCTestCase { var isBackupRunning: [Bool] = [false] let store = TestStore( - initialState: BackupState(), - reducer: backupReducer, - environment: .unimplemented + initialState: BackupComponent.State(), + reducer: BackupComponent() ) - store.environment.mainQueue = .immediate - store.environment.bgQueue = .immediate - store.environment.messenger.stopBackup.run = { + store.dependencies.app.mainQueue = .immediate + store.dependencies.app.bgQueue = .immediate + store.dependencies.app.messenger.stopBackup.run = { actions.append(.didStopBackup) } - store.environment.messenger.isBackupRunning.run = { + store.dependencies.app.messenger.isBackupRunning.run = { isBackupRunning.removeFirst() } - store.environment.backupStorage.remove = { + store.dependencies.app.backupStorage.remove = { actions.append(.didRemoveBackup) } @@ -330,16 +322,15 @@ final class BackupFeatureTests: XCTestCase { var isBackupRunning: [Bool] = [true] let store = TestStore( - initialState: BackupState(), - reducer: backupReducer, - environment: .unimplemented + initialState: BackupComponent.State(), + reducer: BackupComponent() ) - store.environment.mainQueue = .immediate - store.environment.bgQueue = .immediate - store.environment.messenger.stopBackup.run = { + store.dependencies.app.mainQueue = .immediate + store.dependencies.app.bgQueue = .immediate + store.dependencies.app.messenger.stopBackup.run = { throw failure } - store.environment.messenger.isBackupRunning.run = { + store.dependencies.app.messenger.isBackupRunning.run = { isBackupRunning.removeFirst() } @@ -356,11 +347,10 @@ final class BackupFeatureTests: XCTestCase { func testAlertDismissed() { let store = TestStore( - initialState: BackupState( + initialState: BackupComponent.State( alert: .error(NSError(domain: "test", code: 0)) ), - reducer: backupReducer, - environment: .unimplemented + reducer: BackupComponent() ) store.send(.alertDismissed) { @@ -372,14 +362,13 @@ final class BackupFeatureTests: XCTestCase { let backupData = "backup-data".data(using: .utf8)! let store = TestStore( - initialState: BackupState( + initialState: BackupComponent.State( backup: .init( date: Date(), data: backupData ) ), - reducer: backupReducer, - environment: .unimplemented + reducer: BackupComponent() ) store.send(.exportTapped) {