mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 10:33:45 +00:00
Finish migration of UnitTests target from XCTestCase to Swift Testing (#5129)
* Initial plan * Migrate 3 test files from XCTest to Swift Testing - MediaUploadPreviewScreenViewModelTests: @MainActor @Suite struct with init(), BundleFinder class for Bundle(for:), mutating test/setup functions, [self] capture replacing [weak self] in closures - NotificationManagerTests: @MainActor @Suite final class with init()/deinit, expectation/fulfillment(of:) replaced with confirmation(...), test_ prefix stripped - NotificationSettingsScreenViewModelTests: @MainActor @Suite struct with init() throws, non-optional stored properties, test prefix stripped Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Migrate 3 XCTest files to Swift Testing - NotificationSettingsEditScreenViewModelTests: @MainActor @Suite struct with init() throws, mutating test methods - TimelineViewModelTests: @MainActor @Suite final class with init() async throws + deinit - AttributedStringBuilderTests: @Suite struct with init() async throws All XCT assertions replaced with #expect/#require/Issue.record Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Migrate 4 test files from XCTest to Swift Testing - TimelineMediaPreviewViewModelTests: @Suite struct, mutating @Test funcs, testLoadingItem renamed to loadingItem (called internally by other tests) - ServerConfirmationScreenViewModelTests: @Suite final class with init()/deinit - CompletionSuggestionServiceTests: @Suite struct with init() - RoomFlowCoordinatorTests: @Suite final class with deinit Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Migrate 4 test files from XCTest to Swift Testing - VoiceMessageRecorderTests: @Suite struct with init() async throws, added BundleFinder class for Bundle lookup, migrated all assertions - SpaceScreenViewModelTests: @Suite struct, private mutating setupViewModel, all test funcs mutating, XCTestExpectation → confirmation - RoomNotificationSettingsScreenViewModelTests: @Suite struct with init() throws, cancellable tests marked mutating - JoinRoomScreenViewModelTests: @Suite final class with init()/deinit, XCTestExpectation → confirmation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Migrate 6 test files from XCTestCase to Swift Testing Co-authored-by: pixlwave <6060466+pixlwave@users.noreply.github.com> * Fix trailing blank line in RoomPollsHistoryScreenViewModelTests Co-authored-by: pixlwave <6060466+pixlwave@users.noreply.github.com> * Migrate 3 test files from XCTest to Swift Testing - MediaUploadingPreprocessorTests: @Suite final class with init()/deinit, removed executionTimeAllowance, XCTAssertEqual(accuracy:) → abs(Double) - SecurityAndPrivacyScreenViewModelTests: @MainActor @Suite final class, 5 expectation+fulfillment → await confirmation(...) - CreateRoomViewModelTests: @MainActor @Suite final class, 4 expectation+fulfillment → await confirmation(...) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Migrate RoomScreenViewModelTests and RoomDetailsScreenViewModelTests to Swift Testing - Replace XCTest with Testing framework - RoomScreenViewModelTests: final class with init() async throws + deinit - RoomDetailsScreenViewModelTests: struct with init() and mutating funcs - Convert XCT assertions to #expect / Issue.record - Convert XCTestExpectation patterns to confirmation { confirm in } - Strip 'test' prefix from all test function names Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Migrate ComposerToolbarViewModelTests from XCTest to Swift Testing - Replace import XCTest with import Testing - Convert XCTestCase class to @MainActor @Suite final class - Replace setUp()/tearDown() with init()/deinit - Strip 'test' prefix from all 41 test method names and add @Test - Replace XCTAssert* with #expect()/#require() - Replace try XCTUnwrap() with try #require() - Convert expectation+wait patterns to deferFulfillment with PassthroughSubject - Convert isInverted expectation to boolean flag checked after await - Use deferFulfillment on $viewState for state-transition tests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address comments with Copilot. * Fix the failing tests. * Fixed flaky tests (#5137) resolved flaky tests * Tweaks and fixes. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: pixlwave <6060466+pixlwave@users.noreply.github.com> Co-authored-by: Doug <douglase@element.io> Co-authored-by: Mauro <34335419+Velin92@users.noreply.github.com>
This commit is contained in:
@@ -8,27 +8,30 @@
|
||||
|
||||
import Combine
|
||||
@testable import ElementX
|
||||
import Foundation
|
||||
import MatrixRustSDK
|
||||
import XCTest
|
||||
import Testing
|
||||
|
||||
@Suite
|
||||
@MainActor
|
||||
class TimelineViewModelTests: XCTestCase {
|
||||
final class TimelineViewModelTests {
|
||||
var userIndicatorControllerMock: UserIndicatorControllerMock!
|
||||
var cancellables = Set<AnyCancellable>()
|
||||
|
||||
override func setUp() async throws {
|
||||
init() async throws {
|
||||
AppSettings.resetAllSettings()
|
||||
cancellables.removeAll()
|
||||
userIndicatorControllerMock = UserIndicatorControllerMock.default
|
||||
}
|
||||
|
||||
override func tearDown() async throws {
|
||||
deinit {
|
||||
userIndicatorControllerMock = nil
|
||||
}
|
||||
|
||||
// MARK: - Message Grouping
|
||||
|
||||
func testMessageGrouping() {
|
||||
@Test
|
||||
func messageGrouping() {
|
||||
// Given 3 messages from Bob.
|
||||
let items = [
|
||||
TextRoomTimelineItem(text: "Message 1",
|
||||
@@ -45,12 +48,13 @@ class TimelineViewModelTests: XCTestCase {
|
||||
let viewModel = makeViewModel(timelineController: timelineController)
|
||||
|
||||
// Then the messages should be grouped together.
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[0].groupStyle, .first, "Nothing should prevent the first message from being grouped.")
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[1].groupStyle, .middle, "Nothing should prevent the middle message from being grouped.")
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[2].groupStyle, .last, "Nothing should prevent the last message from being grouped.")
|
||||
#expect(viewModel.state.timelineState.itemViewStates[0].groupStyle == .first, "Nothing should prevent the first message from being grouped.")
|
||||
#expect(viewModel.state.timelineState.itemViewStates[1].groupStyle == .middle, "Nothing should prevent the middle message from being grouped.")
|
||||
#expect(viewModel.state.timelineState.itemViewStates[2].groupStyle == .last, "Nothing should prevent the last message from being grouped.")
|
||||
}
|
||||
|
||||
func testMessageGroupingMultipleSenders() {
|
||||
@Test
|
||||
func messageGroupingMultipleSenders() {
|
||||
// Given some interleaved messages from Bob and Alice.
|
||||
let items = [
|
||||
TextRoomTimelineItem(text: "Message 1",
|
||||
@@ -73,15 +77,16 @@ class TimelineViewModelTests: XCTestCase {
|
||||
let viewModel = makeViewModel(timelineController: timelineController)
|
||||
|
||||
// Then the messages should be grouped by sender.
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[0].groupStyle, .single, "A message should not be grouped when the sender changes.")
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[1].groupStyle, .single, "A message should not be grouped when the sender changes.")
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[2].groupStyle, .first, "A group should start with a new sender if there are more messages from that sender.")
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[3].groupStyle, .last, "A group should be ended when the sender changes in the next message.")
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[4].groupStyle, .first, "A group should start with a new sender if there are more messages from that sender.")
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[5].groupStyle, .last, "A group should be ended when the sender changes in the next message.")
|
||||
#expect(viewModel.state.timelineState.itemViewStates[0].groupStyle == .single, "A message should not be grouped when the sender changes.")
|
||||
#expect(viewModel.state.timelineState.itemViewStates[1].groupStyle == .single, "A message should not be grouped when the sender changes.")
|
||||
#expect(viewModel.state.timelineState.itemViewStates[2].groupStyle == .first, "A group should start with a new sender if there are more messages from that sender.")
|
||||
#expect(viewModel.state.timelineState.itemViewStates[3].groupStyle == .last, "A group should be ended when the sender changes in the next message.")
|
||||
#expect(viewModel.state.timelineState.itemViewStates[4].groupStyle == .first, "A group should start with a new sender if there are more messages from that sender.")
|
||||
#expect(viewModel.state.timelineState.itemViewStates[5].groupStyle == .last, "A group should be ended when the sender changes in the next message.")
|
||||
}
|
||||
|
||||
func testMessageGroupingWithLeadingReactions() {
|
||||
@Test
|
||||
func messageGroupingWithLeadingReactions() {
|
||||
// Given 3 messages from Bob where the first message has a reaction.
|
||||
let items = [
|
||||
TextRoomTimelineItem(text: "Message 1",
|
||||
@@ -99,12 +104,13 @@ class TimelineViewModelTests: XCTestCase {
|
||||
let viewModel = makeViewModel(timelineController: timelineController)
|
||||
|
||||
// Then the first message should not be grouped but the other two should.
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[0].groupStyle, .single, "When the first message has reactions it should not be grouped.")
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[1].groupStyle, .first, "A new group should be made when the preceding message has reactions.")
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[2].groupStyle, .last, "Nothing should prevent the last message from being grouped.")
|
||||
#expect(viewModel.state.timelineState.itemViewStates[0].groupStyle == .single, "When the first message has reactions it should not be grouped.")
|
||||
#expect(viewModel.state.timelineState.itemViewStates[1].groupStyle == .first, "A new group should be made when the preceding message has reactions.")
|
||||
#expect(viewModel.state.timelineState.itemViewStates[2].groupStyle == .last, "Nothing should prevent the last message from being grouped.")
|
||||
}
|
||||
|
||||
func testMessageGroupingWithInnerReactions() {
|
||||
@Test
|
||||
func messageGroupingWithInnerReactions() {
|
||||
// Given 3 messages from Bob where the middle message has a reaction.
|
||||
let items = [
|
||||
TextRoomTimelineItem(text: "Message 1",
|
||||
@@ -122,12 +128,13 @@ class TimelineViewModelTests: XCTestCase {
|
||||
let viewModel = makeViewModel(timelineController: timelineController)
|
||||
|
||||
// Then the first and second messages should be grouped and the last one should not.
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[0].groupStyle, .first, "Nothing should prevent the first message from being grouped.")
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[1].groupStyle, .last, "When the message has reactions, the group should end here.")
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[2].groupStyle, .single, "The last message should not be grouped when the preceding message has reactions.")
|
||||
#expect(viewModel.state.timelineState.itemViewStates[0].groupStyle == .first, "Nothing should prevent the first message from being grouped.")
|
||||
#expect(viewModel.state.timelineState.itemViewStates[1].groupStyle == .last, "When the message has reactions, the group should end here.")
|
||||
#expect(viewModel.state.timelineState.itemViewStates[2].groupStyle == .single, "The last message should not be grouped when the preceding message has reactions.")
|
||||
}
|
||||
|
||||
func testMessageGroupingWithTrailingReactions() {
|
||||
@Test
|
||||
func messageGroupingWithTrailingReactions() {
|
||||
// Given 3 messages from Bob where the last message has a reaction.
|
||||
let items = [
|
||||
TextRoomTimelineItem(text: "Message 1",
|
||||
@@ -145,14 +152,15 @@ class TimelineViewModelTests: XCTestCase {
|
||||
let viewModel = makeViewModel(timelineController: timelineController)
|
||||
|
||||
// Then the messages should be grouped together.
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[0].groupStyle, .first, "Nothing should prevent the first message from being grouped.")
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[1].groupStyle, .middle, "Nothing should prevent the second message from being grouped.")
|
||||
XCTAssertEqual(viewModel.state.timelineState.itemViewStates[2].groupStyle, .last, "Reactions on the last message should not prevent it from being grouped.")
|
||||
#expect(viewModel.state.timelineState.itemViewStates[0].groupStyle == .first, "Nothing should prevent the first message from being grouped.")
|
||||
#expect(viewModel.state.timelineState.itemViewStates[1].groupStyle == .middle, "Nothing should prevent the second message from being grouped.")
|
||||
#expect(viewModel.state.timelineState.itemViewStates[2].groupStyle == .last, "Reactions on the last message should not prevent it from being grouped.")
|
||||
}
|
||||
|
||||
// MARK: - Focussing
|
||||
|
||||
func testFocusItem() async throws {
|
||||
@Test
|
||||
func focusItem() async throws {
|
||||
// Given a room with 3 items loaded in a live timeline.
|
||||
let items = [TextRoomTimelineItem(eventID: "t1"),
|
||||
TextRoomTimelineItem(eventID: "t2"),
|
||||
@@ -161,9 +169,9 @@ class TimelineViewModelTests: XCTestCase {
|
||||
timelineController.timelineItems = items
|
||||
|
||||
let viewModel = makeViewModel(timelineController: timelineController)
|
||||
XCTAssertEqual(timelineController.focusOnEventCallCount, 0)
|
||||
XCTAssertTrue(viewModel.context.viewState.timelineState.isLive)
|
||||
XCTAssertNil(viewModel.context.viewState.timelineState.focussedEvent)
|
||||
#expect(timelineController.focusOnEventCallCount == 0)
|
||||
#expect(viewModel.context.viewState.timelineState.isLive)
|
||||
#expect(viewModel.context.viewState.timelineState.focussedEvent == nil)
|
||||
|
||||
// When focussing on an item that isn't loaded.
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState) { !$0.timelineState.isLive }
|
||||
@@ -171,12 +179,13 @@ class TimelineViewModelTests: XCTestCase {
|
||||
try await deferred.fulfill()
|
||||
|
||||
// Then a new timeline should be loaded and the room focussed on that event.
|
||||
XCTAssertEqual(timelineController.focusOnEventCallCount, 1)
|
||||
XCTAssertFalse(viewModel.context.viewState.timelineState.isLive)
|
||||
XCTAssertEqual(viewModel.context.viewState.timelineState.focussedEvent, .init(eventID: "t4", appearance: .immediate))
|
||||
#expect(timelineController.focusOnEventCallCount == 1)
|
||||
#expect(!viewModel.context.viewState.timelineState.isLive)
|
||||
#expect(viewModel.context.viewState.timelineState.focussedEvent == .init(eventID: "t4", appearance: .immediate))
|
||||
}
|
||||
|
||||
func testFocusLoadedItem() async throws {
|
||||
@Test
|
||||
func focusLoadedItem() async throws {
|
||||
// Given a room with 3 items loaded in a live timeline.
|
||||
let items = [TextRoomTimelineItem(eventID: "t1"),
|
||||
TextRoomTimelineItem(eventID: "t2"),
|
||||
@@ -185,22 +194,23 @@ class TimelineViewModelTests: XCTestCase {
|
||||
timelineController.timelineItems = items
|
||||
|
||||
let viewModel = makeViewModel(timelineController: timelineController)
|
||||
XCTAssertEqual(timelineController.focusOnEventCallCount, 0)
|
||||
XCTAssertTrue(viewModel.context.viewState.timelineState.isLive)
|
||||
XCTAssertNil(viewModel.context.viewState.timelineState.focussedEvent)
|
||||
#expect(timelineController.focusOnEventCallCount == 0)
|
||||
#expect(viewModel.context.viewState.timelineState.isLive)
|
||||
#expect(viewModel.context.viewState.timelineState.focussedEvent == nil)
|
||||
|
||||
// When focussing on a loaded item.
|
||||
let deferred = deferFailure(viewModel.context.$viewState, timeout: 1) { !$0.timelineState.isLive }
|
||||
let deferred = deferFailure(viewModel.context.$viewState, timeout: .seconds(1)) { !$0.timelineState.isLive }
|
||||
await viewModel.focusOnEvent(eventID: "t1")
|
||||
try await deferred.fulfill()
|
||||
|
||||
// Then the timeline should remain live and the item should be focussed.
|
||||
XCTAssertEqual(timelineController.focusOnEventCallCount, 0)
|
||||
XCTAssertTrue(viewModel.context.viewState.timelineState.isLive)
|
||||
XCTAssertEqual(viewModel.context.viewState.timelineState.focussedEvent, .init(eventID: "t1", appearance: .animated))
|
||||
#expect(timelineController.focusOnEventCallCount == 0)
|
||||
#expect(viewModel.context.viewState.timelineState.isLive)
|
||||
#expect(viewModel.context.viewState.timelineState.focussedEvent == .init(eventID: "t1", appearance: .animated))
|
||||
}
|
||||
|
||||
func testFocusLive() async throws {
|
||||
@Test
|
||||
func focusLive() async throws {
|
||||
// Given a room with a non-live timeline focussed on a particular event.
|
||||
let items = [TextRoomTimelineItem(eventID: "t1"),
|
||||
TextRoomTimelineItem(eventID: "t2"),
|
||||
@@ -214,9 +224,9 @@ class TimelineViewModelTests: XCTestCase {
|
||||
await viewModel.focusOnEvent(eventID: "t4")
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertEqual(timelineController.focusLiveCallCount, 0)
|
||||
XCTAssertFalse(viewModel.context.viewState.timelineState.isLive)
|
||||
XCTAssertEqual(viewModel.context.viewState.timelineState.focussedEvent, .init(eventID: "t4", appearance: .immediate))
|
||||
#expect(timelineController.focusLiveCallCount == 0)
|
||||
#expect(!viewModel.context.viewState.timelineState.isLive)
|
||||
#expect(viewModel.context.viewState.timelineState.focussedEvent == .init(eventID: "t4", appearance: .immediate))
|
||||
|
||||
// When switching back to a live timeline.
|
||||
deferred = deferFulfillment(viewModel.context.$viewState) { $0.timelineState.isLive }
|
||||
@@ -224,21 +234,23 @@ class TimelineViewModelTests: XCTestCase {
|
||||
try await deferred.fulfill()
|
||||
|
||||
// Then the timeline should switch back to being live and the event focus should be removed.
|
||||
XCTAssertEqual(timelineController.focusLiveCallCount, 1)
|
||||
XCTAssertTrue(viewModel.context.viewState.timelineState.isLive)
|
||||
XCTAssertNil(viewModel.context.viewState.timelineState.focussedEvent)
|
||||
#expect(timelineController.focusLiveCallCount == 1)
|
||||
#expect(viewModel.context.viewState.timelineState.isLive)
|
||||
#expect(viewModel.context.viewState.timelineState.focussedEvent == nil)
|
||||
}
|
||||
|
||||
func testInitialFocusViewState() {
|
||||
@Test
|
||||
func initialFocusViewState() {
|
||||
let timelineController = MockTimelineController()
|
||||
|
||||
let viewModel = makeViewModel(focussedEventID: "t10", timelineController: timelineController)
|
||||
XCTAssertEqual(viewModel.context.viewState.timelineState.focussedEvent, .init(eventID: "t10", appearance: .immediate))
|
||||
#expect(viewModel.context.viewState.timelineState.focussedEvent == .init(eventID: "t10", appearance: .immediate))
|
||||
}
|
||||
|
||||
// MARK: - Read Receipts
|
||||
|
||||
func testSendReadReceipt() async throws {
|
||||
@Test
|
||||
func sendReadReceipt() async throws {
|
||||
// Given a room with only text items in the timeline
|
||||
let items = [TextRoomTimelineItem(eventID: "t1"),
|
||||
TextRoomTimelineItem(eventID: "t2"),
|
||||
@@ -246,17 +258,18 @@ class TimelineViewModelTests: XCTestCase {
|
||||
let (viewModel, _, timelineProxy, _) = readReceiptsConfiguration(with: items)
|
||||
|
||||
// When sending a read receipt for the last item.
|
||||
try viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(XCTUnwrap(items.last?.id)))
|
||||
try viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(#require(items.last?.id)))
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
|
||||
// Then the receipt should be sent.
|
||||
XCTAssertEqual(timelineProxy.sendReadReceiptForTypeCalled, true)
|
||||
#expect(timelineProxy.sendReadReceiptForTypeCalled == true)
|
||||
let arguments = timelineProxy.sendReadReceiptForTypeReceivedArguments
|
||||
XCTAssertEqual(arguments?.eventID, "t3")
|
||||
XCTAssertEqual(arguments?.type, .read)
|
||||
#expect(arguments?.eventID == "t3")
|
||||
#expect(arguments?.type == .read)
|
||||
}
|
||||
|
||||
func testSendReadReceiptWithoutEvents() async throws {
|
||||
@Test
|
||||
func sendReadReceiptWithoutEvents() async throws {
|
||||
// Given a room with only virtual items.
|
||||
let items = [SeparatorRoomTimelineItem(uniqueID: .init("v1")),
|
||||
SeparatorRoomTimelineItem(uniqueID: .init("v2")),
|
||||
@@ -264,14 +277,15 @@ class TimelineViewModelTests: XCTestCase {
|
||||
let (viewModel, _, timelineProxy, _) = readReceiptsConfiguration(with: items)
|
||||
|
||||
// When sending a read receipt for the last item.
|
||||
try viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(XCTUnwrap(items.last?.id)))
|
||||
try viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(#require(items.last?.id)))
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
|
||||
// Then nothing should be sent.
|
||||
XCTAssertEqual(timelineProxy.sendReadReceiptForTypeCalled, false)
|
||||
#expect(timelineProxy.sendReadReceiptForTypeCalled == false)
|
||||
}
|
||||
|
||||
func testSendReadReceiptVirtualLast() async throws {
|
||||
@Test
|
||||
func sendReadReceiptVirtualLast() async throws {
|
||||
// Given a room where the last event is a virtual item.
|
||||
let items: [RoomTimelineItemProtocol] = [TextRoomTimelineItem(eventID: "t1"),
|
||||
TextRoomTimelineItem(eventID: "t2"),
|
||||
@@ -279,7 +293,7 @@ class TimelineViewModelTests: XCTestCase {
|
||||
let (viewModel, _, _, _) = readReceiptsConfiguration(with: items)
|
||||
|
||||
// When sending a read receipt for the last item.
|
||||
try viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(XCTUnwrap(items.last?.id)))
|
||||
try viewModel.context.send(viewAction: .sendReadReceiptIfNeeded(#require(items.last?.id)))
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
}
|
||||
|
||||
@@ -314,7 +328,8 @@ class TimelineViewModelTests: XCTestCase {
|
||||
return (viewModel, roomProxy, timelineProxy, timelineController)
|
||||
}
|
||||
|
||||
func testShowReadReceipts() async throws {
|
||||
@Test
|
||||
func showReadReceipts() async throws {
|
||||
let receipts: [ReadReceipt] = [.init(userID: "@alice:matrix.org", formattedTimestamp: "12:00"),
|
||||
.init(userID: "@charlie:matrix.org", formattedTimestamp: "11:00")]
|
||||
// Given 3 messages from Bob where the middle message has a reaction.
|
||||
@@ -346,7 +361,8 @@ class TimelineViewModelTests: XCTestCase {
|
||||
try await deferred.fulfill()
|
||||
}
|
||||
|
||||
func testShowManageUserAsAdmin() async throws {
|
||||
@Test
|
||||
func showManageUserAsAdmin() async throws {
|
||||
let viewModel = TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "",
|
||||
members: [RoomMemberProxyMock.mockAdmin,
|
||||
RoomMemberProxyMock.mockAlice],
|
||||
@@ -375,14 +391,15 @@ class TimelineViewModelTests: XCTestCase {
|
||||
viewModel.context.send(viewAction: .tappedOnSenderDetails(sender: .init(with: RoomMemberProxyMock.mockAlice)))
|
||||
try await deferred.fulfill()
|
||||
|
||||
XCTAssertEqual(viewModel.context.manageMemberViewModel?.id, RoomMemberProxyMock.mockAlice.userID)
|
||||
XCTAssertEqual(viewModel.context.manageMemberViewModel?.state.permissions.canBan, true)
|
||||
XCTAssertEqual(viewModel.context.manageMemberViewModel?.state.permissions.canKick, true)
|
||||
XCTAssertEqual(viewModel.context.manageMemberViewModel?.state.isKickDisabled, false)
|
||||
XCTAssertEqual(viewModel.context.manageMemberViewModel?.state.isBanUnbanDisabled, false)
|
||||
#expect(viewModel.context.manageMemberViewModel?.id == RoomMemberProxyMock.mockAlice.userID)
|
||||
#expect(viewModel.context.manageMemberViewModel?.state.permissions.canBan == true)
|
||||
#expect(viewModel.context.manageMemberViewModel?.state.permissions.canKick == true)
|
||||
#expect(viewModel.context.manageMemberViewModel?.state.isKickDisabled == false)
|
||||
#expect(viewModel.context.manageMemberViewModel?.state.isBanUnbanDisabled == false)
|
||||
}
|
||||
|
||||
func testShowDetailsForAnAdmin() async throws {
|
||||
@Test
|
||||
func showDetailsForAnAdmin() async throws {
|
||||
let viewModel = TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "",
|
||||
members: [RoomMemberProxyMock.mockAdmin,
|
||||
RoomMemberProxyMock.mockAlice],
|
||||
@@ -411,14 +428,15 @@ class TimelineViewModelTests: XCTestCase {
|
||||
viewModel.context.send(viewAction: .tappedOnSenderDetails(sender: .init(with: RoomMemberProxyMock.mockAdmin)))
|
||||
try await deferredState.fulfill()
|
||||
|
||||
XCTAssertEqual(viewModel.context.manageMemberViewModel?.state.permissions.canBan, false)
|
||||
XCTAssertEqual(viewModel.context.manageMemberViewModel?.state.permissions.canKick, false)
|
||||
XCTAssertEqual(viewModel.context.manageMemberViewModel?.state.isKickDisabled, true)
|
||||
XCTAssertEqual(viewModel.context.manageMemberViewModel?.state.isBanUnbanDisabled, true)
|
||||
XCTAssertEqual(viewModel.context.manageMemberViewModel?.id, RoomMemberProxyMock.mockAdmin.userID)
|
||||
#expect(viewModel.context.manageMemberViewModel?.state.permissions.canBan == false)
|
||||
#expect(viewModel.context.manageMemberViewModel?.state.permissions.canKick == false)
|
||||
#expect(viewModel.context.manageMemberViewModel?.state.isKickDisabled == true)
|
||||
#expect(viewModel.context.manageMemberViewModel?.state.isBanUnbanDisabled == true)
|
||||
#expect(viewModel.context.manageMemberViewModel?.id == RoomMemberProxyMock.mockAdmin.userID)
|
||||
}
|
||||
|
||||
func testShowDetailsForABannedUser() async throws {
|
||||
@Test
|
||||
func showDetailsForABannedUser() async throws {
|
||||
let viewModel = TimelineViewModel(roomProxy: JoinedRoomProxyMock(.init(name: "",
|
||||
members: [RoomMemberProxyMock.mockAdmin,
|
||||
RoomMemberProxyMock.mockBanned[0]],
|
||||
@@ -447,17 +465,18 @@ class TimelineViewModelTests: XCTestCase {
|
||||
viewModel.context.send(viewAction: .tappedOnSenderDetails(sender: .init(with: RoomMemberProxyMock.mockBanned[0])))
|
||||
try await deferredState.fulfill()
|
||||
|
||||
XCTAssertEqual(viewModel.context.manageMemberViewModel?.state.permissions.canBan, true)
|
||||
XCTAssertEqual(viewModel.context.manageMemberViewModel?.state.permissions.canKick, true)
|
||||
XCTAssertEqual(viewModel.context.manageMemberViewModel?.state.isKickDisabled, true)
|
||||
XCTAssertEqual(viewModel.context.manageMemberViewModel?.state.isBanUnbanDisabled, false)
|
||||
XCTAssertEqual(viewModel.context.manageMemberViewModel?.state.isMemberBanned, true)
|
||||
XCTAssertEqual(viewModel.context.manageMemberViewModel?.id, RoomMemberProxyMock.mockBanned[0].userID)
|
||||
#expect(viewModel.context.manageMemberViewModel?.state.permissions.canBan == true)
|
||||
#expect(viewModel.context.manageMemberViewModel?.state.permissions.canKick == true)
|
||||
#expect(viewModel.context.manageMemberViewModel?.state.isKickDisabled == true)
|
||||
#expect(viewModel.context.manageMemberViewModel?.state.isBanUnbanDisabled == false)
|
||||
#expect(viewModel.context.manageMemberViewModel?.state.isMemberBanned == true)
|
||||
#expect(viewModel.context.manageMemberViewModel?.id == RoomMemberProxyMock.mockBanned[0].userID)
|
||||
}
|
||||
|
||||
// MARK: - Pins
|
||||
|
||||
func testPinnedEvents() async throws {
|
||||
@Test
|
||||
func pinnedEvents() async throws {
|
||||
var configuration = JoinedRoomProxyMockConfiguration(name: "",
|
||||
pinnedEventIDs: .init(["test1"]))
|
||||
let roomProxyMock = JoinedRoomProxyMock(configuration)
|
||||
@@ -475,7 +494,7 @@ class TimelineViewModelTests: XCTestCase {
|
||||
emojiProvider: EmojiProvider(appSettings: ServiceLocator.shared.settings),
|
||||
linkMetadataProvider: LinkMetadataProvider(),
|
||||
timelineControllerFactory: TimelineControllerFactoryMock(.init()))
|
||||
XCTAssertEqual(configuration.pinnedEventIDs, viewModel.context.viewState.pinnedEventIDs)
|
||||
#expect(configuration.pinnedEventIDs == viewModel.context.viewState.pinnedEventIDs)
|
||||
|
||||
configuration.pinnedEventIDs = ["test1", "test2"]
|
||||
let deferred = deferFulfillment(viewModel.context.$viewState) { value in
|
||||
@@ -485,7 +504,8 @@ class TimelineViewModelTests: XCTestCase {
|
||||
try await deferred.fulfill()
|
||||
}
|
||||
|
||||
func testCanUserPinEvents() async throws {
|
||||
@Test
|
||||
func canUserPinEvents() async throws {
|
||||
let configuration = JoinedRoomProxyMockConfiguration(name: "",
|
||||
powerLevelsConfiguration: .init(canUserPin: true))
|
||||
let roomProxyMock = JoinedRoomProxyMock(configuration)
|
||||
@@ -526,32 +546,34 @@ class TimelineViewModelTests: XCTestCase {
|
||||
|
||||
// MARK: - Tap Actions
|
||||
|
||||
func testTapSendInfoEncryptionAuthentictyDisplaysAlert() {
|
||||
@Test
|
||||
func tapSendInfoEncryptionAuthentictyDisplaysAlert() {
|
||||
// Given a room with an event whose authenticity could not be verified
|
||||
let items = [TextRoomTimelineItem(eventID: "t1", encryptionAuthenticity: .verificationViolation(color: .red))]
|
||||
let timelineController = MockTimelineController()
|
||||
timelineController.timelineItems = items
|
||||
let viewModel = makeViewModel(timelineController: timelineController)
|
||||
|
||||
XCTAssertNil(viewModel.state.bindings.alertInfo)
|
||||
#expect(viewModel.state.bindings.alertInfo == nil)
|
||||
|
||||
viewModel.process(viewAction: .itemSendInfoTapped(itemID: items[0].id))
|
||||
|
||||
XCTAssertEqual(viewModel.state.bindings.alertInfo?.title, "Encrypted by a previously-verified user.")
|
||||
#expect(viewModel.state.bindings.alertInfo?.title == "Encrypted by a previously-verified user.")
|
||||
}
|
||||
|
||||
func testTapSendInfoEncryptionForwarderDisplaysAlert() {
|
||||
@Test
|
||||
func tapSendInfoEncryptionForwarderDisplaysAlert() {
|
||||
// Given a room with an event whose key was forwarded
|
||||
let items = [TextRoomTimelineItem(eventID: "t1", keyForwarder: .test)]
|
||||
let timelineController = MockTimelineController()
|
||||
timelineController.timelineItems = items
|
||||
let viewModel = makeViewModel(timelineController: timelineController)
|
||||
|
||||
XCTAssertNil(viewModel.state.bindings.alertInfo)
|
||||
#expect(viewModel.state.bindings.alertInfo == nil)
|
||||
|
||||
viewModel.process(viewAction: .itemSendInfoTapped(itemID: items[0].id))
|
||||
|
||||
XCTAssertEqual(viewModel.state.bindings.alertInfo?.title, "alice (@alice:matrix.org) shared this message since you were not in the room when it was sent.")
|
||||
#expect(viewModel.state.bindings.alertInfo?.title == "alice (@alice:matrix.org) shared this message since you were not in the room when it was sent.")
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
Reference in New Issue
Block a user