mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-10-31 08:15:24 +00:00
Ignore repeated confirms on the server selection screen
Tapping Continue and then the keyboard's Done key (or double tapping Continue) started two server configurations. With a password-only server both asked the flow to show the login screen, and the second request hit the authentication state machine's unexpected transition fatalError. Keep the running confirmation task and ignore further confirms until it finishes, so the flow continues once. The guard clears afterwards so confirming again after going back from the login screen still works. Fixes ELEMENT-X-IOS-XED
This commit is contained in:
+11
-5
@@ -20,6 +20,8 @@ class ServerSelectionScreenViewModel: ServerSelectionScreenViewModelType, Server
|
|||||||
|
|
||||||
/// Debounces autocompletion from user input
|
/// Debounces autocompletion from user input
|
||||||
private var debouncedAutocompleteTask: Task<Void, Never>?
|
private var debouncedAutocompleteTask: Task<Void, Never>?
|
||||||
|
/// The confirmation in progress, used to ignore repeated confirms (e.g. Continue then keyboard Done).
|
||||||
|
private var confirmTask: Task<Void, Never>?
|
||||||
|
|
||||||
private var actionsSubject: PassthroughSubject<ServerSelectionScreenViewModelAction, Never> = .init()
|
private var actionsSubject: PassthroughSubject<ServerSelectionScreenViewModelAction, Never> = .init()
|
||||||
|
|
||||||
@@ -65,11 +67,15 @@ class ServerSelectionScreenViewModel: ServerSelectionScreenViewModelType, Server
|
|||||||
guard state.textField != textField else { return } // stop the infinite loop
|
guard state.textField != textField else { return } // stop the infinite loop
|
||||||
state.textField = textField
|
state.textField = textField
|
||||||
case .confirm:
|
case .confirm:
|
||||||
switch state.mode {
|
guard confirmTask == nil else { return }
|
||||||
case .userInput:
|
confirmTask = Task {
|
||||||
Task { await configureHomeserver() }
|
switch state.mode {
|
||||||
case .picker:
|
case .userInput:
|
||||||
Task { await pickServer() }
|
await configureHomeserver()
|
||||||
|
case .picker:
|
||||||
|
await pickServer()
|
||||||
|
}
|
||||||
|
confirmTask = nil
|
||||||
}
|
}
|
||||||
case .clearFooterError:
|
case .clearFooterError:
|
||||||
clearFooterError()
|
clearFooterError()
|
||||||
|
|||||||
@@ -210,6 +210,36 @@ struct ServerSelectionScreenViewModelTests {
|
|||||||
#expect(clientFactory.makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 2)
|
#expect(clientFactory.makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
mutating func userInputConfirmTwiceContinuesOnce() async throws {
|
||||||
|
// Given a view model for login with a server that doesn't support OAuth.
|
||||||
|
try setup(authenticationFlow: .login)
|
||||||
|
context.serverNameOrBaseURL = Self.passwordOnlyServer
|
||||||
|
|
||||||
|
// When confirming a second time before the first confirmation has finished (e.g. Continue then keyboard Done).
|
||||||
|
var continueCount = 0
|
||||||
|
let deferredFailure = deferFailure(viewModel.actions, timeout: .seconds(1), message: "The flow should only continue once.") { action in
|
||||||
|
guard action.isContinueWithPassword else { return false }
|
||||||
|
continueCount += 1
|
||||||
|
return continueCount > 1
|
||||||
|
}
|
||||||
|
context.send(viewAction: .confirm)
|
||||||
|
context.send(viewAction: .confirm)
|
||||||
|
try await deferredFailure.fulfill()
|
||||||
|
|
||||||
|
// Then the server should only be configured once.
|
||||||
|
#expect(continueCount == 1)
|
||||||
|
#expect(clientFactory.makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 1)
|
||||||
|
|
||||||
|
// And when confirming again once finished (e.g. after navigating back from the login screen).
|
||||||
|
let deferred = deferFulfillment(viewModel.actions) { $0.isContinueWithPassword }
|
||||||
|
context.send(viewAction: .confirm)
|
||||||
|
try await deferred.fulfill()
|
||||||
|
|
||||||
|
// Then the flow should continue again.
|
||||||
|
#expect(clientFactory.makeAuthenticationClientServerNameOrBaseURLSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount == 2)
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: - Picker mode
|
// MARK: - Picker mode
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user