From c438e05476cf9c861a542aed96524af4b6ed2fed Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Fri, 21 Oct 2022 11:42:32 +0200
Subject: [PATCH] Migrate RegisterFeature to ReducerProtocol

---
 .../RegisterFeature/RegisterComponent.swift   | 104 ++++++++++++++
 .../RegisterFeature/RegisterFeature.swift     | 127 ------------------
 .../RegisterFeature/RegisterView.swift        |  17 ++-
 ...sts.swift => RegisterComponentTests.swift} |  62 ++++-----
 4 files changed, 141 insertions(+), 169 deletions(-)
 create mode 100644 Examples/xx-messenger/Sources/RegisterFeature/RegisterComponent.swift
 delete mode 100644 Examples/xx-messenger/Sources/RegisterFeature/RegisterFeature.swift
 rename Examples/xx-messenger/Tests/RegisterFeatureTests/{RegisterFeatureTests.swift => RegisterComponentTests.swift} (76%)

diff --git a/Examples/xx-messenger/Sources/RegisterFeature/RegisterComponent.swift b/Examples/xx-messenger/Sources/RegisterFeature/RegisterComponent.swift
new file mode 100644
index 00000000..062080af
--- /dev/null
+++ b/Examples/xx-messenger/Sources/RegisterFeature/RegisterComponent.swift
@@ -0,0 +1,104 @@
+import AppCore
+import ComposableArchitecture
+import Foundation
+import XCTestDynamicOverlay
+import XXClient
+import XXMessengerClient
+import XXModels
+
+public struct RegisterComponent: ReducerProtocol {
+  public struct State: Equatable {
+    public enum Error: Swift.Error, Equatable {
+      case usernameMismatch(registering: String, registered: String?)
+    }
+
+    public enum Field: String, Hashable {
+      case username
+    }
+
+    public init(
+      focusedField: Field? = nil,
+      username: String = "",
+      isRegistering: Bool = false,
+      failure: String? = nil
+    ) {
+      self.focusedField = focusedField
+      self.username = username
+      self.isRegistering = isRegistering
+      self.failure = failure
+    }
+
+    @BindableState public var focusedField: Field?
+    @BindableState public var username: String
+    public var isRegistering: Bool
+    public var failure: String?
+  }
+
+  public enum Action: Equatable, BindableAction {
+    case registerTapped
+    case failed(String)
+    case finished
+    case binding(BindingAction<State>)
+  }
+
+  public init() {}
+
+  @Dependency(\.app.messenger) var messenger: Messenger
+  @Dependency(\.app.dbManager.getDB) var db: DBManagerGetDB
+  @Dependency(\.app.now) var now: () -> Date
+  @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
+      switch action {
+      case .binding(_):
+        return .none
+
+      case .registerTapped:
+        state.focusedField = nil
+        state.isRegistering = true
+        state.failure = nil
+        return .future { [username = state.username] fulfill in
+          do {
+            let db = try db()
+            try messenger.register(username: username)
+            let contact = try messenger.myContact()
+            let facts = try contact.getFacts()
+            try db.saveContact(Contact(
+              id: try contact.getId(),
+              marshaled: contact.data,
+              username: facts.get(.username)?.value,
+              email: facts.get(.email)?.value,
+              phone: facts.get(.phone)?.value,
+              createdAt: now()
+            ))
+            guard facts.get(.username)?.value == username else {
+              throw State.Error.usernameMismatch(
+                registering: username,
+                registered: facts.get(.username)?.value
+              )
+            }
+            fulfill(.success(.finished))
+          }
+          catch {
+            fulfill(.success(.failed(error.localizedDescription)))
+          }
+        }
+        .subscribe(on: bgQueue)
+        .receive(on: mainQueue)
+        .eraseToEffect()
+
+      case .failed(let failure):
+        state.isRegistering = false
+        state.failure = failure
+        return .none
+
+      case .finished:
+        state.isRegistering = false
+        return .none
+      }
+    }
+  }
+}
diff --git a/Examples/xx-messenger/Sources/RegisterFeature/RegisterFeature.swift b/Examples/xx-messenger/Sources/RegisterFeature/RegisterFeature.swift
deleted file mode 100644
index f8fdabef..00000000
--- a/Examples/xx-messenger/Sources/RegisterFeature/RegisterFeature.swift
+++ /dev/null
@@ -1,127 +0,0 @@
-import AppCore
-import ComposableArchitecture
-import Foundation
-import XCTestDynamicOverlay
-import XXClient
-import XXMessengerClient
-import XXModels
-
-public struct RegisterState: Equatable {
-  public enum Error: Swift.Error, Equatable {
-    case usernameMismatch(registering: String, registered: String?)
-  }
-
-  public enum Field: String, Hashable {
-    case username
-  }
-
-  public init(
-    focusedField: Field? = nil,
-    username: String = "",
-    isRegistering: Bool = false,
-    failure: String? = nil
-  ) {
-    self.focusedField = focusedField
-    self.username = username
-    self.isRegistering = isRegistering
-    self.failure = failure
-  }
-
-  @BindableState public var focusedField: Field?
-  @BindableState public var username: String
-  public var isRegistering: Bool
-  public var failure: String?
-}
-
-public enum RegisterAction: Equatable, BindableAction {
-  case registerTapped
-  case failed(String)
-  case finished
-  case binding(BindingAction<RegisterState>)
-}
-
-public struct RegisterEnvironment {
-  public init(
-    messenger: Messenger,
-    db: DBManagerGetDB,
-    now: @escaping () -> Date,
-    mainQueue: AnySchedulerOf<DispatchQueue>,
-    bgQueue: AnySchedulerOf<DispatchQueue>
-  ) {
-    self.messenger = messenger
-    self.db = db
-    self.now = now
-    self.mainQueue = mainQueue
-    self.bgQueue = bgQueue
-  }
-
-  public var messenger: Messenger
-  public var db: DBManagerGetDB
-  public var now: () -> Date
-  public var mainQueue: AnySchedulerOf<DispatchQueue>
-  public var bgQueue: AnySchedulerOf<DispatchQueue>
-}
-
-#if DEBUG
-extension RegisterEnvironment {
-  public static let unimplemented = RegisterEnvironment(
-    messenger: .unimplemented,
-    db: .unimplemented,
-    now: XCTUnimplemented("\(Self.self).now"),
-    mainQueue: .unimplemented,
-    bgQueue: .unimplemented
-  )
-}
-#endif
-
-public let registerReducer = Reducer<RegisterState, RegisterAction, RegisterEnvironment>
-{ state, action, env in
-  switch action {
-  case .binding(_):
-    return .none
-
-  case .registerTapped:
-    state.focusedField = nil
-    state.isRegistering = true
-    state.failure = nil
-    return .future { [username = state.username] fulfill in
-      do {
-        let db = try env.db()
-        try env.messenger.register(username: username)
-        let contact = try env.messenger.myContact()
-        let facts = try contact.getFacts()
-        try db.saveContact(Contact(
-          id: try contact.getId(),
-          marshaled: contact.data,
-          username: facts.get(.username)?.value,
-          email: facts.get(.email)?.value,
-          phone: facts.get(.phone)?.value,
-          createdAt: env.now()
-        ))
-        guard facts.get(.username)?.value == username else {
-          throw RegisterState.Error.usernameMismatch(
-            registering: username,
-            registered: facts.get(.username)?.value
-          )
-        }
-        fulfill(.success(.finished))
-      }
-      catch {
-        fulfill(.success(.failed(error.localizedDescription)))
-      }
-    }
-    .subscribe(on: env.bgQueue)
-    .receive(on: env.mainQueue)
-    .eraseToEffect()
-
-  case .failed(let failure):
-    state.isRegistering = false
-    state.failure = failure
-    return .none
-
-  case .finished:
-    state.isRegistering = false
-    return .none
-  }
-}
-.binding()
diff --git a/Examples/xx-messenger/Sources/RegisterFeature/RegisterView.swift b/Examples/xx-messenger/Sources/RegisterFeature/RegisterView.swift
index 4b168c27..a41e16ab 100644
--- a/Examples/xx-messenger/Sources/RegisterFeature/RegisterView.swift
+++ b/Examples/xx-messenger/Sources/RegisterFeature/RegisterView.swift
@@ -2,22 +2,22 @@ import ComposableArchitecture
 import SwiftUI
 
 public struct RegisterView: View {
-  public init(store: Store<RegisterState, RegisterAction>) {
+  public init(store: StoreOf<RegisterComponent>) {
     self.store = store
   }
 
-  let store: Store<RegisterState, RegisterAction>
-  @FocusState var focusedField: RegisterState.Field?
+  let store: StoreOf<RegisterComponent>
+  @FocusState var focusedField: RegisterComponent.State.Field?
 
   struct ViewState: Equatable {
-    init(_ state: RegisterState) {
+    init(_ state: RegisterComponent.State) {
       focusedField = state.focusedField
       username = state.username
       isRegistering = state.isRegistering
       failure = state.failure
     }
 
-    var focusedField: RegisterState.Field?
+    var focusedField: RegisterComponent.State.Field?
     var username: String
     var isRegistering: Bool
     var failure: String?
@@ -31,7 +31,7 @@ public struct RegisterView: View {
             TextField(
               text: viewStore.binding(
                 get: \.username,
-                send: { RegisterAction.set(\.$username, $0) }
+                send: { RegisterComponent.Action.set(\.$username, $0) }
               ),
               prompt: Text("Enter username"),
               label: { Text("Username") }
@@ -78,9 +78,8 @@ public struct RegisterView: View {
 public struct RegisterView_Previews: PreviewProvider {
   public static var previews: some View {
     RegisterView(store: Store(
-      initialState: RegisterState(),
-      reducer: .empty,
-      environment: ()
+      initialState: RegisterComponent.State(),
+      reducer: EmptyReducer()
     ))
   }
 }
diff --git a/Examples/xx-messenger/Tests/RegisterFeatureTests/RegisterFeatureTests.swift b/Examples/xx-messenger/Tests/RegisterFeatureTests/RegisterComponentTests.swift
similarity index 76%
rename from Examples/xx-messenger/Tests/RegisterFeatureTests/RegisterFeatureTests.swift
rename to Examples/xx-messenger/Tests/RegisterFeatureTests/RegisterComponentTests.swift
index e0ffc5ef..0be4647e 100644
--- a/Examples/xx-messenger/Tests/RegisterFeatureTests/RegisterFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/RegisterFeatureTests/RegisterComponentTests.swift
@@ -6,7 +6,7 @@ import XXMessengerClient
 import XXModels
 @testable import RegisterFeature
 
-final class RegisterFeatureTests: XCTestCase {
+final class RegisterComponentTests: XCTestCase {
   func testRegister() throws {
     let now = Date()
     let username = "registering-username"
@@ -26,24 +26,23 @@ final class RegisterFeatureTests: XCTestCase {
     var dbDidSaveContact: [XXModels.Contact] = []
 
     let store = TestStore(
-      initialState: RegisterState(),
-      reducer: registerReducer,
-      environment: .unimplemented
+      initialState: RegisterComponent.State(),
+      reducer: RegisterComponent()
     )
-    store.environment.now = { now }
-    store.environment.mainQueue = .immediate
-    store.environment.bgQueue = .immediate
-    store.environment.messenger.register.run = { username in
+    store.dependencies.app.now = { now }
+    store.dependencies.app.mainQueue = .immediate
+    store.dependencies.app.bgQueue = .immediate
+    store.dependencies.app.messenger.register.run = { username in
       messengerDidRegisterUsername.append(username)
     }
-    store.environment.messenger.myContact.run = { includeFacts in
+    store.dependencies.app.messenger.myContact.run = { includeFacts in
       didGetMyContact.append(includeFacts)
       var contact = XXClient.Contact.unimplemented(myContactData)
       contact.getIdFromContact.run = { _ in myContactId }
       contact.getFactsFromContact.run = { _ in myContactFacts }
       return contact
     }
-    store.environment.db.run = {
+    store.dependencies.app.dbManager.getDB.run = {
       var db: Database = .unimplemented
       db.saveContact.run = { contact in
         dbDidSaveContact.append(contact)
@@ -87,17 +86,16 @@ final class RegisterFeatureTests: XCTestCase {
     let error = Error()
 
     let store = TestStore(
-      initialState: RegisterState(),
-      reducer: registerReducer,
-      environment: .unimplemented
+      initialState: RegisterComponent.State(),
+      reducer: RegisterComponent()
     )
 
     let mainQueue = DispatchQueue.test
     let bgQueue = DispatchQueue.test
 
-    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
-    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
-    store.environment.db.run = { throw error }
+    store.dependencies.app.mainQueue = mainQueue.eraseToAnyScheduler()
+    store.dependencies.app.bgQueue = bgQueue.eraseToAnyScheduler()
+    store.dependencies.app.dbManager.getDB.run = { throw error }
 
     store.send(.registerTapped) {
       $0.isRegistering = true
@@ -117,18 +115,17 @@ final class RegisterFeatureTests: XCTestCase {
     let error = Error()
 
     let store = TestStore(
-      initialState: RegisterState(),
-      reducer: registerReducer,
-      environment: .unimplemented
+      initialState: RegisterComponent.State(),
+      reducer: RegisterComponent()
     )
 
     let mainQueue = DispatchQueue.test
     let bgQueue = DispatchQueue.test
 
-    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
-    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
-    store.environment.db.run = { .unimplemented }
-    store.environment.messenger.register.run = { _ in throw error }
+    store.dependencies.app.mainQueue = mainQueue.eraseToAnyScheduler()
+    store.dependencies.app.bgQueue = bgQueue.eraseToAnyScheduler()
+    store.dependencies.app.dbManager.getDB.run = { .unimplemented }
+    store.dependencies.app.messenger.register.run = { _ in throw error }
 
     store.send(.registerTapped) {
       $0.isRegistering = true
@@ -162,26 +159,25 @@ final class RegisterFeatureTests: XCTestCase {
     var dbDidSaveContact: [XXModels.Contact] = []
 
     let store = TestStore(
-      initialState: RegisterState(
+      initialState: RegisterComponent.State(
         username: username
       ),
-      reducer: registerReducer,
-      environment: .unimplemented
+      reducer: RegisterComponent()
     )
-    store.environment.now = { now }
-    store.environment.mainQueue = .immediate
-    store.environment.bgQueue = .immediate
-    store.environment.messenger.register.run = { username in
+    store.dependencies.app.now = { now }
+    store.dependencies.app.mainQueue = .immediate
+    store.dependencies.app.bgQueue = .immediate
+    store.dependencies.app.messenger.register.run = { username in
       messengerDidRegisterUsername.append(username)
     }
-    store.environment.messenger.myContact.run = { includeFacts in
+    store.dependencies.app.messenger.myContact.run = { includeFacts in
       didGetMyContact.append(includeFacts)
       var contact = XXClient.Contact.unimplemented(myContactData)
       contact.getIdFromContact.run = { _ in myContactId }
       contact.getFactsFromContact.run = { _ in myContactFacts }
       return contact
     }
-    store.environment.db.run = {
+    store.dependencies.app.dbManager.getDB.run = {
       var db: Database = .unimplemented
       db.saveContact.run = { contact in
         dbDidSaveContact.append(contact)
@@ -207,7 +203,7 @@ final class RegisterFeatureTests: XCTestCase {
       )
     ])
 
-    let failure = RegisterState.Error.usernameMismatch(
+    let failure = RegisterComponent.State.Error.usernameMismatch(
       registering: username,
       registered: myContactUsername
     )
-- 
GitLab