From fad17f05e3a3497d62a33085b71229b917cb98aa Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 3 Sep 2024 18:21:42 +0200 Subject: [PATCH] Pinned messages list : fix and add tests --- .../impl/actionlist/ActionListPresenter.kt | 25 ++- .../pinned/list/PinnedMessagesListEvents.kt | 1 - .../list/PinnedMessagesListPresenter.kt | 1 - .../impl/timeline/TimelinePresenter.kt | 1 - .../messages/impl/timeline/TimelineState.kt | 1 - .../impl/timeline/TimelineStateProvider.kt | 2 - .../messages/impl/MessagesPresenterTest.kt | 11 +- .../actionlist/ActionListPresenterTest.kt | 11 +- .../actionlist/FakeActionListPresenter.kt | 34 ++++ .../PinnedMessagesBannerPresenterTest.kt | 27 ++- .../list/FakePinnedMessagesListNavigator.kt | 38 ++++ .../list/PinnedMessagesListPresenterTest.kt | 174 ++++++++++++++++++ 12 files changed, 293 insertions(+), 33 deletions(-) create mode 100644 features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/FakeActionListPresenter.kt create mode 100644 features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/FakePinnedMessagesListNavigator.kt create mode 100644 features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenterTest.kt diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt index 1f94d2a847..cbd6c6c789 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenter.kt @@ -23,9 +23,11 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import com.squareup.anvil.annotations.ContributesBinding import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import io.element.android.features.messages.api.pinned.IsPinnedMessagesFeatureEnabled import io.element.android.features.messages.impl.UserEventPermissions import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.actionlist.model.TimelineItemActionPostProcessor @@ -40,8 +42,7 @@ import io.element.android.features.messages.impl.timeline.model.event.canBeCopie import io.element.android.features.messages.impl.timeline.model.event.canBeForwarded import io.element.android.features.messages.impl.timeline.model.event.canReact import io.element.android.libraries.architecture.Presenter -import io.element.android.libraries.featureflag.api.FeatureFlagService -import io.element.android.libraries.featureflag.api.FeatureFlags +import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.preferences.api.store.AppPreferencesStore @@ -52,16 +53,24 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch -class ActionListPresenter @AssistedInject constructor( +interface ActionListPresenter : Presenter { + interface Factory { + fun create(postProcessor: TimelineItemActionPostProcessor): ActionListPresenter + } +} + +class DefaultActionListPresenter @AssistedInject constructor( @Assisted private val postProcessor: TimelineItemActionPostProcessor, private val appPreferencesStore: AppPreferencesStore, - private val featureFlagsService: FeatureFlagService, + private val isPinnedMessagesFeatureEnabled: IsPinnedMessagesFeatureEnabled, private val room: MatrixRoom, -) : Presenter { +) : ActionListPresenter { + @AssistedFactory - interface Factory { - fun create(postProcessor: TimelineItemActionPostProcessor): ActionListPresenter + @ContributesBinding(RoomScope::class) + interface Factory: ActionListPresenter.Factory { + override fun create(postProcessor: TimelineItemActionPostProcessor): DefaultActionListPresenter } @Composable @@ -73,7 +82,7 @@ class ActionListPresenter @AssistedInject constructor( } val isDeveloperModeEnabled by appPreferencesStore.isDeveloperModeEnabledFlow().collectAsState(initial = false) - val isPinnedEventsEnabled by featureFlagsService.isFeatureEnabledFlow(FeatureFlags.PinnedEvents).collectAsState(initial = false) + val isPinnedEventsEnabled = isPinnedMessagesFeatureEnabled() val pinnedEventIds by remember { room.roomInfoFlow.map { it.pinnedEventIds } }.collectAsState(initial = persistentListOf()) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListEvents.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListEvents.kt index 54149fd260..e785e3c8ae 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListEvents.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListEvents.kt @@ -16,7 +16,6 @@ package io.element.android.features.messages.impl.pinned.list -import io.element.android.features.messages.impl.MessagesEvents import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.timeline.model.TimelineItem diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt index b3fb8b8927..471f8d58ab 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt @@ -83,7 +83,6 @@ class PinnedMessagesListPresenter @AssistedInject constructor( TimelineRoomInfo( isDm = room.isDm, name = room.displayName, - isMainTimeline = false, // We don't need to compute those values userHasPermissionToSendMessage = false, userHasPermissionToSendReaction = false, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt index 5324ef44fa..26954d2a63 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt @@ -222,7 +222,6 @@ class TimelinePresenter @AssistedInject constructor( userHasPermissionToSendMessage = userHasPermissionToSendMessage, userHasPermissionToSendReaction = userHasPermissionToSendReaction, isCallOngoing = roomInfo?.hasRoomCall.orFalse(), - isMainTimeline = true ) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt index 114dc08a98..d2a66a2e1c 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt @@ -75,5 +75,4 @@ data class TimelineRoomInfo( val userHasPermissionToSendMessage: Boolean, val userHasPermissionToSendReaction: Boolean, val isCallOngoing: Boolean, - val isMainTimeline: Boolean, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt index d697eb5554..a1d4524ee2 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt @@ -242,12 +242,10 @@ internal fun aTimelineRoomInfo( name: String = "Room name", isDm: Boolean = false, userHasPermissionToSendMessage: Boolean = true, - isMainTimeline: Boolean = true, ) = TimelineRoomInfo( isDm = isDm, name = name, userHasPermissionToSendMessage = userHasPermissionToSendMessage, userHasPermissionToSendReaction = true, isCallOngoing = false, - isMainTimeline = isMainTimeline, ) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt index c9700a60bd..ce8c254868 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt @@ -24,7 +24,10 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.messages.impl.actionlist.ActionListPresenter import io.element.android.features.messages.impl.actionlist.ActionListState +import io.element.android.features.messages.impl.actionlist.FakeActionListPresenter +import io.element.android.features.messages.impl.actionlist.anActionListState import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction +import io.element.android.features.messages.impl.actionlist.model.TimelineItemActionPostProcessor import io.element.android.features.messages.impl.draft.FakeComposerDraftService import io.element.android.features.messages.impl.fixtures.aMessageEvent import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactory @@ -60,6 +63,7 @@ import io.element.android.features.poll.test.actions.FakeEndPollAction import io.element.android.features.poll.test.actions.FakeSendPollResponseAction import io.element.android.libraries.androidutils.clipboard.FakeClipboardHelper import io.element.android.libraries.architecture.AsyncData +import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.designsystem.components.avatar.AvatarData @@ -1052,11 +1056,6 @@ class MessagesPresenterTest { } } val featureFlagService = FakeFeatureFlagService() - val actionListPresenter = ActionListPresenter( - appPreferencesStore = appPreferencesStore, - featureFlagsService = featureFlagService, - room = matrixRoom, - ) val typingNotificationPresenter = TypingNotificationPresenter( room = matrixRoom, sessionPreferencesStore = sessionPreferencesStore, @@ -1072,7 +1071,7 @@ class MessagesPresenterTest { voiceMessageComposerPresenter = voiceMessageComposerPresenter, timelinePresenterFactory = timelinePresenterFactory, typingNotificationPresenter = typingNotificationPresenter, - actionListPresenter = actionListPresenter, + actionListPresenterFactory = FakeActionListPresenter.Factory, customReactionPresenter = customReactionPresenter, reactionSummaryPresenter = reactionSummaryPresenter, readReceiptBottomSheetPresenter = readReceiptBottomSheetPresenter, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt index e8ea7cbbc2..720bd54125 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/ActionListPresenterTest.kt @@ -22,6 +22,7 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.messages.impl.aUserEventPermissions import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction +import io.element.android.features.messages.impl.actionlist.model.TimelineItemActionPostProcessor import io.element.android.features.messages.impl.fixtures.aMessageEvent import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemCallNotifyContent @@ -974,14 +975,10 @@ private fun createActionListPresenter( room: MatrixRoom = FakeMatrixRoom(), ): ActionListPresenter { val preferencesStore = InMemoryAppPreferencesStore(isDeveloperModeEnabled = isDeveloperModeEnabled) - val featureFlagsService = FakeFeatureFlagService( - initialState = mapOf( - FeatureFlags.PinnedEvents.key to isPinFeatureEnabled, - ) - ) - return ActionListPresenter( + return DefaultActionListPresenter( + postProcessor = TimelineItemActionPostProcessor.Default, appPreferencesStore = preferencesStore, - featureFlagsService = featureFlagsService, + isPinnedMessagesFeatureEnabled = {isPinFeatureEnabled}, room = room ) } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/FakeActionListPresenter.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/FakeActionListPresenter.kt new file mode 100644 index 0000000000..9e34c19567 --- /dev/null +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/actionlist/FakeActionListPresenter.kt @@ -0,0 +1,34 @@ +/* + * 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 + * + * https://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.messages.impl.actionlist + +import androidx.compose.runtime.Composable +import io.element.android.features.messages.impl.actionlist.model.TimelineItemActionPostProcessor + +class FakeActionListPresenter : ActionListPresenter { + + object Factory : ActionListPresenter.Factory { + override fun create(postProcessor: TimelineItemActionPostProcessor): ActionListPresenter { + return FakeActionListPresenter() + } + } + + @Composable + override fun present(): ActionListState { + return anActionListState() + } +} diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerPresenterTest.kt index 1ae4729a40..8b79ecb13b 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerPresenterTest.kt @@ -17,9 +17,12 @@ package io.element.android.features.messages.impl.pinned.banner import com.google.common.truth.Truth.assertThat +import io.element.android.features.messages.impl.pinned.PinnedEventsTimelineProvider import io.element.android.features.networkmonitor.api.NetworkMonitor import io.element.android.features.networkmonitor.test.FakeNetworkMonitor import io.element.android.libraries.eventformatter.test.FakePinnedMessagesBannerFormatter +import io.element.android.libraries.featureflag.api.FeatureFlags +import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.test.AN_EVENT_ID @@ -31,8 +34,12 @@ import io.element.android.libraries.matrix.test.timeline.aMessageContent import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem import io.element.android.tests.testutils.test import io.element.android.tests.testutils.testCoroutineDispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test @@ -65,7 +72,7 @@ class PinnedMessagesBannerPresenterTest { } val presenter = createPinnedMessagesBannerPresenter(room = room) presenter.test { - skipItems(1) + skipItems(2) val loadingState = awaitItem() assertThat(loadingState).isEqualTo(PinnedMessagesBannerState.Loading(1)) assertThat(loadingState.pinnedMessagesCount()).isEqualTo(1) @@ -96,7 +103,7 @@ class PinnedMessagesBannerPresenterTest { } val presenter = createPinnedMessagesBannerPresenter(room = room) presenter.test { - skipItems(2) + skipItems(3) val loadedState = awaitItem() as PinnedMessagesBannerState.Loaded assertThat(loadedState.currentPinnedMessageIndex).isEqualTo(0) assertThat(loadedState.loadedPinnedMessagesCount).isEqualTo(1) @@ -135,7 +142,7 @@ class PinnedMessagesBannerPresenterTest { } val presenter = createPinnedMessagesBannerPresenter(room = room) presenter.test { - skipItems(2) + skipItems(3) awaitItem().also { loadedState -> loadedState as PinnedMessagesBannerState.Loaded assertThat(loadedState.currentPinnedMessageIndex).isEqualTo(1) @@ -170,7 +177,7 @@ class PinnedMessagesBannerPresenterTest { } val presenter = createPinnedMessagesBannerPresenter(room = room) presenter.test { - skipItems(1) + skipItems(2) awaitItem().also { loadingState -> assertThat(loadingState).isEqualTo(PinnedMessagesBannerState.Loading(1)) assertThat(loadingState.pinnedMessagesCount()).isEqualTo(1) @@ -193,11 +200,19 @@ class PinnedMessagesBannerPresenterTest { networkMonitor: NetworkMonitor = FakeNetworkMonitor(), isFeatureEnabled: Boolean = true, ): PinnedMessagesBannerPresenter { + val timelineProvider = PinnedEventsTimelineProvider( + room = room, + networkMonitor = networkMonitor, + featureFlagService = FakeFeatureFlagService( + initialState = mapOf(FeatureFlags.PinnedEvents.key to isFeatureEnabled) + ) + ) + timelineProvider.launchIn(backgroundScope) + return PinnedMessagesBannerPresenter( room = room, itemFactory = itemFactory, - isFeatureEnabled = { isFeatureEnabled }, - networkMonitor = networkMonitor, + pinnedEventsTimelineProvider = timelineProvider, ) } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/FakePinnedMessagesListNavigator.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/FakePinnedMessagesListNavigator.kt new file mode 100644 index 0000000000..5d5f27a97a --- /dev/null +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/FakePinnedMessagesListNavigator.kt @@ -0,0 +1,38 @@ +/* + * 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 + * + * https://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.messages.impl.pinned.list + +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo + +class FakePinnedMessagesListNavigator: PinnedMessagesListNavigator { + + var onViewInTimelineClickLambda: ((EventId) -> Unit)? = null + override fun onViewInTimelineClick(eventId: EventId) { + onViewInTimelineClickLambda?.invoke(eventId) + } + + var onShowEventDebugInfoClickLambda: ((EventId?, TimelineItemDebugInfo) -> Unit)? = null + override fun onShowEventDebugInfoClick(eventId: EventId?, debugInfo: TimelineItemDebugInfo) { + onShowEventDebugInfoClickLambda?.invoke(eventId, debugInfo) + } + + var onForwardEventClickLambda: ((EventId) -> Unit)? = null + override fun onForwardEventClick(eventId: EventId) { + onForwardEventClickLambda?.invoke(eventId) + } +} diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenterTest.kt new file mode 100644 index 0000000000..d3bf7efa24 --- /dev/null +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenterTest.kt @@ -0,0 +1,174 @@ +/* + * 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 + * + * https://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.messages.impl.pinned.list + +import com.google.common.truth.Truth.assertThat +import io.element.android.features.messages.impl.actionlist.FakeActionListPresenter +import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactory +import io.element.android.features.messages.impl.pinned.PinnedEventsTimelineProvider +import io.element.android.features.networkmonitor.api.NetworkMonitor +import io.element.android.features.networkmonitor.test.FakeNetworkMonitor +import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher +import io.element.android.libraries.featureflag.api.FeatureFlags +import io.element.android.libraries.featureflag.test.FakeFeatureFlagService +import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem +import io.element.android.libraries.matrix.test.AN_EVENT_ID +import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.libraries.matrix.test.room.aRoomInfo +import io.element.android.libraries.matrix.test.timeline.FakeTimeline +import io.element.android.libraries.matrix.test.timeline.aMessageContent +import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem +import io.element.android.tests.testutils.test +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class PinnedMessagesListPresenterTest { + + @Test + fun `present - initial state feature disabled`() = runTest { + val room = FakeMatrixRoom( + canRedactOwnResult = { Result.success(true) }, + canRedactOtherResult = { Result.success(true) }, + canUserPinUnpinResult = { Result.success(true) }, + ) + val presenter = createPinnedMessagesListPresenter(room = room, isFeatureEnabled = false) + presenter.test { + val initialState = awaitItem() + assertThat(initialState).isEqualTo(PinnedMessagesListState.Loading) + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `present - initial state feature enabled`() = runTest { + val room = FakeMatrixRoom( + canRedactOwnResult = { Result.success(true) }, + canRedactOtherResult = { Result.success(true) }, + canUserPinUnpinResult = { Result.success(true) }, + ).apply { + givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID))) + } + val presenter = createPinnedMessagesListPresenter(room = room, isFeatureEnabled = true) + presenter.test { + val initialState = awaitItem() + assertThat(initialState).isEqualTo(PinnedMessagesListState.Loading) + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `present - timeline failure state`() = runTest { + val room = FakeMatrixRoom( + pinnedEventsTimelineResult = { Result.failure(RuntimeException()) }, + canRedactOwnResult = { Result.success(true) }, + canRedactOtherResult = { Result.success(true) }, + canUserPinUnpinResult = { Result.success(true) }, + ).apply { + givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID))) + } + val presenter = createPinnedMessagesListPresenter(room = room, isFeatureEnabled = true) + presenter.test { + skipItems(3) + val failureState = awaitItem() + assertThat(failureState).isEqualTo(PinnedMessagesListState.Failed) + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `present - empty state`() = runTest { + val room = FakeMatrixRoom( + pinnedEventsTimelineResult = { Result.success(FakeTimeline()) }, + canRedactOwnResult = { Result.success(true) }, + canRedactOtherResult = { Result.success(true) }, + canUserPinUnpinResult = { Result.success(true) }, + ).apply { + givenRoomInfo(aRoomInfo(pinnedEventIds = listOf())) + } + val presenter = createPinnedMessagesListPresenter(room = room, isFeatureEnabled = true) + presenter.test { + skipItems(3) + val emptyState = awaitItem() + assertThat(emptyState).isEqualTo(PinnedMessagesListState.Empty) + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `present - filled state`() = runTest { + val messageContent = aMessageContent("A message") + val pinnedEventsTimeline = FakeTimeline( + timelineItems = flowOf( + listOf( + MatrixTimelineItem.Event( + uniqueId = "FAKE_UNIQUE_ID", + event = anEventTimelineItem( + eventId = AN_EVENT_ID, + content = messageContent, + ), + ) + ) + ) + ) + val room = FakeMatrixRoom( + pinnedEventsTimelineResult = { Result.success(pinnedEventsTimeline) }, + canRedactOwnResult = { Result.success(true) }, + canRedactOtherResult = { Result.success(true) }, + canUserPinUnpinResult = { Result.success(true) }, + ).apply { + givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID))) + } + val presenter = createPinnedMessagesListPresenter(room = room, isFeatureEnabled = true) + presenter.test { + skipItems(3) + val filledState = awaitItem() as PinnedMessagesListState.Filled + assertThat(filledState.timelineItems).hasSize(1) + assertThat(filledState.loadedPinnedMessagesCount).isEqualTo(1) + assertThat(filledState.userEventPermissions.canRedactOwn).isTrue() + assertThat(filledState.userEventPermissions.canRedactOther).isTrue() + assertThat(filledState.userEventPermissions.canPinUnpin).isTrue() + cancelAndIgnoreRemainingEvents() + } + } + + private fun TestScope.createPinnedMessagesListPresenter( + navigator: PinnedMessagesListNavigator = FakePinnedMessagesListNavigator(), + room: MatrixRoom = FakeMatrixRoom(), + networkMonitor: NetworkMonitor = FakeNetworkMonitor(), + isFeatureEnabled: Boolean = true, + ): PinnedMessagesListPresenter { + val timelineProvider = PinnedEventsTimelineProvider( + room = room, + networkMonitor = networkMonitor, + featureFlagService = FakeFeatureFlagService( + initialState = mapOf(FeatureFlags.PinnedEvents.key to isFeatureEnabled) + ) + ) + timelineProvider.launchIn(backgroundScope) + return PinnedMessagesListPresenter( + navigator = navigator, + room = room, + timelineItemsFactory = aTimelineItemsFactory(), + timelineProvider = timelineProvider, + snackbarDispatcher = SnackbarDispatcher(), + actionListPresenterFactory = FakeActionListPresenter.Factory, + ) + } +}