From d107bb5820e4524b366091945b222db090180ea2 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Thu, 8 Sep 2022 12:18:18 +0200
Subject: [PATCH] Remove UserSearchResultFeature

---
 .../AppFeature/AppEnvironment+Live.swift      |  3 -
 .../UserSearchFeature/UserSearchFeature.swift | 54 ++++++++++----
 .../UserSearchResultFeature.swift             | 55 --------------
 .../UserSearchResultView.swift                | 74 -------------------
 .../UserSearchFeature/UserSearchView.swift    | 37 ++++++++--
 .../UserSearchFeatureTests.swift              | 37 ++++++++--
 .../UserSearchResultFeatureTests.swift        | 46 ------------
 7 files changed, 100 insertions(+), 206 deletions(-)
 delete mode 100644 Examples/xx-messenger/Sources/UserSearchFeature/UserSearchResultFeature.swift
 delete mode 100644 Examples/xx-messenger/Sources/UserSearchFeature/UserSearchResultView.swift
 delete mode 100644 Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchResultFeatureTests.swift

diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
index 298cb9f6..01e31519 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
@@ -53,9 +53,6 @@ extension AppEnvironment {
               messenger: messenger,
               mainQueue: mainQueue,
               bgQueue: bgQueue,
-              result: {
-                UserSearchResultEnvironment()
-              },
               contact: {
                 ContactEnvironment(
                   messenger: messenger,
diff --git a/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchFeature.swift b/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchFeature.swift
index 1b10c1b3..d49b4440 100644
--- a/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchFeature.swift
+++ b/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchFeature.swift
@@ -13,12 +13,38 @@ public struct UserSearchState: Equatable {
     case phone
   }
 
+  public struct Result: Equatable, Identifiable {
+    public init(
+      id: Data,
+      xxContact: XXClient.Contact,
+      username: String? = nil,
+      email: String? = nil,
+      phone: String? = nil
+    ) {
+      self.id = id
+      self.xxContact = xxContact
+      self.username = username
+      self.email = email
+      self.phone = phone
+    }
+
+    public var id: Data
+    public var xxContact: XXClient.Contact
+    public var username: String?
+    public var email: String?
+    public var phone: String?
+
+    public var hasFacts: Bool {
+      username != nil || email != nil || phone != nil
+    }
+  }
+
   public init(
     focusedField: Field? = nil,
     query: MessengerSearchUsers.Query = .init(),
     isSearching: Bool = false,
     failure: String? = nil,
-    results: IdentifiedArrayOf<UserSearchResultState> = [],
+    results: IdentifiedArrayOf<Result> = [],
     contact: ContactState? = nil
   ) {
     self.focusedField = focusedField
@@ -33,7 +59,7 @@ public struct UserSearchState: Equatable {
   @BindableState public var query: MessengerSearchUsers.Query
   public var isSearching: Bool
   public var failure: String?
-  public var results: IdentifiedArrayOf<UserSearchResultState>
+  public var results: IdentifiedArrayOf<Result>
   public var contact: ContactState?
 }
 
@@ -42,8 +68,8 @@ public enum UserSearchAction: Equatable, BindableAction {
   case didFail(String)
   case didSucceed([Contact])
   case didDismissContact
+  case resultTapped(id: Data)
   case binding(BindingAction<UserSearchState>)
-  case result(id: UserSearchResultState.ID, action: UserSearchResultAction)
   case contact(ContactAction)
 }
 
@@ -52,20 +78,17 @@ public struct UserSearchEnvironment {
     messenger: Messenger,
     mainQueue: AnySchedulerOf<DispatchQueue>,
     bgQueue: AnySchedulerOf<DispatchQueue>,
-    result: @escaping () -> UserSearchResultEnvironment,
     contact: @escaping () -> ContactEnvironment
   ) {
     self.messenger = messenger
     self.mainQueue = mainQueue
     self.bgQueue = bgQueue
-    self.result = result
     self.contact = contact
   }
 
   public var messenger: Messenger
   public var mainQueue: AnySchedulerOf<DispatchQueue>
   public var bgQueue: AnySchedulerOf<DispatchQueue>
-  public var result: () -> UserSearchResultEnvironment
   public var contact: () -> ContactEnvironment
 }
 
@@ -75,7 +98,6 @@ extension UserSearchEnvironment {
     messenger: .unimplemented,
     mainQueue: .unimplemented,
     bgQueue: .unimplemented,
-    result: { .unimplemented },
     contact: { .unimplemented }
   )
 }
@@ -105,7 +127,13 @@ public let userSearchReducer = Reducer<UserSearchState, UserSearchAction, UserSe
     state.failure = nil
     state.results = IdentifiedArray(uniqueElements: contacts.compactMap { contact in
       guard let id = try? contact.getId() else { return nil }
-      return UserSearchResultState(id: id, xxContact: contact)
+      return UserSearchState.Result(
+        id: id,
+        xxContact: contact,
+        username: try? contact.getFact(.username)?.fact,
+        email: try? contact.getFact(.email)?.fact,
+        phone: try? contact.getFact(.phone)?.fact
+      )
     })
     return .none
 
@@ -119,24 +147,18 @@ public let userSearchReducer = Reducer<UserSearchState, UserSearchAction, UserSe
     state.contact = nil
     return .none
 
-  case .result(let id, action: .tapped):
+  case .resultTapped(let id):
     state.contact = ContactState(
       id: id,
       xxContact: state.results[id: id]?.xxContact
     )
     return .none
 
-  case .binding(_), .result(_, _), .contact(_):
+  case .binding(_), .contact(_):
     return .none
   }
 }
 .binding()
-.presenting(
-  forEach: userSearchResultReducer,
-  state: \.results,
-  action: /UserSearchAction.result(id:action:),
-  environment: { $0.result() }
-)
 .presenting(
   contactReducer,
   state: .keyPath(\.contact),
diff --git a/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchResultFeature.swift b/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchResultFeature.swift
deleted file mode 100644
index 839e6886..00000000
--- a/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchResultFeature.swift
+++ /dev/null
@@ -1,55 +0,0 @@
-import ComposableArchitecture
-import Foundation
-import XCTestDynamicOverlay
-import XXClient
-
-public struct UserSearchResultState: Equatable, Identifiable {
-  public init(
-    id: Data,
-    xxContact: XXClient.Contact,
-    username: String? = nil,
-    email: String? = nil,
-    phone: String? = nil
-  ) {
-    self.id = id
-    self.xxContact = xxContact
-    self.username = username
-    self.email = email
-    self.phone = phone
-  }
-
-  public var id: Data
-  public var xxContact: XXClient.Contact
-  public var username: String?
-  public var email: String?
-  public var phone: String?
-}
-
-public enum UserSearchResultAction: Equatable {
-  case start
-  case tapped
-}
-
-public struct UserSearchResultEnvironment {
-  public init() {}
-}
-
-#if DEBUG
-extension UserSearchResultEnvironment {
-  public static let unimplemented = UserSearchResultEnvironment()
-}
-#endif
-
-public let userSearchResultReducer = Reducer<UserSearchResultState, UserSearchResultAction, UserSearchResultEnvironment>
-{ state, action, env in
-  switch action {
-  case .start:
-    state.username = try? state.xxContact.getFact(.username)?.fact
-    state.email = try? state.xxContact.getFact(.email)?.fact
-    state.phone = try? state.xxContact.getFact(.phone)?.fact
-    return .none
-
-  case .tapped:
-    return .none
-  }
-}
diff --git a/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchResultView.swift b/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchResultView.swift
deleted file mode 100644
index fd29a84f..00000000
--- a/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchResultView.swift
+++ /dev/null
@@ -1,74 +0,0 @@
-import ComposableArchitecture
-import SwiftUI
-import XXModels
-
-public struct UserSearchResultView: View {
-  public init(store: Store<UserSearchResultState, UserSearchResultAction>) {
-    self.store = store
-  }
-
-  let store: Store<UserSearchResultState, UserSearchResultAction>
-
-  struct ViewState: Equatable {
-    var username: String?
-    var email: String?
-    var phone: String?
-
-    init(state: UserSearchResultState) {
-      username = state.username
-      email = state.email
-      phone = state.phone
-    }
-
-    var isEmpty: Bool {
-      username == nil && email == nil && phone == nil
-    }
-  }
-
-  public var body: some View {
-    WithViewStore(store.scope(state: ViewState.init)) { viewStore in
-      Section {
-        Button {
-          viewStore.send(.tapped)
-        } label: {
-          HStack {
-            VStack {
-              if viewStore.isEmpty {
-                Image(systemName: "questionmark")
-                  .frame(maxWidth: .infinity)
-              } else {
-                if let username = viewStore.username {
-                  Text(username)
-                }
-                if let email = viewStore.email {
-                  Text(email)
-                }
-                if let phone = viewStore.phone {
-                  Text(phone)
-                }
-              }
-            }
-            Spacer()
-            Image(systemName: "chevron.forward")
-          }
-        }
-      }
-      .task { viewStore.send(.start) }
-    }
-  }
-}
-
-#if DEBUG
-public struct UserSearchResultView_Previews: PreviewProvider {
-  public static var previews: some View {
-    UserSearchResultView(store: Store(
-      initialState: UserSearchResultState(
-        id: "contact-id".data(using: .utf8)!,
-        xxContact: .unimplemented("contact-data".data(using: .utf8)!)
-      ),
-      reducer: .empty,
-      environment: ()
-    ))
-  }
-}
-#endif
diff --git a/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchView.swift b/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchView.swift
index f0416b3a..9f76ed0a 100644
--- a/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchView.swift
+++ b/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchView.swift
@@ -17,12 +17,14 @@ public struct UserSearchView: View {
     var query: MessengerSearchUsers.Query
     var isSearching: Bool
     var failure: String?
+    var results: IdentifiedArrayOf<UserSearchState.Result>
 
     init(state: UserSearchState) {
       focusedField = state.focusedField
       query = state.query
       isSearching = state.isSearching
       failure = state.failure
+      results = state.results
     }
   }
 
@@ -87,13 +89,34 @@ public struct UserSearchView: View {
           }
         }
 
-        ForEachStore(
-          store.scope(
-            state: \.results,
-            action: UserSearchAction.result(id:action:)
-          ),
-          content: UserSearchResultView.init(store:)
-        )
+        ForEach(viewStore.results) { result in
+          Section {
+            Button {
+              viewStore.send(.resultTapped(id: result.id))
+            } label: {
+              HStack {
+                VStack {
+                  if result.hasFacts {
+                    if let username = result.username {
+                      Text(username)
+                    }
+                    if let email = result.email {
+                      Text(email)
+                    }
+                    if let phone = result.phone {
+                      Text(phone)
+                    }
+                  } else {
+                    Image(systemName: "questionmark")
+                      .frame(maxWidth: .infinity)
+                  }
+                }
+                Spacer()
+                Image(systemName: "chevron.forward")
+              }
+            }
+          }
+        }
       }
       .onChange(of: viewStore.focusedField) { focusedField = $0 }
       .onChange(of: focusedField) { viewStore.send(.set(\.$focusedField, $0)) }
diff --git a/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchFeatureTests.swift b/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchFeatureTests.swift
index c457327c..f8f482f6 100644
--- a/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchFeatureTests.swift
@@ -20,12 +20,26 @@ final class UserSearchFeatureTests: XCTestCase {
 
     var contact1 = Contact.unimplemented("contact-1".data(using: .utf8)!)
     contact1.getIdFromContact.run = { _ in "contact-1-id".data(using: .utf8)! }
-    var contact2 = Contact.unimplemented("contact-1".data(using: .utf8)!)
+    contact1.getFactsFromContact.run = { _ in
+      [
+        Fact(type: .username, value: "contact-1-username"),
+        Fact(type: .email, value: "contact-1-email"),
+        Fact(type: .phone, value: "contact-1-phone"),
+      ]
+    }
+    var contact2 = Contact.unimplemented("contact-2".data(using: .utf8)!)
     contact2.getIdFromContact.run = { _ in "contact-2-id".data(using: .utf8)! }
+    contact2.getFactsFromContact.run = { _ in
+      [
+        Fact(type: .username, value: "contact-2-username"),
+      ]
+    }
     var contact3 = Contact.unimplemented("contact-3".data(using: .utf8)!)
     contact3.getIdFromContact.run = { _ in throw GetIdFromContactError() }
+    contact3.getFactsFromContact.run = { _ in [] }
     var contact4 = Contact.unimplemented("contact-4".data(using: .utf8)!)
     contact4.getIdFromContact.run = { _ in "contact-4-id".data(using: .utf8)! }
+    contact4.getFactsFromContact.run = { _ in throw GetFactsFromContactError() }
     let contacts = [contact1, contact2, contact3, contact4]
 
     store.environment.bgQueue = .immediate
@@ -54,9 +68,22 @@ final class UserSearchFeatureTests: XCTestCase {
       $0.isSearching = false
       $0.failure = nil
       $0.results = [
-        .init(id: "contact-1-id".data(using: .utf8)!, xxContact: contact1),
-        .init(id: "contact-2-id".data(using: .utf8)!, xxContact: contact2),
-        .init(id: "contact-4-id".data(using: .utf8)!, xxContact: contact4)
+        .init(
+          id: "contact-1-id".data(using: .utf8)!,
+          xxContact: contact1,
+          username: "contact-1-username",
+          email: "contact-1-email",
+          phone: "contact-1-phone"
+        ),
+        .init(
+          id: "contact-2-id".data(using: .utf8)!,
+          xxContact: contact2,
+          username: "contact-2-username"
+        ),
+        .init(
+          id: "contact-4-id".data(using: .utf8)!,
+          xxContact: contact4
+        )
       ]
     }
   }
@@ -103,7 +130,7 @@ final class UserSearchFeatureTests: XCTestCase {
       environment: .unimplemented
     )
 
-    store.send(.result(id: "contact-id".data(using: .utf8)!, action: .tapped)) {
+    store.send(.resultTapped(id: "contact-id".data(using: .utf8)!)) {
       $0.contact = ContactState(
         id: "contact-id".data(using: .utf8)!,
         xxContact: .unimplemented("contact-data".data(using: .utf8)!)
diff --git a/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchResultFeatureTests.swift b/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchResultFeatureTests.swift
deleted file mode 100644
index c8f2a99b..00000000
--- a/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchResultFeatureTests.swift
+++ /dev/null
@@ -1,46 +0,0 @@
-import ComposableArchitecture
-import XCTest
-import XCTestDynamicOverlay
-import XXClient
-@testable import UserSearchFeature
-
-final class UserSearchResultFeatureTests: XCTestCase {
-  func testStart() {
-    var contact = Contact.unimplemented("contact-data".data(using: .utf8)!)
-    contact.getFactsFromContact.run = { _ in
-      [
-        Fact(fact: "contact-username", type: 0),
-        Fact(fact: "contact-email", type: 1),
-        Fact(fact: "contact-phone", type: 2),
-      ]
-    }
-
-    let store = TestStore(
-      initialState: UserSearchResultState(
-        id: "contact-id".data(using: .utf8)!,
-        xxContact: contact
-      ),
-      reducer: userSearchResultReducer,
-      environment: .unimplemented
-    )
-
-    store.send(.start) {
-      $0.username = "contact-username"
-      $0.email = "contact-email"
-      $0.phone = "contact-phone"
-    }
-  }
-
-  func testTapped() {
-    let store = TestStore(
-      initialState: UserSearchResultState(
-        id: "contact-id".data(using: .utf8)!,
-        xxContact: .unimplemented("contact-data".data(using: .utf8)!)
-      ),
-      reducer: userSearchResultReducer,
-      environment: .unimplemented
-    )
-
-    store.send(.tapped)
-  }
-}
-- 
GitLab