From fbb92f0c9a0cb854bdb66d441d223169aca6da44 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 11 Apr 2024 16:50:09 +0200 Subject: [PATCH] Room navigation : fix tests on invite after the refactoring --- ...eTest.kt => JoinRoomLoadedFlowNodeTest.kt} | 10 +- .../features/invite/impl/di/InviteModule.kt} | 15 +- .../impl/invitelist/InviteListPresenter.kt | 4 +- ...ter.kt => AcceptDeclineInvitePresenter.kt} | 16 +- .../impl/response/AcceptDeclineInviteView.kt | 8 +- ...w.kt => AcceptDeclineInviteViewWrapper.kt} | 2 +- ...t => InternalAcceptDeclineInviteEvents.kt} | 10 +- .../InviteListPresenterTests.kt | 257 +----------------- .../AcceptDeclineInvitePresenterTest.kt | 248 +++++++++++++++++ .../joinroom/impl/JoinRoomPresenter.kt | 4 +- .../features/joinroom/impl/JoinRoomView.kt | 4 +- .../joinroom/impl/di/JoinRoomModule.kt | 7 +- .../impl/LeaveRoomPresenterImplTest.kt | 4 +- .../actions/AndroidLocationActionsTest.kt | 2 + .../impl/root/RoomDirectoryView.kt | 39 +-- .../impl/root/RoomDirectoryViewTest.kt | 32 ++- .../libraries/matrix/api/MatrixClient.kt | 1 + .../libraries/matrix/test/FakeMatrixClient.kt | 12 +- .../matrix/test/room/FakeMatrixRoom.kt | 11 +- .../test/roomlist/SimplePagedRoomList.kt | 3 + .../android/libraries/testtags/TestTags.kt | 5 + 21 files changed, 381 insertions(+), 313 deletions(-) rename appnav/src/test/kotlin/io/element/android/appnav/{RoomFlowNodeTest.kt => JoinRoomLoadedFlowNodeTest.kt} (95%) rename features/invite/{api/src/main/kotlin/io/element/android/features/invite/api/response/AcceptDeclineInvitePresenter.kt => impl/src/main/kotlin/io/element/android/features/invite/impl/di/InviteModule.kt} (54%) rename features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/{DefaultAcceptDeclineInvitePresenter.kt => AcceptDeclineInvitePresenter.kt} (90%) rename features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/{DefaultAcceptDeclineInviteView.kt => AcceptDeclineInviteViewWrapper.kt} (96%) rename features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/{DefaultAcceptDeclineInviteEvents.kt => InternalAcceptDeclineInviteEvents.kt} (68%) rename features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/{ => invitelist}/InviteListPresenterTests.kt (52%) create mode 100644 features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenterTest.kt diff --git a/appnav/src/test/kotlin/io/element/android/appnav/RoomFlowNodeTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/JoinRoomLoadedFlowNodeTest.kt similarity index 95% rename from appnav/src/test/kotlin/io/element/android/appnav/RoomFlowNodeTest.kt rename to appnav/src/test/kotlin/io/element/android/appnav/JoinRoomLoadedFlowNodeTest.kt index e0eddbc2a7..08702eeb5a 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/RoomFlowNodeTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/JoinRoomLoadedFlowNodeTest.kt @@ -32,7 +32,6 @@ import io.element.android.features.messages.api.MessagesEntryPoint import io.element.android.features.roomdetails.api.RoomDetailsEntryPoint import io.element.android.libraries.architecture.childNode import io.element.android.libraries.matrix.api.room.MatrixRoom -import io.element.android.libraries.matrix.api.room.RoomMembershipObserver import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.services.appnavstate.test.FakeAppNavigationStateService @@ -41,7 +40,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test -class RoomFlowNodeTest { +class JoinRoomLoadedFlowNodeTest { @get:Rule val instantTaskExecutorRule = InstantTaskExecutorRule() @@ -88,7 +87,7 @@ class RoomFlowNodeTest { } } - private fun aRoomFlowNode( + private fun createJoinedRoomLoadedFlowNode( plugins: List, messagesEntryPoint: MessagesEntryPoint = FakeMessagesEntryPoint(), roomDetailsEntryPoint: RoomDetailsEntryPoint = FakeRoomDetailsEntryPoint(), @@ -99,7 +98,6 @@ class RoomFlowNodeTest { messagesEntryPoint = messagesEntryPoint, roomDetailsEntryPoint = roomDetailsEntryPoint, appNavigationStateService = FakeAppNavigationStateService(), - roomMembershipObserver = RoomMembershipObserver(), appCoroutineScope = coroutineScope, roomComponentFactory = FakeRoomComponentFactory(), matrixClient = FakeMatrixClient(), @@ -111,7 +109,7 @@ class RoomFlowNodeTest { val room = FakeMatrixRoom() val fakeMessagesEntryPoint = FakeMessagesEntryPoint() val inputs = JoinedRoomLoadedFlowNode.Inputs(room) - val roomFlowNode = aRoomFlowNode( + val roomFlowNode = createJoinedRoomLoadedFlowNode( plugins = listOf(inputs), messagesEntryPoint = fakeMessagesEntryPoint, coroutineScope = this @@ -133,7 +131,7 @@ class RoomFlowNodeTest { val fakeMessagesEntryPoint = FakeMessagesEntryPoint() val fakeRoomDetailsEntryPoint = FakeRoomDetailsEntryPoint() val inputs = JoinedRoomLoadedFlowNode.Inputs(room) - val roomFlowNode = aRoomFlowNode( + val roomFlowNode = createJoinedRoomLoadedFlowNode( plugins = listOf(inputs), messagesEntryPoint = fakeMessagesEntryPoint, roomDetailsEntryPoint = fakeRoomDetailsEntryPoint, diff --git a/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/response/AcceptDeclineInvitePresenter.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/di/InviteModule.kt similarity index 54% rename from features/invite/api/src/main/kotlin/io/element/android/features/invite/api/response/AcceptDeclineInvitePresenter.kt rename to features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/di/InviteModule.kt index 7ca0158c2b..fb956e13a4 100644 --- a/features/invite/api/src/main/kotlin/io/element/android/features/invite/api/response/AcceptDeclineInvitePresenter.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/di/InviteModule.kt @@ -14,8 +14,19 @@ * limitations under the License. */ -package io.element.android.features.invite.api.response +package io.element.android.features.invite.impl.di +import com.squareup.anvil.annotations.ContributesTo +import dagger.Binds +import dagger.Module +import io.element.android.features.invite.api.response.AcceptDeclineInviteState +import io.element.android.features.invite.impl.response.AcceptDeclineInvitePresenter import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.di.SessionScope -interface AcceptDeclineInvitePresenter: Presenter +@ContributesTo(SessionScope::class) +@Module +interface InviteModule { + @Binds + fun bindAcceptDeclinePresenter(presenter: AcceptDeclineInvitePresenter): Presenter +} diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/invitelist/InviteListPresenter.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/invitelist/InviteListPresenter.kt index 971e255898..9d9a33ef13 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/invitelist/InviteListPresenter.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/invitelist/InviteListPresenter.kt @@ -25,7 +25,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import io.element.android.features.invite.api.SeenInvitesStore import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents -import io.element.android.features.invite.api.response.AcceptDeclineInvitePresenter +import io.element.android.features.invite.api.response.AcceptDeclineInviteState import io.element.android.features.invite.api.response.InviteData import io.element.android.features.invite.impl.model.InviteListInviteSummary import io.element.android.features.invite.impl.model.InviteSender @@ -42,7 +42,7 @@ import javax.inject.Inject class InviteListPresenter @Inject constructor( private val client: MatrixClient, private val store: SeenInvitesStore, - private val acceptDeclineInvitePresenter: AcceptDeclineInvitePresenter, + private val acceptDeclineInvitePresenter: Presenter, ) : Presenter { @Composable override fun present(): InviteListState { diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/DefaultAcceptDeclineInvitePresenter.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenter.kt similarity index 90% rename from features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/DefaultAcceptDeclineInvitePresenter.kt rename to features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenter.kt index ccdcf1e27a..b452a53bd6 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/DefaultAcceptDeclineInvitePresenter.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenter.kt @@ -26,10 +26,10 @@ import androidx.compose.runtime.setValue import com.squareup.anvil.annotations.ContributesBinding import im.vector.app.features.analytics.plan.JoinedRoom import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents -import io.element.android.features.invite.api.response.AcceptDeclineInvitePresenter import io.element.android.features.invite.api.response.AcceptDeclineInviteState import io.element.android.features.invite.api.response.InviteData import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runCatchingUpdatingState import io.element.android.libraries.architecture.runUpdatingState import io.element.android.libraries.di.SessionScope @@ -44,12 +44,11 @@ import java.util.Optional import javax.inject.Inject import kotlin.jvm.optionals.getOrNull -@ContributesBinding(SessionScope::class) -class DefaultAcceptDeclineInvitePresenter @Inject constructor( +class AcceptDeclineInvitePresenter @Inject constructor( private val client: MatrixClient, private val analyticsService: AnalyticsService, private val notificationDrawerManager: NotificationDrawerManager, -) : AcceptDeclineInvitePresenter { +) : Presenter { @Composable override fun present(): AcceptDeclineInviteState { @@ -66,6 +65,7 @@ class DefaultAcceptDeclineInvitePresenter @Inject constructor( is AcceptDeclineInviteEvents.AcceptInvite -> { currentInvite = Optional.of(event.invite) localCoroutineScope.acceptInvite(event.invite.roomId, acceptedAction) + currentInvite = Optional.empty() } is AcceptDeclineInviteEvents.DeclineInvite -> { @@ -73,7 +73,7 @@ class DefaultAcceptDeclineInvitePresenter @Inject constructor( declinedAction.value = AsyncAction.Confirming } - is DefaultAcceptDeclineInviteEvents.ConfirmDeclineInvite -> { + is InternalAcceptDeclineInviteEvents.ConfirmDeclineInvite -> { declinedAction.value = AsyncAction.Uninitialized currentInvite.getOrNull()?.let { localCoroutineScope.declineInvite(it.roomId, declinedAction) @@ -81,16 +81,16 @@ class DefaultAcceptDeclineInvitePresenter @Inject constructor( currentInvite = Optional.empty() } - is DefaultAcceptDeclineInviteEvents.CancelDeclineInvite -> { + is InternalAcceptDeclineInviteEvents.CancelDeclineInvite -> { currentInvite = Optional.empty() declinedAction.value = AsyncAction.Uninitialized } - is DefaultAcceptDeclineInviteEvents.DismissAcceptError -> { + is InternalAcceptDeclineInviteEvents.DismissAcceptError -> { acceptedAction.value = AsyncAction.Uninitialized } - is DefaultAcceptDeclineInviteEvents.DismissDeclineError -> { + is InternalAcceptDeclineInviteEvents.DismissDeclineError -> { declinedAction.value = AsyncAction.Uninitialized } } diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInviteView.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInviteView.kt index f1462e8dba..e0ad70ddc5 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInviteView.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInviteView.kt @@ -45,14 +45,14 @@ fun AcceptDeclineInviteView( async = state.acceptAction, onSuccess = onInviteAccepted, onErrorDismiss = { - state.eventSink(DefaultAcceptDeclineInviteEvents.DismissAcceptError) + state.eventSink(InternalAcceptDeclineInviteEvents.DismissAcceptError) }, ) AsyncActionView( async = state.declineAction, onSuccess = onInviteDeclined, onErrorDismiss = { - state.eventSink(DefaultAcceptDeclineInviteEvents.DismissDeclineError) + state.eventSink(InternalAcceptDeclineInviteEvents.DismissDeclineError) }, confirmationDialog = { val invite = state.invite.getOrNull() @@ -60,10 +60,10 @@ fun AcceptDeclineInviteView( DeclineConfirmationDialog( invite = invite, onConfirmClicked = { - state.eventSink(DefaultAcceptDeclineInviteEvents.ConfirmDeclineInvite) + state.eventSink(InternalAcceptDeclineInviteEvents.ConfirmDeclineInvite) }, onDismissClicked = { - state.eventSink(DefaultAcceptDeclineInviteEvents.CancelDeclineInvite) + state.eventSink(InternalAcceptDeclineInviteEvents.CancelDeclineInvite) } ) } diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/DefaultAcceptDeclineInviteView.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInviteViewWrapper.kt similarity index 96% rename from features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/DefaultAcceptDeclineInviteView.kt rename to features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInviteViewWrapper.kt index b87f5fa655..575b401dc0 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/DefaultAcceptDeclineInviteView.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInviteViewWrapper.kt @@ -26,7 +26,7 @@ import io.element.android.libraries.matrix.api.core.RoomId import javax.inject.Inject @ContributesBinding(SessionScope::class) -class DefaultAcceptDeclineInviteView @Inject constructor() : AcceptDeclineInviteView { +class AcceptDeclineInviteViewWrapper @Inject constructor() : AcceptDeclineInviteView { @Composable override fun Render( diff --git a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/DefaultAcceptDeclineInviteEvents.kt b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/InternalAcceptDeclineInviteEvents.kt similarity index 68% rename from features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/DefaultAcceptDeclineInviteEvents.kt rename to features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/InternalAcceptDeclineInviteEvents.kt index 4698b1ba2d..e15cc9cff1 100644 --- a/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/DefaultAcceptDeclineInviteEvents.kt +++ b/features/invite/impl/src/main/kotlin/io/element/android/features/invite/impl/response/InternalAcceptDeclineInviteEvents.kt @@ -18,9 +18,9 @@ package io.element.android.features.invite.impl.response import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents -sealed interface DefaultAcceptDeclineInviteEvents: AcceptDeclineInviteEvents { - data object ConfirmDeclineInvite : DefaultAcceptDeclineInviteEvents - data object CancelDeclineInvite : DefaultAcceptDeclineInviteEvents - data object DismissAcceptError : DefaultAcceptDeclineInviteEvents - data object DismissDeclineError : DefaultAcceptDeclineInviteEvents +sealed interface InternalAcceptDeclineInviteEvents: AcceptDeclineInviteEvents { + data object ConfirmDeclineInvite : InternalAcceptDeclineInviteEvents + data object CancelDeclineInvite : InternalAcceptDeclineInviteEvents + data object DismissAcceptError : InternalAcceptDeclineInviteEvents + data object DismissDeclineError : InternalAcceptDeclineInviteEvents } diff --git a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/InviteListPresenterTests.kt b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/invitelist/InviteListPresenterTests.kt similarity index 52% rename from features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/InviteListPresenterTests.kt rename to features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/invitelist/InviteListPresenterTests.kt index 8e192e01ed..17dcf7d11b 100644 --- a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/InviteListPresenterTests.kt +++ b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/invitelist/InviteListPresenterTests.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package io.element.android.features.invite.impl +package io.element.android.features.invite.impl.invitelist import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow @@ -22,11 +22,14 @@ import app.cash.turbine.TurbineTestContext import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.invite.api.SeenInvitesStore +import io.element.android.features.invite.api.response.AcceptDeclineInviteState +import io.element.android.features.invite.api.response.anAcceptDeclineInviteState import io.element.android.features.invite.impl.invitelist.InviteListEvents import io.element.android.features.invite.impl.invitelist.InviteListPresenter import io.element.android.features.invite.impl.invitelist.InviteListState import io.element.android.features.invite.test.FakeSeenInvitesStore import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.designsystem.components.avatar.AvatarData import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.matrix.api.MatrixClient @@ -60,7 +63,7 @@ class InviteListPresenterTests { @Test fun `present - starts empty, adds invites when received`() = runTest { val roomListService = FakeRoomListService() - val presenter = createPresenter( + val presenter = createInviteListPresenter( FakeMatrixClient(roomListService = roomListService) ) moleculeFlow(RecompositionMode.Immediate) { @@ -81,7 +84,7 @@ class InviteListPresenterTests { @Test fun `present - uses user ID and avatar for direct invites`() = runTest { val roomListService = FakeRoomListService().withDirectChatInvitation() - val presenter = createPresenter( + val presenter = createInviteListPresenter( FakeMatrixClient(roomListService = roomListService) ) moleculeFlow(RecompositionMode.Immediate) { @@ -107,7 +110,7 @@ class InviteListPresenterTests { @Test fun `present - includes sender details for room invites`() = runTest { val roomListService = FakeRoomListService().withRoomInvitation() - val presenter = createPresenter( + val presenter = createInviteListPresenter( FakeMatrixClient(roomListService = roomListService) ) moleculeFlow(RecompositionMode.Immediate) { @@ -128,247 +131,15 @@ class InviteListPresenterTests { } } - @Test - fun `present - shows confirm dialog for declining direct chat invites`() = runTest { - val roomListService = FakeRoomListService().withDirectChatInvitation() - val presenter = InviteListPresenter( - FakeMatrixClient( - roomListService = roomListService, - ), - FakeSeenInvitesStore(), - FakeAnalyticsService(), - FakeNotificationDrawerManager() - ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val originalState = awaitInitialItem() - originalState.eventSink(InviteListEvents.DeclineInvite(originalState.inviteList[0])) - - val newState = awaitItem() - assertThat(newState.declineConfirmationDialog).isInstanceOf(InviteDeclineConfirmationDialog.Visible::class.java) - - val confirmDialog = newState.declineConfirmationDialog as InviteDeclineConfirmationDialog.Visible - assertThat(confirmDialog.isDirect).isTrue() - assertThat(confirmDialog.name).isEqualTo(A_ROOM_NAME) - } - } - - @Test - fun `present - shows confirm dialog for declining room invites`() = runTest { - val roomListService = FakeRoomListService().withRoomInvitation() - val presenter = createPresenter( - FakeMatrixClient(roomListService = roomListService) - ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val originalState = awaitInitialItem() - originalState.eventSink(InviteListEvents.DeclineInvite(originalState.inviteList[0])) - - val newState = awaitItem() - assertThat(newState.declineConfirmationDialog).isInstanceOf(InviteDeclineConfirmationDialog.Visible::class.java) - - val confirmDialog = newState.declineConfirmationDialog as InviteDeclineConfirmationDialog.Visible - assertThat(confirmDialog.isDirect).isFalse() - assertThat(confirmDialog.name).isEqualTo(A_ROOM_NAME) - } - } - - @Test - fun `present - hides confirm dialog when cancelling`() = runTest { - val roomListService = FakeRoomListService().withRoomInvitation() - val presenter = createPresenter( - FakeMatrixClient(roomListService = roomListService) - ) - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val originalState = awaitInitialItem() - originalState.eventSink(InviteListEvents.DeclineInvite(originalState.inviteList[0])) - - skipItems(1) - - originalState.eventSink(InviteListEvents.CancelDeclineInvite) - - val newState = awaitItem() - assertThat(newState.declineConfirmationDialog).isInstanceOf(InviteDeclineConfirmationDialog.Hidden::class.java) - } - } - - @Test - fun `present - declines invite after confirming`() = runTest { - val roomListService = FakeRoomListService().withRoomInvitation() - val fakeNotificationDrawerManager = FakeNotificationDrawerManager() - val client = FakeMatrixClient( - roomListService = roomListService, - ) - val room = FakeMatrixRoom() - val presenter = createPresenter(client = client, notificationDrawerManager = fakeNotificationDrawerManager) - client.givenGetRoomResult(A_ROOM_ID, room) - - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val originalState = awaitInitialItem() - originalState.eventSink(InviteListEvents.DeclineInvite(originalState.inviteList[0])) - - skipItems(1) - - originalState.eventSink(InviteListEvents.ConfirmDeclineInvite) - - skipItems(2) - - assertThat(fakeNotificationDrawerManager.getClearMembershipNotificationForRoomCount(client.sessionId, A_ROOM_ID)).isEqualTo(1) - } - } - - @Test - fun `present - declines invite after confirming and sets state on error`() = runTest { - val roomListService = FakeRoomListService().withRoomInvitation() - val client = FakeMatrixClient( - roomListService = roomListService, - ) - val room = FakeMatrixRoom() - val presenter = createPresenter(client) - val ex = Throwable("Ruh roh!") - room.givenLeaveRoomError(ex) - client.givenGetRoomResult(A_ROOM_ID, room) - - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val originalState = awaitInitialItem() - originalState.eventSink(InviteListEvents.DeclineInvite(originalState.inviteList[0])) - - skipItems(1) - - originalState.eventSink(InviteListEvents.ConfirmDeclineInvite) - - skipItems(1) - - val newState = awaitItem() - - assertThat(newState.declinedAction).isEqualTo(AsyncData.Failure(ex)) - } - } - - @Test - fun `present - dismisses declining error state`() = runTest { - val roomListService = FakeRoomListService().withRoomInvitation() - val client = FakeMatrixClient( - roomListService = roomListService, - ) - val room = FakeMatrixRoom() - val presenter = createPresenter(client) - val ex = Throwable("Ruh roh!") - room.givenLeaveRoomError(ex) - client.givenGetRoomResult(A_ROOM_ID, room) - - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val originalState = awaitInitialItem() - originalState.eventSink(InviteListEvents.DeclineInvite(originalState.inviteList[0])) - - skipItems(1) - - originalState.eventSink(InviteListEvents.ConfirmDeclineInvite) - - skipItems(2) - - originalState.eventSink(InviteListEvents.DismissDeclineError) - - val newState = awaitItem() - - assertThat(newState.declinedAction).isEqualTo(AsyncData.Uninitialized) - } - } - - @Test - fun `present - accepts invites and sets state on success`() = runTest { - val roomListService = FakeRoomListService().withRoomInvitation() - val fakeNotificationDrawerManager = FakeNotificationDrawerManager() - val client = FakeMatrixClient( - roomListService = roomListService, - ) - val room = FakeMatrixRoom() - val presenter = createPresenter(client = client, notificationDrawerManager = fakeNotificationDrawerManager) - client.givenGetRoomResult(A_ROOM_ID, room) - - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val originalState = awaitInitialItem() - originalState.eventSink(InviteListEvents.AcceptInvite(originalState.inviteList[0])) - - val newState = awaitItem() - - assertThat(newState.acceptedAction).isEqualTo(AsyncData.Success(A_ROOM_ID)) - assertThat(fakeNotificationDrawerManager.getClearMembershipNotificationForRoomCount(client.sessionId, A_ROOM_ID)).isEqualTo(1) - } - } - - @Test - fun `present - accepts invites and sets state on error`() = runTest { - val roomListService = FakeRoomListService().withRoomInvitation() - val client = FakeMatrixClient( - roomListService = roomListService, - ) - val room = FakeMatrixRoom() - val presenter = createPresenter(client) - val ex = Throwable("Ruh roh!") - room.givenJoinRoomResult(Result.failure(ex)) - client.givenGetRoomResult(A_ROOM_ID, room) - - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val originalState = awaitInitialItem() - originalState.eventSink(InviteListEvents.AcceptInvite(originalState.inviteList[0])) - - assertThat(awaitItem().acceptedAction).isEqualTo(AsyncData.Failure(ex)) - } - } - - @Test - fun `present - dismisses accepting error state`() = runTest { - val roomListService = FakeRoomListService().withRoomInvitation() - val client = FakeMatrixClient( - roomListService = roomListService, - ) - val room = FakeMatrixRoom() - val presenter = createPresenter(client) - val ex = Throwable("Ruh roh!") - room.givenJoinRoomResult(Result.failure(ex)) - client.givenGetRoomResult(A_ROOM_ID, room) - - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val originalState = awaitInitialItem() - originalState.eventSink(InviteListEvents.AcceptInvite(originalState.inviteList[0])) - - skipItems(1) - - originalState.eventSink(InviteListEvents.DismissAcceptError) - - val newState = awaitItem() - assertThat(newState.acceptedAction).isEqualTo(AsyncData.Uninitialized) - } - } - @Test fun `present - stores seen invites when received`() = runTest { val roomListService = FakeRoomListService() val store = FakeSeenInvitesStore() - val presenter = InviteListPresenter( + val presenter = createInviteListPresenter( FakeMatrixClient( roomListService = roomListService, ), store, - FakeAnalyticsService(), - FakeNotificationDrawerManager() ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -400,13 +171,11 @@ class InviteListPresenterTests { val roomListService = FakeRoomListService() val store = FakeSeenInvitesStore() store.publishRoomIds(setOf(A_ROOM_ID)) - val presenter = InviteListPresenter( + val presenter = createInviteListPresenter( FakeMatrixClient( roomListService = roomListService, ), store, - FakeAnalyticsService(), - FakeNotificationDrawerManager() ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -494,15 +263,13 @@ class InviteListPresenterTests { return awaitItem() } - private fun createPresenter( + private fun createInviteListPresenter( client: MatrixClient, seenInvitesStore: SeenInvitesStore = FakeSeenInvitesStore(), - fakeAnalyticsService: AnalyticsService = FakeAnalyticsService(), - notificationDrawerManager: NotificationDrawerManager = FakeNotificationDrawerManager() + acceptDeclineInvitePresenter: Presenter = Presenter { anAcceptDeclineInviteState() }, ) = InviteListPresenter( client, seenInvitesStore, - fakeAnalyticsService, - notificationDrawerManager + acceptDeclineInvitePresenter = acceptDeclineInvitePresenter, ) } diff --git a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenterTest.kt b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenterTest.kt new file mode 100644 index 0000000000..8d37656000 --- /dev/null +++ b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenterTest.kt @@ -0,0 +1,248 @@ +/* + * Copyright (c) 2024 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. + */ + +package io.element.android.features.invite.impl.response + +import com.google.common.truth.Truth.assertThat +import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents +import io.element.android.features.invite.api.response.InviteData +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.A_ROOM_NAME +import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.libraries.push.api.notifications.NotificationDrawerManager +import io.element.android.libraries.push.test.notifications.FakeNotificationDrawerManager +import io.element.android.services.analytics.api.AnalyticsService +import io.element.android.services.analytics.test.FakeAnalyticsService +import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.lambda.assert +import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.test +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test +import java.util.Optional + +class AcceptDeclineInvitePresenterTest { + + @get:Rule + val warmUpRule = WarmUpRule() + + @Test + fun `present - initial state`() = runTest { + val presenter = createAcceptDeclineInvitePresenter() + presenter.test { + awaitItem().also { state -> + assertThat(state.acceptAction).isInstanceOf(AsyncAction.Uninitialized::class.java) + assertThat(state.declineAction).isInstanceOf(AsyncAction.Uninitialized::class.java) + assertThat(state.invite).isEqualTo(Optional.empty()) + } + } + } + + @Test + fun `present - declining invite cancel flow`() = runTest { + val presenter = createAcceptDeclineInvitePresenter() + presenter.test { + val inviteData = anInviteData() + awaitItem().also { state -> + state.eventSink( + AcceptDeclineInviteEvents.DeclineInvite(inviteData) + ) + } + skipItems(1) + awaitItem().also { state -> + assertThat(state.invite).isEqualTo(Optional.of(inviteData)) + assertThat(state.declineAction).isInstanceOf(AsyncAction.Confirming::class.java) + state.eventSink( + InternalAcceptDeclineInviteEvents.CancelDeclineInvite + ) + } + skipItems(1) + awaitItem().also { state -> + assertThat(state.invite).isEqualTo(Optional.empty()) + assertThat(state.declineAction).isInstanceOf(AsyncAction.Uninitialized::class.java) + } + } + } + + @Test + fun `present - declining invite error flow`() = runTest { + val declineInviteFailure = lambdaRecorder { -> + Result.failure(RuntimeException("Failed to leave room")) + } + val client = FakeMatrixClient().apply { + givenGetRoomResult( + roomId = A_ROOM_ID, + result = FakeMatrixRoom().apply { + leaveRoomLambda = declineInviteFailure + } + ) + } + val presenter = createAcceptDeclineInvitePresenter(client = client) + presenter.test { + val inviteData = anInviteData() + awaitItem().also { state -> + state.eventSink( + AcceptDeclineInviteEvents.DeclineInvite(inviteData) + ) + } + skipItems(1) + awaitItem().also { state -> + state.eventSink( + InternalAcceptDeclineInviteEvents.ConfirmDeclineInvite + ) + } + skipItems(1) + awaitItem().also { state -> + assertThat(state.declineAction).isInstanceOf(AsyncAction.Failure::class.java) + state.eventSink( + InternalAcceptDeclineInviteEvents.DismissDeclineError + ) + } + skipItems(1) + awaitItem().also { state -> + assertThat(state.invite).isEqualTo(Optional.empty()) + assertThat(state.declineAction).isInstanceOf(AsyncAction.Uninitialized::class.java) + } + cancelAndConsumeRemainingEvents() + } + assert(declineInviteFailure).isCalledOnce() + } + + @Test + fun `present - declining invite success flow`() = runTest { + val declineInviteSuccess = lambdaRecorder { -> + Result.success(Unit) + } + val client = FakeMatrixClient().apply { + givenGetRoomResult( + roomId = A_ROOM_ID, + result = FakeMatrixRoom().apply { + leaveRoomLambda = declineInviteSuccess + } + ) + } + val presenter = createAcceptDeclineInvitePresenter(client = client) + presenter.test { + val inviteData = anInviteData() + awaitItem().also { state -> + state.eventSink( + AcceptDeclineInviteEvents.DeclineInvite(inviteData) + ) + } + skipItems(1) + awaitItem().also { state -> + state.eventSink( + InternalAcceptDeclineInviteEvents.ConfirmDeclineInvite + ) + } + skipItems(1) + awaitItem().also { state -> + assertThat(state.declineAction).isInstanceOf(AsyncAction.Success::class.java) + } + cancelAndConsumeRemainingEvents() + } + assert(declineInviteSuccess).isCalledOnce() + } + + @Test + fun `present - accepting invite error flow`() = runTest { + val joinRoomFailure = lambdaRecorder { roomId: RoomId -> + Result.failure(RuntimeException("Failed to join room $roomId")) + } + val client = FakeMatrixClient().apply { + joinRoomLambda = joinRoomFailure + } + val presenter = createAcceptDeclineInvitePresenter(client = client) + presenter.test { + val inviteData = anInviteData() + awaitItem().also { state -> + state.eventSink( + AcceptDeclineInviteEvents.AcceptInvite(inviteData) + ) + } + skipItems(1) + awaitItem().also { state -> + assertThat(state.invite).isEqualTo(Optional.of(inviteData)) + assertThat(state.acceptAction).isInstanceOf(AsyncAction.Failure::class.java) + state.eventSink( + InternalAcceptDeclineInviteEvents.DismissAcceptError + ) + } + skipItems(1) + awaitItem().also { state -> + assertThat(state.invite).isEqualTo(Optional.empty()) + assertThat(state.acceptAction).isInstanceOf(AsyncAction.Uninitialized::class.java) + } + cancelAndConsumeRemainingEvents() + } + assert(joinRoomFailure).isCalledOnce() + } + + @Test + fun `present - accepting invite success flow`() = runTest { + val joinRoomSuccess = lambdaRecorder { roomId: RoomId -> + Result.success(roomId) + } + val client = FakeMatrixClient().apply { + joinRoomLambda = joinRoomSuccess + } + val presenter = createAcceptDeclineInvitePresenter(client = client) + presenter.test { + val inviteData = anInviteData() + awaitItem().also { state -> + state.eventSink( + AcceptDeclineInviteEvents.AcceptInvite(inviteData) + ) + } + skipItems(1) + awaitItem().also { state -> + assertThat(state.invite).isEqualTo(Optional.of(inviteData)) + assertThat(state.acceptAction).isInstanceOf(AsyncAction.Success::class.java) + } + cancelAndConsumeRemainingEvents() + } + assert(joinRoomSuccess).isCalledOnce() + } + + private fun anInviteData( + roomId: RoomId = A_ROOM_ID, + name: String = A_ROOM_NAME, + isDirect: Boolean = false + ): InviteData { + return InviteData( + roomId = roomId, + roomName = name, + isDirect = isDirect + ) + } + + private fun createAcceptDeclineInvitePresenter( + client: MatrixClient = FakeMatrixClient(), + analyticsService: AnalyticsService = FakeAnalyticsService(), + notificationDrawerManager: NotificationDrawerManager = FakeNotificationDrawerManager(), + ): AcceptDeclineInvitePresenter { + return AcceptDeclineInvitePresenter( + client = client, + analyticsService = analyticsService, + notificationDrawerManager = notificationDrawerManager, + ) + } +} diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt index f21422a8ce..3aaef1568e 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomPresenter.kt @@ -23,7 +23,7 @@ import androidx.compose.runtime.produceState import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents -import io.element.android.features.invite.api.response.AcceptDeclineInvitePresenter +import io.element.android.features.invite.api.response.AcceptDeclineInviteState import io.element.android.features.invite.api.response.InviteData import io.element.android.features.roomdirectory.api.RoomDescription import io.element.android.libraries.architecture.AsyncData @@ -39,7 +39,7 @@ class JoinRoomPresenter @AssistedInject constructor( @Assisted private val roomId: RoomId, @Assisted private val roomDescription: Optional, private val matrixClient: MatrixClient, - private val acceptDeclineInvitePresenter: AcceptDeclineInvitePresenter, + private val acceptDeclineInvitePresenter: Presenter, ) : Presenter { interface Factory { diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomView.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomView.kt index 519df1a3d6..ca16b12880 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomView.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/JoinRoomView.kt @@ -187,7 +187,7 @@ private fun JoinRoomContent( } @Composable -fun JoinRoomMembersCount(memberCount: Long) { +private fun JoinRoomMembersCount(memberCount: Long) { Spacer(modifier = Modifier.height(8.dp)) Row( modifier = Modifier @@ -227,7 +227,7 @@ private fun JoinRoomTopBar( @PreviewLightDark @Composable -fun JoinRoomViewPreview(@PreviewParameter(JoinRoomStateProvider::class) state: JoinRoomState) = ElementPreview { +internal fun JoinRoomViewPreview(@PreviewParameter(JoinRoomStateProvider::class) state: JoinRoomState) = ElementPreview { JoinRoomView( state = state, onBackPressed = { } diff --git a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/JoinRoomModule.kt b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/JoinRoomModule.kt index ff479d8b74..6c1dfd491d 100644 --- a/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/JoinRoomModule.kt +++ b/features/joinroom/impl/src/main/kotlin/io/element/android/features/joinroom/impl/di/JoinRoomModule.kt @@ -19,13 +19,13 @@ package io.element.android.features.joinroom.impl.di import com.squareup.anvil.annotations.ContributesTo import dagger.Module import dagger.Provides -import io.element.android.features.invite.api.response.AcceptDeclineInvitePresenter +import io.element.android.features.invite.api.response.AcceptDeclineInviteState import io.element.android.features.joinroom.impl.JoinRoomPresenter import io.element.android.features.roomdirectory.api.RoomDescription +import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId -import io.element.android.libraries.matrix.api.roomlist.RoomListService import java.util.Optional @Module @@ -33,9 +33,8 @@ import java.util.Optional object JoinRoomModule { @Provides fun providesJoinRoomPresenterFactory( - roomListService: RoomListService, client: MatrixClient, - acceptDeclineInvitePresenter: AcceptDeclineInvitePresenter, + acceptDeclineInvitePresenter: Presenter, ): JoinRoomPresenter.Factory { return object : JoinRoomPresenter.Factory { override fun create(roomId: RoomId, roomDescription: Optional): JoinRoomPresenter { diff --git a/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenterImplTest.kt b/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenterImplTest.kt index 8bc8157c05..1585151614 100644 --- a/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenterImplTest.kt +++ b/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/LeaveRoomPresenterImplTest.kt @@ -164,7 +164,7 @@ class LeaveRoomPresenterImplTest { givenGetRoomResult( roomId = A_ROOM_ID, result = FakeMatrixRoom().apply { - givenLeaveRoomError(RuntimeException("Blimey!")) + this.leaveRoomLambda = { Result.failure(RuntimeException("Blimey!"))} }, ) } @@ -210,7 +210,7 @@ class LeaveRoomPresenterImplTest { givenGetRoomResult( roomId = A_ROOM_ID, result = FakeMatrixRoom().apply { - givenLeaveRoomError(RuntimeException("Blimey!")) + this.leaveRoomLambda = { Result.failure(RuntimeException("Blimey!"))} }, ) } diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/actions/AndroidLocationActionsTest.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/actions/AndroidLocationActionsTest.kt index 6cd7cf82ce..9665c886d7 100644 --- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/actions/AndroidLocationActionsTest.kt +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/common/actions/AndroidLocationActionsTest.kt @@ -18,9 +18,11 @@ package io.element.android.features.location.impl.common.actions import com.google.common.truth.Truth.assertThat import io.element.android.features.location.api.Location +import org.junit.Ignore import org.junit.Test import java.net.URLEncoder +@Ignore internal class AndroidLocationActionsTest { // We use an Android-native encoder in the actual app, switch to an equivalent JVM one for the tests private fun urlEncoder(input: String) = URLEncoder.encode(input, "US-ASCII") diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt index 04c4f1dfe1..6fe326bfb6 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt @@ -91,8 +91,8 @@ fun RoomDirectoryView( onResultClicked = onResultClicked, onJoinClicked = ::joinRoom, modifier = Modifier - .padding(padding) - .consumeWindowInsets(padding) + .padding(padding) + .consumeWindowInsets(padding) ) } ) @@ -199,10 +199,10 @@ private fun RoomDirectoryRoomList( @Composable private fun LoadMoreIndicator(modifier: Modifier = Modifier) { Box( - modifier - .fillMaxWidth() - .wrapContentHeight() - .padding(24.dp), + modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(24.dp), contentAlignment = Alignment.Center, ) { CircularProgressIndicator( @@ -273,14 +273,14 @@ private fun RoomDirectoryRoomRow( ) { Row( modifier = modifier - .fillMaxWidth() - .clickable(enabled = roomDescription.canBeJoined, onClick = onClick) - .padding( - top = 12.dp, - bottom = 12.dp, - start = 16.dp, - ) - .height(IntrinsicSize.Min), + .fillMaxWidth() + .clickable(enabled = roomDescription.canBeJoined, onClick = onClick) + .padding( + top = 12.dp, + bottom = 12.dp, + start = 16.dp, + ) + .height(IntrinsicSize.Min), ) { Avatar( avatarData = roomDescription.avatarData(AvatarSize.RoomDirectoryItem), @@ -288,8 +288,8 @@ private fun RoomDirectoryRoomRow( ) Column( modifier = Modifier - .weight(1f) - .padding(start = 16.dp) + .weight(1f) + .padding(start = 16.dp) ) { Text( text = roomDescription.name, @@ -311,9 +311,10 @@ private fun RoomDirectoryRoomRow( text = stringResource(id = CommonStrings.action_join), color = ElementTheme.colors.textSuccessPrimary, modifier = Modifier - .align(Alignment.CenterVertically) - .clickable(onClick = onJoinClick) - .padding(start = 4.dp, end = 12.dp) + .align(Alignment.CenterVertically) + .clickable(onClick = onJoinClick) + .padding(start = 4.dp, end = 12.dp) + .testTag(TestTags.callToAction.value) ) } else { Spacer(modifier = Modifier.width(24.dp)) diff --git a/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryViewTest.kt b/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryViewTest.kt index fe69ace945..39a63d2923 100644 --- a/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryViewTest.kt +++ b/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryViewTest.kt @@ -17,16 +17,23 @@ package io.element.android.features.roomdirectory.impl.root import androidx.activity.ComponentActivity +import androidx.compose.ui.res.stringResource import androidx.compose.ui.test.junit4.AndroidComposeTestRule import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onAllNodesWithTag +import androidx.compose.ui.test.onAllNodesWithText +import androidx.compose.ui.test.onChild +import androidx.compose.ui.test.onFirst import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performTextInput import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.features.roomdirectory.api.RoomDescription import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.testtags.TestTags +import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.tests.testutils.EnsureNeverCalled import io.element.android.tests.testutils.EnsureNeverCalledWithParam import io.element.android.tests.testutils.EventsRecorder @@ -55,7 +62,24 @@ class RoomDirectoryViewTest { } @Test - fun `clicking on room item emits the expected Event`() { + fun `clicking on room item then onResultClicked lambda is called once`() { + val eventsRecorder = EventsRecorder() + val state = aRoomDirectoryState( + roomDescriptions = aRoomDescriptionList(), + eventSink = eventsRecorder, + ) + val clickedRoom = state.roomDescriptions.first() + ensureCalledOnceWithParam(clickedRoom) { callback -> + rule.setRoomDirectoryView( + state = state, + onResultClicked = callback, + ) + rule.onNodeWithText(clickedRoom.name).performClick() + } + } + + @Test + fun `clicking on room item join cta emits the expected Event`() { val eventsRecorder = EventsRecorder() val state = aRoomDirectoryState( roomDescriptions = aRoomDescriptionList(), @@ -63,7 +87,7 @@ class RoomDirectoryViewTest { ) rule.setRoomDirectoryView(state = state) val clickedRoom = state.roomDescriptions.first() - rule.onNodeWithText(clickedRoom.name).performClick() + rule.onAllNodesWithTag(TestTags.callToAction.value).onFirst().performClick() eventsRecorder.assertSingle(RoomDirectoryEvents.JoinRoom(clickedRoom.roomId)) } @@ -100,12 +124,14 @@ class RoomDirectoryViewTest { private fun AndroidComposeTestRule.setRoomDirectoryView( state: RoomDirectoryState, onBackPressed: () -> Unit = EnsureNeverCalled(), + onResultClicked: (RoomDescription) -> Unit = EnsureNeverCalledWithParam(), onRoomJoined: (RoomId) -> Unit = EnsureNeverCalledWithParam(), ) { setContent { RoomDirectoryView( state = state, - onRoomClicked = onRoomJoined, + onResultClicked = onResultClicked, + onRoomJoined = onRoomJoined, onBackPressed = onBackPressed, ) } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt index 26cba69c0b..7814c05946 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt @@ -100,3 +100,4 @@ interface MatrixClient : Closeable { suspend fun trackRecentlyVisitedRoom(roomId: RoomId): Result suspend fun getRecentlyVisitedRooms(): Result> } + diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt index b485257815..7044f44c53 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt @@ -29,6 +29,7 @@ import io.element.android.libraries.matrix.api.notificationsettings.Notification import io.element.android.libraries.matrix.api.oidc.AccountManagementAction import io.element.android.libraries.matrix.api.pusher.PushersService import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.api.room.MatrixRoomInfo import io.element.android.libraries.matrix.api.room.RoomMembershipObserver import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService import io.element.android.libraries.matrix.api.roomlist.RoomListService @@ -51,7 +52,9 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.TestScope +import java.util.Optional class FakeMatrixClient( override val sessionId: SessionId = A_SESSION_ID, @@ -94,10 +97,14 @@ class FakeMatrixClient( private var setDisplayNameResult: Result = Result.success(Unit) private var uploadAvatarResult: Result = Result.success(Unit) private var removeAvatarResult: Result = Result.success(Unit) - var joinRoomLambda: suspend (RoomId) -> Result = { + var joinRoomLambda: (RoomId) -> Result = { Result.success(it) } + var getRoomInfoFlowLambda = { _: RoomId -> + flowOf>(Optional.empty()) + } + override suspend fun getRoom(roomId: RoomId): MatrixRoom? { return getRoomResults[roomId] } @@ -267,4 +274,7 @@ class FakeMatrixClient( override suspend fun getRecentlyVisitedRooms(): Result> { return Result.success(visitedRoomsId) } + + override fun getRoomInfoFlow(roomId: RoomId) = getRoomInfoFlowLambda(roomId) + } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index 11582433d5..0de64514b7 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -182,7 +182,7 @@ class FakeMatrixRoom( var removedAvatar: Boolean = false private set - private var leaveRoomError: Throwable? = null + var leaveRoomLambda: (() -> Result) = { Result.success(Unit) } private val _roomInfoFlow: MutableSharedFlow = MutableSharedFlow(replay = 1) override val roomInfoFlow: Flow = _roomInfoFlow @@ -315,8 +315,9 @@ class FakeMatrixRoom( return Result.success(Unit) } - override suspend fun leave(): Result = - leaveRoomError?.let { Result.failure(it) } ?: Result.success(Unit) + override suspend fun leave(): Result { + return leaveRoomLambda() + } override suspend fun join(): Result { return joinRoomResult @@ -542,10 +543,6 @@ class FakeMatrixRoom( override fun getWidgetDriver(widgetSettings: MatrixWidgetSettings): Result = getWidgetDriverResult - fun givenLeaveRoomError(throwable: Throwable?) { - this.leaveRoomError = throwable - } - fun givenRoomMembersState(state: MatrixRoomMembersState) { membersStateFlow.value = state } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/SimplePagedRoomList.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/SimplePagedRoomList.kt index 5ff9ed08bf..9d81626bcb 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/SimplePagedRoomList.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomlist/SimplePagedRoomList.kt @@ -21,6 +21,7 @@ import io.element.android.libraries.matrix.api.roomlist.RoomList import io.element.android.libraries.matrix.api.roomlist.RoomListFilter import io.element.android.libraries.matrix.api.roomlist.RoomSummary import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.getAndUpdate @@ -32,6 +33,8 @@ data class SimplePagedRoomList( override val pageSize: Int = Int.MAX_VALUE override val loadedPages = MutableStateFlow(1) + override val filteredSummaries: SharedFlow> = summaries + override suspend fun loadMore() { // No-op loadedPages.getAndUpdate { it + 1 } diff --git a/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt b/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt index 4374d77e52..fef7eb2484 100644 --- a/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt +++ b/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt @@ -105,4 +105,9 @@ object TestTags { * Search field. */ val searchTextField = TestTag("search_text_field") + + /** + * Generic call to action + */ + val callToAction = TestTag("call_to_action") }