mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 21:39:12 +00:00
Improve tests' reliability (#763)
* Create publisher extension into the unit test target * Add ViewModelContext test extension * Refactor BugReportViewModelTests * Fix failing UTs * Idea PublishedClosure * Refactor RoomDetailsViewModelTests * Replace more Task.yield/Task.sleep * Move leaveRoom/ignore/unignore under the @MainActor * Revert "Idea PublishedClosure" This reverts commit 4ab25291041f0dbd99083baf9d95bc6647f1fd97. * Make process(viewAction:) sync * Refactor BugReportViewModel callback to a publisher * Fix UTs * Refactor ReportContentViewModel * Fix ui test build error * Try make sonar happy * Empty commit * Revert "Try make sonar happy" This reverts commit 97804b19373a8f55f12174ccbf27f1fd8db583b7. * Rename ui test identifier * Cleanup * Callback -> actions refactor * Update template * Add publisher in TemplateCoordinator * Add env variable in IntegrationTests.xctestplan * Add async sequence extension * Amend integration test plan * Remove env variable from target.yml * Cleanup * Fix failing UI tests
This commit is contained in:
parent
d24ee73d15
commit
2439431287
@ -23,17 +23,3 @@ extension Publisher where Self.Failure == Never {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Published.Publisher {
|
||||
/// Returns the next output from the publisher skipping the current value stored into it (which is readable from the @Published property itself).
|
||||
/// - Returns: the next output from the publisher
|
||||
var nextValue: Output? {
|
||||
get async {
|
||||
var iterator = values.makeAsyncIterator()
|
||||
|
||||
// skips the publisher's current value
|
||||
_ = await iterator.next()
|
||||
return await iterator.next()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -89,14 +89,14 @@ class StateStoreViewModel<State: BindableState, ViewAction> {
|
||||
.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
|
||||
Task { await self.process(viewAction: action) }
|
||||
self.process(viewAction: action)
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
/// Override to handles incoming `ViewAction`s from the `ViewModel`.
|
||||
/// - Parameter viewAction: The `ViewAction` to be processed in `ViewModel` implementation.
|
||||
func process(viewAction: ViewAction) async {
|
||||
func process(viewAction: ViewAction) {
|
||||
// Default implementation, -no-op
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ class AnalyticsPromptViewModel: AnalyticsPromptViewModelType, AnalyticsPromptVie
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: AnalyticsPromptViewAction) async {
|
||||
override func process(viewAction: AnalyticsPromptViewAction) {
|
||||
switch viewAction {
|
||||
case .enable:
|
||||
callback?(.enable)
|
||||
|
@ -28,7 +28,7 @@ class LoginViewModel: LoginViewModelType, LoginViewModelProtocol {
|
||||
super.init(initialViewState: viewState)
|
||||
}
|
||||
|
||||
override func process(viewAction: LoginViewAction) async {
|
||||
override func process(viewAction: LoginViewAction) {
|
||||
switch viewAction {
|
||||
case .selectServer:
|
||||
callback?(.selectServer)
|
||||
|
@ -28,7 +28,7 @@ class ServerSelectionViewModel: ServerSelectionViewModelType, ServerSelectionVie
|
||||
isModallyPresented: isModallyPresented))
|
||||
}
|
||||
|
||||
override func process(viewAction: ServerSelectionViewAction) async {
|
||||
override func process(viewAction: ServerSelectionViewAction) {
|
||||
switch viewAction {
|
||||
case .confirm:
|
||||
callback?(.confirm(homeserverAddress: state.bindings.homeserverAddress))
|
||||
|
@ -33,7 +33,7 @@ class SoftLogoutViewModel: SoftLogoutViewModelType, SoftLogoutViewModelProtocol
|
||||
super.init(initialViewState: viewState)
|
||||
}
|
||||
|
||||
override func process(viewAction: SoftLogoutViewAction) async {
|
||||
override func process(viewAction: SoftLogoutViewAction) {
|
||||
switch viewAction {
|
||||
case .login:
|
||||
callback?(.login(state.bindings.password))
|
||||
|
@ -14,6 +14,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
enum BugReportCoordinatorResult {
|
||||
@ -34,6 +35,7 @@ struct BugReportCoordinatorParameters {
|
||||
final class BugReportCoordinator: CoordinatorProtocol {
|
||||
private let parameters: BugReportCoordinatorParameters
|
||||
private var viewModel: BugReportViewModelProtocol
|
||||
private var cancellables: Set<AnyCancellable> = .init()
|
||||
|
||||
var completion: ((BugReportCoordinatorResult) -> Void)?
|
||||
|
||||
@ -50,7 +52,9 @@ final class BugReportCoordinator: CoordinatorProtocol {
|
||||
// MARK: - Public
|
||||
|
||||
func start() {
|
||||
viewModel.callback = { [weak self] result in
|
||||
viewModel
|
||||
.actions
|
||||
.sink { [weak self] result in
|
||||
guard let self else { return }
|
||||
MXLog.info("BugReportViewModel did complete with result: \(result).")
|
||||
switch result {
|
||||
@ -66,6 +70,7 @@ final class BugReportCoordinator: CoordinatorProtocol {
|
||||
self.showError(label: error.localizedDescription)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
func stop() {
|
||||
|
@ -14,6 +14,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
typealias BugReportViewModelType = StateStoreViewModel<BugReportViewState, BugReportViewAction>
|
||||
@ -22,8 +23,11 @@ class BugReportViewModel: BugReportViewModelType, BugReportViewModelProtocol {
|
||||
private let bugReportService: BugReportServiceProtocol
|
||||
private let userID: String
|
||||
private let deviceID: String?
|
||||
private let actionsSubject: PassthroughSubject<BugReportViewModelAction, Never> = .init()
|
||||
|
||||
var callback: ((BugReportViewModelAction) -> Void)?
|
||||
var actions: AnyPublisher<BugReportViewModelAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(bugReportService: BugReportServiceProtocol,
|
||||
userID: String,
|
||||
@ -42,12 +46,12 @@ class BugReportViewModel: BugReportViewModelType, BugReportViewModelProtocol {
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: BugReportViewAction) async {
|
||||
override func process(viewAction: BugReportViewAction) {
|
||||
switch viewAction {
|
||||
case .cancel:
|
||||
callback?(.cancel)
|
||||
actionsSubject.send(.cancel)
|
||||
case .submit:
|
||||
await submitBugReport()
|
||||
Task { await submitBugReport() }
|
||||
case .removeScreenshot:
|
||||
state.screenshot = nil
|
||||
case let .attachScreenshot(image):
|
||||
@ -59,7 +63,7 @@ class BugReportViewModel: BugReportViewModelType, BugReportViewModelProtocol {
|
||||
|
||||
private func submitBugReport() async {
|
||||
let progressTracker = ProgressTracker()
|
||||
callback?(.submitStarted(progressTracker: progressTracker))
|
||||
actionsSubject.send(.submitStarted(progressTracker: progressTracker))
|
||||
do {
|
||||
var files: [URL] = []
|
||||
if let screenshot = context.viewState.screenshot {
|
||||
@ -78,10 +82,10 @@ class BugReportViewModel: BugReportViewModelType, BugReportViewModelProtocol {
|
||||
let result = try await bugReportService.submitBugReport(bugReport,
|
||||
progressListener: progressTracker)
|
||||
MXLog.info("SubmitBugReport succeeded, result: \(result.reportUrl)")
|
||||
callback?(.submitFinished)
|
||||
actionsSubject.send(.submitFinished)
|
||||
} catch {
|
||||
MXLog.error("SubmitBugReport failed: \(error)")
|
||||
callback?(.submitFailed(error: error))
|
||||
actionsSubject.send(.submitFailed(error: error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,10 +14,10 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol BugReportViewModelProtocol {
|
||||
var callback: ((BugReportViewModelAction) -> Void)? { get set }
|
||||
var actions: AnyPublisher<BugReportViewModelAction, Never> { get }
|
||||
var context: BugReportViewModelType.Context { get }
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ class DeveloperOptionsScreenViewModel: DeveloperOptionsScreenViewModelType, Deve
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
override func process(viewAction: DeveloperOptionsScreenViewAction) async {
|
||||
override func process(viewAction: DeveloperOptionsScreenViewAction) {
|
||||
switch viewAction {
|
||||
case .changedShouldCollapseRoomStateEvents:
|
||||
ServiceLocator.shared.settings.shouldCollapseRoomStateEvents = state.bindings.shouldCollapseRoomStateEvents
|
||||
|
@ -32,11 +32,13 @@ class EmojiPickerScreenViewModel: EmojiPickerScreenViewModelType, EmojiPickerScr
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: EmojiPickerScreenViewAction) async {
|
||||
override func process(viewAction: EmojiPickerScreenViewAction) {
|
||||
switch viewAction {
|
||||
case let .search(searchString: searchString):
|
||||
Task {
|
||||
let categories = await emojiProvider.getCategories(searchString: searchString)
|
||||
state.categories = convert(emojiCategories: categories)
|
||||
}
|
||||
case let .emojiTapped(emoji: emoji):
|
||||
callback?(.emojiSelected(emoji: emoji.value))
|
||||
case .dismiss:
|
||||
|
@ -25,7 +25,7 @@ class FilePreviewViewModel: FilePreviewViewModelType, FilePreviewViewModelProtoc
|
||||
super.init(initialViewState: FilePreviewViewState(mediaFile: mediaFile, title: title))
|
||||
}
|
||||
|
||||
override func process(viewAction: FilePreviewViewAction) async {
|
||||
override func process(viewAction: FilePreviewViewAction) {
|
||||
switch viewAction {
|
||||
case .cancel:
|
||||
callback?(.cancel)
|
||||
|
@ -133,7 +133,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: HomeScreenViewAction) async {
|
||||
override func process(viewAction: HomeScreenViewAction) {
|
||||
switch viewAction {
|
||||
case .selectRoom(let roomIdentifier):
|
||||
callback?(.presentRoom(roomIdentifier: roomIdentifier))
|
||||
|
@ -25,7 +25,7 @@ class MediaPickerPreviewScreenViewModel: MediaPickerPreviewScreenViewModelType,
|
||||
super.init(initialViewState: MediaPickerPreviewScreenViewState(url: url, title: title))
|
||||
}
|
||||
|
||||
override func process(viewAction: MediaPickerPreviewScreenViewAction) async {
|
||||
override func process(viewAction: MediaPickerPreviewScreenViewAction) {
|
||||
switch viewAction {
|
||||
case .send:
|
||||
callback?(.send)
|
||||
|
@ -26,7 +26,7 @@ class OnboardingViewModel: OnboardingViewModelType, OnboardingViewModelProtocol
|
||||
super.init(initialViewState: OnboardingViewState())
|
||||
}
|
||||
|
||||
override func process(viewAction: OnboardingViewAction) async {
|
||||
override func process(viewAction: OnboardingViewAction) {
|
||||
switch viewAction {
|
||||
case .login:
|
||||
callback?(.login)
|
||||
|
@ -14,6 +14,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
struct ReportContentCoordinatorParameters {
|
||||
@ -31,6 +32,7 @@ enum ReportContentCoordinatorAction {
|
||||
final class ReportContentCoordinator: CoordinatorProtocol {
|
||||
private let parameters: ReportContentCoordinatorParameters
|
||||
private var viewModel: ReportContentViewModelProtocol
|
||||
private var cancellables: Set<AnyCancellable> = .init()
|
||||
|
||||
var callback: ((ReportContentCoordinatorAction) -> Void)?
|
||||
|
||||
@ -43,7 +45,8 @@ final class ReportContentCoordinator: CoordinatorProtocol {
|
||||
// MARK: - Public
|
||||
|
||||
func start() {
|
||||
viewModel.callback = { [weak self] action in
|
||||
viewModel.actions
|
||||
.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .submitStarted:
|
||||
@ -58,6 +61,7 @@ final class ReportContentCoordinator: CoordinatorProtocol {
|
||||
self.callback?(.cancel)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
func stop() {
|
||||
|
@ -14,16 +14,20 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
typealias ReportContentViewModelType = StateStoreViewModel<ReportContentViewState, ReportContentViewAction>
|
||||
|
||||
class ReportContentViewModel: ReportContentViewModelType, ReportContentViewModelProtocol {
|
||||
var callback: ((ReportContentViewModelAction) -> Void)?
|
||||
|
||||
private let itemID: String
|
||||
private let senderID: String
|
||||
private let roomProxy: RoomProxyProtocol
|
||||
private let actionsSubject: PassthroughSubject<ReportContentViewModelAction, Never> = .init()
|
||||
|
||||
var actions: AnyPublisher<ReportContentViewModelAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(itemID: String, senderID: String, roomProxy: RoomProxyProtocol) {
|
||||
self.itemID = itemID
|
||||
@ -35,34 +39,34 @@ class ReportContentViewModel: ReportContentViewModelType, ReportContentViewModel
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: ReportContentViewAction) async {
|
||||
override func process(viewAction: ReportContentViewAction) {
|
||||
switch viewAction {
|
||||
case .cancel:
|
||||
callback?(.cancel)
|
||||
actionsSubject.send(.cancel)
|
||||
case .submit:
|
||||
await submitReport()
|
||||
Task { await submitReport() }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private func submitReport() async {
|
||||
callback?(.submitStarted)
|
||||
actionsSubject.send(.submitStarted)
|
||||
|
||||
if case let .failure(error) = await roomProxy.reportContent(itemID, reason: state.bindings.reasonText) {
|
||||
MXLog.error("Submit Report Content failed: \(error)")
|
||||
callback?(.submitFailed(error: error))
|
||||
actionsSubject.send(.submitFailed(error: error))
|
||||
return
|
||||
}
|
||||
|
||||
// Ignore the sender if the user wants to.
|
||||
if state.bindings.ignoreUser, case let .failure(error) = await roomProxy.ignoreUser(senderID) {
|
||||
MXLog.error("Ignore user failed: \(error)")
|
||||
callback?(.submitFailed(error: error))
|
||||
actionsSubject.send(.submitFailed(error: error))
|
||||
return
|
||||
}
|
||||
|
||||
MXLog.info("Submit Report Content succeeded")
|
||||
callback?(.submitFinished)
|
||||
actionsSubject.send(.submitFinished)
|
||||
}
|
||||
}
|
||||
|
@ -14,10 +14,11 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import Foundation
|
||||
|
||||
@MainActor
|
||||
protocol ReportContentViewModelProtocol {
|
||||
var callback: ((ReportContentViewModelAction) -> Void)? { get set }
|
||||
var actions: AnyPublisher<ReportContentViewModelAction, Never> { get }
|
||||
var context: ReportContentViewModelType.Context { get }
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ class RoomDetailsViewModel: RoomDetailsViewModelType, RoomDetailsViewModelProtoc
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: RoomDetailsViewAction) async {
|
||||
override func process(viewAction: RoomDetailsViewAction) {
|
||||
switch viewAction {
|
||||
case .processTapPeople:
|
||||
callback?(.requestMemberDetailsPresentation(members))
|
||||
@ -81,15 +81,15 @@ class RoomDetailsViewModel: RoomDetailsViewModelType, RoomDetailsViewModelProtoc
|
||||
}
|
||||
state.bindings.leaveRoomAlertItem = LeaveRoomAlertItem(state: roomProxy.isPublic ? .public : .private)
|
||||
case .confirmLeave:
|
||||
await leaveRoom()
|
||||
Task { await leaveRoom() }
|
||||
case .processTapIgnore:
|
||||
state.bindings.ignoreUserRoomAlertItem = .init(action: .ignore)
|
||||
case .processTapUnignore:
|
||||
state.bindings.ignoreUserRoomAlertItem = .init(action: .unignore)
|
||||
case .ignoreConfirmed:
|
||||
await ignore()
|
||||
Task { await ignore() }
|
||||
case .unignoreConfirmed:
|
||||
await unignore()
|
||||
Task { await unignore() }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -32,21 +32,22 @@ class RoomMemberDetailsViewModel: RoomMemberDetailsViewModelType, RoomMemberDeta
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: RoomMemberDetailsViewAction) async {
|
||||
override func process(viewAction: RoomMemberDetailsViewAction) {
|
||||
switch viewAction {
|
||||
case .showUnignoreAlert:
|
||||
state.bindings.ignoreUserAlert = .init(action: .unignore)
|
||||
case .showIgnoreAlert:
|
||||
state.bindings.ignoreUserAlert = .init(action: .ignore)
|
||||
case .ignoreConfirmed:
|
||||
await ignoreUser()
|
||||
Task { await ignoreUser() }
|
||||
case .unignoreConfirmed:
|
||||
await unignoreUser()
|
||||
Task { await unignoreUser() }
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
@MainActor
|
||||
private func ignoreUser() async {
|
||||
state.isProcessingIgnoreRequest = true
|
||||
let result = await roomMemberProxy.ignoreUser()
|
||||
@ -59,6 +60,7 @@ class RoomMemberDetailsViewModel: RoomMemberDetailsViewModelType, RoomMemberDeta
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func unignoreUser() async {
|
||||
state.isProcessingIgnoreRequest = true
|
||||
let result = await roomMemberProxy.unignoreUser()
|
||||
|
@ -35,7 +35,7 @@ class RoomMembersListViewModel: RoomMembersListViewModelType, RoomMembersListVie
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: RoomMembersListViewAction) async {
|
||||
override func process(viewAction: RoomMembersListViewAction) {
|
||||
switch viewAction {
|
||||
case .selectMember(let id):
|
||||
guard let member = members.first(where: { $0.userID == id }) else {
|
||||
|
@ -87,33 +87,33 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
||||
var callback: ((RoomScreenViewModelAction) -> Void)?
|
||||
|
||||
// swiftlint:disable:next cyclomatic_complexity
|
||||
override func process(viewAction: RoomScreenViewAction) async {
|
||||
override func process(viewAction: RoomScreenViewAction) {
|
||||
switch viewAction {
|
||||
case .displayRoomDetails:
|
||||
callback?(.displayRoomDetails)
|
||||
case .paginateBackwards:
|
||||
await paginateBackwards()
|
||||
Task { await paginateBackwards() }
|
||||
case .itemAppeared(let id):
|
||||
await timelineController.processItemAppearance(id)
|
||||
Task { await timelineController.processItemAppearance(id) }
|
||||
case .itemDisappeared(let id):
|
||||
await timelineController.processItemDisappearance(id)
|
||||
Task { await timelineController.processItemDisappearance(id) }
|
||||
case .itemTapped(let id):
|
||||
await itemTapped(with: id)
|
||||
Task { await itemTapped(with: id) }
|
||||
case .itemDoubleTapped(let id):
|
||||
itemDoubleTapped(with: id)
|
||||
case .linkClicked(let url):
|
||||
MXLog.warning("Link clicked: \(url)")
|
||||
case .sendMessage:
|
||||
await sendCurrentMessage()
|
||||
Task { await sendCurrentMessage() }
|
||||
case .sendReaction(let emoji, let itemId):
|
||||
await timelineController.sendReaction(emoji, to: itemId)
|
||||
Task { await timelineController.sendReaction(emoji, to: itemId) }
|
||||
case .cancelReply:
|
||||
state.composerMode = .default
|
||||
case .cancelEdit:
|
||||
state.composerMode = .default
|
||||
state.bindings.composerText = ""
|
||||
case .markRoomAsRead:
|
||||
await markRoomAsRead()
|
||||
Task { await markRoomAsRead() }
|
||||
case .contextMenuAction(let itemID, let action):
|
||||
processContentMenuAction(action, itemID: itemID)
|
||||
case .displayCameraPicker:
|
||||
|
@ -63,7 +63,7 @@ class SessionVerificationViewModel: SessionVerificationViewModelType, SessionVer
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
override func process(viewAction: SessionVerificationViewAction) async {
|
||||
override func process(viewAction: SessionVerificationViewAction) {
|
||||
switch viewAction {
|
||||
case .requestVerification:
|
||||
stateMachine.processEvent(.requestVerification)
|
||||
|
@ -73,7 +73,7 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
override func process(viewAction: SettingsScreenViewAction) async {
|
||||
override func process(viewAction: SettingsScreenViewAction) {
|
||||
switch viewAction {
|
||||
case .close:
|
||||
callback?(.close)
|
||||
|
@ -36,7 +36,7 @@ class StartChatViewModel: StartChatViewModelType, StartChatViewModelProtocol {
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: StartChatViewAction) async {
|
||||
override func process(viewAction: StartChatViewAction) {
|
||||
switch viewAction {
|
||||
case .close:
|
||||
callback?(.close)
|
||||
|
@ -322,7 +322,7 @@ class MockScreen: Identifiable {
|
||||
let coordinator = StartChatCoordinator(parameters: .init(userSession: MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider())))
|
||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||
return navigationStackCoordinator
|
||||
case .startChatSearchingNonExistingID:
|
||||
case .startChatSearchingNonexistentID:
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
let clientProxy = MockClientProxy(userID: "@mock:client.com")
|
||||
clientProxy.searchUsersResult = .success(.init(results: [.mockAlice], limited: true))
|
||||
|
@ -50,7 +50,7 @@ enum UITestsScreenIdentifier: String {
|
||||
case reportContent
|
||||
case startChat
|
||||
case startChatWithSearchResults
|
||||
case startChatSearchingNonExistingID
|
||||
case startChatSearchingNonexistentID
|
||||
}
|
||||
|
||||
extension UITestsScreenIdentifier: CustomStringConvertible {
|
||||
|
@ -9,6 +9,25 @@
|
||||
}
|
||||
],
|
||||
"defaultOptions" : {
|
||||
"environmentVariableEntries" : [
|
||||
{
|
||||
"key" : "INTEGRATION_TESTS_HOST",
|
||||
"value" : "${INTEGRATION_TESTS_HOST}"
|
||||
},
|
||||
{
|
||||
"key" : "INTEGRATION_TESTS_PASSWORD",
|
||||
"value" : "${INTEGRATION_TESTS_PASSWORD}"
|
||||
},
|
||||
{
|
||||
"key" : "INTEGRATION_TESTS_USERNAME",
|
||||
"value" : "${INTEGRATION_TESTS_USERNAME}"
|
||||
}
|
||||
],
|
||||
"targetForVariableExpansion" : {
|
||||
"containerPath" : "container:ElementX.xcodeproj",
|
||||
"identifier" : "D3DB351B7FBE0F49649171FC",
|
||||
"name" : "IntegrationTests"
|
||||
},
|
||||
"testTimeoutsEnabled" : true
|
||||
},
|
||||
"testTargets" : [
|
||||
|
@ -14,10 +14,6 @@ schemes:
|
||||
run:
|
||||
config: Debug
|
||||
disableMainThreadChecker: false
|
||||
environmentVariables:
|
||||
INTEGRATION_TESTS_HOST: ${INTEGRATION_TESTS_HOST}
|
||||
INTEGRATION_TESTS_USERNAME: ${INTEGRATION_TESTS_USERNAME}
|
||||
INTEGRATION_TESTS_PASSWORD: ${INTEGRATION_TESTS_PASSWORD}
|
||||
test:
|
||||
config: Debug
|
||||
disableMainThreadChecker: false
|
||||
|
@ -14,6 +14,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
struct TemplateCoordinatorParameters {
|
||||
@ -30,8 +31,12 @@ enum TemplateCoordinatorAction {
|
||||
final class TemplateCoordinator: CoordinatorProtocol {
|
||||
private let parameters: TemplateCoordinatorParameters
|
||||
private var viewModel: TemplateViewModelProtocol
|
||||
private let actionsSubject: PassthroughSubject<TemplateCoordinatorAction, Never> = .init()
|
||||
private var cancellables: Set<AnyCancellable> = .init()
|
||||
|
||||
var callback: ((TemplateCoordinatorAction) -> Void)?
|
||||
var actions: AnyPublisher<TemplateCoordinatorAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(parameters: TemplateCoordinatorParameters) {
|
||||
self.parameters = parameters
|
||||
@ -40,16 +45,17 @@ final class TemplateCoordinator: CoordinatorProtocol {
|
||||
}
|
||||
|
||||
func start() {
|
||||
viewModel.callback = { [weak self] action in
|
||||
viewModel.actions.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .accept:
|
||||
MXLog.info("User accepted the prompt.")
|
||||
self.callback?(.accept)
|
||||
self.actionsSubject.send(.accept)
|
||||
case .cancel:
|
||||
self.callback?(.cancel)
|
||||
self.actionsSubject.send(.cancel)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
func toPresentable() -> AnyView {
|
||||
|
@ -14,12 +14,17 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
typealias TemplateViewModelType = StateStoreViewModel<TemplateViewState, TemplateViewAction>
|
||||
|
||||
class TemplateViewModel: TemplateViewModelType, TemplateViewModelProtocol {
|
||||
var callback: ((TemplateViewModelAction) -> Void)?
|
||||
private var actionsSubject: PassthroughSubject<TemplateViewModelAction, Never> = .init()
|
||||
|
||||
var actions: AnyPublisher<TemplateViewModelAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(promptType: TemplatePromptType, initialCount: Int = 0) {
|
||||
super.init(initialViewState: TemplateViewState(promptType: promptType, count: 0))
|
||||
@ -27,12 +32,12 @@ class TemplateViewModel: TemplateViewModelType, TemplateViewModelProtocol {
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: TemplateViewAction) async {
|
||||
override func process(viewAction: TemplateViewAction) {
|
||||
switch viewAction {
|
||||
case .accept:
|
||||
callback?(.accept)
|
||||
actionsSubject.send(.accept)
|
||||
case .cancel:
|
||||
callback?(.cancel)
|
||||
actionsSubject.send(.cancel)
|
||||
case .incrementCount:
|
||||
state.count += 1
|
||||
case .decrementCount:
|
||||
|
@ -14,10 +14,10 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol TemplateViewModelProtocol {
|
||||
var callback: ((TemplateViewModelAction) -> Void)? { get set }
|
||||
var actions: AnyPublisher<TemplateViewModelAction, Never> { get }
|
||||
var context: TemplateViewModelType.Context { get }
|
||||
}
|
||||
|
@ -38,15 +38,12 @@ class TemplateScreenViewModelTests: XCTestCase {
|
||||
|
||||
func testCounter() async throws {
|
||||
context.send(viewAction: .incrementCount)
|
||||
await Task.yield()
|
||||
XCTAssertEqual(context.viewState.count, 1)
|
||||
|
||||
context.send(viewAction: .incrementCount)
|
||||
await Task.yield()
|
||||
XCTAssertEqual(context.viewState.count, 2)
|
||||
|
||||
context.send(viewAction: .decrementCount)
|
||||
await Task.yield()
|
||||
XCTAssertEqual(context.viewState.count, 1)
|
||||
}
|
||||
}
|
||||
|
@ -25,21 +25,6 @@ class BugReportUITests: XCTestCase {
|
||||
app.assertScreenshot(.bugReport, step: 0)
|
||||
}
|
||||
|
||||
func testToggleSendingLogs() {
|
||||
let app = Application.launch(.bugReport)
|
||||
|
||||
// Don't know why, but there's an issue on CI where the toggle is tapped but doesn't respond. Waiting for
|
||||
// it fixes this (even it it already exists). Reproducible by running the test after quitting the simulator.
|
||||
let sendingLogsToggle = app.switches[A11yIdentifiers.bugReportScreen.sendLogs]
|
||||
XCTAssertTrue(sendingLogsToggle.waitForExistence(timeout: 1))
|
||||
XCTAssertTrue(sendingLogsToggle.isOn)
|
||||
|
||||
sendingLogsToggle.tap()
|
||||
|
||||
XCTAssertFalse(sendingLogsToggle.isOn)
|
||||
app.assertScreenshot(.bugReport, step: 1)
|
||||
}
|
||||
|
||||
func testReportText() {
|
||||
let app = Application.launch(.bugReport)
|
||||
|
||||
|
@ -22,19 +22,4 @@ class ReportContentScreenUITests: XCTestCase {
|
||||
let app = Application.launch(.reportContent)
|
||||
app.assertScreenshot(.reportContent, step: 0)
|
||||
}
|
||||
|
||||
func testToggleIgnoreUser() {
|
||||
let app = Application.launch(.reportContent)
|
||||
|
||||
// Don't know why, but there's an issue on CI where the toggle is tapped but doesn't respond. Waiting for
|
||||
// it fixes this (even it it already exists). Reproducible by running the test after quitting the simulator.
|
||||
let sendingLogsToggle = app.switches[A11yIdentifiers.reportContent.ignoreUser]
|
||||
XCTAssertTrue(sendingLogsToggle.waitForExistence(timeout: 1))
|
||||
XCTAssertFalse(sendingLogsToggle.isOn)
|
||||
|
||||
sendingLogsToggle.tap()
|
||||
|
||||
XCTAssertTrue(sendingLogsToggle.isOn)
|
||||
app.assertScreenshot(.reportContent, step: 1)
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ class StartChatScreenUITests: XCTestCase {
|
||||
}
|
||||
|
||||
func testSearchExactNotExistingMatrixID() {
|
||||
let app = Application.launch(.startChatSearchingInexistingID)
|
||||
let app = Application.launch(.startChatSearchingNonexistentID)
|
||||
let searchField = app.searchFields.firstMatch
|
||||
searchField.clearAndTypeText("@a:b.com")
|
||||
XCTAssertFalse(app.staticTexts[A11yIdentifiers.startChatScreen.searchNoResults].waitForExistence(timeout: 1.0))
|
||||
|
BIN
UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.bugReport-1.png
(Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.bugReport-1.png
(Stored with Git LFS)
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.reportContent-1.png
(Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/en-GB-iPad-9th-generation.reportContent-1.png
(Stored with Git LFS)
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.bugReport-1.png
(Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.bugReport-1.png
(Stored with Git LFS)
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.reportContent-1.png
(Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/en-GB-iPhone-14.reportContent-1.png
(Stored with Git LFS)
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.bugReport-1.png
(Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.bugReport-1.png
(Stored with Git LFS)
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.reportContent-1.png
(Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/pseudo-iPad-9th-generation.reportContent-1.png
(Stored with Git LFS)
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.bugReport-1.png
(Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.bugReport-1.png
(Stored with Git LFS)
Binary file not shown.
BIN
UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.reportContent-1.png
(Stored with Git LFS)
BIN
UITests/Sources/__Snapshots__/Application/pseudo-iPhone-14.reportContent-1.png
(Stored with Git LFS)
Binary file not shown.
@ -46,7 +46,6 @@ class BugReportViewModelTests: XCTestCase {
|
||||
let context = viewModel.context
|
||||
|
||||
context.send(viewAction: .removeScreenshot)
|
||||
try await Task.sleep(nanoseconds: 100_000_000)
|
||||
XCTAssertNil(context.viewState.screenshot)
|
||||
}
|
||||
|
||||
@ -58,7 +57,6 @@ class BugReportViewModelTests: XCTestCase {
|
||||
let context = viewModel.context
|
||||
XCTAssertNil(context.viewState.screenshot)
|
||||
context.send(viewAction: .attachScreenshot(UIImage.actions))
|
||||
try await Task.sleep(nanoseconds: 100_000_000)
|
||||
XCTAssert(context.viewState.screenshot == UIImage.actions)
|
||||
}
|
||||
|
||||
@ -70,19 +68,20 @@ class BugReportViewModelTests: XCTestCase {
|
||||
deviceID: nil,
|
||||
screenshot: nil, isModallyPresented: false)
|
||||
let context = viewModel.context
|
||||
var isSuccess = false
|
||||
viewModel.callback = { result in
|
||||
switch result {
|
||||
case .submitFinished:
|
||||
isSuccess = true
|
||||
default: break
|
||||
}
|
||||
}
|
||||
context.send(viewAction: .submit)
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
|
||||
_ = await viewModel
|
||||
.actions
|
||||
.values
|
||||
.first {
|
||||
guard case .submitFinished = $0 else {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
XCTAssert(mockService.submitBugReportProgressListenerCallsCount == 1)
|
||||
XCTAssert(mockService.submitBugReportProgressListenerReceivedArguments?.bugReport == BugReport(userID: "@mock.client.com", deviceID: nil, text: "", includeLogs: true, includeCrashLog: true, githubLabels: [], files: []))
|
||||
XCTAssertTrue(isSuccess)
|
||||
}
|
||||
|
||||
func testSendReportWithError() async throws {
|
||||
@ -95,20 +94,19 @@ class BugReportViewModelTests: XCTestCase {
|
||||
deviceID: nil,
|
||||
screenshot: nil, isModallyPresented: false)
|
||||
let context = viewModel.context
|
||||
var isFailure = false
|
||||
|
||||
viewModel.callback = { result in
|
||||
switch result {
|
||||
case .submitFailed:
|
||||
isFailure = true
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
||||
context.send(viewAction: .submit)
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
|
||||
_ = await viewModel
|
||||
.actions
|
||||
.values
|
||||
.first {
|
||||
guard case .submitFailed = $0 else {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
XCTAssert(mockService.submitBugReportProgressListenerCallsCount == 1)
|
||||
XCTAssert(mockService.submitBugReportProgressListenerReceivedArguments?.bugReport == BugReport(userID: "@mock.client.com", deviceID: nil, text: "", includeLogs: true, includeCrashLog: true, githubLabels: [], files: []))
|
||||
XCTAssertTrue(isFailure)
|
||||
}
|
||||
}
|
||||
|
21
UnitTests/Sources/Extensions/AsyncSequence.swift
Normal file
21
UnitTests/Sources/Extensions/AsyncSequence.swift
Normal file
@ -0,0 +1,21 @@
|
||||
//
|
||||
// Copyright 2023 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
extension AsyncSequence {
|
||||
func first() async rethrows -> Self.Element? {
|
||||
try await first { _ in true }
|
||||
}
|
||||
}
|
31
UnitTests/Sources/Extensions/Publisher.swift
Normal file
31
UnitTests/Sources/Extensions/Publisher.swift
Normal file
@ -0,0 +1,31 @@
|
||||
//
|
||||
// Copyright 2023 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Combine
|
||||
|
||||
extension Published.Publisher {
|
||||
/// Returns the next output from the publisher skipping the current value stored into it (which is readable from the @Published property itself).
|
||||
/// - Returns: the next output from the publisher
|
||||
var nextValue: Output? {
|
||||
get async {
|
||||
var iterator = values.makeAsyncIterator()
|
||||
|
||||
// skips the publisher's current value
|
||||
_ = await iterator.next()
|
||||
return await iterator.next()
|
||||
}
|
||||
}
|
||||
}
|
24
UnitTests/Sources/Extensions/ViewModelContext.swift
Normal file
24
UnitTests/Sources/Extensions/ViewModelContext.swift
Normal file
@ -0,0 +1,24 @@
|
||||
//
|
||||
// Copyright 2023 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
@testable import ElementX
|
||||
|
||||
extension ViewModelContext {
|
||||
@discardableResult
|
||||
func nextViewState() async -> ViewState? {
|
||||
await $viewState.nextValue
|
||||
}
|
||||
}
|
@ -23,12 +23,12 @@ class FilePreviewScreenViewModelTests: XCTestCase {
|
||||
var viewModel: FilePreviewViewModelProtocol!
|
||||
var context: FilePreviewViewModelType.Context!
|
||||
|
||||
@MainActor override func setUpWithError() throws {
|
||||
override func setUpWithError() throws {
|
||||
viewModel = FilePreviewViewModel(mediaFile: .unmanaged(url: URL(staticString: "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf")))
|
||||
context = viewModel.context
|
||||
}
|
||||
|
||||
@MainActor func testCancel() async throws {
|
||||
func testCancel() async throws {
|
||||
var correctResult = false
|
||||
viewModel.callback = { result in
|
||||
switch result {
|
||||
|
@ -18,18 +18,19 @@ import XCTest
|
||||
|
||||
@testable import ElementX
|
||||
|
||||
@MainActor
|
||||
class HomeScreenViewModelTests: XCTestCase {
|
||||
var viewModel: HomeScreenViewModelProtocol!
|
||||
var context: HomeScreenViewModelType.Context!
|
||||
|
||||
@MainActor override func setUpWithError() throws {
|
||||
override func setUpWithError() throws {
|
||||
viewModel = HomeScreenViewModel(userSession: MockUserSession(clientProxy: MockClientProxy(userID: "@mock:client.com"),
|
||||
mediaProvider: MockMediaProvider()),
|
||||
attributedStringBuilder: AttributedStringBuilder())
|
||||
context = viewModel.context
|
||||
}
|
||||
|
||||
@MainActor func testSelectRoom() async throws {
|
||||
func testSelectRoom() async throws {
|
||||
let mockRoomId = "mock_room_id"
|
||||
var correctResult = false
|
||||
var selectedRoomId = ""
|
||||
@ -49,7 +50,7 @@ class HomeScreenViewModelTests: XCTestCase {
|
||||
XCTAssertEqual(mockRoomId, selectedRoomId)
|
||||
}
|
||||
|
||||
@MainActor func testTapUserAvatar() async throws {
|
||||
func testTapUserAvatar() async throws {
|
||||
var correctResult = false
|
||||
viewModel.callback = { result in
|
||||
switch result {
|
||||
|
@ -36,7 +36,8 @@ class ReportContentScreenViewModelTests: XCTestCase {
|
||||
viewModel.state.bindings.reasonText = reportReason
|
||||
viewModel.state.bindings.ignoreUser = false
|
||||
viewModel.context.send(viewAction: .submit)
|
||||
await Task.yield()
|
||||
|
||||
_ = await viewModel.actions.values.first()
|
||||
|
||||
// Then the content should be reported, but the user should not be included.
|
||||
XCTAssertEqual(roomProxy.reportContentReasonCallsCount, 1, "The content should always be reported.")
|
||||
@ -59,7 +60,8 @@ class ReportContentScreenViewModelTests: XCTestCase {
|
||||
viewModel.state.bindings.reasonText = reportReason
|
||||
viewModel.state.bindings.ignoreUser = true
|
||||
viewModel.context.send(viewAction: .submit)
|
||||
await Task.yield()
|
||||
|
||||
_ = await viewModel.actions.values.first()
|
||||
|
||||
// Then the content should be reported, and the user should be ignored.
|
||||
XCTAssertEqual(roomProxy.reportContentReasonCallsCount, 1, "The content should always be reported.")
|
||||
|
@ -34,24 +34,21 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isPublic: true, members: mockedMembers))
|
||||
viewModel = RoomDetailsViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
context.send(viewAction: .processTapLeave)
|
||||
await Task.yield()
|
||||
XCTAssertEqual(context.leaveRoomAlertItem?.state, .public)
|
||||
XCTAssertEqual(context.leaveRoomAlertItem?.subtitle, L10n.leaveRoomAlertSubtitle)
|
||||
}
|
||||
|
||||
func testLeavRoomTappedWhenRoomNotPublic() async {
|
||||
func testLeaveRoomTappedWhenRoomNotPublic() async {
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockBob, .mockAlice]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isPublic: false, members: mockedMembers))
|
||||
viewModel = RoomDetailsViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
context.send(viewAction: .processTapLeave)
|
||||
await Task.yield()
|
||||
XCTAssertEqual(context.leaveRoomAlertItem?.state, .private)
|
||||
XCTAssertEqual(context.leaveRoomAlertItem?.subtitle, L10n.leaveRoomAlertPrivateSubtitle)
|
||||
}
|
||||
|
||||
func testLeaveRoomTappedWithLessThanTwoMembers() async {
|
||||
context.send(viewAction: .processTapLeave)
|
||||
await Task.yield()
|
||||
XCTAssertEqual(context.leaveRoomAlertItem?.state, .empty)
|
||||
XCTAssertEqual(context.leaveRoomAlertItem?.subtitle, L10n.leaveRoomAlertEmptySubtitle)
|
||||
}
|
||||
@ -78,7 +75,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
.failure(.failedLeavingRoom)
|
||||
}
|
||||
context.send(viewAction: .confirmLeave)
|
||||
await Task.yield()
|
||||
await context.nextViewState()
|
||||
XCTAssertEqual(roomProxyMock.leaveRoomCallsCount, 1)
|
||||
XCTAssertNotNil(context.alertInfo)
|
||||
}
|
||||
@ -103,10 +100,10 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
||||
|
||||
context.send(viewAction: .ignoreConfirmed)
|
||||
await Task.yield()
|
||||
await context.nextViewState()
|
||||
|
||||
XCTAssertTrue(context.viewState.isProcessingIgnoreRequest)
|
||||
try await Task.sleep(for: .milliseconds(10))
|
||||
await context.nextViewState()
|
||||
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
||||
XCTAssert(context.viewState.dmRecipient?.isIgnored == true)
|
||||
}
|
||||
@ -123,10 +120,10 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
||||
|
||||
context.send(viewAction: .ignoreConfirmed)
|
||||
await Task.yield()
|
||||
await context.nextViewState()
|
||||
|
||||
XCTAssertTrue(context.viewState.isProcessingIgnoreRequest)
|
||||
try await Task.sleep(for: .milliseconds(10))
|
||||
await context.nextViewState()
|
||||
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
||||
XCTAssert(context.viewState.dmRecipient?.isIgnored == false)
|
||||
XCTAssertNotNil(context.alertInfo)
|
||||
@ -144,10 +141,10 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
||||
|
||||
context.send(viewAction: .unignoreConfirmed)
|
||||
await Task.yield()
|
||||
await context.nextViewState()
|
||||
|
||||
XCTAssertTrue(context.viewState.isProcessingIgnoreRequest)
|
||||
try await Task.sleep(for: .milliseconds(10))
|
||||
await context.nextViewState()
|
||||
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
||||
XCTAssert(context.viewState.dmRecipient?.isIgnored == false)
|
||||
}
|
||||
@ -164,10 +161,10 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
||||
|
||||
context.send(viewAction: .unignoreConfirmed)
|
||||
await Task.yield()
|
||||
await context.nextViewState()
|
||||
XCTAssertTrue(context.viewState.isProcessingIgnoreRequest)
|
||||
|
||||
try await Task.sleep(for: .milliseconds(10))
|
||||
await context.nextViewState()
|
||||
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
||||
XCTAssert(context.viewState.dmRecipient?.isIgnored == true)
|
||||
XCTAssertNotNil(context.alertInfo)
|
||||
|
@ -42,14 +42,13 @@ class RoomMemberDetailsViewModelTests: XCTestCase {
|
||||
viewModel = RoomMemberDetailsViewModel(roomMemberProxy: roomMemberProxyMock, mediaProvider: MockMediaProvider())
|
||||
|
||||
context.send(viewAction: .showIgnoreAlert)
|
||||
await Task.yield()
|
||||
XCTAssertEqual(context.ignoreUserAlert, .init(action: .ignore))
|
||||
|
||||
context.send(viewAction: .ignoreConfirmed)
|
||||
await Task.yield()
|
||||
await context.nextViewState()
|
||||
XCTAssertTrue(context.viewState.isProcessingIgnoreRequest)
|
||||
XCTAssertFalse(context.viewState.details.isIgnored)
|
||||
try await Task.sleep(for: .milliseconds(10))
|
||||
await context.nextViewState()
|
||||
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
||||
XCTAssertTrue(context.viewState.details.isIgnored)
|
||||
}
|
||||
@ -61,16 +60,14 @@ class RoomMemberDetailsViewModelTests: XCTestCase {
|
||||
return .failure(.ignoreUserFailed)
|
||||
}
|
||||
viewModel = RoomMemberDetailsViewModel(roomMemberProxy: roomMemberProxyMock, mediaProvider: MockMediaProvider())
|
||||
|
||||
context.send(viewAction: .showIgnoreAlert)
|
||||
await Task.yield()
|
||||
XCTAssertEqual(context.ignoreUserAlert, .init(action: .ignore))
|
||||
|
||||
context.send(viewAction: .ignoreConfirmed)
|
||||
await Task.yield()
|
||||
await context.nextViewState()
|
||||
XCTAssertTrue(context.viewState.isProcessingIgnoreRequest)
|
||||
XCTAssertFalse(context.viewState.details.isIgnored)
|
||||
try await Task.sleep(for: .milliseconds(10))
|
||||
await context.nextViewState()
|
||||
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
||||
XCTAssertNotNil(context.errorAlert)
|
||||
XCTAssertFalse(context.viewState.details.isIgnored)
|
||||
@ -85,14 +82,13 @@ class RoomMemberDetailsViewModelTests: XCTestCase {
|
||||
viewModel = RoomMemberDetailsViewModel(roomMemberProxy: roomMemberProxyMock, mediaProvider: MockMediaProvider())
|
||||
|
||||
context.send(viewAction: .showUnignoreAlert)
|
||||
await Task.yield()
|
||||
XCTAssertEqual(context.ignoreUserAlert, .init(action: .unignore))
|
||||
|
||||
context.send(viewAction: .unignoreConfirmed)
|
||||
await Task.yield()
|
||||
await context.nextViewState()
|
||||
XCTAssertTrue(context.viewState.isProcessingIgnoreRequest)
|
||||
XCTAssertTrue(context.viewState.details.isIgnored)
|
||||
try await Task.sleep(for: .milliseconds(10))
|
||||
await context.nextViewState()
|
||||
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
||||
XCTAssertFalse(context.viewState.details.isIgnored)
|
||||
}
|
||||
@ -106,14 +102,13 @@ class RoomMemberDetailsViewModelTests: XCTestCase {
|
||||
viewModel = RoomMemberDetailsViewModel(roomMemberProxy: roomMemberProxyMock, mediaProvider: MockMediaProvider())
|
||||
|
||||
context.send(viewAction: .showUnignoreAlert)
|
||||
await Task.yield()
|
||||
XCTAssertEqual(context.ignoreUserAlert, .init(action: .unignore))
|
||||
|
||||
context.send(viewAction: .unignoreConfirmed)
|
||||
await Task.yield()
|
||||
await context.nextViewState()
|
||||
XCTAssertTrue(context.viewState.isProcessingIgnoreRequest)
|
||||
XCTAssertTrue(context.viewState.details.isIgnored)
|
||||
try await Task.sleep(for: .milliseconds(10))
|
||||
await context.nextViewState()
|
||||
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
||||
XCTAssertTrue(context.viewState.details.isIgnored)
|
||||
XCTAssertNotNil(context.errorAlert)
|
||||
|
@ -25,7 +25,6 @@ class SessionVerificationViewModelTests: XCTestCase {
|
||||
var context: SessionVerificationViewModelType.Context!
|
||||
var sessionVerificationController: SessionVerificationControllerProxyMock!
|
||||
|
||||
@MainActor
|
||||
override func setUpWithError() throws {
|
||||
sessionVerificationController = SessionVerificationControllerProxyMock.configureMock()
|
||||
viewModel = SessionVerificationViewModel(sessionVerificationControllerProxy: sessionVerificationController)
|
||||
@ -49,18 +48,14 @@ class SessionVerificationViewModelTests: XCTestCase {
|
||||
|
||||
context.send(viewAction: .close)
|
||||
|
||||
await Task.yield()
|
||||
|
||||
XCTAssertEqual(context.viewState.verificationState, .cancelling)
|
||||
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
await context.nextViewState()
|
||||
|
||||
XCTAssertEqual(context.viewState.verificationState, .cancelled)
|
||||
|
||||
context.send(viewAction: .restart)
|
||||
|
||||
await Task.yield()
|
||||
|
||||
XCTAssertEqual(context.viewState.verificationState, .initial)
|
||||
|
||||
XCTAssert(sessionVerificationController.requestVerificationCallsCount == 1)
|
||||
|
@ -50,4 +50,3 @@ targets:
|
||||
- path: ../../Tools/Scripts/Templates/SimpleScreenExample/Tests/Unit
|
||||
- path: ../Resources
|
||||
- path: ../../ElementX/Sources/Other/InfoPlistReader.swift
|
||||
- path: ../../ElementX/Sources/Other/Extensions/Publisher.swift
|
||||
|
Loading…
x
Reference in New Issue
Block a user