From c7e474b152deccc7fa08d3e3a0fef3071bbbe4ae Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Mon, 5 Sep 2022 10:15:43 +0200
Subject: [PATCH 1/5] Save user contact when registering

---
 Examples/xx-messenger/Package.swift           |  2 +
 .../AppFeature/AppEnvironment+Live.swift      |  2 +
 .../RegisterFeature/RegisterFeature.swift     | 19 ++++++
 .../RegisterFeatureTests.swift                | 66 ++++++++++++++++++-
 4 files changed, 87 insertions(+), 2 deletions(-)

diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift
index f7e0d039..8f1e26f7 100644
--- a/Examples/xx-messenger/Package.swift
+++ b/Examples/xx-messenger/Package.swift
@@ -106,8 +106,10 @@ let package = Package(
     .target(
       name: "RegisterFeature",
       dependencies: [
+        .target(name: "AppCore"),
         .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
         .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"),
+        .product(name: "XXModels", package: "client-ios-db"),
       ],
       swiftSettings: swiftSettings
     ),
diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
index d3cc134a..3cbca1db 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
@@ -38,6 +38,8 @@ extension AppEnvironment {
           register: {
             RegisterEnvironment(
               messenger: messenger,
+              db: dbManager.getDB,
+              now: Date.init,
               mainQueue: mainQueue,
               bgQueue: bgQueue
             )
diff --git a/Examples/xx-messenger/Sources/RegisterFeature/RegisterFeature.swift b/Examples/xx-messenger/Sources/RegisterFeature/RegisterFeature.swift
index f929d18f..a8d3280c 100644
--- a/Examples/xx-messenger/Sources/RegisterFeature/RegisterFeature.swift
+++ b/Examples/xx-messenger/Sources/RegisterFeature/RegisterFeature.swift
@@ -1,6 +1,9 @@
+import AppCore
 import ComposableArchitecture
 import SwiftUI
+import XCTestDynamicOverlay
 import XXMessengerClient
+import XXModels
 
 public struct RegisterState: Equatable {
   public enum Field: String, Hashable {
@@ -33,15 +36,21 @@ public enum RegisterAction: Equatable, BindableAction {
 public struct RegisterEnvironment {
   public init(
     messenger: Messenger,
+    db: DBManagerGetDB,
+    now: @escaping () -> Date,
     mainQueue: AnySchedulerOf<DispatchQueue>,
     bgQueue: AnySchedulerOf<DispatchQueue>
   ) {
     self.messenger = messenger
+    self.db = db
+    self.now = now
     self.mainQueue = mainQueue
     self.bgQueue = bgQueue
   }
 
   public var messenger: Messenger
+  public var db: DBManagerGetDB
+  public var now: () -> Date
   public var mainQueue: AnySchedulerOf<DispatchQueue>
   public var bgQueue: AnySchedulerOf<DispatchQueue>
 }
@@ -49,6 +58,8 @@ public struct RegisterEnvironment {
 extension RegisterEnvironment {
   public static let unimplemented = RegisterEnvironment(
     messenger: .unimplemented,
+    db: .unimplemented,
+    now: XCTUnimplemented("\(Self.self).now"),
     mainQueue: .unimplemented,
     bgQueue: .unimplemented
   )
@@ -66,7 +77,15 @@ public let registerReducer = Reducer<RegisterState, RegisterAction, RegisterEnvi
     state.failure = nil
     return .future { [username = state.username] fulfill in
       do {
+        let db = try env.db()
         try env.messenger.register(username: username)
+        let contact = env.messenger.e2e()!.getContact()
+        try db.saveContact(Contact(
+          id: try contact.getId(),
+          marshaled: contact.data,
+          username: username,
+          createdAt: env.now()
+        ))
         fulfill(.success(.finished))
       }
       catch {
diff --git a/Examples/xx-messenger/Tests/RegisterFeatureTests/RegisterFeatureTests.swift b/Examples/xx-messenger/Tests/RegisterFeatureTests/RegisterFeatureTests.swift
index 21d4da16..a2aad5bd 100644
--- a/Examples/xx-messenger/Tests/RegisterFeatureTests/RegisterFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/RegisterFeatureTests/RegisterFeatureTests.swift
@@ -1,8 +1,10 @@
 import ComposableArchitecture
 import XCTest
+import XXClient
+import XXMessengerClient
+import XXModels
 @testable import RegisterFeature
 
-@MainActor
 final class RegisterFeatureTests: XCTestCase {
   func testRegister() throws {
     let store = TestStore(
@@ -11,15 +13,35 @@ final class RegisterFeatureTests: XCTestCase {
       environment: .unimplemented
     )
 
+    let now = Date()
     let mainQueue = DispatchQueue.test
     let bgQueue = DispatchQueue.test
+    var dbDidSaveContact: [XXModels.Contact] = []
     var messengerDidRegisterUsername: [String] = []
 
+    store.environment.now = { now }
     store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
     store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
     store.environment.messenger.register.run = { username in
       messengerDidRegisterUsername.append(username)
     }
+    store.environment.messenger.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getContact.run = {
+        var contact = XXClient.Contact.unimplemented("contact-data".data(using: .utf8)!)
+        contact.getIdFromContact.run = { _ in "contact-id".data(using: .utf8)! }
+        return contact
+      }
+      return e2e
+    }
+    store.environment.db.run = {
+      var db: Database = .failing
+      db.saveContact.run = { contact in
+        dbDidSaveContact.append(contact)
+        return contact
+      }
+      return db
+    }
 
     store.send(.set(\.$username, "NewUser")) {
       $0.username = "NewUser"
@@ -30,17 +52,56 @@ final class RegisterFeatureTests: XCTestCase {
     }
 
     XCTAssertNoDifference(messengerDidRegisterUsername, [])
+    XCTAssertNoDifference(dbDidSaveContact, [])
 
     bgQueue.advance()
 
     XCTAssertNoDifference(messengerDidRegisterUsername, ["NewUser"])
+    XCTAssertNoDifference(dbDidSaveContact, [
+      XXModels.Contact(
+        id: "contact-id".data(using: .utf8)!,
+        marshaled: "contact-data".data(using: .utf8)!,
+        username: "NewUser",
+        createdAt: now
+      )
+    ])
 
     mainQueue.advance()
 
     store.receive(.finished)
   }
 
-  func testRegisterFailure() throws {
+  func testGetDbFailure() throws {
+    struct Error: Swift.Error, Equatable {}
+    let error = Error()
+
+    let store = TestStore(
+      initialState: RegisterState(),
+      reducer: registerReducer,
+      environment: .unimplemented
+    )
+
+    let mainQueue = DispatchQueue.test
+    let bgQueue = DispatchQueue.test
+
+    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
+    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
+    store.environment.db.run = { throw error }
+
+    store.send(.registerTapped) {
+      $0.isRegistering = true
+    }
+
+    bgQueue.advance()
+    mainQueue.advance()
+
+    store.receive(.failed(error.localizedDescription)) {
+      $0.isRegistering = false
+      $0.failure = error.localizedDescription
+    }
+  }
+
+  func testMessengerRegisterFailure() throws {
     struct Error: Swift.Error, Equatable {}
     let error = Error()
 
@@ -55,6 +116,7 @@ final class RegisterFeatureTests: XCTestCase {
 
     store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
     store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
+    store.environment.db.run = { .failing }
     store.environment.messenger.register.run = { _ in throw error }
 
     store.send(.registerTapped) {
-- 
GitLab


From 4a38e1cd5aef3edff0c76177b8ef859759d42f75 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Mon, 5 Sep 2022 10:18:33 +0200
Subject: [PATCH 2/5] Remove username from HomeFeature

---
 .../Sources/HomeFeature/HomeFeature.swift     |  9 ------
 .../Sources/HomeFeature/HomeView.swift        | 10 -------
 .../HomeFeatureTests/HomeFeatureTests.swift   | 28 -------------------
 3 files changed, 47 deletions(-)

diff --git a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
index 29abfb6b..5c20364d 100644
--- a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
+++ b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
@@ -8,16 +8,13 @@ import XXMessengerClient
 
 public struct HomeState: Equatable {
   public init(
-    username: String? = nil,
     failure: String? = nil,
     register: RegisterState? = nil
   ) {
-    self.username = username
     self.failure = failure
     self.register = register
   }
 
-  @BindableState public var username: String?
   @BindableState public var failure: String?
   @BindableState public var register: RegisterState?
 }
@@ -76,12 +73,6 @@ public let homeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
           }
           try env.messenger.logIn()
         }
-
-        if let contact = env.messenger.e2e()?.getContact(),
-           let facts = try? contact.getFacts(),
-           let username = facts.first(where: { $0.type == 0 })?.fact {
-          subscriber.send(.set(\.$username, username))
-        }
       } catch {
         subscriber.send(.set(\.$failure, error.localizedDescription))
       }
diff --git a/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
index e40bb1db..7dd8f668 100644
--- a/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
+++ b/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
@@ -11,11 +11,9 @@ public struct HomeView: View {
   let store: Store<HomeState, HomeAction>
 
   struct ViewState: Equatable {
-    var username: String?
     var failure: String?
 
     init(state: HomeState) {
-      username = state.username
       failure = state.failure
     }
   }
@@ -24,14 +22,6 @@ public struct HomeView: View {
     WithViewStore(store.scope(state: ViewState.init)) { viewStore in
       NavigationView {
         Form {
-          if let username = viewStore.username {
-            Section {
-              Text(username)
-            } header: {
-              Text("Username")
-            }
-          }
-
           if let failure = viewStore.failure {
             Section {
               Text(failure)
diff --git a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
index efdd867f..ddb6b035 100644
--- a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
@@ -47,7 +47,6 @@ final class HomeFeatureTests: XCTestCase {
       environment: .unimplemented
     )
 
-    let username = "test_username"
     let bgQueue = DispatchQueue.test
     let mainQueue = DispatchQueue.test
     var messengerDidStartWithTimeout: [Int] = []
@@ -62,15 +61,6 @@ final class HomeFeatureTests: XCTestCase {
     store.environment.messenger.isLoggedIn.run = { false }
     store.environment.messenger.isRegistered.run = { true }
     store.environment.messenger.logIn.run = { messengerDidLogIn += 1 }
-    store.environment.messenger.e2e.get = {
-      var e2e = E2E.unimplemented
-      e2e.getContact.run = {
-        var contact = Contact.unimplemented(Data())
-        contact.getFactsFromContact.run = { _ in [Fact(fact: username, type: 0)] }
-        return contact
-      }
-      return e2e
-    }
 
     store.send(.start)
 
@@ -81,10 +71,6 @@ final class HomeFeatureTests: XCTestCase {
     XCTAssertNoDifference(messengerDidLogIn, 1)
 
     mainQueue.advance()
-
-    store.receive(.set(\.$username, username)) {
-      $0.username = username
-    }
   }
 
   func testRegisterFinished() {
@@ -96,7 +82,6 @@ final class HomeFeatureTests: XCTestCase {
       environment: .unimplemented
     )
 
-    let username = "test_username"
     let bgQueue = DispatchQueue.test
     let mainQueue = DispatchQueue.test
     var messengerDidStartWithTimeout: [Int] = []
@@ -109,15 +94,6 @@ final class HomeFeatureTests: XCTestCase {
     store.environment.messenger.isLoggedIn.run = { false }
     store.environment.messenger.isRegistered.run = { true }
     store.environment.messenger.logIn.run = { messengerDidLogIn += 1 }
-    store.environment.messenger.e2e.get = {
-      var e2e = E2E.unimplemented
-      e2e.getContact.run = {
-        var contact = Contact.unimplemented(Data())
-        contact.getFactsFromContact.run = { _ in [Fact(fact: username, type: 0)] }
-        return contact
-      }
-      return e2e
-    }
 
     store.send(.register(.finished)) {
       $0.register = nil
@@ -131,10 +107,6 @@ final class HomeFeatureTests: XCTestCase {
     XCTAssertNoDifference(messengerDidLogIn, 1)
 
     mainQueue.advance()
-
-    store.receive(.set(\.$username, username)) {
-      $0.username = username
-    }
   }
 
   func testStartMessengerStartFailure() {
-- 
GitLab


From 747e6c6069c373b90d76df10ad642a97724510d2 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Mon, 5 Sep 2022 11:00:24 +0200
Subject: [PATCH 3/5] Add Stored.tryGet extension

---
 Sources/XXMessengerClient/Utils/Stored.swift | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/Sources/XXMessengerClient/Utils/Stored.swift b/Sources/XXMessengerClient/Utils/Stored.swift
index 3f5d5fdb..052d6387 100644
--- a/Sources/XXMessengerClient/Utils/Stored.swift
+++ b/Sources/XXMessengerClient/Utils/Stored.swift
@@ -31,6 +31,23 @@ private final class Memory<Value> {
   var value: Value
 }
 
+extension Stored {
+  public struct MissingValueError: Error, Equatable {
+    public init(typeDescription: String) {
+      self.typeDescription = typeDescription
+    }
+
+    public var typeDescription: String
+  }
+
+  public func tryGet<T>() throws -> T where Value == Optional<T> {
+    guard let value = get() else {
+      throw MissingValueError(typeDescription: "\(Self.self)")
+    }
+    return value
+  }
+}
+
 extension Stored {
   public static func unimplemented(placeholder: Value) -> Stored<Value> {
     Stored<Value>(
-- 
GitLab


From a020aae2f3cc8e55ff20de85552e1d4c30cdc30c Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Mon, 5 Sep 2022 11:18:41 +0200
Subject: [PATCH 4/5] Implement messenger account deletion example

---
 .../AppFeature/AppEnvironment+Live.swift      |   1 +
 .../Sources/AppFeature/AppFeature.swift       |   2 +-
 .../Sources/HomeFeature/Alerts.swift          |  22 ++++
 .../Sources/HomeFeature/HomeFeature.swift     |  48 +++++++-
 .../Sources/HomeFeature/HomeView.swift        |  23 ++++
 .../AppFeatureTests/AppFeatureTests.swift     |  30 +++++
 .../HomeFeatureTests/HomeFeatureTests.swift   | 109 ++++++++++++++++++
 7 files changed, 233 insertions(+), 2 deletions(-)
 create mode 100644 Examples/xx-messenger/Sources/HomeFeature/Alerts.swift

diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
index 3cbca1db..19f1b7f5 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
@@ -33,6 +33,7 @@ extension AppEnvironment {
       home: {
         HomeEnvironment(
           messenger: messenger,
+          db: dbManager.getDB,
           mainQueue: mainQueue,
           bgQueue: bgQueue,
           register: {
diff --git a/Examples/xx-messenger/Sources/AppFeature/AppFeature.swift b/Examples/xx-messenger/Sources/AppFeature/AppFeature.swift
index 535d69fc..582478d4 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppFeature.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppFeature.swift
@@ -68,7 +68,7 @@ extension AppEnvironment {
 let appReducer = Reducer<AppState, AppAction, AppEnvironment>
 { state, action, env in
   switch action {
-  case .start, .welcome(.finished), .restore(.finished):
+  case .start, .welcome(.finished), .restore(.finished), .home(.didDeleteAccount):
     state.screen = .loading
     return .run { subscriber in
       do {
diff --git a/Examples/xx-messenger/Sources/HomeFeature/Alerts.swift b/Examples/xx-messenger/Sources/HomeFeature/Alerts.swift
new file mode 100644
index 00000000..a3347be4
--- /dev/null
+++ b/Examples/xx-messenger/Sources/HomeFeature/Alerts.swift
@@ -0,0 +1,22 @@
+import ComposableArchitecture
+
+extension AlertState {
+  public static func confirmAccountDeletion() -> AlertState<HomeAction> {
+    AlertState<HomeAction>(
+      title: TextState("Delete Account"),
+      message: TextState("This will permanently delete your account and can't be undone."),
+      buttons: [
+        .destructive(TextState("Delete"), action: .send(.deleteAccountConfirmed)),
+        .cancel(TextState("Cancel"))
+      ]
+    )
+  }
+
+  public static func accountDeletionFailed(_ error: Error) -> AlertState<HomeAction> {
+    AlertState<HomeAction>(
+      title: TextState("Error"),
+      message: TextState(error.localizedDescription),
+      buttons: []
+    )
+  }
+}
diff --git a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
index 5c20364d..279a5628 100644
--- a/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
+++ b/Examples/xx-messenger/Sources/HomeFeature/HomeFeature.swift
@@ -1,3 +1,4 @@
+import AppCore
 import Combine
 import ComposableArchitecture
 import ComposablePresentation
@@ -9,18 +10,27 @@ import XXMessengerClient
 public struct HomeState: Equatable {
   public init(
     failure: String? = nil,
-    register: RegisterState? = nil
+    register: RegisterState? = nil,
+    alert: AlertState<HomeAction>? = nil,
+    isDeletingAccount: Bool = false
   ) {
     self.failure = failure
     self.register = register
+    self.alert = alert
+    self.isDeletingAccount = isDeletingAccount
   }
 
   @BindableState public var failure: String?
   @BindableState public var register: RegisterState?
+  @BindableState public var alert: AlertState<HomeAction>?
+  @BindableState public var isDeletingAccount: Bool
 }
 
 public enum HomeAction: Equatable, BindableAction {
   case start
+  case deleteAccountButtonTapped
+  case deleteAccountConfirmed
+  case didDeleteAccount
   case binding(BindingAction<HomeState>)
   case register(RegisterAction)
 }
@@ -28,17 +38,20 @@ public enum HomeAction: Equatable, BindableAction {
 public struct HomeEnvironment {
   public init(
     messenger: Messenger,
+    db: DBManagerGetDB,
     mainQueue: AnySchedulerOf<DispatchQueue>,
     bgQueue: AnySchedulerOf<DispatchQueue>,
     register: @escaping () -> RegisterEnvironment
   ) {
     self.messenger = messenger
+    self.db = db
     self.mainQueue = mainQueue
     self.bgQueue = bgQueue
     self.register = register
   }
 
   public var messenger: Messenger
+  public var db: DBManagerGetDB
   public var mainQueue: AnySchedulerOf<DispatchQueue>
   public var bgQueue: AnySchedulerOf<DispatchQueue>
   public var register: () -> RegisterEnvironment
@@ -47,6 +60,7 @@ public struct HomeEnvironment {
 extension HomeEnvironment {
   public static let unimplemented = HomeEnvironment(
     messenger: .unimplemented,
+    db: .unimplemented,
     mainQueue: .unimplemented,
     bgQueue: .unimplemented,
     register: { .unimplemented }
@@ -83,6 +97,38 @@ public let homeReducer = Reducer<HomeState, HomeAction, HomeEnvironment>
     .receive(on: env.mainQueue)
     .eraseToEffect()
 
+  case .deleteAccountButtonTapped:
+    state.alert = .confirmAccountDeletion()
+    return .none
+
+  case .deleteAccountConfirmed:
+    state.isDeletingAccount = true
+    return .run { subscriber in
+      do {
+        let contactId = try env.messenger.e2e.tryGet().getContact().getId()
+        let contact = try env.db().fetchContacts(.init(id: [contactId])).first
+        if let username = contact?.username {
+          let ud = try env.messenger.ud.tryGet()
+          try ud.permanentDeleteAccount(username: Fact(fact: username, type: 0))
+        }
+        try env.messenger.destroy()
+        try env.db().drop()
+        subscriber.send(.didDeleteAccount)
+      } catch {
+        subscriber.send(.set(\.$isDeletingAccount, false))
+        subscriber.send(.set(\.$alert, .accountDeletionFailed(error)))
+      }
+      subscriber.send(completion: .finished)
+      return AnyCancellable {}
+    }
+    .subscribe(on: env.bgQueue)
+    .receive(on: env.mainQueue)
+    .eraseToEffect()
+
+  case .didDeleteAccount:
+    state.isDeletingAccount = false
+    return .none
+
   case .register(.finished):
     state.register = nil
     return Effect(value: .start)
diff --git a/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift b/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
index 7dd8f668..f73f6b8a 100644
--- a/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
+++ b/Examples/xx-messenger/Sources/HomeFeature/HomeView.swift
@@ -12,9 +12,11 @@ public struct HomeView: View {
 
   struct ViewState: Equatable {
     var failure: String?
+    var isDeletingAccount: Bool
 
     init(state: HomeState) {
       failure = state.failure
+      isDeletingAccount = state.isDeletingAccount
     }
   }
 
@@ -34,8 +36,29 @@ public struct HomeView: View {
               Text("Error")
             }
           }
+
+          Section {
+            Button(role: .destructive) {
+              viewStore.send(.deleteAccountButtonTapped)
+            } label: {
+              HStack {
+                Text("Delete Account")
+                Spacer()
+                if viewStore.isDeletingAccount {
+                  ProgressView()
+                }
+              }
+            }
+            .disabled(viewStore.isDeletingAccount)
+          } header: {
+            Text("Account")
+          }
         }
         .navigationTitle("Home")
+        .alert(
+          store.scope(state: \.alert),
+          dismiss: HomeAction.set(\.$alert, nil)
+        )
       }
       .navigationViewStyle(.stack)
       .task { viewStore.send(.start) }
diff --git a/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift b/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift
index 5f055492..5ea96813 100644
--- a/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/AppFeatureTests/AppFeatureTests.swift
@@ -141,6 +141,36 @@ final class AppFeatureTests: XCTestCase {
     }
   }
 
+  func testHomeDidDeleteAccount() {
+    let store = TestStore(
+      initialState: AppState(
+        screen: .home(HomeState())
+      ),
+      reducer: appReducer,
+      environment: .unimplemented
+    )
+
+    let mainQueue = DispatchQueue.test
+    let bgQueue = DispatchQueue.test
+
+    store.environment.mainQueue = mainQueue.eraseToAnyScheduler()
+    store.environment.bgQueue = bgQueue.eraseToAnyScheduler()
+    store.environment.dbManager.hasDB.run = { true }
+    store.environment.messenger.isLoaded.run = { false }
+    store.environment.messenger.isCreated.run = { false }
+
+    store.send(.home(.didDeleteAccount)) {
+      $0.screen = .loading
+    }
+
+    bgQueue.advance()
+    mainQueue.advance()
+
+    store.receive(.set(\.$screen, .welcome(WelcomeState()))) {
+      $0.screen = .welcome(WelcomeState())
+    }
+  }
+
   func testWelcomeRestoreTapped() {
     let store = TestStore(
       initialState: AppState(
diff --git a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
index ddb6b035..4fbad5b8 100644
--- a/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/HomeFeatureTests/HomeFeatureTests.swift
@@ -3,6 +3,7 @@ import RegisterFeature
 import XCTest
 import XXClient
 import XXMessengerClient
+import XXModels
 @testable import HomeFeature
 
 final class HomeFeatureTests: XCTestCase {
@@ -201,4 +202,112 @@ final class HomeFeatureTests: XCTestCase {
       $0.failure = error.localizedDescription
     }
   }
+
+  func testAccountDeletion() {
+    let store = TestStore(
+      initialState: HomeState(),
+      reducer: homeReducer,
+      environment: .unimplemented
+    )
+
+    var dbDidFetchContacts: [XXModels.Contact.Query] = []
+    var udDidPermanentDeleteAccount: [Fact] = []
+    var messengerDidDestroy = 0
+    var dbDidDrop = 0
+
+    store.environment.bgQueue = .immediate
+    store.environment.mainQueue = .immediate
+    store.environment.messenger.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getContact.run = {
+        var contact = Contact.unimplemented("contact-data".data(using: .utf8)!)
+        contact.getIdFromContact.run = { _ in "contact-id".data(using: .utf8)! }
+        return contact
+      }
+      return e2e
+    }
+    store.environment.db.run = {
+      var db: Database = .failing
+      db.fetchContacts.run = { query in
+        dbDidFetchContacts.append(query)
+        return [
+          XXModels.Contact(
+            id: "contact-id".data(using: .utf8)!,
+            marshaled: "contact-data".data(using: .utf8)!,
+            username: "MyUsername"
+          )
+        ]
+      }
+      db.drop.run = {
+        dbDidDrop += 1
+      }
+      return db
+    }
+    store.environment.messenger.ud.get = {
+      var ud: UserDiscovery = .unimplemented
+      ud.permanentDeleteAccount.run = { usernameFact in
+        udDidPermanentDeleteAccount.append(usernameFact)
+      }
+      return ud
+    }
+    store.environment.messenger.destroy.run = {
+      messengerDidDestroy += 1
+    }
+
+    store.send(.deleteAccountButtonTapped) {
+      $0.alert = .confirmAccountDeletion()
+    }
+
+    store.send(.set(\.$alert, nil)) {
+      $0.alert = nil
+    }
+
+    store.send(.deleteAccountConfirmed) {
+      $0.isDeletingAccount = true
+    }
+
+    XCTAssertNoDifference(dbDidFetchContacts, [.init(id: ["contact-id".data(using: .utf8)!])])
+    XCTAssertNoDifference(udDidPermanentDeleteAccount, [Fact(fact: "MyUsername", type: 0)])
+    XCTAssertNoDifference(messengerDidDestroy, 1)
+    XCTAssertNoDifference(dbDidDrop, 1)
+
+    store.receive(.didDeleteAccount) {
+      $0.isDeletingAccount = false
+    }
+  }
+
+  func testAccountDeletionFailure() {
+    let store = TestStore(
+      initialState: HomeState(),
+      reducer: homeReducer,
+      environment: .unimplemented
+    )
+
+    struct Failure: Error {}
+    let error = Failure()
+
+    store.environment.bgQueue = .immediate
+    store.environment.mainQueue = .immediate
+    store.environment.messenger.e2e.get = {
+      var e2e: E2E = .unimplemented
+      e2e.getContact.run = {
+        var contact = Contact.unimplemented("contact-data".data(using: .utf8)!)
+        contact.getIdFromContact.run = { _ in throw error }
+        return contact
+      }
+      return e2e
+    }
+
+    store.send(.deleteAccountConfirmed) {
+      $0.isDeletingAccount = true
+    }
+
+    store.receive(.set(\.$isDeletingAccount, false)) {
+      $0.isDeletingAccount = false
+    }
+
+    store.receive(.set(\.$alert, .accountDeletionFailed(error))) {
+      $0.alert = .accountDeletionFailed(error)
+    }
+  }
 }
-- 
GitLab


From a5c9f161fb92c062a99d11895ba885e74a8405bc Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Mon, 5 Sep 2022 11:22:30 +0200
Subject: [PATCH 5/5] Update buttons appearance

---
 .../Sources/RegisterFeature/RegisterView.swift           | 8 +++-----
 .../Sources/WelcomeFeature/WelcomeView.swift             | 9 +++------
 2 files changed, 6 insertions(+), 11 deletions(-)

diff --git a/Examples/xx-messenger/Sources/RegisterFeature/RegisterView.swift b/Examples/xx-messenger/Sources/RegisterFeature/RegisterView.swift
index 27bc0962..195b655b 100644
--- a/Examples/xx-messenger/Sources/RegisterFeature/RegisterView.swift
+++ b/Examples/xx-messenger/Sources/RegisterFeature/RegisterView.swift
@@ -46,14 +46,12 @@ public struct RegisterView: View {
               viewStore.send(.registerTapped)
             } label: {
               HStack {
+                Text("Register")
+                Spacer()
                 if viewStore.isRegistering {
-                  ProgressView().padding(.trailing)
-                  Text("Registering...")
-                } else {
-                  Text("Register")
+                  ProgressView()
                 }
               }
-              .frame(maxWidth: .infinity)
             }
           }
 
diff --git a/Examples/xx-messenger/Sources/WelcomeFeature/WelcomeView.swift b/Examples/xx-messenger/Sources/WelcomeFeature/WelcomeView.swift
index d3686c5a..1205c67e 100644
--- a/Examples/xx-messenger/Sources/WelcomeFeature/WelcomeView.swift
+++ b/Examples/xx-messenger/Sources/WelcomeFeature/WelcomeView.swift
@@ -29,21 +29,18 @@ public struct WelcomeView: View {
               viewStore.send(.newAccountTapped)
             } label: {
               HStack {
+                Text("New Account")
+                Spacer()
                 if viewStore.isCreatingAccount {
-                  ProgressView().padding(.trailing)
-                  Text("Creating Account...")
-                } else {
-                  Text("New Account")
+                  ProgressView()
                 }
               }
-              .frame(maxWidth: .infinity)
             }
 
             Button {
               viewStore.send(.restoreTapped)
             } label: {
               Text("Restore from Backup")
-                .frame(maxWidth: .infinity)
             }
           }
         }
-- 
GitLab