mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-03 06:55:59 +00:00
Room's details edit screen (#956)
* Add RoomDetailsEditScreen template * Add navigation to the edit screen * Delete template code * Start RoomDetailsEditScreen UI * Manage power levels in RoomDetailsScreen * Start RoomDetailsEditScreenViewModel logic * Inject initial room state * Add cancel action * Expose set name/topic APIs in RoomProxy * Refine RoomDetailsEditScreen UI * Add save logic * Add localisations * Fix avatar image * Update localisations * Add “Add topic” button * Add feature flag * Add dismiss on save logic * Reduce throttling * Improve form logic * Fix UT build errors * Add media sheet * Add media preprocessing * Add LoadableEditableAvatarImage * Add condition on delete image * Add avatar save button logic * Add remove avatar logic * Cleanup * Fix edit bug in DM * Add upload avatar * Add focus * Add RoomDetailsViewModel UTs * Fix button style * Add UTs * Add empty topic ui test * Fix iPad sheet presentation * Revert topic appearance in room’s details * Address PR comments * Add UI tests
This commit is contained in:
@@ -65,9 +65,8 @@ class InviteUsersScreenViewModelTests: XCTestCase {
|
||||
_ = await viewModel.context.$viewState.values.first(where: { $0.membershipState.isEmpty == false })
|
||||
context.send(viewAction: .toggleUser(.mockAlice))
|
||||
|
||||
Task {
|
||||
await Task.yield()
|
||||
context.send(viewAction: .proceed)
|
||||
Task.detached(priority: .low) {
|
||||
await self.context.send(viewAction: .proceed)
|
||||
}
|
||||
|
||||
let action = await viewModel.actions.values.first()
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import MatrixRustSDK
|
||||
import XCTest
|
||||
|
||||
@testable import ElementX
|
||||
|
||||
@MainActor
|
||||
class RoomDetailsEditScreenViewModelTests: XCTestCase {
|
||||
var viewModel: RoomDetailsEditScreenViewModel!
|
||||
|
||||
var userIndicatorController: MockUserIndicatorController!
|
||||
|
||||
var context: RoomDetailsEditScreenViewModelType.Context {
|
||||
viewModel.context
|
||||
}
|
||||
|
||||
func testCannotSaveOnLanding() {
|
||||
setupViewModel(accountOwner: .mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]),
|
||||
roomProxyConfiguration: .init(name: "Some room", displayName: "Some room"))
|
||||
XCTAssertFalse(context.viewState.canSave)
|
||||
}
|
||||
|
||||
func testCanEdit() {
|
||||
setupViewModel(accountOwner: .mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]),
|
||||
roomProxyConfiguration: .init(name: "Some room", displayName: "Some room"))
|
||||
XCTAssertTrue(context.viewState.canEditAvatar)
|
||||
XCTAssertTrue(context.viewState.canEditName)
|
||||
XCTAssertTrue(context.viewState.canEditTopic)
|
||||
}
|
||||
|
||||
func testCannotEdit() {
|
||||
setupViewModel(accountOwner: .mockOwner(allowedStateEvents: []),
|
||||
roomProxyConfiguration: .init(name: "Some room", displayName: "Some room"))
|
||||
XCTAssertFalse(context.viewState.canEditAvatar)
|
||||
XCTAssertFalse(context.viewState.canEditName)
|
||||
XCTAssertFalse(context.viewState.canEditTopic)
|
||||
}
|
||||
|
||||
func testNameDidChange() {
|
||||
setupViewModel(accountOwner: .mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]),
|
||||
roomProxyConfiguration: .init(name: "Some room", displayName: "Some room"))
|
||||
context.name = "name"
|
||||
XCTAssertTrue(context.viewState.nameDidChange)
|
||||
XCTAssertTrue(context.viewState.canSave)
|
||||
}
|
||||
|
||||
func testTopicDidChange() {
|
||||
setupViewModel(accountOwner: .mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]),
|
||||
roomProxyConfiguration: .init(name: "Some room", displayName: "Some room"))
|
||||
context.topic = "topic"
|
||||
XCTAssertTrue(context.viewState.topicDidChange)
|
||||
XCTAssertTrue(context.viewState.canSave)
|
||||
}
|
||||
|
||||
func testAvatarDidChange() {
|
||||
setupViewModel(accountOwner: .mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]),
|
||||
roomProxyConfiguration: .init(name: "Some room", displayName: "Some room", avatarURL: .picturesDirectory))
|
||||
context.send(viewAction: .removeImage)
|
||||
XCTAssertTrue(context.viewState.avatarDidChange)
|
||||
XCTAssertTrue(context.viewState.canSave)
|
||||
}
|
||||
|
||||
func testEmptyNameCannotBeSaved() {
|
||||
setupViewModel(accountOwner: .mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]),
|
||||
roomProxyConfiguration: .init(name: "Some room", displayName: "Some room"))
|
||||
context.name = ""
|
||||
XCTAssertFalse(context.viewState.canSave)
|
||||
}
|
||||
|
||||
func testSaveShowsSheet() {
|
||||
setupViewModel(accountOwner: .mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]),
|
||||
roomProxyConfiguration: .init(name: "Some room", displayName: "Some room"))
|
||||
context.name = "name"
|
||||
XCTAssertFalse(context.showMediaSheet)
|
||||
context.send(viewAction: .presentMediaSource)
|
||||
XCTAssertTrue(context.showMediaSheet)
|
||||
}
|
||||
|
||||
func testSaveTriggersViewModelAction() async {
|
||||
setupViewModel(accountOwner: .mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]),
|
||||
roomProxyConfiguration: .init(name: "Some room", displayName: "Some room"))
|
||||
context.name = "name"
|
||||
context.send(viewAction: .save)
|
||||
let action = await viewModel.actions.values.first()
|
||||
XCTAssertEqual(action, .saveFinished)
|
||||
}
|
||||
|
||||
func testErrorShownOnFailedFetchOfMedia() async throws {
|
||||
setupViewModel(accountOwner: .mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]),
|
||||
roomProxyConfiguration: .init(name: "Some room", displayName: "Some room"))
|
||||
viewModel.didSelectMediaUrl(url: .picturesDirectory)
|
||||
|
||||
let alertInfo = await userIndicatorController.alertInfoPublisher
|
||||
.compactMap { $0 }
|
||||
.values
|
||||
.first()
|
||||
|
||||
XCTAssertNotNil(alertInfo)
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private func setupViewModel(accountOwner: RoomMemberProxyMock, roomProxyConfiguration: RoomProxyMockConfiguration) {
|
||||
userIndicatorController = MockUserIndicatorController()
|
||||
viewModel = .init(accountOwner: accountOwner,
|
||||
mediaProvider: MockMediaProvider(),
|
||||
roomProxy: RoomProxyMock(with: roomProxyConfiguration),
|
||||
userIndicatorController: userIndicatorController)
|
||||
}
|
||||
}
|
||||
|
||||
private extension ImageInfo {
|
||||
static let mock: ImageInfo = .init(height: nil,
|
||||
width: nil,
|
||||
mimetype: nil,
|
||||
size: nil,
|
||||
thumbnailInfo: nil,
|
||||
thumbnailSource: nil,
|
||||
blurhash: nil)
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import MatrixRustSDK
|
||||
import XCTest
|
||||
|
||||
@testable import ElementX
|
||||
@@ -27,6 +28,9 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
override func setUp() {
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test"))
|
||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
|
||||
AppSettings.reset()
|
||||
ServiceLocator.shared.settings.editRoomDetailsFlowEnabled = true
|
||||
}
|
||||
|
||||
func testLeaveRoomTappedWhenPublic() async {
|
||||
@@ -219,4 +223,66 @@ class RoomDetailsScreenViewModelTests: XCTestCase {
|
||||
await context.nextViewState()
|
||||
XCTAssertEqual(roomProxyMock.registerTimelineListenerIfNeededCallsCount, 1)
|
||||
}
|
||||
|
||||
func testCanEditAvatar() async {
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockOwner(allowedStateEvents: [.roomAvatar]), .mockBob, .mockAlice]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: false, isPublic: false, members: mockedMembers))
|
||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
|
||||
_ = await context.$viewState.values.first(where: { !$0.members.isEmpty })
|
||||
|
||||
XCTAssertTrue(context.viewState.canEditRoomAvatar)
|
||||
XCTAssertFalse(context.viewState.canEditRoomName)
|
||||
XCTAssertFalse(context.viewState.canEditRoomTopic)
|
||||
XCTAssertTrue(context.viewState.canEdit)
|
||||
}
|
||||
|
||||
func testCanEditName() async {
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockOwner(allowedStateEvents: [.roomName]), .mockBob, .mockAlice]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: false, isPublic: false, members: mockedMembers))
|
||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
|
||||
_ = await context.$viewState.values.first(where: { !$0.members.isEmpty })
|
||||
|
||||
XCTAssertFalse(context.viewState.canEditRoomAvatar)
|
||||
XCTAssertTrue(context.viewState.canEditRoomName)
|
||||
XCTAssertFalse(context.viewState.canEditRoomTopic)
|
||||
XCTAssertTrue(context.viewState.canEdit)
|
||||
}
|
||||
|
||||
func testCanEditTopic() async {
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockOwner(allowedStateEvents: [.roomTopic]), .mockBob, .mockAlice]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: false, isPublic: false, members: mockedMembers))
|
||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
|
||||
_ = await context.$viewState.values.first(where: { !$0.members.isEmpty })
|
||||
|
||||
XCTAssertFalse(context.viewState.canEditRoomAvatar)
|
||||
XCTAssertFalse(context.viewState.canEditRoomName)
|
||||
XCTAssertTrue(context.viewState.canEditRoomTopic)
|
||||
XCTAssertTrue(context.viewState.canEdit)
|
||||
}
|
||||
|
||||
func testCannotEditRoom() async {
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockOwner(allowedStateEvents: []), .mockBob, .mockAlice]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: false, isPublic: false, members: mockedMembers))
|
||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
|
||||
_ = await context.$viewState.values.first(where: { !$0.members.isEmpty })
|
||||
|
||||
XCTAssertFalse(context.viewState.canEditRoomAvatar)
|
||||
XCTAssertFalse(context.viewState.canEditRoomName)
|
||||
XCTAssertFalse(context.viewState.canEditRoomTopic)
|
||||
XCTAssertFalse(context.viewState.canEdit)
|
||||
}
|
||||
|
||||
func testCannotEditDirectRoom() async {
|
||||
let mockedMembers: [RoomMemberProxyMock] = [.mockOwner(allowedStateEvents: [.roomAvatar, .roomName, .roomTopic]), .mockBob, .mockAlice]
|
||||
roomProxyMock = RoomProxyMock(with: .init(displayName: "Test", isDirect: true, isPublic: false, members: mockedMembers))
|
||||
viewModel = RoomDetailsScreenViewModel(roomProxy: roomProxyMock, mediaProvider: MockMediaProvider())
|
||||
|
||||
_ = await context.$viewState.values.first(where: { !$0.members.isEmpty })
|
||||
|
||||
XCTAssertFalse(context.viewState.canEdit)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user