|
|
|
|
@@ -7,33 +7,23 @@
|
|
|
|
|
|
|
|
|
|
package io.element.android.features.messages.impl
|
|
|
|
|
|
|
|
|
|
import android.net.Uri
|
|
|
|
|
import app.cash.molecule.RecompositionMode
|
|
|
|
|
import app.cash.molecule.moleculeFlow
|
|
|
|
|
import app.cash.turbine.ReceiveTurbine
|
|
|
|
|
import app.cash.turbine.test
|
|
|
|
|
import com.google.common.truth.Truth.assertThat
|
|
|
|
|
import im.vector.app.features.analytics.plan.PinUnpinAction
|
|
|
|
|
import io.element.android.features.messages.impl.actionlist.ActionListEvents
|
|
|
|
|
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.model.TimelineItemAction
|
|
|
|
|
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.aResolveVerifiedUserSendFailureState
|
|
|
|
|
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.aTimelineItemsFactoryCreator
|
|
|
|
|
import io.element.android.features.messages.impl.messagecomposer.DefaultMessageComposerContext
|
|
|
|
|
import io.element.android.features.messages.impl.messagecomposer.FakeRoomAliasSuggestionsDataSource
|
|
|
|
|
import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter
|
|
|
|
|
import io.element.android.features.messages.impl.messagecomposer.TestRichTextEditorStateFactory
|
|
|
|
|
import io.element.android.features.messages.impl.messagecomposer.suggestions.SuggestionsProcessor
|
|
|
|
|
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents
|
|
|
|
|
import io.element.android.features.messages.impl.messagecomposer.MessageComposerState
|
|
|
|
|
import io.element.android.features.messages.impl.messagecomposer.aMessageComposerState
|
|
|
|
|
import io.element.android.features.messages.impl.pinned.banner.aLoadedPinnedMessagesBannerState
|
|
|
|
|
import io.element.android.features.messages.impl.timeline.TimelineController
|
|
|
|
|
import io.element.android.features.messages.impl.timeline.TimelineItemIndexer
|
|
|
|
|
import io.element.android.features.messages.impl.timeline.TimelinePresenter
|
|
|
|
|
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionPresenter
|
|
|
|
|
import io.element.android.features.messages.impl.timeline.components.customreaction.FakeEmojibaseProvider
|
|
|
|
|
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryPresenter
|
|
|
|
|
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetPresenter
|
|
|
|
|
import io.element.android.features.messages.impl.timeline.createTimelinePresenter
|
|
|
|
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
|
|
|
|
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
|
|
|
|
|
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent
|
|
|
|
|
@@ -41,25 +31,19 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
|
|
|
|
|
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent
|
|
|
|
|
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
|
|
|
|
|
import io.element.android.features.messages.impl.timeline.protection.aTimelineProtectionState
|
|
|
|
|
import io.element.android.features.messages.impl.typing.aTypingNotificationState
|
|
|
|
|
import io.element.android.features.messages.impl.utils.FakeTextPillificationHelper
|
|
|
|
|
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerPlayer
|
|
|
|
|
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerPresenter
|
|
|
|
|
import io.element.android.features.messages.impl.voicemessages.timeline.FakeRedactedVoiceMessageManager
|
|
|
|
|
import io.element.android.features.messages.test.FakeMessageComposerContext
|
|
|
|
|
import io.element.android.features.messages.impl.voicemessages.composer.aVoiceMessageComposerState
|
|
|
|
|
import io.element.android.features.messages.test.timeline.FakeHtmlConverterProvider
|
|
|
|
|
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
|
|
|
|
|
import io.element.android.features.poll.api.actions.EndPollAction
|
|
|
|
|
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
|
|
|
|
|
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
|
|
|
|
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.core.EventId
|
|
|
|
|
import io.element.android.libraries.matrix.api.core.TransactionId
|
|
|
|
|
@@ -72,33 +56,24 @@ import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
|
|
|
|
import io.element.android.libraries.matrix.api.room.MessageEventType
|
|
|
|
|
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
|
|
|
|
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
|
|
|
|
|
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
|
|
|
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
|
|
|
|
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
|
|
|
|
import io.element.android.libraries.matrix.test.A_SESSION_ID_2
|
|
|
|
|
import io.element.android.libraries.matrix.test.A_THROWABLE
|
|
|
|
|
import io.element.android.libraries.matrix.test.A_UNIQUE_ID
|
|
|
|
|
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
|
|
|
|
import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder
|
|
|
|
|
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
|
|
|
|
|
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.room.aRoomMember
|
|
|
|
|
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
|
|
|
|
|
import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache
|
|
|
|
|
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
|
|
|
|
|
import io.element.android.libraries.mediapickers.test.FakePickerProvider
|
|
|
|
|
import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer
|
|
|
|
|
import io.element.android.libraries.mediaupload.api.MediaSender
|
|
|
|
|
import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor
|
|
|
|
|
import io.element.android.libraries.mediaviewer.test.FakeLocalMediaFactory
|
|
|
|
|
import io.element.android.libraries.permissions.api.PermissionsPresenter
|
|
|
|
|
import io.element.android.libraries.permissions.test.FakePermissionsPresenter
|
|
|
|
|
import io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory
|
|
|
|
|
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
|
|
|
|
|
import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider
|
|
|
|
|
import io.element.android.libraries.textcomposer.model.MarkdownTextEditorState
|
|
|
|
|
import io.element.android.libraries.textcomposer.model.MessageComposerMode
|
|
|
|
|
import io.element.android.libraries.voicerecorder.test.FakeVoiceRecorder
|
|
|
|
|
import io.element.android.libraries.textcomposer.model.TextEditorState
|
|
|
|
|
import io.element.android.services.analytics.test.FakeAnalyticsService
|
|
|
|
|
import io.element.android.tests.testutils.EventsRecorder
|
|
|
|
|
import io.element.android.tests.testutils.WarmUpRule
|
|
|
|
|
import io.element.android.tests.testutils.consumeItemsUntilPredicate
|
|
|
|
|
import io.element.android.tests.testutils.consumeItemsUntilTimeout
|
|
|
|
|
@@ -107,7 +82,6 @@ import io.element.android.tests.testutils.lambda.lambdaError
|
|
|
|
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
|
|
|
|
import io.element.android.tests.testutils.lambda.value
|
|
|
|
|
import io.element.android.tests.testutils.testCoroutineDispatchers
|
|
|
|
|
import io.mockk.mockk
|
|
|
|
|
import kotlinx.collections.immutable.persistentListOf
|
|
|
|
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
|
|
|
import kotlinx.coroutines.delay
|
|
|
|
|
@@ -123,8 +97,6 @@ class MessagesPresenterTest {
|
|
|
|
|
@get:Rule
|
|
|
|
|
val warmUpRule = WarmUpRule()
|
|
|
|
|
|
|
|
|
|
private val mockMediaUrl: Uri = mockk("localMediaUri")
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
fun `present - initial state`() = runTest {
|
|
|
|
|
val presenter = createMessagesPresenter()
|
|
|
|
|
@@ -212,15 +184,13 @@ class MessagesPresenterTest {
|
|
|
|
|
}.test {
|
|
|
|
|
skipItems(1)
|
|
|
|
|
val initialState = awaitItem()
|
|
|
|
|
initialState.eventSink.invoke(MessagesEvents.ToggleReaction("👍", A_UNIQUE_ID))
|
|
|
|
|
// No crashes when sending a reaction failed
|
|
|
|
|
timeline.apply { toggleReactionLambda = toggleReactionFailure }
|
|
|
|
|
initialState.eventSink.invoke(MessagesEvents.ToggleReaction("👍", A_UNIQUE_ID))
|
|
|
|
|
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
|
|
|
|
|
|
|
|
|
|
initialState.eventSink(MessagesEvents.ToggleReaction("👍", A_UNIQUE_ID))
|
|
|
|
|
assert(toggleReactionSuccess)
|
|
|
|
|
.isCalledOnce()
|
|
|
|
|
.with(value("👍"), value(A_UNIQUE_ID))
|
|
|
|
|
// No crashes when sending a reaction failed
|
|
|
|
|
timeline.apply { toggleReactionLambda = toggleReactionFailure }
|
|
|
|
|
initialState.eventSink(MessagesEvents.ToggleReaction("👍", A_UNIQUE_ID))
|
|
|
|
|
assert(toggleReactionFailure)
|
|
|
|
|
.isCalledOnce()
|
|
|
|
|
.with(value("👍"), value(A_UNIQUE_ID))
|
|
|
|
|
@@ -248,15 +218,16 @@ class MessagesPresenterTest {
|
|
|
|
|
moleculeFlow(RecompositionMode.Immediate) {
|
|
|
|
|
presenter.present()
|
|
|
|
|
}.test {
|
|
|
|
|
val initialState = awaitFirstItem()
|
|
|
|
|
initialState.eventSink.invoke(MessagesEvents.ToggleReaction("👍", A_UNIQUE_ID))
|
|
|
|
|
initialState.eventSink.invoke(MessagesEvents.ToggleReaction("👍", A_UNIQUE_ID))
|
|
|
|
|
val initialState = awaitItem()
|
|
|
|
|
initialState.eventSink(MessagesEvents.ToggleReaction("👍", A_UNIQUE_ID))
|
|
|
|
|
initialState.eventSink(MessagesEvents.ToggleReaction("👍", A_UNIQUE_ID))
|
|
|
|
|
assert(toggleReactionSuccess)
|
|
|
|
|
.isCalledExactly(2)
|
|
|
|
|
.withSequence(
|
|
|
|
|
listOf(value("👍"), value(A_UNIQUE_ID)),
|
|
|
|
|
listOf(value("👍"), value(A_UNIQUE_ID)),
|
|
|
|
|
)
|
|
|
|
|
skipItems(1)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -267,9 +238,8 @@ class MessagesPresenterTest {
|
|
|
|
|
moleculeFlow(RecompositionMode.Immediate) {
|
|
|
|
|
presenter.present()
|
|
|
|
|
}.test {
|
|
|
|
|
skipItems(1)
|
|
|
|
|
val initialState = awaitItem()
|
|
|
|
|
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Forward, aMessageEvent()))
|
|
|
|
|
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Forward, aMessageEvent()))
|
|
|
|
|
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
|
|
|
|
|
assertThat(navigator.onForwardEventClickedCount).isEqualTo(1)
|
|
|
|
|
}
|
|
|
|
|
@@ -283,9 +253,9 @@ class MessagesPresenterTest {
|
|
|
|
|
moleculeFlow(RecompositionMode.Immediate) {
|
|
|
|
|
presenter.present()
|
|
|
|
|
}.test {
|
|
|
|
|
val initialState = awaitFirstItem()
|
|
|
|
|
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Copy, event))
|
|
|
|
|
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
|
|
|
|
|
val initialState = awaitItem()
|
|
|
|
|
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Copy, event))
|
|
|
|
|
skipItems(2)
|
|
|
|
|
assertThat(clipboardHelper.clipboardContents).isEqualTo((event.content as TimelineItemTextContent).body)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -310,24 +280,33 @@ class MessagesPresenterTest {
|
|
|
|
|
moleculeFlow(RecompositionMode.Immediate) {
|
|
|
|
|
presenter.present()
|
|
|
|
|
}.test {
|
|
|
|
|
val initialState = awaitFirstItem()
|
|
|
|
|
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.CopyLink, event))
|
|
|
|
|
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
|
|
|
|
|
val initialState = awaitItem()
|
|
|
|
|
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.CopyLink, event))
|
|
|
|
|
skipItems(2)
|
|
|
|
|
assertThat(clipboardHelper.clipboardContents).isEqualTo("a link")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
fun `present - handle action reply`() = runTest {
|
|
|
|
|
val presenter = createMessagesPresenter()
|
|
|
|
|
val composerRecorder = EventsRecorder<MessageComposerEvents>()
|
|
|
|
|
val presenter = createMessagesPresenter(
|
|
|
|
|
messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) },
|
|
|
|
|
)
|
|
|
|
|
moleculeFlow(RecompositionMode.Immediate) {
|
|
|
|
|
presenter.present()
|
|
|
|
|
}.test {
|
|
|
|
|
val initialState = awaitFirstItem()
|
|
|
|
|
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Reply, aMessageEvent()))
|
|
|
|
|
val finalState = awaitItem()
|
|
|
|
|
assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Reply::class.java)
|
|
|
|
|
assertThat(finalState.actionListState.target).isEqualTo(ActionListState.Target.None)
|
|
|
|
|
val initialState = awaitItem()
|
|
|
|
|
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, aMessageEvent()))
|
|
|
|
|
awaitItem()
|
|
|
|
|
composerRecorder.assertSingle(
|
|
|
|
|
MessageComposerEvents.SetMode(
|
|
|
|
|
composerMode = MessageComposerMode.Reply(
|
|
|
|
|
replyToDetails = InReplyToDetails.Loading(AN_EVENT_ID),
|
|
|
|
|
hideImage = false,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -337,21 +316,22 @@ class MessagesPresenterTest {
|
|
|
|
|
moleculeFlow(RecompositionMode.Immediate) {
|
|
|
|
|
presenter.present()
|
|
|
|
|
}.test {
|
|
|
|
|
val initialState = awaitFirstItem()
|
|
|
|
|
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Reply, aMessageEvent(eventId = null)))
|
|
|
|
|
assertThat(initialState.actionListState.target).isEqualTo(ActionListState.Target.None)
|
|
|
|
|
// Otherwise we would have some extra items here
|
|
|
|
|
ensureAllEventsConsumed()
|
|
|
|
|
val initialState = awaitItem()
|
|
|
|
|
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, aMessageEvent(eventId = null)))
|
|
|
|
|
skipItems(1)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
fun `present - handle action reply to an image media message`() = runTest {
|
|
|
|
|
val presenter = createMessagesPresenter()
|
|
|
|
|
val composerRecorder = EventsRecorder<MessageComposerEvents>()
|
|
|
|
|
val presenter = createMessagesPresenter(
|
|
|
|
|
messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) },
|
|
|
|
|
)
|
|
|
|
|
moleculeFlow(RecompositionMode.Immediate) {
|
|
|
|
|
presenter.present()
|
|
|
|
|
}.test {
|
|
|
|
|
val initialState = awaitFirstItem()
|
|
|
|
|
val initialState = awaitItem()
|
|
|
|
|
val mediaMessage = aMessageEvent(
|
|
|
|
|
content = TimelineItemImageContent(
|
|
|
|
|
body = "image.jpg",
|
|
|
|
|
@@ -368,22 +348,29 @@ class MessagesPresenterTest {
|
|
|
|
|
formattedFileSize = "4MB"
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Reply, mediaMessage))
|
|
|
|
|
val finalState = awaitItem()
|
|
|
|
|
assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Reply::class.java)
|
|
|
|
|
val replyMode = finalState.composerState.mode as MessageComposerMode.Reply
|
|
|
|
|
assertThat(replyMode.replyToDetails).isInstanceOf(InReplyToDetails.Loading::class.java)
|
|
|
|
|
assertThat(finalState.actionListState.target).isEqualTo(ActionListState.Target.None)
|
|
|
|
|
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, mediaMessage))
|
|
|
|
|
awaitItem()
|
|
|
|
|
composerRecorder.assertSingle(
|
|
|
|
|
MessageComposerEvents.SetMode(
|
|
|
|
|
composerMode = MessageComposerMode.Reply(
|
|
|
|
|
replyToDetails = InReplyToDetails.Loading(AN_EVENT_ID),
|
|
|
|
|
hideImage = false,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
fun `present - handle action reply to a video media message`() = runTest {
|
|
|
|
|
val presenter = createMessagesPresenter()
|
|
|
|
|
val composerRecorder = EventsRecorder<MessageComposerEvents>()
|
|
|
|
|
val presenter = createMessagesPresenter(
|
|
|
|
|
messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) },
|
|
|
|
|
)
|
|
|
|
|
moleculeFlow(RecompositionMode.Immediate) {
|
|
|
|
|
presenter.present()
|
|
|
|
|
}.test {
|
|
|
|
|
val initialState = awaitFirstItem()
|
|
|
|
|
val initialState = awaitItem()
|
|
|
|
|
val mediaMessage = aMessageEvent(
|
|
|
|
|
content = TimelineItemVideoContent(
|
|
|
|
|
body = "video.mp4",
|
|
|
|
|
@@ -401,22 +388,29 @@ class MessagesPresenterTest {
|
|
|
|
|
formattedFileSize = "50MB"
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Reply, mediaMessage))
|
|
|
|
|
val finalState = awaitItem()
|
|
|
|
|
assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Reply::class.java)
|
|
|
|
|
val replyMode = finalState.composerState.mode as MessageComposerMode.Reply
|
|
|
|
|
assertThat(replyMode.replyToDetails).isInstanceOf(InReplyToDetails.Loading::class.java)
|
|
|
|
|
assertThat(finalState.actionListState.target).isEqualTo(ActionListState.Target.None)
|
|
|
|
|
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, mediaMessage))
|
|
|
|
|
awaitItem()
|
|
|
|
|
composerRecorder.assertSingle(
|
|
|
|
|
MessageComposerEvents.SetMode(
|
|
|
|
|
composerMode = MessageComposerMode.Reply(
|
|
|
|
|
replyToDetails = InReplyToDetails.Loading(AN_EVENT_ID),
|
|
|
|
|
hideImage = false,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
fun `present - handle action reply to a file media message`() = runTest {
|
|
|
|
|
val presenter = createMessagesPresenter()
|
|
|
|
|
val composerRecorder = EventsRecorder<MessageComposerEvents>()
|
|
|
|
|
val presenter = createMessagesPresenter(
|
|
|
|
|
messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) },
|
|
|
|
|
)
|
|
|
|
|
moleculeFlow(RecompositionMode.Immediate) {
|
|
|
|
|
presenter.present()
|
|
|
|
|
}.test {
|
|
|
|
|
val initialState = awaitFirstItem()
|
|
|
|
|
val initialState = awaitItem()
|
|
|
|
|
val mediaMessage = aMessageEvent(
|
|
|
|
|
content = TimelineItemFileContent(
|
|
|
|
|
body = "file.pdf",
|
|
|
|
|
@@ -427,26 +421,40 @@ class MessagesPresenterTest {
|
|
|
|
|
fileExtension = "pdf",
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Reply, mediaMessage))
|
|
|
|
|
val finalState = awaitItem()
|
|
|
|
|
assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Reply::class.java)
|
|
|
|
|
val replyMode = finalState.composerState.mode as MessageComposerMode.Reply
|
|
|
|
|
assertThat(replyMode.replyToDetails).isInstanceOf(InReplyToDetails.Loading::class.java)
|
|
|
|
|
assertThat(finalState.actionListState.target).isEqualTo(ActionListState.Target.None)
|
|
|
|
|
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, mediaMessage))
|
|
|
|
|
awaitItem()
|
|
|
|
|
composerRecorder.assertSingle(
|
|
|
|
|
MessageComposerEvents.SetMode(
|
|
|
|
|
composerMode = MessageComposerMode.Reply(
|
|
|
|
|
replyToDetails = InReplyToDetails.Loading(AN_EVENT_ID),
|
|
|
|
|
hideImage = false,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
fun `present - handle action edit`() = runTest {
|
|
|
|
|
val presenter = createMessagesPresenter()
|
|
|
|
|
val composerRecorder = EventsRecorder<MessageComposerEvents>()
|
|
|
|
|
val presenter = createMessagesPresenter(
|
|
|
|
|
messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) },
|
|
|
|
|
)
|
|
|
|
|
moleculeFlow(RecompositionMode.Immediate) {
|
|
|
|
|
presenter.present()
|
|
|
|
|
}.test {
|
|
|
|
|
val initialState = awaitFirstItem()
|
|
|
|
|
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Edit, aMessageEvent()))
|
|
|
|
|
val finalState = awaitItem()
|
|
|
|
|
assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Edit::class.java)
|
|
|
|
|
assertThat(finalState.actionListState.target).isEqualTo(ActionListState.Target.None)
|
|
|
|
|
val initialState = awaitItem()
|
|
|
|
|
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Edit, aMessageEvent()))
|
|
|
|
|
awaitItem()
|
|
|
|
|
composerRecorder.assertSingle(
|
|
|
|
|
MessageComposerEvents.SetMode(
|
|
|
|
|
composerMode = MessageComposerMode.Edit(
|
|
|
|
|
eventId = AN_EVENT_ID,
|
|
|
|
|
transactionId = null,
|
|
|
|
|
content = (aMessageEvent().content as TimelineItemTextContent).body
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -457,8 +465,9 @@ class MessagesPresenterTest {
|
|
|
|
|
moleculeFlow(RecompositionMode.Immediate) {
|
|
|
|
|
presenter.present()
|
|
|
|
|
}.test {
|
|
|
|
|
val initialState = awaitFirstItem()
|
|
|
|
|
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Edit, aMessageEvent(content = aTimelineItemPollContent())))
|
|
|
|
|
val initialState = awaitItem()
|
|
|
|
|
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Edit, aMessageEvent(content = aTimelineItemPollContent())))
|
|
|
|
|
awaitItem()
|
|
|
|
|
assertThat(navigator.onEditPollClickedCount).isEqualTo(1)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -470,9 +479,9 @@ class MessagesPresenterTest {
|
|
|
|
|
moleculeFlow(RecompositionMode.Immediate) {
|
|
|
|
|
presenter.present()
|
|
|
|
|
}.test {
|
|
|
|
|
val initialState = awaitFirstItem()
|
|
|
|
|
val initialState = awaitItem()
|
|
|
|
|
endPollAction.verifyExecutionCount(0)
|
|
|
|
|
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.EndPoll, aMessageEvent(content = aTimelineItemPollContent())))
|
|
|
|
|
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.EndPoll, aMessageEvent(content = aTimelineItemPollContent())))
|
|
|
|
|
delay(1)
|
|
|
|
|
endPollAction.verifyExecutionCount(1)
|
|
|
|
|
cancelAndIgnoreRemainingEvents()
|
|
|
|
|
@@ -496,16 +505,17 @@ class MessagesPresenterTest {
|
|
|
|
|
|
|
|
|
|
val redactEventLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String? -> Result.success(Unit) }
|
|
|
|
|
liveTimeline.redactEventLambda = redactEventLambda
|
|
|
|
|
|
|
|
|
|
val presenter = createMessagesPresenter(matrixRoom = matrixRoom, coroutineDispatchers = coroutineDispatchers)
|
|
|
|
|
val presenter = createMessagesPresenter(
|
|
|
|
|
matrixRoom = matrixRoom,
|
|
|
|
|
coroutineDispatchers = coroutineDispatchers,
|
|
|
|
|
)
|
|
|
|
|
moleculeFlow(RecompositionMode.Immediate) {
|
|
|
|
|
presenter.present()
|
|
|
|
|
}.test {
|
|
|
|
|
skipItems(1)
|
|
|
|
|
val initialState = awaitItem()
|
|
|
|
|
val messageEvent = aMessageEvent()
|
|
|
|
|
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Redact, messageEvent))
|
|
|
|
|
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
|
|
|
|
|
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Redact, messageEvent))
|
|
|
|
|
awaitItem()
|
|
|
|
|
assert(redactEventLambda)
|
|
|
|
|
.isCalledOnce()
|
|
|
|
|
.with(value(messageEvent.eventId), value(messageEvent.transactionId), value(null))
|
|
|
|
|
@@ -519,9 +529,8 @@ class MessagesPresenterTest {
|
|
|
|
|
moleculeFlow(RecompositionMode.Immediate) {
|
|
|
|
|
presenter.present()
|
|
|
|
|
}.test {
|
|
|
|
|
skipItems(1)
|
|
|
|
|
val initialState = awaitItem()
|
|
|
|
|
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.ReportContent, aMessageEvent()))
|
|
|
|
|
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.ReportContent, aMessageEvent()))
|
|
|
|
|
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
|
|
|
|
|
assertThat(navigator.onReportContentClickedCount).isEqualTo(1)
|
|
|
|
|
}
|
|
|
|
|
@@ -533,9 +542,8 @@ class MessagesPresenterTest {
|
|
|
|
|
moleculeFlow(RecompositionMode.Immediate) {
|
|
|
|
|
presenter.present()
|
|
|
|
|
}.test {
|
|
|
|
|
skipItems(1)
|
|
|
|
|
val initialState = awaitItem()
|
|
|
|
|
initialState.eventSink.invoke(MessagesEvents.Dismiss)
|
|
|
|
|
initialState.eventSink(MessagesEvents.Dismiss)
|
|
|
|
|
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -547,9 +555,8 @@ class MessagesPresenterTest {
|
|
|
|
|
moleculeFlow(RecompositionMode.Immediate) {
|
|
|
|
|
presenter.present()
|
|
|
|
|
}.test {
|
|
|
|
|
skipItems(1)
|
|
|
|
|
val initialState = awaitItem()
|
|
|
|
|
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.ViewSource, aMessageEvent()))
|
|
|
|
|
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.ViewSource, aMessageEvent()))
|
|
|
|
|
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
|
|
|
|
|
assertThat(navigator.onShowEventDebugInfoClickedCount).isEqualTo(1)
|
|
|
|
|
}
|
|
|
|
|
@@ -572,21 +579,18 @@ class MessagesPresenterTest {
|
|
|
|
|
moleculeFlow(RecompositionMode.Immediate) {
|
|
|
|
|
presenter.present()
|
|
|
|
|
}.test {
|
|
|
|
|
skipItems(1)
|
|
|
|
|
val initialState = awaitItem()
|
|
|
|
|
// Initially the composer doesn't have focus, so we don't show the alert
|
|
|
|
|
assertThat(initialState.showReinvitePrompt).isFalse()
|
|
|
|
|
// When the input field is focused we show the alert
|
|
|
|
|
initialState.composerState.textEditorState.requestFocus()
|
|
|
|
|
val focusedState = consumeItemsUntilPredicate(timeout = 250.milliseconds) { state ->
|
|
|
|
|
state.showReinvitePrompt
|
|
|
|
|
}.last()
|
|
|
|
|
(initialState.composerState.textEditorState as TextEditorState.Markdown).state.hasFocus = true
|
|
|
|
|
skipItems(1)
|
|
|
|
|
val focusedState = awaitItem()
|
|
|
|
|
assertThat(focusedState.showReinvitePrompt).isTrue()
|
|
|
|
|
// If it's dismissed then we stop showing the alert
|
|
|
|
|
initialState.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Cancel))
|
|
|
|
|
val dismissedState = consumeItemsUntilPredicate(timeout = 250.milliseconds) { state ->
|
|
|
|
|
!state.showReinvitePrompt
|
|
|
|
|
}.last()
|
|
|
|
|
skipItems(1)
|
|
|
|
|
val dismissedState = awaitItem()
|
|
|
|
|
assertThat(dismissedState.showReinvitePrompt).isFalse()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -608,9 +612,9 @@ class MessagesPresenterTest {
|
|
|
|
|
moleculeFlow(RecompositionMode.Immediate) {
|
|
|
|
|
presenter.present()
|
|
|
|
|
}.test {
|
|
|
|
|
val initialState = awaitFirstItem()
|
|
|
|
|
val initialState = awaitItem()
|
|
|
|
|
assertThat(initialState.showReinvitePrompt).isFalse()
|
|
|
|
|
initialState.composerState.textEditorState.requestFocus()
|
|
|
|
|
(initialState.composerState.textEditorState as TextEditorState.Markdown).state.hasFocus = true
|
|
|
|
|
val focusedState = awaitItem()
|
|
|
|
|
assertThat(focusedState.showReinvitePrompt).isFalse()
|
|
|
|
|
}
|
|
|
|
|
@@ -633,9 +637,9 @@ class MessagesPresenterTest {
|
|
|
|
|
moleculeFlow(RecompositionMode.Immediate) {
|
|
|
|
|
presenter.present()
|
|
|
|
|
}.test {
|
|
|
|
|
val initialState = awaitFirstItem()
|
|
|
|
|
val initialState = awaitItem()
|
|
|
|
|
assertThat(initialState.showReinvitePrompt).isFalse()
|
|
|
|
|
initialState.composerState.textEditorState.requestFocus()
|
|
|
|
|
(initialState.composerState.textEditorState as TextEditorState.Markdown).state.hasFocus = true
|
|
|
|
|
val focusedState = awaitItem()
|
|
|
|
|
assertThat(focusedState.showReinvitePrompt).isFalse()
|
|
|
|
|
}
|
|
|
|
|
@@ -799,7 +803,8 @@ class MessagesPresenterTest {
|
|
|
|
|
moleculeFlow(RecompositionMode.Immediate) {
|
|
|
|
|
presenter.present()
|
|
|
|
|
}.test {
|
|
|
|
|
val state = awaitFirstItem()
|
|
|
|
|
skipItems(1)
|
|
|
|
|
val state = awaitItem()
|
|
|
|
|
assertThat(state.userEventPermissions.canSendMessage).isTrue()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -826,9 +831,7 @@ class MessagesPresenterTest {
|
|
|
|
|
}.test {
|
|
|
|
|
// Default value
|
|
|
|
|
assertThat(awaitItem().userEventPermissions.canSendMessage).isTrue()
|
|
|
|
|
skipItems(1)
|
|
|
|
|
assertThat(awaitItem().userEventPermissions.canSendMessage).isFalse()
|
|
|
|
|
cancelAndIgnoreRemainingEvents()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -876,21 +879,27 @@ class MessagesPresenterTest {
|
|
|
|
|
|
|
|
|
|
@Test
|
|
|
|
|
fun `present - handle action reply to a poll`() = runTest {
|
|
|
|
|
val presenter = createMessagesPresenter()
|
|
|
|
|
val composerRecorder = EventsRecorder<MessageComposerEvents>()
|
|
|
|
|
val presenter = createMessagesPresenter(
|
|
|
|
|
messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) },
|
|
|
|
|
)
|
|
|
|
|
moleculeFlow(RecompositionMode.Immediate) {
|
|
|
|
|
presenter.present()
|
|
|
|
|
}.test {
|
|
|
|
|
val initialState = awaitFirstItem()
|
|
|
|
|
val initialState = awaitItem()
|
|
|
|
|
val poll = aMessageEvent(
|
|
|
|
|
content = aTimelineItemPollContent()
|
|
|
|
|
)
|
|
|
|
|
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Reply, poll))
|
|
|
|
|
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, poll))
|
|
|
|
|
val finalState = awaitItem()
|
|
|
|
|
assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Reply::class.java)
|
|
|
|
|
val replyMode = finalState.composerState.mode as MessageComposerMode.Reply
|
|
|
|
|
|
|
|
|
|
assertThat(replyMode.replyToDetails).isInstanceOf(InReplyToDetails.Loading::class.java)
|
|
|
|
|
assertThat(finalState.actionListState.target).isEqualTo(ActionListState.Target.None)
|
|
|
|
|
composerRecorder.assertSingle(
|
|
|
|
|
MessageComposerEvents.SetMode(
|
|
|
|
|
composerMode = MessageComposerMode.Reply(
|
|
|
|
|
replyToDetails = InReplyToDetails.Loading(AN_EVENT_ID),
|
|
|
|
|
hideImage = false,
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -916,15 +925,16 @@ class MessagesPresenterTest {
|
|
|
|
|
val messageEvent = aMessageEvent(
|
|
|
|
|
content = aTimelineItemTextContent()
|
|
|
|
|
)
|
|
|
|
|
val initialState = awaitFirstItem()
|
|
|
|
|
val initialState = awaitItem()
|
|
|
|
|
|
|
|
|
|
timeline.pinEventLambda = successPinEventLambda
|
|
|
|
|
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Pin, messageEvent))
|
|
|
|
|
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Pin, messageEvent))
|
|
|
|
|
assert(successPinEventLambda).isCalledOnce().with(value(messageEvent.eventId))
|
|
|
|
|
|
|
|
|
|
timeline.pinEventLambda = failurePinEventLambda
|
|
|
|
|
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Pin, messageEvent))
|
|
|
|
|
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Pin, messageEvent))
|
|
|
|
|
assert(failurePinEventLambda).isCalledOnce().with(value(messageEvent.eventId))
|
|
|
|
|
skipItems(1)
|
|
|
|
|
assertThat(awaitItem().snackbarMessage).isNotNull()
|
|
|
|
|
assertThat(analyticsService.capturedEvents).containsExactly(
|
|
|
|
|
PinUnpinAction(kind = PinUnpinAction.Kind.Pin, from = PinUnpinAction.From.Timeline),
|
|
|
|
|
@@ -955,15 +965,16 @@ class MessagesPresenterTest {
|
|
|
|
|
val messageEvent = aMessageEvent(
|
|
|
|
|
content = aTimelineItemTextContent()
|
|
|
|
|
)
|
|
|
|
|
val initialState = awaitFirstItem()
|
|
|
|
|
val initialState = awaitItem()
|
|
|
|
|
|
|
|
|
|
timeline.unpinEventLambda = successUnpinEventLambda
|
|
|
|
|
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Unpin, messageEvent))
|
|
|
|
|
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Unpin, messageEvent))
|
|
|
|
|
assert(successUnpinEventLambda).isCalledOnce().with(value(messageEvent.eventId))
|
|
|
|
|
|
|
|
|
|
timeline.unpinEventLambda = failureUnpinEventLambda
|
|
|
|
|
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Unpin, messageEvent))
|
|
|
|
|
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Unpin, messageEvent))
|
|
|
|
|
assert(failureUnpinEventLambda).isCalledOnce().with(value(messageEvent.eventId))
|
|
|
|
|
skipItems(1)
|
|
|
|
|
assertThat(awaitItem().snackbarMessage).isNotNull()
|
|
|
|
|
assertThat(analyticsService.capturedEvents).containsExactly(
|
|
|
|
|
PinUnpinAction(kind = PinUnpinAction.Kind.Unpin, from = PinUnpinAction.From.Timeline),
|
|
|
|
|
@@ -972,12 +983,6 @@ class MessagesPresenterTest {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private suspend fun <T> ReceiveTurbine<T>.awaitFirstItem(): T {
|
|
|
|
|
// Skip 2 item if Mentions feature is enabled, else 1
|
|
|
|
|
skipItems(if (FeatureFlags.Mentions.defaultValue(aBuildMeta())) 2 else 1)
|
|
|
|
|
return awaitItem()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun TestScope.createMessagesPresenter(
|
|
|
|
|
coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
|
|
|
|
|
matrixRoom: MatrixRoom = FakeMatrixRoom(
|
|
|
|
|
@@ -993,83 +998,34 @@ class MessagesPresenterTest {
|
|
|
|
|
navigator: FakeMessagesNavigator = FakeMessagesNavigator(),
|
|
|
|
|
clipboardHelper: FakeClipboardHelper = FakeClipboardHelper(),
|
|
|
|
|
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
|
|
|
|
|
permissionsPresenter: PermissionsPresenter = FakePermissionsPresenter(),
|
|
|
|
|
endPollAction: EndPollAction = FakeEndPollAction(),
|
|
|
|
|
permalinkParser: PermalinkParser = FakePermalinkParser(),
|
|
|
|
|
messageComposerPresenter: Presenter<MessageComposerState> = Presenter {
|
|
|
|
|
aMessageComposerState(
|
|
|
|
|
// Use TextEditorState.Markdown, so that we can request focus manually.
|
|
|
|
|
textEditorState = TextEditorState.Markdown(MarkdownTextEditorState(initialText = "", initialFocus = false))
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
actionListEventSink: (ActionListEvents) -> Unit = {},
|
|
|
|
|
): MessagesPresenter {
|
|
|
|
|
val mediaSender = MediaSender(FakeMediaPreProcessor(), matrixRoom)
|
|
|
|
|
val permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionsPresenter)
|
|
|
|
|
val sessionPreferencesStore = InMemorySessionPreferencesStore()
|
|
|
|
|
val mentionSpanProvider = MentionSpanProvider(FakePermalinkParser())
|
|
|
|
|
val messageComposerPresenter = MessageComposerPresenter(
|
|
|
|
|
appCoroutineScope = this,
|
|
|
|
|
room = matrixRoom,
|
|
|
|
|
mediaPickerProvider = FakePickerProvider(),
|
|
|
|
|
featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.NotificationSettings.key to true)),
|
|
|
|
|
sessionPreferencesStore = InMemorySessionPreferencesStore(),
|
|
|
|
|
localMediaFactory = FakeLocalMediaFactory(mockMediaUrl),
|
|
|
|
|
mediaSender = mediaSender,
|
|
|
|
|
snackbarDispatcher = SnackbarDispatcher(),
|
|
|
|
|
analyticsService = analyticsService,
|
|
|
|
|
messageComposerContext = DefaultMessageComposerContext(),
|
|
|
|
|
richTextEditorStateFactory = TestRichTextEditorStateFactory(),
|
|
|
|
|
roomAliasSuggestionsDataSource = FakeRoomAliasSuggestionsDataSource(),
|
|
|
|
|
permissionsPresenterFactory = permissionsPresenterFactory,
|
|
|
|
|
permalinkParser = FakePermalinkParser(),
|
|
|
|
|
permalinkBuilder = FakePermalinkBuilder(),
|
|
|
|
|
timelineController = TimelineController(matrixRoom),
|
|
|
|
|
draftService = FakeComposerDraftService(),
|
|
|
|
|
mentionSpanProvider = mentionSpanProvider,
|
|
|
|
|
pillificationHelper = FakeTextPillificationHelper(),
|
|
|
|
|
roomMemberProfilesCache = RoomMemberProfilesCache(),
|
|
|
|
|
suggestionsProcessor = SuggestionsProcessor(),
|
|
|
|
|
).apply {
|
|
|
|
|
showTextFormatting = true
|
|
|
|
|
isTesting = true
|
|
|
|
|
}
|
|
|
|
|
val voiceMessageComposerPresenter = VoiceMessageComposerPresenter(
|
|
|
|
|
this,
|
|
|
|
|
FakeVoiceRecorder(),
|
|
|
|
|
analyticsService,
|
|
|
|
|
mediaSender,
|
|
|
|
|
player = VoiceMessageComposerPlayer(FakeMediaPlayer(), this),
|
|
|
|
|
messageComposerContext = FakeMessageComposerContext(),
|
|
|
|
|
permissionsPresenterFactory,
|
|
|
|
|
)
|
|
|
|
|
val timelinePresenter = TimelinePresenter(
|
|
|
|
|
timelineItemsFactoryCreator = aTimelineItemsFactoryCreator(),
|
|
|
|
|
room = matrixRoom,
|
|
|
|
|
dispatchers = coroutineDispatchers,
|
|
|
|
|
appScope = this,
|
|
|
|
|
navigator = navigator,
|
|
|
|
|
redactedVoiceMessageManager = FakeRedactedVoiceMessageManager(),
|
|
|
|
|
endPollAction = endPollAction,
|
|
|
|
|
sendPollResponseAction = FakeSendPollResponseAction(),
|
|
|
|
|
sessionPreferencesStore = sessionPreferencesStore,
|
|
|
|
|
timelineItemIndexer = TimelineItemIndexer(),
|
|
|
|
|
timelineController = TimelineController(matrixRoom),
|
|
|
|
|
resolveVerifiedUserSendFailurePresenter = { aResolveVerifiedUserSendFailureState() },
|
|
|
|
|
typingNotificationPresenter = { aTypingNotificationState() },
|
|
|
|
|
)
|
|
|
|
|
val timelinePresenterFactory = object : TimelinePresenter.Factory {
|
|
|
|
|
override fun create(navigator: MessagesNavigator): TimelinePresenter {
|
|
|
|
|
return timelinePresenter
|
|
|
|
|
return createTimelinePresenter(
|
|
|
|
|
endPollAction = endPollAction,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
val featureFlagService = FakeFeatureFlagService()
|
|
|
|
|
val readReceiptBottomSheetPresenter = ReadReceiptBottomSheetPresenter()
|
|
|
|
|
val customReactionPresenter = CustomReactionPresenter(emojibaseProvider = FakeEmojibaseProvider())
|
|
|
|
|
val reactionSummaryPresenter = ReactionSummaryPresenter(room = matrixRoom)
|
|
|
|
|
return MessagesPresenter(
|
|
|
|
|
room = matrixRoom,
|
|
|
|
|
composerPresenter = messageComposerPresenter,
|
|
|
|
|
voiceMessageComposerPresenter = voiceMessageComposerPresenter,
|
|
|
|
|
voiceMessageComposerPresenter = { aVoiceMessageComposerState() },
|
|
|
|
|
timelinePresenterFactory = timelinePresenterFactory,
|
|
|
|
|
timelineProtectionPresenter = { aTimelineProtectionState() },
|
|
|
|
|
actionListPresenterFactory = FakeActionListPresenter.Factory,
|
|
|
|
|
customReactionPresenter = customReactionPresenter,
|
|
|
|
|
reactionSummaryPresenter = reactionSummaryPresenter,
|
|
|
|
|
readReceiptBottomSheetPresenter = readReceiptBottomSheetPresenter,
|
|
|
|
|
actionListPresenterFactory = FakeActionListPresenter.Factory(actionListEventSink),
|
|
|
|
|
customReactionPresenter = { aCustomReactionState() },
|
|
|
|
|
reactionSummaryPresenter = { aReactionSummaryState() },
|
|
|
|
|
readReceiptBottomSheetPresenter = { aReadReceiptBottomSheetState() },
|
|
|
|
|
pinnedMessagesBannerPresenter = { aLoadedPinnedMessagesBannerState() },
|
|
|
|
|
networkMonitor = FakeNetworkMonitor(),
|
|
|
|
|
snackbarDispatcher = SnackbarDispatcher(),
|
|
|
|
|
|