mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-01 01:03:01 +00:00
22d14ff8e4
* create DependenciesProtocol and replace ServiceLocator registration with it replace mocking calls for ServiceLocator to Dependencies replace ServiceLocator with instance level calls replace ServiceLocator configs with TestDependencies config in Tests replace servicelocator usage in analytics service replace remaining ServiceLocator calls in unit tests that depended on the UnitTestAppCoordinator configuration with per suite dependencies instances fix swiftlint warning remove remaining references to ServiceLocator make dependencies properties consistent in tests delete Dependencies and DependenciesProtocol fix whitespace modifications updates from PR feedback a pass over converting AppSettings and AnalyticsService test instance properties to per test instantiation fix a missed PR feedback Sync up after rebase create DependenciesProtocol and replace ServiceLocator registration with it replace mocking calls for ServiceLocator to Dependencies replace ServiceLocator with instance level calls replace ServiceLocator configs with TestDependencies config in Tests replace servicelocator usage in analytics service replace remaining ServiceLocator calls in unit tests that depended on the UnitTestAppCoordinator configuration with per suite dependencies instances fix swiftlint warning remove remaining references to ServiceLocator make dependencies properties consistent in tests delete Dependencies and DependenciesProtocol * AppSettings is instance level mockable now * replace mocked and preview usages of reset + AppSettings global init with AppSettings(store: UserDefaultsMock()) * AppSettings.UserDefaultsKeys is now a RawRep struct, making it extensible * update tests with new AppSettings so it builds * update remaining AppSettings test/mock inits * AppSettings.UserDefaultsKey conformed to ExpressibleByStringLiteral * addressed issues found in first pass ai review * update AppSettings instantiations after rebase * fix accessibility tests coordinator after rebase * Rename to VolatileUserDefaults * AppSettings init uses mandatory parameter * Revert UserDefaultsKey to enum * Add TrackedUserDefaults to be able to reset the defaults object * Added volatile static func to AppSettings * Use .volatile() static func instead of full init * review fixups * Resolve build issues after rebase * updated enterprise module * remove the static reset settings call from the preview tests --------- Co-authored-by: Mauro Romito <mauro.romito@element.io>
499 lines
28 KiB
Swift
499 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 = AppSettings.volatile()
|
|
}
|
|
|
|
@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: AnalyticsServiceMock.default,
|
|
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: AnalyticsServiceMock.default,
|
|
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: AnalyticsServiceMock.default,
|
|
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: AnalyticsServiceMock.default,
|
|
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: AnalyticsServiceMock.default,
|
|
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: AnalyticsServiceMock.default,
|
|
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: AnalyticsServiceMock.default,
|
|
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: AnalyticsServiceMock.default,
|
|
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: AnalyticsServiceMock.default,
|
|
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: AnalyticsServiceMock.default,
|
|
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: AnalyticsServiceMock.default,
|
|
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()
|
|
}
|
|
}
|