mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 12:07:55 +00:00
Add a banner to encrypted rooms with visible history. (#4851)
* Add a banner to encrypted rooms with visible history. (#4738) * feat: Add history visible alert. - Adds a dismissable alert that is displayed whenever the user opens a room with `history_visibility` != `joined`. When cleared, this is recorded in the app's data store. - When opening a room with `history_visibility` = `joined`, this flag is cleared. Issue: element-hq/element-meta#2875 * tests: Add unit tests for history sharing in `RoomScreenFooterView`. * feat: Rename flag to `hasSeenHistoryVisibleBannerRooms`, document. * refactor: Merge enum variants, use function for banner description. * feat: Use `AppSettings.historyVisibleDetailsURL` over hard-coded value. * tests: Correct potential race condition with deferred assertion. * chore: Use Localazy translation string over project-defined. * fix: Final tweaks and review comments. * chore: Checkout `Enterprise` submodule. * tests: Final fixes. * fix: Condition banner visibility on feature flag state. * fix: Prioritise identity violations over history visibility banner. * tests: Add snapshots for history visible banner. * tests: Use deferred failure timeout for improved test power. * chore: Tweaks to spelling, simplify state logic. * fix: Remove "g". * fix: Show banner for shared/world-readable rooms, not invited. * refactor: Use `else-if` instead of `if`.
This commit is contained in:
@@ -438,4 +438,213 @@ class RoomScreenViewModelTests: XCTestCase {
|
||||
}
|
||||
try await deferred.fulfill()
|
||||
}
|
||||
|
||||
// MARK: History Sharing
|
||||
|
||||
func testHistoryVisibleBannerDoesNotAppearIfFeatureDisabled() async throws {
|
||||
ServiceLocator.shared.settings.enableKeyShareOnInvite = false
|
||||
ServiceLocator.shared.settings.acknowledgedHistoryVisibleRooms = Set()
|
||||
|
||||
let configuration = JoinedRoomProxyMockConfiguration(isEncrypted: true)
|
||||
let roomProxyMock = JoinedRoomProxyMock(configuration)
|
||||
|
||||
let roomInfoProxyMock = RoomInfoProxyMock(configuration)
|
||||
roomInfoProxyMock.historyVisibility = .shared
|
||||
|
||||
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: ServiceLocator.shared.settings,
|
||||
appHooks: AppHooks(),
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
||||
|
||||
self.viewModel = viewModel
|
||||
|
||||
let deferred = deferFailure(viewModel.context.$viewState, timeout: 1) { $0.footerDetails != nil }
|
||||
try await deferred.fulfill()
|
||||
}
|
||||
|
||||
func testHistoryVisibleBannerDoesNotAppearIfNotEncrypted() async throws {
|
||||
ServiceLocator.shared.settings.enableKeyShareOnInvite = true
|
||||
ServiceLocator.shared.settings.acknowledgedHistoryVisibleRooms = Set()
|
||||
|
||||
let roomProxyMock = JoinedRoomProxyMock(.init(isEncrypted: false))
|
||||
|
||||
let viewModel = RoomScreenViewModel(userSession: UserSessionMock(.init()),
|
||||
roomProxy: roomProxyMock,
|
||||
initialSelectedPinnedEventID: nil,
|
||||
ongoingCallRoomIDPublisher: .init(.init(nil)),
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
appHooks: AppHooks(),
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
||||
|
||||
self.viewModel = viewModel
|
||||
|
||||
let deferred = deferFailure(viewModel.context.$viewState, timeout: 1) { $0.footerDetails != nil }
|
||||
try await deferred.fulfill()
|
||||
}
|
||||
|
||||
func testHistoryVisibleBannerDoesNotAppearIfJoinedOrInvited() async throws {
|
||||
ServiceLocator.shared.settings.enableKeyShareOnInvite = true
|
||||
ServiceLocator.shared.settings.acknowledgedHistoryVisibleRooms = Set()
|
||||
|
||||
let configuration = JoinedRoomProxyMockConfiguration(isEncrypted: true)
|
||||
let roomProxyMock = JoinedRoomProxyMock(configuration)
|
||||
|
||||
let roomInfoProxyMock = RoomInfoProxyMock(configuration)
|
||||
roomInfoProxyMock.historyVisibility = .joined
|
||||
|
||||
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: ServiceLocator.shared.settings,
|
||||
appHooks: AppHooks(),
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
||||
|
||||
self.viewModel = viewModel
|
||||
|
||||
var deferred = deferFailure(viewModel.context.$viewState, timeout: 1) { $0.footerDetails != nil }
|
||||
try await deferred.fulfill()
|
||||
|
||||
// Update visibility to invited
|
||||
roomInfoProxyMock.historyVisibility = .invited
|
||||
infoSubject.send(roomInfoProxyMock)
|
||||
|
||||
deferred = deferFailure(viewModel.context.$viewState, timeout: 1) { $0.footerDetails != nil }
|
||||
try await deferred.fulfill()
|
||||
}
|
||||
|
||||
func testHistoryVisibleBannerDoesNotAppearIfAcknowledged() async throws {
|
||||
ServiceLocator.shared.settings.enableKeyShareOnInvite = true
|
||||
ServiceLocator.shared.settings.acknowledgedHistoryVisibleRooms = Set()
|
||||
ServiceLocator.shared.settings.acknowledgedHistoryVisibleRooms.insert("$room:example.com")
|
||||
|
||||
let configuration = JoinedRoomProxyMockConfiguration(id: "$room:example.com", isEncrypted: true)
|
||||
let roomProxyMock = JoinedRoomProxyMock(configuration)
|
||||
|
||||
let roomInfoProxyMock = RoomInfoProxyMock(configuration)
|
||||
roomInfoProxyMock.historyVisibility = .shared
|
||||
|
||||
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: ServiceLocator.shared.settings,
|
||||
appHooks: AppHooks(),
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
||||
|
||||
self.viewModel = viewModel
|
||||
|
||||
let deferred = deferFailure(viewModel.context.$viewState, timeout: 1) { $0.footerDetails != nil }
|
||||
try await deferred.fulfill()
|
||||
}
|
||||
|
||||
func testHistoryVisibleBannerAppearsThenDisappearsOnAcknowledge() async throws {
|
||||
ServiceLocator.shared.settings.enableKeyShareOnInvite = true
|
||||
ServiceLocator.shared.settings.acknowledgedHistoryVisibleRooms = Set()
|
||||
|
||||
let configuration = JoinedRoomProxyMockConfiguration(id: "$room:example.com", isEncrypted: true)
|
||||
let roomProxyMock = JoinedRoomProxyMock(configuration)
|
||||
|
||||
let roomInfoProxyMock = RoomInfoProxyMock(configuration)
|
||||
roomInfoProxyMock.historyVisibility = .shared
|
||||
|
||||
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: ServiceLocator.shared.settings,
|
||||
appHooks: AppHooks(),
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
||||
|
||||
self.viewModel = viewModel
|
||||
|
||||
var deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.footerDetails != nil
|
||||
}
|
||||
try await deferred.fulfill()
|
||||
|
||||
deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.footerDetails == nil
|
||||
}
|
||||
|
||||
ServiceLocator.shared.settings.acknowledgedHistoryVisibleRooms.insert("$room:example.com")
|
||||
viewModel.context.send(viewAction: .footerViewAction(RoomScreenFooterViewAction.dismissHistoryVisibleAlert))
|
||||
|
||||
try await deferred.fulfill()
|
||||
}
|
||||
|
||||
func testHistoryVisibleBannerAppearsFullFlow() async throws {
|
||||
ServiceLocator.shared.settings.enableKeyShareOnInvite = false
|
||||
ServiceLocator.shared.settings.acknowledgedHistoryVisibleRooms = Set()
|
||||
|
||||
let configuration = JoinedRoomProxyMockConfiguration(id: "$room:example.com", isEncrypted: true)
|
||||
let roomProxyMock = JoinedRoomProxyMock(configuration)
|
||||
|
||||
let roomInfoProxyMock = RoomInfoProxyMock(configuration)
|
||||
roomInfoProxyMock.historyVisibility = .joined
|
||||
|
||||
let infoSubject = CurrentValueSubject<RoomInfoProxyProtocol, Never>(roomInfoProxyMock)
|
||||
roomProxyMock.underlyingInfoPublisher = infoSubject.asCurrentValuePublisher()
|
||||
|
||||
var viewModel = RoomScreenViewModel(userSession: UserSessionMock(.init()),
|
||||
roomProxy: roomProxyMock,
|
||||
initialSelectedPinnedEventID: nil,
|
||||
ongoingCallRoomIDPublisher: .init(.init(nil)),
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
appHooks: AppHooks(),
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
||||
|
||||
self.viewModel = viewModel
|
||||
|
||||
// When the history is not shared, the banner should not be visible.
|
||||
var deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.footerDetails == nil
|
||||
}
|
||||
try await deferred.fulfill()
|
||||
|
||||
roomInfoProxyMock.historyVisibility = .shared
|
||||
infoSubject.send(roomInfoProxyMock)
|
||||
|
||||
// When the feature is off, the banner should not be visible.
|
||||
deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.footerDetails == nil
|
||||
}
|
||||
try await deferred.fulfill()
|
||||
|
||||
// When the history is shared, and the feature is on, the banner should be visible.
|
||||
ServiceLocator.shared.settings.enableKeyShareOnInvite = true
|
||||
viewModel = RoomScreenViewModel(userSession: UserSessionMock(.init()),
|
||||
roomProxy: roomProxyMock,
|
||||
initialSelectedPinnedEventID: nil,
|
||||
ongoingCallRoomIDPublisher: .init(.init(nil)),
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
appHooks: AppHooks(),
|
||||
analyticsService: ServiceLocator.shared.analytics,
|
||||
userIndicatorController: ServiceLocator.shared.userIndicatorController)
|
||||
deferred = deferFulfillment(viewModel.context.$viewState) { state in
|
||||
state.footerDetails != nil
|
||||
}
|
||||
try await deferred.fulfill()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user