mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
Add tests for linking a new device. (#4934)
* Replace GrantLoginWithQrCodeHandlerSDKMock with LinkNewDeviceServiceMock. Add tests for all initial states on the QRCodeLoginScreen. * Add tests for linking both mobile and desktop devices. * Add UI tests for linking a new device. * Don't show the Link Desktop Computer button when running on macOS. This mirrors the decision to hide the Sign In With QR Code button on the start screen.
This commit is contained in:
@@ -9,41 +9,63 @@
|
||||
import Combine
|
||||
import XCTest
|
||||
|
||||
import MatrixRustSDK
|
||||
|
||||
@testable import ElementX
|
||||
import MatrixRustSDKMocks
|
||||
|
||||
@MainActor
|
||||
final class QRCodeLoginScreenViewModelTests: XCTestCase {
|
||||
private var qrProgressSubject: CurrentValueSubject<QRLoginProgress, AuthenticationServiceError>!
|
||||
private var qrServiceMock: QRCodeLoginServiceMock!
|
||||
private var appMediatorMock: AppMediatorMock!
|
||||
private var qrLoginProgressSubject: CurrentValueSubject<QRLoginProgress, AuthenticationServiceError>!
|
||||
private var qrCodeLoginService: QRCodeLoginServiceMock!
|
||||
|
||||
private var linkMobileProgressSubject: CurrentValueSubject<LinkNewDeviceService.LinkMobileProgress, QRCodeLoginError>!
|
||||
private var linkDesktopProgressSubject: CurrentValueSubject<LinkNewDeviceService.LinkDesktopProgress, QRCodeLoginError>!
|
||||
private var linkNewDeviceService: LinkNewDeviceServiceMock!
|
||||
|
||||
private var appMediator: AppMediatorMock!
|
||||
|
||||
private var viewModel: QRCodeLoginScreenViewModelProtocol!
|
||||
|
||||
private var context: QRCodeLoginScreenViewModelType.Context {
|
||||
viewModel.context
|
||||
}
|
||||
private var context: QRCodeLoginScreenViewModelType.Context { viewModel.context }
|
||||
|
||||
override func setUp() {
|
||||
qrProgressSubject = .init(.starting)
|
||||
qrServiceMock = QRCodeLoginServiceMock()
|
||||
qrServiceMock.loginWithQRCodeDataReturnValue = qrProgressSubject.asCurrentValuePublisher()
|
||||
appMediatorMock = AppMediatorMock.default
|
||||
viewModel = QRCodeLoginScreenViewModel(mode: .login(qrServiceMock),
|
||||
canSignInManually: true,
|
||||
appMediator: appMediatorMock)
|
||||
}
|
||||
|
||||
func testInitialState() {
|
||||
func testLoginInitialState() {
|
||||
setupViewModel(mode: .login)
|
||||
|
||||
XCTAssertEqual(context.viewState.state, .loginInstructions)
|
||||
XCTAssertNil(context.qrResult)
|
||||
XCTAssertFalse(qrServiceMock.loginWithQRCodeDataCalled)
|
||||
XCTAssertFalse(appMediatorMock.requestAuthorizationIfNeededCalled)
|
||||
XCTAssertFalse(appMediatorMock.openAppSettingsCalled)
|
||||
XCTAssertFalse(qrCodeLoginService.loginWithQRCodeDataCalled)
|
||||
XCTAssertFalse(appMediator.requestAuthorizationIfNeededCalled)
|
||||
XCTAssertFalse(appMediator.openAppSettingsCalled)
|
||||
|
||||
XCTAssertFalse(linkNewDeviceService.linkMobileDeviceCalled)
|
||||
XCTAssertFalse(linkNewDeviceService.linkDesktopDeviceWithCalled)
|
||||
}
|
||||
|
||||
func testLinkDesktopInitialState() {
|
||||
setupViewModel(mode: .linkDesktop)
|
||||
|
||||
XCTAssertEqual(context.viewState.state, .linkDesktopInstructions)
|
||||
XCTAssertNil(context.qrResult)
|
||||
XCTAssertFalse(linkNewDeviceService.linkDesktopDeviceWithCalled)
|
||||
XCTAssertFalse(appMediator.requestAuthorizationIfNeededCalled)
|
||||
XCTAssertFalse(appMediator.openAppSettingsCalled)
|
||||
|
||||
XCTAssertFalse(linkNewDeviceService.linkMobileDeviceCalled)
|
||||
XCTAssertFalse(qrCodeLoginService.loginWithQRCodeDataCalled)
|
||||
}
|
||||
|
||||
func testLinkMobileInitialState() {
|
||||
setupViewModel(mode: .linkMobile)
|
||||
|
||||
XCTAssertTrue(context.viewState.state.isDisplayQR)
|
||||
XCTAssertTrue(linkNewDeviceService.linkMobileDeviceCalled)
|
||||
|
||||
XCTAssertFalse(linkNewDeviceService.linkDesktopDeviceWithCalled)
|
||||
XCTAssertFalse(qrCodeLoginService.loginWithQRCodeDataCalled)
|
||||
XCTAssertNil(context.qrResult)
|
||||
}
|
||||
|
||||
func testRequestCameraPermission() async throws {
|
||||
appMediatorMock.requestAuthorizationIfNeededReturnValue = false
|
||||
setupViewModel(mode: .login)
|
||||
appMediator.requestAuthorizationIfNeededReturnValue = false
|
||||
XCTAssert(context.viewState.state == .loginInstructions)
|
||||
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
@@ -51,15 +73,16 @@ final class QRCodeLoginScreenViewModelTests: XCTestCase {
|
||||
}
|
||||
context.send(viewAction: .startScan)
|
||||
try await deferred.fulfill()
|
||||
XCTAssertTrue(appMediatorMock.requestAuthorizationIfNeededCalled)
|
||||
XCTAssertTrue(appMediator.requestAuthorizationIfNeededCalled)
|
||||
|
||||
context.send(viewAction: .errorAction(.openSettings))
|
||||
await Task.yield()
|
||||
XCTAssertTrue(appMediatorMock.openAppSettingsCalled)
|
||||
XCTAssertTrue(appMediator.openAppSettingsCalled)
|
||||
XCTAssertNil(context.qrResult)
|
||||
}
|
||||
|
||||
func testLogin() async throws {
|
||||
setupViewModel(mode: .login)
|
||||
XCTAssert(context.viewState.state == .loginInstructions)
|
||||
|
||||
var deferred = deferFulfillment(context.$viewState) { state in
|
||||
@@ -67,7 +90,7 @@ final class QRCodeLoginScreenViewModelTests: XCTestCase {
|
||||
}
|
||||
context.send(viewAction: .startScan)
|
||||
try await deferred.fulfill()
|
||||
XCTAssertTrue(appMediatorMock.requestAuthorizationIfNeededCalled)
|
||||
XCTAssertTrue(appMediator.requestAuthorizationIfNeededCalled)
|
||||
|
||||
deferred = deferFulfillment(context.$viewState) { state in
|
||||
state.state == .scan(.connecting)
|
||||
@@ -78,13 +101,13 @@ final class QRCodeLoginScreenViewModelTests: XCTestCase {
|
||||
deferred = deferFulfillment(context.$viewState) { state in
|
||||
state.state == .displayCode(.deviceCode("01"))
|
||||
}
|
||||
qrProgressSubject.send(.establishingSecureChannel(checkCode: 1, checkCodeString: "01"))
|
||||
qrLoginProgressSubject.send(.establishingSecureChannel(checkCode: 1, checkCodeString: "01"))
|
||||
try await deferred.fulfill()
|
||||
|
||||
deferred = deferFulfillment(context.$viewState) { state in
|
||||
state.state == .displayCode(.verificationCode("ABCDEF"))
|
||||
}
|
||||
qrProgressSubject.send(.waitingForToken(userCode: "ABCDEF"))
|
||||
qrLoginProgressSubject.send(.waitingForToken(userCode: "ABCDEF"))
|
||||
try await deferred.fulfill()
|
||||
|
||||
let deferredAction = deferFulfillment(viewModel.actionsPublisher) { action in
|
||||
@@ -93,7 +116,109 @@ final class QRCodeLoginScreenViewModelTests: XCTestCase {
|
||||
default: false
|
||||
}
|
||||
}
|
||||
qrProgressSubject.send(.signedIn(UserSessionMock(.init(clientProxy: ClientProxyMock()))))
|
||||
qrLoginProgressSubject.send(.signedIn(UserSessionMock(.init(clientProxy: ClientProxyMock()))))
|
||||
try await deferredAction.fulfill()
|
||||
}
|
||||
|
||||
func testLinkDesktopComputer() async throws {
|
||||
setupViewModel(mode: .linkDesktop)
|
||||
XCTAssert(context.viewState.state == .linkDesktopInstructions)
|
||||
|
||||
var deferred = deferFulfillment(context.$viewState) { $0.state == .scan(.scanning) }
|
||||
context.send(viewAction: .startScan)
|
||||
try await deferred.fulfill()
|
||||
XCTAssertTrue(appMediator.requestAuthorizationIfNeededCalled)
|
||||
|
||||
deferred = deferFulfillment(context.$viewState) { $0.state == .scan(.connecting) }
|
||||
context.qrResult = .init()
|
||||
try await deferred.fulfill()
|
||||
|
||||
deferred = deferFulfillment(context.$viewState) { $0.state == .displayCode(.deviceCode("01")) }
|
||||
linkDesktopProgressSubject.send(.establishingSecureChannel(checkCodeString: "01"))
|
||||
try await deferred.fulfill()
|
||||
|
||||
var deferredAction = deferFulfillment(viewModel.actionsPublisher) { action in
|
||||
guard case .requestOIDCAuthorisation = action else { return false }
|
||||
return true
|
||||
}
|
||||
linkDesktopProgressSubject.send(.waitingForAuthorisation(verificationURL: .homeDirectory))
|
||||
try await deferredAction.fulfill()
|
||||
|
||||
let currentState = context.viewState.state
|
||||
let deferredFailure = deferFailure(context.$viewState, timeout: 1) { $0.state != currentState }
|
||||
linkDesktopProgressSubject.send(.syncingSecrets)
|
||||
try await deferredFailure.fulfill()
|
||||
|
||||
deferredAction = deferFulfillment(viewModel.actionsPublisher) { action in
|
||||
guard case .dismiss = action else { return false }
|
||||
return true
|
||||
}
|
||||
linkDesktopProgressSubject.send(.done)
|
||||
try await deferredAction.fulfill()
|
||||
}
|
||||
|
||||
func testLinkMobileDevice() async throws {
|
||||
setupViewModel(mode: .linkMobile)
|
||||
XCTAssert(context.viewState.state.isDisplayQR)
|
||||
|
||||
let checkCodeSender = CheckCodeSenderSDKMock()
|
||||
let checkCodeSenderProxy = CheckCodeSenderProxy(underlyingSender: checkCodeSender)
|
||||
var deferredState = deferFulfillment(context.$viewState) { $0.state == .confirmCode(.inputCode(checkCodeSenderProxy)) }
|
||||
linkMobileProgressSubject.send(.qrScanned(checkCodeSenderProxy))
|
||||
try await deferredState.fulfill()
|
||||
|
||||
deferredState = deferFulfillment(context.$viewState) { $0.state == .confirmCode(.sendingCode) }
|
||||
context.checkCodeInput = "01"
|
||||
context.send(viewAction: .sendCheckCode)
|
||||
try await deferredState.fulfill()
|
||||
|
||||
var deferredAction = deferFulfillment(viewModel.actionsPublisher) { action in
|
||||
guard case .requestOIDCAuthorisation = action else { return false }
|
||||
return true
|
||||
}
|
||||
linkMobileProgressSubject.send(.waitingForAuthorisation(verificationURL: .homeDirectory))
|
||||
try await deferredAction.fulfill()
|
||||
|
||||
let currentState = context.viewState.state
|
||||
let deferredFailure = deferFailure(context.$viewState, timeout: 1) { $0.state != currentState }
|
||||
linkMobileProgressSubject.send(.syncingSecrets)
|
||||
try await deferredFailure.fulfill()
|
||||
|
||||
deferredAction = deferFulfillment(viewModel.actionsPublisher) { action in
|
||||
guard case .dismiss = action else { return false }
|
||||
return true
|
||||
}
|
||||
linkMobileProgressSubject.send(.done)
|
||||
try await deferredAction.fulfill()
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
enum Mode { case login, linkDesktop, linkMobile }
|
||||
|
||||
private func setupViewModel(mode: Mode) {
|
||||
qrLoginProgressSubject = .init(.starting)
|
||||
qrCodeLoginService = QRCodeLoginServiceMock()
|
||||
qrCodeLoginService.loginWithQRCodeDataReturnValue = qrLoginProgressSubject.asCurrentValuePublisher()
|
||||
|
||||
linkMobileProgressSubject = .init(.qrReady(LinkNewDeviceServiceMock.mockQRCodeImage))
|
||||
linkDesktopProgressSubject = .init(.starting)
|
||||
linkNewDeviceService = LinkNewDeviceServiceMock(.init(linkMobileProgressPublisher: linkMobileProgressSubject.asCurrentValuePublisher(),
|
||||
linkDesktopProgressPublisher: linkDesktopProgressSubject.asCurrentValuePublisher()))
|
||||
|
||||
let screenMode: QRCodeLoginScreenMode
|
||||
switch mode {
|
||||
case .login:
|
||||
screenMode = .login(qrCodeLoginService)
|
||||
case .linkDesktop:
|
||||
screenMode = .linkDesktop(linkNewDeviceService)
|
||||
case .linkMobile:
|
||||
screenMode = .linkMobile(linkNewDeviceService.linkMobileDevice())
|
||||
}
|
||||
|
||||
appMediator = AppMediatorMock.default
|
||||
viewModel = QRCodeLoginScreenViewModel(mode: screenMode,
|
||||
canSignInManually: true,
|
||||
appMediator: appMediator)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user