From 59b4b115357e98ac978526fb8f4e3d45f34b334f Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Fri, 21 Oct 2022 12:38:02 +0200 Subject: [PATCH] Migrate ContactsFeature to ReducerProtocol --- .../ContactsFeature/ContactsComponent.swift | 105 ++++++++++++++ .../ContactsFeature/ContactsFeature.swift | 135 ------------------ .../ContactsFeature/ContactsView.swift | 15 +- ...sts.swift => ContactsComponentTests.swift} | 43 +++--- 4 files changed, 131 insertions(+), 167 deletions(-) create mode 100644 Examples/xx-messenger/Sources/ContactsFeature/ContactsComponent.swift delete mode 100644 Examples/xx-messenger/Sources/ContactsFeature/ContactsFeature.swift rename Examples/xx-messenger/Tests/ContactsFeatureTests/{ContactsFeatureTests.swift => ContactsComponentTests.swift} (70%) diff --git a/Examples/xx-messenger/Sources/ContactsFeature/ContactsComponent.swift b/Examples/xx-messenger/Sources/ContactsFeature/ContactsComponent.swift new file mode 100644 index 00000000..a0ce4ed8 --- /dev/null +++ b/Examples/xx-messenger/Sources/ContactsFeature/ContactsComponent.swift @@ -0,0 +1,105 @@ +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() } + ) + } +} diff --git a/Examples/xx-messenger/Sources/ContactsFeature/ContactsFeature.swift b/Examples/xx-messenger/Sources/ContactsFeature/ContactsFeature.swift deleted file mode 100644 index 680a231e..00000000 --- a/Examples/xx-messenger/Sources/ContactsFeature/ContactsFeature.swift +++ /dev/null @@ -1,135 +0,0 @@ -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() } -) diff --git a/Examples/xx-messenger/Sources/ContactsFeature/ContactsView.swift b/Examples/xx-messenger/Sources/ContactsFeature/ContactsView.swift index e09725d9..9813bc54 100644 --- a/Examples/xx-messenger/Sources/ContactsFeature/ContactsView.swift +++ b/Examples/xx-messenger/Sources/ContactsFeature/ContactsView.swift @@ -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() )) } } diff --git a/Examples/xx-messenger/Tests/ContactsFeatureTests/ContactsFeatureTests.swift b/Examples/xx-messenger/Tests/ContactsFeatureTests/ContactsComponentTests.swift similarity index 70% rename from Examples/xx-messenger/Tests/ContactsFeatureTests/ContactsFeatureTests.swift rename to Examples/xx-messenger/Tests/ContactsFeatureTests/ContactsComponentTests.swift index 9b3ac0d0..e0732e69 100644 --- a/Examples/xx-messenger/Tests/ContactsFeatureTests/ContactsFeatureTests.swift +++ b/Examples/xx-messenger/Tests/ContactsFeatureTests/ContactsComponentTests.swift @@ -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) { -- GitLab