Add Block user to Report Content screen. (#742)

This commit is contained in:
Doug
2023-04-03 10:21:24 +01:00
committed by GitHub
parent bfe8331e05
commit 192974ff3a
33 changed files with 220 additions and 130 deletions

View File

@@ -114,7 +114,7 @@
"preference_rageshake" = "Rageshake to report bug";
"rageshake_detection_dialog_content" = "You seem to be shaking the phone in frustration. Would you like to open the bug report screen?";
"rageshake_dialog_content" = "You seem to be shaking the phone in frustration. Would you like to open the bug report screen?";
"report_content_explanation" = "Reporting this message will send its unique event ID to the administrator of your homeserver. If messages in this room are encrypted, your homeserver administrator will not be able to read the message text or view any files or images.";
"report_content_explanation" = "This message will be reported to your homeservers administrator. They will not be able to read any encrypted messages.";
"report_content_hint" = "Reason for reporting this content";
"rich_text_editor_bullet_list" = "Toggle bullet list";
"rich_text_editor_code_block" = "Toggle code block";
@@ -166,6 +166,8 @@
"screen_login_username_hint" = "Username";
"screen_onboarding_welcome_subtitle" = "Welcome to the %1$@ Beta. Supercharged, for speed and simplicity.";
"screen_onboarding_welcome_title" = "Be in your Element";
"screen_report_content_block_user" = "Block user";
"screen_report_content_block_user_hint" = "Check if you want to hide all current and future messages from this user";
"screen_room_details_encryption_enabled_subtitle" = "Messages are secured with locks. Only you and the recipients have the unique keys to unlock them.";
"screen_room_details_encryption_enabled_title" = "Message encryption enabled";
"screen_room_details_invite_people_title" = "Invite people";

View File

@@ -114,7 +114,7 @@
"preference_rageshake" = "Rageshake to report bug";
"rageshake_detection_dialog_content" = "You seem to be shaking the phone in frustration. Would you like to open the bug report screen?";
"rageshake_dialog_content" = "You seem to be shaking the phone in frustration. Would you like to open the bug report screen?";
"report_content_explanation" = "Reporting this message will send its unique event ID to the administrator of your homeserver. If messages in this room are encrypted, your homeserver administrator will not be able to read the message text or view any files or images.";
"report_content_explanation" = "This message will be reported to your homeservers administrator. They will not be able to read any encrypted messages.";
"report_content_hint" = "Reason for reporting this content";
"rich_text_editor_bullet_list" = "Toggle bullet list";
"rich_text_editor_code_block" = "Toggle code block";
@@ -166,6 +166,8 @@
"screen_login_username_hint" = "Username";
"screen_onboarding_welcome_subtitle" = "Welcome to the %1$@ Beta. Supercharged, for speed and simplicity.";
"screen_onboarding_welcome_title" = "Be in your Element";
"screen_report_content_block_user" = "Block user";
"screen_report_content_block_user_hint" = "Check if you want to hide all current and future messages from this user";
"screen_room_details_encryption_enabled_subtitle" = "Messages are secured with locks. Only you and the recipients have the unique keys to unlock them.";
"screen_room_details_encryption_enabled_title" = "Message encryption enabled";
"screen_room_details_invite_people_title" = "Invite people";

View File

@@ -114,7 +114,7 @@
"preference_rageshake" = "Rageshake to report bug";
"rageshake_detection_dialog_content" = "You seem to be shaking the phone in frustration. Would you like to open the bug report screen?";
"rageshake_dialog_content" = "You seem to be shaking the phone in frustration. Would you like to open the bug report screen?";
"report_content_explanation" = "Reporting this message will send its unique event ID to the administrator of your homeserver. If messages in this room are encrypted, your homeserver administrator will not be able to read the message text or view any files or images.";
"report_content_explanation" = "This message will be reported to your homeservers administrator. They will not be able to read any encrypted messages.";
"report_content_hint" = "Reason for reporting this content";
"rich_text_editor_bullet_list" = "Toggle bullet list";
"rich_text_editor_code_block" = "Toggle code block";
@@ -166,6 +166,8 @@
"screen_login_username_hint" = "Username";
"screen_onboarding_welcome_subtitle" = "Welcome to the %1$@ Beta. Supercharged, for speed and simplicity.";
"screen_onboarding_welcome_title" = "Be in your Element";
"screen_report_content_block_user" = "Block user";
"screen_report_content_block_user_hint" = "Check if you want to hide all current and future messages from this user";
"screen_room_details_encryption_enabled_subtitle" = "Messages are secured with locks. Only you and the recipients have the unique keys to unlock them.";
"screen_room_details_encryption_enabled_title" = "Message encryption enabled";
"screen_room_details_invite_people_title" = "Invite people";

View File

@@ -256,7 +256,7 @@ public enum L10n {
public static var rageshakeDetectionDialogContent: String { return L10n.tr("Localizable", "rageshake_detection_dialog_content") }
/// You seem to be shaking the phone in frustration. Would you like to open the bug report screen?
public static var rageshakeDialogContent: String { return L10n.tr("Localizable", "rageshake_dialog_content") }
/// Reporting this message will send its unique event ID to the administrator of your homeserver. If messages in this room are encrypted, your homeserver administrator will not be able to read the message text or view any files or images.
/// This message will be reported to your homeservers administrator. They will not be able to read any encrypted messages.
public static var reportContentExplanation: String { return L10n.tr("Localizable", "report_content_explanation") }
/// Reason for reporting this content
public static var reportContentHint: String { return L10n.tr("Localizable", "report_content_hint") }
@@ -372,6 +372,10 @@ public enum L10n {
}
/// Be in your Element
public static var screenOnboardingWelcomeTitle: String { return L10n.tr("Localizable", "screen_onboarding_welcome_title") }
/// Block user
public static var screenReportContentBlockUser: String { return L10n.tr("Localizable", "screen_report_content_block_user") }
/// Check if you want to hide all current and future messages from this user
public static var screenReportContentBlockUserHint: String { return L10n.tr("Localizable", "screen_report_content_block_user_hint") }
/// Messages are secured with locks. Only you and the recipients have the unique keys to unlock them.
public static var screenRoomDetailsEncryptionEnabledSubtitle: String { return L10n.tr("Localizable", "screen_room_details_encryption_enabled_subtitle") }
/// Message encryption enabled

View File

@@ -427,6 +427,27 @@ class RoomProxyMock: RoomProxyProtocol {
return membersReturnValue
}
}
//MARK: - ignoreUser
var ignoreUserCallsCount = 0
var ignoreUserCalled: Bool {
return ignoreUserCallsCount > 0
}
var ignoreUserReceivedUserID: String?
var ignoreUserReceivedInvocations: [String] = []
var ignoreUserReturnValue: Result<Void, RoomProxyError>!
var ignoreUserClosure: ((String) async -> Result<Void, RoomProxyError>)?
func ignoreUser(_ userID: String) async -> Result<Void, RoomProxyError> {
ignoreUserCallsCount += 1
ignoreUserReceivedUserID = userID
ignoreUserReceivedInvocations.append(userID)
if let ignoreUserClosure = ignoreUserClosure {
return await ignoreUserClosure(userID)
} else {
return ignoreUserReturnValue
}
}
//MARK: - retryDecryption
var retryDecryptionForCallsCount = 0

View File

@@ -22,6 +22,7 @@ struct A11yIdentifiers {
static let homeScreen = HomeScreen()
static let loginScreen = LoginScreen()
static let onboardingScreen = OnboardingScreen()
static let reportContent = ReportContent()
static let roomScreen = RoomScreen()
static let roomDetailsScreen = RoomDetailsScreen()
static let sessionVerificationScreen = SessionVerificationScreen()
@@ -66,6 +67,10 @@ struct A11yIdentifiers {
let signIn = "onboarding-sign_in"
let hidden = "onboarding-hidden"
}
struct ReportContent {
let ignoreUser = "report_content-ignore_user"
}
struct RoomScreen {
let name = "room-name"

View File

@@ -1,64 +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.
//
import SwiftUI
struct FormTextEditor: View {
@Binding var text: String
let placeholder: String
var editorAccessibilityIdentifier: String?
var body: some View {
ZStack(alignment: .topLeading) {
RoundedRectangle(cornerRadius: 14, style: .continuous)
.fill(Color.element.formRowBackground)
let textEditor = TextEditor(text: $text)
.tint(.element.brand)
.padding(.horizontal, 10)
.padding(.vertical, 4)
.cornerRadius(14)
.scrollContentBackground(.hidden)
if let editorAccessibilityIdentifier {
textEditor
.accessibilityIdentifier(editorAccessibilityIdentifier)
} else {
textEditor
}
if text.isEmpty {
Text(placeholder)
.font(.element.body)
.foregroundColor(Color.element.secondaryContent)
.padding(.horizontal, 16)
.padding(.vertical, 12)
.allowsHitTesting(false)
}
RoundedRectangle(cornerRadius: 14, style: .continuous)
.stroke(Color.element.quaternaryContent)
}
.frame(maxWidth: .infinity)
.frame(height: 220)
.font(.body)
}
}
struct FormTextEditor_Previews: PreviewProvider {
static var previews: some View {
FormTextEditor(text: .constant(""), placeholder: "test", editorAccessibilityIdentifier: nil)
}
}

View File

@@ -18,6 +18,7 @@ import SwiftUI
struct ReportContentCoordinatorParameters {
let itemID: String
let senderID: String
let roomProxy: RoomProxyProtocol
weak var userIndicatorController: UserIndicatorControllerProtocol?
}
@@ -36,7 +37,7 @@ final class ReportContentCoordinator: CoordinatorProtocol {
init(parameters: ReportContentCoordinatorParameters) {
self.parameters = parameters
viewModel = ReportContentViewModel(itemID: parameters.itemID, roomProxy: parameters.roomProxy)
viewModel = ReportContentViewModel(itemID: parameters.itemID, senderID: parameters.senderID, roomProxy: parameters.roomProxy)
}
// MARK: - Public

View File

@@ -29,6 +29,7 @@ struct ReportContentViewState: BindableState {
struct ReportContentViewStateBindings {
var reasonText: String
var ignoreUser: Bool
}
enum ReportContentViewAction {

View File

@@ -22,12 +22,15 @@ class ReportContentViewModel: ReportContentViewModelType, ReportContentViewModel
var callback: ((ReportContentViewModelAction) -> Void)?
private let itemID: String
private let senderID: String
private let roomProxy: RoomProxyProtocol
init(itemID: String, roomProxy: RoomProxyProtocol) {
init(itemID: String, senderID: String, roomProxy: RoomProxyProtocol) {
self.itemID = itemID
self.senderID = senderID
self.roomProxy = roomProxy
super.init(initialViewState: ReportContentViewState(bindings: ReportContentViewStateBindings(reasonText: "")))
super.init(initialViewState: ReportContentViewState(bindings: ReportContentViewStateBindings(reasonText: "", ignoreUser: false)))
}
// MARK: - Public
@@ -45,13 +48,21 @@ class ReportContentViewModel: ReportContentViewModelType, ReportContentViewModel
private func submitReport() async {
callback?(.submitStarted)
switch await roomProxy.reportContent(itemID, reason: state.bindings.reasonText) {
case .success:
MXLog.info("Submit Report Content succeeded")
callback?(.submitFinished)
case let .failure(error):
if case let .failure(error) = await roomProxy.reportContent(itemID, reason: state.bindings.reasonText) {
MXLog.error("Submit Report Content failed: \(error)")
callback?(.submitFailed(error: error))
return
}
// Ignore the sender if the user wants to.
if state.bindings.ignoreUser, case let .failure(error) = await roomProxy.ignoreUser(senderID) {
MXLog.error("Ignore user failed: \(error)")
callback?(.submitFailed(error: error))
return
}
MXLog.info("Submit Report Content succeeded")
callback?(.submitFinished)
}
}

View File

@@ -26,35 +26,43 @@ struct ReportContentScreen: View {
}
var body: some View {
ScrollView {
mainContent
.padding(.top, 50)
.padding(.horizontal, horizontalPadding)
Form {
reasonSection
ignoreUserSection
}
.scrollDismissesKeyboard(.immediately)
.background(Color.element.formBackground.ignoresSafeArea())
.compoundForm()
.navigationTitle(L10n.actionReportContent)
.navigationBarTitleDisplayMode(.inline)
.toolbar { toolbar }
.interactiveDismissDisabled()
}
/// The main content of the view to be shown in a scroll view.
var mainContent: some View {
VStack(alignment: .leading, spacing: 24) {
infoText
reasonTextEditor
private var reasonSection: some View {
Section {
TextField(L10n.reportContentHint,
text: $context.reasonText,
prompt: Text(L10n.reportContentHint).compoundFormTextFieldPlaceholder(),
axis: .vertical)
.lineLimit(4, reservesSpace: true)
.textFieldStyle(.compoundForm)
} footer: {
Text(L10n.reportContentExplanation)
.compoundFormSectionFooter()
}
.compoundFormSection()
}
private var infoText: some View {
Text(L10n.reportContentExplanation)
.font(.element.body)
.foregroundColor(Color.element.primaryContent)
}
private var reasonTextEditor: some View {
FormTextEditor(text: $context.reasonText, placeholder: L10n.reportContentHint)
private var ignoreUserSection: some View {
Section {
Toggle(L10n.screenReportContentBlockUser, isOn: $context.ignoreUser)
.toggleStyle(.compoundForm)
.accessibilityIdentifier(A11yIdentifiers.reportContent.ignoreUser)
} footer: {
Text(L10n.screenReportContentBlockUserHint)
.compoundFormSectionFooter()
}
}
@ToolbarContentBuilder
@@ -76,9 +84,13 @@ struct ReportContentScreen: View {
// MARK: - Previews
struct ReportContent_Previews: PreviewProvider {
static let viewModel = ReportContentViewModel(itemID: "", roomProxy: RoomProxyMock(with: .init(displayName: nil)))
static let viewModel = ReportContentViewModel(itemID: "",
senderID: "",
roomProxy: RoomProxyMock(with: .init(displayName: nil)))
static var previews: some View {
ReportContentScreen(context: viewModel.context)
NavigationStack {
ReportContentScreen(context: viewModel.context)
}
}
}

View File

@@ -58,10 +58,10 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
self.displayRoomDetails()
case .displayMediaFile(let file, let title):
self.displayFilePreview(for: file, with: title)
case .displayEmojiPicker(let itemId):
self.displayEmojiPickerScreen(for: itemId)
case .displayReportContent(let itemId):
self.displayReportContent(for: itemId)
case .displayEmojiPicker(let itemID):
self.displayEmojiPickerScreen(for: itemID)
case .displayReportContent(let itemID, let senderID):
self.displayReportContent(for: itemID, from: senderID)
case .displayCameraPicker:
self.displayMediaPickerWithSource(.camera)
case .displayMediaPicker:
@@ -159,10 +159,11 @@ final class RoomScreenCoordinator: CoordinatorProtocol {
navigationStackCoordinator.push(coordinator)
}
private func displayReportContent(for itemId: String) {
private func displayReportContent(for itemID: String, from senderID: String) {
let navigationCoordinator = NavigationStackCoordinator()
let userIndicatorController = UserIndicatorController(rootCoordinator: NavigationStackCoordinator())
let parameters = ReportContentCoordinatorParameters(itemID: itemId,
let userIndicatorController = UserIndicatorController(rootCoordinator: navigationCoordinator)
let parameters = ReportContentCoordinatorParameters(itemID: itemID,
senderID: senderID,
roomProxy: parameters.roomProxy,
userIndicatorController: userIndicatorController)
let coordinator = ReportContentCoordinator(parameters: parameters)

View File

@@ -21,8 +21,8 @@ import UIKit
enum RoomScreenViewModelAction {
case displayRoomDetails
case displayMediaFile(file: MediaFileHandleProxy, title: String?)
case displayEmojiPicker(itemId: String)
case displayReportContent(itemId: String)
case displayEmojiPicker(itemID: String)
case displayReportContent(itemID: String, senderID: String)
case displayCameraPicker
case displayMediaPicker
case displayDocumentPicker

View File

@@ -155,7 +155,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
private func itemDoubleTapped(with itemId: String) {
guard let item = state.items.first(where: { $0.id == itemId }), item.isReactable else { return }
callback?(.displayEmojiPicker(itemId: itemId))
callback?(.displayEmojiPicker(itemID: itemId))
}
private func buildTimelineViews() {
@@ -294,7 +294,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
switch action {
case .react:
callback?(.displayEmojiPicker(itemId: item.id))
callback?(.displayEmojiPicker(itemID: item.id))
case .copy:
UIPasteboard.general.string = item.body
case .edit:
@@ -327,7 +327,7 @@ class RoomScreenViewModel: RoomScreenViewModelType, RoomScreenViewModelProtocol
await timelineController.retryDecryption(for: sessionID)
}
case .report:
callback?(.displayReportContent(itemId: itemID))
callback?(.displayReportContent(itemID: itemID, senderID: item.sender.id))
}
if action.switchToDefaultComposer {

View File

@@ -288,6 +288,22 @@ class RoomProxy: RoomProxyProtocol {
return .failure(.failedRetrievingMembers)
}
}
func ignoreUser(_ userID: String) async -> Result<Void, RoomProxyError> {
sendMessageBackgroundTask = backgroundTaskService.startBackgroundTask(withName: backgroundTaskName, isReusable: true)
defer {
sendMessageBackgroundTask?.stop()
}
return await Task.dispatch(on: userInitiatedDispatchQueue) {
do {
try self.room.ignoreUser(userId: userID)
return .success(())
} catch {
return .failure(.failedReportingContent)
}
}
}
@MainActor
private func buildRoomMemberProxies(members: [RoomMember]) -> [RoomMemberProxy] {

View File

@@ -76,10 +76,12 @@ protocol RoomProxyProtocol {
func editMessage(_ newMessage: String, original eventID: String) async -> Result<Void, RoomProxyError>
func redact(_ eventID: String) async -> Result<Void, RoomProxyError>
func reportContent(_ eventID: String, reason: String?) async -> Result<Void, RoomProxyError>
func members() async -> Result<[RoomMemberProxyProtocol], RoomProxyError>
func ignoreUser(_ userID: String) async -> Result<Void, RoomProxyError>
func retryDecryption(for sessionID: String) async

View File

@@ -305,7 +305,9 @@ class MockScreen: Identifiable {
return navigationStackCoordinator
case .reportContent:
let navigationStackCoordinator = NavigationStackCoordinator()
let coordinator = ReportContentCoordinator(parameters: .init(itemID: "test", roomProxy: RoomProxyMock(with: .init(displayName: "test"))))
let coordinator = ReportContentCoordinator(parameters: .init(itemID: "test",
senderID: RoomMemberProxyMock.mockAlice.userID,
roomProxy: RoomProxyMock(with: .init(displayName: "test"))))
navigationStackCoordinator.setRootCoordinator(coordinator)
return navigationStackCoordinator
case .startChat:

View File

@@ -20,6 +20,21 @@ import XCTest
class ReportContentScreenUITests: XCTestCase {
func testInitialStateComponents() {
let app = Application.launch(.reportContent)
app.assertScreenshot(.reportContent)
app.assertScreenshot(.reportContent, step: 0)
}
func testToggleIgnoreUser() {
let app = Application.launch(.reportContent)
// Don't know why, but there's an issue on CI where the toggle is tapped but doesn't respond. Waiting for
// it fixes this (even it it already exists). Reproducible by running the test after quitting the simulator.
let sendingLogsToggle = app.switches[A11yIdentifiers.reportContent.ignoreUser]
XCTAssertTrue(sendingLogsToggle.waitForExistence(timeout: 1))
XCTAssertFalse(sendingLogsToggle.isOn)
sendingLogsToggle.tap()
XCTAssertTrue(sendingLogsToggle.isOn)
app.assertScreenshot(.reportContent, step: 1)
}
}

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c5e16b7b56132cb5c7c30d502911dcb29b41efe3228c10053576fe4b34e5178e
size 94846

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:06d95ccd192c2c9550e4a405b76352d8f75d86a1d72cbc42dfab15743bcac2de
size 96766

View File

@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:71b413b0ec66b086cbcb077defbf5890332f56d2c865fb0ddc2ec251224a7000
size 102265

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5a330b8acd8f2a4ebdda45d125c29c17e21f6cd8fb2397443d7527988ff5a943
size 117146

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:63627788babeceb6940989acf8e07c7244d50b4ab84c97bc74b4e19b9ee6ce47
size 120785

View File

@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8c816fa74f027b401c84513b2c4d8f912fb91e3229e4b4fa1cdda17db2d88be4
size 131307

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c253c4c823104c55bf916c9d112ad87e4de3db24fd324a61350a834584edf275
size 116667

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:324756300a6ca0abca19892b5ff4fea9bcd1718fb9375ed87de8e9e981a06908
size 118557

View File

@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a5f69a58df0557ef1e1adb04a1dbe9b245a003684f0b96228a7a438da168ec25
size 133827

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4781fa334f9c08adf7ba7e3b0885e9b04a7f28c7e1e407553a74171570775a41
size 162791

View File

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b4120dca6e5eb55094f9e301bb3fa6e538b6f9a1226f29a2ab4832886661b429
size 163825

View File

@@ -1,3 +0,0 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c47eb8afbde353f625bb0d70712a62df83e769284a4c1a7d0918f27369414d65
size 193082

View File

@@ -97,6 +97,8 @@ private class MockMediaLoadingClient: ClientProtocol {
func getMediaFile(source: MatrixRustSDK.MediaSource, mimeType: String) throws -> MatrixRustSDK.MediaFileHandle { fatalError() }
func getProfile(userId: String) throws -> MatrixRustSDK.UserProfile { fatalError() }
func getSessionVerificationController() throws -> MatrixRustSDK.SessionVerificationController { fatalError() }
func fullSlidingSync() throws -> MatrixRustSDK.SlidingSync { fatalError() }

View File

@@ -20,10 +20,52 @@ import XCTest
@MainActor
class ReportContentScreenViewModelTests: XCTestCase {
func testInitialState() {
let viewModel = ReportContentViewModel(itemID: "test-id", roomProxy: RoomProxyMock(with: .init(displayName: "test")))
let context = viewModel.context
XCTAssertEqual(context.reasonText, "")
let itemID = "test-id"
let senderID = "@meany:server.com"
let reportReason = "I don't like it."
func testReportContent() async {
// Given the report content view for some content.
let roomProxy = RoomProxyMock(with: .init(displayName: "test"))
roomProxy.reportContentReasonReturnValue = .success(())
let viewModel = ReportContentViewModel(itemID: itemID,
senderID: senderID,
roomProxy: roomProxy)
// When reporting the content without ignoring the user.
viewModel.state.bindings.reasonText = reportReason
viewModel.state.bindings.ignoreUser = false
viewModel.context.send(viewAction: .submit)
await Task.yield()
// Then the content should be reported, but the user should not be included.
XCTAssertEqual(roomProxy.reportContentReasonCallsCount, 1, "The content should always be reported.")
XCTAssertEqual(roomProxy.reportContentReasonReceivedArguments?.eventID, itemID, "The event ID should match the content being reported.")
XCTAssertEqual(roomProxy.reportContentReasonReceivedArguments?.reason, reportReason, "The reason should match the user input.")
XCTAssertEqual(roomProxy.ignoreUserCallsCount, 0, "A call to ignore a user should not have been made.")
XCTAssertNil(roomProxy.ignoreUserReceivedUserID, "The sender shouldn't have been ignored.")
}
func testReportIgnoringSender() async {
// Given the report content view for some content.
let roomProxy = RoomProxyMock(with: .init(displayName: "test"))
roomProxy.reportContentReasonReturnValue = .success(())
roomProxy.ignoreUserReturnValue = .success(())
let viewModel = ReportContentViewModel(itemID: itemID,
senderID: senderID,
roomProxy: roomProxy)
// When reporting the content and also ignoring the user.
viewModel.state.bindings.reasonText = reportReason
viewModel.state.bindings.ignoreUser = true
viewModel.context.send(viewAction: .submit)
await Task.yield()
// Then the content should be reported, and the user should be ignored.
XCTAssertEqual(roomProxy.reportContentReasonCallsCount, 1, "The content should always be reported.")
XCTAssertEqual(roomProxy.reportContentReasonReceivedArguments?.eventID, itemID, "The event ID should match the content being reported.")
XCTAssertEqual(roomProxy.reportContentReasonReceivedArguments?.reason, reportReason, "The reason should match the user input.")
XCTAssertEqual(roomProxy.ignoreUserCallsCount, 1, "A call should have been made to ignore the sender.")
XCTAssertEqual(roomProxy.ignoreUserReceivedUserID, senderID, "The ignored user ID should match the sender.")
}
}

1
changelog.d/115.change Normal file
View File

@@ -0,0 +1 @@
Add Block user toggle to Report Content screen.