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:
Copilot
2026-02-24 12:20:01 +00:00
committed by GitHub
parent 62f193eda1
commit e58c5801fc
38 changed files with 3460 additions and 3304 deletions
@@ -8,17 +8,18 @@
@testable import ElementX
import MatrixRustSDK
import XCTest
import Testing
@Suite
@MainActor
class NotificationSettingsScreenViewModelTests: XCTestCase {
private var viewModel: NotificationSettingsScreenViewModelProtocol!
private var context: NotificationSettingsScreenViewModelType.Context!
private var appSettings: AppSettings!
private var userNotificationCenter: UserNotificationCenterMock!
private var notificationSettingsProxy: NotificationSettingsProxyMock!
struct NotificationSettingsScreenViewModelTests {
private var viewModel: NotificationSettingsScreenViewModelProtocol
private var context: NotificationSettingsScreenViewModelType.Context
private var appSettings: AppSettings
private var userNotificationCenter: UserNotificationCenterMock
private var notificationSettingsProxy: NotificationSettingsProxyMock
@MainActor override func setUpWithError() throws {
init() throws {
AppSettings.resetAllSettings()
userNotificationCenter = UserNotificationCenterMock()
@@ -36,19 +37,22 @@ class NotificationSettingsScreenViewModelTests: XCTestCase {
context = viewModel.context
}
func testEnableNotifications() {
@Test
func enableNotifications() {
appSettings.enableNotifications = false
context.send(viewAction: .changedEnableNotifications)
XCTAssertTrue(appSettings.enableNotifications)
#expect(appSettings.enableNotifications)
}
func testDisableNotifications() {
@Test
func disableNotifications() {
appSettings.enableNotifications = true
context.send(viewAction: .changedEnableNotifications)
XCTAssertFalse(appSettings.enableNotifications)
#expect(!appSettings.enableNotifications)
}
func testFetchSettings() async throws {
@Test
func fetchSettings() async throws {
notificationSettingsProxy.getDefaultRoomNotificationModeIsEncryptedIsOneToOneClosure = { isEncrypted, isOneToOne in
switch (isEncrypted, isOneToOne) {
case (_, true):
@@ -64,17 +68,18 @@ class NotificationSettingsScreenViewModelTests: XCTestCase {
try await deferred.fulfill()
XCTAssertEqual(notificationSettingsProxy.getDefaultRoomNotificationModeIsEncryptedIsOneToOneCallsCount, 4)
XCTAssert(notificationSettingsProxy.isRoomMentionEnabledCalled)
XCTAssert(notificationSettingsProxy.isCallEnabledCalled)
#expect(notificationSettingsProxy.getDefaultRoomNotificationModeIsEncryptedIsOneToOneCallsCount == 4)
#expect(notificationSettingsProxy.isRoomMentionEnabledCalled)
#expect(notificationSettingsProxy.isCallEnabledCalled)
XCTAssertEqual(context.viewState.settings?.groupChatsMode, .mentionsAndKeywordsOnly)
XCTAssertEqual(context.viewState.settings?.directChatsMode, .allMessages)
XCTAssertEqual(context.viewState.settings?.inconsistentSettings, [])
XCTAssertNil(context.viewState.bindings.alertInfo)
#expect(context.viewState.settings?.groupChatsMode == .mentionsAndKeywordsOnly)
#expect(context.viewState.settings?.directChatsMode == .allMessages)
#expect(context.viewState.settings?.inconsistentSettings == [])
#expect(context.viewState.bindings.alertInfo == nil)
}
func testInconsistentGroupChatsSettings() async throws {
@Test
func inconsistentGroupChatsSettings() async throws {
notificationSettingsProxy.getDefaultRoomNotificationModeIsEncryptedIsOneToOneClosure = { isEncrypted, isOneToOne in
switch (isEncrypted, isOneToOne) {
case (true, false):
@@ -92,11 +97,12 @@ class NotificationSettingsScreenViewModelTests: XCTestCase {
try await deferred.fulfill()
XCTAssertEqual(context.viewState.settings?.groupChatsMode, .allMessages)
XCTAssertEqual(context.viewState.settings?.inconsistentSettings, [.init(chatType: .groupChat, isEncrypted: false)])
#expect(context.viewState.settings?.groupChatsMode == .allMessages)
#expect(context.viewState.settings?.inconsistentSettings == [.init(chatType: .groupChat, isEncrypted: false)])
}
func testInconsistentDirectChatsSettings() async throws {
@Test
func inconsistentDirectChatsSettings() async throws {
notificationSettingsProxy.getDefaultRoomNotificationModeIsEncryptedIsOneToOneClosure = { isEncrypted, isOneToOne in
switch (isEncrypted, isOneToOne) {
case (true, true):
@@ -114,11 +120,12 @@ class NotificationSettingsScreenViewModelTests: XCTestCase {
try await deferred.fulfill()
XCTAssertEqual(context.viewState.settings?.directChatsMode, .allMessages)
XCTAssertEqual(context.viewState.settings?.inconsistentSettings, [.init(chatType: .oneToOneChat, isEncrypted: false)])
#expect(context.viewState.settings?.directChatsMode == .allMessages)
#expect(context.viewState.settings?.inconsistentSettings == [.init(chatType: .oneToOneChat, isEncrypted: false)])
}
func testFixInconsistentSettings() async throws {
@Test
func fixInconsistentSettings() async throws {
// Initialize with a configuration mismatch where encrypted one-to-one chats is `.allMessages` and unencrypted one-to-one chats is `.mentionsAndKeywordsOnly`
notificationSettingsProxy.getDefaultRoomNotificationModeIsEncryptedIsOneToOneClosure = { isEncrypted, isOneToOne in
switch (isEncrypted, isOneToOne) {
@@ -137,8 +144,8 @@ class NotificationSettingsScreenViewModelTests: XCTestCase {
try await deferredSettings.fulfill()
XCTAssertEqual(context.viewState.settings?.directChatsMode, .allMessages)
XCTAssertEqual(context.viewState.settings?.inconsistentSettings, [.init(chatType: .oneToOneChat, isEncrypted: false)])
#expect(context.viewState.settings?.directChatsMode == .allMessages)
#expect(context.viewState.settings?.inconsistentSettings == [.init(chatType: .oneToOneChat, isEncrypted: false)])
let deferredMismatch = deferFulfillment(viewModel.context.observe(\.viewState.fixingConfigurationMismatch),
transitionValues: [false, true, false])
@@ -148,14 +155,15 @@ class NotificationSettingsScreenViewModelTests: XCTestCase {
try await deferredMismatch.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)
#expect(notificationSettingsProxy.setDefaultRoomNotificationModeIsEncryptedIsOneToOneModeCallsCount == 1)
let callArguments = notificationSettingsProxy.setDefaultRoomNotificationModeIsEncryptedIsOneToOneModeReceivedArguments
XCTAssertEqual(callArguments?.isEncrypted, false)
XCTAssertEqual(callArguments?.isOneToOne, true)
XCTAssertEqual(callArguments?.mode, .allMessages)
#expect(callArguments?.isEncrypted == false)
#expect(callArguments?.isOneToOne == true)
#expect(callArguments?.mode == .allMessages)
}
func testFixAllInconsistentSettings() async throws {
@Test
func fixAllInconsistentSettings() async throws {
// Initialize with a configuration mismatch where
// - encrypted one-to-one chats is `.allMessages` and unencrypted one-to-one chats is `.mentionsAndKeywordsOnly`
// - encrypted group chats is `.allMessages` and unencrypted group chats is `.mentionsAndKeywordsOnly`
@@ -174,8 +182,8 @@ class NotificationSettingsScreenViewModelTests: XCTestCase {
try await deferredSettings.fulfill()
XCTAssertEqual(context.viewState.settings?.directChatsMode, .allMessages)
XCTAssertEqual(context.viewState.settings?.inconsistentSettings, [.init(chatType: .groupChat, isEncrypted: false), .init(chatType: .oneToOneChat, isEncrypted: false)])
#expect(context.viewState.settings?.directChatsMode == .allMessages)
#expect(context.viewState.settings?.inconsistentSettings == [.init(chatType: .groupChat, isEncrypted: false), .init(chatType: .oneToOneChat, isEncrypted: false)])
var deferredMismatch = deferFulfillment(viewModel.context.observe(\.viewState.fixingConfigurationMismatch)) { $0 }
@@ -188,19 +196,20 @@ class NotificationSettingsScreenViewModelTests: XCTestCase {
try await deferredMismatch.fulfill()
// All problems should be fixed
XCTAssertEqual(notificationSettingsProxy.setDefaultRoomNotificationModeIsEncryptedIsOneToOneModeCallsCount, 2)
#expect(notificationSettingsProxy.setDefaultRoomNotificationModeIsEncryptedIsOneToOneModeCallsCount == 2)
let callArguments = notificationSettingsProxy.setDefaultRoomNotificationModeIsEncryptedIsOneToOneModeReceivedInvocations
// Ensure we fix the invalid unencrypted group chats setting (it should be set to `.allMessages` to match encrypted group chats)
XCTAssertEqual(callArguments[0].isEncrypted, false)
XCTAssertEqual(callArguments[0].isOneToOne, false)
XCTAssertEqual(callArguments[0].mode, .allMessages)
#expect(callArguments[0].isEncrypted == false)
#expect(callArguments[0].isOneToOne == false)
#expect(callArguments[0].mode == .allMessages)
// Ensure we fix the invalid unencrypted one-to-one chats setting (it should be set to `.allMessages` to match encrypted one-to-one chats)
XCTAssertEqual(callArguments[1].isEncrypted, false)
XCTAssertEqual(callArguments[1].isOneToOne, true)
XCTAssertEqual(callArguments[1].mode, .allMessages)
#expect(callArguments[1].isEncrypted == false)
#expect(callArguments[1].isOneToOne == true)
#expect(callArguments[1].mode == .allMessages)
}
func testToggleRoomMentionOff() async throws {
@Test
func toggleRoomMentionOff() async throws {
notificationSettingsProxy.isRoomMentionEnabledReturnValue = true
let deferredState = deferFulfillment(viewModel.context.observe(\.viewState.settings)) { $0 != nil }
@@ -219,11 +228,12 @@ class NotificationSettingsScreenViewModelTests: XCTestCase {
try await deferred.fulfill()
XCTAssert(notificationSettingsProxy.setRoomMentionEnabledEnabledCalled)
XCTAssertEqual(notificationSettingsProxy.setRoomMentionEnabledEnabledReceivedEnabled, false)
#expect(notificationSettingsProxy.setRoomMentionEnabledEnabledCalled)
#expect(notificationSettingsProxy.setRoomMentionEnabledEnabledReceivedEnabled == false)
}
func testToggleRoomMentionOn() async throws {
@Test
func toggleRoomMentionOn() async throws {
notificationSettingsProxy.isRoomMentionEnabledReturnValue = false
let deferredInitialFetch = deferFulfillment(viewModel.context.observe(\.viewState.settings)) { $0 != nil }
@@ -241,11 +251,12 @@ class NotificationSettingsScreenViewModelTests: XCTestCase {
try await deferred.fulfill()
XCTAssert(notificationSettingsProxy.setRoomMentionEnabledEnabledCalled)
XCTAssertEqual(notificationSettingsProxy.setRoomMentionEnabledEnabledReceivedEnabled, true)
#expect(notificationSettingsProxy.setRoomMentionEnabledEnabledCalled)
#expect(notificationSettingsProxy.setRoomMentionEnabledEnabledReceivedEnabled == true)
}
func testToggleRoomMentionFailure() async throws {
@Test
func toggleRoomMentionFailure() async throws {
notificationSettingsProxy.setRoomMentionEnabledEnabledThrowableError = NotificationSettingsError.Generic(msg: "error")
notificationSettingsProxy.isRoomMentionEnabledReturnValue = false
@@ -267,10 +278,11 @@ class NotificationSettingsScreenViewModelTests: XCTestCase {
try await deferred.fulfill()
XCTAssertNotNil(context.alertInfo)
#expect(context.alertInfo != nil)
}
func testToggleCallsOff() async throws {
@Test
func toggleCallsOff() async throws {
notificationSettingsProxy.isCallEnabledReturnValue = true
let deferredInitialFetch = deferFulfillment(viewModel.context.observe(\.viewState.settings)) { $0 != nil }
@@ -288,11 +300,12 @@ class NotificationSettingsScreenViewModelTests: XCTestCase {
try await deferred.fulfill()
XCTAssert(notificationSettingsProxy.setCallEnabledEnabledCalled)
XCTAssertEqual(notificationSettingsProxy.setCallEnabledEnabledReceivedEnabled, false)
#expect(notificationSettingsProxy.setCallEnabledEnabledCalled)
#expect(notificationSettingsProxy.setCallEnabledEnabledReceivedEnabled == false)
}
func testToggleCallsOn() async throws {
@Test
func toggleCallsOn() async throws {
notificationSettingsProxy.isCallEnabledReturnValue = false
let deferredInitialFetch = deferFulfillment(viewModel.context.observe(\.viewState.settings)) { $0 != nil }
@@ -311,11 +324,12 @@ class NotificationSettingsScreenViewModelTests: XCTestCase {
try await deferred.fulfill()
XCTAssert(notificationSettingsProxy.setCallEnabledEnabledCalled)
XCTAssertEqual(notificationSettingsProxy.setCallEnabledEnabledReceivedEnabled, true)
#expect(notificationSettingsProxy.setCallEnabledEnabledCalled)
#expect(notificationSettingsProxy.setCallEnabledEnabledReceivedEnabled == true)
}
func testToggleCallsFailure() async throws {
@Test
func toggleCallsFailure() async throws {
notificationSettingsProxy.setCallEnabledEnabledThrowableError = NotificationSettingsError.Generic(msg: "error")
notificationSettingsProxy.isCallEnabledReturnValue = false
@@ -337,10 +351,11 @@ class NotificationSettingsScreenViewModelTests: XCTestCase {
try await deferred.fulfill()
XCTAssertNotNil(context.alertInfo)
#expect(context.alertInfo != nil)
}
func testToggleInvitationsOff() async throws {
@Test
func toggleInvitationsOff() async throws {
notificationSettingsProxy.isInviteForMeEnabledReturnValue = true
let deferredInitialFetch = deferFulfillment(viewModel.context.observe(\.viewState.settings)) { $0 != nil }
@@ -358,11 +373,12 @@ class NotificationSettingsScreenViewModelTests: XCTestCase {
try await deferred.fulfill()
XCTAssert(notificationSettingsProxy.setInviteForMeEnabledEnabledCalled)
XCTAssertEqual(notificationSettingsProxy.setInviteForMeEnabledEnabledReceivedEnabled, false)
#expect(notificationSettingsProxy.setInviteForMeEnabledEnabledCalled)
#expect(notificationSettingsProxy.setInviteForMeEnabledEnabledReceivedEnabled == false)
}
func testToggleInvitationsOn() async throws {
@Test
func toggleInvitationsOn() async throws {
notificationSettingsProxy.isInviteForMeEnabledReturnValue = false
let deferredInitialFetch = deferFulfillment(viewModel.context.observe(\.viewState.settings)) { $0 != nil }
@@ -381,11 +397,12 @@ class NotificationSettingsScreenViewModelTests: XCTestCase {
try await deferred.fulfill()
XCTAssert(notificationSettingsProxy.setInviteForMeEnabledEnabledCalled)
XCTAssertEqual(notificationSettingsProxy.setInviteForMeEnabledEnabledReceivedEnabled, true)
#expect(notificationSettingsProxy.setInviteForMeEnabledEnabledCalled)
#expect(notificationSettingsProxy.setInviteForMeEnabledEnabledReceivedEnabled == true)
}
func testToggleInvitesFailure() async throws {
@Test
func toggleInvitesFailure() async throws {
notificationSettingsProxy.setInviteForMeEnabledEnabledThrowableError = NotificationSettingsError.Generic(msg: "error")
notificationSettingsProxy.isInviteForMeEnabledReturnValue = false
@@ -407,6 +424,6 @@ class NotificationSettingsScreenViewModelTests: XCTestCase {
try await deferred.fulfill()
XCTAssertNotNil(context.alertInfo)
#expect(context.alertInfo != nil)
}
}