From dd822557bc13e67e2238aa5b8da3264937c38fd2 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Fri, 2 Dec 2022 11:42:47 +0100
Subject: [PATCH] Fetch and display group details

---
 .../Sources/GroupFeature/GroupComponent.swift | 30 ++++++-
 .../Sources/GroupFeature/GroupView.swift      | 55 ++++++++++---
 .../GroupsFeature/GroupsComponent.swift       |  2 +-
 .../GroupComponentTests.swift                 | 79 ++++++++++++++++---
 .../GroupsComponentTests.swift                | 30 ++++---
 5 files changed, 158 insertions(+), 38 deletions(-)

diff --git a/Examples/xx-messenger/Sources/GroupFeature/GroupComponent.swift b/Examples/xx-messenger/Sources/GroupFeature/GroupComponent.swift
index 5c2b7773..122fe918 100644
--- a/Examples/xx-messenger/Sources/GroupFeature/GroupComponent.swift
+++ b/Examples/xx-messenger/Sources/GroupFeature/GroupComponent.swift
@@ -1,27 +1,51 @@
+import AppCore
 import ComposableArchitecture
+import Foundation
 import XXModels
 
 public struct GroupComponent: ReducerProtocol {
   public struct State: Equatable {
     public init(
-      group: XXModels.Group
+      groupId: XXModels.Group.ID,
+      groupInfo: XXModels.GroupInfo? = nil
     ) {
-      self.group = group
+      self.groupId = groupId
+      self.groupInfo = groupInfo
     }
 
-    public var group: XXModels.Group
+    public var groupId: XXModels.Group.ID
+    public var groupInfo: XXModels.GroupInfo?
   }
 
   public enum Action: Equatable {
     case start
+    case didFetchGroupInfo(XXModels.GroupInfo?)
   }
 
   public init() {}
 
+  @Dependency(\.app.dbManager.getDB) var db: DBManagerGetDB
+  @Dependency(\.app.mainQueue) var mainQueue: AnySchedulerOf<DispatchQueue>
+  @Dependency(\.app.bgQueue) var bgQueue: AnySchedulerOf<DispatchQueue>
+
   public var body: some ReducerProtocol<State, Action> {
     Reduce { state, action in
       switch action {
       case .start:
+        return Effect
+          .catching { try db() }
+          .flatMap { [state] in
+            let query = GroupInfo.Query(groupId: state.groupId)
+            return $0.fetchGroupInfosPublisher(query).map(\.first)
+          }
+          .assertNoFailure()
+          .map(Action.didFetchGroupInfo)
+          .subscribe(on: bgQueue)
+          .receive(on: mainQueue)
+          .eraseToEffect()
+
+      case .didFetchGroupInfo(let groupInfo):
+        state.groupInfo = groupInfo
         return .none
       }
     }
diff --git a/Examples/xx-messenger/Sources/GroupFeature/GroupView.swift b/Examples/xx-messenger/Sources/GroupFeature/GroupView.swift
index 95d24a1f..2cd55078 100644
--- a/Examples/xx-messenger/Sources/GroupFeature/GroupView.swift
+++ b/Examples/xx-messenger/Sources/GroupFeature/GroupView.swift
@@ -15,17 +15,33 @@ public struct GroupView: View {
 
   struct ViewState: Equatable {
     init(state: Component.State) {
-      group = state.group
+      info = state.groupInfo
     }
 
-    var group: XXModels.Group
+    var info: XXModels.GroupInfo?
   }
 
   public var body: some View {
     WithViewStore(store, observe: ViewState.init) { viewStore in
       Form {
-        Section("Group name") {
-          Text(viewStore.group.name)
+        if let info = viewStore.info {
+          Section("Name") {
+            Text(info.group.name)
+          }
+
+          Section("Leader") {
+            Label(info.leader.username ?? "", systemImage: "person.badge.shield.checkmark")
+          }
+
+          Section("Members") {
+            ForEach(info.members.filter { $0 != info.leader }) { contact in
+              Label(contact.username ?? "", systemImage: "person")
+            }
+          }
+
+          Section("Status") {
+            GroupAuthStatusView(info.group.authStatus)
+          }
         }
       }
       .navigationTitle("Group")
@@ -40,13 +56,30 @@ public struct GroupView_Previews: PreviewProvider {
     NavigationView {
       GroupView(store: Store(
         initialState: GroupComponent.State(
-          group: .init(
-            id: "group-id".data(using: .utf8)!,
-            name: "Preview group",
-            leaderId: "group-leader-id".data(using: .utf8)!,
-            createdAt: Date(timeIntervalSince1970: TimeInterval(86_400)),
-            authStatus: .participating,
-            serialized: "group-serialized".data(using: .utf8)!
+          groupId: "group-id".data(using: .utf8)!,
+          groupInfo: .init(
+            group: .init(
+              id: "group-id".data(using: .utf8)!,
+              name: "Preview group",
+              leaderId: "group-leader-id".data(using: .utf8)!,
+              createdAt: Date(timeIntervalSince1970: TimeInterval(86_400)),
+              authStatus: .participating,
+              serialized: "group-serialized".data(using: .utf8)!
+            ),
+            leader: .init(
+              id: "group-leader-id".data(using: .utf8)!,
+              username: "Group leader"
+            ),
+            members: [
+              .init(
+                id: "member-1-id".data(using: .utf8)!,
+                username: "Member 1"
+              ),
+              .init(
+                id: "member-2-id".data(using: .utf8)!,
+                username: "Member 2"
+              ),
+            ]
           )
         ),
         reducer: EmptyReducer()
diff --git a/Examples/xx-messenger/Sources/GroupsFeature/GroupsComponent.swift b/Examples/xx-messenger/Sources/GroupsFeature/GroupsComponent.swift
index 0a22359f..91e6dc55 100644
--- a/Examples/xx-messenger/Sources/GroupsFeature/GroupsComponent.swift
+++ b/Examples/xx-messenger/Sources/GroupsFeature/GroupsComponent.swift
@@ -58,7 +58,7 @@ public struct GroupsComponent: ReducerProtocol {
         return .none
 
       case .didSelectGroup(let group):
-        state.group = GroupComponent.State(group: group)
+        state.group = GroupComponent.State(groupId: group.id)
         return .none
 
       case .didDismissGroup:
diff --git a/Examples/xx-messenger/Tests/GroupFeatureTests/GroupComponentTests.swift b/Examples/xx-messenger/Tests/GroupFeatureTests/GroupComponentTests.swift
index 9fe423d5..3522ab7c 100644
--- a/Examples/xx-messenger/Tests/GroupFeatureTests/GroupComponentTests.swift
+++ b/Examples/xx-messenger/Tests/GroupFeatureTests/GroupComponentTests.swift
@@ -1,30 +1,89 @@
+import Combine
 import ComposableArchitecture
+import CustomDump
 import XCTest
 import XXModels
 @testable import GroupFeature
 
 final class GroupComponentTests: XCTestCase {
+  enum Action: Equatable {
+    case didFetchGroupInfos(GroupInfo.Query)
+  }
+
+  var actions: [Action]!
+
+  override func setUp() {
+    actions = []
+  }
+
+  override func tearDown() {
+    actions = nil
+  }
+
   func testStart() {
+    let groupId = "group-id".data(using: .utf8)!
+    let groupInfosSubject = PassthroughSubject<[GroupInfo], Error>()
+
     let store = TestStore(
       initialState: GroupComponent.State(
-        group: .stub()
+        groupId: groupId
       ),
       reducer: GroupComponent()
     )
 
+    store.dependencies.app.mainQueue = .immediate
+    store.dependencies.app.bgQueue = .immediate
+    store.dependencies.app.dbManager.getDB.run = {
+      var db: Database = .unimplemented
+      db.fetchGroupInfosPublisher.run = { query in
+        self.actions.append(.didFetchGroupInfos(query))
+        return groupInfosSubject.eraseToAnyPublisher()
+      }
+      return db
+    }
+
     store.send(.start)
+
+    XCTAssertNoDifference(actions, [
+      .didFetchGroupInfos(.init(groupId: groupId)),
+    ])
+
+    let groupInfo = GroupInfo.stub()
+    groupInfosSubject.send([groupInfo])
+
+    store.receive(.didFetchGroupInfo(groupInfo)) {
+      $0.groupInfo = groupInfo
+    }
+
+    groupInfosSubject.send(completion: .finished)
   }
 }
 
-private extension XXModels.Group {
-  static func stub() -> XXModels.Group {
-    XXModels.Group(
-      id: "group-id".data(using: .utf8)!,
-      name: "Group name",
-      leaderId: "group-leader-id".data(using: .utf8)!,
-      createdAt: Date(timeIntervalSince1970: TimeInterval(86_400)),
-      authStatus: .participating,
-      serialized: "group-serialized".data(using: .utf8)!
+private extension XXModels.GroupInfo {
+  static func stub() -> XXModels.GroupInfo {
+    XXModels.GroupInfo(
+      group: .init(
+        id: "group-id".data(using: .utf8)!,
+        name: "Group Name",
+        leaderId: "group-leader-id".data(using: .utf8)!,
+        createdAt: Date(timeIntervalSince1970: TimeInterval(86_400)),
+        authStatus: .participating,
+        serialized: "group-serialized".data(using: .utf8)!
+      ),
+      leader: .init(
+        id: "group-leader-id".data(using: .utf8)!,
+        username: "Group leader"
+      ),
+      members: [
+        .init(
+          id: "member-1-id".data(using: .utf8)!,
+          username: "Member 1"
+        ),
+        .init(
+          id: "member-2-id".data(using: .utf8)!,
+          username: "Member 2"
+        ),
+      ]
     )
   }
 }
diff --git a/Examples/xx-messenger/Tests/GroupsFeatureTests/GroupsComponentTests.swift b/Examples/xx-messenger/Tests/GroupsFeatureTests/GroupsComponentTests.swift
index 49778aab..5742aaf7 100644
--- a/Examples/xx-messenger/Tests/GroupsFeatureTests/GroupsComponentTests.swift
+++ b/Examples/xx-messenger/Tests/GroupsFeatureTests/GroupsComponentTests.swift
@@ -62,32 +62,36 @@ final class GroupsComponentTests: XCTestCase {
   }
 
   func testSelectGroup() {
+    let groups: [XXModels.Group] = [
+      .stub(1),
+      .stub(2),
+      .stub(3),
+    ]
+
     let store = TestStore(
       initialState: GroupsComponent.State(
-        groups: IdentifiedArray(uniqueElements: [
-          .stub(1),
-          .stub(2),
-          .stub(3),
-        ])
+        groups: IdentifiedArray(uniqueElements: groups)
       ),
       reducer: GroupsComponent()
     )
 
-    store.send(.didSelectGroup(.stub(2))) {
-      $0.group = GroupComponent.State(group: .stub(2))
+    store.send(.didSelectGroup(groups[1])) {
+      $0.group = GroupComponent.State(groupId: groups[1].id)
     }
   }
 
   func testDismissGroup() {
+    let groups: [XXModels.Group] = [
+      .stub(1),
+      .stub(2),
+      .stub(3),
+    ]
+
     let store = TestStore(
       initialState: GroupsComponent.State(
-        groups: IdentifiedArray(uniqueElements: [
-          .stub(1),
-          .stub(2),
-          .stub(3),
-        ]),
+        groups: IdentifiedArray(uniqueElements: groups),
         group: GroupComponent.State(
-          group: .stub(2)
+          groupId: groups[1].id
         )
       ),
       reducer: GroupsComponent()
-- 
GitLab