mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +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,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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user