mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 16:10:20 +00:00
Add support for manually starting SaS verification flows and accepting remotely started ones
This commit is contained in:
committed by
Stefan Ceriu
parent
7888736a4d
commit
956e92a9ac
@@ -23,8 +23,6 @@
|
|||||||
|
|
||||||
"session_verification_banner_title" = "Help keep your messages secure";
|
"session_verification_banner_title" = "Help keep your messages secure";
|
||||||
"session_verification_banner_message" = "Looks like you’re using a new device. Verify its you.";
|
"session_verification_banner_message" = "Looks like you’re using a new device. Verify its you.";
|
||||||
"session_verification_screen_emojis_title" = "Lets check if these";
|
|
||||||
"session_verification_screen_emojis_message" = "Open Element on one of your other sessions to compare.";
|
|
||||||
|
|
||||||
"server_selection_server_footer" = "You can only connect to a server that has already been set up";
|
"server_selection_server_footer" = "You can only connect to a server that has already been set up";
|
||||||
|
|
||||||
|
|||||||
@@ -58,10 +58,6 @@ extension ElementL10n {
|
|||||||
public static let sessionVerificationBannerMessage = ElementL10n.tr("Untranslated", "session_verification_banner_message")
|
public static let sessionVerificationBannerMessage = ElementL10n.tr("Untranslated", "session_verification_banner_message")
|
||||||
/// Help keep your messages secure
|
/// Help keep your messages secure
|
||||||
public static let sessionVerificationBannerTitle = ElementL10n.tr("Untranslated", "session_verification_banner_title")
|
public static let sessionVerificationBannerTitle = ElementL10n.tr("Untranslated", "session_verification_banner_title")
|
||||||
/// Open Element on one of your other sessions to compare.
|
|
||||||
public static let sessionVerificationScreenEmojisMessage = ElementL10n.tr("Untranslated", "session_verification_screen_emojis_message")
|
|
||||||
/// Lets check if these
|
|
||||||
public static let sessionVerificationScreenEmojisTitle = ElementL10n.tr("Untranslated", "session_verification_screen_emojis_title")
|
|
||||||
/// Appearance
|
/// Appearance
|
||||||
public static let settingsAppearance = ElementL10n.tr("Untranslated", "settings_appearance")
|
public static let settingsAppearance = ElementL10n.tr("Untranslated", "settings_appearance")
|
||||||
/// Timeline Style
|
/// Timeline Style
|
||||||
|
|||||||
@@ -22,22 +22,19 @@ enum SessionVerificationViewModelAction {
|
|||||||
|
|
||||||
struct SessionVerificationViewState: BindableState {
|
struct SessionVerificationViewState: BindableState {
|
||||||
var verificationState: SessionVerificationStateMachine.State = .initial
|
var verificationState: SessionVerificationStateMachine.State = .initial
|
||||||
|
|
||||||
var title: String? {
|
|
||||||
switch verificationState {
|
|
||||||
case .showingChallenge:
|
|
||||||
return ElementL10n.sessionVerificationScreenEmojisTitle
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var message: String {
|
var message: String {
|
||||||
switch verificationState {
|
switch verificationState {
|
||||||
case .initial:
|
case .initial:
|
||||||
return ElementL10n.verificationOpenOtherToVerify
|
return ElementL10n.verificationOpenOtherToVerify
|
||||||
case .requestingVerification:
|
case .requestingVerification:
|
||||||
return ElementL10n.verificationRequestWaiting
|
return ElementL10n.verificationRequestWaiting
|
||||||
|
case .verificationRequestAccepted:
|
||||||
|
return ElementL10n.verificationEmojiNotice
|
||||||
|
case .startingSasVerification:
|
||||||
|
return ElementL10n.verificationRequestWaiting
|
||||||
|
case .sasVerificationStarted:
|
||||||
|
return ElementL10n.verificationRequestWaiting
|
||||||
case .acceptingChallenge:
|
case .acceptingChallenge:
|
||||||
return ElementL10n.verificationRequestWaiting
|
return ElementL10n.verificationRequestWaiting
|
||||||
case .decliningChallenge:
|
case .decliningChallenge:
|
||||||
@@ -45,7 +42,7 @@ struct SessionVerificationViewState: BindableState {
|
|||||||
case .cancelling:
|
case .cancelling:
|
||||||
return ElementL10n.verificationRequestWaiting
|
return ElementL10n.verificationRequestWaiting
|
||||||
case .showingChallenge:
|
case .showingChallenge:
|
||||||
return ElementL10n.sessionVerificationScreenEmojisMessage
|
return ElementL10n.verificationCodeNotice
|
||||||
case .verified:
|
case .verified:
|
||||||
return ElementL10n.verificationConclusionOkSelfNotice
|
return ElementL10n.verificationConclusionOkSelfNotice
|
||||||
case .cancelled:
|
case .cancelled:
|
||||||
@@ -55,7 +52,8 @@ struct SessionVerificationViewState: BindableState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enum SessionVerificationViewAction {
|
enum SessionVerificationViewAction {
|
||||||
case start
|
case requestVerification
|
||||||
|
case startSasVerification
|
||||||
case restart
|
case restart
|
||||||
case accept
|
case accept
|
||||||
case decline
|
case decline
|
||||||
|
|||||||
@@ -24,6 +24,12 @@ class SessionVerificationStateMachine {
|
|||||||
case initial
|
case initial
|
||||||
/// Waiting for verification acceptance
|
/// Waiting for verification acceptance
|
||||||
case requestingVerification
|
case requestingVerification
|
||||||
|
/// Verification request accepted. Waiting for start
|
||||||
|
case verificationRequestAccepted
|
||||||
|
/// Waiting for SaS verification start
|
||||||
|
case startingSasVerification
|
||||||
|
/// A SaS verification flow has been started
|
||||||
|
case sasVerificationStarted
|
||||||
/// Verification accepted and emojis received
|
/// Verification accepted and emojis received
|
||||||
case showingChallenge(emojis: [SessionVerificationEmoji])
|
case showingChallenge(emojis: [SessionVerificationEmoji])
|
||||||
/// Emojis match locally
|
/// Emojis match locally
|
||||||
@@ -42,6 +48,12 @@ class SessionVerificationStateMachine {
|
|||||||
enum Event: EventType {
|
enum Event: EventType {
|
||||||
/// Request verification
|
/// Request verification
|
||||||
case requestVerification
|
case requestVerification
|
||||||
|
/// The current verification request has been accepted
|
||||||
|
case didAcceptVerificationRequest
|
||||||
|
/// Start a SaS verification flow
|
||||||
|
case startSasVerification
|
||||||
|
/// Started a SaS verification flow
|
||||||
|
case didStartSasVerification
|
||||||
/// Has received emojis
|
/// Has received emojis
|
||||||
case didReceiveChallenge(emojis: [SessionVerificationEmoji])
|
case didReceiveChallenge(emojis: [SessionVerificationEmoji])
|
||||||
/// Emojis match
|
/// Emojis match
|
||||||
@@ -69,15 +81,19 @@ class SessionVerificationStateMachine {
|
|||||||
init() {
|
init() {
|
||||||
stateMachine = StateMachine(state: .initial) { machine in
|
stateMachine = StateMachine(state: .initial) { machine in
|
||||||
machine.addRoutes(event: .requestVerification, transitions: [.initial => .requestingVerification])
|
machine.addRoutes(event: .requestVerification, transitions: [.initial => .requestingVerification])
|
||||||
|
machine.addRoutes(event: .didAcceptVerificationRequest, transitions: [.requestingVerification => .verificationRequestAccepted])
|
||||||
|
machine.addRoutes(event: .startSasVerification, transitions: [.verificationRequestAccepted => .startingSasVerification])
|
||||||
machine.addRoutes(event: .didFail, transitions: [.requestingVerification => .initial])
|
machine.addRoutes(event: .didFail, transitions: [.requestingVerification => .initial])
|
||||||
machine.addRoutes(event: .restart, transitions: [.cancelled => .initial])
|
machine.addRoutes(event: .restart, transitions: [.cancelled => .initial])
|
||||||
|
|
||||||
// Transitions with associated values need to be handled through `addRouteMapping`
|
// Transitions with associated values need to be handled through `addRouteMapping`
|
||||||
machine.addRouteMapping { event, fromState, _ in
|
machine.addRouteMapping { event, fromState, _ in
|
||||||
switch (event, fromState) {
|
switch (event, fromState) {
|
||||||
case (.didReceiveChallenge(let emojis), .requestingVerification):
|
case (.didStartSasVerification, _):
|
||||||
|
return .sasVerificationStarted
|
||||||
|
|
||||||
|
case (.didReceiveChallenge(let emojis), .sasVerificationStarted):
|
||||||
return .showingChallenge(emojis: emojis)
|
return .showingChallenge(emojis: emojis)
|
||||||
|
|
||||||
case (.acceptChallenge, .showingChallenge(let emojis)):
|
case (.acceptChallenge, .showingChallenge(let emojis)):
|
||||||
return .acceptingChallenge(emojis: emojis)
|
return .acceptingChallenge(emojis: emojis)
|
||||||
case (.didFail, .acceptingChallenge(let emojis)):
|
case (.didFail, .acceptingChallenge(let emojis)):
|
||||||
@@ -100,6 +116,14 @@ class SessionVerificationStateMachine {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addTransitionHandler { context in
|
||||||
|
if let event = context.event {
|
||||||
|
MXLog.info("Transitioning from `\(context.fromState)` to `\(context.toState)` with event `\(event)`")
|
||||||
|
} else {
|
||||||
|
MXLog.info("Transitioning from \(context.fromState)` to `\(context.toState)`")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -41,8 +41,12 @@ class SessionVerificationViewModel: SessionVerificationViewModelType, SessionVer
|
|||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
|
|
||||||
switch callback {
|
switch callback {
|
||||||
|
case .acceptedVerificationRequest:
|
||||||
|
self.stateMachine.processEvent(.didAcceptVerificationRequest)
|
||||||
|
case .startedSasVerification:
|
||||||
|
self.stateMachine.processEvent(.didStartSasVerification)
|
||||||
case .receivedVerificationData(let emojis):
|
case .receivedVerificationData(let emojis):
|
||||||
guard self.stateMachine.state == .requestingVerification else {
|
guard self.stateMachine.state == .sasVerificationStarted else {
|
||||||
MXLog.warning("Callbacks: Ignoring receivedVerificationData due to invalid state.")
|
MXLog.warning("Callbacks: Ignoring receivedVerificationData due to invalid state.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -61,8 +65,10 @@ class SessionVerificationViewModel: SessionVerificationViewModelType, SessionVer
|
|||||||
|
|
||||||
override func process(viewAction: SessionVerificationViewAction) async {
|
override func process(viewAction: SessionVerificationViewAction) async {
|
||||||
switch viewAction {
|
switch viewAction {
|
||||||
case .start:
|
case .requestVerification:
|
||||||
stateMachine.processEvent(.requestVerification)
|
stateMachine.processEvent(.requestVerification)
|
||||||
|
case .startSasVerification:
|
||||||
|
stateMachine.processEvent(.startSasVerification)
|
||||||
case .restart:
|
case .restart:
|
||||||
stateMachine.processEvent(.restart)
|
stateMachine.processEvent(.restart)
|
||||||
case .close:
|
case .close:
|
||||||
@@ -92,6 +98,8 @@ class SessionVerificationViewModel: SessionVerificationViewModelType, SessionVer
|
|||||||
switch (context.fromState, context.event, context.toState) {
|
switch (context.fromState, context.event, context.toState) {
|
||||||
case (.initial, .requestVerification, .requestingVerification):
|
case (.initial, .requestVerification, .requestingVerification):
|
||||||
self.requestVerification()
|
self.requestVerification()
|
||||||
|
case (.verificationRequestAccepted, .startSasVerification, .startingSasVerification):
|
||||||
|
self.startSasVerification()
|
||||||
case (.showingChallenge, .acceptChallenge, .acceptingChallenge):
|
case (.showingChallenge, .acceptChallenge, .acceptingChallenge):
|
||||||
self.acceptChallenge()
|
self.acceptChallenge()
|
||||||
case (.showingChallenge, .declineChallenge, .decliningChallenge):
|
case (.showingChallenge, .declineChallenge, .decliningChallenge):
|
||||||
@@ -131,6 +139,18 @@ class SessionVerificationViewModel: SessionVerificationViewModelType, SessionVer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func startSasVerification() {
|
||||||
|
Task {
|
||||||
|
switch await sessionVerificationControllerProxy.startSasVerification() {
|
||||||
|
case .success:
|
||||||
|
// Need to wait for the callback from the remote
|
||||||
|
break
|
||||||
|
case .failure:
|
||||||
|
stateMachine.processEvent(.didFail)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func acceptChallenge() {
|
private func acceptChallenge() {
|
||||||
Task {
|
Task {
|
||||||
switch await sessionVerificationControllerProxy.approveVerification() {
|
switch await sessionVerificationControllerProxy.approveVerification() {
|
||||||
|
|||||||
@@ -23,13 +23,6 @@ struct SessionVerificationScreen: View {
|
|||||||
NavigationView {
|
NavigationView {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
VStack(spacing: 32.0) {
|
VStack(spacing: 32.0) {
|
||||||
if let title = context.viewState.title {
|
|
||||||
Text(title)
|
|
||||||
.font(.element.headlineBold)
|
|
||||||
.foregroundColor(.element.systemPrimaryLabel)
|
|
||||||
.multilineTextAlignment(.center)
|
|
||||||
}
|
|
||||||
|
|
||||||
Text(context.viewState.message)
|
Text(context.viewState.message)
|
||||||
.font(.body)
|
.font(.body)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
@@ -66,6 +59,15 @@ struct SessionVerificationScreen: View {
|
|||||||
case .requestingVerification:
|
case .requestingVerification:
|
||||||
ProgressView()
|
ProgressView()
|
||||||
.accessibilityIdentifier("requestingVerificationProgressView")
|
.accessibilityIdentifier("requestingVerificationProgressView")
|
||||||
|
|
||||||
|
case .verificationRequestAccepted:
|
||||||
|
StateIcon(systemName: "lock.shield")
|
||||||
|
case .startingSasVerification:
|
||||||
|
ProgressView()
|
||||||
|
.accessibilityIdentifier("startingSasVerification")
|
||||||
|
case .sasVerificationStarted:
|
||||||
|
ProgressView()
|
||||||
|
.accessibilityIdentifier("startedSasVerification")
|
||||||
case .cancelling:
|
case .cancelling:
|
||||||
ProgressView()
|
ProgressView()
|
||||||
.accessibilityIdentifier("cancellingVerificationProgressView")
|
.accessibilityIdentifier("cancellingVerificationProgressView")
|
||||||
@@ -98,10 +100,10 @@ struct SessionVerificationScreen: View {
|
|||||||
switch context.viewState.verificationState {
|
switch context.viewState.verificationState {
|
||||||
case .initial:
|
case .initial:
|
||||||
Button(ElementL10n.startVerification) {
|
Button(ElementL10n.startVerification) {
|
||||||
context.send(viewAction: .start)
|
context.send(viewAction: .requestVerification)
|
||||||
}
|
}
|
||||||
.buttonStyle(.elementAction(.xLarge))
|
.buttonStyle(.elementAction(.xLarge))
|
||||||
.accessibilityIdentifier("startButton")
|
.accessibilityIdentifier("requestVerificationButton")
|
||||||
|
|
||||||
case .cancelled:
|
case .cancelled:
|
||||||
Button(ElementL10n.globalRetry) {
|
Button(ElementL10n.globalRetry) {
|
||||||
@@ -109,16 +111,23 @@ struct SessionVerificationScreen: View {
|
|||||||
}
|
}
|
||||||
.buttonStyle(.elementAction(.xLarge))
|
.buttonStyle(.elementAction(.xLarge))
|
||||||
.accessibilityIdentifier("restartButton")
|
.accessibilityIdentifier("restartButton")
|
||||||
|
|
||||||
|
case .verificationRequestAccepted:
|
||||||
|
Button(ElementL10n.startVerification) {
|
||||||
|
context.send(viewAction: .startSasVerification)
|
||||||
|
}
|
||||||
|
.buttonStyle(.elementAction(.xLarge))
|
||||||
|
.accessibilityIdentifier("sasVerificationStartButton")
|
||||||
|
|
||||||
case .showingChallenge:
|
case .showingChallenge:
|
||||||
VStack(spacing: 30) {
|
VStack(spacing: 30) {
|
||||||
Button { context.send(viewAction: .accept) } label: {
|
Button { context.send(viewAction: .accept) } label: {
|
||||||
Label(ElementL10n.actionMatch, systemImage: "checkmark")
|
Label(ElementL10n.verificationSasMatch, systemImage: "checkmark")
|
||||||
}
|
}
|
||||||
.buttonStyle(.elementAction(.xLarge))
|
.buttonStyle(.elementAction(.xLarge))
|
||||||
.accessibilityLabel("challengeAcceptButton")
|
.accessibilityLabel("challengeAcceptButton")
|
||||||
|
|
||||||
Button(ElementL10n.no) {
|
Button(ElementL10n.verificationSasDoNotMatch) {
|
||||||
context.send(viewAction: .decline)
|
context.send(viewAction: .decline)
|
||||||
}
|
}
|
||||||
.font(.element.bodyBold)
|
.font(.element.bodyBold)
|
||||||
|
|||||||
+15
-1
@@ -27,7 +27,21 @@ struct MockSessionVerificationControllerProxy: SessionVerificationControllerProx
|
|||||||
func requestVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
func requestVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||||
Task.detached {
|
Task.detached {
|
||||||
try await Task.sleep(for: requestDelay)
|
try await Task.sleep(for: requestDelay)
|
||||||
callbacks.send(.receivedVerificationData(Self.emojis))
|
callbacks.send(.acceptedVerificationRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
return .success(())
|
||||||
|
}
|
||||||
|
|
||||||
|
func startSasVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||||
|
Task.detached {
|
||||||
|
try await Task.sleep(for: requestDelay)
|
||||||
|
callbacks.send(.startedSasVerification)
|
||||||
|
|
||||||
|
Task.detached {
|
||||||
|
try await Task.sleep(for: requestDelay)
|
||||||
|
callbacks.send(.receivedVerificationData(Self.emojis))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return .success(())
|
return .success(())
|
||||||
|
|||||||
+27
@@ -31,6 +31,14 @@ private class WeakSessionVerificationControllerProxy: SessionVerificationControl
|
|||||||
proxy?.didReceiveData(data)
|
proxy?.didReceiveData(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func didAcceptVerificationRequest() {
|
||||||
|
proxy?.didAcceptVerificationRequest()
|
||||||
|
}
|
||||||
|
|
||||||
|
func didStartSasVerification() {
|
||||||
|
proxy?.didStartSasVerification()
|
||||||
|
}
|
||||||
|
|
||||||
func didFail() {
|
func didFail() {
|
||||||
proxy?.didFail()
|
proxy?.didFail()
|
||||||
}
|
}
|
||||||
@@ -73,6 +81,17 @@ class SessionVerificationControllerProxy: SessionVerificationControllerProxyProt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func startSasVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||||
|
await Task.dispatch(on: .global()) {
|
||||||
|
do {
|
||||||
|
try self.sessionVerificationController.startSasVerification()
|
||||||
|
return .success(())
|
||||||
|
} catch {
|
||||||
|
return .failure(.failedStartingSasVerification)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func approveVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
func approveVerification() async -> Result<Void, SessionVerificationControllerProxyError> {
|
||||||
await Task.dispatch(on: .global()) {
|
await Task.dispatch(on: .global()) {
|
||||||
do {
|
do {
|
||||||
@@ -108,6 +127,14 @@ class SessionVerificationControllerProxy: SessionVerificationControllerProxyProt
|
|||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
|
fileprivate func didAcceptVerificationRequest() {
|
||||||
|
callbacks.send(.acceptedVerificationRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileprivate func didStartSasVerification() {
|
||||||
|
callbacks.send(.startedSasVerification)
|
||||||
|
}
|
||||||
|
|
||||||
fileprivate func didReceiveData(_ data: [MatrixRustSDK.SessionVerificationEmoji]) {
|
fileprivate func didReceiveData(_ data: [MatrixRustSDK.SessionVerificationEmoji]) {
|
||||||
callbacks.send(.receivedVerificationData(data.map { emoji in
|
callbacks.send(.receivedVerificationData(data.map { emoji in
|
||||||
SessionVerificationEmoji(symbol: emoji.symbol(), description: emoji.description())
|
SessionVerificationEmoji(symbol: emoji.symbol(), description: emoji.description())
|
||||||
|
|||||||
+5
@@ -19,12 +19,15 @@ import Foundation
|
|||||||
|
|
||||||
enum SessionVerificationControllerProxyError: Error {
|
enum SessionVerificationControllerProxyError: Error {
|
||||||
case failedRequestingVerification
|
case failedRequestingVerification
|
||||||
|
case failedStartingSasVerification
|
||||||
case failedApprovingVerification
|
case failedApprovingVerification
|
||||||
case failedDecliningVerification
|
case failedDecliningVerification
|
||||||
case failedCancellingVerification
|
case failedCancellingVerification
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SessionVerificationControllerProxyCallback {
|
enum SessionVerificationControllerProxyCallback {
|
||||||
|
case acceptedVerificationRequest
|
||||||
|
case startedSasVerification
|
||||||
case receivedVerificationData([SessionVerificationEmoji])
|
case receivedVerificationData([SessionVerificationEmoji])
|
||||||
case finished
|
case finished
|
||||||
case cancelled
|
case cancelled
|
||||||
@@ -43,6 +46,8 @@ protocol SessionVerificationControllerProxyProtocol {
|
|||||||
|
|
||||||
func requestVerification() async -> Result<Void, SessionVerificationControllerProxyError>
|
func requestVerification() async -> Result<Void, SessionVerificationControllerProxyError>
|
||||||
|
|
||||||
|
func startSasVerification() async -> Result<Void, SessionVerificationControllerProxyError>
|
||||||
|
|
||||||
func approveVerification() async -> Result<Void, SessionVerificationControllerProxyError>
|
func approveVerification() async -> Result<Void, SessionVerificationControllerProxyError>
|
||||||
|
|
||||||
func declineVerification() async -> Result<Void, SessionVerificationControllerProxyError>
|
func declineVerification() async -> Result<Void, SessionVerificationControllerProxyError>
|
||||||
|
|||||||
@@ -22,16 +22,22 @@ class SessionVerificationUITests: XCTestCase {
|
|||||||
let app = Application.launch()
|
let app = Application.launch()
|
||||||
app.goToScreenWithIdentifier(.sessionVerification)
|
app.goToScreenWithIdentifier(.sessionVerification)
|
||||||
|
|
||||||
XCTAssert(app.buttons["startButton"].exists)
|
XCTAssert(app.buttons["requestVerificationButton"].exists)
|
||||||
XCTAssert(app.buttons["closeButton"].exists)
|
XCTAssert(app.buttons["closeButton"].exists)
|
||||||
XCTAssert(app.staticTexts["titleLabel"].exists)
|
XCTAssert(app.staticTexts["titleLabel"].exists)
|
||||||
|
|
||||||
app.assertScreenshot(.sessionVerification)
|
app.assertScreenshot(.sessionVerification)
|
||||||
|
|
||||||
app.buttons["startButton"].tap()
|
app.buttons["requestVerificationButton"].tap()
|
||||||
|
|
||||||
XCTAssert(app.activityIndicators["requestingVerificationProgressView"].exists)
|
XCTAssert(app.activityIndicators["requestingVerificationProgressView"].exists)
|
||||||
|
|
||||||
|
XCTAssert(app.buttons["sasVerificationStartButton"].waitForExistence(timeout: 5.0))
|
||||||
|
app.buttons["sasVerificationStartButton"].tap()
|
||||||
|
|
||||||
|
XCTAssert(app.activityIndicators["startingSasVerification"].waitForExistence(timeout: 5.0))
|
||||||
|
XCTAssert(app.activityIndicators["startedSasVerification"].waitForExistence(timeout: 5.0))
|
||||||
|
|
||||||
XCTAssert(app.buttons["challengeAcceptButton"].waitForExistence(timeout: 5.0))
|
XCTAssert(app.buttons["challengeAcceptButton"].waitForExistence(timeout: 5.0))
|
||||||
XCTAssert(app.buttons["challengeDeclineButton"].waitForExistence(timeout: 5.0))
|
XCTAssert(app.buttons["challengeDeclineButton"].waitForExistence(timeout: 5.0))
|
||||||
|
|
||||||
@@ -50,14 +56,20 @@ class SessionVerificationUITests: XCTestCase {
|
|||||||
let app = Application.launch()
|
let app = Application.launch()
|
||||||
app.goToScreenWithIdentifier(.sessionVerification)
|
app.goToScreenWithIdentifier(.sessionVerification)
|
||||||
|
|
||||||
XCTAssert(app.buttons["startButton"].exists)
|
XCTAssert(app.buttons["requestVerificationButton"].exists)
|
||||||
XCTAssert(app.buttons["closeButton"].exists)
|
XCTAssert(app.buttons["closeButton"].exists)
|
||||||
XCTAssert(app.staticTexts["titleLabel"].exists)
|
XCTAssert(app.staticTexts["titleLabel"].exists)
|
||||||
|
|
||||||
app.buttons["startButton"].tap()
|
app.buttons["requestVerificationButton"].tap()
|
||||||
|
|
||||||
XCTAssert(app.activityIndicators["requestingVerificationProgressView"].exists)
|
XCTAssert(app.activityIndicators["requestingVerificationProgressView"].exists)
|
||||||
|
|
||||||
|
XCTAssert(app.buttons["sasVerificationStartButton"].waitForExistence(timeout: 5.0))
|
||||||
|
app.buttons["sasVerificationStartButton"].tap()
|
||||||
|
|
||||||
|
XCTAssert(app.activityIndicators["startingSasVerification"].waitForExistence(timeout: 5.0))
|
||||||
|
XCTAssert(app.activityIndicators["startedSasVerification"].waitForExistence(timeout: 5.0))
|
||||||
|
|
||||||
XCTAssert(app.buttons["challengeAcceptButton"].waitForExistence(timeout: 5.0))
|
XCTAssert(app.buttons["challengeAcceptButton"].waitForExistence(timeout: 5.0))
|
||||||
XCTAssert(app.buttons["challengeDeclineButton"].waitForExistence(timeout: 5.0))
|
XCTAssert(app.buttons["challengeDeclineButton"].waitForExistence(timeout: 5.0))
|
||||||
|
|
||||||
@@ -74,14 +86,20 @@ class SessionVerificationUITests: XCTestCase {
|
|||||||
let app = Application.launch()
|
let app = Application.launch()
|
||||||
app.goToScreenWithIdentifier(.sessionVerification)
|
app.goToScreenWithIdentifier(.sessionVerification)
|
||||||
|
|
||||||
XCTAssert(app.buttons["startButton"].exists)
|
XCTAssert(app.buttons["requestVerificationButton"].exists)
|
||||||
XCTAssert(app.buttons["closeButton"].exists)
|
XCTAssert(app.buttons["closeButton"].exists)
|
||||||
XCTAssert(app.staticTexts["titleLabel"].exists)
|
XCTAssert(app.staticTexts["titleLabel"].exists)
|
||||||
|
|
||||||
app.buttons["startButton"].tap()
|
app.buttons["requestVerificationButton"].tap()
|
||||||
|
|
||||||
XCTAssert(app.activityIndicators["requestingVerificationProgressView"].waitForExistence(timeout: 1))
|
XCTAssert(app.activityIndicators["requestingVerificationProgressView"].waitForExistence(timeout: 1))
|
||||||
|
|
||||||
|
XCTAssert(app.buttons["sasVerificationStartButton"].waitForExistence(timeout: 5.0))
|
||||||
|
app.buttons["sasVerificationStartButton"].tap()
|
||||||
|
|
||||||
|
XCTAssert(app.activityIndicators["startingSasVerification"].waitForExistence(timeout: 5.0))
|
||||||
|
XCTAssert(app.activityIndicators["startedSasVerification"].waitForExistence(timeout: 5.0))
|
||||||
|
|
||||||
app.buttons["closeButton"].tap()
|
app.buttons["closeButton"].tap()
|
||||||
|
|
||||||
XCTAssert(app.images["sessionVerificationFailedIcon"].waitForExistence(timeout: 1))
|
XCTAssert(app.images["sessionVerificationFailedIcon"].waitForExistence(timeout: 1))
|
||||||
|
|||||||
@@ -33,6 +33,12 @@ class SessionVerificationStateMachineTests: XCTestCase {
|
|||||||
stateMachine.processEvent(.requestVerification)
|
stateMachine.processEvent(.requestVerification)
|
||||||
XCTAssertEqual(stateMachine.state, .requestingVerification)
|
XCTAssertEqual(stateMachine.state, .requestingVerification)
|
||||||
|
|
||||||
|
stateMachine.processEvent(.didAcceptVerificationRequest)
|
||||||
|
XCTAssertEqual(stateMachine.state, .verificationRequestAccepted)
|
||||||
|
|
||||||
|
stateMachine.processEvent(.didStartSasVerification)
|
||||||
|
XCTAssertEqual(stateMachine.state, .sasVerificationStarted)
|
||||||
|
|
||||||
stateMachine.processEvent(.didReceiveChallenge(emojis: MockSessionVerificationControllerProxy.emojis))
|
stateMachine.processEvent(.didReceiveChallenge(emojis: MockSessionVerificationControllerProxy.emojis))
|
||||||
XCTAssertEqual(stateMachine.state, .showingChallenge(emojis: MockSessionVerificationControllerProxy.emojis))
|
XCTAssertEqual(stateMachine.state, .showingChallenge(emojis: MockSessionVerificationControllerProxy.emojis))
|
||||||
|
|
||||||
@@ -49,6 +55,12 @@ class SessionVerificationStateMachineTests: XCTestCase {
|
|||||||
stateMachine.processEvent(.requestVerification)
|
stateMachine.processEvent(.requestVerification)
|
||||||
XCTAssertEqual(stateMachine.state, .requestingVerification)
|
XCTAssertEqual(stateMachine.state, .requestingVerification)
|
||||||
|
|
||||||
|
stateMachine.processEvent(.didAcceptVerificationRequest)
|
||||||
|
XCTAssertEqual(stateMachine.state, .verificationRequestAccepted)
|
||||||
|
|
||||||
|
stateMachine.processEvent(.didStartSasVerification)
|
||||||
|
XCTAssertEqual(stateMachine.state, .sasVerificationStarted)
|
||||||
|
|
||||||
stateMachine.processEvent(.didReceiveChallenge(emojis: MockSessionVerificationControllerProxy.emojis))
|
stateMachine.processEvent(.didReceiveChallenge(emojis: MockSessionVerificationControllerProxy.emojis))
|
||||||
XCTAssertEqual(stateMachine.state, .showingChallenge(emojis: MockSessionVerificationControllerProxy.emojis))
|
XCTAssertEqual(stateMachine.state, .showingChallenge(emojis: MockSessionVerificationControllerProxy.emojis))
|
||||||
|
|
||||||
@@ -84,6 +96,12 @@ class SessionVerificationStateMachineTests: XCTestCase {
|
|||||||
stateMachine.processEvent(.requestVerification)
|
stateMachine.processEvent(.requestVerification)
|
||||||
XCTAssertEqual(stateMachine.state, .requestingVerification)
|
XCTAssertEqual(stateMachine.state, .requestingVerification)
|
||||||
|
|
||||||
|
stateMachine.processEvent(.didAcceptVerificationRequest)
|
||||||
|
XCTAssertEqual(stateMachine.state, .verificationRequestAccepted)
|
||||||
|
|
||||||
|
stateMachine.processEvent(.didStartSasVerification)
|
||||||
|
XCTAssertEqual(stateMachine.state, .sasVerificationStarted)
|
||||||
|
|
||||||
stateMachine.processEvent(.didReceiveChallenge(emojis: MockSessionVerificationControllerProxy.emojis))
|
stateMachine.processEvent(.didReceiveChallenge(emojis: MockSessionVerificationControllerProxy.emojis))
|
||||||
XCTAssertEqual(stateMachine.state, .showingChallenge(emojis: MockSessionVerificationControllerProxy.emojis))
|
XCTAssertEqual(stateMachine.state, .showingChallenge(emojis: MockSessionVerificationControllerProxy.emojis))
|
||||||
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class SessionVerificationViewModelTests: XCTestCase {
|
|||||||
func testRequestVerification() async {
|
func testRequestVerification() async {
|
||||||
XCTAssertEqual(context.viewState.verificationState, .initial)
|
XCTAssertEqual(context.viewState.verificationState, .initial)
|
||||||
|
|
||||||
context.send(viewAction: .start)
|
context.send(viewAction: .requestVerification)
|
||||||
|
|
||||||
await Task.yield()
|
await Task.yield()
|
||||||
|
|
||||||
@@ -45,7 +45,7 @@ class SessionVerificationViewModelTests: XCTestCase {
|
|||||||
func testVerificationCancellation() async throws {
|
func testVerificationCancellation() async throws {
|
||||||
XCTAssertEqual(context.viewState.verificationState, .initial)
|
XCTAssertEqual(context.viewState.verificationState, .initial)
|
||||||
|
|
||||||
context.send(viewAction: .start)
|
context.send(viewAction: .requestVerification)
|
||||||
|
|
||||||
context.send(viewAction: .close)
|
context.send(viewAction: .close)
|
||||||
|
|
||||||
@@ -125,14 +125,20 @@ class SessionVerificationViewModelTests: XCTestCase {
|
|||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private func setupChallengeReceived() {
|
private func setupChallengeReceived() {
|
||||||
let expectation = XCTestExpectation(description: "Wait for challenge")
|
let requestAcceptanceExpectation = XCTestExpectation(description: "Wait for request acceptance")
|
||||||
|
let sasVerificationStartExpectation = XCTestExpectation(description: "Wait for SaS verification start")
|
||||||
|
let verificationDataReceivalExpectation = XCTestExpectation(description: "Wait for Emoji data")
|
||||||
|
|
||||||
let cancellable = sessionVerificationController.callbacks
|
let cancellable = sessionVerificationController.callbacks
|
||||||
.debounce(for: .seconds(2.0), scheduler: DispatchQueue.main)
|
.debounce(for: .seconds(2.0), scheduler: DispatchQueue.main)
|
||||||
.sink { callback in
|
.sink { callback in
|
||||||
switch callback {
|
switch callback {
|
||||||
|
case .acceptedVerificationRequest:
|
||||||
|
requestAcceptanceExpectation.fulfill()
|
||||||
|
case .startedSasVerification:
|
||||||
|
sasVerificationStartExpectation.fulfill()
|
||||||
case .receivedVerificationData:
|
case .receivedVerificationData:
|
||||||
expectation.fulfill()
|
verificationDataReceivalExpectation.fulfill()
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -142,10 +148,15 @@ class SessionVerificationViewModelTests: XCTestCase {
|
|||||||
cancellable.cancel()
|
cancellable.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
context.send(viewAction: .start)
|
context.send(viewAction: .requestVerification)
|
||||||
|
wait(for: [requestAcceptanceExpectation], timeout: 10.0)
|
||||||
|
XCTAssertEqual(context.viewState.verificationState, .verificationRequestAccepted)
|
||||||
|
|
||||||
wait(for: [expectation], timeout: 10.0)
|
context.send(viewAction: .startSasVerification)
|
||||||
|
wait(for: [sasVerificationStartExpectation], timeout: 10.0)
|
||||||
|
XCTAssertEqual(context.viewState.verificationState, .sasVerificationStarted)
|
||||||
|
|
||||||
|
wait(for: [verificationDataReceivalExpectation], timeout: 10.0)
|
||||||
XCTAssertEqual(context.viewState.verificationState, .showingChallenge(emojis: MockSessionVerificationControllerProxy.emojis))
|
XCTAssertEqual(context.viewState.verificationState, .showingChallenge(emojis: MockSessionVerificationControllerProxy.emojis))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
Add support for manually starting SaS verification flows and accepting remotely started ones
|
||||||
Reference in New Issue
Block a user