From 5e4eec5a05858f03c93288e44eff2ace27ee9a90 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Mon, 5 Sep 2022 15:04:49 +0200
Subject: [PATCH] Refactor

---
 .../Sources/AppFeature/AppFeature.swift       |  2 +-
 .../Sources/HomeFeature/Alerts.swift          |  2 +-
 .../Sources/HomeFeature/HomeFeature.swift     | 91 +++++++++++++------
 .../Sources/HomeFeature/HomeView.swift        | 10 +-
 .../AppFeatureTests/AppFeatureTests.swift     |  2 +-
 .../HomeFeatureTests/HomeFeatureTests.swift   | 81 +++++++++++------
 6 files changed, 124 insertions(+), 64 deletions(-)

diff --git a/Examples/xx-messenger/Sources/AppFeature/AppFeature.swift b/Examples/xx-messenger/Sources/AppFeature/AppFeature.swift
index 582478d4..43cede69 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppFeature.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppFeature.swift
@@ -68,7 +68,7 @@ extension AppEnvironment {
 let appReducer = Reducer<AppState, AppAction, AppEnvironment>
 { state, action, env in
   switch action {
-  case .start, .welcome(.finished), .restore(.finished), .home(.didDeleteAccount):
+  case .start, .welcome(.finished), .restore(.finished), .home(.deleteAccount(.success)):
     state.screen = .loading
     return .run { subscriber in
       do {
diff --git a/Examples/xx-messenger/Sources/HomeFeature/Alerts.swift b/Examples/xx-messenger/Sources/HomeFeature/Alerts.swift
index a3347be4..3b9b9a3d 100644
--- a/Examples/xx-messenger/Sources/HomeFeature/Alerts.swift
+++ b/Examples/xx-messenger/Sources/HomeFeature/Alerts.swift
@@ -6,7 +6,7 @@ extension AlertState {
       title: TextState("Delete Account"),
       message: TextState("This will permanently delete your account and can't be undone."),
       buttons: [
-        .destructive(TextState("Delete"), action: .send(.deleteAccountConfirmed)),
+        .destructive(TextState("Delete"), action: .send(.deleteAccount(.confirmed))),
         .cancel(TextState("Cancel"))
       ]
     )
diff --git a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
index 279a5628..8116fde0 100644
--- a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
+++ b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
@@ -20,18 +20,31 @@ public struct HomeState: Equatable {
     self.isDeletingAccount = isDeletingAccount
   }
 
-  @BindableState public var failure: String?
-  @BindableState public var register: RegisterState?
-  @BindableState public var alert: AlertState<HomeAction>?
-  @BindableState public var isDeletingAccount: Bool
+  public var failure: String?
+  public var register: RegisterState?
+  public var alert: AlertState<HomeAction>?
+  public var isDeletingAccount: Bool
 }
 
-public enum HomeAction: Equatable, BindableAction {
-  case start
-  case deleteAccountButtonTapped
-  case deleteAccountConfirmed
-  case didDeleteAccount
-  case binding(BindingAction<HomeState>)
+public enum HomeAction: Equatable {
+  public enum Messenger: Equatable {
+    case start
+    case didStartRegistered
+    case didStartUnregistered
+    case failure(NSError)
+  }
+
+  public enum DeleteAccount: Equatable {
+    case buttonTapped
+    case confirmed
+    case success
+    case failure(NSError)
+  }
+
+  case messenger(Messenger)
+  case deleteAccount(DeleteAccount)
+  case didDismissAlert
+  case didDismissRegister
   case register(RegisterAction)
 }
 
@@ -70,8 +83,8 @@ extension HomeEnvironment {
 public let homeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
 { state, action, env in
   switch action {
-  case .start:
-    return .run { subscriber in
+  case .messenger(.start):
+    return .result {
       do {
         try env.messenger.start()
 
@@ -81,29 +94,38 @@ public let homeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
 
         if env.messenger.isLoggedIn() == false {
           if try env.messenger.isRegistered() == false {
-            subscriber.send(.set(\.$register, RegisterState()))
-            subscriber.send(completion: .finished)
-            return AnyCancellable {}
+            return .success(.messenger(.didStartUnregistered))
           }
           try env.messenger.logIn()
         }
+
+        return .success(.messenger(.didStartRegistered))
       } catch {
-        subscriber.send(.set(\.$failure, error.localizedDescription))
+        return .success(.messenger(.failure(error as NSError)))
       }
-      subscriber.send(completion: .finished)
-      return AnyCancellable {}
     }
     .subscribe(on: env.bgQueue)
     .receive(on: env.mainQueue)
     .eraseToEffect()
 
-  case .deleteAccountButtonTapped:
+  case .messenger(.didStartUnregistered):
+    state.register = RegisterState()
+    return .none
+
+  case .messenger(.didStartRegistered):
+    return .none
+
+  case .messenger(.failure(let error)):
+    state.failure = error.localizedDescription
+    return .none
+
+  case .deleteAccount(.buttonTapped):
     state.alert = .confirmAccountDeletion()
     return .none
 
-  case .deleteAccountConfirmed:
+  case .deleteAccount(.confirmed):
     state.isDeletingAccount = true
-    return .run { subscriber in
+    return .result {
       do {
         let contactId = try env.messenger.e2e.tryGet().getContact().getId()
         let contact = try env.db().fetchContacts(.init(id: [contactId])).first
@@ -113,31 +135,40 @@ public let homeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
         }
         try env.messenger.destroy()
         try env.db().drop()
-        subscriber.send(.didDeleteAccount)
+        return .success(.deleteAccount(.success))
       } catch {
-        subscriber.send(.set(\.$isDeletingAccount, false))
-        subscriber.send(.set(\.$alert, .accountDeletionFailed(error)))
+        return .success(.deleteAccount(.failure(error as NSError)))
       }
-      subscriber.send(completion: .finished)
-      return AnyCancellable {}
     }
     .subscribe(on: env.bgQueue)
     .receive(on: env.mainQueue)
     .eraseToEffect()
 
-  case .didDeleteAccount:
+  case .deleteAccount(.success):
     state.isDeletingAccount = false
     return .none
 
+  case .deleteAccount(.failure(let error)):
+    state.isDeletingAccount = false
+    state.alert = .accountDeletionFailed(error)
+    return .none
+
+  case .didDismissAlert:
+    state.alert = nil
+    return .none
+
+  case .didDismissRegister:
+    state.register = nil
+    return .none
+
   case .register(.finished):
     state.register = nil
-    return Effect(value: .start)
+    return Effect(value: .messenger(.start))
 
-  case .binding(_), .register(_):
+  case .register(_):
     return .none
   }
 }
-.binding()
 .presenting(
   registerReducer,
   state: .keyPath(\.register),
diff --git a/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
index f73f6b8a..b2c9e660 100644
--- a/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
+++ b/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
@@ -28,7 +28,7 @@ public struct HomeView: View {
             Section {
               Text(failure)
               Button {
-                viewStore.send(.start)
+                viewStore.send(.messenger(.start))
               } label: {
                 Text("Retry")
               }
@@ -39,7 +39,7 @@ public struct HomeView: View {
 
           Section {
             Button(role: .destructive) {
-              viewStore.send(.deleteAccountButtonTapped)
+              viewStore.send(.deleteAccount(.buttonTapped))
             } label: {
               HStack {
                 Text("Delete Account")
@@ -57,18 +57,18 @@ public struct HomeView: View {
         .navigationTitle("Home")
         .alert(
           store.scope(state: \.alert),
-          dismiss: HomeAction.set(\.$alert, nil)
+          dismiss: HomeAction.didDismissAlert
         )
       }
       .navigationViewStyle(.stack)
-      .task { viewStore.send(.start) }
+      .task { viewStore.send(.messenger(.start)) }
       .fullScreenCover(
         store.scope(
           state: \.register,
           action: HomeAction.register
         ),
         onDismiss: {
-          viewStore.send(.set(\.$register, nil))
+          viewStore.send(.didDismissRegister)
         },
         content: RegisterView.init(store:)
       )
diff --git a/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift b/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift
index 5ea96813..5a013b28 100644
--- a/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift
@@ -159,7 +159,7 @@ final class AppFeatureTests: XCTestCase {
     store.environment.messenger.isLoaded.run = { false }
     store.environment.messenger.isCreated.run = { false }
 
-    store.send(.home(.didDeleteAccount)) {
+    store.send(.home(.deleteAccount(.success))) {
       $0.screen = .loading
     }
 
diff --git a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
index 4fbad5b8..60c7e305 100644
--- a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
@@ -7,7 +7,7 @@ import XXModels
 @testable import HomeFeature
 
 final class HomeFeatureTests: XCTestCase {
-  func testStartUnregistered() {
+  func testMessengerStartUnregistered() {
     let store = TestStore(
       initialState: HomeState(),
       reducer: homeReducer,
@@ -27,7 +27,7 @@ final class HomeFeatureTests: XCTestCase {
     store.environment.messenger.isLoggedIn.run = { false }
     store.environment.messenger.isRegistered.run = { false }
 
-    store.send(.start)
+    store.send(.messenger(.start))
 
     bgQueue.advance()
 
@@ -36,12 +36,12 @@ final class HomeFeatureTests: XCTestCase {
 
     mainQueue.advance()
 
-    store.receive(.set(\.$register, RegisterState())) {
+    store.receive(.messenger(.didStartUnregistered)) {
       $0.register = RegisterState()
     }
   }
 
-  func testStartRegistered() {
+  func testMessengerStartRegistered() {
     let store = TestStore(
       initialState: HomeState(),
       reducer: homeReducer,
@@ -63,7 +63,7 @@ final class HomeFeatureTests: XCTestCase {
     store.environment.messenger.isRegistered.run = { true }
     store.environment.messenger.logIn.run = { messengerDidLogIn += 1 }
 
-    store.send(.start)
+    store.send(.messenger(.start))
 
     bgQueue.advance()
 
@@ -72,6 +72,8 @@ final class HomeFeatureTests: XCTestCase {
     XCTAssertNoDifference(messengerDidLogIn, 1)
 
     mainQueue.advance()
+
+    store.receive(.messenger(.didStartRegistered))
   }
 
   func testRegisterFinished() {
@@ -100,7 +102,7 @@ final class HomeFeatureTests: XCTestCase {
       $0.register = nil
     }
 
-    store.receive(.start)
+    store.receive(.messenger(.start))
 
     bgQueue.advance()
 
@@ -108,9 +110,11 @@ final class HomeFeatureTests: XCTestCase {
     XCTAssertNoDifference(messengerDidLogIn, 1)
 
     mainQueue.advance()
+
+    store.receive(.messenger(.didStartRegistered))
   }
 
-  func testStartMessengerStartFailure() {
+  func testMessengerStartFailure() {
     let store = TestStore(
       initialState: HomeState(),
       reducer: homeReducer,
@@ -124,14 +128,14 @@ final class HomeFeatureTests: XCTestCase {
     store.environment.mainQueue = .immediate
     store.environment.messenger.start.run = { _ in throw error }
 
-    store.send(.start)
+    store.send(.messenger(.start))
 
-    store.receive(.set(\.$failure, error.localizedDescription)) {
+    store.receive(.messenger(.failure(error as NSError))) {
       $0.failure = error.localizedDescription
     }
   }
 
-  func testStartMessengerConnectFailure() {
+  func testMessengerStartConnectFailure() {
     let store = TestStore(
       initialState: HomeState(),
       reducer: homeReducer,
@@ -147,14 +151,14 @@ final class HomeFeatureTests: XCTestCase {
     store.environment.messenger.isConnected.run = { false }
     store.environment.messenger.connect.run = { throw error }
 
-    store.send(.start)
+    store.send(.messenger(.start))
 
-    store.receive(.set(\.$failure, error.localizedDescription)) {
+    store.receive(.messenger(.failure(error as NSError))) {
       $0.failure = error.localizedDescription
     }
   }
 
-  func testStartMessengerIsRegisteredFailure() {
+  func testMessengerStartIsRegisteredFailure() {
     let store = TestStore(
       initialState: HomeState(),
       reducer: homeReducer,
@@ -171,14 +175,14 @@ final class HomeFeatureTests: XCTestCase {
     store.environment.messenger.isLoggedIn.run = { false }
     store.environment.messenger.isRegistered.run = { throw error }
 
-    store.send(.start)
+    store.send(.messenger(.start))
 
-    store.receive(.set(\.$failure, error.localizedDescription)) {
+    store.receive(.messenger(.failure(error as NSError))) {
       $0.failure = error.localizedDescription
     }
   }
 
-  func testStartMessengerLogInFailure() {
+  func testMessengerStartLogInFailure() {
     let store = TestStore(
       initialState: HomeState(),
       reducer: homeReducer,
@@ -196,9 +200,9 @@ final class HomeFeatureTests: XCTestCase {
     store.environment.messenger.isRegistered.run = { true }
     store.environment.messenger.logIn.run = { throw error }
 
-    store.send(.start)
+    store.send(.messenger(.start))
 
-    store.receive(.set(\.$failure, error.localizedDescription)) {
+    store.receive(.messenger(.failure(error as NSError))) {
       $0.failure = error.localizedDescription
     }
   }
@@ -254,15 +258,15 @@ final class HomeFeatureTests: XCTestCase {
       messengerDidDestroy += 1
     }
 
-    store.send(.deleteAccountButtonTapped) {
+    store.send(.deleteAccount(.buttonTapped)) {
       $0.alert = .confirmAccountDeletion()
     }
 
-    store.send(.set(\.$alert, nil)) {
+    store.send(.didDismissAlert) {
       $0.alert = nil
     }
 
-    store.send(.deleteAccountConfirmed) {
+    store.send(.deleteAccount(.confirmed)) {
       $0.isDeletingAccount = true
     }
 
@@ -271,7 +275,7 @@ final class HomeFeatureTests: XCTestCase {
     XCTAssertNoDifference(messengerDidDestroy, 1)
     XCTAssertNoDifference(dbDidDrop, 1)
 
-    store.receive(.didDeleteAccount) {
+    store.receive(.deleteAccount(.success)) {
       $0.isDeletingAccount = false
     }
   }
@@ -298,16 +302,41 @@ final class HomeFeatureTests: XCTestCase {
       return e2e
     }
 
-    store.send(.deleteAccountConfirmed) {
+    store.send(.deleteAccount(.confirmed)) {
       $0.isDeletingAccount = true
     }
 
-    store.receive(.set(\.$isDeletingAccount, false)) {
+    store.receive(.deleteAccount(.failure(error as NSError))) {
       $0.isDeletingAccount = false
+      $0.alert = .accountDeletionFailed(error)
     }
+  }
 
-    store.receive(.set(\.$alert, .accountDeletionFailed(error))) {
-      $0.alert = .accountDeletionFailed(error)
+  func testDidDismissAlert() {
+    let store = TestStore(
+      initialState: HomeState(
+        alert: AlertState(title: TextState(""))
+      ),
+      reducer: homeReducer,
+      environment: .unimplemented
+    )
+
+    store.send(.didDismissAlert) {
+      $0.alert = nil
+    }
+  }
+
+  func testDidDismissRegister() {
+    let store = TestStore(
+      initialState: HomeState(
+        register: RegisterState()
+      ),
+      reducer: homeReducer,
+      environment: .unimplemented
+    )
+
+    store.send(.didDismissRegister) {
+      $0.register = nil
     }
   }
 }
-- 
GitLab