mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-10-31 08:15:24 +00:00
2d486ece33
* use observation in unit and preview tests instead of the async stream * use a parsing approach that is independent of the sourcery annotation * Fix observation deferred fulfillments not receiving the initial value Observation's observe only emits transitions, not the value present at subscription time. Drop the leading initial value from the affected transitionValues sequences and assert it directly with #expect instead. * improved docs
266 lines
12 KiB
Swift
266 lines
12 KiB
Swift
//
|
|
// Copyright 2025 Element Creations Ltd.
|
|
// Copyright 2022-2025 New Vector Ltd.
|
|
//
|
|
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
|
// Please see LICENSE files in the repository root for full details.
|
|
//
|
|
|
|
@testable import ElementX
|
|
import MatrixRustSDK
|
|
import Testing
|
|
|
|
@MainActor
|
|
struct NotificationSettingsEditScreenViewModelTests {
|
|
private var viewModel: NotificationSettingsEditScreenViewModelProtocol!
|
|
private var notificationSettingsProxy: NotificationSettingsProxyMock!
|
|
private var userSession: UserSessionMock!
|
|
private var clientProxy: ClientProxyMock!
|
|
|
|
private var context: NotificationSettingsEditScreenViewModelType.Context {
|
|
viewModel.context
|
|
}
|
|
|
|
init() throws {
|
|
notificationSettingsProxy = NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration())
|
|
notificationSettingsProxy.getDefaultRoomNotificationModeIsEncryptedIsOneToOneReturnValue = .allMessages
|
|
|
|
clientProxy = ClientProxyMock(.init(userID: "@a:b.com", notificationSettings: notificationSettingsProxy))
|
|
userSession = UserSessionMock(.init(clientProxy: clientProxy))
|
|
}
|
|
|
|
@Test
|
|
mutating func fetchSettings() async throws {
|
|
notificationSettingsProxy.getDefaultRoomNotificationModeIsEncryptedIsOneToOneClosure = { isEncrypted, isOneToOne in
|
|
switch (isEncrypted, isOneToOne) {
|
|
case (_, true):
|
|
return .allMessages
|
|
case (_, _):
|
|
return .mentionsAndKeywordsOnly
|
|
}
|
|
}
|
|
viewModel = NotificationSettingsEditScreenViewModel(chatType: .groupChat, userSession: userSession)
|
|
|
|
let deferred = deferFulfillment(viewModel.context.observe(\.viewState.defaultMode)) { $0 != nil }
|
|
|
|
viewModel.fetchInitialContent()
|
|
|
|
try await deferred.fulfill()
|
|
|
|
// `getDefaultRoomNotificationModeIsEncryptedIsOneToOne` must have been called twice (for encrypted and unencrypted group chats)
|
|
let invocations = notificationSettingsProxy.getDefaultRoomNotificationModeIsEncryptedIsOneToOneReceivedInvocations
|
|
|
|
#expect(invocations.count == 2)
|
|
// First call for encrypted group chats
|
|
#expect(invocations[0].isEncrypted == true)
|
|
#expect(invocations[0].isOneToOne == false)
|
|
// Second call for unencrypted group chats
|
|
#expect(invocations[1].isEncrypted == false)
|
|
#expect(invocations[1].isOneToOne == false)
|
|
|
|
#expect(context.viewState.defaultMode == .mentionsAndKeywordsOnly)
|
|
#expect(context.viewState.bindings.alertInfo == nil)
|
|
#expect(!context.viewState.canPushEncryptedEvents)
|
|
#expect(context.viewState.description(for: .mentionsAndKeywordsOnly) != nil)
|
|
}
|
|
|
|
@Test
|
|
mutating func fetchSettingsWithCanPushEncryptedEvents() async throws {
|
|
notificationSettingsProxy.getDefaultRoomNotificationModeIsEncryptedIsOneToOneClosure = { isEncrypted, isOneToOne in
|
|
switch (isEncrypted, isOneToOne) {
|
|
case (_, true):
|
|
return .allMessages
|
|
case (_, _):
|
|
return .mentionsAndKeywordsOnly
|
|
}
|
|
}
|
|
notificationSettingsProxy.canPushEncryptedEventsToDeviceClosure = {
|
|
true
|
|
}
|
|
viewModel = NotificationSettingsEditScreenViewModel(chatType: .groupChat, userSession: userSession)
|
|
|
|
let deferred = deferFulfillment(viewModel.context.observe(\.viewState.defaultMode)) { $0 != nil }
|
|
|
|
viewModel.fetchInitialContent()
|
|
|
|
try await deferred.fulfill()
|
|
|
|
// `getDefaultRoomNotificationModeIsEncryptedIsOneToOne` must have been called twice (for encrypted and unencrypted group chats)
|
|
let invocations = notificationSettingsProxy.getDefaultRoomNotificationModeIsEncryptedIsOneToOneReceivedInvocations
|
|
|
|
#expect(invocations.count == 2)
|
|
// First call for encrypted group chats
|
|
#expect(invocations[0].isEncrypted == true)
|
|
#expect(invocations[0].isOneToOne == false)
|
|
// Second call for unencrypted group chats
|
|
#expect(invocations[1].isEncrypted == false)
|
|
#expect(invocations[1].isOneToOne == false)
|
|
|
|
#expect(context.viewState.defaultMode == .mentionsAndKeywordsOnly)
|
|
#expect(context.viewState.bindings.alertInfo == nil)
|
|
#expect(context.viewState.canPushEncryptedEvents)
|
|
#expect(context.viewState.description(for: .mentionsAndKeywordsOnly) == nil)
|
|
}
|
|
|
|
@Test
|
|
mutating func setModeAllMessages() async throws {
|
|
notificationSettingsProxy.getDefaultRoomNotificationModeIsEncryptedIsOneToOneReturnValue = .mentionsAndKeywordsOnly
|
|
viewModel = NotificationSettingsEditScreenViewModel(chatType: .groupChat, userSession: userSession)
|
|
let deferred = deferFulfillment(viewModel.context.observe(\.viewState.defaultMode)) { $0 != nil }
|
|
|
|
viewModel.fetchInitialContent()
|
|
|
|
try await deferred.fulfill()
|
|
|
|
#expect(viewModel.context.viewState.pendingMode == nil)
|
|
|
|
var deferredViewState = deferFulfillment(viewModel.context.observe(\.viewState.pendingMode),
|
|
transitionValues: [.allMessages, nil])
|
|
|
|
context.send(viewAction: .setMode(.allMessages))
|
|
|
|
try await deferredViewState.fulfill()
|
|
|
|
// `setDefaultRoomNotificationModeIsEncryptedIsOneToOneMode` must have been called twice (for encrypted and unencrypted group chats)
|
|
let invocations = notificationSettingsProxy.setDefaultRoomNotificationModeIsEncryptedIsOneToOneModeReceivedInvocations
|
|
#expect(notificationSettingsProxy.setDefaultRoomNotificationModeIsEncryptedIsOneToOneModeCallsCount == 2)
|
|
// First call for encrypted group chats
|
|
#expect(invocations[0].isEncrypted == true)
|
|
#expect(invocations[0].isOneToOne == false)
|
|
#expect(invocations[0].mode == .allMessages)
|
|
// Second call for unencrypted group chats
|
|
#expect(invocations[1].isEncrypted == false)
|
|
#expect(invocations[1].isOneToOne == false)
|
|
#expect(invocations[1].mode == .allMessages)
|
|
|
|
deferredViewState = deferFulfillment(viewModel.context.observe(\.viewState.defaultMode),
|
|
transitionValues: [.allMessages])
|
|
|
|
try await deferredViewState.fulfill()
|
|
|
|
#expect(context.viewState.defaultMode == .allMessages)
|
|
#expect(context.viewState.bindings.alertInfo == nil)
|
|
}
|
|
|
|
@Test
|
|
mutating func setModeMentions() async throws {
|
|
viewModel = NotificationSettingsEditScreenViewModel(chatType: .groupChat, userSession: userSession)
|
|
|
|
let deferred = deferFulfillment(viewModel.context.observe(\.viewState.defaultMode)) { $0 != nil }
|
|
|
|
viewModel.fetchInitialContent()
|
|
|
|
try await deferred.fulfill()
|
|
|
|
#expect(viewModel.context.viewState.pendingMode == nil)
|
|
|
|
var deferredViewState = deferFulfillment(viewModel.context.observe(\.viewState.pendingMode),
|
|
transitionValues: [.mentionsAndKeywordsOnly, nil])
|
|
|
|
context.send(viewAction: .setMode(.mentionsAndKeywordsOnly))
|
|
|
|
try await deferredViewState.fulfill()
|
|
|
|
// `setDefaultRoomNotificationModeIsEncryptedIsOneToOneMode` must have been called twice (for encrypted and unencrypted group chats)
|
|
let invocations = notificationSettingsProxy.setDefaultRoomNotificationModeIsEncryptedIsOneToOneModeReceivedInvocations
|
|
#expect(notificationSettingsProxy.setDefaultRoomNotificationModeIsEncryptedIsOneToOneModeCallsCount == 2)
|
|
// First call for encrypted group chats
|
|
#expect(invocations[0].isEncrypted == true)
|
|
#expect(invocations[0].isOneToOne == false)
|
|
#expect(invocations[0].mode == .mentionsAndKeywordsOnly)
|
|
// Second call for unencrypted group chats
|
|
#expect(invocations[1].isEncrypted == false)
|
|
#expect(invocations[1].isOneToOne == false)
|
|
#expect(invocations[1].mode == .mentionsAndKeywordsOnly)
|
|
|
|
deferredViewState = deferFulfillment(viewModel.context.observe(\.viewState.defaultMode),
|
|
transitionValues: [.mentionsAndKeywordsOnly])
|
|
|
|
try await deferredViewState.fulfill()
|
|
|
|
#expect(context.viewState.defaultMode == .mentionsAndKeywordsOnly)
|
|
#expect(context.viewState.bindings.alertInfo == nil)
|
|
}
|
|
|
|
@Test
|
|
mutating func setModeDirectChats() async throws {
|
|
notificationSettingsProxy.getDefaultRoomNotificationModeIsEncryptedIsOneToOneReturnValue = .mentionsAndKeywordsOnly
|
|
// Initialize for direct chats
|
|
viewModel = NotificationSettingsEditScreenViewModel(chatType: .oneToOneChat, userSession: userSession)
|
|
|
|
let deferred = deferFulfillment(viewModel.context.observe(\.viewState.defaultMode)) { $0 != nil }
|
|
|
|
viewModel.fetchInitialContent()
|
|
|
|
try await deferred.fulfill()
|
|
|
|
#expect(viewModel.context.viewState.pendingMode == nil)
|
|
|
|
let deferredViewState = deferFulfillment(viewModel.context.observe(\.viewState.pendingMode),
|
|
transitionValues: [.allMessages, nil])
|
|
|
|
context.send(viewAction: .setMode(.allMessages))
|
|
|
|
try await deferredViewState.fulfill()
|
|
|
|
// `setDefaultRoomNotificationModeIsEncryptedIsOneToOneMode` must have been called twice (for encrypted and unencrypted direct chats)
|
|
let invocations = notificationSettingsProxy.setDefaultRoomNotificationModeIsEncryptedIsOneToOneModeReceivedInvocations
|
|
#expect(notificationSettingsProxy.setDefaultRoomNotificationModeIsEncryptedIsOneToOneModeCallsCount == 2)
|
|
// First call for encrypted direct chats
|
|
#expect(invocations[0].isEncrypted == true)
|
|
#expect(invocations[0].isOneToOne == true)
|
|
#expect(invocations[0].mode == .allMessages)
|
|
// Second call for unencrypted direct chats
|
|
#expect(invocations[1].isEncrypted == false)
|
|
#expect(invocations[1].isOneToOne == true)
|
|
#expect(invocations[1].mode == .allMessages)
|
|
}
|
|
|
|
@Test
|
|
mutating func setModeFailure() async throws {
|
|
notificationSettingsProxy.getDefaultRoomNotificationModeIsEncryptedIsOneToOneReturnValue = .mentionsAndKeywordsOnly
|
|
notificationSettingsProxy.setDefaultRoomNotificationModeIsEncryptedIsOneToOneModeThrowableError = NotificationSettingsError.Generic(msg: "error")
|
|
viewModel = NotificationSettingsEditScreenViewModel(chatType: .oneToOneChat, userSession: userSession)
|
|
|
|
let deferred = deferFulfillment(viewModel.context.observe(\.viewState.defaultMode)) { $0 != nil }
|
|
|
|
viewModel.fetchInitialContent()
|
|
|
|
try await deferred.fulfill()
|
|
|
|
#expect(viewModel.context.viewState.pendingMode == nil)
|
|
|
|
let deferredViewState = deferFulfillment(viewModel.context.observe(\.viewState.pendingMode),
|
|
transitionValues: [.allMessages, nil])
|
|
|
|
context.send(viewAction: .setMode(.allMessages))
|
|
|
|
try await deferredViewState.fulfill()
|
|
|
|
#expect(context.viewState.bindings.alertInfo != nil)
|
|
}
|
|
|
|
@Test
|
|
mutating func selectRoom() async throws {
|
|
let roomID = "!roomidentifier:matrix.org"
|
|
viewModel = NotificationSettingsEditScreenViewModel(chatType: .oneToOneChat, userSession: userSession)
|
|
|
|
let deferredActions = deferFulfillment(viewModel.actions) { action in
|
|
switch action {
|
|
case .requestRoomNotificationSettingsPresentation:
|
|
return true
|
|
}
|
|
}
|
|
|
|
context.send(viewAction: .selectRoom(roomIdentifier: roomID))
|
|
|
|
let sentAction = try await deferredActions.fulfill()
|
|
|
|
let expectedAction = NotificationSettingsEditScreenViewModelAction.requestRoomNotificationSettingsPresentation(roomID: roomID)
|
|
guard case let .requestRoomNotificationSettingsPresentation(roomID: receivedRoomID) = sentAction, receivedRoomID == roomID else {
|
|
Issue.record("Expected action \(expectedAction), but was \(sentAction)")
|
|
return
|
|
}
|
|
}
|
|
}
|