Add support for linking new devices in the QRCodeLoginScreen. (#4891)

* Adds the remaining parts for showing/scanning a QR code to link a new device.

* Refactor the QRCodeLoginService to work the same way as the LinkNewDeviceService.
This commit is contained in:
Doug
2026-01-07 12:18:39 +00:00
committed by GitHub
parent be651de9d7
commit 7c839efffc
62 changed files with 959 additions and 388 deletions

View File

@@ -24,7 +24,7 @@ final class AVMetadataMachineReadableCodeObjectExtensionsTest: XCTestCase {
return
}
guard let resultData = AVMetadataMachineReadableCodeObject.removeQrProtocolData(data, symbolVersion: symbolVersion) else {
guard let resultData = try? AVMetadataMachineReadableCodeObject.removeQRProtocolData(data, symbolVersion: symbolVersion) else {
XCTFail("Could not remove the protocol data")
return
}

View File

@@ -15,7 +15,7 @@ import MatrixRustSDK
@MainActor
final class QRCodeLoginScreenViewModelTests: XCTestCase {
private var qrProgressSubject: PassthroughSubject<QrLoginProgress, Never>!
private var qrProgressSubject: CurrentValueSubject<QRLoginProgress, AuthenticationServiceError>!
private var qrServiceMock: QRCodeLoginServiceMock!
private var appMediatorMock: AppMediatorMock!
private var viewModel: QRCodeLoginScreenViewModelProtocol!
@@ -25,17 +25,17 @@ final class QRCodeLoginScreenViewModelTests: XCTestCase {
}
override func setUp() {
qrProgressSubject = PassthroughSubject<QrLoginProgress, Never>()
qrProgressSubject = .init(.starting)
qrServiceMock = QRCodeLoginServiceMock()
qrServiceMock.underlyingQrLoginProgressPublisher = qrProgressSubject.eraseToAnyPublisher()
qrServiceMock.loginWithQRCodeDataReturnValue = qrProgressSubject.asCurrentValuePublisher()
appMediatorMock = AppMediatorMock.default
viewModel = QRCodeLoginScreenViewModel(qrCodeLoginService: qrServiceMock,
viewModel = QRCodeLoginScreenViewModel(mode: .login(qrServiceMock),
canSignInManually: true,
appMediator: appMediatorMock)
}
func testInitialState() {
XCTAssertEqual(context.viewState.state, .initial)
XCTAssertEqual(context.viewState.state, .loginInstructions)
XCTAssertNil(context.qrResult)
XCTAssertFalse(qrServiceMock.loginWithQRCodeDataCalled)
XCTAssertFalse(appMediatorMock.requestAuthorizationIfNeededCalled)
@@ -44,7 +44,7 @@ final class QRCodeLoginScreenViewModelTests: XCTestCase {
func testRequestCameraPermission() async throws {
appMediatorMock.requestAuthorizationIfNeededReturnValue = false
XCTAssert(context.viewState.state == .initial)
XCTAssert(context.viewState.state == .loginInstructions)
let deferred = deferFulfillment(viewModel.context.$viewState) { state in
state.state == .error(.noCameraPermission)
@@ -60,15 +60,7 @@ final class QRCodeLoginScreenViewModelTests: XCTestCase {
}
func testLogin() async throws {
var isCompleted = false
qrServiceMock.loginWithQRCodeDataClosure = { _ in
while !isCompleted {
await Task.yield()
}
return .success(UserSessionMock(.init(clientProxy: ClientProxyMock())))
}
XCTAssert(context.viewState.state == .initial)
XCTAssert(context.viewState.state == .loginInstructions)
var deferred = deferFulfillment(context.$viewState) { state in
state.state == .scan(.scanning)
@@ -97,14 +89,11 @@ final class QRCodeLoginScreenViewModelTests: XCTestCase {
let deferredAction = deferFulfillment(viewModel.actionsPublisher) { action in
switch action {
case .done:
return true
default:
return false
case .signedIn: true
default: false
}
}
qrProgressSubject.send(.done)
isCompleted = true
qrProgressSubject.send(.signedIn(UserSessionMock(.init(clientProxy: ClientProxyMock()))))
try await deferredAction.fulfill()
}
}