mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
Membership aware room routes (#2709)
* Remove the invites button when room list invites are enabled * element-hq/element-x-ios/issues/2632 - Handle room permalinks based on their membership state and show the according room joining screens: - modify the room flow coordinator to only take a roomID and be able to decide its own room flows (especially for unjoined rooms) - add support for the new room preview API - have RoomDirectorySearch go through the same paths
This commit is contained in:
@@ -21,32 +21,33 @@ import Combine
|
||||
|
||||
@MainActor
|
||||
class RoomFlowCoordinatorTests: XCTestCase {
|
||||
var clientProxy: ClientProxyMock!
|
||||
var roomFlowCoordinator: RoomFlowCoordinator!
|
||||
var navigationStackCoordinator: NavigationStackCoordinator!
|
||||
var cancellables = Set<AnyCancellable>()
|
||||
|
||||
func testRoomPresentation() async throws {
|
||||
await setupViewModel()
|
||||
await setupRoomFlowCoordinator()
|
||||
|
||||
try await process(route: .room(roomID: "1"))
|
||||
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomScreenCoordinator)
|
||||
|
||||
try await process(route: .roomList, expectedAction: .finished)
|
||||
try await clearRoute(expectedActions: [.finished])
|
||||
XCTAssertNil(navigationStackCoordinator.rootCoordinator)
|
||||
}
|
||||
|
||||
func testRoomDetailsPresentation() async throws {
|
||||
await setupViewModel()
|
||||
await setupRoomFlowCoordinator()
|
||||
|
||||
try await process(route: .roomDetails(roomID: "1"))
|
||||
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomDetailsScreenCoordinator)
|
||||
|
||||
try await process(route: .roomList, expectedAction: .finished)
|
||||
try await clearRoute(expectedActions: [.finished])
|
||||
XCTAssertNil(navigationStackCoordinator.rootCoordinator)
|
||||
}
|
||||
|
||||
func testNoOp() async throws {
|
||||
await setupViewModel()
|
||||
await setupRoomFlowCoordinator()
|
||||
|
||||
try await process(route: .roomDetails(roomID: "1"))
|
||||
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomDetailsScreenCoordinator)
|
||||
@@ -60,7 +61,7 @@ class RoomFlowCoordinatorTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testPushDetails() async throws {
|
||||
await setupViewModel()
|
||||
await setupRoomFlowCoordinator()
|
||||
|
||||
try await process(route: .room(roomID: "1"))
|
||||
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomScreenCoordinator)
|
||||
@@ -73,7 +74,7 @@ class RoomFlowCoordinatorTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testChildRoomFlow() async throws {
|
||||
await setupViewModel()
|
||||
await setupRoomFlowCoordinator()
|
||||
|
||||
try await process(route: .room(roomID: "1"))
|
||||
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomScreenCoordinator)
|
||||
@@ -88,14 +89,14 @@ class RoomFlowCoordinatorTests: XCTestCase {
|
||||
XCTAssert(navigationStackCoordinator.stackCoordinators.first is RoomScreenCoordinator)
|
||||
XCTAssert(navigationStackCoordinator.stackCoordinators.last is RoomScreenCoordinator)
|
||||
|
||||
try await process(route: .roomList, expectedAction: .finished)
|
||||
try await clearRoute(expectedActions: [.finished])
|
||||
XCTAssertNil(navigationStackCoordinator.rootCoordinator)
|
||||
XCTAssertEqual(navigationStackCoordinator.stackCoordinators.count, 0)
|
||||
}
|
||||
|
||||
/// Tests the child flow teardown in isolation of it's parent.
|
||||
func testChildFlowTearDown() async throws {
|
||||
await setupViewModel(asChildFlow: true)
|
||||
await setupRoomFlowCoordinator(asChildFlow: true)
|
||||
navigationStackCoordinator.setRootCoordinator(BlankFormCoordinator())
|
||||
|
||||
try await process(route: .room(roomID: "1"))
|
||||
@@ -105,7 +106,7 @@ class RoomFlowCoordinatorTests: XCTestCase {
|
||||
XCTAssertTrue(navigationStackCoordinator.stackCoordinators.first is RoomScreenCoordinator)
|
||||
XCTAssertTrue(navigationStackCoordinator.stackCoordinators.last is RoomDetailsScreenCoordinator)
|
||||
|
||||
try await process(route: .roomList, expectedAction: .finished)
|
||||
try await clearRoute(expectedActions: [.finished])
|
||||
XCTAssertTrue(navigationStackCoordinator.rootCoordinator is BlankFormCoordinator)
|
||||
XCTAssertEqual(navigationStackCoordinator.stackCoordinators.count, 2, "A child room flow should leave its parent to clean up the stack.")
|
||||
XCTAssertTrue(navigationStackCoordinator.stackCoordinators.first is RoomScreenCoordinator, "A child room flow should leave its parent to clean up the stack.")
|
||||
@@ -113,7 +114,7 @@ class RoomFlowCoordinatorTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testChildRoomMemberDetails() async throws {
|
||||
await setupViewModel()
|
||||
await setupRoomFlowCoordinator()
|
||||
|
||||
try await process(route: .room(roomID: "1"))
|
||||
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomScreenCoordinator)
|
||||
@@ -130,7 +131,7 @@ class RoomFlowCoordinatorTests: XCTestCase {
|
||||
}
|
||||
|
||||
func testChildRoomIgnoresDirectDuplicate() async throws {
|
||||
await setupViewModel()
|
||||
await setupRoomFlowCoordinator()
|
||||
|
||||
try await process(route: .room(roomID: "1"))
|
||||
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomScreenCoordinator)
|
||||
@@ -151,6 +152,60 @@ class RoomFlowCoordinatorTests: XCTestCase {
|
||||
XCTAssert(navigationStackCoordinator.stackCoordinators.last is RoomScreenCoordinator)
|
||||
}
|
||||
|
||||
func testRoomMembershipInvite() async throws {
|
||||
await setupRoomFlowCoordinator(roomType: .invited(roomID: "InvitedRoomID"))
|
||||
|
||||
try await process(route: .room(roomID: "InvitedRoomID"))
|
||||
XCTAssert(navigationStackCoordinator.rootCoordinator is JoinRoomScreenCoordinator)
|
||||
XCTAssertEqual(navigationStackCoordinator.stackCoordinators.count, 0)
|
||||
|
||||
try await clearRoute(expectedActions: [.finished])
|
||||
XCTAssertNil(navigationStackCoordinator.rootCoordinator)
|
||||
|
||||
try await process(route: .room(roomID: "InvitedRoomID"))
|
||||
XCTAssert(navigationStackCoordinator.rootCoordinator is JoinRoomScreenCoordinator)
|
||||
XCTAssertEqual(navigationStackCoordinator.stackCoordinators.count, 0)
|
||||
|
||||
// "Join" the room
|
||||
clientProxy.roomForIdentifierClosure = { _ in
|
||||
RoomProxyMock(with: .init())
|
||||
}
|
||||
|
||||
try await process(route: .room(roomID: "InvitedRoomID"))
|
||||
XCTAssert(navigationStackCoordinator.rootCoordinator is RoomScreenCoordinator)
|
||||
XCTAssertEqual(navigationStackCoordinator.stackCoordinators.count, 0)
|
||||
}
|
||||
|
||||
func testChildRoomMembershipInvite() async throws {
|
||||
await setupRoomFlowCoordinator(asChildFlow: true, roomType: .invited(roomID: "InvitedRoomID"))
|
||||
navigationStackCoordinator.setRootCoordinator(BlankFormCoordinator())
|
||||
|
||||
try await process(route: .room(roomID: "InvitedRoomID"))
|
||||
XCTAssertTrue(navigationStackCoordinator.rootCoordinator is BlankFormCoordinator, "A child room flow should push onto the stack, leaving the root alone.")
|
||||
XCTAssertEqual(navigationStackCoordinator.stackCoordinators.count, 1)
|
||||
XCTAssertTrue(navigationStackCoordinator.stackCoordinators.last is JoinRoomScreenCoordinator)
|
||||
|
||||
try await clearRoute(expectedActions: [.finished])
|
||||
XCTAssertNil(navigationStackCoordinator.stackCoordinators.last, "A child room flow should remove the join room scren on dismissal")
|
||||
|
||||
navigationStackCoordinator.popToRoot()
|
||||
|
||||
try await process(route: .room(roomID: "InvitedRoomID"))
|
||||
XCTAssertTrue(navigationStackCoordinator.rootCoordinator is BlankFormCoordinator, "A child room flow should push onto the stack, leaving the root alone.")
|
||||
XCTAssertEqual(navigationStackCoordinator.stackCoordinators.count, 1)
|
||||
XCTAssertTrue(navigationStackCoordinator.stackCoordinators.last is JoinRoomScreenCoordinator)
|
||||
|
||||
// "Join" the room
|
||||
clientProxy.roomForIdentifierClosure = { _ in
|
||||
RoomProxyMock(with: .init())
|
||||
}
|
||||
|
||||
try await process(route: .room(roomID: "InvitedRoomID"))
|
||||
XCTAssertTrue(navigationStackCoordinator.rootCoordinator is BlankFormCoordinator, "A child room flow should push onto the stack, leaving the root alone.")
|
||||
XCTAssertEqual(navigationStackCoordinator.stackCoordinators.count, 1)
|
||||
XCTAssertTrue(navigationStackCoordinator.stackCoordinators.last is RoomScreenCoordinator)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func process(route: AppRoute) async throws {
|
||||
@@ -159,11 +214,15 @@ class RoomFlowCoordinatorTests: XCTestCase {
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
}
|
||||
|
||||
private func process(route: AppRoute, expectedAction: RoomFlowCoordinatorAction) async throws {
|
||||
try await process(route: route, expectedActions: [expectedAction])
|
||||
private func clearRoute(expectedActions: [RoomFlowCoordinatorAction]) async throws {
|
||||
try await processRouteOrClear(route: nil, expectedActions: expectedActions)
|
||||
}
|
||||
|
||||
private func process(route: AppRoute, expectedActions: [RoomFlowCoordinatorAction]) async throws {
|
||||
try await processRouteOrClear(route: route, expectedActions: expectedActions)
|
||||
}
|
||||
|
||||
private func processRouteOrClear(route: AppRoute?, expectedActions: [RoomFlowCoordinatorAction]) async throws {
|
||||
guard !expectedActions.isEmpty else {
|
||||
return
|
||||
}
|
||||
@@ -176,16 +235,40 @@ class RoomFlowCoordinatorTests: XCTestCase {
|
||||
})
|
||||
}
|
||||
|
||||
roomFlowCoordinator.handleAppRoute(route, animated: true)
|
||||
if let route {
|
||||
roomFlowCoordinator.handleAppRoute(route, animated: true)
|
||||
} else {
|
||||
roomFlowCoordinator.clearRoute(animated: true)
|
||||
}
|
||||
|
||||
for fulfillment in fulfillments {
|
||||
try await fulfillment.fulfill()
|
||||
}
|
||||
}
|
||||
|
||||
private func setupViewModel(asChildFlow: Bool = false) async {
|
||||
private func setupRoomFlowCoordinator(asChildFlow: Bool = false, roomType: RoomType? = nil) async {
|
||||
cancellables.removeAll()
|
||||
let clientProxy = ClientProxyMock(.init(userID: "hi@bob", roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded(.mockRooms)))))
|
||||
clientProxy = ClientProxyMock(.init(userID: "hi@bob", roomSummaryProvider: RoomSummaryProviderMock(.init(state: .loaded(.mockRooms)))))
|
||||
|
||||
clientProxy.roomPreviewForIdentifierClosure = { [roomType] roomID in
|
||||
switch roomType {
|
||||
case .invited:
|
||||
return .success(.init(roomID: roomID,
|
||||
name: "Invite",
|
||||
canonicalAlias: nil,
|
||||
topic: nil,
|
||||
avatarURL: nil,
|
||||
memberCount: 0,
|
||||
isHistoryWorldReadable: false,
|
||||
isJoined: false,
|
||||
isInvited: true,
|
||||
isPublic: false,
|
||||
canKnock: false))
|
||||
default:
|
||||
fatalError("Something isn't set up right")
|
||||
}
|
||||
}
|
||||
|
||||
let mediaProvider = MockMediaProvider()
|
||||
let voiceMessageMediaManager = VoiceMessageMediaManagerMock()
|
||||
let userSession = MockUserSession(clientProxy: clientProxy,
|
||||
@@ -196,7 +279,14 @@ class RoomFlowCoordinatorTests: XCTestCase {
|
||||
navigationStackCoordinator = NavigationStackCoordinator()
|
||||
navigationSplitCoordinator.setDetailCoordinator(navigationStackCoordinator)
|
||||
|
||||
roomFlowCoordinator = await RoomFlowCoordinator(roomProxy: RoomProxyMock(with: .init(id: "1")),
|
||||
let roomID = switch roomType {
|
||||
case .invited(let roomID):
|
||||
roomID
|
||||
default:
|
||||
"1"
|
||||
}
|
||||
|
||||
roomFlowCoordinator = await RoomFlowCoordinator(roomID: roomID,
|
||||
userSession: userSession,
|
||||
isChildFlow: asChildFlow,
|
||||
roomTimelineControllerFactory: MockRoomTimelineControllerFactory(),
|
||||
@@ -209,3 +299,7 @@ class RoomFlowCoordinatorTests: XCTestCase {
|
||||
orientationManager: OrientationManagerMock())
|
||||
}
|
||||
}
|
||||
|
||||
private enum RoomType {
|
||||
case invited(roomID: String)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user