mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-10-31 16:24:11 +00:00
ff5dcb040d
Removes the ServiceLocator singleton and instead directly injects the dependencies it was holding for more control over instantiation of the various component subtrees and better visibility over how they're being used.
504 lines
28 KiB
Swift
504 lines
28 KiB
Swift
//
|
|
// Copyright 2025 Element Creations Ltd.
|
|
// Copyright 2024-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.
|
|
//
|
|
|
|
import Combine
|
|
@testable import ElementX
|
|
import Foundation
|
|
import MatrixRustSDK
|
|
import MatrixRustSDKMocks
|
|
import Testing
|
|
|
|
@MainActor
|
|
final class RoomScreenViewModelTests {
|
|
private var viewModel: RoomScreenViewModel!
|
|
|
|
private let appSettings: AppSettings
|
|
|
|
init() async throws {
|
|
AppSettings.resetAllSettings()
|
|
appSettings = AppSettings()
|
|
}
|
|
|
|
deinit {
|
|
AppSettings.resetAllSettings()
|
|
}
|
|
|
|
@Test
|
|
func pinnedEventsBanner() async throws {
|
|
var configuration = JoinedRoomProxyMockConfiguration()
|
|
let (stream, continuation) = AsyncStream.makeStream(of: TimelineProxyProtocol.self)
|
|
let infoSubject = CurrentValueSubject<RoomInfoProxyProtocol, Never>(RoomInfoProxyMock(configuration))
|
|
let roomProxyMock = JoinedRoomProxyMock(configuration)
|
|
// setup a way to inject the mock of the pinned events timeline
|
|
roomProxyMock.pinnedEventsTimelineClosure = {
|
|
guard let timeline = await stream.first() else {
|
|
fatalError()
|
|
}
|
|
|
|
return .success(timeline)
|
|
}
|
|
// setup the room proxy actions publisher
|
|
roomProxyMock.underlyingInfoPublisher = infoSubject.asCurrentValuePublisher()
|
|
let viewModel = RoomScreenViewModel(userSession: UserSessionMock(.init()),
|
|
roomProxy: roomProxyMock,
|
|
initialSelectedPinnedEventID: nil,
|
|
ongoingCallRoomIDPublisher: .init(.init(nil)),
|
|
appSettings: appSettings,
|
|
appHooks: AppHooks(),
|
|
analyticsService: .mock(settings: appSettings),
|
|
userIndicatorController: UserIndicatorControllerMock.default)
|
|
self.viewModel = viewModel
|
|
|
|
// check if in the default state is not showing but is indeed loading
|
|
var deferred = deferFulfillment(viewModel.context.$viewState) { viewState in
|
|
viewState.pinnedEventsBannerState.count == 0
|
|
}
|
|
try await deferred.fulfill()
|
|
#expect(viewModel.context.viewState.pinnedEventsBannerState.isLoading)
|
|
#expect(!viewModel.context.viewState.shouldShowPinnedEventsBanner)
|
|
|
|
// check if if after the pinned event ids are set the banner is still in a loading state, but is both loading and showing with a counter
|
|
deferred = deferFulfillment(viewModel.context.$viewState) { viewState in
|
|
viewState.pinnedEventsBannerState.count == 2
|
|
}
|
|
configuration.pinnedEventIDs = ["test1", "test2"]
|
|
infoSubject.send(RoomInfoProxyMock(configuration))
|
|
try await deferred.fulfill()
|
|
#expect(viewModel.context.viewState.pinnedEventsBannerState.isLoading)
|
|
#expect(viewModel.context.viewState.shouldShowPinnedEventsBanner)
|
|
#expect(viewModel.context.viewState.pinnedEventsBannerState.selectedPinnedIndex == 1)
|
|
|
|
// setup the loaded pinned events injection in the timeline
|
|
let pinnedTimelineMock = TimelineProxyMock()
|
|
let pinnedTimelineItemProviderMock = TimelineItemProviderMock()
|
|
let providerUpdateSubject = PassthroughSubject<([TimelineItemProxy], TimelinePaginationState), Never>()
|
|
pinnedTimelineItemProviderMock.underlyingUpdatePublisher = providerUpdateSubject.eraseToAnyPublisher()
|
|
pinnedTimelineMock.timelineItemProvider = pinnedTimelineItemProviderMock
|
|
pinnedTimelineItemProviderMock.itemProxies = [.event(.init(item: EventTimelineItem(configuration: .init(eventID: "test1")), uniqueID: .init("1"))),
|
|
.event(.init(item: EventTimelineItem(configuration: .init(eventID: "test2")), uniqueID: .init("2")))]
|
|
|
|
// check if the banner is now in a loaded state and is showing the counter
|
|
deferred = deferFulfillment(viewModel.context.$viewState) { viewState in
|
|
!viewState.pinnedEventsBannerState.isLoading
|
|
}
|
|
continuation.yield(pinnedTimelineMock)
|
|
try await deferred.fulfill()
|
|
#expect(viewModel.context.viewState.pinnedEventsBannerState.count == 2)
|
|
#expect(viewModel.context.viewState.shouldShowPinnedEventsBanner)
|
|
#expect(viewModel.context.viewState.pinnedEventsBannerState.selectedPinnedIndex == 1)
|
|
|
|
// check if the banner is updating alongside the timeline
|
|
deferred = deferFulfillment(viewModel.context.$viewState) { viewState in
|
|
viewState.pinnedEventsBannerState.count == 3
|
|
}
|
|
providerUpdateSubject.send(([.event(.init(item: EventTimelineItem(configuration: .init(eventID: "test1")), uniqueID: .init("1"))),
|
|
.event(.init(item: EventTimelineItem(configuration: .init(eventID: "test2")), uniqueID: .init("2"))),
|
|
.event(.init(item: EventTimelineItem(configuration: .init(eventID: "test3")), uniqueID: .init("3")))], .initial))
|
|
try await deferred.fulfill()
|
|
#expect(!viewModel.context.viewState.pinnedEventsBannerState.isLoading)
|
|
#expect(viewModel.context.viewState.shouldShowPinnedEventsBanner)
|
|
#expect(viewModel.context.viewState.pinnedEventsBannerState.selectedPinnedIndex == 1)
|
|
|
|
// check how the scrolling changes the banner visibility
|
|
viewModel.timelineHasScrolled(direction: .top)
|
|
#expect(!viewModel.context.viewState.shouldShowPinnedEventsBanner)
|
|
|
|
viewModel.timelineHasScrolled(direction: .bottom)
|
|
#expect(viewModel.context.viewState.shouldShowPinnedEventsBanner)
|
|
}
|
|
|
|
@Test
|
|
func pinnedEventsBannerSelection() async throws {
|
|
let roomProxyMock = JoinedRoomProxyMock(.init())
|
|
roomProxyMock.loadOrFetchEventDetailsForReturnValue = .success(TimelineEventSDKMock())
|
|
// setup a way to inject the mock of the pinned events timeline
|
|
let pinnedTimelineMock = TimelineProxyMock()
|
|
let pinnedTimelineItemProviderMock = TimelineItemProviderMock()
|
|
pinnedTimelineMock.timelineItemProvider = pinnedTimelineItemProviderMock
|
|
pinnedTimelineItemProviderMock.underlyingUpdatePublisher = Empty<([TimelineItemProxy], TimelinePaginationState), Never>().eraseToAnyPublisher()
|
|
pinnedTimelineItemProviderMock.itemProxies = [.event(.init(item: EventTimelineItem(configuration: .init(eventID: "test1")), uniqueID: .init("1"))),
|
|
.event(.init(item: EventTimelineItem(configuration: .init(eventID: "test2")), uniqueID: .init("2"))),
|
|
.event(.init(item: EventTimelineItem(configuration: .init(eventID: "test3")), uniqueID: .init("3")))]
|
|
roomProxyMock.pinnedEventsTimelineReturnValue = .success(pinnedTimelineMock)
|
|
let viewModel = RoomScreenViewModel(userSession: UserSessionMock(.init()),
|
|
roomProxy: roomProxyMock,
|
|
initialSelectedPinnedEventID: "test1",
|
|
ongoingCallRoomIDPublisher: .init(.init(nil)),
|
|
appSettings: appSettings,
|
|
appHooks: AppHooks(),
|
|
analyticsService: .mock(settings: appSettings),
|
|
userIndicatorController: UserIndicatorControllerMock.default)
|
|
self.viewModel = viewModel
|
|
|
|
// check if the banner is now in a loaded state and is showing the counter
|
|
var deferred = deferFulfillment(viewModel.context.$viewState) { viewState in
|
|
!viewState.pinnedEventsBannerState.isLoading
|
|
}
|
|
try await deferred.fulfill()
|
|
#expect(viewModel.context.viewState.pinnedEventsBannerState.count == 3)
|
|
#expect(viewModel.context.viewState.shouldShowPinnedEventsBanner)
|
|
// And that is actually displaying the `initialSelectedPinEventID` which is gthe first one in the list
|
|
#expect(viewModel.context.viewState.pinnedEventsBannerState.selectedPinnedIndex == 0)
|
|
|
|
// check if the banner scrolls when tapping the previous pin
|
|
deferred = deferFulfillment(viewModel.context.$viewState) { viewState in
|
|
viewState.pinnedEventsBannerState.selectedPinnedIndex == 2
|
|
}
|
|
let deferredAction = deferFulfillment(viewModel.actions) { action in
|
|
if case let .focusEvent(eventID) = action {
|
|
return eventID == "test1"
|
|
}
|
|
return false
|
|
}
|
|
viewModel.context.send(viewAction: .tappedPinnedEventsBanner)
|
|
try await deferred.fulfill()
|
|
try await deferredAction.fulfill()
|
|
|
|
// check if the banner scrolls to the specific selected pin
|
|
deferred = deferFulfillment(viewModel.context.$viewState) { viewState in
|
|
viewState.pinnedEventsBannerState.selectedPinnedIndex == 1
|
|
}
|
|
viewModel.setSelectedPinnedEventID("test2")
|
|
try await deferred.fulfill()
|
|
}
|
|
|
|
@Test
|
|
func pinnedEventsBannerThreadedSelection() async throws {
|
|
appSettings.threadsEnabled = true
|
|
|
|
let roomProxyMock = JoinedRoomProxyMock(.init())
|
|
let eventMock = TimelineEventSDKMock()
|
|
eventMock.threadRootEventIdReturnValue = "thread"
|
|
roomProxyMock.loadOrFetchEventDetailsForReturnValue = .success(eventMock)
|
|
|
|
// setup a way to inject the mock of the pinned events timeline
|
|
let pinnedTimelineMock = TimelineProxyMock()
|
|
let pinnedTimelineItemProviderMock = TimelineItemProviderMock()
|
|
pinnedTimelineMock.timelineItemProvider = pinnedTimelineItemProviderMock
|
|
pinnedTimelineItemProviderMock.underlyingUpdatePublisher = Empty<([TimelineItemProxy], TimelinePaginationState), Never>().eraseToAnyPublisher()
|
|
pinnedTimelineItemProviderMock.itemProxies = [.event(.init(item: EventTimelineItem(configuration: .init(eventID: "test1")), uniqueID: .init("1"))),
|
|
.event(.init(item: EventTimelineItem(configuration: .init(eventID: "test2")), uniqueID: .init("2"))),
|
|
.event(.init(item: EventTimelineItem(configuration: .init(eventID: "test3")), uniqueID: .init("3")))]
|
|
roomProxyMock.pinnedEventsTimelineReturnValue = .success(pinnedTimelineMock)
|
|
|
|
let viewModel = RoomScreenViewModel(userSession: UserSessionMock(.init()),
|
|
roomProxy: roomProxyMock,
|
|
initialSelectedPinnedEventID: "test1",
|
|
ongoingCallRoomIDPublisher: .init(.init(nil)),
|
|
appSettings: appSettings,
|
|
appHooks: AppHooks(),
|
|
analyticsService: .mock(settings: appSettings),
|
|
userIndicatorController: UserIndicatorControllerMock.default)
|
|
self.viewModel = viewModel
|
|
|
|
// check if the banner is now in a loaded state and is showing the counter
|
|
var deferred = deferFulfillment(viewModel.context.$viewState) { viewState in
|
|
!viewState.pinnedEventsBannerState.isLoading
|
|
}
|
|
try await deferred.fulfill()
|
|
#expect(viewModel.context.viewState.pinnedEventsBannerState.count == 3)
|
|
#expect(viewModel.context.viewState.shouldShowPinnedEventsBanner)
|
|
// And that is actually displaying the `initialSelectedPinEventID` which is gthe first one in the list
|
|
#expect(viewModel.context.viewState.pinnedEventsBannerState.selectedPinnedIndex == 0)
|
|
|
|
// check if the banner scrolls when tapping the previous pin
|
|
deferred = deferFulfillment(viewModel.context.$viewState) { viewState in
|
|
viewState.pinnedEventsBannerState.selectedPinnedIndex == 2
|
|
}
|
|
let deferredAction1 = deferFulfillment(viewModel.actions) { action in
|
|
if case let .focusEvent(threadRootEventID) = action {
|
|
return threadRootEventID == "thread"
|
|
}
|
|
return false
|
|
}
|
|
let deferredAction2 = deferFulfillment(viewModel.actions) { action in
|
|
if case let .displayThread(threadRootEventID, focussedEventID) = action {
|
|
return threadRootEventID == "thread" && focussedEventID == "test1"
|
|
}
|
|
return false
|
|
}
|
|
|
|
viewModel.context.send(viewAction: .tappedPinnedEventsBanner)
|
|
try await deferred.fulfill()
|
|
try await deferredAction1.fulfill()
|
|
try await deferredAction2.fulfill()
|
|
}
|
|
|
|
@Test
|
|
func roomInfoUpdate() async throws {
|
|
var configuration = JoinedRoomProxyMockConfiguration(id: "TestID", name: "StartingName", avatarURL: nil, hasOngoingCall: false)
|
|
let roomProxyMock = JoinedRoomProxyMock(configuration)
|
|
|
|
let powerLevelsMock = RoomPowerLevelsProxyMock(configuration: .init())
|
|
powerLevelsMock.canUserJoinCallUserIDReturnValue = .success(false)
|
|
powerLevelsMock.canOwnUserJoinCallReturnValue = false
|
|
roomProxyMock.powerLevelsReturnValue = .success(powerLevelsMock)
|
|
|
|
let roomInfoProxyMock = RoomInfoProxyMock(configuration)
|
|
roomInfoProxyMock.powerLevels = powerLevelsMock
|
|
|
|
let infoSubject = CurrentValueSubject<RoomInfoProxyProtocol, Never>(roomInfoProxyMock)
|
|
roomProxyMock.underlyingInfoPublisher = infoSubject.asCurrentValuePublisher()
|
|
|
|
let viewModel = RoomScreenViewModel(userSession: UserSessionMock(.init()),
|
|
roomProxy: roomProxyMock,
|
|
initialSelectedPinnedEventID: nil,
|
|
ongoingCallRoomIDPublisher: .init(.init(nil)),
|
|
appSettings: appSettings,
|
|
appHooks: AppHooks(),
|
|
analyticsService: .mock(settings: appSettings),
|
|
userIndicatorController: UserIndicatorControllerMock.default)
|
|
self.viewModel = viewModel
|
|
|
|
#expect(viewModel.state.roomTitle == "StartingName")
|
|
#expect(viewModel.state.roomAvatar == .room(id: "TestID", name: "StartingName", avatarURL: nil))
|
|
#expect(!viewModel.state.canJoinCall)
|
|
#expect(!viewModel.state.hasOngoingCall)
|
|
|
|
let deferred = deferFulfillment(viewModel.context.$viewState) { viewState in
|
|
viewState.roomTitle == "NewName" &&
|
|
viewState.roomAvatar == .room(id: "TestID", name: "NewName", avatarURL: .mockMXCAvatar) &&
|
|
viewState.canJoinCall &&
|
|
viewState.hasOngoingCall
|
|
}
|
|
|
|
configuration.name = "NewName"
|
|
configuration.avatarURL = .mockMXCAvatar
|
|
configuration.hasOngoingCall = true
|
|
powerLevelsMock.canUserJoinCallUserIDReturnValue = .success(true)
|
|
|
|
infoSubject.send(RoomInfoProxyMock(configuration))
|
|
|
|
try await deferred.fulfill()
|
|
}
|
|
|
|
@Test
|
|
func callButtonVisibility() async throws {
|
|
// Given a room screen with no ongoing call.
|
|
let ongoingCallRoomIDSubject = CurrentValueSubject<String?, Never>(nil)
|
|
let roomProxyMock = JoinedRoomProxyMock(.init(id: "MyRoomID"))
|
|
let viewModel = RoomScreenViewModel(userSession: UserSessionMock(.init()),
|
|
roomProxy: roomProxyMock,
|
|
initialSelectedPinnedEventID: nil,
|
|
ongoingCallRoomIDPublisher: ongoingCallRoomIDSubject.asCurrentValuePublisher(),
|
|
appSettings: appSettings,
|
|
appHooks: AppHooks(),
|
|
analyticsService: .mock(settings: appSettings),
|
|
userIndicatorController: UserIndicatorControllerMock.default)
|
|
self.viewModel = viewModel
|
|
#expect(viewModel.state.shouldShowCallButton)
|
|
|
|
// When a call starts in this room.
|
|
var deferred = deferFulfillment(viewModel.context.$viewState) { !$0.shouldShowCallButton }
|
|
ongoingCallRoomIDSubject.send("MyRoomID")
|
|
try await deferred.fulfill()
|
|
|
|
// Then the call button should be hidden.
|
|
#expect(!viewModel.state.shouldShowCallButton)
|
|
|
|
// When a call starts in a different room.
|
|
deferred = deferFulfillment(viewModel.context.$viewState) { $0.shouldShowCallButton }
|
|
ongoingCallRoomIDSubject.send("OtherRoomID")
|
|
try await deferred.fulfill()
|
|
|
|
// Then the call button should be shown again.
|
|
#expect(viewModel.state.shouldShowCallButton)
|
|
|
|
// When the call from the other room finishes.
|
|
let deferredFailure = deferFailure(viewModel.context.$viewState, timeout: .seconds(1)) { !$0.shouldShowCallButton }
|
|
ongoingCallRoomIDSubject.send(nil)
|
|
try await deferredFailure.fulfill()
|
|
|
|
// Then the call button should remain visible shown.
|
|
#expect(viewModel.state.shouldShowCallButton)
|
|
}
|
|
|
|
@Test
|
|
func roomFullyRead() async {
|
|
await waitForConfirmation("Wait for fully read") { confirm in
|
|
let roomProxyMock = JoinedRoomProxyMock(.init(id: "MyRoomID"))
|
|
roomProxyMock.markAsReadReceiptTypeClosure = { readReceiptType in
|
|
#expect(readReceiptType == .fullyRead)
|
|
confirm()
|
|
return .success(())
|
|
}
|
|
let viewModel = RoomScreenViewModel(userSession: UserSessionMock(.init()),
|
|
roomProxy: roomProxyMock,
|
|
initialSelectedPinnedEventID: nil,
|
|
ongoingCallRoomIDPublisher: .init(.init(nil)),
|
|
appSettings: appSettings,
|
|
appHooks: AppHooks(),
|
|
analyticsService: .mock(settings: appSettings),
|
|
userIndicatorController: UserIndicatorControllerMock.default)
|
|
self.viewModel = viewModel
|
|
viewModel.stop()
|
|
}
|
|
}
|
|
|
|
// MARK: - Knock Requests
|
|
|
|
@Test
|
|
func knockRequestBanner() async throws {
|
|
let roomProxyMock = JoinedRoomProxyMock(.init(knockRequestsState: .loaded([KnockRequestProxyMock(.init(eventID: "1", userID: "@alice:matrix.org", displayName: "Alice", reason: "Hello World!")),
|
|
// This one should be filtered
|
|
KnockRequestProxyMock(.init(eventID: "2", userID: "@bob:matrix.org", isSeen: true))]),
|
|
joinRule: .knock))
|
|
let viewModel = RoomScreenViewModel(userSession: UserSessionMock(.init()),
|
|
roomProxy: roomProxyMock,
|
|
initialSelectedPinnedEventID: nil,
|
|
ongoingCallRoomIDPublisher: .init(.init(nil)),
|
|
appSettings: appSettings,
|
|
appHooks: AppHooks(),
|
|
analyticsService: .mock(settings: appSettings),
|
|
userIndicatorController: UserIndicatorControllerMock.default)
|
|
self.viewModel = viewModel
|
|
|
|
var deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
|
state.shouldSeeKnockRequests &&
|
|
state.unseenKnockRequests == [.init(displayName: "Alice", avatarURL: nil, userID: "@alice:matrix.org", reason: "Hello World!", eventID: "1")]
|
|
}
|
|
try await deferred.fulfill()
|
|
|
|
let deferredAction = deferFulfillment(viewModel.actions) { $0 == .displayKnockRequests }
|
|
viewModel.context.send(viewAction: .viewKnockRequests)
|
|
try await deferredAction.fulfill()
|
|
|
|
deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
|
state.handledEventIDs == ["1"] &&
|
|
!state.shouldSeeKnockRequests
|
|
}
|
|
viewModel.context.send(viewAction: .acceptKnock(eventID: "1"))
|
|
try await deferred.fulfill()
|
|
}
|
|
|
|
@Test
|
|
func knockRequestBannerMarkAsSeen() async throws {
|
|
let roomProxyMock = JoinedRoomProxyMock(.init(knockRequestsState: .loaded([KnockRequestProxyMock(.init(eventID: "1", userID: "@alice:matrix.org", displayName: "Alice", reason: "Hello World!")),
|
|
// This one should be filtered
|
|
KnockRequestProxyMock(.init(eventID: "2", userID: "@bob:matrix.org"))]),
|
|
joinRule: .knock))
|
|
let viewModel = RoomScreenViewModel(userSession: UserSessionMock(.init()),
|
|
roomProxy: roomProxyMock,
|
|
initialSelectedPinnedEventID: nil,
|
|
ongoingCallRoomIDPublisher: .init(.init(nil)),
|
|
appSettings: appSettings,
|
|
appHooks: AppHooks(),
|
|
analyticsService: .mock(settings: appSettings),
|
|
userIndicatorController: UserIndicatorControllerMock.default)
|
|
self.viewModel = viewModel
|
|
|
|
var deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
|
state.shouldSeeKnockRequests &&
|
|
state.unseenKnockRequests == [.init(displayName: "Alice", avatarURL: nil, userID: "@alice:matrix.org", reason: "Hello World!", eventID: "1"),
|
|
.init(displayName: nil, avatarURL: nil, userID: "@bob:matrix.org", reason: nil, eventID: "2")]
|
|
}
|
|
try await deferred.fulfill()
|
|
|
|
deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
|
state.handledEventIDs == ["1", "2"] &&
|
|
!state.shouldSeeKnockRequests
|
|
}
|
|
viewModel.context.send(viewAction: .dismissKnockRequests)
|
|
try await deferred.fulfill()
|
|
}
|
|
|
|
@Test
|
|
func loadingKnockRequests() async throws {
|
|
let roomProxyMock = JoinedRoomProxyMock(.init(knockRequestsState: .loading,
|
|
joinRule: .knock))
|
|
let viewModel = RoomScreenViewModel(userSession: UserSessionMock(.init()),
|
|
roomProxy: roomProxyMock,
|
|
initialSelectedPinnedEventID: nil,
|
|
ongoingCallRoomIDPublisher: .init(.init(nil)),
|
|
appSettings: appSettings,
|
|
appHooks: AppHooks(),
|
|
analyticsService: .mock(settings: appSettings),
|
|
userIndicatorController: UserIndicatorControllerMock.default)
|
|
self.viewModel = viewModel
|
|
|
|
// Loading state just does not appear at all
|
|
let deferred = deferFulfillment(viewModel.context.$viewState) { !$0.shouldSeeKnockRequests }
|
|
try await deferred.fulfill()
|
|
}
|
|
|
|
@Test
|
|
func knockRequestsBannerDoesNotAppearIfUserHasNoPermission() async throws {
|
|
let roomProxyMock = JoinedRoomProxyMock(.init(knockRequestsState: .loaded([KnockRequestProxyMock(.init(eventID: "1", userID: "@alice:matrix.org", displayName: "Alice", reason: "Hello World!"))]),
|
|
joinRule: .knock,
|
|
powerLevelsConfiguration: .init(canUserInvite: false)))
|
|
let viewModel = RoomScreenViewModel(userSession: UserSessionMock(.init()),
|
|
roomProxy: roomProxyMock,
|
|
initialSelectedPinnedEventID: nil,
|
|
ongoingCallRoomIDPublisher: .init(.init(nil)),
|
|
appSettings: appSettings,
|
|
appHooks: AppHooks(),
|
|
analyticsService: .mock(settings: appSettings),
|
|
userIndicatorController: UserIndicatorControllerMock.default)
|
|
self.viewModel = viewModel
|
|
|
|
let deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
|
state.unseenKnockRequests == [.init(displayName: "Alice", avatarURL: nil, userID: "@alice:matrix.org", reason: "Hello World!", eventID: "1")] &&
|
|
!state.shouldSeeKnockRequests
|
|
}
|
|
try await deferred.fulfill()
|
|
}
|
|
|
|
// MARK: - History Sharing
|
|
|
|
@Test
|
|
func roomWithSharedHistoryDisplaysBadge() async throws {
|
|
var configuration = JoinedRoomProxyMockConfiguration(isEncrypted: false, historyVisibility: .joined)
|
|
let infoSubject = CurrentValueSubject<RoomInfoProxyProtocol, Never>(RoomInfoProxyMock(configuration))
|
|
let roomProxyMock = JoinedRoomProxyMock(configuration)
|
|
|
|
// setup the room proxy actions publisher
|
|
roomProxyMock.underlyingInfoPublisher = infoSubject.asCurrentValuePublisher()
|
|
let viewModel = RoomScreenViewModel(userSession: UserSessionMock(.init()),
|
|
roomProxy: roomProxyMock,
|
|
initialSelectedPinnedEventID: nil,
|
|
ongoingCallRoomIDPublisher: .init(.init(nil)),
|
|
appSettings: appSettings,
|
|
appHooks: AppHooks(),
|
|
analyticsService: .mock(settings: appSettings),
|
|
userIndicatorController: UserIndicatorControllerMock.default)
|
|
self.viewModel = viewModel
|
|
|
|
let deferredInvisible = deferFailure(viewModel.context.$viewState,
|
|
timeout: .seconds(1),
|
|
message: "The icon should be hidden when the room history visibility is not .shared or .worldReadable") { viewState in
|
|
viewState.roomHistorySharingState != nil
|
|
}
|
|
try await deferredInvisible.fulfill()
|
|
|
|
configuration.historyVisibility = .shared
|
|
infoSubject.send(RoomInfoProxyMock(configuration))
|
|
let deferredInvisibleUnencrypted = deferFailure(viewModel.context.$viewState,
|
|
timeout: .seconds(1),
|
|
message: "The icon should not be shown when the room is unencrypted") { viewState in
|
|
viewState.roomHistorySharingState != nil
|
|
}
|
|
try await deferredInvisibleUnencrypted.fulfill()
|
|
|
|
configuration.isEncrypted = true
|
|
infoSubject.send(RoomInfoProxyMock(configuration))
|
|
let deferredShared = deferFulfillment(viewModel.context.$viewState,
|
|
message: "The icon should be shown when the room history visibility is .shared") { viewState in
|
|
viewState.roomHistorySharingState == .shared
|
|
}
|
|
try await deferredShared.fulfill()
|
|
|
|
configuration.historyVisibility = .worldReadable
|
|
infoSubject.send(RoomInfoProxyMock(configuration))
|
|
let deferredWorldReadable = deferFulfillment(viewModel.context.$viewState,
|
|
message: "The icon should be shown when the room history visibility is .worldReadable") { viewState in
|
|
viewState.roomHistorySharingState == .worldReadable
|
|
}
|
|
try await deferredWorldReadable.fulfill()
|
|
}
|
|
}
|