mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +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:
@@ -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
|
.sink { [weak self] action in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
|
|
||||||
Task { await self.process(viewAction: action) }
|
self.process(viewAction: action)
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Override to handles incoming `ViewAction`s from the `ViewModel`.
|
/// Override to handles incoming `ViewAction`s from the `ViewModel`.
|
||||||
/// - Parameter viewAction: The `ViewAction` to be processed in `ViewModel` implementation.
|
/// - Parameter viewAction: The `ViewAction` to be processed in `ViewModel` implementation.
|
||||||
func process(viewAction: ViewAction) async {
|
func process(viewAction: ViewAction) {
|
||||||
// Default implementation, -no-op
|
// Default implementation, -no-op
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class AnalyticsPromptViewModel: AnalyticsPromptViewModelType, AnalyticsPromptVie
|
|||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
|
|
||||||
override func process(viewAction: AnalyticsPromptViewAction) async {
|
override func process(viewAction: AnalyticsPromptViewAction) {
|
||||||
switch viewAction {
|
switch viewAction {
|
||||||
case .enable:
|
case .enable:
|
||||||
callback?(.enable)
|
callback?(.enable)
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class LoginViewModel: LoginViewModelType, LoginViewModelProtocol {
|
|||||||
super.init(initialViewState: viewState)
|
super.init(initialViewState: viewState)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func process(viewAction: LoginViewAction) async {
|
override func process(viewAction: LoginViewAction) {
|
||||||
switch viewAction {
|
switch viewAction {
|
||||||
case .selectServer:
|
case .selectServer:
|
||||||
callback?(.selectServer)
|
callback?(.selectServer)
|
||||||
|
|||||||
+1
-1
@@ -28,7 +28,7 @@ class ServerSelectionViewModel: ServerSelectionViewModelType, ServerSelectionVie
|
|||||||
isModallyPresented: isModallyPresented))
|
isModallyPresented: isModallyPresented))
|
||||||
}
|
}
|
||||||
|
|
||||||
override func process(viewAction: ServerSelectionViewAction) async {
|
override func process(viewAction: ServerSelectionViewAction) {
|
||||||
switch viewAction {
|
switch viewAction {
|
||||||
case .confirm:
|
case .confirm:
|
||||||
callback?(.confirm(homeserverAddress: state.bindings.homeserverAddress))
|
callback?(.confirm(homeserverAddress: state.bindings.homeserverAddress))
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class SoftLogoutViewModel: SoftLogoutViewModelType, SoftLogoutViewModelProtocol
|
|||||||
super.init(initialViewState: viewState)
|
super.init(initialViewState: viewState)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func process(viewAction: SoftLogoutViewAction) async {
|
override func process(viewAction: SoftLogoutViewAction) {
|
||||||
switch viewAction {
|
switch viewAction {
|
||||||
case .login:
|
case .login:
|
||||||
callback?(.login(state.bindings.password))
|
callback?(.login(state.bindings.password))
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
enum BugReportCoordinatorResult {
|
enum BugReportCoordinatorResult {
|
||||||
@@ -34,6 +35,7 @@ struct BugReportCoordinatorParameters {
|
|||||||
final class BugReportCoordinator: CoordinatorProtocol {
|
final class BugReportCoordinator: CoordinatorProtocol {
|
||||||
private let parameters: BugReportCoordinatorParameters
|
private let parameters: BugReportCoordinatorParameters
|
||||||
private var viewModel: BugReportViewModelProtocol
|
private var viewModel: BugReportViewModelProtocol
|
||||||
|
private var cancellables: Set<AnyCancellable> = .init()
|
||||||
|
|
||||||
var completion: ((BugReportCoordinatorResult) -> Void)?
|
var completion: ((BugReportCoordinatorResult) -> Void)?
|
||||||
|
|
||||||
@@ -50,22 +52,25 @@ final class BugReportCoordinator: CoordinatorProtocol {
|
|||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
|
|
||||||
func start() {
|
func start() {
|
||||||
viewModel.callback = { [weak self] result in
|
viewModel
|
||||||
guard let self else { return }
|
.actions
|
||||||
MXLog.info("BugReportViewModel did complete with result: \(result).")
|
.sink { [weak self] result in
|
||||||
switch result {
|
guard let self else { return }
|
||||||
case .cancel:
|
MXLog.info("BugReportViewModel did complete with result: \(result).")
|
||||||
self.completion?(.cancel)
|
switch result {
|
||||||
case let .submitStarted(progressTracker):
|
case .cancel:
|
||||||
self.startLoading(label: L10n.commonSending, progressPublisher: progressTracker)
|
self.completion?(.cancel)
|
||||||
case .submitFinished:
|
case let .submitStarted(progressTracker):
|
||||||
self.stopLoading()
|
self.startLoading(label: L10n.commonSending, progressPublisher: progressTracker)
|
||||||
self.completion?(.finish)
|
case .submitFinished:
|
||||||
case .submitFailed(let error):
|
self.stopLoading()
|
||||||
self.stopLoading()
|
self.completion?(.finish)
|
||||||
self.showError(label: error.localizedDescription)
|
case .submitFailed(let error):
|
||||||
|
self.stopLoading()
|
||||||
|
self.showError(label: error.localizedDescription)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
func stop() {
|
func stop() {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
typealias BugReportViewModelType = StateStoreViewModel<BugReportViewState, BugReportViewAction>
|
typealias BugReportViewModelType = StateStoreViewModel<BugReportViewState, BugReportViewAction>
|
||||||
@@ -22,8 +23,11 @@ class BugReportViewModel: BugReportViewModelType, BugReportViewModelProtocol {
|
|||||||
private let bugReportService: BugReportServiceProtocol
|
private let bugReportService: BugReportServiceProtocol
|
||||||
private let userID: String
|
private let userID: String
|
||||||
private let deviceID: 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,
|
init(bugReportService: BugReportServiceProtocol,
|
||||||
userID: String,
|
userID: String,
|
||||||
@@ -42,12 +46,12 @@ class BugReportViewModel: BugReportViewModelType, BugReportViewModelProtocol {
|
|||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
|
|
||||||
override func process(viewAction: BugReportViewAction) async {
|
override func process(viewAction: BugReportViewAction) {
|
||||||
switch viewAction {
|
switch viewAction {
|
||||||
case .cancel:
|
case .cancel:
|
||||||
callback?(.cancel)
|
actionsSubject.send(.cancel)
|
||||||
case .submit:
|
case .submit:
|
||||||
await submitBugReport()
|
Task { await submitBugReport() }
|
||||||
case .removeScreenshot:
|
case .removeScreenshot:
|
||||||
state.screenshot = nil
|
state.screenshot = nil
|
||||||
case let .attachScreenshot(image):
|
case let .attachScreenshot(image):
|
||||||
@@ -59,7 +63,7 @@ class BugReportViewModel: BugReportViewModelType, BugReportViewModelProtocol {
|
|||||||
|
|
||||||
private func submitBugReport() async {
|
private func submitBugReport() async {
|
||||||
let progressTracker = ProgressTracker()
|
let progressTracker = ProgressTracker()
|
||||||
callback?(.submitStarted(progressTracker: progressTracker))
|
actionsSubject.send(.submitStarted(progressTracker: progressTracker))
|
||||||
do {
|
do {
|
||||||
var files: [URL] = []
|
var files: [URL] = []
|
||||||
if let screenshot = context.viewState.screenshot {
|
if let screenshot = context.viewState.screenshot {
|
||||||
@@ -78,10 +82,10 @@ class BugReportViewModel: BugReportViewModelType, BugReportViewModelProtocol {
|
|||||||
let result = try await bugReportService.submitBugReport(bugReport,
|
let result = try await bugReportService.submitBugReport(bugReport,
|
||||||
progressListener: progressTracker)
|
progressListener: progressTracker)
|
||||||
MXLog.info("SubmitBugReport succeeded, result: \(result.reportUrl)")
|
MXLog.info("SubmitBugReport succeeded, result: \(result.reportUrl)")
|
||||||
callback?(.submitFinished)
|
actionsSubject.send(.submitFinished)
|
||||||
} catch {
|
} catch {
|
||||||
MXLog.error("SubmitBugReport failed: \(error)")
|
MXLog.error("SubmitBugReport failed: \(error)")
|
||||||
callback?(.submitFailed(error: error))
|
actionsSubject.send(.submitFailed(error: error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,10 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Combine
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
protocol BugReportViewModelProtocol {
|
protocol BugReportViewModelProtocol {
|
||||||
var callback: ((BugReportViewModelAction) -> Void)? { get set }
|
var actions: AnyPublisher<BugReportViewModelAction, Never> { get }
|
||||||
var context: BugReportViewModelType.Context { get }
|
var context: BugReportViewModelType.Context { get }
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -34,7 +34,7 @@ class DeveloperOptionsScreenViewModel: DeveloperOptionsScreenViewModelType, Deve
|
|||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func process(viewAction: DeveloperOptionsScreenViewAction) async {
|
override func process(viewAction: DeveloperOptionsScreenViewAction) {
|
||||||
switch viewAction {
|
switch viewAction {
|
||||||
case .changedShouldCollapseRoomStateEvents:
|
case .changedShouldCollapseRoomStateEvents:
|
||||||
ServiceLocator.shared.settings.shouldCollapseRoomStateEvents = state.bindings.shouldCollapseRoomStateEvents
|
ServiceLocator.shared.settings.shouldCollapseRoomStateEvents = state.bindings.shouldCollapseRoomStateEvents
|
||||||
|
|||||||
@@ -32,11 +32,13 @@ class EmojiPickerScreenViewModel: EmojiPickerScreenViewModelType, EmojiPickerScr
|
|||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
|
|
||||||
override func process(viewAction: EmojiPickerScreenViewAction) async {
|
override func process(viewAction: EmojiPickerScreenViewAction) {
|
||||||
switch viewAction {
|
switch viewAction {
|
||||||
case let .search(searchString: searchString):
|
case let .search(searchString: searchString):
|
||||||
let categories = await emojiProvider.getCategories(searchString: searchString)
|
Task {
|
||||||
state.categories = convert(emojiCategories: categories)
|
let categories = await emojiProvider.getCategories(searchString: searchString)
|
||||||
|
state.categories = convert(emojiCategories: categories)
|
||||||
|
}
|
||||||
case let .emojiTapped(emoji: emoji):
|
case let .emojiTapped(emoji: emoji):
|
||||||
callback?(.emojiSelected(emoji: emoji.value))
|
callback?(.emojiSelected(emoji: emoji.value))
|
||||||
case .dismiss:
|
case .dismiss:
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ class FilePreviewViewModel: FilePreviewViewModelType, FilePreviewViewModelProtoc
|
|||||||
super.init(initialViewState: FilePreviewViewState(mediaFile: mediaFile, title: title))
|
super.init(initialViewState: FilePreviewViewState(mediaFile: mediaFile, title: title))
|
||||||
}
|
}
|
||||||
|
|
||||||
override func process(viewAction: FilePreviewViewAction) async {
|
override func process(viewAction: FilePreviewViewAction) {
|
||||||
switch viewAction {
|
switch viewAction {
|
||||||
case .cancel:
|
case .cancel:
|
||||||
callback?(.cancel)
|
callback?(.cancel)
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol
|
|||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
|
|
||||||
override func process(viewAction: HomeScreenViewAction) async {
|
override func process(viewAction: HomeScreenViewAction) {
|
||||||
switch viewAction {
|
switch viewAction {
|
||||||
case .selectRoom(let roomIdentifier):
|
case .selectRoom(let roomIdentifier):
|
||||||
callback?(.presentRoom(roomIdentifier: roomIdentifier))
|
callback?(.presentRoom(roomIdentifier: roomIdentifier))
|
||||||
|
|||||||
+1
-1
@@ -25,7 +25,7 @@ class MediaPickerPreviewScreenViewModel: MediaPickerPreviewScreenViewModelType,
|
|||||||
super.init(initialViewState: MediaPickerPreviewScreenViewState(url: url, title: title))
|
super.init(initialViewState: MediaPickerPreviewScreenViewState(url: url, title: title))
|
||||||
}
|
}
|
||||||
|
|
||||||
override func process(viewAction: MediaPickerPreviewScreenViewAction) async {
|
override func process(viewAction: MediaPickerPreviewScreenViewAction) {
|
||||||
switch viewAction {
|
switch viewAction {
|
||||||
case .send:
|
case .send:
|
||||||
callback?(.send)
|
callback?(.send)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class OnboardingViewModel: OnboardingViewModelType, OnboardingViewModelProtocol
|
|||||||
super.init(initialViewState: OnboardingViewState())
|
super.init(initialViewState: OnboardingViewState())
|
||||||
}
|
}
|
||||||
|
|
||||||
override func process(viewAction: OnboardingViewAction) async {
|
override func process(viewAction: OnboardingViewAction) {
|
||||||
switch viewAction {
|
switch viewAction {
|
||||||
case .login:
|
case .login:
|
||||||
callback?(.login)
|
callback?(.login)
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct ReportContentCoordinatorParameters {
|
struct ReportContentCoordinatorParameters {
|
||||||
@@ -31,6 +32,7 @@ enum ReportContentCoordinatorAction {
|
|||||||
final class ReportContentCoordinator: CoordinatorProtocol {
|
final class ReportContentCoordinator: CoordinatorProtocol {
|
||||||
private let parameters: ReportContentCoordinatorParameters
|
private let parameters: ReportContentCoordinatorParameters
|
||||||
private var viewModel: ReportContentViewModelProtocol
|
private var viewModel: ReportContentViewModelProtocol
|
||||||
|
private var cancellables: Set<AnyCancellable> = .init()
|
||||||
|
|
||||||
var callback: ((ReportContentCoordinatorAction) -> Void)?
|
var callback: ((ReportContentCoordinatorAction) -> Void)?
|
||||||
|
|
||||||
@@ -43,21 +45,23 @@ final class ReportContentCoordinator: CoordinatorProtocol {
|
|||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
|
|
||||||
func start() {
|
func start() {
|
||||||
viewModel.callback = { [weak self] action in
|
viewModel.actions
|
||||||
guard let self else { return }
|
.sink { [weak self] action in
|
||||||
switch action {
|
guard let self else { return }
|
||||||
case .submitStarted:
|
switch action {
|
||||||
self.startLoading()
|
case .submitStarted:
|
||||||
case let .submitFailed(error):
|
self.startLoading()
|
||||||
self.stopLoading()
|
case let .submitFailed(error):
|
||||||
self.showError(description: error.localizedDescription)
|
self.stopLoading()
|
||||||
case .submitFinished:
|
self.showError(description: error.localizedDescription)
|
||||||
self.stopLoading()
|
case .submitFinished:
|
||||||
self.callback?(.finish)
|
self.stopLoading()
|
||||||
case .cancel:
|
self.callback?(.finish)
|
||||||
self.callback?(.cancel)
|
case .cancel:
|
||||||
|
self.callback?(.cancel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
func stop() {
|
func stop() {
|
||||||
|
|||||||
@@ -14,16 +14,20 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
typealias ReportContentViewModelType = StateStoreViewModel<ReportContentViewState, ReportContentViewAction>
|
typealias ReportContentViewModelType = StateStoreViewModel<ReportContentViewState, ReportContentViewAction>
|
||||||
|
|
||||||
class ReportContentViewModel: ReportContentViewModelType, ReportContentViewModelProtocol {
|
class ReportContentViewModel: ReportContentViewModelType, ReportContentViewModelProtocol {
|
||||||
var callback: ((ReportContentViewModelAction) -> Void)?
|
|
||||||
|
|
||||||
private let itemID: String
|
private let itemID: String
|
||||||
private let senderID: String
|
private let senderID: String
|
||||||
private let roomProxy: RoomProxyProtocol
|
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) {
|
init(itemID: String, senderID: String, roomProxy: RoomProxyProtocol) {
|
||||||
self.itemID = itemID
|
self.itemID = itemID
|
||||||
@@ -35,34 +39,34 @@ class ReportContentViewModel: ReportContentViewModelType, ReportContentViewModel
|
|||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
|
|
||||||
override func process(viewAction: ReportContentViewAction) async {
|
override func process(viewAction: ReportContentViewAction) {
|
||||||
switch viewAction {
|
switch viewAction {
|
||||||
case .cancel:
|
case .cancel:
|
||||||
callback?(.cancel)
|
actionsSubject.send(.cancel)
|
||||||
case .submit:
|
case .submit:
|
||||||
await submitReport()
|
Task { await submitReport() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Private
|
// MARK: Private
|
||||||
|
|
||||||
private func submitReport() async {
|
private func submitReport() async {
|
||||||
callback?(.submitStarted)
|
actionsSubject.send(.submitStarted)
|
||||||
|
|
||||||
if case let .failure(error) = await roomProxy.reportContent(itemID, reason: state.bindings.reasonText) {
|
if case let .failure(error) = await roomProxy.reportContent(itemID, reason: state.bindings.reasonText) {
|
||||||
MXLog.error("Submit Report Content failed: \(error)")
|
MXLog.error("Submit Report Content failed: \(error)")
|
||||||
callback?(.submitFailed(error: error))
|
actionsSubject.send(.submitFailed(error: error))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ignore the sender if the user wants to.
|
// Ignore the sender if the user wants to.
|
||||||
if state.bindings.ignoreUser, case let .failure(error) = await roomProxy.ignoreUser(senderID) {
|
if state.bindings.ignoreUser, case let .failure(error) = await roomProxy.ignoreUser(senderID) {
|
||||||
MXLog.error("Ignore user failed: \(error)")
|
MXLog.error("Ignore user failed: \(error)")
|
||||||
callback?(.submitFailed(error: error))
|
actionsSubject.send(.submitFailed(error: error))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
MXLog.info("Submit Report Content succeeded")
|
MXLog.info("Submit Report Content succeeded")
|
||||||
callback?(.submitFinished)
|
actionsSubject.send(.submitFinished)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,10 +14,11 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
protocol ReportContentViewModelProtocol {
|
protocol ReportContentViewModelProtocol {
|
||||||
var callback: ((ReportContentViewModelAction) -> Void)? { get set }
|
var actions: AnyPublisher<ReportContentViewModelAction, Never> { get }
|
||||||
var context: ReportContentViewModelType.Context { get }
|
var context: ReportContentViewModelType.Context { get }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class RoomDetailsViewModel: RoomDetailsViewModelType, RoomDetailsViewModelProtoc
|
|||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
|
|
||||||
override func process(viewAction: RoomDetailsViewAction) async {
|
override func process(viewAction: RoomDetailsViewAction) {
|
||||||
switch viewAction {
|
switch viewAction {
|
||||||
case .processTapPeople:
|
case .processTapPeople:
|
||||||
callback?(.requestMemberDetailsPresentation(members))
|
callback?(.requestMemberDetailsPresentation(members))
|
||||||
@@ -81,15 +81,15 @@ class RoomDetailsViewModel: RoomDetailsViewModelType, RoomDetailsViewModelProtoc
|
|||||||
}
|
}
|
||||||
state.bindings.leaveRoomAlertItem = LeaveRoomAlertItem(state: roomProxy.isPublic ? .public : .private)
|
state.bindings.leaveRoomAlertItem = LeaveRoomAlertItem(state: roomProxy.isPublic ? .public : .private)
|
||||||
case .confirmLeave:
|
case .confirmLeave:
|
||||||
await leaveRoom()
|
Task { await leaveRoom() }
|
||||||
case .processTapIgnore:
|
case .processTapIgnore:
|
||||||
state.bindings.ignoreUserRoomAlertItem = .init(action: .ignore)
|
state.bindings.ignoreUserRoomAlertItem = .init(action: .ignore)
|
||||||
case .processTapUnignore:
|
case .processTapUnignore:
|
||||||
state.bindings.ignoreUserRoomAlertItem = .init(action: .unignore)
|
state.bindings.ignoreUserRoomAlertItem = .init(action: .unignore)
|
||||||
case .ignoreConfirmed:
|
case .ignoreConfirmed:
|
||||||
await ignore()
|
Task { await ignore() }
|
||||||
case .unignoreConfirmed:
|
case .unignoreConfirmed:
|
||||||
await unignore()
|
Task { await unignore() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,21 +32,22 @@ class RoomMemberDetailsViewModel: RoomMemberDetailsViewModelType, RoomMemberDeta
|
|||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
|
|
||||||
override func process(viewAction: RoomMemberDetailsViewAction) async {
|
override func process(viewAction: RoomMemberDetailsViewAction) {
|
||||||
switch viewAction {
|
switch viewAction {
|
||||||
case .showUnignoreAlert:
|
case .showUnignoreAlert:
|
||||||
state.bindings.ignoreUserAlert = .init(action: .unignore)
|
state.bindings.ignoreUserAlert = .init(action: .unignore)
|
||||||
case .showIgnoreAlert:
|
case .showIgnoreAlert:
|
||||||
state.bindings.ignoreUserAlert = .init(action: .ignore)
|
state.bindings.ignoreUserAlert = .init(action: .ignore)
|
||||||
case .ignoreConfirmed:
|
case .ignoreConfirmed:
|
||||||
await ignoreUser()
|
Task { await ignoreUser() }
|
||||||
case .unignoreConfirmed:
|
case .unignoreConfirmed:
|
||||||
await unignoreUser()
|
Task { await unignoreUser() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
|
@MainActor
|
||||||
private func ignoreUser() async {
|
private func ignoreUser() async {
|
||||||
state.isProcessingIgnoreRequest = true
|
state.isProcessingIgnoreRequest = true
|
||||||
let result = await roomMemberProxy.ignoreUser()
|
let result = await roomMemberProxy.ignoreUser()
|
||||||
@@ -59,6 +60,7 @@ class RoomMemberDetailsViewModel: RoomMemberDetailsViewModelType, RoomMemberDeta
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
private func unignoreUser() async {
|
private func unignoreUser() async {
|
||||||
state.isProcessingIgnoreRequest = true
|
state.isProcessingIgnoreRequest = true
|
||||||
let result = await roomMemberProxy.unignoreUser()
|
let result = await roomMemberProxy.unignoreUser()
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ class RoomMembersListViewModel: RoomMembersListViewModelType, RoomMembersListVie
|
|||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
|
|
||||||
override func process(viewAction: RoomMembersListViewAction) async {
|
override func process(viewAction: RoomMembersListViewAction) {
|
||||||
switch viewAction {
|
switch viewAction {
|
||||||
case .selectMember(let id):
|
case .selectMember(let id):
|
||||||
guard let member = members.first(where: { $0.userID == id }) else {
|
guard let member = members.first(where: { $0.userID == id }) else {
|
||||||
|
|||||||
@@ -87,33 +87,33 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
|
|||||||
var callback: ((RoomScreenViewModelAction) -> Void)?
|
var callback: ((RoomScreenViewModelAction) -> Void)?
|
||||||
|
|
||||||
// swiftlint:disable:next cyclomatic_complexity
|
// swiftlint:disable:next cyclomatic_complexity
|
||||||
override func process(viewAction: RoomScreenViewAction) async {
|
override func process(viewAction: RoomScreenViewAction) {
|
||||||
switch viewAction {
|
switch viewAction {
|
||||||
case .displayRoomDetails:
|
case .displayRoomDetails:
|
||||||
callback?(.displayRoomDetails)
|
callback?(.displayRoomDetails)
|
||||||
case .paginateBackwards:
|
case .paginateBackwards:
|
||||||
await paginateBackwards()
|
Task { await paginateBackwards() }
|
||||||
case .itemAppeared(let id):
|
case .itemAppeared(let id):
|
||||||
await timelineController.processItemAppearance(id)
|
Task { await timelineController.processItemAppearance(id) }
|
||||||
case .itemDisappeared(let id):
|
case .itemDisappeared(let id):
|
||||||
await timelineController.processItemDisappearance(id)
|
Task { await timelineController.processItemDisappearance(id) }
|
||||||
case .itemTapped(let id):
|
case .itemTapped(let id):
|
||||||
await itemTapped(with: id)
|
Task { await itemTapped(with: id) }
|
||||||
case .itemDoubleTapped(let id):
|
case .itemDoubleTapped(let id):
|
||||||
itemDoubleTapped(with: id)
|
itemDoubleTapped(with: id)
|
||||||
case .linkClicked(let url):
|
case .linkClicked(let url):
|
||||||
MXLog.warning("Link clicked: \(url)")
|
MXLog.warning("Link clicked: \(url)")
|
||||||
case .sendMessage:
|
case .sendMessage:
|
||||||
await sendCurrentMessage()
|
Task { await sendCurrentMessage() }
|
||||||
case .sendReaction(let emoji, let itemId):
|
case .sendReaction(let emoji, let itemId):
|
||||||
await timelineController.sendReaction(emoji, to: itemId)
|
Task { await timelineController.sendReaction(emoji, to: itemId) }
|
||||||
case .cancelReply:
|
case .cancelReply:
|
||||||
state.composerMode = .default
|
state.composerMode = .default
|
||||||
case .cancelEdit:
|
case .cancelEdit:
|
||||||
state.composerMode = .default
|
state.composerMode = .default
|
||||||
state.bindings.composerText = ""
|
state.bindings.composerText = ""
|
||||||
case .markRoomAsRead:
|
case .markRoomAsRead:
|
||||||
await markRoomAsRead()
|
Task { await markRoomAsRead() }
|
||||||
case .contextMenuAction(let itemID, let action):
|
case .contextMenuAction(let itemID, let action):
|
||||||
processContentMenuAction(action, itemID: itemID)
|
processContentMenuAction(action, itemID: itemID)
|
||||||
case .displayCameraPicker:
|
case .displayCameraPicker:
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ class SessionVerificationViewModel: SessionVerificationViewModelType, SessionVer
|
|||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func process(viewAction: SessionVerificationViewAction) async {
|
override func process(viewAction: SessionVerificationViewAction) {
|
||||||
switch viewAction {
|
switch viewAction {
|
||||||
case .requestVerification:
|
case .requestVerification:
|
||||||
stateMachine.processEvent(.requestVerification)
|
stateMachine.processEvent(.requestVerification)
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
|
|||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func process(viewAction: SettingsScreenViewAction) async {
|
override func process(viewAction: SettingsScreenViewAction) {
|
||||||
switch viewAction {
|
switch viewAction {
|
||||||
case .close:
|
case .close:
|
||||||
callback?(.close)
|
callback?(.close)
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ class StartChatViewModel: StartChatViewModelType, StartChatViewModelProtocol {
|
|||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
|
|
||||||
override func process(viewAction: StartChatViewAction) async {
|
override func process(viewAction: StartChatViewAction) {
|
||||||
switch viewAction {
|
switch viewAction {
|
||||||
case .close:
|
case .close:
|
||||||
callback?(.close)
|
callback?(.close)
|
||||||
|
|||||||
@@ -322,7 +322,7 @@ class MockScreen: Identifiable {
|
|||||||
let coordinator = StartChatCoordinator(parameters: .init(userSession: MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider())))
|
let coordinator = StartChatCoordinator(parameters: .init(userSession: MockUserSession(clientProxy: clientProxy, mediaProvider: MockMediaProvider())))
|
||||||
navigationStackCoordinator.setRootCoordinator(coordinator)
|
navigationStackCoordinator.setRootCoordinator(coordinator)
|
||||||
return navigationStackCoordinator
|
return navigationStackCoordinator
|
||||||
case .startChatSearchingNonExistingID:
|
case .startChatSearchingNonexistentID:
|
||||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||||
let clientProxy = MockClientProxy(userID: "@mock:client.com")
|
let clientProxy = MockClientProxy(userID: "@mock:client.com")
|
||||||
clientProxy.searchUsersResult = .success(.init(results: [.mockAlice], limited: true))
|
clientProxy.searchUsersResult = .success(.init(results: [.mockAlice], limited: true))
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ enum UITestsScreenIdentifier: String {
|
|||||||
case reportContent
|
case reportContent
|
||||||
case startChat
|
case startChat
|
||||||
case startChatWithSearchResults
|
case startChatWithSearchResults
|
||||||
case startChatSearchingNonExistingID
|
case startChatSearchingNonexistentID
|
||||||
}
|
}
|
||||||
|
|
||||||
extension UITestsScreenIdentifier: CustomStringConvertible {
|
extension UITestsScreenIdentifier: CustomStringConvertible {
|
||||||
|
|||||||
@@ -9,6 +9,25 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"defaultOptions" : {
|
"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
|
"testTimeoutsEnabled" : true
|
||||||
},
|
},
|
||||||
"testTargets" : [
|
"testTargets" : [
|
||||||
|
|||||||
@@ -14,10 +14,6 @@ schemes:
|
|||||||
run:
|
run:
|
||||||
config: Debug
|
config: Debug
|
||||||
disableMainThreadChecker: false
|
disableMainThreadChecker: false
|
||||||
environmentVariables:
|
|
||||||
INTEGRATION_TESTS_HOST: ${INTEGRATION_TESTS_HOST}
|
|
||||||
INTEGRATION_TESTS_USERNAME: ${INTEGRATION_TESTS_USERNAME}
|
|
||||||
INTEGRATION_TESTS_PASSWORD: ${INTEGRATION_TESTS_PASSWORD}
|
|
||||||
test:
|
test:
|
||||||
config: Debug
|
config: Debug
|
||||||
disableMainThreadChecker: false
|
disableMainThreadChecker: false
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct TemplateCoordinatorParameters {
|
struct TemplateCoordinatorParameters {
|
||||||
@@ -30,8 +31,12 @@ enum TemplateCoordinatorAction {
|
|||||||
final class TemplateCoordinator: CoordinatorProtocol {
|
final class TemplateCoordinator: CoordinatorProtocol {
|
||||||
private let parameters: TemplateCoordinatorParameters
|
private let parameters: TemplateCoordinatorParameters
|
||||||
private var viewModel: TemplateViewModelProtocol
|
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) {
|
init(parameters: TemplateCoordinatorParameters) {
|
||||||
self.parameters = parameters
|
self.parameters = parameters
|
||||||
@@ -40,16 +45,17 @@ final class TemplateCoordinator: CoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func start() {
|
func start() {
|
||||||
viewModel.callback = { [weak self] action in
|
viewModel.actions.sink { [weak self] action in
|
||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
switch action {
|
switch action {
|
||||||
case .accept:
|
case .accept:
|
||||||
MXLog.info("User accepted the prompt.")
|
MXLog.info("User accepted the prompt.")
|
||||||
self.callback?(.accept)
|
self.actionsSubject.send(.accept)
|
||||||
case .cancel:
|
case .cancel:
|
||||||
self.callback?(.cancel)
|
self.actionsSubject.send(.cancel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
func toPresentable() -> AnyView {
|
func toPresentable() -> AnyView {
|
||||||
|
|||||||
@@ -14,12 +14,17 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
typealias TemplateViewModelType = StateStoreViewModel<TemplateViewState, TemplateViewAction>
|
typealias TemplateViewModelType = StateStoreViewModel<TemplateViewState, TemplateViewAction>
|
||||||
|
|
||||||
class TemplateViewModel: TemplateViewModelType, TemplateViewModelProtocol {
|
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) {
|
init(promptType: TemplatePromptType, initialCount: Int = 0) {
|
||||||
super.init(initialViewState: TemplateViewState(promptType: promptType, count: 0))
|
super.init(initialViewState: TemplateViewState(promptType: promptType, count: 0))
|
||||||
@@ -27,12 +32,12 @@ class TemplateViewModel: TemplateViewModelType, TemplateViewModelProtocol {
|
|||||||
|
|
||||||
// MARK: - Public
|
// MARK: - Public
|
||||||
|
|
||||||
override func process(viewAction: TemplateViewAction) async {
|
override func process(viewAction: TemplateViewAction) {
|
||||||
switch viewAction {
|
switch viewAction {
|
||||||
case .accept:
|
case .accept:
|
||||||
callback?(.accept)
|
actionsSubject.send(.accept)
|
||||||
case .cancel:
|
case .cancel:
|
||||||
callback?(.cancel)
|
actionsSubject.send(.cancel)
|
||||||
case .incrementCount:
|
case .incrementCount:
|
||||||
state.count += 1
|
state.count += 1
|
||||||
case .decrementCount:
|
case .decrementCount:
|
||||||
|
|||||||
+2
-2
@@ -14,10 +14,10 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import Combine
|
||||||
|
|
||||||
@MainActor
|
@MainActor
|
||||||
protocol TemplateViewModelProtocol {
|
protocol TemplateViewModelProtocol {
|
||||||
var callback: ((TemplateViewModelAction) -> Void)? { get set }
|
var actions: AnyPublisher<TemplateViewModelAction, Never> { get }
|
||||||
var context: TemplateViewModelType.Context { get }
|
var context: TemplateViewModelType.Context { get }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,15 +38,12 @@ class TemplateScreenViewModelTests: XCTestCase {
|
|||||||
|
|
||||||
func testCounter() async throws {
|
func testCounter() async throws {
|
||||||
context.send(viewAction: .incrementCount)
|
context.send(viewAction: .incrementCount)
|
||||||
await Task.yield()
|
|
||||||
XCTAssertEqual(context.viewState.count, 1)
|
XCTAssertEqual(context.viewState.count, 1)
|
||||||
|
|
||||||
context.send(viewAction: .incrementCount)
|
context.send(viewAction: .incrementCount)
|
||||||
await Task.yield()
|
|
||||||
XCTAssertEqual(context.viewState.count, 2)
|
XCTAssertEqual(context.viewState.count, 2)
|
||||||
|
|
||||||
context.send(viewAction: .decrementCount)
|
context.send(viewAction: .decrementCount)
|
||||||
await Task.yield()
|
|
||||||
XCTAssertEqual(context.viewState.count, 1)
|
XCTAssertEqual(context.viewState.count, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,21 +25,6 @@ class BugReportUITests: XCTestCase {
|
|||||||
app.assertScreenshot(.bugReport, step: 0)
|
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() {
|
func testReportText() {
|
||||||
let app = Application.launch(.bugReport)
|
let app = Application.launch(.bugReport)
|
||||||
|
|
||||||
|
|||||||
@@ -22,19 +22,4 @@ class ReportContentScreenUITests: XCTestCase {
|
|||||||
let app = Application.launch(.reportContent)
|
let app = Application.launch(.reportContent)
|
||||||
app.assertScreenshot(.reportContent, step: 0)
|
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() {
|
func testSearchExactNotExistingMatrixID() {
|
||||||
let app = Application.launch(.startChatSearchingInexistingID)
|
let app = Application.launch(.startChatSearchingNonexistentID)
|
||||||
let searchField = app.searchFields.firstMatch
|
let searchField = app.searchFields.firstMatch
|
||||||
searchField.clearAndTypeText("@a:b.com")
|
searchField.clearAndTypeText("@a:b.com")
|
||||||
XCTAssertFalse(app.staticTexts[A11yIdentifiers.startChatScreen.searchNoResults].waitForExistence(timeout: 1.0))
|
XCTAssertFalse(app.staticTexts[A11yIdentifiers.startChatScreen.searchNoResults].waitForExistence(timeout: 1.0))
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:3d3a511a1c225751c5fe3bf6e296c12612e28efa7f101b533191fa862eace69a
|
|
||||||
size 114029
|
|
||||||
-3
@@ -1,3 +0,0 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:06d95ccd192c2c9550e4a405b76352d8f75d86a1d72cbc42dfab15743bcac2de
|
|
||||||
size 96766
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:6ed199f6c4b98bf600364b87010655c454a2dfeee9edc455e37988b60386e696
|
|
||||||
size 146593
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:63627788babeceb6940989acf8e07c7244d50b4ab84c97bc74b4e19b9ee6ce47
|
|
||||||
size 120785
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:22868875281c5277f94754837ecb9b85bcbdd5149c25552819e4e794395374e5
|
|
||||||
size 144520
|
|
||||||
-3
@@ -1,3 +0,0 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:324756300a6ca0abca19892b5ff4fea9bcd1718fb9375ed87de8e9e981a06908
|
|
||||||
size 118557
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:c1d1fa4ff4b3a3981f78f3ac41e62d110d38f1bc465bb41b77683544fc0bda1d
|
|
||||||
size 210079
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
version https://git-lfs.github.com/spec/v1
|
|
||||||
oid sha256:b4120dca6e5eb55094f9e301bb3fa6e538b6f9a1226f29a2ab4832886661b429
|
|
||||||
size 163825
|
|
||||||
@@ -46,7 +46,6 @@ class BugReportViewModelTests: XCTestCase {
|
|||||||
let context = viewModel.context
|
let context = viewModel.context
|
||||||
|
|
||||||
context.send(viewAction: .removeScreenshot)
|
context.send(viewAction: .removeScreenshot)
|
||||||
try await Task.sleep(nanoseconds: 100_000_000)
|
|
||||||
XCTAssertNil(context.viewState.screenshot)
|
XCTAssertNil(context.viewState.screenshot)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,7 +57,6 @@ class BugReportViewModelTests: XCTestCase {
|
|||||||
let context = viewModel.context
|
let context = viewModel.context
|
||||||
XCTAssertNil(context.viewState.screenshot)
|
XCTAssertNil(context.viewState.screenshot)
|
||||||
context.send(viewAction: .attachScreenshot(UIImage.actions))
|
context.send(viewAction: .attachScreenshot(UIImage.actions))
|
||||||
try await Task.sleep(nanoseconds: 100_000_000)
|
|
||||||
XCTAssert(context.viewState.screenshot == UIImage.actions)
|
XCTAssert(context.viewState.screenshot == UIImage.actions)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,19 +68,20 @@ class BugReportViewModelTests: XCTestCase {
|
|||||||
deviceID: nil,
|
deviceID: nil,
|
||||||
screenshot: nil, isModallyPresented: false)
|
screenshot: nil, isModallyPresented: false)
|
||||||
let context = viewModel.context
|
let context = viewModel.context
|
||||||
var isSuccess = false
|
|
||||||
viewModel.callback = { result in
|
|
||||||
switch result {
|
|
||||||
case .submitFinished:
|
|
||||||
isSuccess = true
|
|
||||||
default: break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
context.send(viewAction: .submit)
|
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.submitBugReportProgressListenerCallsCount == 1)
|
||||||
XCTAssert(mockService.submitBugReportProgressListenerReceivedArguments?.bugReport == BugReport(userID: "@mock.client.com", deviceID: nil, text: "", includeLogs: true, includeCrashLog: true, githubLabels: [], files: []))
|
XCTAssert(mockService.submitBugReportProgressListenerReceivedArguments?.bugReport == BugReport(userID: "@mock.client.com", deviceID: nil, text: "", includeLogs: true, includeCrashLog: true, githubLabels: [], files: []))
|
||||||
XCTAssertTrue(isSuccess)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSendReportWithError() async throws {
|
func testSendReportWithError() async throws {
|
||||||
@@ -95,20 +94,19 @@ class BugReportViewModelTests: XCTestCase {
|
|||||||
deviceID: nil,
|
deviceID: nil,
|
||||||
screenshot: nil, isModallyPresented: false)
|
screenshot: nil, isModallyPresented: false)
|
||||||
let context = viewModel.context
|
let context = viewModel.context
|
||||||
var isFailure = false
|
|
||||||
|
|
||||||
viewModel.callback = { result in
|
|
||||||
switch result {
|
|
||||||
case .submitFailed:
|
|
||||||
isFailure = true
|
|
||||||
default: break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
context.send(viewAction: .submit)
|
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.submitBugReportProgressListenerCallsCount == 1)
|
||||||
XCTAssert(mockService.submitBugReportProgressListenerReceivedArguments?.bugReport == BugReport(userID: "@mock.client.com", deviceID: nil, text: "", includeLogs: true, includeCrashLog: true, githubLabels: [], files: []))
|
XCTAssert(mockService.submitBugReportProgressListenerReceivedArguments?.bugReport == BugReport(userID: "@mock.client.com", deviceID: nil, text: "", includeLogs: true, includeCrashLog: true, githubLabels: [], files: []))
|
||||||
XCTAssertTrue(isFailure)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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 viewModel: FilePreviewViewModelProtocol!
|
||||||
var context: FilePreviewViewModelType.Context!
|
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")))
|
viewModel = FilePreviewViewModel(mediaFile: .unmanaged(url: URL(staticString: "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf")))
|
||||||
context = viewModel.context
|
context = viewModel.context
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor func testCancel() async throws {
|
func testCancel() async throws {
|
||||||
var correctResult = false
|
var correctResult = false
|
||||||
viewModel.callback = { result in
|
viewModel.callback = { result in
|
||||||
switch result {
|
switch result {
|
||||||
|
|||||||
@@ -18,18 +18,19 @@ import XCTest
|
|||||||
|
|
||||||
@testable import ElementX
|
@testable import ElementX
|
||||||
|
|
||||||
|
@MainActor
|
||||||
class HomeScreenViewModelTests: XCTestCase {
|
class HomeScreenViewModelTests: XCTestCase {
|
||||||
var viewModel: HomeScreenViewModelProtocol!
|
var viewModel: HomeScreenViewModelProtocol!
|
||||||
var context: HomeScreenViewModelType.Context!
|
var context: HomeScreenViewModelType.Context!
|
||||||
|
|
||||||
@MainActor override func setUpWithError() throws {
|
override func setUpWithError() throws {
|
||||||
viewModel = HomeScreenViewModel(userSession: MockUserSession(clientProxy: MockClientProxy(userID: "@mock:client.com"),
|
viewModel = HomeScreenViewModel(userSession: MockUserSession(clientProxy: MockClientProxy(userID: "@mock:client.com"),
|
||||||
mediaProvider: MockMediaProvider()),
|
mediaProvider: MockMediaProvider()),
|
||||||
attributedStringBuilder: AttributedStringBuilder())
|
attributedStringBuilder: AttributedStringBuilder())
|
||||||
context = viewModel.context
|
context = viewModel.context
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor func testSelectRoom() async throws {
|
func testSelectRoom() async throws {
|
||||||
let mockRoomId = "mock_room_id"
|
let mockRoomId = "mock_room_id"
|
||||||
var correctResult = false
|
var correctResult = false
|
||||||
var selectedRoomId = ""
|
var selectedRoomId = ""
|
||||||
@@ -49,7 +50,7 @@ class HomeScreenViewModelTests: XCTestCase {
|
|||||||
XCTAssertEqual(mockRoomId, selectedRoomId)
|
XCTAssertEqual(mockRoomId, selectedRoomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainActor func testTapUserAvatar() async throws {
|
func testTapUserAvatar() async throws {
|
||||||
var correctResult = false
|
var correctResult = false
|
||||||
viewModel.callback = { result in
|
viewModel.callback = { result in
|
||||||
switch result {
|
switch result {
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ class ReportContentScreenViewModelTests: XCTestCase {
|
|||||||
viewModel.state.bindings.reasonText = reportReason
|
viewModel.state.bindings.reasonText = reportReason
|
||||||
viewModel.state.bindings.ignoreUser = false
|
viewModel.state.bindings.ignoreUser = false
|
||||||
viewModel.context.send(viewAction: .submit)
|
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.
|
// Then the content should be reported, but the user should not be included.
|
||||||
XCTAssertEqual(roomProxy.reportContentReasonCallsCount, 1, "The content should always be reported.")
|
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.reasonText = reportReason
|
||||||
viewModel.state.bindings.ignoreUser = true
|
viewModel.state.bindings.ignoreUser = true
|
||||||
viewModel.context.send(viewAction: .submit)
|
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.
|
// Then the content should be reported, and the user should be ignored.
|
||||||
XCTAssertEqual(roomProxy.reportContentReasonCallsCount, 1, "The content should always be reported.")
|
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))
|
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isPublic: true, members: mockedMembers))
|
||||||
viewModel = RoomDetailsViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
viewModel = RoomDetailsViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||||
context.send(viewAction: .processTapLeave)
|
context.send(viewAction: .processTapLeave)
|
||||||
await Task.yield()
|
|
||||||
XCTAssertEqual(context.leaveRoomAlertItem?.state, .public)
|
XCTAssertEqual(context.leaveRoomAlertItem?.state, .public)
|
||||||
XCTAssertEqual(context.leaveRoomAlertItem?.subtitle, L10n.leaveRoomAlertSubtitle)
|
XCTAssertEqual(context.leaveRoomAlertItem?.subtitle, L10n.leaveRoomAlertSubtitle)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testLeavRoomTappedWhenRoomNotPublic() async {
|
func testLeaveRoomTappedWhenRoomNotPublic() async {
|
||||||
let mockedMembers: [RoomMemberProxyMock] = [.mockBob, .mockAlice]
|
let mockedMembers: [RoomMemberProxyMock] = [.mockBob, .mockAlice]
|
||||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isPublic: false, members: mockedMembers))
|
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isPublic: false, members: mockedMembers))
|
||||||
viewModel = RoomDetailsViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
viewModel = RoomDetailsViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||||
context.send(viewAction: .processTapLeave)
|
context.send(viewAction: .processTapLeave)
|
||||||
await Task.yield()
|
|
||||||
XCTAssertEqual(context.leaveRoomAlertItem?.state, .private)
|
XCTAssertEqual(context.leaveRoomAlertItem?.state, .private)
|
||||||
XCTAssertEqual(context.leaveRoomAlertItem?.subtitle, L10n.leaveRoomAlertPrivateSubtitle)
|
XCTAssertEqual(context.leaveRoomAlertItem?.subtitle, L10n.leaveRoomAlertPrivateSubtitle)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testLeaveRoomTappedWithLessThanTwoMembers() async {
|
func testLeaveRoomTappedWithLessThanTwoMembers() async {
|
||||||
context.send(viewAction: .processTapLeave)
|
context.send(viewAction: .processTapLeave)
|
||||||
await Task.yield()
|
|
||||||
XCTAssertEqual(context.leaveRoomAlertItem?.state, .empty)
|
XCTAssertEqual(context.leaveRoomAlertItem?.state, .empty)
|
||||||
XCTAssertEqual(context.leaveRoomAlertItem?.subtitle, L10n.leaveRoomAlertEmptySubtitle)
|
XCTAssertEqual(context.leaveRoomAlertItem?.subtitle, L10n.leaveRoomAlertEmptySubtitle)
|
||||||
}
|
}
|
||||||
@@ -78,7 +75,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
|||||||
.failure(.failedLeavingRoom)
|
.failure(.failedLeavingRoom)
|
||||||
}
|
}
|
||||||
context.send(viewAction: .confirmLeave)
|
context.send(viewAction: .confirmLeave)
|
||||||
await Task.yield()
|
await context.nextViewState()
|
||||||
XCTAssertEqual(roomProxyMock.leaveRoomCallsCount, 1)
|
XCTAssertEqual(roomProxyMock.leaveRoomCallsCount, 1)
|
||||||
XCTAssertNotNil(context.alertInfo)
|
XCTAssertNotNil(context.alertInfo)
|
||||||
}
|
}
|
||||||
@@ -103,10 +100,10 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
|||||||
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
||||||
|
|
||||||
context.send(viewAction: .ignoreConfirmed)
|
context.send(viewAction: .ignoreConfirmed)
|
||||||
await Task.yield()
|
await context.nextViewState()
|
||||||
|
|
||||||
XCTAssertTrue(context.viewState.isProcessingIgnoreRequest)
|
XCTAssertTrue(context.viewState.isProcessingIgnoreRequest)
|
||||||
try await Task.sleep(for: .milliseconds(10))
|
await context.nextViewState()
|
||||||
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
||||||
XCTAssert(context.viewState.dmRecipient?.isIgnored == true)
|
XCTAssert(context.viewState.dmRecipient?.isIgnored == true)
|
||||||
}
|
}
|
||||||
@@ -123,10 +120,10 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
|||||||
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
||||||
|
|
||||||
context.send(viewAction: .ignoreConfirmed)
|
context.send(viewAction: .ignoreConfirmed)
|
||||||
await Task.yield()
|
await context.nextViewState()
|
||||||
|
|
||||||
XCTAssertTrue(context.viewState.isProcessingIgnoreRequest)
|
XCTAssertTrue(context.viewState.isProcessingIgnoreRequest)
|
||||||
try await Task.sleep(for: .milliseconds(10))
|
await context.nextViewState()
|
||||||
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
||||||
XCTAssert(context.viewState.dmRecipient?.isIgnored == false)
|
XCTAssert(context.viewState.dmRecipient?.isIgnored == false)
|
||||||
XCTAssertNotNil(context.alertInfo)
|
XCTAssertNotNil(context.alertInfo)
|
||||||
@@ -144,10 +141,10 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
|||||||
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
||||||
|
|
||||||
context.send(viewAction: .unignoreConfirmed)
|
context.send(viewAction: .unignoreConfirmed)
|
||||||
await Task.yield()
|
await context.nextViewState()
|
||||||
|
|
||||||
XCTAssertTrue(context.viewState.isProcessingIgnoreRequest)
|
XCTAssertTrue(context.viewState.isProcessingIgnoreRequest)
|
||||||
try await Task.sleep(for: .milliseconds(10))
|
await context.nextViewState()
|
||||||
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
||||||
XCTAssert(context.viewState.dmRecipient?.isIgnored == false)
|
XCTAssert(context.viewState.dmRecipient?.isIgnored == false)
|
||||||
}
|
}
|
||||||
@@ -164,10 +161,10 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
|||||||
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
||||||
|
|
||||||
context.send(viewAction: .unignoreConfirmed)
|
context.send(viewAction: .unignoreConfirmed)
|
||||||
await Task.yield()
|
await context.nextViewState()
|
||||||
XCTAssertTrue(context.viewState.isProcessingIgnoreRequest)
|
XCTAssertTrue(context.viewState.isProcessingIgnoreRequest)
|
||||||
|
|
||||||
try await Task.sleep(for: .milliseconds(10))
|
await context.nextViewState()
|
||||||
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
||||||
XCTAssert(context.viewState.dmRecipient?.isIgnored == true)
|
XCTAssert(context.viewState.dmRecipient?.isIgnored == true)
|
||||||
XCTAssertNotNil(context.alertInfo)
|
XCTAssertNotNil(context.alertInfo)
|
||||||
|
|||||||
@@ -42,14 +42,13 @@ class RoomMemberDetailsViewModelTests: XCTestCase {
|
|||||||
viewModel = RoomMemberDetailsViewModel(roomMemberProxy: roomMemberProxyMock, mediaProvider: MockMediaProvider())
|
viewModel = RoomMemberDetailsViewModel(roomMemberProxy: roomMemberProxyMock, mediaProvider: MockMediaProvider())
|
||||||
|
|
||||||
context.send(viewAction: .showIgnoreAlert)
|
context.send(viewAction: .showIgnoreAlert)
|
||||||
await Task.yield()
|
|
||||||
XCTAssertEqual(context.ignoreUserAlert, .init(action: .ignore))
|
XCTAssertEqual(context.ignoreUserAlert, .init(action: .ignore))
|
||||||
|
|
||||||
context.send(viewAction: .ignoreConfirmed)
|
context.send(viewAction: .ignoreConfirmed)
|
||||||
await Task.yield()
|
await context.nextViewState()
|
||||||
XCTAssertTrue(context.viewState.isProcessingIgnoreRequest)
|
XCTAssertTrue(context.viewState.isProcessingIgnoreRequest)
|
||||||
XCTAssertFalse(context.viewState.details.isIgnored)
|
XCTAssertFalse(context.viewState.details.isIgnored)
|
||||||
try await Task.sleep(for: .milliseconds(10))
|
await context.nextViewState()
|
||||||
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
||||||
XCTAssertTrue(context.viewState.details.isIgnored)
|
XCTAssertTrue(context.viewState.details.isIgnored)
|
||||||
}
|
}
|
||||||
@@ -61,16 +60,14 @@ class RoomMemberDetailsViewModelTests: XCTestCase {
|
|||||||
return .failure(.ignoreUserFailed)
|
return .failure(.ignoreUserFailed)
|
||||||
}
|
}
|
||||||
viewModel = RoomMemberDetailsViewModel(roomMemberProxy: roomMemberProxyMock, mediaProvider: MockMediaProvider())
|
viewModel = RoomMemberDetailsViewModel(roomMemberProxy: roomMemberProxyMock, mediaProvider: MockMediaProvider())
|
||||||
|
|
||||||
context.send(viewAction: .showIgnoreAlert)
|
context.send(viewAction: .showIgnoreAlert)
|
||||||
await Task.yield()
|
|
||||||
XCTAssertEqual(context.ignoreUserAlert, .init(action: .ignore))
|
XCTAssertEqual(context.ignoreUserAlert, .init(action: .ignore))
|
||||||
|
|
||||||
context.send(viewAction: .ignoreConfirmed)
|
context.send(viewAction: .ignoreConfirmed)
|
||||||
await Task.yield()
|
await context.nextViewState()
|
||||||
XCTAssertTrue(context.viewState.isProcessingIgnoreRequest)
|
XCTAssertTrue(context.viewState.isProcessingIgnoreRequest)
|
||||||
XCTAssertFalse(context.viewState.details.isIgnored)
|
XCTAssertFalse(context.viewState.details.isIgnored)
|
||||||
try await Task.sleep(for: .milliseconds(10))
|
await context.nextViewState()
|
||||||
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
||||||
XCTAssertNotNil(context.errorAlert)
|
XCTAssertNotNil(context.errorAlert)
|
||||||
XCTAssertFalse(context.viewState.details.isIgnored)
|
XCTAssertFalse(context.viewState.details.isIgnored)
|
||||||
@@ -85,14 +82,13 @@ class RoomMemberDetailsViewModelTests: XCTestCase {
|
|||||||
viewModel = RoomMemberDetailsViewModel(roomMemberProxy: roomMemberProxyMock, mediaProvider: MockMediaProvider())
|
viewModel = RoomMemberDetailsViewModel(roomMemberProxy: roomMemberProxyMock, mediaProvider: MockMediaProvider())
|
||||||
|
|
||||||
context.send(viewAction: .showUnignoreAlert)
|
context.send(viewAction: .showUnignoreAlert)
|
||||||
await Task.yield()
|
|
||||||
XCTAssertEqual(context.ignoreUserAlert, .init(action: .unignore))
|
XCTAssertEqual(context.ignoreUserAlert, .init(action: .unignore))
|
||||||
|
|
||||||
context.send(viewAction: .unignoreConfirmed)
|
context.send(viewAction: .unignoreConfirmed)
|
||||||
await Task.yield()
|
await context.nextViewState()
|
||||||
XCTAssertTrue(context.viewState.isProcessingIgnoreRequest)
|
XCTAssertTrue(context.viewState.isProcessingIgnoreRequest)
|
||||||
XCTAssertTrue(context.viewState.details.isIgnored)
|
XCTAssertTrue(context.viewState.details.isIgnored)
|
||||||
try await Task.sleep(for: .milliseconds(10))
|
await context.nextViewState()
|
||||||
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
||||||
XCTAssertFalse(context.viewState.details.isIgnored)
|
XCTAssertFalse(context.viewState.details.isIgnored)
|
||||||
}
|
}
|
||||||
@@ -106,14 +102,13 @@ class RoomMemberDetailsViewModelTests: XCTestCase {
|
|||||||
viewModel = RoomMemberDetailsViewModel(roomMemberProxy: roomMemberProxyMock, mediaProvider: MockMediaProvider())
|
viewModel = RoomMemberDetailsViewModel(roomMemberProxy: roomMemberProxyMock, mediaProvider: MockMediaProvider())
|
||||||
|
|
||||||
context.send(viewAction: .showUnignoreAlert)
|
context.send(viewAction: .showUnignoreAlert)
|
||||||
await Task.yield()
|
|
||||||
XCTAssertEqual(context.ignoreUserAlert, .init(action: .unignore))
|
XCTAssertEqual(context.ignoreUserAlert, .init(action: .unignore))
|
||||||
|
|
||||||
context.send(viewAction: .unignoreConfirmed)
|
context.send(viewAction: .unignoreConfirmed)
|
||||||
await Task.yield()
|
await context.nextViewState()
|
||||||
XCTAssertTrue(context.viewState.isProcessingIgnoreRequest)
|
XCTAssertTrue(context.viewState.isProcessingIgnoreRequest)
|
||||||
XCTAssertTrue(context.viewState.details.isIgnored)
|
XCTAssertTrue(context.viewState.details.isIgnored)
|
||||||
try await Task.sleep(for: .milliseconds(10))
|
await context.nextViewState()
|
||||||
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
||||||
XCTAssertTrue(context.viewState.details.isIgnored)
|
XCTAssertTrue(context.viewState.details.isIgnored)
|
||||||
XCTAssertNotNil(context.errorAlert)
|
XCTAssertNotNil(context.errorAlert)
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ class SessionVerificationViewModelTests: XCTestCase {
|
|||||||
var context: SessionVerificationViewModelType.Context!
|
var context: SessionVerificationViewModelType.Context!
|
||||||
var sessionVerificationController: SessionVerificationControllerProxyMock!
|
var sessionVerificationController: SessionVerificationControllerProxyMock!
|
||||||
|
|
||||||
@MainActor
|
|
||||||
override func setUpWithError() throws {
|
override func setUpWithError() throws {
|
||||||
sessionVerificationController = SessionVerificationControllerProxyMock.configureMock()
|
sessionVerificationController = SessionVerificationControllerProxyMock.configureMock()
|
||||||
viewModel = SessionVerificationViewModel(sessionVerificationControllerProxy: sessionVerificationController)
|
viewModel = SessionVerificationViewModel(sessionVerificationControllerProxy: sessionVerificationController)
|
||||||
@@ -49,18 +48,14 @@ class SessionVerificationViewModelTests: XCTestCase {
|
|||||||
|
|
||||||
context.send(viewAction: .close)
|
context.send(viewAction: .close)
|
||||||
|
|
||||||
await Task.yield()
|
|
||||||
|
|
||||||
XCTAssertEqual(context.viewState.verificationState, .cancelling)
|
XCTAssertEqual(context.viewState.verificationState, .cancelling)
|
||||||
|
|
||||||
try await Task.sleep(for: .milliseconds(100))
|
await context.nextViewState()
|
||||||
|
|
||||||
XCTAssertEqual(context.viewState.verificationState, .cancelled)
|
XCTAssertEqual(context.viewState.verificationState, .cancelled)
|
||||||
|
|
||||||
context.send(viewAction: .restart)
|
context.send(viewAction: .restart)
|
||||||
|
|
||||||
await Task.yield()
|
|
||||||
|
|
||||||
XCTAssertEqual(context.viewState.verificationState, .initial)
|
XCTAssertEqual(context.viewState.verificationState, .initial)
|
||||||
|
|
||||||
XCTAssert(sessionVerificationController.requestVerificationCallsCount == 1)
|
XCTAssert(sessionVerificationController.requestVerificationCallsCount == 1)
|
||||||
|
|||||||
@@ -50,4 +50,3 @@ targets:
|
|||||||
- path: ../../Tools/Scripts/Templates/SimpleScreenExample/Tests/Unit
|
- path: ../../Tools/Scripts/Templates/SimpleScreenExample/Tests/Unit
|
||||||
- path: ../Resources
|
- path: ../Resources
|
||||||
- path: ../../ElementX/Sources/Other/InfoPlistReader.swift
|
- path: ../../ElementX/Sources/Other/InfoPlistReader.swift
|
||||||
- path: ../../ElementX/Sources/Other/Extensions/Publisher.swift
|
|
||||||
|
|||||||
Reference in New Issue
Block a user