From 1117ef010d1aef5086ef730ee1d989dfec71ddd2 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 20 Sep 2022 09:13:47 +0200 Subject: [PATCH 01/22] Add MyContactFeature library --- .../xcschemes/MyContactFeature.xcscheme | 78 +++++++++++++++++++ Examples/xx-messenger/Package.swift | 19 +++++ .../xcschemes/XXMessenger.xcscheme | 10 +++ .../MyContactFeature/MyContactFeature.swift | 28 +++++++ .../MyContactFeature/MyContactView.swift | 35 +++++++++ .../MyContactFeatureTests.swift | 15 ++++ 6 files changed, 185 insertions(+) create mode 100644 Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/MyContactFeature.xcscheme create mode 100644 Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift create mode 100644 Examples/xx-messenger/Sources/MyContactFeature/MyContactView.swift create mode 100644 Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift diff --git a/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/MyContactFeature.xcscheme b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/MyContactFeature.xcscheme new file mode 100644 index 00000000..4eb2a433 --- /dev/null +++ b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/MyContactFeature.xcscheme @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "1400" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + <BuildActionEntries> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "MyContactFeature" + BuildableName = "MyContactFeature" + BlueprintName = "MyContactFeature" + ReferencedContainer = "container:"> + </BuildableReference> + </BuildActionEntry> + </BuildActionEntries> + </BuildAction> + <TestAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> + <Testables> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "MyContactFeatureTests" + BuildableName = "MyContactFeatureTests" + BlueprintName = "MyContactFeatureTests" + ReferencedContainer = "container:"> + </BuildableReference> + </TestableReference> + </Testables> + </TestAction> + <LaunchAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + debugServiceExtension = "internal" + allowLocationSimulation = "YES"> + </LaunchAction> + <ProfileAction + buildConfiguration = "Release" + shouldUseLaunchSchemeArgsEnv = "YES" + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + debugDocumentVersioning = "YES"> + <MacroExpansion> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "MyContactFeature" + BuildableName = "MyContactFeature" + BlueprintName = "MyContactFeature" + ReferencedContainer = "container:"> + </BuildableReference> + </MacroExpansion> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme> diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift index c5bcba6a..be093741 100644 --- a/Examples/xx-messenger/Package.swift +++ b/Examples/xx-messenger/Package.swift @@ -26,6 +26,7 @@ let package = Package( .library(name: "ContactFeature", targets: ["ContactFeature"]), .library(name: "ContactsFeature", targets: ["ContactsFeature"]), .library(name: "HomeFeature", targets: ["HomeFeature"]), + .library(name: "MyContactFeature", targets: ["MyContactFeature"]), .library(name: "RegisterFeature", targets: ["RegisterFeature"]), .library(name: "RestoreFeature", targets: ["RestoreFeature"]), .library(name: "SendRequestFeature", targets: ["SendRequestFeature"]), @@ -216,6 +217,24 @@ let package = Package( ], swiftSettings: swiftSettings ), + .target( + name: "MyContactFeature", + dependencies: [ + .target(name: "AppCore"), + .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), + .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"), + .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"), + .product(name: "XXModels", package: "client-ios-db"), + ], + swiftSettings: swiftSettings + ), + .testTarget( + name: "MyContactFeatureTests", + dependencies: [ + .target(name: "MyContactFeature"), + ], + swiftSettings: swiftSettings + ), .target( name: "RegisterFeature", dependencies: [ diff --git a/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme b/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme index 65400dff..aa97ea7d 100644 --- a/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme +++ b/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme @@ -109,6 +109,16 @@ ReferencedContainer = "container:.."> </BuildableReference> </TestableReference> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "MyContactFeatureTests" + BuildableName = "MyContactFeatureTests" + BlueprintName = "MyContactFeatureTests" + ReferencedContainer = "container:.."> + </BuildableReference> + </TestableReference> <TestableReference skipped = "NO"> <BuildableReference diff --git a/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift b/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift new file mode 100644 index 00000000..8d335305 --- /dev/null +++ b/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift @@ -0,0 +1,28 @@ +import ComposableArchitecture +import XCTestDynamicOverlay + +public struct MyContactState: Equatable { + public init() {} +} + +public enum MyContactAction: Equatable { + case start +} + +public struct MyContactEnvironment { + public init() {} +} + +#if DEBUG +extension MyContactEnvironment { + public static let unimplemented = MyContactEnvironment() +} +#endif + +public let myContactReducer = Reducer<MyContactState, MyContactAction, MyContactEnvironment> +{ state, action, env in + switch action { + case .start: + return .none + } +} diff --git a/Examples/xx-messenger/Sources/MyContactFeature/MyContactView.swift b/Examples/xx-messenger/Sources/MyContactFeature/MyContactView.swift new file mode 100644 index 00000000..d5092e4b --- /dev/null +++ b/Examples/xx-messenger/Sources/MyContactFeature/MyContactView.swift @@ -0,0 +1,35 @@ +import ComposableArchitecture +import SwiftUI + +public struct MyContactView: View { + public init(store: Store<MyContactState, MyContactAction>) { + self.store = store + } + + let store: Store<MyContactState, MyContactAction> + + struct ViewState: Equatable { + init(state: MyContactState) {} + } + + public var body: some View { + WithViewStore(store, observe: ViewState.init) { viewStore in + Form { + + } + .navigationTitle("My Contact") + } + } +} + +#if DEBUG +public struct MyContactView_Previews: PreviewProvider { + public static var previews: some View { + MyContactView(store: Store( + initialState: MyContactState(), + reducer: .empty, + environment: () + )) + } +} +#endif diff --git a/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift b/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift new file mode 100644 index 00000000..6807dcd6 --- /dev/null +++ b/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift @@ -0,0 +1,15 @@ +import ComposableArchitecture +import XCTest +@testable import MyContactFeature + +final class MyContactFeatureTests: XCTestCase { + func testStart() { + let store = TestStore( + initialState: MyContactState(), + reducer: myContactReducer, + environment: .unimplemented + ) + + store.send(.start) + } +} -- GitLab From f893860df269d18c8148af7238bd3c83a0115999 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 20 Sep 2022 09:29:33 +0200 Subject: [PATCH 02/22] Present MyContact from Contacts --- Examples/xx-messenger/Package.swift | 2 ++ .../AppFeature/AppEnvironment+Live.swift | 6 +++- .../ContactsFeature/ContactsFeature.swift | 34 ++++++++++++++++--- .../ContactsFeature/ContactsView.swift | 29 ++++++++++++---- .../ContactsFeatureTests.swift | 27 +++++++++++++++ 5 files changed, 87 insertions(+), 11 deletions(-) diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift index be093741..c184020f 100644 --- a/Examples/xx-messenger/Package.swift +++ b/Examples/xx-messenger/Package.swift @@ -84,6 +84,7 @@ let package = Package( .target(name: "ContactFeature"), .target(name: "ContactsFeature"), .target(name: "HomeFeature"), + .target(name: "MyContactFeature"), .target(name: "RegisterFeature"), .target(name: "RestoreFeature"), .target(name: "SendRequestFeature"), @@ -182,6 +183,7 @@ let package = Package( dependencies: [ .target(name: "AppCore"), .target(name: "ContactFeature"), + .target(name: "MyContactFeature"), .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), .product(name: "ComposablePresentation", package: "swift-composable-presentation"), .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"), diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift index 611123e0..b93b0359 100644 --- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift +++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift @@ -6,6 +6,7 @@ import ContactFeature import ContactsFeature import Foundation import HomeFeature +import MyContactFeature import RegisterFeature import RestoreFeature import SendRequestFeature @@ -122,7 +123,10 @@ extension AppEnvironment { db: dbManager.getDB, mainQueue: mainQueue, bgQueue: bgQueue, - contact: { contactEnvironment } + contact: { contactEnvironment }, + myContact: { + MyContactEnvironment() + } ) }, userSearch: { diff --git a/Examples/xx-messenger/Sources/ContactsFeature/ContactsFeature.swift b/Examples/xx-messenger/Sources/ContactsFeature/ContactsFeature.swift index 1ded89de..680a231e 100644 --- a/Examples/xx-messenger/Sources/ContactsFeature/ContactsFeature.swift +++ b/Examples/xx-messenger/Sources/ContactsFeature/ContactsFeature.swift @@ -3,6 +3,7 @@ import ComposableArchitecture import ComposablePresentation import ContactFeature import Foundation +import MyContactFeature import XCTestDynamicOverlay import XXClient import XXMessengerClient @@ -12,16 +13,19 @@ public struct ContactsState: Equatable { public init( myId: Data? = nil, contacts: IdentifiedArrayOf<XXModels.Contact> = [], - contact: ContactState? = nil + 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 { @@ -30,6 +34,9 @@ public enum ContactsAction: Equatable { case contactSelected(XXModels.Contact) case contactDismissed case contact(ContactAction) + case myContactSelected + case myContactDismissed + case myContact(MyContactAction) } public struct ContactsEnvironment { @@ -38,13 +45,15 @@ public struct ContactsEnvironment { db: DBManagerGetDB, mainQueue: AnySchedulerOf<DispatchQueue>, bgQueue: AnySchedulerOf<DispatchQueue>, - contact: @escaping () -> ContactEnvironment + 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 @@ -52,6 +61,7 @@ public struct ContactsEnvironment { public var mainQueue: AnySchedulerOf<DispatchQueue> public var bgQueue: AnySchedulerOf<DispatchQueue> public var contact: () -> ContactEnvironment + public var myContact: () -> MyContactEnvironment } #if DEBUG @@ -61,7 +71,8 @@ extension ContactsEnvironment { db: .unimplemented, mainQueue: .unimplemented, bgQueue: .unimplemented, - contact: { .unimplemented } + contact: { .unimplemented }, + myContact: { .unimplemented } ) } #endif @@ -96,7 +107,15 @@ public let contactsReducer = Reducer<ContactsState, ContactsAction, ContactsEnvi state.contact = nil return .none - case .contact(_): + case .myContactSelected: + state.myContact = MyContactState() + return .none + + case .myContactDismissed: + state.myContact = nil + return .none + + case .contact(_), .myContact(_): return .none } } @@ -107,3 +126,10 @@ public let contactsReducer = Reducer<ContactsState, ContactsAction, ContactsEnvi 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 ce811f9d..e09725d9 100644 --- a/Examples/xx-messenger/Sources/ContactsFeature/ContactsView.swift +++ b/Examples/xx-messenger/Sources/ContactsFeature/ContactsView.swift @@ -2,6 +2,7 @@ import AppCore import ComposableArchitecture import ComposablePresentation import ContactFeature +import MyContactFeature import SwiftUI import XXModels @@ -28,13 +29,21 @@ public struct ContactsView: View { ForEach(viewStore.contacts) { contact in if contact.id == viewStore.myId { Section { - VStack(alignment: .leading, spacing: 8) { - Label(contact.username ?? "", systemImage: "person") - Label(contact.email ?? "", systemImage: "envelope") - Label(contact.phone ?? "", systemImage: "phone") + Button { + viewStore.send(.myContactSelected) + } label: { + HStack { + VStack(alignment: .leading, spacing: 8) { + Label(contact.username ?? "", systemImage: "person") + Label(contact.email ?? "", systemImage: "envelope") + Label(contact.phone ?? "", systemImage: "phone") + } + .font(.callout) + .tint(Color.primary) + Spacer() + Image(systemName: "chevron.forward") + } } - .font(.callout) - .tint(Color.primary) } header: { Text("My contact") } @@ -70,6 +79,14 @@ public struct ContactsView: View { onDeactivate: { viewStore.send(.contactDismissed) }, destination: ContactView.init(store:) )) + .background(NavigationLinkWithStore( + store.scope( + state: \.myContact, + action: ContactsAction.myContact + ), + onDeactivate: { viewStore.send(.myContactDismissed) }, + destination: MyContactView.init(store:) + )) } } } diff --git a/Examples/xx-messenger/Tests/ContactsFeatureTests/ContactsFeatureTests.swift b/Examples/xx-messenger/Tests/ContactsFeatureTests/ContactsFeatureTests.swift index a0c0291e..ffd3eafe 100644 --- a/Examples/xx-messenger/Tests/ContactsFeatureTests/ContactsFeatureTests.swift +++ b/Examples/xx-messenger/Tests/ContactsFeatureTests/ContactsFeatureTests.swift @@ -2,6 +2,7 @@ import Combine import ComposableArchitecture import ContactFeature import CustomDump +import MyContactFeature import XCTest import XXClient import XXMessengerClient @@ -94,4 +95,30 @@ final class ContactsFeatureTests: XCTestCase { $0.contact = nil } } + + func testSelectMyContact() { + let store = TestStore( + initialState: ContactsState(), + reducer: contactsReducer, + environment: .unimplemented + ) + + store.send(.myContactSelected) { + $0.myContact = MyContactState() + } + } + + func testDismissMyContact() { + let store = TestStore( + initialState: ContactsState( + myContact: MyContactState() + ), + reducer: contactsReducer, + environment: .unimplemented + ) + + store.send(.myContactDismissed) { + $0.myContact = nil + } + } } -- GitLab From 6d0f1a18332868b199643ea117a0814842ef2a1f Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 20 Sep 2022 09:30:20 +0200 Subject: [PATCH 03/22] Update example app package dependencies --- .../xcshareddata/swiftpm/Package.resolved | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Examples/xx-messenger/XXMessenger.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Examples/xx-messenger/XXMessenger.xcworkspace/xcshareddata/swiftpm/Package.resolved index 45de0ce8..42a8b2ba 100644 --- a/Examples/xx-messenger/XXMessenger.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Examples/xx-messenger/XXMessenger.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -59,8 +59,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-composable-architecture.git", "state" : { - "revision" : "cbe013b42b3c368957f8f882c960b93845e1589d", - "version" : "0.40.1" + "revision" : "9ea8c763061287052a68d5e6723fed45e898b7d9", + "version" : "0.40.2" } }, { @@ -75,10 +75,10 @@ { "identity" : "swift-custom-dump", "kind" : "remoteSourceControl", - "location" : "https://github.com/pointfreeco/swift-custom-dump", + "location" : "https://github.com/pointfreeco/swift-custom-dump.git", "state" : { - "revision" : "21ec1d717c07cea5a026979cb0471dd95c7087e7", - "version" : "0.5.0" + "revision" : "c9b6b940d95c0a925c63f6858943415714d8a981", + "version" : "0.5.2" } }, { @@ -95,8 +95,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay.git", "state" : { - "revision" : "38bc9242e4388b80bd23ddfdf3071428859e3260", - "version" : "0.4.0" + "revision" : "30314f1ece684dd60679d598a9b89107557b67d9", + "version" : "0.4.1" } } ], -- GitLab From 97727f6fc811025a13f25fe94378981fe016c835 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 20 Sep 2022 10:22:08 +0200 Subject: [PATCH 04/22] Fetch my contact --- .../AppFeature/AppEnvironment+Live.swift | 7 +- .../MyContactFeature/MyContactFeature.swift | 95 ++++++++++++++- .../MyContactFeature/MyContactView.swift | 111 +++++++++++++++++- .../MyContactFeatureTests.swift | 107 +++++++++++++++++ 4 files changed, 309 insertions(+), 11 deletions(-) diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift index b93b0359..d824d6d4 100644 --- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift +++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift @@ -125,7 +125,12 @@ extension AppEnvironment { bgQueue: bgQueue, contact: { contactEnvironment }, myContact: { - MyContactEnvironment() + MyContactEnvironment( + messenger: messenger, + db: dbManager.getDB, + mainQueue: mainQueue, + bgQueue: bgQueue + ) } ) }, diff --git a/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift b/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift index 8d335305..c2cfe972 100644 --- a/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift +++ b/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift @@ -1,28 +1,115 @@ +import AppCore import ComposableArchitecture +import Foundation import XCTestDynamicOverlay +import XXClient +import XXMessengerClient +import XXModels public struct MyContactState: Equatable { - public init() {} + public enum Field: String, Hashable { + case email + case phone + } + + public init( + contact: XXModels.Contact? = nil, + focusedField: Field? = nil, + email: String = "", + phone: String = "" + ) { + self.contact = contact + self.focusedField = focusedField + self.email = email + self.phone = phone + } + + public var contact: XXModels.Contact? + @BindableState public var focusedField: Field? + @BindableState public var email: String + @BindableState public var phone: String } -public enum MyContactAction: Equatable { +public enum MyContactAction: Equatable, BindableAction { case start + case contactFetched(XXModels.Contact?) + case registerEmailTapped + case unregisterEmailTapped + case registerPhoneTapped + case unregisterPhoneTapped + case loadFactsTapped + case binding(BindingAction<MyContactState>) } public struct MyContactEnvironment { - public init() {} + public init( + messenger: Messenger, + db: DBManagerGetDB, + mainQueue: AnySchedulerOf<DispatchQueue>, + bgQueue: AnySchedulerOf<DispatchQueue> + ) { + self.messenger = messenger + self.db = db + self.mainQueue = mainQueue + self.bgQueue = bgQueue + } + + public var messenger: Messenger + public var db: DBManagerGetDB + public var mainQueue: AnySchedulerOf<DispatchQueue> + public var bgQueue: AnySchedulerOf<DispatchQueue> } #if DEBUG extension MyContactEnvironment { - public static let unimplemented = MyContactEnvironment() + public static let unimplemented = MyContactEnvironment( + messenger: .unimplemented, + db: .unimplemented, + mainQueue: .unimplemented, + bgQueue: .unimplemented + ) } #endif public let myContactReducer = Reducer<MyContactState, MyContactAction, MyContactEnvironment> { state, action, env in + enum DBFetchEffectID {} + switch action { case .start: + return Effect + .catching { try env.messenger.e2e.tryGet().getContact().getId() } + .tryMap { try env.db().fetchContactsPublisher(.init(id: [$0])) } + .flatMap { $0 } + .assertNoFailure() + .map(\.first) + .map(MyContactAction.contactFetched) + .subscribe(on: env.bgQueue) + .receive(on: env.mainQueue) + .eraseToEffect() + .cancellable(id: DBFetchEffectID.self, cancelInFlight: true) + + case .contactFetched(let contact): + state.contact = contact + return .none + + case .registerEmailTapped: + return .none + + case .unregisterEmailTapped: + return .none + + case .registerPhoneTapped: + return .none + + case .unregisterPhoneTapped: + return .none + + case .loadFactsTapped: + return .none + + case .binding(_): return .none } } +.binding() diff --git a/Examples/xx-messenger/Sources/MyContactFeature/MyContactView.swift b/Examples/xx-messenger/Sources/MyContactFeature/MyContactView.swift index d5092e4b..0887f682 100644 --- a/Examples/xx-messenger/Sources/MyContactFeature/MyContactView.swift +++ b/Examples/xx-messenger/Sources/MyContactFeature/MyContactView.swift @@ -1,5 +1,6 @@ import ComposableArchitecture import SwiftUI +import XXModels public struct MyContactView: View { public init(store: Store<MyContactState, MyContactAction>) { @@ -7,17 +8,113 @@ public struct MyContactView: View { } let store: Store<MyContactState, MyContactAction> + @FocusState var focusedField: MyContactState.Field? struct ViewState: Equatable { - init(state: MyContactState) {} + init(state: MyContactState) { + contact = state.contact + focusedField = state.focusedField + email = state.email + phone = state.phone + } + + var contact: XXModels.Contact? + var focusedField: MyContactState.Field? + var email: String + var phone: String } public var body: some View { WithViewStore(store, observe: ViewState.init) { viewStore in Form { + Section { + Text(viewStore.contact?.username ?? "") + } header: { + Label("Username", systemImage: "person") + } + + Section { + if let contact = viewStore.contact { + if let email = contact.email { + Text(email) + Button(role: .destructive) { + viewStore.send(.unregisterEmailTapped) + } label: { + Text("Unregister") + } + } else { + TextField( + text: viewStore.binding( + get: \.email, + send: { MyContactAction.set(\.$email, $0) } + ), + prompt: Text("Enter email"), + label: { Text("Email") } + ) + .focused($focusedField, equals: .email) + .textInputAutocapitalization(.never) + .disableAutocorrection(true) + Button { + viewStore.send(.registerEmailTapped) + } label: { + Text("Register") + } + } + } else { + Text("") + } + } header: { + Label("Email", systemImage: "envelope") + } + Section { + if let contact = viewStore.contact { + if let phone = contact.phone { + Text(phone) + Button(role: .destructive) { + viewStore.send(.unregisterPhoneTapped) + } label: { + Text("Unregister") + } + } else { + TextField( + text: viewStore.binding( + get: \.phone, + send: { MyContactAction.set(\.$phone, $0) } + ), + prompt: Text("Enter phone"), + label: { Text("Phone") } + ) + .focused($focusedField, equals: .phone) + .textInputAutocapitalization(.never) + .disableAutocorrection(true) + Button { + viewStore.send(.registerPhoneTapped) + } label: { + Text("Register") + } + } + } else { + Text("") + } + } header: { + Label("Phone", systemImage: "phone") + } + + Section { + Button { + viewStore.send(.loadFactsTapped) + } label: { + Text("Load facts from client") + } + } header: { + Text("Actions") + } } .navigationTitle("My Contact") + .task { viewStore.send(.start) } + .onChange(of: viewStore.focusedField) { focusedField = $0 } + .onChange(of: focusedField) { viewStore.send(.set(\.$focusedField, $0)) } } } } @@ -25,11 +122,13 @@ public struct MyContactView: View { #if DEBUG public struct MyContactView_Previews: PreviewProvider { public static var previews: some View { - MyContactView(store: Store( - initialState: MyContactState(), - reducer: .empty, - environment: () - )) + NavigationView { + MyContactView(store: Store( + initialState: MyContactState(), + reducer: .empty, + environment: () + )) + } } } #endif diff --git a/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift b/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift index 6807dcd6..eba876a9 100644 --- a/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift +++ b/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift @@ -1,15 +1,122 @@ +import Combine import ComposableArchitecture +import CustomDump import XCTest +import XXClient +import XXMessengerClient +import XXModels @testable import MyContactFeature final class MyContactFeatureTests: XCTestCase { func testStart() { + let contactId = "contact-id".data(using: .utf8)! + let store = TestStore( initialState: MyContactState(), reducer: myContactReducer, environment: .unimplemented ) + var dbDidFetchContacts: [XXModels.Contact.Query] = [] + let dbContactsPublisher = PassthroughSubject<[XXModels.Contact], Error>() + + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.messenger.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getContact.run = { + var contact: XXClient.Contact = .unimplemented(Data()) + contact.getIdFromContact.run = { _ in contactId } + return contact + } + return e2e + } + store.environment.db.run = { + var db: Database = .failing + db.fetchContactsPublisher.run = { query in + dbDidFetchContacts.append(query) + return dbContactsPublisher.eraseToAnyPublisher() + } + return db + } + store.send(.start) + + XCTAssertNoDifference(dbDidFetchContacts, [.init(id: [contactId])]) + + dbContactsPublisher.send([]) + + store.receive(.contactFetched(nil)) + + let contact = XXModels.Contact(id: contactId) + dbContactsPublisher.send([contact]) + + store.receive(.contactFetched(contact)) { + $0.contact = contact + } + + dbContactsPublisher.send(completion: .finished) + } + + func testRegisterEmail() { + let email = "test@email.com" + + let store = TestStore( + initialState: MyContactState(), + reducer: myContactReducer, + environment: .unimplemented + ) + + store.send(.set(\.$email, email)) { + $0.email = email + } + + store.send(.registerEmailTapped) + } + + func testUnregisterEmail() { + let store = TestStore( + initialState: MyContactState(), + reducer: myContactReducer, + environment: .unimplemented + ) + + store.send(.unregisterEmailTapped) + } + + func testRegisterPhone() { + let phone = "123456789" + + let store = TestStore( + initialState: MyContactState(), + reducer: myContactReducer, + environment: .unimplemented + ) + + store.send(.set(\.$phone, phone)) { + $0.phone = phone + } + + store.send(.registerPhoneTapped) + } + + func testUnregisterPhone() { + let store = TestStore( + initialState: MyContactState(), + reducer: myContactReducer, + environment: .unimplemented + ) + + store.send(.unregisterPhoneTapped) + } + + func testLoadFactsFromClient() { + let store = TestStore( + initialState: MyContactState(), + reducer: myContactReducer, + environment: .unimplemented + ) + + store.send(.loadFactsTapped) } } -- GitLab From 2bd54573482aed14c8bd9623d17baec22ffae406 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 20 Sep 2022 11:36:52 +0200 Subject: [PATCH 05/22] Add error alert to MyContactFeature --- .../Sources/MyContactFeature/Alerts.swift | 11 +++++++++++ .../MyContactFeature/MyContactFeature.swift | 15 ++++++++++++++- .../MyContactFeature/MyContactView.swift | 1 + .../MyContactFeatureTests.swift | 18 ++++++++++++++++++ 4 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 Examples/xx-messenger/Sources/MyContactFeature/Alerts.swift diff --git a/Examples/xx-messenger/Sources/MyContactFeature/Alerts.swift b/Examples/xx-messenger/Sources/MyContactFeature/Alerts.swift new file mode 100644 index 00000000..321139ae --- /dev/null +++ b/Examples/xx-messenger/Sources/MyContactFeature/Alerts.swift @@ -0,0 +1,11 @@ +import ComposableArchitecture + +extension AlertState { + public static func error(_ message: String) -> AlertState<MyContactAction> { + AlertState<MyContactAction>( + title: TextState("Error"), + message: TextState(message), + buttons: [] + ) + } +} diff --git a/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift b/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift index c2cfe972..9669cac5 100644 --- a/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift +++ b/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift @@ -16,18 +16,21 @@ public struct MyContactState: Equatable { contact: XXModels.Contact? = nil, focusedField: Field? = nil, email: String = "", - phone: String = "" + phone: String = "", + alert: AlertState<MyContactAction>? = nil ) { self.contact = contact self.focusedField = focusedField self.email = email self.phone = phone + self.alert = alert } public var contact: XXModels.Contact? @BindableState public var focusedField: Field? @BindableState public var email: String @BindableState public var phone: String + public var alert: AlertState<MyContactAction>? } public enum MyContactAction: Equatable, BindableAction { @@ -38,6 +41,8 @@ public enum MyContactAction: Equatable, BindableAction { case registerPhoneTapped case unregisterPhoneTapped case loadFactsTapped + case didFail(String) + case alertDismissed case binding(BindingAction<MyContactState>) } @@ -108,6 +113,14 @@ public let myContactReducer = Reducer<MyContactState, MyContactAction, MyContact case .loadFactsTapped: return .none + case .didFail(let failure): + state.alert = .error(failure) + return .none + + case .alertDismissed: + state.alert = nil + return .none + case .binding(_): return .none } diff --git a/Examples/xx-messenger/Sources/MyContactFeature/MyContactView.swift b/Examples/xx-messenger/Sources/MyContactFeature/MyContactView.swift index 0887f682..6e2cbc8f 100644 --- a/Examples/xx-messenger/Sources/MyContactFeature/MyContactView.swift +++ b/Examples/xx-messenger/Sources/MyContactFeature/MyContactView.swift @@ -115,6 +115,7 @@ public struct MyContactView: View { .task { viewStore.send(.start) } .onChange(of: viewStore.focusedField) { focusedField = $0 } .onChange(of: focusedField) { viewStore.send(.set(\.$focusedField, $0)) } + .alert(store.scope(state: \.alert), dismiss: .alertDismissed) } } } diff --git a/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift b/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift index eba876a9..169450c8 100644 --- a/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift +++ b/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift @@ -119,4 +119,22 @@ final class MyContactFeatureTests: XCTestCase { store.send(.loadFactsTapped) } + + func testErrorAlert() { + let store = TestStore( + initialState: MyContactState(), + reducer: myContactReducer, + environment: .unimplemented + ) + + let failure = "Something went wrong" + + store.send(.didFail(failure)) { + $0.alert = .error(failure) + } + + store.send(.alertDismissed) { + $0.alert = nil + } + } } -- GitLab From 1f6e477c8fe5e338812a1d5bee7a7cbc6a017919 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 20 Sep 2022 12:26:41 +0200 Subject: [PATCH 06/22] Implement loading my facts from the client --- .../MyContactFeature/MyContactFeature.swift | 20 +++++++- .../MyContactFeatureTests.swift | 48 +++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift b/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift index 9669cac5..818807c8 100644 --- a/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift +++ b/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift @@ -1,4 +1,5 @@ import AppCore +import Combine import ComposableArchitecture import Foundation import XCTestDynamicOverlay @@ -111,7 +112,24 @@ public let myContactReducer = Reducer<MyContactState, MyContactAction, MyContact return .none case .loadFactsTapped: - return .none + return Effect.run { subscriber in + do { + let contactId = try env.messenger.e2e.tryGet().getContact().getId() + if var dbContact = try env.db().fetchContacts(.init(id: [contactId])).first { + let facts = try env.messenger.ud.tryGet().getFacts() + dbContact.email = facts.get(.email)?.value + dbContact.phone = facts.get(.phone)?.value + try env.db().saveContact(dbContact) + } + } catch { + subscriber.send(.didFail(error.localizedDescription)) + } + subscriber.send(completion: .finished) + return AnyCancellable {} + } + .subscribe(on: env.bgQueue) + .receive(on: env.mainQueue) + .eraseToEffect() case .didFail(let failure): state.alert = .error(failure) diff --git a/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift b/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift index 169450c8..62e7fcd9 100644 --- a/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift +++ b/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift @@ -111,13 +111,61 @@ final class MyContactFeatureTests: XCTestCase { } func testLoadFactsFromClient() { + let contactId = "contact-id".data(using: .utf8)! + let dbContact = XXModels.Contact(id: contactId) + let email = "test@email.com" + let phone = "123456789" + + var didFetchContacts: [XXModels.Contact.Query] = [] + var didSaveContact: [XXModels.Contact] = [] + let store = TestStore( initialState: MyContactState(), reducer: myContactReducer, environment: .unimplemented ) + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.messenger.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getContact.run = { + var contact: XXClient.Contact = .unimplemented(Data()) + contact.getIdFromContact.run = { _ in contactId } + return contact + } + return e2e + } + store.environment.messenger.ud.get = { + var ud: UserDiscovery = .unimplemented + ud.getFacts.run = { + [ + Fact(type: .email, value: email), + Fact(type: .phone, value: phone), + ] + } + return ud + } + store.environment.db.run = { + var db: Database = .failing + db.fetchContacts.run = { query in + didFetchContacts.append(query) + return [dbContact] + } + db.saveContact.run = { contact in + didSaveContact.append(contact) + return contact + } + return db + } + store.send(.loadFactsTapped) + + XCTAssertNoDifference(didFetchContacts, [.init(id: [contactId])]) + var expectedSavedContact = dbContact + expectedSavedContact.email = email + expectedSavedContact.phone = phone + XCTAssertNoDifference(didSaveContact, [expectedSavedContact]) } func testErrorAlert() { -- GitLab From 1d201c28badc516a14ced0297a0eb98c88c07377 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 20 Sep 2022 12:32:00 +0200 Subject: [PATCH 07/22] Add facts loading indicator --- .../Sources/MyContactFeature/MyContactFeature.swift | 5 +++++ .../Sources/MyContactFeature/MyContactView.swift | 11 ++++++++++- .../MyContactFeatureTests/MyContactFeatureTests.swift | 8 +++++++- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift b/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift index 818807c8..c08ed6b7 100644 --- a/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift +++ b/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift @@ -18,12 +18,14 @@ public struct MyContactState: Equatable { focusedField: Field? = nil, email: String = "", phone: String = "", + isLoadingFacts: Bool = false, alert: AlertState<MyContactAction>? = nil ) { self.contact = contact self.focusedField = focusedField self.email = email self.phone = phone + self.isLoadingFacts = isLoadingFacts self.alert = alert } @@ -31,6 +33,7 @@ public struct MyContactState: Equatable { @BindableState public var focusedField: Field? @BindableState public var email: String @BindableState public var phone: String + @BindableState public var isLoadingFacts: Bool public var alert: AlertState<MyContactAction>? } @@ -112,6 +115,7 @@ public let myContactReducer = Reducer<MyContactState, MyContactAction, MyContact return .none case .loadFactsTapped: + state.isLoadingFacts = true return Effect.run { subscriber in do { let contactId = try env.messenger.e2e.tryGet().getContact().getId() @@ -124,6 +128,7 @@ public let myContactReducer = Reducer<MyContactState, MyContactAction, MyContact } catch { subscriber.send(.didFail(error.localizedDescription)) } + subscriber.send(.set(\.$isLoadingFacts, false)) subscriber.send(completion: .finished) return AnyCancellable {} } diff --git a/Examples/xx-messenger/Sources/MyContactFeature/MyContactView.swift b/Examples/xx-messenger/Sources/MyContactFeature/MyContactView.swift index 6e2cbc8f..7eb8e7aa 100644 --- a/Examples/xx-messenger/Sources/MyContactFeature/MyContactView.swift +++ b/Examples/xx-messenger/Sources/MyContactFeature/MyContactView.swift @@ -16,12 +16,14 @@ public struct MyContactView: View { focusedField = state.focusedField email = state.email phone = state.phone + isLoadingFacts = state.isLoadingFacts } var contact: XXModels.Contact? var focusedField: MyContactState.Field? var email: String var phone: String + var isLoadingFacts: Bool } public var body: some View { @@ -105,8 +107,15 @@ public struct MyContactView: View { Button { viewStore.send(.loadFactsTapped) } label: { - Text("Load facts from client") + HStack { + Text("Reload facts") + Spacer() + if viewStore.isLoadingFacts { + ProgressView() + } + } } + .disabled(viewStore.isLoadingFacts) } header: { Text("Actions") } diff --git a/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift b/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift index 62e7fcd9..4193e935 100644 --- a/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift +++ b/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift @@ -159,13 +159,19 @@ final class MyContactFeatureTests: XCTestCase { return db } - store.send(.loadFactsTapped) + store.send(.loadFactsTapped) { + $0.isLoadingFacts = true + } XCTAssertNoDifference(didFetchContacts, [.init(id: [contactId])]) var expectedSavedContact = dbContact expectedSavedContact.email = email expectedSavedContact.phone = phone XCTAssertNoDifference(didSaveContact, [expectedSavedContact]) + + store.receive(.set(\.$isLoadingFacts, false)) { + $0.isLoadingFacts = false + } } func testErrorAlert() { -- GitLab From 4ad76dc368eb7623fd4a9421e0a204f6df76df1c Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 20 Sep 2022 12:34:30 +0200 Subject: [PATCH 08/22] Update tests --- .../MyContactFeatureTests.swift | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift b/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift index 4193e935..9c30f7df 100644 --- a/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift +++ b/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift @@ -174,6 +174,41 @@ final class MyContactFeatureTests: XCTestCase { } } + func testLoadFactsFromClientFailure() { + struct Failure: Error {} + let failure = Failure() + + let store = TestStore( + initialState: MyContactState(), + reducer: myContactReducer, + environment: .unimplemented + ) + + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.messenger.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getContact.run = { + var contact: XXClient.Contact = .unimplemented(Data()) + contact.getIdFromContact.run = { _ in throw failure } + return contact + } + return e2e + } + + store.send(.loadFactsTapped) { + $0.isLoadingFacts = true + } + + store.receive(.didFail(failure.localizedDescription)) { + $0.alert = .error(failure.localizedDescription) + } + + store.receive(.set(\.$isLoadingFacts, false)) { + $0.isLoadingFacts = false + } + } + func testErrorAlert() { let store = TestStore( initialState: MyContactState(), -- GitLab From bfad972facde6bb7fa44514b4a2a3722ceedacaf Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 20 Sep 2022 13:28:25 +0200 Subject: [PATCH 09/22] Implement facts registration and confirmation --- .../MyContactFeature/MyContactFeature.swift | 120 +++++- .../MyContactFeature/MyContactView.swift | 102 ++++- .../MyContactFeatureTests.swift | 370 +++++++++++++++++- 3 files changed, 579 insertions(+), 13 deletions(-) diff --git a/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift b/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift index c08ed6b7..99486c8a 100644 --- a/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift +++ b/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift @@ -10,21 +10,39 @@ import XXModels public struct MyContactState: Equatable { public enum Field: String, Hashable { case email + case emailCode case phone + case phoneCode } public init( contact: XXModels.Contact? = nil, focusedField: Field? = nil, email: String = "", + emailConfirmationID: String? = nil, + emailConfirmationCode: String = "", + isRegisteringEmail: Bool = false, + isConfirmingEmail: Bool = false, phone: String = "", + phoneConfirmationID: String? = nil, + phoneConfirmationCode: String = "", + isRegisteringPhone: Bool = false, + isConfirmingPhone: Bool = false, isLoadingFacts: Bool = false, alert: AlertState<MyContactAction>? = nil ) { self.contact = contact self.focusedField = focusedField self.email = email + self.emailConfirmationID = emailConfirmationID + self.emailConfirmationCode = emailConfirmationCode + self.isRegisteringEmail = isRegisteringEmail + self.isConfirmingEmail = isConfirmingEmail self.phone = phone + self.phoneConfirmationID = phoneConfirmationID + self.phoneConfirmationCode = phoneConfirmationCode + self.isRegisteringPhone = isRegisteringPhone + self.isConfirmingPhone = isConfirmingPhone self.isLoadingFacts = isLoadingFacts self.alert = alert } @@ -32,7 +50,15 @@ public struct MyContactState: Equatable { public var contact: XXModels.Contact? @BindableState public var focusedField: Field? @BindableState public var email: String + @BindableState public var emailConfirmationID: String? + @BindableState public var emailConfirmationCode: String + @BindableState public var isRegisteringEmail: Bool + @BindableState public var isConfirmingEmail: Bool @BindableState public var phone: String + @BindableState public var phoneConfirmationID: String? + @BindableState public var phoneConfirmationCode: String + @BindableState public var isRegisteringPhone: Bool + @BindableState public var isConfirmingPhone: Bool @BindableState public var isLoadingFacts: Bool public var alert: AlertState<MyContactAction>? } @@ -41,8 +67,10 @@ public enum MyContactAction: Equatable, BindableAction { case start case contactFetched(XXModels.Contact?) case registerEmailTapped + case confirmEmailTapped case unregisterEmailTapped case registerPhoneTapped + case confirmPhoneTapped case unregisterPhoneTapped case loadFactsTapped case didFail(String) @@ -103,13 +131,101 @@ public let myContactReducer = Reducer<MyContactState, MyContactAction, MyContact return .none case .registerEmailTapped: - return .none + state.focusedField = nil + state.isRegisteringEmail = true + return Effect.run { [state] subscriber in + do { + let ud = try env.messenger.ud.tryGet() + let fact = Fact(type: .email, value: state.email) + let confirmationID = try ud.sendRegisterFact(fact) + subscriber.send(.set(\.$emailConfirmationID, confirmationID)) + } catch { + subscriber.send(.didFail(error.localizedDescription)) + } + subscriber.send(.set(\.$isRegisteringEmail, false)) + subscriber.send(completion: .finished) + return AnyCancellable {} + } + .subscribe(on: env.bgQueue) + .receive(on: env.mainQueue) + .eraseToEffect() + + case .confirmEmailTapped: + guard let confirmationID = state.emailConfirmationID else { return .none } + state.focusedField = nil + state.isConfirmingEmail = true + return Effect.run { [state] subscriber in + do { + let ud = try env.messenger.ud.tryGet() + try ud.confirmFact(confirmationId: confirmationID, code: state.emailConfirmationCode) + let contactId = try env.messenger.e2e.tryGet().getContact().getId() + if var dbContact = try env.db().fetchContacts(.init(id: [contactId])).first { + dbContact.email = state.email + try env.db().saveContact(dbContact) + } + subscriber.send(.set(\.$email, "")) + subscriber.send(.set(\.$emailConfirmationID, nil)) + subscriber.send(.set(\.$emailConfirmationCode, "")) + } catch { + subscriber.send(.didFail(error.localizedDescription)) + } + subscriber.send(.set(\.$isConfirmingEmail, false)) + subscriber.send(completion: .finished) + return AnyCancellable {} + } + .subscribe(on: env.bgQueue) + .receive(on: env.mainQueue) + .eraseToEffect() case .unregisterEmailTapped: return .none case .registerPhoneTapped: - return .none + state.focusedField = nil + state.isRegisteringPhone = true + return Effect.run { [state] subscriber in + do { + let ud = try env.messenger.ud.tryGet() + let fact = Fact(type: .phone, value: state.phone) + let confirmationID = try ud.sendRegisterFact(fact) + subscriber.send(.set(\.$phoneConfirmationID, confirmationID)) + } catch { + subscriber.send(.didFail(error.localizedDescription)) + } + subscriber.send(.set(\.$isRegisteringPhone, false)) + subscriber.send(completion: .finished) + return AnyCancellable {} + } + .subscribe(on: env.bgQueue) + .receive(on: env.mainQueue) + .eraseToEffect() + + case .confirmPhoneTapped: + guard let confirmationID = state.phoneConfirmationID else { return .none } + state.focusedField = nil + state.isConfirmingPhone = true + return Effect.run { [state] subscriber in + do { + let ud = try env.messenger.ud.tryGet() + try ud.confirmFact(confirmationId: confirmationID, code: state.phoneConfirmationCode) + let contactId = try env.messenger.e2e.tryGet().getContact().getId() + if var dbContact = try env.db().fetchContacts(.init(id: [contactId])).first { + dbContact.phone = state.phone + try env.db().saveContact(dbContact) + } + subscriber.send(.set(\.$phone, "")) + subscriber.send(.set(\.$phoneConfirmationID, nil)) + subscriber.send(.set(\.$phoneConfirmationCode, "")) + } catch { + subscriber.send(.didFail(error.localizedDescription)) + } + subscriber.send(.set(\.$isConfirmingPhone, false)) + subscriber.send(completion: .finished) + return AnyCancellable {} + } + .subscribe(on: env.bgQueue) + .receive(on: env.mainQueue) + .eraseToEffect() case .unregisterPhoneTapped: return .none diff --git a/Examples/xx-messenger/Sources/MyContactFeature/MyContactView.swift b/Examples/xx-messenger/Sources/MyContactFeature/MyContactView.swift index 7eb8e7aa..3f65ba80 100644 --- a/Examples/xx-messenger/Sources/MyContactFeature/MyContactView.swift +++ b/Examples/xx-messenger/Sources/MyContactFeature/MyContactView.swift @@ -15,14 +15,30 @@ public struct MyContactView: View { contact = state.contact focusedField = state.focusedField email = state.email + emailConfirmation = state.emailConfirmationID != nil + emailCode = state.emailConfirmationCode + isRegisteringEmail = state.isRegisteringEmail + isConfirmingEmail = state.isConfirmingEmail phone = state.phone + phoneConfirmation = state.phoneConfirmationID != nil + phoneCode = state.phoneConfirmationCode + isRegisteringPhone = state.isRegisteringPhone + isConfirmingPhone = state.isConfirmingPhone isLoadingFacts = state.isLoadingFacts } var contact: XXModels.Contact? var focusedField: MyContactState.Field? var email: String + var emailConfirmation: Bool + var emailCode: String + var isRegisteringEmail: Bool + var isConfirmingEmail: Bool var phone: String + var phoneConfirmation: Bool + var phoneCode: String + var isRegisteringPhone: Bool + var isConfirmingPhone: Bool var isLoadingFacts: Bool } @@ -56,10 +72,45 @@ public struct MyContactView: View { .focused($focusedField, equals: .email) .textInputAutocapitalization(.never) .disableAutocorrection(true) - Button { - viewStore.send(.registerEmailTapped) - } label: { - Text("Register") + .disabled(viewStore.isRegisteringEmail || viewStore.emailConfirmation) + if viewStore.emailConfirmation { + TextField( + text: viewStore.binding( + get: \.emailCode, + send: { MyContactAction.set(\.$emailConfirmationCode, $0) } + ), + prompt: Text("Enter confirmation code"), + label: { Text("Confirmation code") } + ) + .focused($focusedField, equals: .emailCode) + .textInputAutocapitalization(.never) + .disableAutocorrection(true) + .disabled(viewStore.isConfirmingEmail) + Button { + viewStore.send(.confirmEmailTapped) + } label: { + HStack { + Text("Confirm") + Spacer() + if viewStore.isConfirmingEmail { + ProgressView() + } + } + } + .disabled(viewStore.isConfirmingEmail) + } else { + Button { + viewStore.send(.registerEmailTapped) + } label: { + HStack { + Text("Register") + Spacer() + if viewStore.isRegisteringEmail { + ProgressView() + } + } + } + .disabled(viewStore.isRegisteringEmail) } } } else { @@ -90,10 +141,45 @@ public struct MyContactView: View { .focused($focusedField, equals: .phone) .textInputAutocapitalization(.never) .disableAutocorrection(true) - Button { - viewStore.send(.registerPhoneTapped) - } label: { - Text("Register") + .disabled(viewStore.isRegisteringPhone || viewStore.phoneConfirmation) + if viewStore.phoneConfirmation { + TextField( + text: viewStore.binding( + get: \.phoneCode, + send: { MyContactAction.set(\.$phoneConfirmationCode, $0) } + ), + prompt: Text("Enter confirmation code"), + label: { Text("Confirmation code") } + ) + .focused($focusedField, equals: .phoneCode) + .textInputAutocapitalization(.never) + .disableAutocorrection(true) + .disabled(viewStore.isConfirmingPhone) + Button { + viewStore.send(.confirmPhoneTapped) + } label: { + HStack { + Text("Confirm") + Spacer() + if viewStore.isConfirmingPhone { + ProgressView() + } + } + } + .disabled(viewStore.isConfirmingPhone) + } else { + Button { + viewStore.send(.registerPhoneTapped) + } label: { + HStack { + Text("Register") + Spacer() + if viewStore.isRegisteringPhone { + ProgressView() + } + } + } + .disabled(viewStore.isRegisteringPhone) } } } else { diff --git a/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift b/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift index 9c30f7df..a4f25954 100644 --- a/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift +++ b/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift @@ -60,6 +60,9 @@ final class MyContactFeatureTests: XCTestCase { func testRegisterEmail() { let email = "test@email.com" + let confirmationID = "123" + + var didSendRegisterFact: [Fact] = [] let store = TestStore( initialState: MyContactState(), @@ -67,11 +70,190 @@ final class MyContactFeatureTests: XCTestCase { environment: .unimplemented ) + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.messenger.ud.get = { + var ud: UserDiscovery = .unimplemented + ud.sendRegisterFact.run = { fact in + didSendRegisterFact.append(fact) + return confirmationID + } + return ud + } + + store.send(.set(\.$focusedField, .email)) { + $0.focusedField = .email + } + store.send(.set(\.$email, email)) { $0.email = email } - store.send(.registerEmailTapped) + store.send(.registerEmailTapped) { + $0.focusedField = nil + $0.isRegisteringEmail = true + } + + XCTAssertNoDifference(didSendRegisterFact, [.init(type: .email, value: email)]) + + store.receive(.set(\.$emailConfirmationID, confirmationID)) { + $0.emailConfirmationID = confirmationID + } + + store.receive(.set(\.$isRegisteringEmail, false)) { + $0.isRegisteringEmail = false + } + } + + func testRegisterEmailFailure() { + struct Failure: Error {} + let failure = Failure() + + let store = TestStore( + initialState: MyContactState(), + reducer: myContactReducer, + environment: .unimplemented + ) + + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.messenger.ud.get = { + var ud: UserDiscovery = .unimplemented + ud.sendRegisterFact.run = { _ in throw failure } + return ud + } + + store.send(.registerEmailTapped) { + $0.isRegisteringEmail = true + } + + store.receive(.didFail(failure.localizedDescription)) { + $0.alert = .error(failure.localizedDescription) + } + + store.receive(.set(\.$isRegisteringEmail, false)) { + $0.isRegisteringEmail = false + } + } + + func testConfirmEmail() { + let contactID = "contact-id".data(using: .utf8)! + let email = "test@email.com" + let confirmationID = "123" + let confirmationCode = "321" + let dbContact = XXModels.Contact(id: contactID) + + var didConfirmWithID: [String] = [] + var didConfirmWithCode: [String] = [] + var didFetchContacts: [XXModels.Contact.Query] = [] + var didSaveContact: [XXModels.Contact] = [] + + let store = TestStore( + initialState: MyContactState( + email: email, + emailConfirmationID: confirmationID + ), + reducer: myContactReducer, + environment: .unimplemented + ) + + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.messenger.ud.get = { + var ud: UserDiscovery = .unimplemented + ud.confirmFact.run = { id, code in + didConfirmWithID.append(id) + didConfirmWithCode.append(code) + } + return ud + } + store.environment.messenger.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getContact.run = { + var contact: XXClient.Contact = .unimplemented(Data()) + contact.getIdFromContact.run = { _ in contactID } + return contact + } + return e2e + } + store.environment.db.run = { + var db: Database = .failing + db.fetchContacts.run = { query in + didFetchContacts.append(query) + return [dbContact] + } + db.saveContact.run = { contact in + didSaveContact.append(contact) + return contact + } + return db + } + + store.send(.set(\.$focusedField, .emailCode)) { + $0.focusedField = .emailCode + } + + store.send(.set(\.$emailConfirmationCode, confirmationCode)) { + $0.emailConfirmationCode = confirmationCode + } + + store.send(.confirmEmailTapped) { + $0.focusedField = nil + $0.isConfirmingEmail = true + } + + XCTAssertNoDifference(didConfirmWithID, [confirmationID]) + XCTAssertNoDifference(didConfirmWithCode, [confirmationCode]) + XCTAssertNoDifference(didFetchContacts, [.init(id: [contactID])]) + var expectedSavedContact = dbContact + expectedSavedContact.email = email + XCTAssertNoDifference(didSaveContact, [expectedSavedContact]) + + store.receive(.set(\.$email, "")) { + $0.email = "" + } + store.receive(.set(\.$emailConfirmationID, nil)) { + $0.emailConfirmationID = nil + } + store.receive(.set(\.$emailConfirmationCode, "")) { + $0.emailConfirmationCode = "" + } + store.receive(.set(\.$isConfirmingEmail, false)) { + $0.isConfirmingEmail = false + } + } + + func testConfirmEmailFailure() { + struct Failure: Error {} + let failure = Failure() + + let store = TestStore( + initialState: MyContactState( + emailConfirmationID: "123" + ), + reducer: myContactReducer, + environment: .unimplemented + ) + + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.messenger.ud.get = { + var ud: UserDiscovery = .unimplemented + ud.confirmFact.run = { _, _ in throw failure } + return ud + } + + store.send(.confirmEmailTapped) { + $0.isConfirmingEmail = true + } + + store.receive(.didFail(failure.localizedDescription)) { + $0.alert = .error(failure.localizedDescription) + } + + store.receive(.set(\.$isConfirmingEmail, false)) { + $0.isConfirmingEmail = false + } } func testUnregisterEmail() { @@ -85,7 +267,10 @@ final class MyContactFeatureTests: XCTestCase { } func testRegisterPhone() { - let phone = "123456789" + let phone = "+123456789" + let confirmationID = "123" + + var didSendRegisterFact: [Fact] = [] let store = TestStore( initialState: MyContactState(), @@ -93,13 +278,192 @@ final class MyContactFeatureTests: XCTestCase { environment: .unimplemented ) + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.messenger.ud.get = { + var ud: UserDiscovery = .unimplemented + ud.sendRegisterFact.run = { fact in + didSendRegisterFact.append(fact) + return confirmationID + } + return ud + } + + store.send(.set(\.$focusedField, .phone)) { + $0.focusedField = .phone + } + store.send(.set(\.$phone, phone)) { $0.phone = phone } - store.send(.registerPhoneTapped) + store.send(.registerPhoneTapped) { + $0.focusedField = nil + $0.isRegisteringPhone = true + } + + XCTAssertNoDifference(didSendRegisterFact, [.init(type: .phone, value: phone)]) + + store.receive(.set(\.$phoneConfirmationID, confirmationID)) { + $0.phoneConfirmationID = confirmationID + } + + store.receive(.set(\.$isRegisteringPhone, false)) { + $0.isRegisteringPhone = false + } + } + + func testRegisterPhoneFailure() { + struct Failure: Error {} + let failure = Failure() + + let store = TestStore( + initialState: MyContactState(), + reducer: myContactReducer, + environment: .unimplemented + ) + + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.messenger.ud.get = { + var ud: UserDiscovery = .unimplemented + ud.sendRegisterFact.run = { _ in throw failure } + return ud + } + + store.send(.registerPhoneTapped) { + $0.isRegisteringPhone = true + } + + store.receive(.didFail(failure.localizedDescription)) { + $0.alert = .error(failure.localizedDescription) + } + + store.receive(.set(\.$isRegisteringPhone, false)) { + $0.isRegisteringPhone = false + } } + func testConfirmPhone() { + let contactID = "contact-id".data(using: .utf8)! + let phone = "+123456789" + let confirmationID = "123" + let confirmationCode = "321" + let dbContact = XXModels.Contact(id: contactID) + + var didConfirmWithID: [String] = [] + var didConfirmWithCode: [String] = [] + var didFetchContacts: [XXModels.Contact.Query] = [] + var didSaveContact: [XXModels.Contact] = [] + + let store = TestStore( + initialState: MyContactState( + phone: phone, + phoneConfirmationID: confirmationID + ), + reducer: myContactReducer, + environment: .unimplemented + ) + + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.messenger.ud.get = { + var ud: UserDiscovery = .unimplemented + ud.confirmFact.run = { id, code in + didConfirmWithID.append(id) + didConfirmWithCode.append(code) + } + return ud + } + store.environment.messenger.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getContact.run = { + var contact: XXClient.Contact = .unimplemented(Data()) + contact.getIdFromContact.run = { _ in contactID } + return contact + } + return e2e + } + store.environment.db.run = { + var db: Database = .failing + db.fetchContacts.run = { query in + didFetchContacts.append(query) + return [dbContact] + } + db.saveContact.run = { contact in + didSaveContact.append(contact) + return contact + } + return db + } + + store.send(.set(\.$focusedField, .phoneCode)) { + $0.focusedField = .phoneCode + } + + store.send(.set(\.$phoneConfirmationCode, confirmationCode)) { + $0.phoneConfirmationCode = confirmationCode + } + + store.send(.confirmPhoneTapped) { + $0.focusedField = nil + $0.isConfirmingPhone = true + } + + XCTAssertNoDifference(didConfirmWithID, [confirmationID]) + XCTAssertNoDifference(didConfirmWithCode, [confirmationCode]) + XCTAssertNoDifference(didFetchContacts, [.init(id: [contactID])]) + var expectedSavedContact = dbContact + expectedSavedContact.phone = phone + XCTAssertNoDifference(didSaveContact, [expectedSavedContact]) + + store.receive(.set(\.$phone, "")) { + $0.phone = "" + } + store.receive(.set(\.$phoneConfirmationID, nil)) { + $0.phoneConfirmationID = nil + } + store.receive(.set(\.$phoneConfirmationCode, "")) { + $0.phoneConfirmationCode = "" + } + store.receive(.set(\.$isConfirmingPhone, false)) { + $0.isConfirmingPhone = false + } + } + + func testConfirmPhoneFailure() { + struct Failure: Error {} + let failure = Failure() + + let store = TestStore( + initialState: MyContactState( + phoneConfirmationID: "123" + ), + reducer: myContactReducer, + environment: .unimplemented + ) + + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.messenger.ud.get = { + var ud: UserDiscovery = .unimplemented + ud.confirmFact.run = { _, _ in throw failure } + return ud + } + + store.send(.confirmPhoneTapped) { + $0.isConfirmingPhone = true + } + + store.receive(.didFail(failure.localizedDescription)) { + $0.alert = .error(failure.localizedDescription) + } + + store.receive(.set(\.$isConfirmingPhone, false)) { + $0.isConfirmingPhone = false + } + } + func testUnregisterPhone() { let store = TestStore( initialState: MyContactState(), -- GitLab From 726aab7e32844079f39ccdb0bbb301d30dfa70be Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 20 Sep 2022 13:43:51 +0200 Subject: [PATCH 10/22] Implement facts unregistration --- .../MyContactFeature/MyContactFeature.swift | 52 +++++- .../MyContactFeature/MyContactView.swift | 22 ++- .../MyContactFeatureTests.swift | 176 +++++++++++++++++- 3 files changed, 242 insertions(+), 8 deletions(-) diff --git a/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift b/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift index 99486c8a..b25d0212 100644 --- a/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift +++ b/Examples/xx-messenger/Sources/MyContactFeature/MyContactFeature.swift @@ -23,11 +23,13 @@ public struct MyContactState: Equatable { emailConfirmationCode: String = "", isRegisteringEmail: Bool = false, isConfirmingEmail: Bool = false, + isUnregisteringEmail: Bool = false, phone: String = "", phoneConfirmationID: String? = nil, phoneConfirmationCode: String = "", isRegisteringPhone: Bool = false, isConfirmingPhone: Bool = false, + isUnregisteringPhone: Bool = false, isLoadingFacts: Bool = false, alert: AlertState<MyContactAction>? = nil ) { @@ -38,11 +40,13 @@ public struct MyContactState: Equatable { self.emailConfirmationCode = emailConfirmationCode self.isRegisteringEmail = isRegisteringEmail self.isConfirmingEmail = isConfirmingEmail + self.isUnregisteringEmail = isUnregisteringEmail self.phone = phone self.phoneConfirmationID = phoneConfirmationID self.phoneConfirmationCode = phoneConfirmationCode self.isRegisteringPhone = isRegisteringPhone self.isConfirmingPhone = isConfirmingPhone + self.isUnregisteringPhone = isUnregisteringPhone self.isLoadingFacts = isLoadingFacts self.alert = alert } @@ -54,11 +58,13 @@ public struct MyContactState: Equatable { @BindableState public var emailConfirmationCode: String @BindableState public var isRegisteringEmail: Bool @BindableState public var isConfirmingEmail: Bool + @BindableState public var isUnregisteringEmail: Bool @BindableState public var phone: String @BindableState public var phoneConfirmationID: String? @BindableState public var phoneConfirmationCode: String @BindableState public var isRegisteringPhone: Bool @BindableState public var isConfirmingPhone: Bool + @BindableState public var isUnregisteringPhone: Bool @BindableState public var isLoadingFacts: Bool public var alert: AlertState<MyContactAction>? } @@ -178,7 +184,28 @@ public let myContactReducer = Reducer<MyContactState, MyContactAction, MyContact .eraseToEffect() case .unregisterEmailTapped: - return .none + guard let email = state.contact?.email else { return .none } + state.isUnregisteringEmail = true + return Effect.run { [state] subscriber in + do { + let ud: UserDiscovery = try env.messenger.ud.tryGet() + let fact = Fact(type: .email, value: email) + try ud.removeFact(fact) + let contactId = try env.messenger.e2e.tryGet().getContact().getId() + if var dbContact = try env.db().fetchContacts(.init(id: [contactId])).first { + dbContact.email = nil + try env.db().saveContact(dbContact) + } + } catch { + subscriber.send(.didFail(error.localizedDescription)) + } + subscriber.send(.set(\.$isUnregisteringEmail, false)) + subscriber.send(completion: .finished) + return AnyCancellable {} + } + .subscribe(on: env.bgQueue) + .receive(on: env.mainQueue) + .eraseToEffect() case .registerPhoneTapped: state.focusedField = nil @@ -228,7 +255,28 @@ public let myContactReducer = Reducer<MyContactState, MyContactAction, MyContact .eraseToEffect() case .unregisterPhoneTapped: - return .none + guard let phone = state.contact?.phone else { return .none } + state.isUnregisteringPhone = true + return Effect.run { [state] subscriber in + do { + let ud: UserDiscovery = try env.messenger.ud.tryGet() + let fact = Fact(type: .phone, value: phone) + try ud.removeFact(fact) + let contactId = try env.messenger.e2e.tryGet().getContact().getId() + if var dbContact = try env.db().fetchContacts(.init(id: [contactId])).first { + dbContact.phone = nil + try env.db().saveContact(dbContact) + } + } catch { + subscriber.send(.didFail(error.localizedDescription)) + } + subscriber.send(.set(\.$isUnregisteringPhone, false)) + subscriber.send(completion: .finished) + return AnyCancellable {} + } + .subscribe(on: env.bgQueue) + .receive(on: env.mainQueue) + .eraseToEffect() case .loadFactsTapped: state.isLoadingFacts = true diff --git a/Examples/xx-messenger/Sources/MyContactFeature/MyContactView.swift b/Examples/xx-messenger/Sources/MyContactFeature/MyContactView.swift index 3f65ba80..f32af242 100644 --- a/Examples/xx-messenger/Sources/MyContactFeature/MyContactView.swift +++ b/Examples/xx-messenger/Sources/MyContactFeature/MyContactView.swift @@ -19,11 +19,13 @@ public struct MyContactView: View { emailCode = state.emailConfirmationCode isRegisteringEmail = state.isRegisteringEmail isConfirmingEmail = state.isConfirmingEmail + isUnregisteringEmail = state.isUnregisteringEmail phone = state.phone phoneConfirmation = state.phoneConfirmationID != nil phoneCode = state.phoneConfirmationCode isRegisteringPhone = state.isRegisteringPhone isConfirmingPhone = state.isConfirmingPhone + isUnregisteringPhone = state.isUnregisteringPhone isLoadingFacts = state.isLoadingFacts } @@ -34,11 +36,13 @@ public struct MyContactView: View { var emailCode: String var isRegisteringEmail: Bool var isConfirmingEmail: Bool + var isUnregisteringEmail: Bool var phone: String var phoneConfirmation: Bool var phoneCode: String var isRegisteringPhone: Bool var isConfirmingPhone: Bool + var isUnregisteringPhone: Bool var isLoadingFacts: Bool } @@ -58,8 +62,15 @@ public struct MyContactView: View { Button(role: .destructive) { viewStore.send(.unregisterEmailTapped) } label: { - Text("Unregister") + HStack { + Text("Unregister") + Spacer() + if viewStore.isUnregisteringEmail { + ProgressView() + } + } } + .disabled(viewStore.isUnregisteringEmail) } else { TextField( text: viewStore.binding( @@ -127,8 +138,15 @@ public struct MyContactView: View { Button(role: .destructive) { viewStore.send(.unregisterPhoneTapped) } label: { - Text("Unregister") + HStack { + Text("Unregister") + Spacer() + if viewStore.isUnregisteringPhone { + ProgressView() + } + } } + .disabled(viewStore.isUnregisteringPhone) } else { TextField( text: viewStore.binding( diff --git a/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift b/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift index a4f25954..8d5fac94 100644 --- a/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift +++ b/Examples/xx-messenger/Tests/MyContactFeatureTests/MyContactFeatureTests.swift @@ -257,13 +257,97 @@ final class MyContactFeatureTests: XCTestCase { } func testUnregisterEmail() { + let contactID = "contact-id".data(using: .utf8)! + let email = "test@email.com" + let dbContact = XXModels.Contact(id: contactID, email: email) + + var didRemoveFact: [Fact] = [] + var didFetchContacts: [XXModels.Contact.Query] = [] + var didSaveContact: [XXModels.Contact] = [] + let store = TestStore( - initialState: MyContactState(), + initialState: MyContactState( + contact: dbContact + ), + reducer: myContactReducer, + environment: .unimplemented + ) + + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.messenger.ud.get = { + var ud: UserDiscovery = .unimplemented + ud.removeFact.run = { didRemoveFact.append($0) } + return ud + } + store.environment.messenger.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getContact.run = { + var contact: XXClient.Contact = .unimplemented(Data()) + contact.getIdFromContact.run = { _ in contactID } + return contact + } + return e2e + } + store.environment.db.run = { + var db: Database = .failing + db.fetchContacts.run = { query in + didFetchContacts.append(query) + return [dbContact] + } + db.saveContact.run = { contact in + didSaveContact.append(contact) + return contact + } + return db + } + + store.send(.unregisterEmailTapped) { + $0.isUnregisteringEmail = true + } + + XCTAssertNoDifference(didRemoveFact, [.init(type: .email, value: email)]) + XCTAssertNoDifference(didFetchContacts, [.init(id: [contactID])]) + var expectedSavedContact = dbContact + expectedSavedContact.email = nil + XCTAssertNoDifference(didSaveContact, [expectedSavedContact]) + + store.receive(.set(\.$isUnregisteringEmail, false)) { + $0.isUnregisteringEmail = false + } + } + + func testUnregisterEmailFailure() { + struct Failure: Error {} + let failure = Failure() + + let store = TestStore( + initialState: MyContactState( + contact: .init(id: Data(), email: "test@email.com") + ), reducer: myContactReducer, environment: .unimplemented ) - store.send(.unregisterEmailTapped) + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.messenger.ud.get = { + var ud: UserDiscovery = .unimplemented + ud.removeFact.run = { _ in throw failure } + return ud + } + + store.send(.unregisterEmailTapped) { + $0.isUnregisteringEmail = true + } + + store.receive(.didFail(failure.localizedDescription)) { + $0.alert = .error(failure.localizedDescription) + } + + store.receive(.set(\.$isUnregisteringEmail, false)) { + $0.isUnregisteringEmail = false + } } func testRegisterPhone() { @@ -465,13 +549,97 @@ final class MyContactFeatureTests: XCTestCase { } func testUnregisterPhone() { + let contactID = "contact-id".data(using: .utf8)! + let phone = "+123456789" + let dbContact = XXModels.Contact(id: contactID, phone: phone) + + var didRemoveFact: [Fact] = [] + var didFetchContacts: [XXModels.Contact.Query] = [] + var didSaveContact: [XXModels.Contact] = [] + let store = TestStore( - initialState: MyContactState(), + initialState: MyContactState( + contact: dbContact + ), + reducer: myContactReducer, + environment: .unimplemented + ) + + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.messenger.ud.get = { + var ud: UserDiscovery = .unimplemented + ud.removeFact.run = { didRemoveFact.append($0) } + return ud + } + store.environment.messenger.e2e.get = { + var e2e: E2E = .unimplemented + e2e.getContact.run = { + var contact: XXClient.Contact = .unimplemented(Data()) + contact.getIdFromContact.run = { _ in contactID } + return contact + } + return e2e + } + store.environment.db.run = { + var db: Database = .failing + db.fetchContacts.run = { query in + didFetchContacts.append(query) + return [dbContact] + } + db.saveContact.run = { contact in + didSaveContact.append(contact) + return contact + } + return db + } + + store.send(.unregisterPhoneTapped) { + $0.isUnregisteringPhone = true + } + + XCTAssertNoDifference(didRemoveFact, [.init(type: .phone, value: phone)]) + XCTAssertNoDifference(didFetchContacts, [.init(id: [contactID])]) + var expectedSavedContact = dbContact + expectedSavedContact.phone = nil + XCTAssertNoDifference(didSaveContact, [expectedSavedContact]) + + store.receive(.set(\.$isUnregisteringPhone, false)) { + $0.isUnregisteringPhone = false + } + } + + func testUnregisterPhoneFailure() { + struct Failure: Error {} + let failure = Failure() + + let store = TestStore( + initialState: MyContactState( + contact: .init(id: Data(), phone: "+123456789") + ), reducer: myContactReducer, environment: .unimplemented ) - store.send(.unregisterPhoneTapped) + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.messenger.ud.get = { + var ud: UserDiscovery = .unimplemented + ud.removeFact.run = { _ in throw failure } + return ud + } + + store.send(.unregisterPhoneTapped) { + $0.isUnregisteringPhone = true + } + + store.receive(.didFail(failure.localizedDescription)) { + $0.alert = .error(failure.localizedDescription) + } + + store.receive(.set(\.$isUnregisteringPhone, false)) { + $0.isUnregisteringPhone = false + } } func testLoadFactsFromClient() { -- GitLab From ab3cef3ed320c3b0ad9ae9279beeb9c5f0812ba6 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 20 Sep 2022 13:56:33 +0200 Subject: [PATCH 11/22] Bump package dependencies versions --- Examples/xx-messenger/Package.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift index c184020f..8367b5ef 100644 --- a/Examples/xx-messenger/Package.swift +++ b/Examples/xx-messenger/Package.swift @@ -40,7 +40,7 @@ let package = Package( ), .package( url: "https://github.com/pointfreeco/swift-composable-architecture.git", - .upToNextMajor(from: "0.40.1") + .upToNextMajor(from: "0.40.2") ), .package( url: "https://git.xx.network/elixxir/client-ios-db.git", @@ -52,7 +52,7 @@ let package = Package( ), .package( url: "https://github.com/pointfreeco/xctest-dynamic-overlay.git", - .upToNextMajor(from: "0.4.0") + .upToNextMajor(from: "0.4.1") ), ], targets: [ -- GitLab From cdae17a17937fe016b0c97f277f748fc9421ed31 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 20 Sep 2022 14:13:28 +0200 Subject: [PATCH 12/22] Update package dependencies --- Package.resolved | 8 ++++---- Package.swift | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Package.resolved b/Package.resolved index 6741fe9d..225eb0fd 100644 --- a/Package.resolved +++ b/Package.resolved @@ -14,8 +14,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/swift-custom-dump.git", "state" : { - "revision" : "21ec1d717c07cea5a026979cb0471dd95c7087e7", - "version" : "0.5.0" + "revision" : "c9b6b940d95c0a925c63f6858943415714d8a981", + "version" : "0.5.2" } }, { @@ -23,8 +23,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/pointfreeco/xctest-dynamic-overlay.git", "state" : { - "revision" : "38bc9242e4388b80bd23ddfdf3071428859e3260", - "version" : "0.4.0" + "revision" : "30314f1ece684dd60679d598a9b89107557b67d9", + "version" : "0.4.1" } } ], diff --git a/Package.swift b/Package.swift index 30e5c604..d34f3701 100644 --- a/Package.swift +++ b/Package.swift @@ -26,11 +26,11 @@ let package = Package( dependencies: [ .package( url: "https://github.com/pointfreeco/swift-custom-dump.git", - .upToNextMajor(from: "0.5.0") + .upToNextMajor(from: "0.5.2") ), .package( url: "https://github.com/pointfreeco/xctest-dynamic-overlay.git", - .upToNextMajor(from: "0.4.0") + .upToNextMajor(from: "0.4.1") ), .package( url: "https://github.com/kishikawakatsumi/KeychainAccess.git", -- GitLab From 2d7f7791f3b90cec21ac6f2391becfd9fc9f127b Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 20 Sep 2022 14:28:42 +0200 Subject: [PATCH 13/22] Add missing imports --- .../xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift | 1 + .../xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift | 1 + .../Tests/RegisterFeatureTests/RegisterFeatureTests.swift | 1 + .../Tests/SendRequestFeatureTests/SendRequestFeatureTests.swift | 1 + 4 files changed, 4 insertions(+) diff --git a/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift b/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift index 5a013b28..fc58fe4e 100644 --- a/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift +++ b/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift @@ -1,4 +1,5 @@ import ComposableArchitecture +import CustomDump import HomeFeature import RestoreFeature import WelcomeFeature diff --git a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift index fc671b1e..d922d4f9 100644 --- a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift +++ b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift @@ -1,6 +1,7 @@ import AppCore import ComposableArchitecture import ContactsFeature +import CustomDump import RegisterFeature import UserSearchFeature import XCTest diff --git a/Examples/xx-messenger/Tests/RegisterFeatureTests/RegisterFeatureTests.swift b/Examples/xx-messenger/Tests/RegisterFeatureTests/RegisterFeatureTests.swift index 6c802874..8da1e1f1 100644 --- a/Examples/xx-messenger/Tests/RegisterFeatureTests/RegisterFeatureTests.swift +++ b/Examples/xx-messenger/Tests/RegisterFeatureTests/RegisterFeatureTests.swift @@ -1,4 +1,5 @@ import ComposableArchitecture +import CustomDump import XCTest import XXClient import XXMessengerClient diff --git a/Examples/xx-messenger/Tests/SendRequestFeatureTests/SendRequestFeatureTests.swift b/Examples/xx-messenger/Tests/SendRequestFeatureTests/SendRequestFeatureTests.swift index cec3587e..3f35d3df 100644 --- a/Examples/xx-messenger/Tests/SendRequestFeatureTests/SendRequestFeatureTests.swift +++ b/Examples/xx-messenger/Tests/SendRequestFeatureTests/SendRequestFeatureTests.swift @@ -1,5 +1,6 @@ import Combine import ComposableArchitecture +import CustomDump import XCTest import XXClient import XXModels -- GitLab From 450633f2343cf9c3ce1b9a3792f1bf6aec943601 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 20 Sep 2022 14:29:02 +0200 Subject: [PATCH 14/22] Update UserSearchFeatureTests --- .../Tests/UserSearchFeatureTests/UserSearchFeatureTests.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchFeatureTests.swift b/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchFeatureTests.swift index 44731c1f..33f1edb9 100644 --- a/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchFeatureTests.swift +++ b/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchFeatureTests.swift @@ -1,5 +1,6 @@ import ComposableArchitecture import ContactFeature +import CustomDump import XCTest import XXClient import XXMessengerClient @@ -64,6 +65,8 @@ final class UserSearchFeatureTests: XCTestCase { $0.failure = nil } + XCTAssertNoDifference(didSearchWithQuery, [.init(username: "Username")]) + store.receive(.didSucceed(contacts)) { $0.isSearching = false $0.failure = nil -- GitLab From e17db8e25301073e84de6d242342a6c9f50490ee Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 20 Sep 2022 14:29:35 +0200 Subject: [PATCH 15/22] Add explicit CustomDump dependency to test targets --- Examples/xx-messenger/Package.swift | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift index 8367b5ef..c0a4f905 100644 --- a/Examples/xx-messenger/Package.swift +++ b/Examples/xx-messenger/Package.swift @@ -54,6 +54,10 @@ let package = Package( url: "https://github.com/pointfreeco/xctest-dynamic-overlay.git", .upToNextMajor(from: "0.4.1") ), + .package( + url: "https://github.com/pointfreeco/swift-custom-dump.git", + .upToNextMajor(from: "0.5.2") + ), ], targets: [ .target( @@ -70,7 +74,8 @@ let package = Package( .testTarget( name: "AppCoreTests", dependencies: [ - .target(name: "AppCore") + .target(name: "AppCore"), + .product(name: "CustomDump", package: "swift-custom-dump"), ], swiftSettings: swiftSettings ), @@ -102,6 +107,7 @@ let package = Package( name: "AppFeatureTests", dependencies: [ .target(name: "AppFeature"), + .product(name: "CustomDump", package: "swift-custom-dump"), ], swiftSettings: swiftSettings ), @@ -120,6 +126,7 @@ let package = Package( name: "ChatFeatureTests", dependencies: [ .target(name: "ChatFeature"), + .product(name: "CustomDump", package: "swift-custom-dump"), ], swiftSettings: swiftSettings ), @@ -137,6 +144,7 @@ let package = Package( name: "CheckContactAuthFeatureTests", dependencies: [ .target(name: "CheckContactAuthFeature"), + .product(name: "CustomDump", package: "swift-custom-dump"), ] ), .target( @@ -153,6 +161,7 @@ let package = Package( name: "ConfirmRequestFeatureTests", dependencies: [ .target(name: "ConfirmRequestFeature"), + .product(name: "CustomDump", package: "swift-custom-dump"), ] ), .target( @@ -175,6 +184,7 @@ let package = Package( name: "ContactFeatureTests", dependencies: [ .target(name: "ContactFeature"), + .product(name: "CustomDump", package: "swift-custom-dump"), ], swiftSettings: swiftSettings ), @@ -196,6 +206,7 @@ let package = Package( name: "ContactsFeatureTests", dependencies: [ .target(name: "ContactsFeature"), + .product(name: "CustomDump", package: "swift-custom-dump"), ], swiftSettings: swiftSettings ), @@ -216,6 +227,7 @@ let package = Package( name: "HomeFeatureTests", dependencies: [ .target(name: "HomeFeature"), + .product(name: "CustomDump", package: "swift-custom-dump"), ], swiftSettings: swiftSettings ), @@ -234,6 +246,7 @@ let package = Package( name: "MyContactFeatureTests", dependencies: [ .target(name: "MyContactFeature"), + .product(name: "CustomDump", package: "swift-custom-dump"), ], swiftSettings: swiftSettings ), @@ -252,6 +265,7 @@ let package = Package( name: "RegisterFeatureTests", dependencies: [ .target(name: "RegisterFeature"), + .product(name: "CustomDump", package: "swift-custom-dump"), ], swiftSettings: swiftSettings ), @@ -266,6 +280,7 @@ let package = Package( name: "RestoreFeatureTests", dependencies: [ .target(name: "RestoreFeature"), + .product(name: "CustomDump", package: "swift-custom-dump"), ], swiftSettings: swiftSettings ), @@ -284,6 +299,7 @@ let package = Package( name: "SendRequestFeatureTests", dependencies: [ .target(name: "SendRequestFeature"), + .product(name: "CustomDump", package: "swift-custom-dump"), ], swiftSettings: swiftSettings ), @@ -303,6 +319,7 @@ let package = Package( name: "UserSearchFeatureTests", dependencies: [ .target(name: "UserSearchFeature"), + .product(name: "CustomDump", package: "swift-custom-dump"), ], swiftSettings: swiftSettings ), @@ -320,6 +337,7 @@ let package = Package( name: "VerifyContactFeatureTests", dependencies: [ .target(name: "VerifyContactFeature"), + .product(name: "CustomDump", package: "swift-custom-dump"), ] ), .target( @@ -334,6 +352,7 @@ let package = Package( name: "WelcomeFeatureTests", dependencies: [ .target(name: "WelcomeFeature"), + .product(name: "CustomDump", package: "swift-custom-dump"), ], swiftSettings: swiftSettings ), -- GitLab From dbf5b0986e5f3def537497f1faba063d99abd8d6 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 20 Sep 2022 14:47:37 +0200 Subject: [PATCH 16/22] Add xcode-remove-caches script --- xcode-remove-caches.sh | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 xcode-remove-caches.sh diff --git a/xcode-remove-caches.sh b/xcode-remove-caches.sh new file mode 100644 index 00000000..34e26621 --- /dev/null +++ b/xcode-remove-caches.sh @@ -0,0 +1,4 @@ +rm -rf "$(getconf DARWIN_USER_CACHE_DIR)/org.llvm.clang/ModuleCache" +rm -rf "$(getconf DARWIN_USER_CACHE_DIR)/org.llvm.clang.$(whoami)/ModuleCache" +rm -rf ~/Library/Developer/Xcode/DerivedData/* +rm -rf ~/Library/Caches/com.apple.dt.Xcode/* -- GitLab From 116858c76ca5c739e6adcdc19c60508038d77383 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 20 Sep 2022 14:48:44 +0200 Subject: [PATCH 17/22] Remove caches before running example tests on CI --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 10f45cd3..64dbfb17 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -27,5 +27,6 @@ test-examples-ios: tags: - ios script: + - ./xcode-remove-caches.sh - ./run-tests.sh examples-ios retry: 1 -- GitLab From 912c45b5b7f6b88661fd92d072b2d0307c1a49bd Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 20 Sep 2022 14:50:01 +0200 Subject: [PATCH 18/22] Make xcode-remove-caches executable --- xcode-remove-caches.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 xcode-remove-caches.sh diff --git a/xcode-remove-caches.sh b/xcode-remove-caches.sh old mode 100644 new mode 100755 -- GitLab From 098730660417b60725ec3cebf88a1d9b37c8d0bf Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 20 Sep 2022 14:51:51 +0200 Subject: [PATCH 19/22] Update xcode-remove-caches script --- xcode-remove-caches.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xcode-remove-caches.sh b/xcode-remove-caches.sh index 34e26621..4d24eab4 100755 --- a/xcode-remove-caches.sh +++ b/xcode-remove-caches.sh @@ -1,3 +1,5 @@ +pkill -int com.apple.CoreSimulator.CoreSimulatorService +killall Xcode rm -rf "$(getconf DARWIN_USER_CACHE_DIR)/org.llvm.clang/ModuleCache" rm -rf "$(getconf DARWIN_USER_CACHE_DIR)/org.llvm.clang.$(whoami)/ModuleCache" rm -rf ~/Library/Developer/Xcode/DerivedData/* -- GitLab From bd28d1095b4b0866ded5df5eb6eabed53b16db8f Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 20 Sep 2022 14:52:22 +0200 Subject: [PATCH 20/22] Revert "Remove caches before running example tests on CI" This reverts commit 116858c76ca5c739e6adcdc19c60508038d77383. --- .gitlab-ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 64dbfb17..10f45cd3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -27,6 +27,5 @@ test-examples-ios: tags: - ios script: - - ./xcode-remove-caches.sh - ./run-tests.sh examples-ios retry: 1 -- GitLab From 83d355a7f947a2241366f93992959062b6d05484 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 20 Sep 2022 14:53:15 +0200 Subject: [PATCH 21/22] Run xcode-remove-caches on CI --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 10f45cd3..b3307061 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,6 +2,7 @@ before_script: - for ip in $(dig @8.8.8.8 github.com +short); do ssh-keyscan github.com,$ip; ssh-keyscan $ip; done 2>/dev/null >> ~/.ssh/known_hosts - for ip in $(dig @8.8.8.8 git.xx.network +short); do ssh-keyscan git.xx.network,$ip; ssh-keyscan $ip; done 2>/dev/null >> ~/.ssh/known_hosts - xcodebuild -version + - ./xcode-remove-caches.sh stages: - test -- GitLab From 4f3ebddf4f92adb29f797a86f8709d3bcce5552b Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 20 Sep 2022 15:01:22 +0200 Subject: [PATCH 22/22] Revert "Run xcode-remove-caches on CI" This reverts commit 83d355a7f947a2241366f93992959062b6d05484. --- .gitlab-ci.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b3307061..10f45cd3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,7 +2,6 @@ before_script: - for ip in $(dig @8.8.8.8 github.com +short); do ssh-keyscan github.com,$ip; ssh-keyscan $ip; done 2>/dev/null >> ~/.ssh/known_hosts - for ip in $(dig @8.8.8.8 git.xx.network +short); do ssh-keyscan git.xx.network,$ip; ssh-keyscan $ip; done 2>/dev/null >> ~/.ssh/known_hosts - xcodebuild -version - - ./xcode-remove-caches.sh stages: - test -- GitLab