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