From 0390ede2ad9d16c392df0ae271d908c4aa1cd78d Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 6 Aug 2024 16:13:30 +0200 Subject: [PATCH] Pinned events: add tests on PinnedMessagesBannerPresenter --- .../banner/PinnedMessagesBannerPresenter.kt | 16 +- .../PinnedMessagesBannerPresenterTest.kt | 157 ++++++++++++++++-- 2 files changed, 154 insertions(+), 19 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerPresenter.kt index 59bf1c5a2f..6604114fc5 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/banner/PinnedMessagesBannerPresenter.kt @@ -52,15 +52,15 @@ class PinnedMessagesBannerPresenter @Inject constructor( @Composable override fun present(): PinnedMessagesBannerState { val isFeatureEnabled by featureFlagService.isFeatureEnabledFlow(FeatureFlags.PinnedEvents).collectAsState(initial = false) - var timelineFailed by rememberSaveable { mutableStateOf(false) } - var pinnedItems by remember { - mutableStateOf>(persistentListOf()) - } + var hasTimelineFailedToLoad by rememberSaveable { mutableStateOf(false) } + var currentPinnedMessageIndex by rememberSaveable { mutableIntStateOf(0) } val knownPinnedMessagesCount by remember { room.roomInfoFlow.map { roomInfo -> roomInfo.pinnedEventIds.size } }.collectAsState(initial = null) - var currentPinnedMessageIndex by rememberSaveable { mutableIntStateOf(0) } + var pinnedItems by remember { + mutableStateOf>(persistentListOf()) + } PinnedMessagesBannerItemsEffect( isFeatureEnabled = isFeatureEnabled, @@ -72,7 +72,7 @@ class PinnedMessagesBannerPresenter @Inject constructor( pinnedItems = newItems }, onTimelineFail = { hasTimelineFailed -> - timelineFailed = hasTimelineFailed + hasTimelineFailedToLoad = hasTimelineFailed } ) @@ -90,7 +90,7 @@ class PinnedMessagesBannerPresenter @Inject constructor( return pinnedMessagesBannerState( isFeatureEnabled = isFeatureEnabled, - hasTimelineFailed = timelineFailed, + hasTimelineFailed = hasTimelineFailedToLoad, realPinnedMessagesCount = knownPinnedMessagesCount, pinnedItems = pinnedItems, currentPinnedMessageIndex = currentPinnedMessageIndex, @@ -117,7 +117,7 @@ class PinnedMessagesBannerPresenter @Inject constructor( PinnedMessagesBannerState.Loaded( currentPinnedMessage = currentPinnedMessage, currentPinnedMessageIndex = currentPinnedMessageIndex, - knownPinnedMessagesCount = realPinnedMessagesCount, + knownPinnedMessagesCount = pinnedItems.size, eventSink = eventSink ) } 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 baab17577d..33e1b74aba 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,13 +17,23 @@ package io.element.android.features.messages.impl.pinned.banner import com.google.common.truth.Truth.assertThat +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 +import io.element.android.libraries.matrix.test.AN_EVENT_ID_2 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 io.element.android.tests.testutils.testCoroutineDispatchers +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Test @@ -31,23 +41,146 @@ import org.junit.Test class PinnedMessagesBannerPresenterTest { @Test fun `present - initial state`() = runTest { - val presenter = createPinnedMessagesBannerPresenter() + val presenter = createPinnedMessagesBannerPresenter(isFeatureEnabled = true) presenter.test { val initialState = awaitItem() - assertThat(initialState.pinnedMessagesCount).isEqualTo(0) - assertThat(initialState.currentPinnedMessageIndex).isEqualTo(0) - assertThat(initialState.currentPinnedMessage).isNull() + assertThat(initialState).isEqualTo(PinnedMessagesBannerState.Hidden) + cancelAndIgnoreRemainingEvents() } } @Test - fun `present - move to next pinned message when there is no pinned events`() = runTest { - val presenter = createPinnedMessagesBannerPresenter() + fun `present - feature disabled`() = runTest { + val presenter = createPinnedMessagesBannerPresenter(isFeatureEnabled = false) presenter.test { val initialState = awaitItem() - initialState.eventSink(PinnedMessagesBannerEvents.MoveToNextPinned) - // Nothing is emitted - ensureAllEventsConsumed() + assertThat(initialState).isEqualTo(PinnedMessagesBannerState.Hidden) + } + } + + @Test + fun `present - loading state`() = runTest { + val room = FakeMatrixRoom( + pinnedEventsTimelineResult = { Result.success(FakeTimeline()) } + ).apply { + givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID))) + } + val presenter = createPinnedMessagesBannerPresenter(room = room) + presenter.test { + skipItems(1) + val loadingState = awaitItem() + assertThat(loadingState).isEqualTo(PinnedMessagesBannerState.Loading(1)) + assertThat(loadingState.pinnedMessagesCount()).isEqualTo(1) + assertThat(loadingState.currentPinnedMessageIndex()).isEqualTo(0) + } + } + + @Test + fun `present - loaded 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) } + ).apply { + givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID, AN_EVENT_ID_2))) + } + val presenter = createPinnedMessagesBannerPresenter(room = room) + presenter.test { + skipItems(2) + val loadedState = awaitItem() as PinnedMessagesBannerState.Loaded + assertThat(loadedState.currentPinnedMessageIndex).isEqualTo(0) + assertThat(loadedState.knownPinnedMessagesCount).isEqualTo(1) + assertThat(loadedState.currentPinnedMessage.formatted.text).isEqualTo(messageContent.toString()) + } + } + + @Test + fun `present - loaded state - multiple pinned messages`() = runTest { + val messageContent1 = aMessageContent("A message") + val messageContent2 = aMessageContent("Another message") + val pinnedEventsTimeline = FakeTimeline( + timelineItems = flowOf( + listOf( + MatrixTimelineItem.Event( + uniqueId = "FAKE_UNIQUE_ID", + event = anEventTimelineItem( + eventId = AN_EVENT_ID, + content = messageContent1, + ), + ), + MatrixTimelineItem.Event( + uniqueId = "FAKE_UNIQUE_ID_2", + event = anEventTimelineItem( + eventId = AN_EVENT_ID_2, + content = messageContent2, + ), + ) + ) + ) + ) + val room = FakeMatrixRoom( + pinnedEventsTimelineResult = { Result.success(pinnedEventsTimeline) } + ).apply { + givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID, AN_EVENT_ID_2))) + } + val presenter = createPinnedMessagesBannerPresenter(room = room) + presenter.test { + skipItems(2) + awaitItem().also { loadedState -> + loadedState as PinnedMessagesBannerState.Loaded + assertThat(loadedState.currentPinnedMessageIndex).isEqualTo(0) + assertThat(loadedState.knownPinnedMessagesCount).isEqualTo(2) + assertThat(loadedState.currentPinnedMessage.formatted.text).isEqualTo(messageContent1.toString()) + loadedState.eventSink(PinnedMessagesBannerEvents.MoveToNextPinned) + } + + awaitItem().also { loadedState -> + loadedState as PinnedMessagesBannerState.Loaded + assertThat(loadedState.currentPinnedMessageIndex).isEqualTo(1) + assertThat(loadedState.knownPinnedMessagesCount).isEqualTo(2) + assertThat(loadedState.currentPinnedMessage.formatted.text).isEqualTo(messageContent2.toString()) + loadedState.eventSink(PinnedMessagesBannerEvents.MoveToNextPinned) + } + + awaitItem().also { loadedState -> + loadedState as PinnedMessagesBannerState.Loaded + assertThat(loadedState.currentPinnedMessageIndex).isEqualTo(0) + assertThat(loadedState.knownPinnedMessagesCount).isEqualTo(2) + assertThat(loadedState.currentPinnedMessage.formatted.text).isEqualTo(messageContent1.toString()) + } + } + } + + @Test + fun `present - timeline failed`() = runTest { + val room = FakeMatrixRoom( + pinnedEventsTimelineResult = { Result.failure(Exception()) } + ).apply { + givenRoomInfo(aRoomInfo(pinnedEventIds = listOf(AN_EVENT_ID))) + } + val presenter = createPinnedMessagesBannerPresenter(room = room) + presenter.test { + skipItems(1) + awaitItem().also { loadingState -> + assertThat(loadingState).isEqualTo(PinnedMessagesBannerState.Loading(1)) + assertThat(loadingState.pinnedMessagesCount()).isEqualTo(1) + assertThat(loadingState.currentPinnedMessageIndex()).isEqualTo(0) + } + awaitItem().also { failedState -> + assertThat(failedState).isEqualTo(PinnedMessagesBannerState.Hidden) + } } } @@ -56,9 +189,10 @@ class PinnedMessagesBannerPresenterTest { itemFactory: PinnedMessagesBannerItemFactory = PinnedMessagesBannerItemFactory( coroutineDispatchers = testCoroutineDispatchers(), formatter = FakePinnedMessagesBannerFormatter( - formatLambda = { event -> "Content ${event.content}" } + formatLambda = { event -> "${event.content}" } ) ), + networkMonitor: NetworkMonitor = FakeNetworkMonitor(), isFeatureEnabled: Boolean = true, ): PinnedMessagesBannerPresenter { val featureFlagService = FakeFeatureFlagService( @@ -69,7 +203,8 @@ class PinnedMessagesBannerPresenterTest { return PinnedMessagesBannerPresenter( room = room, itemFactory = itemFactory, - featureFlagService = featureFlagService + featureFlagService = featureFlagService, + networkMonitor = networkMonitor, ) } }