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

Migrate ContactsFeature to ReducerProtocol

parent f6df3d2f
No related branches found
No related tags found
2 merge requests!126Migrate example app to ComposableArchitecture's ReducerProtocol,!102Release 1.0.0
This commit is part of merge request !102. Comments created here will be created in the context of that merge request.
import AppCore
import ComposableArchitecture
import ComposablePresentation
import ContactFeature
import Foundation
import MyContactFeature
import XCTestDynamicOverlay
import XXClient
import XXMessengerClient
import XXModels
public struct ContactsComponent: ReducerProtocol {
public struct State: Equatable {
public init(
myId: Data? = nil,
contacts: IdentifiedArrayOf<XXModels.Contact> = [],
contact: ContactComponent.State? = nil,
myContact: MyContactComponent.State? = nil
) {
self.myId = myId
self.contacts = contacts
self.contact = contact
self.myContact = myContact
}
public var myId: Data?
public var contacts: IdentifiedArrayOf<XXModels.Contact>
public var contact: ContactComponent.State?
public var myContact: MyContactComponent.State?
}
public enum Action: Equatable {
case start
case didFetchContacts([XXModels.Contact])
case contactSelected(XXModels.Contact)
case contactDismissed
case contact(ContactComponent.Action)
case myContactSelected
case myContactDismissed
case myContact(MyContactComponent.Action)
}
public init() {}
@Dependency(\.app.messenger) var messenger: Messenger
@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:
state.myId = try? messenger.e2e.tryGet().getContact().getId()
return Effect
.catching { try db() }
.flatMap { $0.fetchContactsPublisher(.init()) }
.assertNoFailure()
.map(Action.didFetchContacts)
.subscribe(on: bgQueue)
.receive(on: mainQueue)
.eraseToEffect()
case .didFetchContacts(var contacts):
if let myId = state.myId,
let myIndex = contacts.firstIndex(where: { $0.id == myId }) {
contacts.move(fromOffsets: [myIndex], toOffset: contacts.startIndex)
}
state.contacts = IdentifiedArray(uniqueElements: contacts)
return .none
case .contactSelected(let contact):
state.contact = ContactComponent.State(id: contact.id, dbContact: contact)
return .none
case .contactDismissed:
state.contact = nil
return .none
case .myContactSelected:
state.myContact = MyContactComponent.State()
return .none
case .myContactDismissed:
state.myContact = nil
return .none
case .contact(_), .myContact(_):
return .none
}
}
.presenting(
state: .keyPath(\.contact),
id: .keyPath(\.?.id),
action: /Action.contact,
presented: { ContactComponent() }
)
.presenting(
state: .keyPath(\.myContact),
id: .notNil(),
action: /Action.myContact,
presented: { MyContactComponent() }
)
}
}
import AppCore
import ComposableArchitecture
import ComposablePresentation
import ContactFeature
import Foundation
import MyContactFeature
import XCTestDynamicOverlay
import XXClient
import XXMessengerClient
import XXModels
public struct ContactsState: Equatable {
public init(
myId: Data? = nil,
contacts: IdentifiedArrayOf<XXModels.Contact> = [],
contact: ContactState? = nil,
myContact: MyContactState? = nil
) {
self.myId = myId
self.contacts = contacts
self.contact = contact
self.myContact = myContact
}
public var myId: Data?
public var contacts: IdentifiedArrayOf<XXModels.Contact>
public var contact: ContactState?
public var myContact: MyContactState?
}
public enum ContactsAction: Equatable {
case start
case didFetchContacts([XXModels.Contact])
case contactSelected(XXModels.Contact)
case contactDismissed
case contact(ContactAction)
case myContactSelected
case myContactDismissed
case myContact(MyContactAction)
}
public struct ContactsEnvironment {
public init(
messenger: Messenger,
db: DBManagerGetDB,
mainQueue: AnySchedulerOf<DispatchQueue>,
bgQueue: AnySchedulerOf<DispatchQueue>,
contact: @escaping () -> ContactEnvironment,
myContact: @escaping () -> MyContactEnvironment
) {
self.messenger = messenger
self.db = db
self.mainQueue = mainQueue
self.bgQueue = bgQueue
self.contact = contact
self.myContact = myContact
}
public var messenger: Messenger
public var db: DBManagerGetDB
public var mainQueue: AnySchedulerOf<DispatchQueue>
public var bgQueue: AnySchedulerOf<DispatchQueue>
public var contact: () -> ContactEnvironment
public var myContact: () -> MyContactEnvironment
}
#if DEBUG
extension ContactsEnvironment {
public static let unimplemented = ContactsEnvironment(
messenger: .unimplemented,
db: .unimplemented,
mainQueue: .unimplemented,
bgQueue: .unimplemented,
contact: { .unimplemented },
myContact: { .unimplemented }
)
}
#endif
public let contactsReducer = Reducer<ContactsState, ContactsAction, ContactsEnvironment>
{ state, action, env in
switch action {
case .start:
state.myId = try? env.messenger.e2e.tryGet().getContact().getId()
return Effect
.catching { try env.db() }
.flatMap { $0.fetchContactsPublisher(.init()) }
.assertNoFailure()
.map(ContactsAction.didFetchContacts)
.subscribe(on: env.bgQueue)
.receive(on: env.mainQueue)
.eraseToEffect()
case .didFetchContacts(var contacts):
if let myId = state.myId,
let myIndex = contacts.firstIndex(where: { $0.id == myId }) {
contacts.move(fromOffsets: [myIndex], toOffset: contacts.startIndex)
}
state.contacts = IdentifiedArray(uniqueElements: contacts)
return .none
case .contactSelected(let contact):
state.contact = ContactState(id: contact.id, dbContact: contact)
return .none
case .contactDismissed:
state.contact = nil
return .none
case .myContactSelected:
state.myContact = MyContactState()
return .none
case .myContactDismissed:
state.myContact = nil
return .none
case .contact(_), .myContact(_):
return .none
}
}
.presenting(
contactReducer,
state: .keyPath(\.contact),
id: .keyPath(\.?.id),
action: /ContactsAction.contact,
environment: { $0.contact() }
)
.presenting(
myContactReducer,
state: .keyPath(\.myContact),
id: .notNil(),
action: /ContactsAction.myContact,
environment: { $0.myContact() }
)
......@@ -7,17 +7,17 @@ import SwiftUI
import XXModels
public struct ContactsView: View {
public init(store: Store<ContactsState, ContactsAction>) {
public init(store: StoreOf<ContactsComponent>) {
self.store = store
}
let store: Store<ContactsState, ContactsAction>
let store: StoreOf<ContactsComponent>
struct ViewState: Equatable {
var myId: Data?
var contacts: IdentifiedArrayOf<XXModels.Contact>
init(state: ContactsState) {
init(state: ContactsComponent.State) {
myId = state.myId
contacts = state.contacts
}
......@@ -74,7 +74,7 @@ public struct ContactsView: View {
.background(NavigationLinkWithStore(
store.scope(
state: \.contact,
action: ContactsAction.contact
action: ContactsComponent.Action.contact
),
onDeactivate: { viewStore.send(.contactDismissed) },
destination: ContactView.init(store:)
......@@ -82,7 +82,7 @@ public struct ContactsView: View {
.background(NavigationLinkWithStore(
store.scope(
state: \.myContact,
action: ContactsAction.myContact
action: ContactsComponent.Action.myContact
),
onDeactivate: { viewStore.send(.myContactDismissed) },
destination: MyContactView.init(store:)
......@@ -96,7 +96,7 @@ public struct ContactsView_Previews: PreviewProvider {
public static var previews: some View {
NavigationView {
ContactsView(store: Store(
initialState: ContactsState(
initialState: ContactsComponent.State(
contacts: [
.init(
id: "1".data(using: .utf8)!,
......@@ -115,8 +115,7 @@ public struct ContactsView_Previews: PreviewProvider {
),
]
),
reducer: .empty,
environment: ()
reducer: EmptyReducer()
))
}
}
......
......@@ -9,21 +9,20 @@ import XXMessengerClient
import XXModels
@testable import ContactsFeature
final class ContactsFeatureTests: XCTestCase {
final class ContactsComponentTests: XCTestCase {
func testStart() {
let store = TestStore(
initialState: ContactsState(),
reducer: contactsReducer,
environment: .unimplemented
initialState: ContactsComponent.State(),
reducer: ContactsComponent()
)
let myId = "2".data(using: .utf8)!
var didFetchContacts: [XXModels.Contact.Query] = []
let contactsPublisher = PassthroughSubject<[XXModels.Contact], Error>()
store.environment.mainQueue = .immediate
store.environment.bgQueue = .immediate
store.environment.messenger.e2e.get = {
store.dependencies.app.mainQueue = .immediate
store.dependencies.app.bgQueue = .immediate
store.dependencies.app.messenger.e2e.get = {
var e2e: E2E = .unimplemented
e2e.getContact.run = {
var contact: XXClient.Contact = .unimplemented(Data())
......@@ -32,7 +31,7 @@ final class ContactsFeatureTests: XCTestCase {
}
return e2e
}
store.environment.db.run = {
store.dependencies.app.dbManager.getDB.run = {
var db: Database = .unimplemented
db.fetchContactsPublisher.run = { query in
didFetchContacts.append(query)
......@@ -67,28 +66,26 @@ final class ContactsFeatureTests: XCTestCase {
func testSelectContact() {
let store = TestStore(
initialState: ContactsState(),
reducer: contactsReducer,
environment: .unimplemented
initialState: ContactsComponent.State(),
reducer: ContactsComponent()
)
let contact = XXModels.Contact(id: "id".data(using: .utf8)!)
store.send(.contactSelected(contact)) {
$0.contact = ContactState(id: contact.id, dbContact: contact)
$0.contact = ContactComponent.State(id: contact.id, dbContact: contact)
}
}
func testDismissContact() {
let store = TestStore(
initialState: ContactsState(
contact: ContactState(
initialState: ContactsComponent.State(
contact: ContactComponent.State(
id: "id".data(using: .utf8)!,
dbContact: Contact(id: "id".data(using: .utf8)!)
)
),
reducer: contactsReducer,
environment: .unimplemented
reducer: ContactsComponent()
)
store.send(.contactDismissed) {
......@@ -98,23 +95,21 @@ final class ContactsFeatureTests: XCTestCase {
func testSelectMyContact() {
let store = TestStore(
initialState: ContactsState(),
reducer: contactsReducer,
environment: .unimplemented
initialState: ContactsComponent.State(),
reducer: ContactsComponent()
)
store.send(.myContactSelected) {
$0.myContact = MyContactState()
$0.myContact = MyContactComponent.State()
}
}
func testDismissMyContact() {
let store = TestStore(
initialState: ContactsState(
myContact: MyContactState()
initialState: ContactsComponent.State(
myContact: MyContactComponent.State()
),
reducer: contactsReducer,
environment: .unimplemented
reducer: ContactsComponent()
)
store.send(.myContactDismissed) {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment