Skip to content
Snippets Groups Projects
Commit dd822557 authored by Dariusz Rybicki's avatar Dariusz Rybicki
Browse files

Fetch and display group details

parent b75b9ccc
No related branches found
No related tags found
2 merge requests!153Release 1.1.0,!151[Messenger example] join group
import AppCore
import ComposableArchitecture import ComposableArchitecture
import Foundation
import XXModels import XXModels
public struct GroupComponent: ReducerProtocol { public struct GroupComponent: ReducerProtocol {
public struct State: Equatable { public struct State: Equatable {
public init( 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 { public enum Action: Equatable {
case start case start
case didFetchGroupInfo(XXModels.GroupInfo?)
} }
public init() {} 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> { public var body: some ReducerProtocol<State, Action> {
Reduce { state, action in Reduce { state, action in
switch action { switch action {
case .start: 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 return .none
} }
} }
......
...@@ -15,17 +15,33 @@ public struct GroupView: View { ...@@ -15,17 +15,33 @@ public struct GroupView: View {
struct ViewState: Equatable { struct ViewState: Equatable {
init(state: Component.State) { init(state: Component.State) {
group = state.group info = state.groupInfo
} }
var group: XXModels.Group var info: XXModels.GroupInfo?
} }
public var body: some View { public var body: some View {
WithViewStore(store, observe: ViewState.init) { viewStore in WithViewStore(store, observe: ViewState.init) { viewStore in
Form { Form {
Section("Group name") { if let info = viewStore.info {
Text(viewStore.group.name) 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") .navigationTitle("Group")
...@@ -40,13 +56,30 @@ public struct GroupView_Previews: PreviewProvider { ...@@ -40,13 +56,30 @@ public struct GroupView_Previews: PreviewProvider {
NavigationView { NavigationView {
GroupView(store: Store( GroupView(store: Store(
initialState: GroupComponent.State( initialState: GroupComponent.State(
group: .init( groupId: "group-id".data(using: .utf8)!,
id: "group-id".data(using: .utf8)!, groupInfo: .init(
name: "Preview group", group: .init(
leaderId: "group-leader-id".data(using: .utf8)!, id: "group-id".data(using: .utf8)!,
createdAt: Date(timeIntervalSince1970: TimeInterval(86_400)), name: "Preview group",
authStatus: .participating, leaderId: "group-leader-id".data(using: .utf8)!,
serialized: "group-serialized".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() reducer: EmptyReducer()
......
...@@ -58,7 +58,7 @@ public struct GroupsComponent: ReducerProtocol { ...@@ -58,7 +58,7 @@ public struct GroupsComponent: ReducerProtocol {
return .none return .none
case .didSelectGroup(let group): case .didSelectGroup(let group):
state.group = GroupComponent.State(group: group) state.group = GroupComponent.State(groupId: group.id)
return .none return .none
case .didDismissGroup: case .didDismissGroup:
......
import Combine
import ComposableArchitecture import ComposableArchitecture
import CustomDump
import XCTest import XCTest
import XXModels import XXModels
@testable import GroupFeature @testable import GroupFeature
final class GroupComponentTests: XCTestCase { 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() { func testStart() {
let groupId = "group-id".data(using: .utf8)!
let groupInfosSubject = PassthroughSubject<[GroupInfo], Error>()
let store = TestStore( let store = TestStore(
initialState: GroupComponent.State( initialState: GroupComponent.State(
group: .stub() groupId: groupId
), ),
reducer: GroupComponent() 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) 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 { private extension XXModels.GroupInfo {
static func stub() -> XXModels.Group { static func stub() -> XXModels.GroupInfo {
XXModels.Group( XXModels.GroupInfo(
id: "group-id".data(using: .utf8)!, group: .init(
name: "Group name", id: "group-id".data(using: .utf8)!,
leaderId: "group-leader-id".data(using: .utf8)!, name: "Group Name",
createdAt: Date(timeIntervalSince1970: TimeInterval(86_400)), leaderId: "group-leader-id".data(using: .utf8)!,
authStatus: .participating, createdAt: Date(timeIntervalSince1970: TimeInterval(86_400)),
serialized: "group-serialized".data(using: .utf8)! 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"
),
]
) )
} }
} }
...@@ -62,32 +62,36 @@ final class GroupsComponentTests: XCTestCase { ...@@ -62,32 +62,36 @@ final class GroupsComponentTests: XCTestCase {
} }
func testSelectGroup() { func testSelectGroup() {
let groups: [XXModels.Group] = [
.stub(1),
.stub(2),
.stub(3),
]
let store = TestStore( let store = TestStore(
initialState: GroupsComponent.State( initialState: GroupsComponent.State(
groups: IdentifiedArray(uniqueElements: [ groups: IdentifiedArray(uniqueElements: groups)
.stub(1),
.stub(2),
.stub(3),
])
), ),
reducer: GroupsComponent() reducer: GroupsComponent()
) )
store.send(.didSelectGroup(.stub(2))) { store.send(.didSelectGroup(groups[1])) {
$0.group = GroupComponent.State(group: .stub(2)) $0.group = GroupComponent.State(groupId: groups[1].id)
} }
} }
func testDismissGroup() { func testDismissGroup() {
let groups: [XXModels.Group] = [
.stub(1),
.stub(2),
.stub(3),
]
let store = TestStore( let store = TestStore(
initialState: GroupsComponent.State( initialState: GroupsComponent.State(
groups: IdentifiedArray(uniqueElements: [ groups: IdentifiedArray(uniqueElements: groups),
.stub(1),
.stub(2),
.stub(3),
]),
group: GroupComponent.State( group: GroupComponent.State(
group: .stub(2) groupId: groups[1].id
) )
), ),
reducer: GroupsComponent() reducer: GroupsComponent()
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment