|
|
|
@ -7,33 +7,23 @@
@@ -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
@@ -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
@@ -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
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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 {
@@ -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(), |
|
|
|
|