Extract composer toolbar to a dedicated component (#1427)

* Extract composer toolbar to a dedicated component

* Use publisher for composer mode

* Introduce `RoomScreenComposerProvider`

* Add `ComposerToolbarViewModelTests`

* Rename protocols and add passthrough subjects for focused and composer mode

* Remove `ComposerToolbarViewActionHandler` and `ComposerToolbarCoordinatorParameters`

* Remove `RoomScreenComposerActionHandlerProtocol` and `RoomScreenComposerProviderProtocol`

* Re-arrange code a bit

* Remove composer mode being stored on `RoomScreen`

* Rename `process(viewAction: ComposerToolbarViewAction)` to `process(composerAction: ComposerToolbarViewAction)`

* Replace PassthroughSubject with direct function call

* Remove `ComposerToolbarCoordinator`

* Remove `cancelEdit` and `cancelReply` from external composer view model actions

* Use `RoomScreenComposerAction` as a sub-`RoomScreenViewModelAction`

* Move `ComposerToolbarViewModel` callback to actionsSubject

* Move `RoomScreenViewModel` callback to actionsSubject

* Fix `RoomScreenViewModelTests`

* Rename `composerAction` parameter to `roomAction`

* Fix unit tests
This commit is contained in:
aringenbach
2023-08-08 14:25:23 +02:00
committed by GitHub
parent 9a7a9c8d98
commit 59865a4b16
19 changed files with 513 additions and 204 deletions
@@ -0,0 +1,59 @@
//
// Copyright 2023 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.
//
@testable import ElementX
import XCTest
@MainActor
class ComposerToolbarViewModelTests: XCTestCase {
func testComposerFocus() {
let viewModel = ComposerToolbarViewModel()
viewModel.process(roomAction: .setMode(mode: .edit(originalItemId: TimelineItemIdentifier(timelineID: "mock"))))
XCTAssertTrue(viewModel.state.bindings.composerFocused)
viewModel.process(roomAction: .removeFocus)
XCTAssertFalse(viewModel.state.bindings.composerFocused)
}
func testComposerMode() {
let viewModel = ComposerToolbarViewModel()
let mode: RoomScreenComposerMode = .edit(originalItemId: TimelineItemIdentifier(timelineID: "mock"))
viewModel.process(roomAction: .setMode(mode: mode))
XCTAssertEqual(viewModel.state.composerMode, mode)
viewModel.process(roomAction: .clear)
XCTAssertEqual(viewModel.state.composerMode, .default)
}
func testComposerModeIsPublished() {
let viewModel = ComposerToolbarViewModel()
let mode: RoomScreenComposerMode = .edit(originalItemId: TimelineItemIdentifier(timelineID: "mock"))
let expectation = expectation(description: "Composer mode is published")
let cancellable = viewModel
.context
.$viewState
.map(\.composerMode)
.removeDuplicates()
.dropFirst()
.sink(receiveValue: { composerMode in
XCTAssertEqual(composerMode, mode)
expectation.fulfill()
})
viewModel.process(roomAction: .setMode(mode: mode))
wait(for: [expectation], timeout: 2.0)
cancellable.cancel()
}
}
@@ -15,11 +15,14 @@
//
@testable import ElementX
import Combine
import XCTest
@MainActor
class RoomScreenViewModelTests: XCTestCase {
var userIndicatorControllerMock: UserIndicatorControllerMock!
var cancellables = Set<AnyCancellable>()
override func setUp() async throws {
userIndicatorControllerMock = UserIndicatorControllerMock.default
@@ -195,15 +198,17 @@ class RoomScreenViewModelTests: XCTestCase {
appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics,
userIndicatorController: userIndicatorControllerMock)
viewModel.callback = { action in
switch action {
case .displayRoomMemberDetails(let member):
XCTAssert(member === roomMemberMock)
default:
XCTFail("Did not received the expected action")
viewModel.actions
.sink { action in
switch action {
case .displayRoomMemberDetails(let member):
XCTAssert(member === roomMemberMock)
default:
XCTFail("Did not received the expected action")
}
expectation.fulfill()
}
expectation.fulfill()
}
.store(in: &cancellables)
// Test
viewModel.context.send(viewAction: .tappedOnUser(userID: "bob"))
@@ -232,15 +237,17 @@ class RoomScreenViewModelTests: XCTestCase {
analytics: ServiceLocator.shared.analytics,
userIndicatorController: userIndicatorControllerMock)
viewModel.callback = { action in
switch action {
case .displayRoomMemberDetails(let member):
XCTAssert(member === roomMemberMock)
expectation.fulfill()
default:
XCTFail("Did not received the expected action")
viewModel.actions
.sink { action in
switch action {
case .displayRoomMemberDetails(let member):
XCTAssert(member === roomMemberMock)
expectation.fulfill()
default:
XCTFail("Did not received the expected action")
}
}
}
.store(in: &cancellables)
// Test
viewModel.context.send(viewAction: .tappedOnUser(userID: "bob"))
@@ -268,9 +275,11 @@ class RoomScreenViewModelTests: XCTestCase {
appSettings: ServiceLocator.shared.settings,
analytics: ServiceLocator.shared.analytics,
userIndicatorController: userIndicatorControllerMock)
viewModel.callback = { _ in
XCTFail("Should not receive any action")
}
viewModel.actions
.sink { _ in
XCTFail("Should not receive any action")
}
.store(in: &cancellables)
// Test
let deferred = deferFulfillment(viewModel.context.$viewState.collect(2).first(),