mirror of
https://github.com/element-hq/element-x-ios.git
synced 2025-03-10 13:37:11 +00:00
Fix various flakey unit tests (#1783)
* Fix flakey emoji provider tests * Fix flakey RoomScreenViewModel tests * Fix flakey HomeScreenViewModel tests * Fix flakey RoomMemberListScreen tests, problem with bindings getting overriden and deferFulfillment cancellable not getting stored * Fix flakey RoomNotificationSettingsScreen tests and crashes * Fix flakey RoomMemberDetailsScreen tests * Deprecate old `deferFulfillment` and `nextViewState` methods * Convert more files to the new `deferFulfillment` * Converted the rest of the tests to the new deferFulfillment * Removed now unused `nextViewState` and `deferFulfillment` * Remove automatic retries from unit tests * Reset analytics flag after running unit tests * Address PR comments * Introduce a new `deferFulfillment(publisher, keyPath, transitionValues)` method and use it where appropiate
This commit is contained in:
parent
1f3898c69d
commit
a05c3e3774
@ -485,7 +485,6 @@
|
||||
992F5E750F5030C4BA2D0D03 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 01C4C7DB37597D7D8379511A /* Assets.xcassets */; };
|
||||
9965CB800CE6BC74ACA969FC /* EncryptedHistoryRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75697AB5E64A12F1F069F511 /* EncryptedHistoryRoomTimelineView.swift */; };
|
||||
99ED42B8F8D6BFB1DBCF4C45 /* AnalyticsEvents in Frameworks */ = {isa = PBXBuildFile; productRef = D661CAB418C075A94306A792 /* AnalyticsEvents */; };
|
||||
99F8DA4CCC6772EE5FE68E24 /* ViewModelContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 818CBE6249ED6E8FC30E8366 /* ViewModelContext.swift */; };
|
||||
9A3B0CDF097E3838FB1B9595 /* Bundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6E89E530A8E92EC44301CA1 /* Bundle.swift */; };
|
||||
9A4E3D5AA44B041DAC3A0D81 /* OIDCAuthenticationPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92390F9FA98255440A6BF5F8 /* OIDCAuthenticationPresenter.swift */; };
|
||||
9AC5F8142413862A9E3A2D98 /* DeviceKit in Frameworks */ = {isa = PBXBuildFile; productRef = A7CA6F33C553805035C3B114 /* DeviceKit */; };
|
||||
@ -1259,7 +1258,6 @@
|
||||
80C4927D09099497233E9980 /* WaitlistScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaitlistScreen.swift; sourceTree = "<group>"; };
|
||||
80E815FF3CC5E5A355E3A25E /* RoomMessageEventStringBuilder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMessageEventStringBuilder.swift; sourceTree = "<group>"; };
|
||||
818695BED971753243FEF897 /* StickerRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickerRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||
818CBE6249ED6E8FC30E8366 /* ViewModelContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModelContext.swift; sourceTree = "<group>"; };
|
||||
8196D64EB9CF2AF1F43E4ED1 /* AnalyticsPromptScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||
81A9B5225D0881CEFA2CF7C9 /* RoomNotificationSettingsScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomNotificationSettingsScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
81B17B1F29448D1B9049B11C /* ReportContentScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReportContentScreenViewModel.swift; sourceTree = "<group>"; };
|
||||
@ -2647,7 +2645,6 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
60F18AECC9D38C2B6D85F99C /* Publisher.swift */,
|
||||
818CBE6249ED6E8FC30E8366 /* ViewModelContext.swift */,
|
||||
74611A4182DCF5F4D42696EC /* XCTestCase.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
@ -4525,7 +4522,6 @@
|
||||
A1DF0E1E526A981ED6D5DF44 /* UserIndicatorControllerTests.swift in Sources */,
|
||||
04F17DE71A50206336749BAC /* UserPreferenceTests.swift in Sources */,
|
||||
81A7C020CB5F6232242A8414 /* UserSessionTests.swift in Sources */,
|
||||
99F8DA4CCC6772EE5FE68E24 /* ViewModelContext.swift in Sources */,
|
||||
FB9A1DD83EF641A75ABBCE69 /* WaitlistScreenViewModelTests.swift in Sources */,
|
||||
7F02063FB3D1C3E5601471A1 /* WelcomeScreenScreenViewModelTests.swift in Sources */,
|
||||
3116693C5EB476E028990416 /* XCTestCase.swift in Sources */,
|
||||
|
@ -41,7 +41,7 @@ class EmojiPickerScreenViewModel: EmojiPickerScreenViewModelType, EmojiPickerScr
|
||||
switch viewAction {
|
||||
case let .search(searchString: searchString):
|
||||
Task {
|
||||
let categories = await emojiProvider.getCategories(searchString: searchString)
|
||||
let categories = await emojiProvider.categories(searchString: searchString)
|
||||
state.categories = convert(emojiCategories: categories)
|
||||
}
|
||||
case let .emojiTapped(emoji: emoji):
|
||||
@ -56,7 +56,7 @@ class EmojiPickerScreenViewModel: EmojiPickerScreenViewModelType, EmojiPickerScr
|
||||
private func loadEmojis() {
|
||||
Task(priority: .userInitiated) { [weak self] in
|
||||
guard let self else { return }
|
||||
let categories = await self.emojiProvider.getCategories(searchString: nil)
|
||||
let categories = await self.emojiProvider.categories(searchString: nil)
|
||||
self.state.categories = convert(emojiCategories: categories)
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ struct RoomMembersListScreenViewState: BindableState {
|
||||
init(joinedMembersCount: Int,
|
||||
joinedMembers: [RoomMemberDetails] = [],
|
||||
invitedMembers: [RoomMemberDetails] = [],
|
||||
bindings: RoomMembersListScreenViewStateBindings = .init()) {
|
||||
bindings: RoomMembersListScreenViewStateBindings) {
|
||||
self.joinedMembersCount = joinedMembersCount
|
||||
self.joinedMembers = joinedMembers
|
||||
self.invitedMembers = invitedMembers
|
||||
|
@ -37,7 +37,7 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
|
||||
self.roomProxy = roomProxy
|
||||
self.userIndicatorController = userIndicatorController
|
||||
|
||||
super.init(initialViewState: .init(joinedMembersCount: roomProxy.joinedMembersCount),
|
||||
super.init(initialViewState: .init(joinedMembersCount: roomProxy.joinedMembersCount, bindings: .init()),
|
||||
imageProvider: mediaProvider)
|
||||
|
||||
setupMembers()
|
||||
@ -83,7 +83,8 @@ class RoomMembersListScreenViewModel: RoomMembersListScreenViewModelType, RoomMe
|
||||
self.members = members
|
||||
self.state = .init(joinedMembersCount: roomProxy.joinedMembersCount,
|
||||
joinedMembers: roomMembersDetails.joinedMembers,
|
||||
invitedMembers: roomMembersDetails.invitedMembers)
|
||||
invitedMembers: roomMembersDetails.invitedMembers,
|
||||
bindings: state.bindings)
|
||||
self.state.canInviteUsers = roomMembersDetails.accountOwner?.canInviteUsers ?? false
|
||||
hideLoader()
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ import Foundation
|
||||
|
||||
@MainActor
|
||||
protocol EmojiProviderProtocol {
|
||||
func getCategories(searchString: String?) async -> [EmojiCategory]
|
||||
func categories(searchString: String?) async -> [EmojiCategory]
|
||||
}
|
||||
|
||||
private enum EmojiProviderState {
|
||||
@ -39,7 +39,7 @@ class EmojiProvider: EmojiProviderProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
func getCategories(searchString: String? = nil) async -> [EmojiCategory] {
|
||||
func categories(searchString: String? = nil) async -> [EmojiCategory] {
|
||||
let emojiCategories = await loadIfNeeded()
|
||||
if let searchString, searchString.isEmpty == false {
|
||||
return search(searchString: searchString, emojiCategories: emojiCategories)
|
||||
|
@ -24,6 +24,10 @@ class AnalyticsSettingsScreenViewModelTests: XCTestCase {
|
||||
private var viewModel: AnalyticsSettingsScreenViewModelProtocol!
|
||||
private var context: AnalyticsSettingsScreenViewModelType.Context!
|
||||
|
||||
override func tearDown() {
|
||||
appSettings.analyticsConsentState = .unknown
|
||||
}
|
||||
|
||||
@MainActor override func setUpWithError() throws {
|
||||
AppSettings.reset()
|
||||
appSettings = AppSettings()
|
||||
|
@ -71,17 +71,18 @@ class BugReportViewModelTests: XCTestCase {
|
||||
deviceID: nil,
|
||||
screenshot: nil, isModallyPresented: false)
|
||||
let context = viewModel.context
|
||||
let deferred = deferFulfillment(viewModel.actions.collect(2).first())
|
||||
|
||||
let deferred = deferFulfillment(viewModel.actions) { action in
|
||||
switch action {
|
||||
case .submitFinished:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
context.send(viewAction: .submit)
|
||||
let actions = try await deferred.fulfill()
|
||||
|
||||
guard case .submitStarted = actions[0] else {
|
||||
return XCTFail("Action 1 was not .submitFailed")
|
||||
}
|
||||
|
||||
guard case .submitFinished = actions[1] else {
|
||||
return XCTFail("Action 2 was not .submitFinished")
|
||||
}
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssert(mockService.submitBugReportProgressListenerCallsCount == 1)
|
||||
XCTAssert(mockService.submitBugReportProgressListenerReceivedArguments?.bugReport == BugReport(userID: "@mock.client.com", deviceID: nil, text: "", includeLogs: true, includeCrashLog: true, canContact: false, githubLabels: [], files: []))
|
||||
@ -97,18 +98,18 @@ class BugReportViewModelTests: XCTestCase {
|
||||
deviceID: nil,
|
||||
screenshot: nil, isModallyPresented: false)
|
||||
|
||||
let deferred = deferFulfillment(viewModel.actions.collect(2).first())
|
||||
let deferred = deferFulfillment(viewModel.actions) { action in
|
||||
switch action {
|
||||
case .submitFailed:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
let context = viewModel.context
|
||||
context.send(viewAction: .submit)
|
||||
let actions = try await deferred.fulfill()
|
||||
|
||||
guard case .submitStarted = actions[0] else {
|
||||
return XCTFail("Action 1 was not .submitFailed")
|
||||
}
|
||||
|
||||
guard case .submitFailed = actions[1] else {
|
||||
return XCTFail("Action 2 was not .submitFailed")
|
||||
}
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssert(mockService.submitBugReportProgressListenerCallsCount == 1)
|
||||
XCTAssert(mockService.submitBugReportProgressListenerReceivedArguments?.bugReport == BugReport(userID: "@mock.client.com", deviceID: nil, text: "", includeLogs: true, includeCrashLog: true, canContact: false, githubLabels: [], files: []))
|
||||
|
@ -44,8 +44,17 @@ class CreatePollScreenViewModelTests: XCTestCase {
|
||||
context.options[1].text = "bla2"
|
||||
XCTAssertFalse(context.viewState.bindings.isCreateButtonDisabled)
|
||||
|
||||
let deferred = deferFulfillment(viewModel.actions.first())
|
||||
let deferred = deferFulfillment(viewModel.actions) { action in
|
||||
switch action {
|
||||
case .create:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
context.send(viewAction: .create)
|
||||
|
||||
let action = try await deferred.fulfill()
|
||||
|
||||
guard case .create(let question, let options, let kind) = action else {
|
||||
|
@ -18,46 +18,55 @@ import XCTest
|
||||
|
||||
@testable import ElementX
|
||||
|
||||
@MainActor
|
||||
final class EmojiProviderTests: XCTestCase {
|
||||
var sut: EmojiProvider!
|
||||
private var emojiLoaderMock: EmojiLoaderMock!
|
||||
|
||||
@MainActor override func setUp() {
|
||||
emojiLoaderMock = EmojiLoaderMock()
|
||||
sut = EmojiProvider(loader: emojiLoaderMock)
|
||||
}
|
||||
|
||||
func test_whenEmojisLoaded_categoriesAreLoadedFromLoader() async throws {
|
||||
func testWhenEmojisLoadedCategoriesAreLoadedFromLoader() async throws {
|
||||
let item = EmojiItem(label: "test", unicode: "test", keywords: ["1", "2"], shortcodes: ["1", "2"], skins: ["🙂"])
|
||||
let category = EmojiCategory(id: "test", emojis: [item])
|
||||
|
||||
let emojiLoaderMock = EmojiLoaderMock()
|
||||
emojiLoaderMock.categories = [category]
|
||||
let categories = await sut.getCategories()
|
||||
|
||||
let emojiProvider = EmojiProvider(loader: emojiLoaderMock)
|
||||
|
||||
let categories = await emojiProvider.categories()
|
||||
XCTAssertEqual(emojiLoaderMock.categories, categories)
|
||||
}
|
||||
|
||||
func test_whenEmojisLoadedAndSearchStringEmpty_allCategoriesReturned() async throws {
|
||||
func testWhenEmojisLoadedAndSearchStringEmptyAllCategoriesReturned() async throws {
|
||||
let item = EmojiItem(label: "test", unicode: "test", keywords: ["1", "2"], shortcodes: ["1", "2"], skins: ["🙂"])
|
||||
let category = EmojiCategory(id: "test", emojis: [item])
|
||||
|
||||
let emojiLoaderMock = EmojiLoaderMock()
|
||||
emojiLoaderMock.categories = [category]
|
||||
let categories = await sut.getCategories(searchString: "")
|
||||
|
||||
let emojiProvider = EmojiProvider(loader: emojiLoaderMock)
|
||||
|
||||
let categories = await emojiProvider.categories(searchString: "")
|
||||
XCTAssertEqual(emojiLoaderMock.categories, categories)
|
||||
}
|
||||
|
||||
func test_whenEmojisLoadedSecondTime_cachedValuesAreUsed() async throws {
|
||||
func testWhenEmojisLoadedSecondTimeCachedValuesAreUsed() async throws {
|
||||
let item = EmojiItem(label: "test", unicode: "test", keywords: ["1", "2"], shortcodes: ["1", "2"], skins: ["🙂"])
|
||||
let item2 = EmojiItem(label: "test2", unicode: "test2", keywords: ["3", "4"], shortcodes: ["3", "4"], skins: ["🙂"])
|
||||
let categoriesForFirstLoad = [EmojiCategory(id: "test",
|
||||
emojis: [item])]
|
||||
let categoriesForSecondLoad = [EmojiCategory(id: "test2",
|
||||
emojis: [item2])]
|
||||
|
||||
let emojiLoaderMock = EmojiLoaderMock()
|
||||
emojiLoaderMock.categories = categoriesForFirstLoad
|
||||
_ = await sut.getCategories()
|
||||
|
||||
let emojiProvider = EmojiProvider(loader: emojiLoaderMock)
|
||||
|
||||
_ = await emojiProvider.categories()
|
||||
emojiLoaderMock.categories = categoriesForSecondLoad
|
||||
let categories = await sut.getCategories()
|
||||
|
||||
let categories = await emojiProvider.categories()
|
||||
XCTAssertEqual(categories, categoriesForFirstLoad)
|
||||
}
|
||||
|
||||
func test_whenEmojisSearched_correctNumberOfCategoriesReturned() async throws {
|
||||
func testWhenEmojisSearchedCorrectNumberOfCategoriesReturned() async throws {
|
||||
let searchString = "smile"
|
||||
var categories = [EmojiCategory]()
|
||||
let item0WithSearchString = EmojiItem(label: "emoji0", unicode: "\(searchString)_123", keywords: ["key1", "key1"], shortcodes: ["key1", "key1"], skins: ["🙂"])
|
||||
@ -74,9 +83,14 @@ final class EmojiProviderTests: XCTestCase {
|
||||
item4WithoutSearchString]))
|
||||
categories.append(EmojiCategory(id: "test",
|
||||
emojis: [item5WithSearchString]))
|
||||
|
||||
let emojiLoaderMock = EmojiLoaderMock()
|
||||
emojiLoaderMock.categories = categories
|
||||
_ = await sut.getCategories()
|
||||
let result = await sut.getCategories(searchString: searchString)
|
||||
|
||||
let emojiProvider = EmojiProvider(loader: emojiLoaderMock)
|
||||
|
||||
_ = await emojiProvider.categories()
|
||||
let result = await emojiProvider.categories(searchString: searchString)
|
||||
XCTAssertEqual(result.count, 2)
|
||||
XCTAssertEqual(result.first?.emojis.count, 4)
|
||||
}
|
||||
|
@ -1,24 +0,0 @@
|
||||
//
|
||||
// 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 StateStoreViewModel.Context {
|
||||
@discardableResult
|
||||
func nextViewState() async -> State? {
|
||||
await $viewState.nextValue
|
||||
}
|
||||
}
|
@ -19,34 +19,37 @@ import XCTest
|
||||
|
||||
extension XCTestCase {
|
||||
/// XCTest utility that assists in subscribing to a publisher and deferring the fulfilment and results until some other actions have been performed.
|
||||
///
|
||||
/// ```
|
||||
/// let collectedEvents = somePublisher.collect(3).first()
|
||||
/// let awaitDeferred = deferFulfillment(collectedEvents)
|
||||
/// // Do some other work that publishes to somePublisher
|
||||
/// XCTAssertEqual(try await awaitDeferred.execute(), [expected, values, here])
|
||||
/// ```
|
||||
/// - Parameters:
|
||||
/// - publisher: The publisher to wait on.
|
||||
/// - timeout: A timeout after which we give up.
|
||||
/// - message: An optional custom expectation message
|
||||
/// - until: callback that evaluates outputs until some condition is reached
|
||||
/// - Returns: The deferred fulfilment to be executed after some actions and that returns the result of the publisher.
|
||||
func deferFulfillment<T: Publisher>(_ publisher: T, timeout: TimeInterval = 10, message: String? = nil) -> DeferredFulfillment<T.Output> {
|
||||
var result: Result<T.Output, Error>?
|
||||
func deferFulfillment<P: Publisher>(_ publisher: P,
|
||||
timeout: TimeInterval = 10,
|
||||
message: String? = nil,
|
||||
until condition: @escaping (P.Output) -> Bool) -> DeferredFulfillment<P.Output> {
|
||||
var result: Result<P.Output, Error>?
|
||||
let expectation = expectation(description: message ?? "Awaiting publisher")
|
||||
var hasFullfilled = false
|
||||
let cancellable = publisher
|
||||
.sink { completion in
|
||||
switch completion {
|
||||
case .failure(let error):
|
||||
result = .failure(error)
|
||||
expectation.fulfill()
|
||||
case .finished:
|
||||
break
|
||||
}
|
||||
expectation.fulfill()
|
||||
} receiveValue: { value in
|
||||
if condition(value), !hasFullfilled {
|
||||
result = .success(value)
|
||||
expectation.fulfill()
|
||||
hasFullfilled = true
|
||||
}
|
||||
}
|
||||
|
||||
return DeferredFulfillment<T.Output> {
|
||||
return DeferredFulfillment<P.Output> {
|
||||
await self.fulfillment(of: [expectation], timeout: timeout)
|
||||
cancellable.cancel()
|
||||
let unwrappedResult = try XCTUnwrap(result, "Awaited publisher did not produce any output")
|
||||
@ -54,6 +57,32 @@ extension XCTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
/// XCTest utility that assists in subscribing to a publisher and deferring the fulfilment and results until some other actions have been performed.
|
||||
/// - Parameters:
|
||||
/// - publisher: The publisher to wait on.
|
||||
/// - keyPath: the key path for the expected values
|
||||
/// - transitionValues: the values through which the keypath needs to transition through
|
||||
/// - timeout: A timeout after which we give up.
|
||||
/// - message: An optional custom expectation message
|
||||
/// - Returns: The deferred fulfilment to be executed after some actions and that returns the result of the publisher.
|
||||
func deferFulfillment<P: Publisher, K: KeyPath<P.Output, V>, V: Equatable>(_ publisher: P,
|
||||
keyPath: K,
|
||||
transitionValues: [V],
|
||||
timeout: TimeInterval = 10,
|
||||
message: String? = nil) -> DeferredFulfillment<P.Output> {
|
||||
var expectedOrder = transitionValues
|
||||
let deferred = deferFulfillment<P>(publisher, timeout: timeout, message: message) { value in
|
||||
let receivedValue = value[keyPath: keyPath]
|
||||
if let index = expectedOrder.firstIndex(where: { $0 == receivedValue }), index == 0 {
|
||||
expectedOrder.remove(at: index)
|
||||
}
|
||||
|
||||
return expectedOrder.isEmpty
|
||||
}
|
||||
|
||||
return deferred
|
||||
}
|
||||
|
||||
struct DeferredFulfillment<T> {
|
||||
let closure: () async throws -> T
|
||||
@discardableResult func fulfill() async throws -> T {
|
||||
|
@ -83,8 +83,15 @@ class HomeScreenViewModelTests: XCTestCase {
|
||||
func testLeaveRoomAlert() async throws {
|
||||
let mockRoomId = "1"
|
||||
clientProxy.roomForIdentifierMocks[mockRoomId] = .init(with: .init(id: mockRoomId, displayName: "Some room"))
|
||||
|
||||
let deferred = deferFulfillment(context.$viewState) { value in
|
||||
value.bindings.leaveRoomAlertItem != nil
|
||||
}
|
||||
|
||||
context.send(viewAction: .leaveRoom(roomIdentifier: mockRoomId))
|
||||
await context.nextViewState()
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertEqual(context.leaveRoomAlertItem?.roomId, mockRoomId)
|
||||
}
|
||||
|
||||
@ -93,9 +100,16 @@ class HomeScreenViewModelTests: XCTestCase {
|
||||
let room: RoomProxyMock = .init(with: .init(id: mockRoomId, displayName: "Some room"))
|
||||
room.leaveRoomClosure = { .failure(.failedLeavingRoom) }
|
||||
clientProxy.roomForIdentifierMocks[mockRoomId] = room
|
||||
|
||||
let deferred = deferFulfillment(context.$viewState) { value in
|
||||
value.bindings.alertInfo != nil
|
||||
}
|
||||
|
||||
context.send(viewAction: .confirmLeaveRoom(roomIdentifier: mockRoomId))
|
||||
let state = await context.nextViewState()
|
||||
XCTAssertNotNil(state?.bindings.alertInfo)
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertNotNil(context.alertInfo)
|
||||
}
|
||||
|
||||
func testLeaveRoomSuccess() async throws {
|
||||
|
@ -67,25 +67,26 @@ class InviteUsersScreenViewModelTests: XCTestCase {
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockAlice, .mockBob]
|
||||
setupWithRoomType(roomType: .room(roomProxy: RoomProxyMock(with: .init(displayName: "test", members: mockedMembers))))
|
||||
|
||||
let deferredState = deferFulfillment(viewModel.context.$viewState
|
||||
.map(\.membershipState)
|
||||
.map(\.isEmpty)
|
||||
.removeDuplicates()
|
||||
.collect(2).first(), message: "2 states should be published.")
|
||||
context.send(viewAction: .toggleUser(.mockAlice))
|
||||
|
||||
let states = try await deferredState.fulfill()
|
||||
XCTAssertEqual(states, [true, false])
|
||||
|
||||
let deferredAction = deferFulfillment(viewModel.actions.first(), message: "1 action should be published.")
|
||||
|
||||
Task.detached(priority: .low) {
|
||||
await self.context.send(viewAction: .proceed)
|
||||
let deferredState = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.isUserSelected(.mockAlice)
|
||||
}
|
||||
|
||||
let action = try await deferredAction.fulfill()
|
||||
context.send(viewAction: .toggleUser(.mockAlice))
|
||||
|
||||
guard case let .invite(members) = action else {
|
||||
try await deferredState.fulfill()
|
||||
|
||||
let deferredAction = deferFulfillment(viewModel.actions) { action in
|
||||
switch action {
|
||||
case .invite:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
context.send(viewAction: .proceed)
|
||||
|
||||
guard case let .invite(members) = try await deferredAction.fulfill() else {
|
||||
XCTFail("Sent action should be 'invite'")
|
||||
return
|
||||
}
|
||||
|
@ -56,7 +56,13 @@ class InvitesScreenViewModelTests: XCTestCase {
|
||||
}
|
||||
setupViewModel(roomSummaries: invites)
|
||||
|
||||
let deferred = deferFulfillment(viewModel.actions.first(), message: "1 action should be published.")
|
||||
let deferred = deferFulfillment(viewModel.actions) { action in
|
||||
switch action {
|
||||
case .openRoom:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
context.send(viewAction: .accept(.init(roomDetails: details, isUnread: false)))
|
||||
let action = try await deferred.fulfill()
|
||||
|
||||
|
@ -49,9 +49,12 @@ class NotificationSettingsEditScreenViewModelTests: XCTestCase {
|
||||
userSession: userSession,
|
||||
notificationSettingsProxy: notificationSettingsProxy)
|
||||
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState.map(\.defaultMode)
|
||||
.first(where: { !$0.isNil }))
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.defaultMode != nil
|
||||
}
|
||||
|
||||
viewModel.fetchInitialContent()
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
// `getDefaultRoomNotificationModeIsEncryptedIsOneToOne` must have been called twice (for encrypted and unencrypted group chats)
|
||||
@ -74,20 +77,19 @@ class NotificationSettingsEditScreenViewModelTests: XCTestCase {
|
||||
viewModel = NotificationSettingsEditScreenViewModel(chatType: .groupChat,
|
||||
userSession: userSession,
|
||||
notificationSettingsProxy: notificationSettingsProxy)
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState.map(\.defaultMode)
|
||||
.first(where: { !$0.isNil }))
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.defaultMode != nil
|
||||
}
|
||||
|
||||
viewModel.fetchInitialContent()
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
// Set mode to .allMessages
|
||||
let deferredViewState = deferFulfillment(context.$viewState
|
||||
.map(\.pendingMode)
|
||||
.removeDuplicates()
|
||||
.collect(3).first())
|
||||
context.send(viewAction: .setMode(.allMessages))
|
||||
let pendingModes = try await deferredViewState.fulfill()
|
||||
var deferredViewState = deferFulfillment(viewModel.context.$viewState, keyPath: \.pendingMode, transitionValues: [nil, .allMessages, nil])
|
||||
|
||||
XCTAssertEqual(pendingModes, [nil, .allMessages, nil])
|
||||
context.send(viewAction: .setMode(.allMessages))
|
||||
|
||||
try await deferredViewState.fulfill()
|
||||
|
||||
// `setDefaultRoomNotificationModeIsEncryptedIsOneToOneMode` must have been called twice (for encrypted and unencrypted group chats)
|
||||
let invocations = notificationSettingsProxy.setDefaultRoomNotificationModeIsEncryptedIsOneToOneModeReceivedInvocations
|
||||
@ -101,11 +103,11 @@ class NotificationSettingsEditScreenViewModelTests: XCTestCase {
|
||||
XCTAssertEqual(invocations[1].isOneToOne, false)
|
||||
XCTAssertEqual(invocations[1].mode, .allMessages)
|
||||
|
||||
// The default mode should be updated
|
||||
let deferredNewViewState = deferFulfillment(context.$viewState
|
||||
.map(\.defaultMode)
|
||||
.first(where: { $0 == .allMessages }))
|
||||
try await deferredNewViewState.fulfill()
|
||||
deferredViewState = deferFulfillment(viewModel.context.$viewState,
|
||||
keyPath: \.defaultMode,
|
||||
transitionValues: [.allMessages])
|
||||
|
||||
try await deferredViewState.fulfill()
|
||||
|
||||
XCTAssertEqual(context.viewState.defaultMode, .allMessages)
|
||||
XCTAssertNil(context.viewState.bindings.alertInfo)
|
||||
@ -115,20 +117,22 @@ class NotificationSettingsEditScreenViewModelTests: XCTestCase {
|
||||
viewModel = NotificationSettingsEditScreenViewModel(chatType: .groupChat,
|
||||
userSession: userSession,
|
||||
notificationSettingsProxy: notificationSettingsProxy)
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState.map(\.defaultMode)
|
||||
.first(where: { !$0.isNil }))
|
||||
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.defaultMode != nil
|
||||
}
|
||||
|
||||
viewModel.fetchInitialContent()
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
// Set mode to .allMessages
|
||||
let deferredViewState = deferFulfillment(context.$viewState
|
||||
.map(\.pendingMode)
|
||||
.removeDuplicates()
|
||||
.collect(3).first())
|
||||
context.send(viewAction: .setMode(.mentionsAndKeywordsOnly))
|
||||
let pendingModes = try await deferredViewState.fulfill()
|
||||
var deferredViewState = deferFulfillment(viewModel.context.$viewState,
|
||||
keyPath: \.pendingMode,
|
||||
transitionValues: [nil, .mentionsAndKeywordsOnly, nil])
|
||||
|
||||
XCTAssertEqual(pendingModes, [nil, .mentionsAndKeywordsOnly, nil])
|
||||
context.send(viewAction: .setMode(.mentionsAndKeywordsOnly))
|
||||
|
||||
try await deferredViewState.fulfill()
|
||||
|
||||
// `setDefaultRoomNotificationModeIsEncryptedIsOneToOneMode` must have been called twice (for encrypted and unencrypted group chats)
|
||||
let invocations = notificationSettingsProxy.setDefaultRoomNotificationModeIsEncryptedIsOneToOneModeReceivedInvocations
|
||||
@ -142,11 +146,11 @@ class NotificationSettingsEditScreenViewModelTests: XCTestCase {
|
||||
XCTAssertEqual(invocations[1].isOneToOne, false)
|
||||
XCTAssertEqual(invocations[1].mode, .mentionsAndKeywordsOnly)
|
||||
|
||||
// The default mode should be updated
|
||||
let deferredNewViewState = deferFulfillment(context.$viewState
|
||||
.map(\.defaultMode)
|
||||
.first(where: { $0 == .mentionsAndKeywordsOnly }))
|
||||
try await deferredNewViewState.fulfill()
|
||||
deferredViewState = deferFulfillment(viewModel.context.$viewState,
|
||||
keyPath: \.defaultMode,
|
||||
transitionValues: [.mentionsAndKeywordsOnly])
|
||||
|
||||
try await deferredViewState.fulfill()
|
||||
|
||||
XCTAssertEqual(context.viewState.defaultMode, .mentionsAndKeywordsOnly)
|
||||
XCTAssertNil(context.viewState.bindings.alertInfo)
|
||||
@ -158,20 +162,22 @@ class NotificationSettingsEditScreenViewModelTests: XCTestCase {
|
||||
viewModel = NotificationSettingsEditScreenViewModel(chatType: .oneToOneChat,
|
||||
userSession: userSession,
|
||||
notificationSettingsProxy: notificationSettingsProxy)
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState.map(\.defaultMode)
|
||||
.first(where: { !$0.isNil }))
|
||||
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.defaultMode != nil
|
||||
}
|
||||
|
||||
viewModel.fetchInitialContent()
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
// Set mode to .allMessages
|
||||
let deferredViewState = deferFulfillment(context.$viewState
|
||||
.map(\.pendingMode)
|
||||
.removeDuplicates()
|
||||
.collect(3).first())
|
||||
context.send(viewAction: .setMode(.allMessages))
|
||||
let pendingModes = try await deferredViewState.fulfill()
|
||||
let deferredViewState = deferFulfillment(viewModel.context.$viewState,
|
||||
keyPath: \.pendingMode,
|
||||
transitionValues: [nil, .allMessages, nil])
|
||||
|
||||
XCTAssertEqual(pendingModes, [nil, .allMessages, nil])
|
||||
context.send(viewAction: .setMode(.allMessages))
|
||||
|
||||
try await deferredViewState.fulfill()
|
||||
|
||||
// `setDefaultRoomNotificationModeIsEncryptedIsOneToOneMode` must have been called twice (for encrypted and unencrypted direct chats)
|
||||
let invocations = notificationSettingsProxy.setDefaultRoomNotificationModeIsEncryptedIsOneToOneModeReceivedInvocations
|
||||
@ -192,20 +198,23 @@ class NotificationSettingsEditScreenViewModelTests: XCTestCase {
|
||||
viewModel = NotificationSettingsEditScreenViewModel(chatType: .oneToOneChat,
|
||||
userSession: userSession,
|
||||
notificationSettingsProxy: notificationSettingsProxy)
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState.map(\.defaultMode)
|
||||
.first(where: { !$0.isNil }))
|
||||
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.defaultMode != nil
|
||||
}
|
||||
|
||||
viewModel.fetchInitialContent()
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
// Set mode to .allMessages
|
||||
let deferredViewState = deferFulfillment(context.$viewState
|
||||
.map(\.pendingMode)
|
||||
.removeDuplicates()
|
||||
.collect(3).first())
|
||||
context.send(viewAction: .setMode(.allMessages))
|
||||
let pendingModes = try await deferredViewState.fulfill()
|
||||
let deferredViewState = deferFulfillment(viewModel.context.$viewState,
|
||||
keyPath: \.pendingMode,
|
||||
transitionValues: [nil, .allMessages, nil])
|
||||
|
||||
context.send(viewAction: .setMode(.allMessages))
|
||||
|
||||
try await deferredViewState.fulfill()
|
||||
|
||||
XCTAssertEqual(pendingModes, [nil, .allMessages, nil])
|
||||
XCTAssertNotNil(context.viewState.bindings.alertInfo)
|
||||
}
|
||||
|
||||
@ -215,13 +224,20 @@ class NotificationSettingsEditScreenViewModelTests: XCTestCase {
|
||||
userSession: userSession,
|
||||
notificationSettingsProxy: notificationSettingsProxy)
|
||||
|
||||
let deferredActions = deferFulfillment(viewModel.actions.first())
|
||||
let deferredActions = deferFulfillment(viewModel.actions) { action in
|
||||
switch action {
|
||||
case .requestRoomNotificationSettingsPresentation:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
context.send(viewAction: .selectRoom(roomIdentifier: roomID))
|
||||
let sentActions = try await deferredActions.fulfill()
|
||||
|
||||
let sentAction = try await deferredActions.fulfill()
|
||||
|
||||
let expectedAction = NotificationSettingsEditScreenViewModelAction.requestRoomNotificationSettingsPresentation(roomID: roomID)
|
||||
guard case let .requestRoomNotificationSettingsPresentation(roomID: receivedRoomID) = sentActions, receivedRoomID == roomID else {
|
||||
XCTFail("Expected action \(expectedAction), but was \(sentActions)")
|
||||
guard case let .requestRoomNotificationSettingsPresentation(roomID: receivedRoomID) = sentAction, receivedRoomID == roomID else {
|
||||
XCTFail("Expected action \(expectedAction), but was \(sentAction)")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -71,9 +71,13 @@ class NotificationSettingsScreenViewModelTests: XCTestCase {
|
||||
return .mentionsAndKeywordsOnly
|
||||
}
|
||||
}
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState.map(\.settings)
|
||||
.first(where: { $0 != nil }))
|
||||
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.settings != nil
|
||||
}
|
||||
|
||||
notificationSettingsProxy.callbacks.send(.settingsDidChange)
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertEqual(notificationSettingsProxy.getDefaultRoomNotificationModeIsEncryptedIsOneToOneCallsCount, 4)
|
||||
@ -98,9 +102,12 @@ class NotificationSettingsScreenViewModelTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState.map(\.settings)
|
||||
.first(where: { $0 != nil }))
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.settings != nil
|
||||
}
|
||||
|
||||
notificationSettingsProxy.callbacks.send(.settingsDidChange)
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertEqual(context.viewState.settings?.groupChatsMode, .allMessages)
|
||||
@ -119,9 +126,12 @@ class NotificationSettingsScreenViewModelTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState.map(\.settings)
|
||||
.first(where: { $0 != nil }))
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.settings != nil
|
||||
}
|
||||
|
||||
notificationSettingsProxy.callbacks.send(.settingsDidChange)
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertEqual(context.viewState.settings?.directChatsMode, .allMessages)
|
||||
@ -141,23 +151,22 @@ class NotificationSettingsScreenViewModelTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState.map(\.settings)
|
||||
.first(where: { $0 != nil }))
|
||||
var deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.settings != nil
|
||||
}
|
||||
|
||||
notificationSettingsProxy.callbacks.send(.settingsDidChange)
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertEqual(context.viewState.settings?.directChatsMode, .allMessages)
|
||||
XCTAssertEqual(context.viewState.settings?.inconsistentSettings, [.init(chatType: .oneToOneChat, isEncrypted: false)])
|
||||
|
||||
let deferredState = deferFulfillment(viewModel.context.$viewState
|
||||
.map(\.fixingConfigurationMismatch)
|
||||
.removeDuplicates()
|
||||
.collect(3)
|
||||
.first())
|
||||
context.send(viewAction: .fixConfigurationMismatchTapped)
|
||||
let fixingStates = try await deferredState.fulfill()
|
||||
deferred = deferFulfillment(viewModel.context.$viewState, keyPath: \.fixingConfigurationMismatch, transitionValues: [false, true, false])
|
||||
|
||||
XCTAssertEqual(fixingStates, [false, true, false])
|
||||
context.send(viewAction: .fixConfigurationMismatchTapped)
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
// Ensure we only fix the invalid setting: unencrypted one-to-one chats should be set to `.allMessages` (to match encrypted one-to-one chats)
|
||||
XCTAssertEqual(notificationSettingsProxy.setDefaultRoomNotificationModeIsEncryptedIsOneToOneModeCallsCount, 1)
|
||||
@ -180,23 +189,30 @@ class NotificationSettingsScreenViewModelTests: XCTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState.map(\.settings)
|
||||
.first(where: { $0 != nil }))
|
||||
var deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.settings != nil
|
||||
}
|
||||
|
||||
notificationSettingsProxy.callbacks.send(.settingsDidChange)
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertEqual(context.viewState.settings?.directChatsMode, .allMessages)
|
||||
XCTAssertEqual(context.viewState.settings?.inconsistentSettings, [.init(chatType: .groupChat, isEncrypted: false), .init(chatType: .oneToOneChat, isEncrypted: false)])
|
||||
|
||||
let deferredState = deferFulfillment(viewModel.context.$viewState
|
||||
.map(\.fixingConfigurationMismatch)
|
||||
.removeDuplicates()
|
||||
.collect(3)
|
||||
.first())
|
||||
context.send(viewAction: .fixConfigurationMismatchTapped)
|
||||
let fixingStates = try await deferredState.fulfill()
|
||||
deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.fixingConfigurationMismatch == true
|
||||
}
|
||||
|
||||
XCTAssertEqual(fixingStates, [false, true, false])
|
||||
context.send(viewAction: .fixConfigurationMismatchTapped)
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.fixingConfigurationMismatch == false
|
||||
}
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
// All problems should be fixed
|
||||
XCTAssertEqual(notificationSettingsProxy.setDefaultRoomNotificationModeIsEncryptedIsOneToOneModeCallsCount, 2)
|
||||
@ -213,15 +229,23 @@ class NotificationSettingsScreenViewModelTests: XCTestCase {
|
||||
|
||||
func testToggleRoomMentionOff() async throws {
|
||||
notificationSettingsProxy.isRoomMentionEnabledReturnValue = true
|
||||
let deferredInitialFetch = deferFulfillment(viewModel.context.$viewState.map(\.settings)
|
||||
.first(where: { $0 != nil }))
|
||||
|
||||
let deferredState = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.settings != nil
|
||||
}
|
||||
|
||||
notificationSettingsProxy.callbacks.send(.settingsDidChange)
|
||||
try await deferredInitialFetch.fulfill()
|
||||
|
||||
try await deferredState.fulfill()
|
||||
|
||||
context.roomMentionsEnabled = false
|
||||
let deferred = deferFulfillment(notificationSettingsProxy.callbacks
|
||||
.first(where: { $0 == .settingsDidChange }))
|
||||
|
||||
let deferred = deferFulfillment(notificationSettingsProxy.callbacks) { callback in
|
||||
callback == .settingsDidChange
|
||||
}
|
||||
|
||||
context.send(viewAction: .roomMentionChanged)
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssert(notificationSettingsProxy.setRoomMentionEnabledEnabledCalled)
|
||||
@ -230,15 +254,22 @@ class NotificationSettingsScreenViewModelTests: XCTestCase {
|
||||
|
||||
func testToggleRoomMentionOn() async throws {
|
||||
notificationSettingsProxy.isRoomMentionEnabledReturnValue = false
|
||||
let deferredInitialFetch = deferFulfillment(viewModel.context.$viewState.map(\.settings)
|
||||
.first(where: { $0 != nil }))
|
||||
|
||||
let deferredInitialFetch = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.settings != nil
|
||||
}
|
||||
|
||||
viewModel.fetchInitialContent()
|
||||
try await deferredInitialFetch.fulfill()
|
||||
|
||||
context.roomMentionsEnabled = true
|
||||
let deferred = deferFulfillment(notificationSettingsProxy.callbacks
|
||||
.first(where: { $0 == .settingsDidChange }))
|
||||
|
||||
let deferred = deferFulfillment(notificationSettingsProxy.callbacks) { callback in
|
||||
callback == .settingsDidChange
|
||||
}
|
||||
|
||||
context.send(viewAction: .roomMentionChanged)
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssert(notificationSettingsProxy.setRoomMentionEnabledEnabledCalled)
|
||||
@ -248,34 +279,52 @@ class NotificationSettingsScreenViewModelTests: XCTestCase {
|
||||
func testToggleRoomMentionFailure() async throws {
|
||||
notificationSettingsProxy.setRoomMentionEnabledEnabledThrowableError = NotificationSettingsError.Generic(message: "error")
|
||||
notificationSettingsProxy.isRoomMentionEnabledReturnValue = false
|
||||
let deferredInitialFetch = deferFulfillment(viewModel.context.$viewState.map(\.settings)
|
||||
.first(where: { $0 != nil }))
|
||||
|
||||
let deferredInitialFetch = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.settings != nil
|
||||
}
|
||||
|
||||
viewModel.fetchInitialContent()
|
||||
|
||||
try await deferredInitialFetch.fulfill()
|
||||
|
||||
context.roomMentionsEnabled = true
|
||||
let deferred = deferFulfillment(context.$viewState.map(\.applyingChange)
|
||||
.removeDuplicates()
|
||||
.collect(3)
|
||||
.first())
|
||||
context.send(viewAction: .roomMentionChanged)
|
||||
let states = try await deferred.fulfill()
|
||||
|
||||
XCTAssertEqual(states, [false, true, false])
|
||||
var deferred = deferFulfillment(context.$viewState) { state in
|
||||
state.applyingChange == true
|
||||
}
|
||||
|
||||
context.send(viewAction: .roomMentionChanged)
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
deferred = deferFulfillment(context.$viewState) { state in
|
||||
state.applyingChange == false
|
||||
}
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertNotNil(context.alertInfo)
|
||||
}
|
||||
|
||||
func testToggleCallsOff() async throws {
|
||||
notificationSettingsProxy.isCallEnabledReturnValue = true
|
||||
let deferredInitialFetch = deferFulfillment(viewModel.context.$viewState.map(\.settings)
|
||||
.first(where: { $0 != nil }))
|
||||
|
||||
let deferredInitialFetch = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.settings != nil
|
||||
}
|
||||
|
||||
viewModel.fetchInitialContent()
|
||||
|
||||
try await deferredInitialFetch.fulfill()
|
||||
|
||||
context.callsEnabled = false
|
||||
let deferred = deferFulfillment(notificationSettingsProxy.callbacks
|
||||
.first(where: { $0 == .settingsDidChange }))
|
||||
let deferred = deferFulfillment(notificationSettingsProxy.callbacks) { callback in
|
||||
callback == .settingsDidChange
|
||||
}
|
||||
|
||||
context.send(viewAction: .callsChanged)
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssert(notificationSettingsProxy.setCallEnabledEnabledCalled)
|
||||
@ -284,15 +333,23 @@ class NotificationSettingsScreenViewModelTests: XCTestCase {
|
||||
|
||||
func testToggleCallsOn() async throws {
|
||||
notificationSettingsProxy.isCallEnabledReturnValue = false
|
||||
let deferredInitialFetch = deferFulfillment(viewModel.context.$viewState.map(\.settings)
|
||||
.first(where: { $0 != nil }))
|
||||
|
||||
let deferredInitialFetch = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.settings != nil
|
||||
}
|
||||
|
||||
viewModel.fetchInitialContent()
|
||||
|
||||
try await deferredInitialFetch.fulfill()
|
||||
|
||||
context.callsEnabled = true
|
||||
let deferred = deferFulfillment(notificationSettingsProxy.callbacks
|
||||
.first(where: { $0 == .settingsDidChange }))
|
||||
|
||||
let deferred = deferFulfillment(notificationSettingsProxy.callbacks) { callback in
|
||||
callback == .settingsDidChange
|
||||
}
|
||||
|
||||
context.send(viewAction: .callsChanged)
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssert(notificationSettingsProxy.setCallEnabledEnabledCalled)
|
||||
@ -302,20 +359,31 @@ class NotificationSettingsScreenViewModelTests: XCTestCase {
|
||||
func testToggleCallsFailure() async throws {
|
||||
notificationSettingsProxy.setCallEnabledEnabledThrowableError = NotificationSettingsError.Generic(message: "error")
|
||||
notificationSettingsProxy.isCallEnabledReturnValue = false
|
||||
let deferredInitialFetch = deferFulfillment(viewModel.context.$viewState.map(\.settings)
|
||||
.first(where: { $0 != nil }))
|
||||
|
||||
let deferredInitialFetch = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.settings != nil
|
||||
}
|
||||
|
||||
viewModel.fetchInitialContent()
|
||||
|
||||
try await deferredInitialFetch.fulfill()
|
||||
|
||||
context.callsEnabled = true
|
||||
let deferred = deferFulfillment(context.$viewState.map(\.applyingChange)
|
||||
.removeDuplicates()
|
||||
.collect(3)
|
||||
.first())
|
||||
context.send(viewAction: .callsChanged)
|
||||
let states = try await deferred.fulfill()
|
||||
|
||||
XCTAssertEqual(states, [false, true, false])
|
||||
var deferred = deferFulfillment(context.$viewState) { state in
|
||||
state.applyingChange == true
|
||||
}
|
||||
|
||||
context.send(viewAction: .callsChanged)
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
deferred = deferFulfillment(context.$viewState) { state in
|
||||
state.applyingChange == false
|
||||
}
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertNotNil(context.alertInfo)
|
||||
}
|
||||
}
|
||||
|
@ -31,15 +31,16 @@ class ReportContentScreenViewModelTests: XCTestCase {
|
||||
senderID: senderID,
|
||||
roomProxy: roomProxy)
|
||||
|
||||
let deferred = deferFulfillment(viewModel.actions.collect(2).first(), message: "2 actions should be published.")
|
||||
|
||||
// When reporting the content without ignoring the user.
|
||||
viewModel.state.bindings.reasonText = reportReason
|
||||
viewModel.state.bindings.ignoreUser = false
|
||||
viewModel.context.send(viewAction: .submit)
|
||||
|
||||
let actions = try await deferred.fulfill()
|
||||
XCTAssertEqual(actions, [.submitStarted, .submitFinished])
|
||||
let deferred = deferFulfillment(viewModel.actions) { action in
|
||||
action == .submitFinished
|
||||
}
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
// Then the content should be reported, but the user should not be included.
|
||||
XCTAssertEqual(roomProxy.reportContentReasonCallsCount, 1, "The content should always be reported.")
|
||||
@ -62,10 +63,13 @@ class ReportContentScreenViewModelTests: XCTestCase {
|
||||
viewModel.state.bindings.reasonText = reportReason
|
||||
viewModel.state.bindings.ignoreUser = true
|
||||
|
||||
let deferred = deferFulfillment(viewModel.actions.collect(2).first())
|
||||
viewModel.context.send(viewAction: .submit)
|
||||
let result = try await deferred.fulfill()
|
||||
XCTAssertEqual(result, [.submitStarted, .submitFinished])
|
||||
|
||||
let deferred = deferFulfillment(viewModel.actions) { action in
|
||||
action == .submitFinished
|
||||
}
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
// Then the content should be reported, and the user should be ignored.
|
||||
XCTAssertEqual(roomProxy.reportContentReasonCallsCount, 1, "The content should always be reported.")
|
||||
|
@ -95,9 +95,13 @@ class RoomDetailsEditScreenViewModelTests: XCTestCase {
|
||||
setupViewModel(accountOwner: .mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]),
|
||||
roomProxyConfiguration: .init(name: "Some room", displayName: "Some room"))
|
||||
|
||||
let deferred = deferFulfillment(viewModel.actions.first())
|
||||
let deferred = deferFulfillment(viewModel.actions) { action in
|
||||
action == .saveFinished
|
||||
}
|
||||
|
||||
context.name = "name"
|
||||
context.send(viewAction: .save)
|
||||
|
||||
let action = try await deferred.fulfill()
|
||||
XCTAssertEqual(action, .saveFinished)
|
||||
}
|
||||
|
@ -50,13 +50,15 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
mediaProvider: MockMediaProvider(),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()))
|
||||
let deferred = deferFulfillment(context.$viewState.collect(2).first())
|
||||
context.send(viewAction: .processTapLeave)
|
||||
let states = try await deferred.fulfill()
|
||||
let deferred = deferFulfillment(context.$viewState) { state in
|
||||
state.bindings.leaveRoomAlertItem != nil
|
||||
}
|
||||
|
||||
XCTAssertNil(states[0].bindings.leaveRoomAlertItem)
|
||||
XCTAssertEqual(states[1].bindings.leaveRoomAlertItem?.state, .public)
|
||||
XCTAssertEqual(states[1].bindings.leaveRoomAlertItem?.subtitle, L10n.leaveRoomAlertSubtitle)
|
||||
context.send(viewAction: .processTapLeave)
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertEqual(context.viewState.bindings.leaveRoomAlertItem?.state, .public)
|
||||
XCTAssertEqual(context.viewState.bindings.leaveRoomAlertItem?.subtitle, L10n.leaveRoomAlertSubtitle)
|
||||
}
|
||||
|
||||
func testLeaveRoomTappedWhenRoomNotPublic() async throws {
|
||||
@ -67,13 +69,16 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
mediaProvider: MockMediaProvider(),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()))
|
||||
let deferred = deferFulfillment(context.$viewState.collect(2).first())
|
||||
let deferred = deferFulfillment(context.$viewState) { state in
|
||||
state.bindings.leaveRoomAlertItem != nil
|
||||
}
|
||||
|
||||
context.send(viewAction: .processTapLeave)
|
||||
let states = try await deferred.fulfill()
|
||||
context.send(viewAction: .processTapLeave)
|
||||
XCTAssertNil(states[0].bindings.leaveRoomAlertItem)
|
||||
XCTAssertEqual(states[1].bindings.leaveRoomAlertItem?.state, .private)
|
||||
XCTAssertEqual(states[1].bindings.leaveRoomAlertItem?.subtitle, L10n.leaveRoomAlertPrivateSubtitle)
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertEqual(context.viewState.bindings.leaveRoomAlertItem?.state, .private)
|
||||
XCTAssertEqual(context.viewState.bindings.leaveRoomAlertItem?.subtitle, L10n.leaveRoomAlertPrivateSubtitle)
|
||||
}
|
||||
|
||||
func testLeaveRoomTappedWithLessThanTwoMembers() async {
|
||||
@ -82,24 +87,24 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
XCTAssertEqual(context.leaveRoomAlertItem?.subtitle, L10n.leaveRoomAlertEmptySubtitle)
|
||||
}
|
||||
|
||||
func testLeaveRoomSuccess() async {
|
||||
let expectation = expectation(description: #function)
|
||||
func testLeaveRoomSuccess() async throws {
|
||||
roomProxyMock.leaveRoomClosure = {
|
||||
.success(())
|
||||
}
|
||||
viewModel.actions
|
||||
.sink { action in
|
||||
|
||||
let deferred = deferFulfillment(viewModel.actions) { action in
|
||||
switch action {
|
||||
case .leftRoom:
|
||||
break
|
||||
return true
|
||||
default:
|
||||
XCTFail("leftRoom expected")
|
||||
return false
|
||||
}
|
||||
expectation.fulfill()
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
context.send(viewAction: .confirmLeave)
|
||||
await fulfillment(of: [expectation])
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertEqual(roomProxyMock.leaveRoomCallsCount, 1)
|
||||
}
|
||||
|
||||
@ -117,7 +122,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
XCTAssertNotNil(context.alertInfo)
|
||||
}
|
||||
|
||||
func testInitialDMDetailsState() async {
|
||||
func testInitialDMDetailsState() async throws {
|
||||
let recipient = RoomMemberProxyMock.mockDan
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers, activeMembersCount: mockedMembers.count))
|
||||
@ -126,7 +131,13 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
mediaProvider: MockMediaProvider(),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()))
|
||||
await context.nextViewState()
|
||||
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.dmRecipient != nil
|
||||
}
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
||||
}
|
||||
|
||||
@ -136,6 +147,7 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
try? await Task.sleep(for: .milliseconds(100))
|
||||
return .success(())
|
||||
}
|
||||
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockMe, recipient]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isEncrypted: true, members: mockedMembers, activeMembersCount: mockedMembers.count))
|
||||
viewModel = RoomDetailsScreenViewModel(accountUserID: "@owner:somewhere.com",
|
||||
@ -143,16 +155,23 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
mediaProvider: MockMediaProvider(),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()))
|
||||
await context.nextViewState()
|
||||
|
||||
var deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.dmRecipient != nil
|
||||
}
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
||||
|
||||
let deferred = deferFulfillment(context.$viewState.map(\.isProcessingIgnoreRequest)
|
||||
.removeDuplicates()
|
||||
.collect(3).first())
|
||||
deferred = deferFulfillment(viewModel.context.$viewState,
|
||||
keyPath: \.isProcessingIgnoreRequest,
|
||||
transitionValues: [false, true, false])
|
||||
|
||||
context.send(viewAction: .ignoreConfirmed)
|
||||
|
||||
let states = try await deferred.fulfill()
|
||||
XCTAssertEqual(states, [false, true, false])
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssert(context.viewState.dmRecipient?.isIgnored == true)
|
||||
}
|
||||
|
||||
@ -169,16 +188,23 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
mediaProvider: MockMediaProvider(),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()))
|
||||
await context.nextViewState()
|
||||
|
||||
var deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.dmRecipient != nil
|
||||
}
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
||||
|
||||
let deferred = deferFulfillment(context.$viewState.map(\.isProcessingIgnoreRequest)
|
||||
.removeDuplicates()
|
||||
.collect(3).first())
|
||||
deferred = deferFulfillment(viewModel.context.$viewState,
|
||||
keyPath: \.isProcessingIgnoreRequest,
|
||||
transitionValues: [false, true, false])
|
||||
|
||||
context.send(viewAction: .ignoreConfirmed)
|
||||
|
||||
let states = try await deferred.fulfill()
|
||||
XCTAssertEqual(states, [false, true, false])
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssert(context.viewState.dmRecipient?.isIgnored == false)
|
||||
XCTAssertNotNil(context.alertInfo)
|
||||
}
|
||||
@ -196,16 +222,23 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
mediaProvider: MockMediaProvider(),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()))
|
||||
await context.nextViewState()
|
||||
|
||||
var deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.dmRecipient != nil
|
||||
}
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
||||
|
||||
let deferred = deferFulfillment(context.$viewState.map(\.isProcessingIgnoreRequest)
|
||||
.removeDuplicates()
|
||||
.collect(3).first())
|
||||
deferred = deferFulfillment(viewModel.context.$viewState,
|
||||
keyPath: \.isProcessingIgnoreRequest,
|
||||
transitionValues: [false, true, false])
|
||||
|
||||
context.send(viewAction: .unignoreConfirmed)
|
||||
let states = try await deferred.fulfill()
|
||||
XCTAssertEqual(states, [false, true, false])
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssert(context.viewState.dmRecipient?.isIgnored == false)
|
||||
}
|
||||
|
||||
@ -222,16 +255,23 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
mediaProvider: MockMediaProvider(),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
notificationSettingsProxy: NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration()))
|
||||
await context.nextViewState()
|
||||
|
||||
var deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.dmRecipient != nil
|
||||
}
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertEqual(context.viewState.dmRecipient, RoomMemberDetails(withProxy: recipient))
|
||||
|
||||
let deferred = deferFulfillment(context.$viewState.map(\.isProcessingIgnoreRequest)
|
||||
.removeDuplicates()
|
||||
.collect(3).first())
|
||||
deferred = deferFulfillment(viewModel.context.$viewState,
|
||||
keyPath: \.isProcessingIgnoreRequest,
|
||||
transitionValues: [false, true, false])
|
||||
|
||||
context.send(viewAction: .unignoreConfirmed)
|
||||
let states = try await deferred.fulfill()
|
||||
XCTAssertEqual(states, [false, true, false])
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssert(context.viewState.dmRecipient?.isIgnored == true)
|
||||
XCTAssertNotNil(context.alertInfo)
|
||||
}
|
||||
@ -379,10 +419,19 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
mediaProvider: MockMediaProvider(),
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController,
|
||||
notificationSettingsProxy: notificationSettingsProxyMock)
|
||||
let deferred = deferFulfillment(context.$viewState.map(\.notificationSettingsState)
|
||||
.filter(\.isError)
|
||||
.first())
|
||||
|
||||
var deferred = deferFulfillment(context.$viewState) { state in
|
||||
state.notificationSettingsState.isError
|
||||
}
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
notificationSettingsProxyMock.callbacks.send(.settingsDidChange)
|
||||
|
||||
deferred = deferFulfillment(context.$viewState) { state in
|
||||
state.notificationSettingsState.isError
|
||||
}
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
let expectedAlertInfo = AlertInfo(id: RoomDetailsScreenErrorType.alert,
|
||||
@ -395,7 +444,11 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
|
||||
func testNotificationDefaultMode() async throws {
|
||||
notificationSettingsProxyMock.getNotificationSettingsRoomIdIsEncryptedIsOneToOneReturnValue = RoomNotificationSettingsProxyMock(with: .init(mode: .allMessages, isDefault: true))
|
||||
let deferred = deferFulfillment(context.$viewState.map(\.notificationSettingsState).first(where: \.isLoaded))
|
||||
|
||||
let deferred = deferFulfillment(context.$viewState) { state in
|
||||
state.notificationSettingsState.isLoaded
|
||||
}
|
||||
|
||||
notificationSettingsProxyMock.callbacks.send(.settingsDidChange)
|
||||
try await deferred.fulfill()
|
||||
|
||||
@ -404,7 +457,11 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
|
||||
func testNotificationCustomMode() async throws {
|
||||
notificationSettingsProxyMock.getNotificationSettingsRoomIdIsEncryptedIsOneToOneReturnValue = RoomNotificationSettingsProxyMock(with: .init(mode: .allMessages, isDefault: false))
|
||||
let deferred = deferFulfillment(context.$viewState.map(\.notificationSettingsState).first(where: \.isCustom))
|
||||
|
||||
let deferred = deferFulfillment(context.$viewState) { state in
|
||||
state.notificationSettingsState.isCustom
|
||||
}
|
||||
|
||||
notificationSettingsProxyMock.callbacks.send(.settingsDidChange)
|
||||
try await deferred.fulfill()
|
||||
|
||||
@ -413,7 +470,11 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
|
||||
func testNotificationRoomMuted() async throws {
|
||||
notificationSettingsProxyMock.getNotificationSettingsRoomIdIsEncryptedIsOneToOneReturnValue = RoomNotificationSettingsProxyMock(with: .init(mode: .mute, isDefault: false))
|
||||
let deferred = deferFulfillment(context.$viewState.map(\.notificationSettingsState).first(where: \.isLoaded))
|
||||
|
||||
let deferred = deferFulfillment(context.$viewState) { state in
|
||||
state.notificationSettingsState.isLoaded
|
||||
}
|
||||
|
||||
notificationSettingsProxyMock.callbacks.send(.settingsDidChange)
|
||||
try await deferred.fulfill()
|
||||
|
||||
@ -425,7 +486,11 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
|
||||
func testNotificationRoomNotMuted() async throws {
|
||||
notificationSettingsProxyMock.getNotificationSettingsRoomIdIsEncryptedIsOneToOneReturnValue = RoomNotificationSettingsProxyMock(with: .init(mode: .mentionsAndKeywordsOnly, isDefault: false))
|
||||
let deferred = deferFulfillment(context.$viewState.map(\.notificationSettingsState).first(where: \.isLoaded))
|
||||
|
||||
let deferred = deferFulfillment(context.$viewState) { state in
|
||||
state.notificationSettingsState.isLoaded
|
||||
}
|
||||
|
||||
notificationSettingsProxyMock.callbacks.send(.settingsDidChange)
|
||||
try await deferred.fulfill()
|
||||
|
||||
@ -497,7 +562,10 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
XCTAssertFalse(context.viewState.isProcessingMuteToggleAction)
|
||||
|
||||
do {
|
||||
let deferred = deferFulfillment(context.$viewState.first())
|
||||
let deferred = deferFulfillment(context.$viewState) { state in
|
||||
state.notificationSettingsState.isLoaded
|
||||
}
|
||||
|
||||
notificationSettingsProxyMock.callbacks.send(.settingsDidChange)
|
||||
try await deferred.fulfill()
|
||||
}
|
||||
@ -524,7 +592,10 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
XCTAssertFalse(context.viewState.isProcessingMuteToggleAction)
|
||||
|
||||
do {
|
||||
let deferred = deferFulfillment(context.$viewState.first())
|
||||
let deferred = deferFulfillment(context.$viewState) { state in
|
||||
state.notificationSettingsState.isLoaded
|
||||
}
|
||||
|
||||
notificationSettingsProxyMock.callbacks.send(.settingsDidChange)
|
||||
try await deferred.fulfill()
|
||||
}
|
||||
|
@ -119,17 +119,22 @@ class RoomFlowCoordinatorTests: XCTestCase {
|
||||
}
|
||||
|
||||
private func process(route: AppRoute, expectedActions: [RoomFlowCoordinatorAction]) async throws {
|
||||
let deferred = deferFulfillment(roomFlowCoordinator.actions.collect(expectedActions.count).first(),
|
||||
message: "The expected number of actions should be published.")
|
||||
|
||||
Task {
|
||||
await Task.yield()
|
||||
self.roomFlowCoordinator.handleAppRoute(route, animated: true)
|
||||
guard !expectedActions.isEmpty else {
|
||||
return
|
||||
}
|
||||
|
||||
if !expectedActions.isEmpty {
|
||||
let actions = try await deferred.fulfill()
|
||||
XCTAssertEqual(actions, expectedActions)
|
||||
var fulfillments = [DeferredFulfillment<RoomFlowCoordinatorAction>]()
|
||||
|
||||
for expectedAction in expectedActions {
|
||||
fulfillments.append(deferFulfillment(roomFlowCoordinator.actions) { action in
|
||||
action == expectedAction
|
||||
})
|
||||
}
|
||||
|
||||
roomFlowCoordinator.handleAppRoute(route, animated: true)
|
||||
|
||||
for fulfillment in fulfillments {
|
||||
try await fulfillment.fulfill()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -55,16 +55,17 @@ class RoomMemberDetailsViewModelTests: XCTestCase {
|
||||
context.send(viewAction: .showIgnoreAlert)
|
||||
XCTAssertEqual(context.ignoreUserAlert, .init(action: .ignore))
|
||||
|
||||
let deferred = deferFulfillment(context.$viewState.map(\.isProcessingIgnoreRequest)
|
||||
.removeDuplicates()
|
||||
.collect(3).first())
|
||||
|
||||
context.send(viewAction: .ignoreConfirmed)
|
||||
let states = try await deferred.fulfill()
|
||||
XCTAssertEqual(states, [false, true, false])
|
||||
|
||||
let deferred = deferFulfillment(context.$viewState) { state in
|
||||
state.details.isIgnored
|
||||
}
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertFalse(context.viewState.isProcessingIgnoreRequest)
|
||||
XCTAssertTrue(context.viewState.details.isIgnored)
|
||||
try await Task.sleep(for: .microseconds(100))
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
XCTAssertTrue(roomProxyMock.updateMembersCalled)
|
||||
}
|
||||
|
||||
@ -81,16 +82,17 @@ class RoomMemberDetailsViewModelTests: XCTestCase {
|
||||
context.send(viewAction: .showIgnoreAlert)
|
||||
XCTAssertEqual(context.ignoreUserAlert, .init(action: .ignore))
|
||||
|
||||
let deferred = deferFulfillment(context.$viewState.map(\.isProcessingIgnoreRequest)
|
||||
.removeDuplicates()
|
||||
.collect(3).first())
|
||||
|
||||
context.send(viewAction: .ignoreConfirmed)
|
||||
let states = try await deferred.fulfill()
|
||||
XCTAssertEqual(states, [false, true, false])
|
||||
|
||||
let deferred = deferFulfillment(context.$viewState) { state in
|
||||
state.bindings.alertInfo != nil
|
||||
}
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertNotNil(context.alertInfo)
|
||||
XCTAssertFalse(context.viewState.details.isIgnored)
|
||||
try await Task.sleep(for: .microseconds(100))
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
XCTAssertFalse(roomProxyMock.updateMembersCalled)
|
||||
}
|
||||
|
||||
@ -108,15 +110,16 @@ class RoomMemberDetailsViewModelTests: XCTestCase {
|
||||
context.send(viewAction: .showUnignoreAlert)
|
||||
XCTAssertEqual(context.ignoreUserAlert, .init(action: .unignore))
|
||||
|
||||
let deferred = deferFulfillment(context.$viewState.map(\.isProcessingIgnoreRequest)
|
||||
.removeDuplicates()
|
||||
.collect(3).first())
|
||||
|
||||
context.send(viewAction: .unignoreConfirmed)
|
||||
let states = try await deferred.fulfill()
|
||||
XCTAssertEqual(states, [false, true, false])
|
||||
|
||||
let deferred = deferFulfillment(context.$viewState) { state in
|
||||
state.details.isIgnored == false
|
||||
}
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertFalse(context.viewState.details.isIgnored)
|
||||
try await Task.sleep(for: .microseconds(100))
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
XCTAssertTrue(roomProxyMock.updateMembersCalled)
|
||||
}
|
||||
|
||||
@ -135,16 +138,17 @@ class RoomMemberDetailsViewModelTests: XCTestCase {
|
||||
context.send(viewAction: .showUnignoreAlert)
|
||||
XCTAssertEqual(context.ignoreUserAlert, .init(action: .unignore))
|
||||
|
||||
let deferred = deferFulfillment(context.$viewState.map(\.isProcessingIgnoreRequest)
|
||||
.removeDuplicates()
|
||||
.collect(3).first())
|
||||
|
||||
context.send(viewAction: .unignoreConfirmed)
|
||||
let states = try await deferred.fulfill()
|
||||
XCTAssertEqual(states, [false, true, false])
|
||||
|
||||
let deferred = deferFulfillment(context.$viewState) { state in
|
||||
state.bindings.alertInfo != nil
|
||||
}
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertTrue(context.viewState.details.isIgnored)
|
||||
XCTAssertNotNil(context.alertInfo)
|
||||
try await Task.sleep(for: .microseconds(100))
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
XCTAssertFalse(roomProxyMock.updateMembersCalled)
|
||||
}
|
||||
|
||||
|
@ -26,49 +26,87 @@ class RoomMembersListScreenViewModelTests: XCTestCase {
|
||||
viewModel.context
|
||||
}
|
||||
|
||||
func testJoinedMembers() async {
|
||||
func testJoinedMembers() async throws {
|
||||
setup(with: [.mockAlice, .mockBob])
|
||||
await context.nextViewState()
|
||||
|
||||
let deferred = deferFulfillment(context.$viewState) { state in
|
||||
state.visibleJoinedMembers.count == 2
|
||||
}
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertEqual(viewModel.state.joinedMembersCount, 2)
|
||||
XCTAssertEqual(viewModel.state.visibleJoinedMembers.count, 2)
|
||||
}
|
||||
|
||||
func testSearch() async {
|
||||
func testSearch() async throws {
|
||||
setup(with: [.mockAlice, .mockBob])
|
||||
await context.nextViewState()
|
||||
|
||||
let deferred = deferFulfillment(context.$viewState) { state in
|
||||
state.visibleJoinedMembers.count == 1
|
||||
}
|
||||
|
||||
context.searchQuery = "alice"
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertEqual(viewModel.state.joinedMembersCount, 2)
|
||||
XCTAssertEqual(viewModel.state.visibleJoinedMembers.count, 1)
|
||||
}
|
||||
|
||||
func testEmptySearch() async {
|
||||
func testEmptySearch() async throws {
|
||||
setup(with: [.mockAlice, .mockBob])
|
||||
await context.nextViewState()
|
||||
context.searchQuery = "WWW"
|
||||
|
||||
let deferred = deferFulfillment(context.$viewState) { state in
|
||||
state.joinedMembersCount == 2
|
||||
}
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertEqual(viewModel.state.joinedMembersCount, 2)
|
||||
XCTAssertEqual(viewModel.state.visibleJoinedMembers.count, 0)
|
||||
}
|
||||
|
||||
func testJoinedAndInvitedMembers() async {
|
||||
func testJoinedAndInvitedMembers() async throws {
|
||||
setup(with: [.mockInvitedAlice, .mockBob])
|
||||
await context.nextViewState()
|
||||
|
||||
let deferred = deferFulfillment(context.$viewState) { state in
|
||||
state.visibleInvitedMembers.count == 1
|
||||
}
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertEqual(viewModel.state.joinedMembersCount, 1)
|
||||
XCTAssertEqual(viewModel.state.visibleInvitedMembers.count, 1)
|
||||
XCTAssertEqual(viewModel.state.visibleJoinedMembers.count, 1)
|
||||
}
|
||||
|
||||
func testInvitedMembers() async {
|
||||
func testInvitedMembers() async throws {
|
||||
setup(with: [.mockInvitedAlice])
|
||||
await context.nextViewState()
|
||||
|
||||
let deferred = deferFulfillment(context.$viewState) { state in
|
||||
state.visibleInvitedMembers.count == 1
|
||||
}
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertEqual(viewModel.state.joinedMembersCount, 0)
|
||||
XCTAssertEqual(viewModel.state.visibleInvitedMembers.count, 1)
|
||||
XCTAssertEqual(viewModel.state.visibleJoinedMembers.count, 0)
|
||||
}
|
||||
|
||||
func testSearchInvitedMembers() async {
|
||||
func testSearchInvitedMembers() async throws {
|
||||
setup(with: [.mockInvitedAlice])
|
||||
|
||||
context.searchQuery = "alice"
|
||||
await context.nextViewState()
|
||||
|
||||
let deferred = deferFulfillment(context.$viewState) { state in
|
||||
state.visibleInvitedMembers.count == 1
|
||||
}
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertEqual(viewModel.state.joinedMembersCount, 0)
|
||||
XCTAssertEqual(viewModel.state.visibleInvitedMembers.count, 1)
|
||||
XCTAssertEqual(viewModel.state.visibleJoinedMembers.count, 0)
|
||||
|
@ -22,99 +22,118 @@ import XCTest
|
||||
|
||||
@MainActor
|
||||
class RoomNotificationSettingsScreenViewModelTests: XCTestCase {
|
||||
var viewModel: RoomNotificationSettingsScreenViewModel!
|
||||
var roomProxyMock: RoomProxyMock!
|
||||
var notificationSettingsProxyMock: NotificationSettingsProxyMock!
|
||||
var context: RoomNotificationSettingsScreenViewModelType.Context { viewModel.context }
|
||||
var cancellables = Set<AnyCancellable>()
|
||||
|
||||
override func setUpWithError() throws {
|
||||
cancellables.removeAll()
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", joinedMembersCount: 0))
|
||||
notificationSettingsProxyMock = NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration())
|
||||
viewModel = RoomNotificationSettingsScreenViewModel(notificationSettingsProxy: notificationSettingsProxyMock,
|
||||
roomProxy: roomProxyMock,
|
||||
displayAsUserDefinedRoomSettings: false)
|
||||
}
|
||||
|
||||
func testInitialStateDefaultMode() async throws {
|
||||
let roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", joinedMembersCount: 0))
|
||||
let notificationSettingsProxyMock = NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration())
|
||||
|
||||
notificationSettingsProxyMock.getNotificationSettingsRoomIdIsEncryptedIsOneToOneReturnValue = RoomNotificationSettingsProxyMock(with: .init(mode: .mentionsAndKeywordsOnly, isDefault: true))
|
||||
viewModel = RoomNotificationSettingsScreenViewModel(notificationSettingsProxy: notificationSettingsProxyMock,
|
||||
|
||||
let viewModel = RoomNotificationSettingsScreenViewModel(notificationSettingsProxy: notificationSettingsProxyMock,
|
||||
roomProxy: roomProxyMock,
|
||||
displayAsUserDefinedRoomSettings: false)
|
||||
let deferred = deferFulfillment(context.$viewState.map(\.notificationSettingsState)
|
||||
.first(where: \.isLoaded))
|
||||
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.notificationSettingsState.isLoaded
|
||||
}
|
||||
|
||||
notificationSettingsProxyMock.callbacks.send(.settingsDidChange)
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertFalse(context.allowCustomSetting)
|
||||
XCTAssertFalse(viewModel.context.allowCustomSetting)
|
||||
}
|
||||
|
||||
func testInitialStateCustomMode() async throws {
|
||||
notificationSettingsProxyMock.getNotificationSettingsRoomIdIsEncryptedIsOneToOneReturnValue = RoomNotificationSettingsProxyMock(with: .init(mode: .mentionsAndKeywordsOnly, isDefault: false))
|
||||
viewModel = RoomNotificationSettingsScreenViewModel(notificationSettingsProxy: notificationSettingsProxyMock,
|
||||
let viewModel = RoomNotificationSettingsScreenViewModel(notificationSettingsProxy: notificationSettingsProxyMock,
|
||||
roomProxy: roomProxyMock,
|
||||
displayAsUserDefinedRoomSettings: false)
|
||||
let deferred = deferFulfillment(context.$viewState.map(\.notificationSettingsState)
|
||||
.first(where: \.isLoaded))
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.notificationSettingsState.isLoaded
|
||||
}
|
||||
|
||||
notificationSettingsProxyMock.callbacks.send(.settingsDidChange)
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertTrue(context.allowCustomSetting)
|
||||
XCTAssertTrue(viewModel.context.allowCustomSetting)
|
||||
}
|
||||
|
||||
func testInitialStateFailure() async throws {
|
||||
notificationSettingsProxyMock.getNotificationSettingsRoomIdIsEncryptedIsOneToOneThrowableError = NotificationSettingsError.Generic(message: "error")
|
||||
viewModel = RoomNotificationSettingsScreenViewModel(notificationSettingsProxy: notificationSettingsProxyMock,
|
||||
let viewModel = RoomNotificationSettingsScreenViewModel(notificationSettingsProxy: notificationSettingsProxyMock,
|
||||
roomProxy: roomProxyMock,
|
||||
displayAsUserDefinedRoomSettings: false)
|
||||
let deferred = deferFulfillment(context.$viewState.map(\.notificationSettingsState)
|
||||
.first(where: \.isError))
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.notificationSettingsState.isError
|
||||
}
|
||||
|
||||
notificationSettingsProxyMock.callbacks.send(.settingsDidChange)
|
||||
try await deferred.fulfill()
|
||||
|
||||
let expectedAlertInfo = AlertInfo(id: RoomNotificationSettingsScreenErrorType.loadingSettingsFailed,
|
||||
title: L10n.commonError,
|
||||
message: L10n.screenRoomNotificationSettingsErrorLoadingSettings)
|
||||
XCTAssertEqual(context.viewState.bindings.alertInfo?.id, expectedAlertInfo.id)
|
||||
XCTAssertEqual(context.viewState.bindings.alertInfo?.title, expectedAlertInfo.title)
|
||||
XCTAssertEqual(context.viewState.bindings.alertInfo?.message, expectedAlertInfo.message)
|
||||
XCTAssertEqual(viewModel.context.viewState.bindings.alertInfo?.id, expectedAlertInfo.id)
|
||||
XCTAssertEqual(viewModel.context.viewState.bindings.alertInfo?.title, expectedAlertInfo.title)
|
||||
XCTAssertEqual(viewModel.context.viewState.bindings.alertInfo?.message, expectedAlertInfo.message)
|
||||
}
|
||||
|
||||
func testToggleAllCustomSettingOff() async throws {
|
||||
notificationSettingsProxyMock.getNotificationSettingsRoomIdIsEncryptedIsOneToOneReturnValue = RoomNotificationSettingsProxyMock(with: .init(mode: .mentionsAndKeywordsOnly, isDefault: false))
|
||||
viewModel = RoomNotificationSettingsScreenViewModel(notificationSettingsProxy: notificationSettingsProxyMock,
|
||||
let viewModel = RoomNotificationSettingsScreenViewModel(notificationSettingsProxy: notificationSettingsProxyMock,
|
||||
roomProxy: roomProxyMock,
|
||||
displayAsUserDefinedRoomSettings: false)
|
||||
let deferred = deferFulfillment(context.$viewState.map(\.notificationSettingsState)
|
||||
.first(where: \.isLoaded))
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.notificationSettingsState.isLoaded
|
||||
}
|
||||
|
||||
notificationSettingsProxyMock.callbacks.send(.settingsDidChange)
|
||||
try await deferred.fulfill()
|
||||
|
||||
let deferredIsRestoringDefaultSettings = deferFulfillment(context.$viewState.map(\.isRestoringDefaultSetting)
|
||||
.removeDuplicates()
|
||||
.collect(3).first())
|
||||
let deferredIsRestoringDefaultSettings = deferFulfillment(viewModel.context.$viewState,
|
||||
keyPath: \.isRestoringDefaultSetting,
|
||||
transitionValues: [false, true, false])
|
||||
|
||||
viewModel.state.bindings.allowCustomSetting = false
|
||||
context.send(viewAction: .changedAllowCustomSettings)
|
||||
let states = try await deferredIsRestoringDefaultSettings.fulfill()
|
||||
XCTAssertEqual(states, [false, true, false])
|
||||
viewModel.context.send(viewAction: .changedAllowCustomSettings)
|
||||
|
||||
try await deferredIsRestoringDefaultSettings.fulfill()
|
||||
|
||||
XCTAssertEqual(notificationSettingsProxyMock.restoreDefaultNotificationModeRoomIdReceivedRoomId, roomProxyMock.id)
|
||||
XCTAssertEqual(notificationSettingsProxyMock.restoreDefaultNotificationModeRoomIdCallsCount, 1)
|
||||
}
|
||||
|
||||
func testToggleAllCustomSettingOffOn() async throws {
|
||||
let notificationSettingsProxyMock = NotificationSettingsProxyMock(with: NotificationSettingsProxyMockConfiguration())
|
||||
notificationSettingsProxyMock.getNotificationSettingsRoomIdIsEncryptedIsOneToOneReturnValue = RoomNotificationSettingsProxyMock(with: .init(mode: .mentionsAndKeywordsOnly, isDefault: true))
|
||||
viewModel = RoomNotificationSettingsScreenViewModel(notificationSettingsProxy: notificationSettingsProxyMock,
|
||||
let viewModel = RoomNotificationSettingsScreenViewModel(notificationSettingsProxy: notificationSettingsProxyMock,
|
||||
roomProxy: roomProxyMock,
|
||||
displayAsUserDefinedRoomSettings: false)
|
||||
var deferred = deferFulfillment(context.$viewState.map(\.notificationSettingsState).first(where: \.isLoaded))
|
||||
|
||||
var deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.notificationSettingsState.isLoaded
|
||||
}
|
||||
|
||||
notificationSettingsProxyMock.callbacks.send(.settingsDidChange)
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
deferred = deferFulfillment(context.$viewState.map(\.notificationSettingsState).first(where: \.isLoaded))
|
||||
deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.notificationSettingsState.isLoaded
|
||||
}
|
||||
|
||||
viewModel.state.bindings.allowCustomSetting = true
|
||||
context.send(viewAction: .changedAllowCustomSettings)
|
||||
viewModel.context.send(viewAction: .changedAllowCustomSettings)
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertEqual(notificationSettingsProxyMock.setNotificationModeRoomIdModeReceivedArguments?.0, roomProxyMock.id)
|
||||
@ -124,17 +143,25 @@ class RoomNotificationSettingsScreenViewModelTests: XCTestCase {
|
||||
|
||||
func testSetCustomMode() async throws {
|
||||
notificationSettingsProxyMock.getNotificationSettingsRoomIdIsEncryptedIsOneToOneReturnValue = RoomNotificationSettingsProxyMock(with: .init(mode: .mentionsAndKeywordsOnly, isDefault: false))
|
||||
viewModel = RoomNotificationSettingsScreenViewModel(notificationSettingsProxy: notificationSettingsProxyMock,
|
||||
let viewModel = RoomNotificationSettingsScreenViewModel(notificationSettingsProxy: notificationSettingsProxyMock,
|
||||
roomProxy: roomProxyMock,
|
||||
displayAsUserDefinedRoomSettings: false)
|
||||
let deferredState = deferFulfillment(context.$viewState.map(\.notificationSettingsState).first(where: \.isLoaded))
|
||||
|
||||
let deferredState = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.notificationSettingsState.isLoaded
|
||||
}
|
||||
|
||||
notificationSettingsProxyMock.callbacks.send(.settingsDidChange)
|
||||
try await deferredState.fulfill()
|
||||
|
||||
do {
|
||||
let deferredViewState = deferFulfillment(context.$viewState.collect(2).first())
|
||||
context.send(viewAction: .setCustomMode(.allMessages))
|
||||
try await deferredViewState.fulfill()
|
||||
viewModel.context.send(viewAction: .setCustomMode(.allMessages))
|
||||
|
||||
let deferredState = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.pendingCustomMode == nil
|
||||
}
|
||||
|
||||
try await deferredState.fulfill()
|
||||
|
||||
XCTAssertEqual(notificationSettingsProxyMock.setNotificationModeRoomIdModeReceivedArguments?.0, roomProxyMock.id)
|
||||
XCTAssertEqual(notificationSettingsProxyMock.setNotificationModeRoomIdModeReceivedArguments?.1, .allMessages)
|
||||
@ -142,9 +169,13 @@ class RoomNotificationSettingsScreenViewModelTests: XCTestCase {
|
||||
}
|
||||
|
||||
do {
|
||||
let deferredViewState = deferFulfillment(context.$viewState.collect(2).first())
|
||||
context.send(viewAction: .setCustomMode(.mute))
|
||||
try await deferredViewState.fulfill()
|
||||
viewModel.context.send(viewAction: .setCustomMode(.mute))
|
||||
|
||||
let deferredState = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.pendingCustomMode == nil
|
||||
}
|
||||
|
||||
try await deferredState.fulfill()
|
||||
|
||||
XCTAssertEqual(notificationSettingsProxyMock.setNotificationModeRoomIdModeReceivedArguments?.0, roomProxyMock.id)
|
||||
XCTAssertEqual(notificationSettingsProxyMock.setNotificationModeRoomIdModeReceivedArguments?.1, .mute)
|
||||
@ -152,9 +183,13 @@ class RoomNotificationSettingsScreenViewModelTests: XCTestCase {
|
||||
}
|
||||
|
||||
do {
|
||||
let deferredViewState = deferFulfillment(context.$viewState.collect(2).first())
|
||||
context.send(viewAction: .setCustomMode(.mentionsAndKeywordsOnly))
|
||||
try await deferredViewState.fulfill()
|
||||
viewModel.context.send(viewAction: .setCustomMode(.mentionsAndKeywordsOnly))
|
||||
|
||||
let deferredState = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.pendingCustomMode == nil
|
||||
}
|
||||
|
||||
try await deferredState.fulfill()
|
||||
|
||||
XCTAssertEqual(notificationSettingsProxyMock.setNotificationModeRoomIdModeReceivedArguments?.0, roomProxyMock.id)
|
||||
XCTAssertEqual(notificationSettingsProxyMock.setNotificationModeRoomIdModeReceivedArguments?.1, .mentionsAndKeywordsOnly)
|
||||
@ -164,12 +199,15 @@ class RoomNotificationSettingsScreenViewModelTests: XCTestCase {
|
||||
|
||||
func testDeleteCustomSettingTapped() async throws {
|
||||
notificationSettingsProxyMock.getNotificationSettingsRoomIdIsEncryptedIsOneToOneReturnValue = RoomNotificationSettingsProxyMock(with: .init(mode: .mentionsAndKeywordsOnly, isDefault: false))
|
||||
viewModel = RoomNotificationSettingsScreenViewModel(notificationSettingsProxy: notificationSettingsProxyMock,
|
||||
let viewModel = RoomNotificationSettingsScreenViewModel(notificationSettingsProxy: notificationSettingsProxyMock,
|
||||
roomProxy: roomProxyMock,
|
||||
displayAsUserDefinedRoomSettings: true)
|
||||
let deferredState = deferFulfillment(context.$viewState.map(\.notificationSettingsState).first(where: \.isLoaded))
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.notificationSettingsState.isLoaded
|
||||
}
|
||||
|
||||
notificationSettingsProxyMock.callbacks.send(.settingsDidChange)
|
||||
try await deferredState.fulfill()
|
||||
try await deferred.fulfill()
|
||||
|
||||
var actionSent: RoomNotificationSettingsScreenViewModelAction?
|
||||
viewModel.actions
|
||||
@ -178,33 +216,35 @@ class RoomNotificationSettingsScreenViewModelTests: XCTestCase {
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
let deferredViewState = deferFulfillment(context.$viewState
|
||||
.map(\.deletingCustomSetting)
|
||||
.removeDuplicates()
|
||||
.collect(3).first())
|
||||
context.send(viewAction: .deleteCustomSettingTapped)
|
||||
let states = try await deferredViewState.fulfill()
|
||||
let deferredViewState = deferFulfillment(viewModel.context.$viewState,
|
||||
keyPath: \.deletingCustomSetting,
|
||||
transitionValues: [false, true, false])
|
||||
|
||||
viewModel.context.send(viewAction: .deleteCustomSettingTapped)
|
||||
|
||||
try await deferredViewState.fulfill()
|
||||
|
||||
// `deletingCustomSetting` must be set to `true` when deleting, and reset to `false` afterwards.
|
||||
XCTAssertEqual(states, [false, true, false])
|
||||
// the `dismiss` action must have been sent
|
||||
XCTAssertEqual(actionSent, .dismiss)
|
||||
// `restoreDefaultNotificationMode` should have been called
|
||||
XCTAssert(notificationSettingsProxyMock.restoreDefaultNotificationModeRoomIdCalled)
|
||||
XCTAssertEqual(notificationSettingsProxyMock.restoreDefaultNotificationModeRoomIdReceivedInvocations, [roomProxyMock.id])
|
||||
// and no alert is expected
|
||||
XCTAssertNil(context.alertInfo)
|
||||
XCTAssertNil(viewModel.context.alertInfo)
|
||||
}
|
||||
|
||||
func testDeleteCustomSettingTappedFailure() async throws {
|
||||
notificationSettingsProxyMock.getNotificationSettingsRoomIdIsEncryptedIsOneToOneReturnValue = RoomNotificationSettingsProxyMock(with: .init(mode: .mentionsAndKeywordsOnly, isDefault: false))
|
||||
notificationSettingsProxyMock.restoreDefaultNotificationModeRoomIdThrowableError = NotificationSettingsError.Generic(message: "error")
|
||||
viewModel = RoomNotificationSettingsScreenViewModel(notificationSettingsProxy: notificationSettingsProxyMock,
|
||||
let viewModel = RoomNotificationSettingsScreenViewModel(notificationSettingsProxy: notificationSettingsProxyMock,
|
||||
roomProxy: roomProxyMock,
|
||||
displayAsUserDefinedRoomSettings: true)
|
||||
let deferredState = deferFulfillment(context.$viewState.map(\.notificationSettingsState).first(where: \.isLoaded))
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.notificationSettingsState.isLoaded
|
||||
}
|
||||
|
||||
notificationSettingsProxyMock.callbacks.send(.settingsDidChange)
|
||||
try await deferredState.fulfill()
|
||||
try await deferred.fulfill()
|
||||
|
||||
var actionSent: RoomNotificationSettingsScreenViewModelAction?
|
||||
viewModel.actions
|
||||
@ -213,17 +253,16 @@ class RoomNotificationSettingsScreenViewModelTests: XCTestCase {
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
let deferredViewState = deferFulfillment(context.$viewState
|
||||
.map(\.deletingCustomSetting)
|
||||
.removeDuplicates()
|
||||
.collect(3).first())
|
||||
context.send(viewAction: .deleteCustomSettingTapped)
|
||||
let states = try await deferredViewState.fulfill()
|
||||
let deferredViewState = deferFulfillment(viewModel.context.$viewState,
|
||||
keyPath: \.deletingCustomSetting,
|
||||
transitionValues: [false, true, false])
|
||||
|
||||
viewModel.context.send(viewAction: .deleteCustomSettingTapped)
|
||||
|
||||
try await deferredViewState.fulfill()
|
||||
|
||||
// `deletingCustomSetting` must be set to `true` when deleting, and reset to `false` afterwards.
|
||||
XCTAssertEqual(states, [false, true, false])
|
||||
// an alert is expected
|
||||
XCTAssertEqual(context.alertInfo?.id, .restoreDefaultFailed)
|
||||
XCTAssertEqual(viewModel.context.alertInfo?.id, .restoreDefaultFailed)
|
||||
// the `dismiss` action must not have been sent
|
||||
XCTAssertNil(actionSent)
|
||||
}
|
||||
|
@ -282,11 +282,14 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
// Test
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState.collect(3).first(),
|
||||
message: "The existing view state plus one new one should be published.")
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState) { value in
|
||||
value.bindings.alertInfo != nil
|
||||
}
|
||||
|
||||
viewModel.context.send(viewAction: .tappedOnUser(userID: "bob"))
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertFalse(viewModel.state.bindings.alertInfo.isNil)
|
||||
XCTAssert(roomProxyMock.getMemberUserIDCallsCount == 1)
|
||||
XCTAssertEqual(roomProxyMock.getMemberUserIDReceivedUserID, "bob")
|
||||
@ -295,7 +298,6 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
// MARK: - Sending
|
||||
|
||||
func testRetrySend() async throws {
|
||||
// Setup
|
||||
let timelineController = MockRoomTimelineController()
|
||||
let roomProxyMock = RoomProxyMock(with: .init(displayName: ""))
|
||||
|
||||
@ -306,16 +308,15 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
analytics: ServiceLocator.shared.analytics,
|
||||
userIndicatorController: userIndicatorControllerMock)
|
||||
|
||||
// Test
|
||||
viewModel.context.send(viewAction: .retrySend(itemID: .init(timelineID: UUID().uuidString, transactionID: "test retry send id")))
|
||||
await Task.yield()
|
||||
try? await Task.sleep(for: .microseconds(500))
|
||||
|
||||
try? await Task.sleep(for: .milliseconds(100))
|
||||
|
||||
XCTAssert(roomProxyMock.retrySendTransactionIDCallsCount == 1)
|
||||
XCTAssert(roomProxyMock.retrySendTransactionIDReceivedInvocations == ["test retry send id"])
|
||||
}
|
||||
|
||||
func testRetrySendNoTransactionID() async {
|
||||
// Setup
|
||||
let timelineController = MockRoomTimelineController()
|
||||
let roomProxyMock = RoomProxyMock(with: .init(displayName: ""))
|
||||
|
||||
@ -326,14 +327,14 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
analytics: ServiceLocator.shared.analytics,
|
||||
userIndicatorController: userIndicatorControllerMock)
|
||||
|
||||
// Test
|
||||
viewModel.context.send(viewAction: .retrySend(itemID: .random))
|
||||
await Task.yield()
|
||||
|
||||
try? await Task.sleep(for: .milliseconds(100))
|
||||
|
||||
XCTAssert(roomProxyMock.retrySendTransactionIDCallsCount == 0)
|
||||
}
|
||||
|
||||
func testCancelSend() async {
|
||||
// Setup
|
||||
let timelineController = MockRoomTimelineController()
|
||||
let roomProxyMock = RoomProxyMock(with: .init(displayName: ""))
|
||||
|
||||
@ -344,15 +345,15 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
analytics: ServiceLocator.shared.analytics,
|
||||
userIndicatorController: userIndicatorControllerMock)
|
||||
|
||||
// Test
|
||||
viewModel.context.send(viewAction: .cancelSend(itemID: .init(timelineID: UUID().uuidString, transactionID: "test cancel send id")))
|
||||
try? await Task.sleep(for: .microseconds(500))
|
||||
|
||||
try? await Task.sleep(for: .milliseconds(100))
|
||||
|
||||
XCTAssert(roomProxyMock.cancelSendTransactionIDCallsCount == 1)
|
||||
XCTAssert(roomProxyMock.cancelSendTransactionIDReceivedInvocations == ["test cancel send id"])
|
||||
}
|
||||
|
||||
func testCancelSendNoTransactionID() async {
|
||||
// Setup
|
||||
let timelineController = MockRoomTimelineController()
|
||||
let roomProxyMock = RoomProxyMock(with: .init(displayName: ""))
|
||||
|
||||
@ -363,16 +364,16 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
analytics: ServiceLocator.shared.analytics,
|
||||
userIndicatorController: userIndicatorControllerMock)
|
||||
|
||||
// Test
|
||||
viewModel.context.send(viewAction: .cancelSend(itemID: .random))
|
||||
await Task.yield()
|
||||
|
||||
try? await Task.sleep(for: .milliseconds(100))
|
||||
|
||||
XCTAssert(roomProxyMock.cancelSendTransactionIDCallsCount == 0)
|
||||
}
|
||||
|
||||
// MARK: - Read Receipts
|
||||
|
||||
// swiftlint:disable force_unwrapping
|
||||
|
||||
func testSendReadReceipt() async throws {
|
||||
// Given a room with only text items in the timeline
|
||||
let items = [TextRoomTimelineItem(eventID: "t1"),
|
||||
@ -382,7 +383,7 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
|
||||
// When sending a read receipt for the last item.
|
||||
viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(items.last!.id))
|
||||
try await Task.sleep(for: .microseconds(100))
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
|
||||
// Then the receipt should be sent.
|
||||
XCTAssertEqual(roomProxy.sendReadReceiptForCalled, true)
|
||||
@ -401,13 +402,13 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
TextRoomTimelineItem(eventID: "t3")]
|
||||
let (viewModel, roomProxy, timelineController, _) = readReceiptsConfiguration(with: items)
|
||||
viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(items.last!.id))
|
||||
try await Task.sleep(for: .microseconds(100))
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
XCTAssertEqual(roomProxy.sendReadReceiptForCallsCount, 1)
|
||||
XCTAssertEqual(roomProxy.sendReadReceiptForReceivedEventID, "t3")
|
||||
|
||||
// When sending a receipt for the first item in the timeline.
|
||||
viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(items.first!.id))
|
||||
try await Task.sleep(for: .microseconds(100))
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
|
||||
// Then the request should be ignored.
|
||||
XCTAssertEqual(roomProxy.sendReadReceiptForCallsCount, 1)
|
||||
@ -417,10 +418,10 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
let newMessage = TextRoomTimelineItem(eventID: "t4")
|
||||
timelineController.timelineItems.append(newMessage)
|
||||
timelineController.callbacks.send(.updatedTimelineItems)
|
||||
try await Task.sleep(for: .microseconds(500))
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
|
||||
viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(newMessage.id))
|
||||
try await Task.sleep(for: .microseconds(100))
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
|
||||
// Then the request should be made.
|
||||
XCTAssertEqual(roomProxy.sendReadReceiptForCallsCount, 2)
|
||||
@ -436,7 +437,7 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
|
||||
// When sending a read receipt for the last item.
|
||||
viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(items.last!.id))
|
||||
try await Task.sleep(for: .microseconds(100))
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
|
||||
// Then nothing should be sent.
|
||||
XCTAssertEqual(roomProxy.sendReadReceiptForCalled, false)
|
||||
@ -451,7 +452,7 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
|
||||
// When sending a read receipt for the last item.
|
||||
viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(items.last!.id))
|
||||
try await Task.sleep(for: .microseconds(100))
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
|
||||
// Then a read receipt should be sent for the item before it.
|
||||
XCTAssertEqual(roomProxy.sendReadReceiptForCalled, true)
|
||||
@ -465,13 +466,13 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
SeparatorRoomTimelineItem(timelineID: "v3")]
|
||||
let (viewModel, roomProxy, _, _) = readReceiptsConfiguration(with: items)
|
||||
viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(items.last!.id))
|
||||
try await Task.sleep(for: .microseconds(100))
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
XCTAssertEqual(roomProxy.sendReadReceiptForCallsCount, 1)
|
||||
XCTAssertEqual(roomProxy.sendReadReceiptForReceivedEventID, "t2")
|
||||
|
||||
// When sending the same receipt again
|
||||
viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(items.last!.id))
|
||||
try await Task.sleep(for: .microseconds(100))
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
|
||||
// Then the second call should be ignored.
|
||||
XCTAssertEqual(roomProxy.sendReadReceiptForCallsCount, 1)
|
||||
|
@ -50,7 +50,11 @@ class SessionVerificationViewModelTests: XCTestCase {
|
||||
|
||||
XCTAssertEqual(context.viewState.verificationState, .cancelling)
|
||||
|
||||
await context.nextViewState()
|
||||
let deferred = deferFulfillment(context.$viewState) { state in
|
||||
state.verificationState == .cancelled
|
||||
}
|
||||
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertEqual(context.viewState.verificationState, .cancelled)
|
||||
|
||||
|
@ -81,7 +81,16 @@ class StaticLocationScreenViewModelTests: XCTestCase {
|
||||
func testSendUserLocation() async throws {
|
||||
context.mapCenterLocation = .init(latitude: 0, longitude: 0)
|
||||
context.geolocationUncertainty = 10
|
||||
let deferred = deferFulfillment(viewModel.actions.first())
|
||||
|
||||
let deferred = deferFulfillment(viewModel.actions) { action in
|
||||
switch action {
|
||||
case .sendLocation:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
context.send(viewAction: .selectLocation)
|
||||
guard case .sendLocation(let geoUri, let isUserLocation) = try await deferred.fulfill() else {
|
||||
XCTFail("Sent action should be 'sendLocation'")
|
||||
@ -95,7 +104,16 @@ class StaticLocationScreenViewModelTests: XCTestCase {
|
||||
context.mapCenterLocation = .init(latitude: 0, longitude: 0)
|
||||
context.isLocationAuthorized = nil
|
||||
context.geolocationUncertainty = 10
|
||||
let deferred = deferFulfillment(viewModel.actions.first())
|
||||
|
||||
let deferred = deferFulfillment(viewModel.actions) { action in
|
||||
switch action {
|
||||
case .sendLocation:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
context.send(viewAction: .selectLocation)
|
||||
guard case .sendLocation(let geoUri, let isUserLocation) = try await deferred.fulfill() else {
|
||||
XCTFail("Sent action should be 'sendLocation'")
|
||||
|
@ -81,7 +81,6 @@ lane :unit_tests do
|
||||
device: 'iPhone 14 (16.4)',
|
||||
ensure_devices_found: true,
|
||||
result_bundle: true,
|
||||
number_of_retries: 3,
|
||||
xcargs: '-skipPackagePluginValidation',
|
||||
)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user