Selecting a server that doesn't support login now fails instead of letting you continue to a failure later. (#3342)
* Fail configuration of the authentication service if the homeserver doesn't support login. * Move the ServerSelectionCoordinator logic into the ViewModel. - Handle the new login alert. - Add more tests
This commit is contained in:
@@ -829,7 +829,6 @@
|
||||
B6DA66EFC13A90846B625836 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 91DE43B8815918E590912DDA /* InfoPlist.strings */; };
|
||||
B6DF6B6FA8734B70F9BF261E /* BlurHashDecode.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5272BC4A60B6AD7553BACA1 /* BlurHashDecode.swift */; };
|
||||
B6EC2148FA5443C9289BEEBA /* MediaProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = F17EFA1D3D09FC2F9C5E1CB2 /* MediaProvider.swift */; };
|
||||
B721125D17A0BA86794F29FB /* MockServerSelectionScreenState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8E057FB1F07A5C201C89061 /* MockServerSelectionScreenState.swift */; };
|
||||
B773ACD8881DB18E876D950C /* WaveformSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94028A227645FA880B966211 /* WaveformSource.swift */; };
|
||||
B7888FC1E1DEF816D175C8D6 /* SecureBackupKeyBackupScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD72A9B720D75DBE60AC299F /* SecureBackupKeyBackupScreenModels.swift */; };
|
||||
B796A25F282C0A340D1B9C12 /* ImageRoomTimelineItemContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2B5EDCD05D50BA9B815C66C /* ImageRoomTimelineItemContent.swift */; };
|
||||
@@ -2141,7 +2140,6 @@
|
||||
D79BB714D28C9F588DD69353 /* SecureBackupScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
D7BB243B26D54EF1A0C422C0 /* NotificationContentBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationContentBuilder.swift; sourceTree = "<group>"; };
|
||||
D7BEB970F500BFB248443FA1 /* BloomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BloomView.swift; sourceTree = "<group>"; };
|
||||
D8E057FB1F07A5C201C89061 /* MockServerSelectionScreenState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockServerSelectionScreenState.swift; sourceTree = "<group>"; };
|
||||
D8E60332509665C00179ACF6 /* MessageForwardingScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageForwardingScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
D8F5F9E02B1AB5350B1815E7 /* TimelineStartRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStartRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||
D8FC33C3F6BF597E095CE9FA /* HomeScreenInviteCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeScreenInviteCell.swift; sourceTree = "<group>"; };
|
||||
@@ -2815,7 +2813,6 @@
|
||||
2D0D49B0533C4C2EB889BF3A /* ServerSelectionScreen */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D8E057FB1F07A5C201C89061 /* MockServerSelectionScreenState.swift */,
|
||||
BB8BC4C791D0E88CFCF4E5DF /* ServerSelectionScreenCoordinator.swift */,
|
||||
9501D11B4258DFA33BA3B40F /* ServerSelectionScreenModels.swift */,
|
||||
E3059CFA00C67D8787273B20 /* ServerSelectionScreenViewModel.swift */,
|
||||
@@ -6593,7 +6590,6 @@
|
||||
C97325EFDCCEE457432A9E82 /* MessageText.swift in Sources */,
|
||||
B659E3A49889E749E3239EA7 /* MockMediaProvider.swift in Sources */,
|
||||
09C83DDDB07C28364F325209 /* MockRoomTimelineController.swift in Sources */,
|
||||
B721125D17A0BA86794F29FB /* MockServerSelectionScreenState.swift in Sources */,
|
||||
AF2ABA2794E376B64104C964 /* MockSoftLogoutScreenState.swift in Sources */,
|
||||
F9842667B68DC6FA1F9ECCBB /* NSItemProvider.swift in Sources */,
|
||||
EA01A06EEDFEF4AE7652E5F3 /* NSRegularExpresion.swift in Sources */,
|
||||
|
||||
@@ -72,6 +72,8 @@ enum ServerConfirmationScreenAlert: Hashable {
|
||||
case invalidWellKnown(String)
|
||||
/// An alert that allows the user to learn about sliding sync.
|
||||
case slidingSync
|
||||
/// An alert that informs the user that login isn't supported.
|
||||
case login
|
||||
/// An alert that informs the user that registration isn't supported.
|
||||
case registration
|
||||
/// An unknown error has occurred.
|
||||
|
||||
@@ -82,6 +82,8 @@ class ServerConfirmationScreenViewModel: ServerConfirmationScreenViewModelType,
|
||||
displayError(.invalidWellKnown(error))
|
||||
case .slidingSyncNotAvailable:
|
||||
displayError(.slidingSync)
|
||||
case .loginNotSupported:
|
||||
displayError(.login)
|
||||
case .registrationNotSupported:
|
||||
displayError(.registration)
|
||||
default:
|
||||
@@ -117,9 +119,13 @@ class ServerConfirmationScreenViewModel: ServerConfirmationScreenViewModelType,
|
||||
message: L10n.screenChangeServerErrorNoSlidingSyncMessage,
|
||||
primaryButton: .init(title: L10n.actionLearnMore, role: .cancel, action: openURL),
|
||||
secondaryButton: .init(title: L10n.actionCancel, action: nil))
|
||||
case .login:
|
||||
state.bindings.alertInfo = AlertInfo(id: .login,
|
||||
title: L10n.commonServerNotSupported,
|
||||
message: L10n.screenLoginErrorUnsupportedAuthentication)
|
||||
case .registration:
|
||||
state.bindings.alertInfo = AlertInfo(id: .registration,
|
||||
title: L10n.errorUnknown,
|
||||
title: L10n.commonServerNotSupported,
|
||||
message: L10n.errorAccountCreationNotPossible)
|
||||
case .unknownError:
|
||||
state.bindings.alertInfo = AlertInfo(id: .unknownError)
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
//
|
||||
// Copyright 2022-2024 New Vector Ltd.
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
// Please see LICENSE in the repository root for full details.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
enum MockServerSelectionScreenState: CaseIterable {
|
||||
case matrix
|
||||
case emptyAddress
|
||||
case invalidAddress
|
||||
|
||||
/// Generate the view struct for the screen state.
|
||||
@MainActor var viewModel: ServerSelectionScreenViewModel {
|
||||
switch self {
|
||||
case .matrix:
|
||||
return ServerSelectionScreenViewModel(homeserverAddress: "https://matrix.org",
|
||||
slidingSyncLearnMoreURL: ServiceLocator.shared.settings.slidingSyncLearnMoreURL)
|
||||
|
||||
case .emptyAddress:
|
||||
return ServerSelectionScreenViewModel(homeserverAddress: "",
|
||||
slidingSyncLearnMoreURL: ServiceLocator.shared.settings.slidingSyncLearnMoreURL)
|
||||
case .invalidAddress:
|
||||
let viewModel = ServerSelectionScreenViewModel(homeserverAddress: "thisisbad",
|
||||
slidingSyncLearnMoreURL: ServiceLocator.shared.settings.slidingSyncLearnMoreURL)
|
||||
viewModel.displayError(.footerMessage(L10n.errorUnknown))
|
||||
return viewModel
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,8 +37,10 @@ final class ServerSelectionScreenCoordinator: CoordinatorProtocol {
|
||||
|
||||
init(parameters: ServerSelectionScreenCoordinatorParameters) {
|
||||
self.parameters = parameters
|
||||
viewModel = ServerSelectionScreenViewModel(homeserverAddress: parameters.authenticationService.homeserver.value.address,
|
||||
slidingSyncLearnMoreURL: parameters.slidingSyncLearnMoreURL)
|
||||
viewModel = ServerSelectionScreenViewModel(authenticationService: parameters.authenticationService,
|
||||
authenticationFlow: parameters.authenticationFlow,
|
||||
slidingSyncLearnMoreURL: parameters.slidingSyncLearnMoreURL,
|
||||
userIndicatorController: parameters.userIndicatorController)
|
||||
userIndicatorController = parameters.userIndicatorController
|
||||
}
|
||||
|
||||
@@ -50,8 +52,8 @@ final class ServerSelectionScreenCoordinator: CoordinatorProtocol {
|
||||
guard let self else { return }
|
||||
|
||||
switch action {
|
||||
case .confirm(let homeserverAddress):
|
||||
self.useHomeserver(homeserverAddress)
|
||||
case .updated:
|
||||
actionsSubject.send(.updated)
|
||||
case .dismiss:
|
||||
actionsSubject.send(.dismiss)
|
||||
}
|
||||
@@ -60,56 +62,10 @@ final class ServerSelectionScreenCoordinator: CoordinatorProtocol {
|
||||
}
|
||||
|
||||
func stop() {
|
||||
stopLoading()
|
||||
parameters.userIndicatorController.retractAllIndicators()
|
||||
}
|
||||
|
||||
func toPresentable() -> AnyView {
|
||||
AnyView(ServerSelectionScreen(context: viewModel.context))
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func startLoading(label: String = L10n.commonLoading) {
|
||||
userIndicatorController.submitIndicator(UserIndicator(type: .modal,
|
||||
title: label,
|
||||
persistent: true))
|
||||
}
|
||||
|
||||
private func stopLoading() {
|
||||
userIndicatorController.retractAllIndicators()
|
||||
}
|
||||
|
||||
/// Updates the login flow using the supplied homeserver address, or shows an error when this isn't possible.
|
||||
private func useHomeserver(_ homeserverAddress: String) {
|
||||
startLoading()
|
||||
|
||||
Task {
|
||||
switch await authenticationService.configure(for: homeserverAddress, flow: parameters.authenticationFlow) {
|
||||
case .success:
|
||||
MXLog.info("Selected homeserver: \(homeserverAddress)")
|
||||
actionsSubject.send(.updated)
|
||||
stopLoading()
|
||||
case .failure(let error):
|
||||
MXLog.info("Invalid homeserver: \(homeserverAddress)")
|
||||
stopLoading()
|
||||
handleError(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Processes an error to either update the flow or display it to the user.
|
||||
private func handleError(_ error: AuthenticationServiceError) {
|
||||
switch error {
|
||||
case .invalidServer, .invalidHomeserverAddress:
|
||||
viewModel.displayError(.footerMessage(L10n.screenChangeServerErrorInvalidHomeserver))
|
||||
case .invalidWellKnown(let error):
|
||||
viewModel.displayError(.invalidWellKnownAlert(error))
|
||||
case .slidingSyncNotAvailable:
|
||||
viewModel.displayError(.slidingSyncAlert)
|
||||
case .registrationNotSupported:
|
||||
viewModel.displayError(.registrationAlert) // TODO: [DOUG] Test me!
|
||||
default:
|
||||
viewModel.displayError(.footerMessage(L10n.errorUnknown))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
import Foundation
|
||||
|
||||
enum ServerSelectionScreenViewModelAction {
|
||||
/// The user would like to use the homeserver at the given address.
|
||||
case confirm(homeserverAddress: String)
|
||||
/// The homeserver selection has been updated.
|
||||
case updated
|
||||
/// Dismiss the view without using the entered address.
|
||||
case dismiss
|
||||
}
|
||||
@@ -74,6 +74,8 @@ enum ServerSelectionScreenErrorType: Hashable {
|
||||
case invalidWellKnownAlert(String)
|
||||
/// An alert that allows the user to learn about sliding sync.
|
||||
case slidingSyncAlert
|
||||
/// An alert that informs the user that login isn't supported.
|
||||
case loginAlert
|
||||
/// An alert that informs the user that registration isn't supported.
|
||||
case registrationAlert
|
||||
}
|
||||
|
||||
@@ -11,7 +11,10 @@ import SwiftUI
|
||||
typealias ServerSelectionScreenViewModelType = StateStoreViewModel<ServerSelectionScreenViewState, ServerSelectionScreenViewAction>
|
||||
|
||||
class ServerSelectionScreenViewModel: ServerSelectionScreenViewModelType, ServerSelectionScreenViewModelProtocol {
|
||||
private let authenticationService: AuthenticationServiceProtocol
|
||||
private let authenticationFlow: AuthenticationFlow
|
||||
private let slidingSyncLearnMoreURL: URL
|
||||
private let userIndicatorController: UserIndicatorControllerProtocol
|
||||
|
||||
private var actionsSubject: PassthroughSubject<ServerSelectionScreenViewModelAction, Never> = .init()
|
||||
|
||||
@@ -19,18 +22,23 @@ class ServerSelectionScreenViewModel: ServerSelectionScreenViewModelType, Server
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(homeserverAddress: String, slidingSyncLearnMoreURL: URL) {
|
||||
init(authenticationService: AuthenticationServiceProtocol,
|
||||
authenticationFlow: AuthenticationFlow,
|
||||
slidingSyncLearnMoreURL: URL,
|
||||
userIndicatorController: UserIndicatorControllerProtocol) {
|
||||
self.authenticationService = authenticationService
|
||||
self.authenticationFlow = authenticationFlow
|
||||
self.slidingSyncLearnMoreURL = slidingSyncLearnMoreURL
|
||||
let bindings = ServerSelectionScreenBindings(homeserverAddress: homeserverAddress)
|
||||
self.userIndicatorController = userIndicatorController
|
||||
|
||||
super.init(initialViewState: ServerSelectionScreenViewState(slidingSyncLearnMoreURL: slidingSyncLearnMoreURL,
|
||||
bindings: bindings))
|
||||
let bindings = ServerSelectionScreenBindings(homeserverAddress: authenticationService.homeserver.value.address)
|
||||
super.init(initialViewState: ServerSelectionScreenViewState(slidingSyncLearnMoreURL: slidingSyncLearnMoreURL, bindings: bindings))
|
||||
}
|
||||
|
||||
override func process(viewAction: ServerSelectionScreenViewAction) {
|
||||
switch viewAction {
|
||||
case .confirm:
|
||||
actionsSubject.send(.confirm(homeserverAddress: state.bindings.homeserverAddress))
|
||||
configureHomeserver()
|
||||
case .dismiss:
|
||||
actionsSubject.send(.dismiss)
|
||||
case .clearFooterError:
|
||||
@@ -38,31 +46,72 @@ class ServerSelectionScreenViewModel: ServerSelectionScreenViewModelType, Server
|
||||
}
|
||||
}
|
||||
|
||||
func displayError(_ type: ServerSelectionScreenErrorType) {
|
||||
switch type {
|
||||
case .footerMessage(let message):
|
||||
withElementAnimation {
|
||||
state.footerErrorMessage = message
|
||||
// MARK: - Private
|
||||
|
||||
/// Updates the login flow using the supplied homeserver address, or shows an error when this isn't possible.
|
||||
private func configureHomeserver() {
|
||||
let homeserverAddress = state.bindings.homeserverAddress
|
||||
startLoading()
|
||||
|
||||
Task {
|
||||
switch await authenticationService.configure(for: homeserverAddress, flow: authenticationFlow) {
|
||||
case .success:
|
||||
MXLog.info("Selected homeserver: \(homeserverAddress)")
|
||||
actionsSubject.send(.updated)
|
||||
stopLoading()
|
||||
case .failure(let error):
|
||||
MXLog.info("Invalid homeserver: \(homeserverAddress)")
|
||||
stopLoading()
|
||||
handleError(error)
|
||||
}
|
||||
case .invalidWellKnownAlert(let error):
|
||||
}
|
||||
}
|
||||
|
||||
private func startLoading(label: String = L10n.commonLoading) {
|
||||
userIndicatorController.submitIndicator(UserIndicator(type: .modal,
|
||||
title: label,
|
||||
persistent: true))
|
||||
}
|
||||
|
||||
private func stopLoading() {
|
||||
userIndicatorController.retractAllIndicators()
|
||||
}
|
||||
|
||||
/// Processes an error to either update the flow or display it to the user.
|
||||
private func handleError(_ error: AuthenticationServiceError) {
|
||||
switch error {
|
||||
case .invalidServer, .invalidHomeserverAddress:
|
||||
showFooterMessage(L10n.screenChangeServerErrorInvalidHomeserver)
|
||||
case .invalidWellKnown(let error):
|
||||
state.bindings.alertInfo = AlertInfo(id: .invalidWellKnownAlert(error),
|
||||
title: L10n.commonServerNotSupported,
|
||||
message: L10n.screenChangeServerErrorInvalidWellKnown(error))
|
||||
case .slidingSyncAlert:
|
||||
case .slidingSyncNotAvailable:
|
||||
let openURL = { UIApplication.shared.open(self.slidingSyncLearnMoreURL) }
|
||||
state.bindings.alertInfo = AlertInfo(id: .slidingSyncAlert,
|
||||
title: L10n.commonServerNotSupported,
|
||||
message: L10n.screenChangeServerErrorNoSlidingSyncMessage,
|
||||
primaryButton: .init(title: L10n.actionLearnMore, role: .cancel, action: openURL),
|
||||
secondaryButton: .init(title: L10n.actionCancel, action: nil))
|
||||
case .registrationAlert:
|
||||
case .loginNotSupported:
|
||||
state.bindings.alertInfo = AlertInfo(id: .loginAlert,
|
||||
title: L10n.commonServerNotSupported,
|
||||
message: L10n.screenLoginErrorUnsupportedAuthentication)
|
||||
case .registrationNotSupported:
|
||||
state.bindings.alertInfo = AlertInfo(id: .registrationAlert,
|
||||
title: L10n.errorUnknown,
|
||||
title: L10n.commonServerNotSupported,
|
||||
message: L10n.errorAccountCreationNotPossible)
|
||||
default:
|
||||
showFooterMessage(L10n.errorUnknown)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
/// Set a new error message to be shown in the text field footer.
|
||||
private func showFooterMessage(_ message: String) {
|
||||
withElementAnimation {
|
||||
state.footerErrorMessage = message
|
||||
}
|
||||
}
|
||||
|
||||
/// Clear any errors shown in the text field footer.
|
||||
private func clearFooterError() {
|
||||
|
||||
@@ -11,7 +11,4 @@ import Combine
|
||||
protocol ServerSelectionScreenViewModelProtocol {
|
||||
var actions: AnyPublisher<ServerSelectionScreenViewModelAction, Never> { get }
|
||||
var context: ServerSelectionScreenViewModelType.Context { get }
|
||||
|
||||
/// Displays an error to the user.
|
||||
func displayError(_ type: ServerSelectionScreenErrorType)
|
||||
}
|
||||
|
||||
@@ -91,11 +91,38 @@ struct ServerSelectionScreen: View {
|
||||
// MARK: - Previews
|
||||
|
||||
struct ServerSelection_Previews: PreviewProvider, TestablePreview {
|
||||
static let matrixViewModel = makeViewModel(for: "https://matrix.org")
|
||||
static let emptyViewModel = makeViewModel(for: "")
|
||||
static let invalidViewModel = makeViewModel(for: "thisisbad")
|
||||
|
||||
static var previews: some View {
|
||||
ForEach(MockServerSelectionScreenState.allCases, id: \.self) { state in
|
||||
NavigationStack {
|
||||
ServerSelectionScreen(context: state.viewModel.context)
|
||||
}
|
||||
NavigationStack {
|
||||
ServerSelectionScreen(context: matrixViewModel.context)
|
||||
}
|
||||
|
||||
NavigationStack {
|
||||
ServerSelectionScreen(context: emptyViewModel.context)
|
||||
}
|
||||
|
||||
NavigationStack {
|
||||
ServerSelectionScreen(context: invalidViewModel.context)
|
||||
}
|
||||
.snapshotPreferences(delay: 0.25)
|
||||
}
|
||||
|
||||
static func makeViewModel(for homeserverAddress: String) -> ServerSelectionScreenViewModel {
|
||||
let authenticationService = AuthenticationService.mock
|
||||
|
||||
let slidingSyncLearnMoreURL = ServiceLocator.shared.settings.slidingSyncLearnMoreURL
|
||||
|
||||
let viewModel = ServerSelectionScreenViewModel(authenticationService: authenticationService,
|
||||
authenticationFlow: .login,
|
||||
slidingSyncLearnMoreURL: slidingSyncLearnMoreURL,
|
||||
userIndicatorController: UserIndicatorControllerMock())
|
||||
viewModel.context.homeserverAddress = homeserverAddress
|
||||
if homeserverAddress == "thisisbad" {
|
||||
viewModel.context.send(viewAction: .confirm)
|
||||
}
|
||||
return viewModel
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,6 +65,9 @@ class AuthenticationService: AuthenticationServiceProtocol {
|
||||
case .failure: nil
|
||||
}
|
||||
|
||||
if flow == .login, homeserver.loginMode == .unsupported {
|
||||
return .failure(.loginNotSupported)
|
||||
}
|
||||
if flow == .register, !homeserver.supportsRegistration {
|
||||
return .failure(.registrationNotSupported)
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ enum AuthenticationServiceError: Error, Equatable {
|
||||
case invalidHomeserverAddress
|
||||
case invalidWellKnown(String)
|
||||
case slidingSyncNotAvailable
|
||||
case loginNotSupported
|
||||
case registrationNotSupported
|
||||
case accountDeactivated
|
||||
case failedLoggingIn
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:667d6afdec1cad4db4439278981efc6cafb8c4bb52a28ed951120da64156481c
|
||||
size 106217
|
||||
oid sha256:b24a1036fe61c64214c929d53d409723bf388191205d4392759009680b451ad3
|
||||
size 121094
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:028f43f14f8ad6f5a7eef82c6bcb01779ddec20b6cda44c405ecd874bc3ed35d
|
||||
size 115393
|
||||
oid sha256:8048d1dd4b99792cd4c68567499cfe3aba40a4e3b0697eda3d1b67a115d684f6
|
||||
size 145736
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:04d2b178fa9a746c0ddef74e83b100011267ba40e98dbcd5ed8d75ec599b89f6
|
||||
size 60409
|
||||
oid sha256:d6b890b433313f7398c38f940ba1db616b89c563c5ba35e6378b0a4863b9c6b5
|
||||
size 75379
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7d05997731f755f45834075ac7dd62cc8b18b94b65354ccd4fb6c198ed91bac1
|
||||
size 73627
|
||||
oid sha256:1fe38dada4e2201f011443eda61791124e33e5423a049ea4f9b7c942c3405b1b
|
||||
size 103829
|
||||
|
||||
@@ -89,7 +89,7 @@ class ServerConfirmationScreenViewModelTests: XCTestCase {
|
||||
|
||||
func testRegistrationNotSupportedAlert() async throws {
|
||||
// Given a view model for registration using a service that hasn't been configured and the default server doesn't support registration.
|
||||
setupViewModel(authenticationFlow: .register, elementWellKnown: false)
|
||||
setupViewModel(authenticationFlow: .register, supportsRegistrationHelper: false)
|
||||
XCTAssertEqual(service.homeserver.value.loginMode, .unknown)
|
||||
XCTAssertFalse(service.homeserver.value.supportsRegistration)
|
||||
XCTAssertEqual(clientBuilderFactory.makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount, 0)
|
||||
@@ -105,10 +105,34 @@ class ServerConfirmationScreenViewModelTests: XCTestCase {
|
||||
XCTAssertEqual(context.alertInfo?.id, .registration)
|
||||
}
|
||||
|
||||
func testLoginNotSupportedAlert() async throws {
|
||||
// Given a view model for login using a service that hasn't been configured and the default server doesn't support login.
|
||||
setupViewModel(authenticationFlow: .login, supportsRegistrationHelper: false, supportsPasswordLogin: false)
|
||||
XCTAssertEqual(service.homeserver.value.loginMode, .unknown)
|
||||
XCTAssertFalse(service.homeserver.value.supportsRegistration)
|
||||
XCTAssertEqual(clientBuilderFactory.makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount, 0)
|
||||
XCTAssertNil(context.alertInfo)
|
||||
|
||||
// When continuing from the confirmation screen.
|
||||
let deferred = deferFulfillment(context.$viewState) { $0.bindings.alertInfo != nil }
|
||||
context.send(viewAction: .confirm)
|
||||
try await deferred.fulfill()
|
||||
|
||||
// Then the configuration should fail with an alert about not supporting login.
|
||||
XCTAssertEqual(clientBuilderFactory.makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount, 1)
|
||||
XCTAssertEqual(context.alertInfo?.id, .login)
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private func setupViewModel(authenticationFlow: AuthenticationFlow, elementWellKnown: Bool = true) {
|
||||
let client = ClientSDKMock(configuration: elementWellKnown ? .init() : .init(elementWellKnown: ""))
|
||||
private func setupViewModel(authenticationFlow: AuthenticationFlow, supportsRegistrationHelper: Bool = true, supportsPasswordLogin: Bool = true) {
|
||||
// Manually create a configuration as the default homeserver address setting is immutable.
|
||||
let clientConfiguration: ClientSDKMock.Configuration = if supportsRegistrationHelper {
|
||||
.init(supportsPasswordLogin: supportsPasswordLogin)
|
||||
} else {
|
||||
.init(supportsPasswordLogin: supportsPasswordLogin, elementWellKnown: "")
|
||||
}
|
||||
let client = ClientSDKMock(configuration: clientConfiguration)
|
||||
let configuration = AuthenticationClientBuilderMock.Configuration(homeserverClients: ["matrix.org": client],
|
||||
qrCodeClient: client)
|
||||
|
||||
|
||||
@@ -11,38 +11,131 @@ import XCTest
|
||||
|
||||
@MainActor
|
||||
class ServerSelectionViewModelTests: XCTestCase {
|
||||
var viewModel: ServerSelectionScreenViewModelProtocol!
|
||||
var context: ServerSelectionScreenViewModelType.Context!
|
||||
var clientBuilderFactory: AuthenticationClientBuilderFactoryMock!
|
||||
var service: AuthenticationServiceProtocol!
|
||||
|
||||
@MainActor override func setUp() {
|
||||
viewModel = ServerSelectionScreenViewModel(homeserverAddress: "",
|
||||
slidingSyncLearnMoreURL: ServiceLocator.shared.settings.slidingSyncLearnMoreURL)
|
||||
context = viewModel.context
|
||||
var viewModel: ServerSelectionScreenViewModelProtocol!
|
||||
var context: ServerSelectionScreenViewModelType.Context { viewModel.context }
|
||||
|
||||
func testSelectForLogin() async throws {
|
||||
// Given a view model for login.
|
||||
setupViewModel(authenticationFlow: .login)
|
||||
XCTAssertEqual(service.homeserver.value.loginMode, .unknown)
|
||||
XCTAssertFalse(service.homeserver.value.supportsRegistration)
|
||||
XCTAssertEqual(clientBuilderFactory.makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount, 0)
|
||||
|
||||
// When selecting matrix.org.
|
||||
context.homeserverAddress = "matrix.org"
|
||||
let deferred = deferFulfillment(viewModel.actions) { $0 == .updated }
|
||||
context.send(viewAction: .confirm)
|
||||
try await deferred.fulfill()
|
||||
|
||||
// Then selection should succeed.
|
||||
XCTAssertEqual(clientBuilderFactory.makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount, 1)
|
||||
XCTAssertEqual(service.homeserver.value, .mockMatrixDotOrg)
|
||||
}
|
||||
|
||||
func testErrorMessage() async throws {
|
||||
|
||||
func testLoginNotSupportedAlert() async throws {
|
||||
// Given a view model for login.
|
||||
setupViewModel(authenticationFlow: .login)
|
||||
XCTAssertEqual(service.homeserver.value.loginMode, .unknown)
|
||||
XCTAssertFalse(service.homeserver.value.supportsRegistration)
|
||||
XCTAssertEqual(clientBuilderFactory.makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount, 0)
|
||||
XCTAssertNil(context.alertInfo)
|
||||
|
||||
// When selecting a server that doesn't support login.
|
||||
context.homeserverAddress = "server.net"
|
||||
let deferred = deferFulfillment(context.$viewState) { $0.bindings.alertInfo != nil }
|
||||
context.send(viewAction: .confirm)
|
||||
try await deferred.fulfill()
|
||||
|
||||
// Then selection should fail with an alert about not supporting registration.
|
||||
XCTAssertEqual(clientBuilderFactory.makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount, 1)
|
||||
XCTAssertEqual(context.alertInfo?.id, .loginAlert)
|
||||
}
|
||||
|
||||
func testSelectForRegistration() async throws {
|
||||
// Given a view model for registration.
|
||||
setupViewModel(authenticationFlow: .register)
|
||||
XCTAssertEqual(service.homeserver.value.loginMode, .unknown)
|
||||
XCTAssertFalse(service.homeserver.value.supportsRegistration)
|
||||
XCTAssertEqual(clientBuilderFactory.makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount, 0)
|
||||
|
||||
// When selecting matrix.org.
|
||||
context.homeserverAddress = "matrix.org"
|
||||
let deferred = deferFulfillment(viewModel.actions) { $0 == .updated }
|
||||
context.send(viewAction: .confirm)
|
||||
try await deferred.fulfill()
|
||||
|
||||
// Then selection should succeed.
|
||||
XCTAssertEqual(clientBuilderFactory.makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount, 1)
|
||||
XCTAssertEqual(service.homeserver.value, .mockMatrixDotOrg)
|
||||
}
|
||||
|
||||
func testRegistrationNotSupportedAlert() async throws {
|
||||
// Given a view model for registration.
|
||||
setupViewModel(authenticationFlow: .register)
|
||||
XCTAssertEqual(service.homeserver.value.loginMode, .unknown)
|
||||
XCTAssertFalse(service.homeserver.value.supportsRegistration)
|
||||
XCTAssertEqual(clientBuilderFactory.makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount, 0)
|
||||
XCTAssertNil(context.alertInfo)
|
||||
|
||||
// When selecting a server that doesn't support registration.
|
||||
context.homeserverAddress = "example.com"
|
||||
let deferred = deferFulfillment(context.$viewState) { $0.bindings.alertInfo != nil }
|
||||
context.send(viewAction: .confirm)
|
||||
try await deferred.fulfill()
|
||||
|
||||
// Then selection should fail with an alert about not supporting registration.
|
||||
XCTAssertEqual(clientBuilderFactory.makeBuilderSessionDirectoriesPassphraseClientSessionDelegateAppSettingsAppHooksCallsCount, 1)
|
||||
XCTAssertEqual(context.alertInfo?.id, .registrationAlert)
|
||||
}
|
||||
|
||||
func testInvalidServer() async throws {
|
||||
// Given a new instance of the view model.
|
||||
setupViewModel(authenticationFlow: .login)
|
||||
XCTAssertFalse(context.viewState.isShowingFooterError, "There should not be an error message for a new view model.")
|
||||
XCTAssertNil(context.viewState.footerErrorMessage, "There should not be an error message for a new view model.")
|
||||
XCTAssertEqual(String(context.viewState.footerMessage.characters), L10n.screenChangeServerFormNotice(L10n.actionLearnMore),
|
||||
"The standard footer message should be shown.")
|
||||
|
||||
// When an error occurs.
|
||||
let message = "Unable to contact server."
|
||||
viewModel.displayError(.footerMessage(message))
|
||||
// When attempting to discover an invalid server
|
||||
var deferred = deferFulfillment(context.$viewState) { $0.isShowingFooterError }
|
||||
context.homeserverAddress = "idontexist"
|
||||
context.send(viewAction: .confirm)
|
||||
try await deferred.fulfill()
|
||||
|
||||
// Then the footer should now be showing an error.
|
||||
XCTAssertEqual(context.viewState.footerErrorMessage, message, "The error message should be stored.")
|
||||
XCTAssertEqual(String(context.viewState.footerMessage.characters), message, "The error message should be shown.")
|
||||
XCTAssertTrue(context.viewState.isShowingFooterError, "The error message should be stored.")
|
||||
XCTAssertNotNil(context.viewState.footerErrorMessage, "The error message should be stored.")
|
||||
XCTAssertNotEqual(String(context.viewState.footerMessage.characters), L10n.screenChangeServerFormNotice(L10n.actionLearnMore),
|
||||
"The error message should be shown.")
|
||||
|
||||
// And when clearing the error.
|
||||
deferred = deferFulfillment(context.$viewState) { !$0.isShowingFooterError }
|
||||
context.homeserverAddress = ""
|
||||
context.send(viewAction: .clearFooterError)
|
||||
|
||||
// Wait for the action to spawn a Task.
|
||||
await Task.yield()
|
||||
try await deferred.fulfill()
|
||||
|
||||
// Then the error message should now be removed.
|
||||
XCTAssertNil(context.viewState.footerErrorMessage, "The error message should have been cleared.")
|
||||
XCTAssertEqual(String(context.viewState.footerMessage.characters), L10n.screenChangeServerFormNotice(L10n.actionLearnMore),
|
||||
"The standard footer message should be shown again.")
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private func setupViewModel(authenticationFlow: AuthenticationFlow) {
|
||||
clientBuilderFactory = AuthenticationClientBuilderFactoryMock(configuration: .init())
|
||||
service = AuthenticationService(userSessionStore: UserSessionStoreMock(configuration: .init()),
|
||||
encryptionKeyProvider: EncryptionKeyProvider(),
|
||||
clientBuilderFactory: clientBuilderFactory,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
appHooks: AppHooks())
|
||||
|
||||
viewModel = ServerSelectionScreenViewModel(authenticationService: service,
|
||||
authenticationFlow: authenticationFlow,
|
||||
slidingSyncLearnMoreURL: ServiceLocator.shared.settings.slidingSyncLearnMoreURL,
|
||||
userIndicatorController: UserIndicatorControllerMock())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user