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

Implement facts unregistration

parent bfad972f
No related branches found
No related tags found
2 merge requests!102Release 1.0.0,!98Messenger example - register, confirm, and unregister user facts
...@@ -23,11 +23,13 @@ public struct MyContactState: Equatable { ...@@ -23,11 +23,13 @@ public struct MyContactState: Equatable {
emailConfirmationCode: String = "", emailConfirmationCode: String = "",
isRegisteringEmail: Bool = false, isRegisteringEmail: Bool = false,
isConfirmingEmail: Bool = false, isConfirmingEmail: Bool = false,
isUnregisteringEmail: Bool = false,
phone: String = "", phone: String = "",
phoneConfirmationID: String? = nil, phoneConfirmationID: String? = nil,
phoneConfirmationCode: String = "", phoneConfirmationCode: String = "",
isRegisteringPhone: Bool = false, isRegisteringPhone: Bool = false,
isConfirmingPhone: Bool = false, isConfirmingPhone: Bool = false,
isUnregisteringPhone: Bool = false,
isLoadingFacts: Bool = false, isLoadingFacts: Bool = false,
alert: AlertState<MyContactAction>? = nil alert: AlertState<MyContactAction>? = nil
) { ) {
...@@ -38,11 +40,13 @@ public struct MyContactState: Equatable { ...@@ -38,11 +40,13 @@ public struct MyContactState: Equatable {
self.emailConfirmationCode = emailConfirmationCode self.emailConfirmationCode = emailConfirmationCode
self.isRegisteringEmail = isRegisteringEmail self.isRegisteringEmail = isRegisteringEmail
self.isConfirmingEmail = isConfirmingEmail self.isConfirmingEmail = isConfirmingEmail
self.isUnregisteringEmail = isUnregisteringEmail
self.phone = phone self.phone = phone
self.phoneConfirmationID = phoneConfirmationID self.phoneConfirmationID = phoneConfirmationID
self.phoneConfirmationCode = phoneConfirmationCode self.phoneConfirmationCode = phoneConfirmationCode
self.isRegisteringPhone = isRegisteringPhone self.isRegisteringPhone = isRegisteringPhone
self.isConfirmingPhone = isConfirmingPhone self.isConfirmingPhone = isConfirmingPhone
self.isUnregisteringPhone = isUnregisteringPhone
self.isLoadingFacts = isLoadingFacts self.isLoadingFacts = isLoadingFacts
self.alert = alert self.alert = alert
} }
...@@ -54,11 +58,13 @@ public struct MyContactState: Equatable { ...@@ -54,11 +58,13 @@ public struct MyContactState: Equatable {
@BindableState public var emailConfirmationCode: String @BindableState public var emailConfirmationCode: String
@BindableState public var isRegisteringEmail: Bool @BindableState public var isRegisteringEmail: Bool
@BindableState public var isConfirmingEmail: Bool @BindableState public var isConfirmingEmail: Bool
@BindableState public var isUnregisteringEmail: Bool
@BindableState public var phone: String @BindableState public var phone: String
@BindableState public var phoneConfirmationID: String? @BindableState public var phoneConfirmationID: String?
@BindableState public var phoneConfirmationCode: String @BindableState public var phoneConfirmationCode: String
@BindableState public var isRegisteringPhone: Bool @BindableState public var isRegisteringPhone: Bool
@BindableState public var isConfirmingPhone: Bool @BindableState public var isConfirmingPhone: Bool
@BindableState public var isUnregisteringPhone: Bool
@BindableState public var isLoadingFacts: Bool @BindableState public var isLoadingFacts: Bool
public var alert: AlertState<MyContactAction>? public var alert: AlertState<MyContactAction>?
} }
...@@ -178,7 +184,28 @@ public let myContactReducer = Reducer<MyContactState, MyContactAction, MyContact ...@@ -178,7 +184,28 @@ public let myContactReducer = Reducer<MyContactState, MyContactAction, MyContact
.eraseToEffect() .eraseToEffect()
case .unregisterEmailTapped: 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: case .registerPhoneTapped:
state.focusedField = nil state.focusedField = nil
...@@ -228,7 +255,28 @@ public let myContactReducer = Reducer<MyContactState, MyContactAction, MyContact ...@@ -228,7 +255,28 @@ public let myContactReducer = Reducer<MyContactState, MyContactAction, MyContact
.eraseToEffect() .eraseToEffect()
case .unregisterPhoneTapped: 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: case .loadFactsTapped:
state.isLoadingFacts = true state.isLoadingFacts = true
......
...@@ -19,11 +19,13 @@ public struct MyContactView: View { ...@@ -19,11 +19,13 @@ public struct MyContactView: View {
emailCode = state.emailConfirmationCode emailCode = state.emailConfirmationCode
isRegisteringEmail = state.isRegisteringEmail isRegisteringEmail = state.isRegisteringEmail
isConfirmingEmail = state.isConfirmingEmail isConfirmingEmail = state.isConfirmingEmail
isUnregisteringEmail = state.isUnregisteringEmail
phone = state.phone phone = state.phone
phoneConfirmation = state.phoneConfirmationID != nil phoneConfirmation = state.phoneConfirmationID != nil
phoneCode = state.phoneConfirmationCode phoneCode = state.phoneConfirmationCode
isRegisteringPhone = state.isRegisteringPhone isRegisteringPhone = state.isRegisteringPhone
isConfirmingPhone = state.isConfirmingPhone isConfirmingPhone = state.isConfirmingPhone
isUnregisteringPhone = state.isUnregisteringPhone
isLoadingFacts = state.isLoadingFacts isLoadingFacts = state.isLoadingFacts
} }
...@@ -34,11 +36,13 @@ public struct MyContactView: View { ...@@ -34,11 +36,13 @@ public struct MyContactView: View {
var emailCode: String var emailCode: String
var isRegisteringEmail: Bool var isRegisteringEmail: Bool
var isConfirmingEmail: Bool var isConfirmingEmail: Bool
var isUnregisteringEmail: Bool
var phone: String var phone: String
var phoneConfirmation: Bool var phoneConfirmation: Bool
var phoneCode: String var phoneCode: String
var isRegisteringPhone: Bool var isRegisteringPhone: Bool
var isConfirmingPhone: Bool var isConfirmingPhone: Bool
var isUnregisteringPhone: Bool
var isLoadingFacts: Bool var isLoadingFacts: Bool
} }
...@@ -58,8 +62,15 @@ public struct MyContactView: View { ...@@ -58,8 +62,15 @@ public struct MyContactView: View {
Button(role: .destructive) { Button(role: .destructive) {
viewStore.send(.unregisterEmailTapped) viewStore.send(.unregisterEmailTapped)
} label: { } label: {
Text("Unregister") HStack {
Text("Unregister")
Spacer()
if viewStore.isUnregisteringEmail {
ProgressView()
}
}
} }
.disabled(viewStore.isUnregisteringEmail)
} else { } else {
TextField( TextField(
text: viewStore.binding( text: viewStore.binding(
...@@ -127,8 +138,15 @@ public struct MyContactView: View { ...@@ -127,8 +138,15 @@ public struct MyContactView: View {
Button(role: .destructive) { Button(role: .destructive) {
viewStore.send(.unregisterPhoneTapped) viewStore.send(.unregisterPhoneTapped)
} label: { } label: {
Text("Unregister") HStack {
Text("Unregister")
Spacer()
if viewStore.isUnregisteringPhone {
ProgressView()
}
}
} }
.disabled(viewStore.isUnregisteringPhone)
} else { } else {
TextField( TextField(
text: viewStore.binding( text: viewStore.binding(
......
...@@ -257,13 +257,97 @@ final class MyContactFeatureTests: XCTestCase { ...@@ -257,13 +257,97 @@ final class MyContactFeatureTests: XCTestCase {
} }
func testUnregisterEmail() { 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( 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, reducer: myContactReducer,
environment: .unimplemented 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() { func testRegisterPhone() {
...@@ -465,13 +549,97 @@ final class MyContactFeatureTests: XCTestCase { ...@@ -465,13 +549,97 @@ final class MyContactFeatureTests: XCTestCase {
} }
func testUnregisterPhone() { 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( 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, reducer: myContactReducer,
environment: .unimplemented 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() { func testLoadFactsFromClient() {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment