mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
Fix a bug where the onboarding flow was dismissed by logging out. (#5481)
* Fix a bug where the onboarding flow was dismissed by logging out. * Add some tests for the available actions while we're here. --------- Co-authored-by: Stefan Ceriu <stefan.ceriu@gmail.com>
This commit is contained in:
@@ -23,6 +23,7 @@
|
|||||||
/* End PBXAggregateTarget section */
|
/* End PBXAggregateTarget section */
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
00091F143129B9A4904563C4 /* IdentityConfirmationScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBBCD7444C2B515D02EC0E8 /* IdentityConfirmationScreenViewModelTests.swift */; };
|
||||||
0033481EE363E4914295F188 /* LocalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C070FD43DC6BF4E50217965A /* LocalizationTests.swift */; };
|
0033481EE363E4914295F188 /* LocalizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C070FD43DC6BF4E50217965A /* LocalizationTests.swift */; };
|
||||||
00C3023B6DF55024D8876B76 /* ShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 3D8BEEFCA07BEA43F4F4BF77 /* ShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
00C3023B6DF55024D8876B76 /* ShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 3D8BEEFCA07BEA43F4F4BF77 /* ShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||||
012D9DDCDE6278E4E0CDCC0F /* LinkNewDeviceFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94EE2C5F0A06F146BBE3A1B1 /* LinkNewDeviceFlowCoordinator.swift */; };
|
012D9DDCDE6278E4E0CDCC0F /* LinkNewDeviceFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94EE2C5F0A06F146BBE3A1B1 /* LinkNewDeviceFlowCoordinator.swift */; };
|
||||||
@@ -2942,6 +2943,7 @@
|
|||||||
EBEB8D9F4940E161B18FE4BC /* UITestsNotificationCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsNotificationCenter.swift; sourceTree = "<group>"; };
|
EBEB8D9F4940E161B18FE4BC /* UITestsNotificationCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsNotificationCenter.swift; sourceTree = "<group>"; };
|
||||||
EC589E641AE46EFB2962534D /* RoomMemberDetailsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetailsViewModelTests.swift; sourceTree = "<group>"; };
|
EC589E641AE46EFB2962534D /* RoomMemberDetailsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMemberDetailsViewModelTests.swift; sourceTree = "<group>"; };
|
||||||
ECB836DD8BE31931F51B8AC9 /* EncryptionSettingsFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionSettingsFlowCoordinator.swift; sourceTree = "<group>"; };
|
ECB836DD8BE31931F51B8AC9 /* EncryptionSettingsFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionSettingsFlowCoordinator.swift; sourceTree = "<group>"; };
|
||||||
|
ECBBCD7444C2B515D02EC0E8 /* IdentityConfirmationScreenViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityConfirmationScreenViewModelTests.swift; sourceTree = "<group>"; };
|
||||||
ECD5FCBA169B6A82F501CA1B /* AnalyticsSettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
ECD5FCBA169B6A82F501CA1B /* AnalyticsSettingsScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsSettingsScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
ECE03E834CC8C2721899E6AC /* StaticLocationSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLocationSheet.swift; sourceTree = "<group>"; };
|
ECE03E834CC8C2721899E6AC /* StaticLocationSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLocationSheet.swift; sourceTree = "<group>"; };
|
||||||
ECEC9A622AA2F8DBE928AC78 /* FloatingDateBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingDateBadge.swift; sourceTree = "<group>"; };
|
ECEC9A622AA2F8DBE928AC78 /* FloatingDateBadge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FloatingDateBadge.swift; sourceTree = "<group>"; };
|
||||||
@@ -4920,6 +4922,7 @@
|
|||||||
EA4D639E27D5882A6A71AECF /* GlobalSearchScreenViewModelTests.swift */,
|
EA4D639E27D5882A6A71AECF /* GlobalSearchScreenViewModelTests.swift */,
|
||||||
B73587C2E3CF5998361AE516 /* HomeScreenRoomTests.swift */,
|
B73587C2E3CF5998361AE516 /* HomeScreenRoomTests.swift */,
|
||||||
505208F28007C0FEC14E1FF0 /* HomeScreenViewModelTests.swift */,
|
505208F28007C0FEC14E1FF0 /* HomeScreenViewModelTests.swift */,
|
||||||
|
ECBBCD7444C2B515D02EC0E8 /* IdentityConfirmationScreenViewModelTests.swift */,
|
||||||
845DDBDE5A0887E73D38B826 /* InviteUsersViewModelTests.swift */,
|
845DDBDE5A0887E73D38B826 /* InviteUsersViewModelTests.swift */,
|
||||||
DE5127D6EA05B2E45D0A7D59 /* JoinRoomScreenViewModelTests.swift */,
|
DE5127D6EA05B2E45D0A7D59 /* JoinRoomScreenViewModelTests.swift */,
|
||||||
FDB9C37196A4C79F24CE80C6 /* KeychainControllerTests.swift */,
|
FDB9C37196A4C79F24CE80C6 /* KeychainControllerTests.swift */,
|
||||||
@@ -7876,6 +7879,7 @@
|
|||||||
EE56238683BC3ECA9BA00684 /* GlobalSearchScreenViewModelTests.swift in Sources */,
|
EE56238683BC3ECA9BA00684 /* GlobalSearchScreenViewModelTests.swift in Sources */,
|
||||||
6817EAD73DC1FFD8B943B5B9 /* HomeScreenRoomTests.swift in Sources */,
|
6817EAD73DC1FFD8B943B5B9 /* HomeScreenRoomTests.swift in Sources */,
|
||||||
F6F49E37272AD7397CD29A01 /* HomeScreenViewModelTests.swift in Sources */,
|
F6F49E37272AD7397CD29A01 /* HomeScreenViewModelTests.swift in Sources */,
|
||||||
|
00091F143129B9A4904563C4 /* IdentityConfirmationScreenViewModelTests.swift in Sources */,
|
||||||
A23B8B27A1436A1049EEF68E /* InfoPlistReader.swift in Sources */,
|
A23B8B27A1436A1049EEF68E /* InfoPlistReader.swift in Sources */,
|
||||||
A216C83ADCF32BA5EF8A6FBC /* InviteUsersViewModelTests.swift in Sources */,
|
A216C83ADCF32BA5EF8A6FBC /* InviteUsersViewModelTests.swift in Sources */,
|
||||||
7C0E29E0279866C62EC67A28 /* JoinRoomScreenViewModelTests.swift in Sources */,
|
7C0E29E0279866C62EC67A28 /* JoinRoomScreenViewModelTests.swift in Sources */,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import SwiftState
|
|||||||
enum OnboardingFlowCoordinatorAction {
|
enum OnboardingFlowCoordinatorAction {
|
||||||
case requestPresentation(animated: Bool)
|
case requestPresentation(animated: Bool)
|
||||||
case dismiss
|
case dismiss
|
||||||
case logout
|
case logoutConfirmed
|
||||||
}
|
}
|
||||||
|
|
||||||
class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
|
class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
|
||||||
@@ -260,8 +260,8 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
stateMachine.tryEvent(.nextSkippingIdentityConfirmed)
|
stateMachine.tryEvent(.nextSkippingIdentityConfirmed)
|
||||||
case .reset:
|
case .reset:
|
||||||
startEncryptionResetFlow()
|
startEncryptionResetFlow()
|
||||||
case .logout:
|
case .logoutConfirmed:
|
||||||
actionsSubject.send(.logout)
|
actionsSubject.send(.logoutConfirmed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|||||||
@@ -275,8 +275,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
navigationTabCoordinator.setFullScreenCoverCoordinator(onboardingStackCoordinator, animated: animated)
|
navigationTabCoordinator.setFullScreenCoverCoordinator(onboardingStackCoordinator, animated: animated)
|
||||||
case .dismiss:
|
case .dismiss:
|
||||||
navigationTabCoordinator.setFullScreenCoverCoordinator(nil)
|
navigationTabCoordinator.setFullScreenCoverCoordinator(nil)
|
||||||
case .logout:
|
case .logoutConfirmed:
|
||||||
logout()
|
actionsSubject.send(.logout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
@@ -494,7 +494,12 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
guard isLastDevice else {
|
guard isLastDevice else {
|
||||||
logout()
|
navigationRootCoordinator.alertInfo = .init(id: .init(),
|
||||||
|
title: L10n.screenSignoutConfirmationDialogTitle,
|
||||||
|
message: L10n.screenSignoutConfirmationDialogContent,
|
||||||
|
primaryButton: .init(title: L10n.screenSignoutConfirmationDialogSubmit, role: .destructive) { [weak self] in
|
||||||
|
self?.actionsSubject.send(.logout)
|
||||||
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -525,15 +530,6 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
presentSecureBackupLogoutConfirmationScreen()
|
presentSecureBackupLogoutConfirmationScreen()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func logout() {
|
|
||||||
navigationRootCoordinator.alertInfo = .init(id: .init(),
|
|
||||||
title: L10n.screenSignoutConfirmationDialogTitle,
|
|
||||||
message: L10n.screenSignoutConfirmationDialogContent,
|
|
||||||
primaryButton: .init(title: L10n.screenSignoutConfirmationDialogSubmit, role: .destructive) { [weak self] in
|
|
||||||
self?.actionsSubject.send(.logout)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private func presentSecureBackupLogoutConfirmationScreen() {
|
private func presentSecureBackupLogoutConfirmationScreen() {
|
||||||
let coordinator = SecureBackupLogoutConfirmationScreenCoordinator(parameters: .init(secureBackupController: userSession.clientProxy.secureBackupController,
|
let coordinator = SecureBackupLogoutConfirmationScreenCoordinator(parameters: .init(secureBackupController: userSession.clientProxy.secureBackupController,
|
||||||
homeserverReachabilityPublisher: userSession.clientProxy.homeserverReachabilityPublisher))
|
homeserverReachabilityPublisher: userSession.clientProxy.homeserverReachabilityPublisher))
|
||||||
|
|||||||
+3
-3
@@ -21,7 +21,7 @@ enum IdentityConfirmationScreenCoordinatorAction {
|
|||||||
/// Only possible in debug builds.
|
/// Only possible in debug builds.
|
||||||
case skip
|
case skip
|
||||||
case reset
|
case reset
|
||||||
case logout
|
case logoutConfirmed
|
||||||
}
|
}
|
||||||
|
|
||||||
final class IdentityConfirmationScreenCoordinator: CoordinatorProtocol {
|
final class IdentityConfirmationScreenCoordinator: CoordinatorProtocol {
|
||||||
@@ -57,8 +57,8 @@ final class IdentityConfirmationScreenCoordinator: CoordinatorProtocol {
|
|||||||
actionsSubject.send(.skip)
|
actionsSubject.send(.skip)
|
||||||
case .reset:
|
case .reset:
|
||||||
actionsSubject.send(.reset)
|
actionsSubject.send(.reset)
|
||||||
case .logout:
|
case .logoutConfirmed:
|
||||||
actionsSubject.send(.logout)
|
actionsSubject.send(.logoutConfirmed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|||||||
+11
-1
@@ -14,7 +14,7 @@ enum IdentityConfirmationScreenViewModelAction {
|
|||||||
/// Only possible in debug builds.
|
/// Only possible in debug builds.
|
||||||
case skip
|
case skip
|
||||||
case reset
|
case reset
|
||||||
case logout
|
case logoutConfirmed
|
||||||
}
|
}
|
||||||
|
|
||||||
struct IdentityConfirmationScreenViewState: BindableState {
|
struct IdentityConfirmationScreenViewState: BindableState {
|
||||||
@@ -25,6 +25,16 @@ struct IdentityConfirmationScreenViewState: BindableState {
|
|||||||
|
|
||||||
var availableActions: [AvailableActions]?
|
var availableActions: [AvailableActions]?
|
||||||
let learnMoreURL: URL
|
let learnMoreURL: URL
|
||||||
|
|
||||||
|
var bindings = IdentityConfirmationScreenBindings()
|
||||||
|
}
|
||||||
|
|
||||||
|
struct IdentityConfirmationScreenBindings {
|
||||||
|
var alertInfo: AlertInfo<IdentityConfirmationScreenAlertType>?
|
||||||
|
}
|
||||||
|
|
||||||
|
enum IdentityConfirmationScreenAlertType {
|
||||||
|
case logout
|
||||||
}
|
}
|
||||||
|
|
||||||
enum IdentityConfirmationScreenViewAction {
|
enum IdentityConfirmationScreenViewAction {
|
||||||
|
|||||||
+14
-1
@@ -49,7 +49,7 @@ class IdentityConfirmationScreenViewModel: IdentityConfirmationScreenViewModelTy
|
|||||||
case .reset:
|
case .reset:
|
||||||
actionsSubject.send(.reset)
|
actionsSubject.send(.reset)
|
||||||
case .logout:
|
case .logout:
|
||||||
actionsSubject.send(.logout)
|
confirmLogout()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,6 +88,19 @@ class IdentityConfirmationScreenViewModel: IdentityConfirmationScreenViewModelTy
|
|||||||
state.availableActions = availableActions
|
state.availableActions = availableActions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func confirmLogout() {
|
||||||
|
// We need to show the confirmation within this flow as letting the UserSession flow do it results in the
|
||||||
|
// onboarding flow's modal being dismissed (by SwiftUI, not us). However we don't need any of the additional
|
||||||
|
// checks made in the UserSession flow as the user's account isn't verified so there's no much they can do unless
|
||||||
|
// they complete verification.
|
||||||
|
state.bindings.alertInfo = .init(id: .logout,
|
||||||
|
title: L10n.screenSignoutConfirmationDialogTitle,
|
||||||
|
message: L10n.screenSignoutConfirmationDialogContent,
|
||||||
|
primaryButton: .init(title: L10n.screenSignoutConfirmationDialogSubmit, role: .destructive) { [weak self] in
|
||||||
|
self?.actionsSubject.send(.logoutConfirmed)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
private static let loadingIndicatorIdentifier = "\(IdentityConfirmationScreenViewModel.self)-Loading"
|
private static let loadingIndicatorIdentifier = "\(IdentityConfirmationScreenViewModel.self)-Loading"
|
||||||
|
|
||||||
private func showLoadingIndicator() {
|
private func showLoadingIndicator() {
|
||||||
|
|||||||
+2
-1
@@ -10,7 +10,7 @@ import Compound
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct IdentityConfirmationScreen: View {
|
struct IdentityConfirmationScreen: View {
|
||||||
let context: IdentityConfirmationScreenViewModel.Context
|
@Bindable var context: IdentityConfirmationScreenViewModel.Context
|
||||||
|
|
||||||
var shouldShowSkipButton: Bool {
|
var shouldShowSkipButton: Bool {
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
@@ -31,6 +31,7 @@ struct IdentityConfirmationScreen: View {
|
|||||||
.backgroundStyle(.compound.bgCanvasDefault)
|
.backgroundStyle(.compound.bgCanvasDefault)
|
||||||
.navigationBarBackButtonHidden(true)
|
.navigationBarBackButtonHidden(true)
|
||||||
.interactiveDismissDisabled()
|
.interactiveDismissDisabled()
|
||||||
|
.alert(item: $context.alertInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2026 Element Creations Ltd.
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
// Please see LICENSE files in the repository root for full details.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
@testable import ElementX
|
||||||
|
import Testing
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
struct IdentityConfirmationScreenViewModelTests {
|
||||||
|
var securityStateSubject: CurrentValueSubject<SessionSecurityState, Never>!
|
||||||
|
|
||||||
|
var viewModel: IdentityConfirmationScreenViewModel!
|
||||||
|
var context: IdentityConfirmationScreenViewModel.Context {
|
||||||
|
viewModel.context
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
mutating func logoutShowsConfirmation() async throws {
|
||||||
|
setupViewModel()
|
||||||
|
|
||||||
|
#expect(context.alertInfo == nil)
|
||||||
|
|
||||||
|
context.send(viewAction: .logout)
|
||||||
|
|
||||||
|
let alertInfo = try #require(context.alertInfo)
|
||||||
|
|
||||||
|
let deferred = deferFulfillment(viewModel.actionsPublisher) { $0 == .logoutConfirmed }
|
||||||
|
alertInfo.primaryButton.action?()
|
||||||
|
try await deferred.fulfill()
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Available Actions
|
||||||
|
|
||||||
|
@Test
|
||||||
|
mutating func availableActionsWithDevicesAndRecovery() async throws {
|
||||||
|
setupViewModel(hasDevicesToVerifyAgainst: true)
|
||||||
|
#expect(context.viewState.availableActions == nil)
|
||||||
|
|
||||||
|
let deferred = deferFulfillment(context.observe(\.viewState.availableActions)) { $0 != nil }
|
||||||
|
securityStateSubject.send(.init(verificationState: .unverified, recoveryState: .enabled))
|
||||||
|
try await deferred.fulfill()
|
||||||
|
|
||||||
|
let availableActions = try #require(context.viewState.availableActions)
|
||||||
|
#expect(availableActions == [.interactiveVerification, .recovery])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
mutating func availableActionsWithDevices() async throws {
|
||||||
|
setupViewModel(hasDevicesToVerifyAgainst: true)
|
||||||
|
#expect(context.viewState.availableActions == nil)
|
||||||
|
|
||||||
|
let deferred = deferFulfillment(context.observe(\.viewState.availableActions)) { $0 != nil }
|
||||||
|
securityStateSubject.send(.init(verificationState: .unverified, recoveryState: .disabled))
|
||||||
|
try await deferred.fulfill()
|
||||||
|
|
||||||
|
let availableActions = try #require(context.viewState.availableActions)
|
||||||
|
#expect(availableActions == [.interactiveVerification])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
mutating func availableActionsWithRecovery() async throws {
|
||||||
|
setupViewModel(hasDevicesToVerifyAgainst: false)
|
||||||
|
#expect(context.viewState.availableActions == nil)
|
||||||
|
|
||||||
|
let deferred = deferFulfillment(context.observe(\.viewState.availableActions)) { $0 != nil }
|
||||||
|
securityStateSubject.send(.init(verificationState: .unverified, recoveryState: .enabled))
|
||||||
|
try await deferred.fulfill()
|
||||||
|
|
||||||
|
let availableActions = try #require(context.viewState.availableActions)
|
||||||
|
#expect(availableActions == [.recovery])
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
mutating func availableActionsWithoutDevicesOrRecovery() async throws {
|
||||||
|
setupViewModel(hasDevicesToVerifyAgainst: false)
|
||||||
|
#expect(context.viewState.availableActions == nil)
|
||||||
|
|
||||||
|
let deferred = deferFulfillment(context.observe(\.viewState.availableActions)) { $0 != nil }
|
||||||
|
securityStateSubject.send(.init(verificationState: .unverified, recoveryState: .disabled))
|
||||||
|
try await deferred.fulfill()
|
||||||
|
|
||||||
|
let availableActions = try #require(context.viewState.availableActions)
|
||||||
|
#expect(availableActions.isEmpty)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
mutating func availableActionsWhileSecurityStateIsPending() async throws {
|
||||||
|
setupViewModel(hasDevicesToVerifyAgainst: true)
|
||||||
|
|
||||||
|
let deferred = deferFailure(context.observe(\.viewState.availableActions), timeout: .seconds(1)) { $0 != nil }
|
||||||
|
try await deferred.fulfill()
|
||||||
|
|
||||||
|
#expect(context.viewState.availableActions == nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MARK: - Private
|
||||||
|
|
||||||
|
mutating func setupViewModel(hasDevicesToVerifyAgainst: Bool = true) {
|
||||||
|
let initialState = SessionSecurityState(verificationState: .unverified, recoveryState: .unknown)
|
||||||
|
securityStateSubject = CurrentValueSubject<SessionSecurityState, Never>(initialState)
|
||||||
|
|
||||||
|
let clientProxy = ClientProxyMock(.init())
|
||||||
|
clientProxy.hasDevicesToVerifyAgainstReturnValue = .success(hasDevicesToVerifyAgainst)
|
||||||
|
let userSession = UserSessionMock(.init(clientProxy: clientProxy))
|
||||||
|
userSession.sessionSecurityStatePublisher = securityStateSubject.asCurrentValuePublisher()
|
||||||
|
|
||||||
|
viewModel = IdentityConfirmationScreenViewModel(userSession: userSession,
|
||||||
|
appSettings: AppSettings(),
|
||||||
|
userIndicatorController: UserIndicatorControllerMock())
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user