Files
element-x-ios/UnitTests/Sources/ManageRoomMemberSheetViewModelTests.swift
Danny 1a72a4e17b Allow tapping the avatar in the room member bottom sheet to view it fullscreen (#5989)
* Extract a shared helper for loading a media preview

The four screens that show a fullscreen avatar each had their own copy of the same
load-and-wrap logic. Move it to MediaPreviewItem.load, with the mime type as a
parameter defaulting to JPEG, and leave each screen's loading indicator where it is.

Signed-off-by: Daniel Anderson <daniel.anderson@toptal.com>

* Only dismiss a QuickLook preview that is still presented

UIKit forwards dismiss() up the presentation chain when the receiver has nothing
presented, so dismissing the avatar view was dismissing the profile details sheet
also.

Signed-off-by: Daniel Anderson <daniel.anderson@toptal.com>

* Allow tapping the avatar in the room member bottom sheet to view it fullscreen

Signed-off-by: Daniel Anderson <daniel.anderson@toptal.com>

---------

Signed-off-by: Daniel Anderson <daniel.anderson@toptal.com>
2026-08-07 13:11:40 +01:00

119 lines
6.1 KiB
Swift

//
// Copyright 2025 Element Creations Ltd.
// Copyright 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE files in the repository root for full details.
//
@testable import ElementX
import Testing
@MainActor
struct ManageRoomMemberSheetViewModelTests {
private var viewModel: ManageRoomMemberSheetViewModel!
private var context: ManageRoomMemberSheetViewModel.Context! {
viewModel.context
}
@Test
mutating func kick() async throws {
let testReason = "Kick Test"
let roomProxy = JoinedRoomProxyMock(.init(members: [RoomMemberProxyMock.mockAdmin, RoomMemberProxyMock.mockAlice]))
var kickCalled = false
roomProxy.kickUserReasonClosure = { userID, reason in
kickCalled = true
#expect(userID == RoomMemberProxyMock.mockAlice.userID)
#expect(reason == testReason)
return .success(())
}
viewModel = ManageRoomMemberSheetViewModel(memberDetails: .memberDetails(roomMember: .init(withProxy: RoomMemberProxyMock.mockAlice)),
permissions: .init(canKick: true, canBan: true, ownPowerLevel: RoomMemberProxyMock.mockAdmin.powerLevel),
roomProxy: roomProxy,
userIndicatorController: UserIndicatorControllerMock(),
analyticsService: AnalyticsServiceMock(.init()),
mediaProvider: MediaProviderMock(.init()))
let deferred = deferFulfillment(context.observe(\.viewState.bindings.alertInfo)) { $0 != nil }
let deferredAction = deferFulfillment(viewModel.actions) { action in
action == .dismiss(shouldShowDetails: false)
}
context.send(viewAction: .kick)
try await deferred.fulfill()
context.alertInfo?.textFields?[0].text.wrappedValue = testReason
context.alertInfo?.secondaryButton?.action?()
try await deferredAction.fulfill()
#expect(kickCalled)
}
@Test
mutating func ban() async throws {
let testReason = "Ban Test"
let roomProxy = JoinedRoomProxyMock(.init(members: [RoomMemberProxyMock.mockAdmin, RoomMemberProxyMock.mockAlice]))
var banCalled = false
roomProxy.banUserReasonClosure = { userID, reason in
banCalled = true
#expect(userID == RoomMemberProxyMock.mockAlice.userID)
#expect(reason == testReason)
return .success(())
}
viewModel = ManageRoomMemberSheetViewModel(memberDetails: .memberDetails(roomMember: .init(withProxy: RoomMemberProxyMock.mockAlice)),
permissions: .init(canKick: true, canBan: true, ownPowerLevel: RoomMemberProxyMock.mockAdmin.powerLevel),
roomProxy: roomProxy,
userIndicatorController: UserIndicatorControllerMock(),
analyticsService: AnalyticsServiceMock(.init()),
mediaProvider: MediaProviderMock(.init()))
let deferred = deferFulfillment(context.observe(\.viewState.bindings.alertInfo)) { $0 != nil }
context.send(viewAction: .ban)
try await deferred.fulfill()
let deferredAction = deferFulfillment(viewModel.actions) { action in
action == .dismiss(shouldShowDetails: false)
}
context.alertInfo?.textFields?[0].text.wrappedValue = testReason
context.alertInfo?.secondaryButton?.action?()
try await deferredAction.fulfill()
#expect(banCalled)
}
@Test
mutating func displayDetails() async throws {
let roomProxy = JoinedRoomProxyMock(.init(members: [RoomMemberProxyMock.mockAdmin, RoomMemberProxyMock.mockAlice]))
viewModel = ManageRoomMemberSheetViewModel(memberDetails: .memberDetails(roomMember: .init(withProxy: RoomMemberProxyMock.mockAlice)),
permissions: .init(canKick: true, canBan: true, ownPowerLevel: RoomMemberProxyMock.mockAdmin.powerLevel),
roomProxy: roomProxy,
userIndicatorController: UserIndicatorControllerMock(),
analyticsService: AnalyticsServiceMock(.init()),
mediaProvider: MediaProviderMock(.init()))
let deferredAction = deferFulfillment(viewModel.actions) { action in
action == .dismiss(shouldShowDetails: true)
}
context.send(viewAction: .displayDetails)
try await deferredAction.fulfill()
#expect(context.alertInfo == nil)
}
@Test
mutating func displayAvatar() async throws {
let member = RoomMemberDetails(withProxy: RoomMemberProxyMock.mockDan)
viewModel = ManageRoomMemberSheetViewModel(memberDetails: .memberDetails(roomMember: member),
permissions: .init(canKick: true, canBan: true, ownPowerLevel: RoomMemberProxyMock.mockAdmin.powerLevel),
roomProxy: JoinedRoomProxyMock(.init()),
userIndicatorController: UserIndicatorControllerMock(),
analyticsService: AnalyticsServiceMock(.init()),
mediaProvider: MediaProviderMock(.init()))
let avatarURL = try #require(member.avatarURL)
let deferred = deferFulfillment(context.observe(\.viewState.bindings.mediaPreviewItem)) { $0 != nil }
context.send(viewAction: .displayAvatar(avatarURL))
try await deferred.fulfill()
#expect(context.mediaPreviewItem?.previewItemTitle == member.name)
}
}