Merge remote-tracking branch 'origin/develop' into feature/fga/pinned_message_banner_ui
This commit is contained in:
commit
56e1957d3d
193 changed files with 4546 additions and 1696 deletions
|
|
@ -60,6 +60,7 @@ import io.element.android.libraries.matrix.api.room.Mention
|
|||
import io.element.android.libraries.matrix.api.room.draft.ComposerDraft
|
||||
import io.element.android.libraries.matrix.api.room.draft.ComposerDraftType
|
||||
import io.element.android.libraries.matrix.api.room.isDm
|
||||
import io.element.android.libraries.matrix.api.timeline.TimelineException
|
||||
import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache
|
||||
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
|
||||
import io.element.android.libraries.matrix.ui.messages.reply.map
|
||||
|
|
@ -436,7 +437,14 @@ class MessageComposerPresenter @Inject constructor(
|
|||
val eventId = capturedMode.eventId
|
||||
val transactionId = capturedMode.transactionId
|
||||
timelineController.invokeOnCurrentTimeline {
|
||||
// First try to edit the message in the current timeline
|
||||
editMessage(eventId, transactionId, message.markdown, message.html, message.mentions)
|
||||
.onFailure { cause ->
|
||||
if (cause is TimelineException.EventNotFound && eventId != null) {
|
||||
// if the event is not found in the timeline, try to edit the message directly
|
||||
room.editMessage(eventId, message.markdown, message.html, message.mentions)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -264,27 +264,27 @@ class TimelineItemContentMessageFactory @Inject constructor(
|
|||
}
|
||||
|
||||
private fun CharSequence.withFixedURLSpans(): CharSequence {
|
||||
if (this !is Spannable) return this
|
||||
val spannable = this.toSpannable()
|
||||
// Get all URL spans, as they will be removed by LinkifyCompat.addLinks
|
||||
val oldURLSpans = getSpans<URLSpan>(0, length).associateWith {
|
||||
val start = getSpanStart(it)
|
||||
val end = getSpanEnd(it)
|
||||
val oldURLSpans = spannable.getSpans<URLSpan>(0, length).associateWith {
|
||||
val start = spannable.getSpanStart(it)
|
||||
val end = spannable.getSpanEnd(it)
|
||||
Pair(start, end)
|
||||
}
|
||||
// Find and set as URLSpans any links present in the text
|
||||
LinkifyCompat.addLinks(this, Linkify.WEB_URLS or Linkify.PHONE_NUMBERS or Linkify.EMAIL_ADDRESSES)
|
||||
LinkifyCompat.addLinks(spannable, Linkify.WEB_URLS or Linkify.PHONE_NUMBERS or Linkify.EMAIL_ADDRESSES)
|
||||
// Restore old spans, remove new ones if there is a conflict
|
||||
for ((urlSpan, location) in oldURLSpans) {
|
||||
val (start, end) = location
|
||||
val addedSpans = getSpans<URLSpan>(start, end).orEmpty()
|
||||
val addedSpans = spannable.getSpans<URLSpan>(start, end).orEmpty()
|
||||
if (addedSpans.isNotEmpty()) {
|
||||
for (span in addedSpans) {
|
||||
removeSpan(span)
|
||||
spannable.removeSpan(span)
|
||||
}
|
||||
}
|
||||
setSpan(urlSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
spannable.setSpan(urlSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
return this
|
||||
return spannable
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,8 +21,10 @@
|
|||
<string name="screen_room_attachment_source_poll">"გამოკითხვა"</string>
|
||||
<string name="screen_room_attachment_text_formatting">"ტექსტის ფორმატირება"</string>
|
||||
<string name="screen_room_encrypted_history_banner">"შეტყობინებების ისტორია ამჟამად მიუწვდომელია."</string>
|
||||
<string name="screen_room_encrypted_history_banner_unverified">"შეტყობინებების ისტორია ამ ოთახში მიუწვდომელია. დაადასტურეთ ეს მოწყობილობა თქვენი შეტყობინებების ისტორიის სანახავად."</string>
|
||||
<string name="screen_room_invite_again_alert_message">"გსურთ მათი კვლავ მოწვევა?"</string>
|
||||
<string name="screen_room_invite_again_alert_title">"თქვენ მარტო ხართ ამ ჩატში"</string>
|
||||
<string name="screen_room_mentions_at_room_subtitle">"მთელი ოთახისათვის შეტყობინება"</string>
|
||||
<string name="screen_room_mentions_at_room_title">"ყველა"</string>
|
||||
<string name="screen_room_retry_send_menu_send_again_action">"Ხელახლა გაგზავნა"</string>
|
||||
<string name="screen_room_retry_send_menu_title">"თქვენი შეტყობინების გაგზავნა ვერ მოხერხდა"</string>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="emoji_picker_category_activity">"Aktywności"</string>
|
||||
<string name="emoji_picker_category_flags">"Flagi"</string>
|
||||
<string name="emoji_picker_category_foods">"Jedzenie i napoje"</string>
|
||||
<string name="emoji_picker_category_nature">"Zwierzęta i natura"</string>
|
||||
<string name="emoji_picker_category_objects">"Obiekty"</string>
|
||||
<string name="emoji_picker_category_people">"Buźki i osoby"</string>
|
||||
<string name="emoji_picker_category_places">"Podróż i miejsca"</string>
|
||||
<string name="emoji_picker_category_symbols">"Symbole"</string>
|
||||
<string name="screen_report_content_block_user">"Zablokuj użytkownika"</string>
|
||||
<string name="screen_report_content_block_user_hint">"Sprawdź, czy chcesz ukryć wszystkie bieżące i przyszłe wiadomości od tego użytkownika."</string>
|
||||
<string name="screen_report_content_explanation">"Ta wiadomość zostanie zgłoszona do administratora Twojego serwera domowego. Nie będzie mógł on przeczytać żadnych zaszyfrowanych wiadomości."</string>
|
||||
<string name="screen_report_content_hint">"Powód zgłoszenia treści"</string>
|
||||
<string name="screen_room_attachment_source_camera">"Kamera"</string>
|
||||
<string name="screen_room_attachment_source_camera_photo">"Zrób zdjęcie"</string>
|
||||
<string name="screen_room_attachment_source_camera_video">"Nagraj film"</string>
|
||||
<string name="screen_room_attachment_source_files">"Załącznik"</string>
|
||||
<string name="screen_room_attachment_source_gallery">"Zdjęcia i filmy"</string>
|
||||
<string name="screen_room_attachment_source_location">"Lokalizacja"</string>
|
||||
<string name="screen_room_attachment_source_poll">"Ankieta"</string>
|
||||
<string name="screen_room_attachment_text_formatting">"Formatowanie tekstu"</string>
|
||||
<string name="screen_room_encrypted_history_banner">"Historia wiadomości jest obecnie niedostępna."</string>
|
||||
<string name="screen_room_encrypted_history_banner_unverified">"Historia wiadomości jest niedostępna w tym pokoju. Zweryfikuj to urządzenie, aby zobaczyć historię wiadomości."</string>
|
||||
<string name="screen_room_invite_again_alert_message">"Czy chcesz zaprosić ich z powrotem?"</string>
|
||||
<string name="screen_room_invite_again_alert_title">"Jesteś sam na tym czacie"</string>
|
||||
<string name="screen_room_mentions_at_room_subtitle">"Powiadom cały pokój"</string>
|
||||
<string name="screen_room_mentions_at_room_title">"Wszyscy"</string>
|
||||
<string name="screen_room_retry_send_menu_send_again_action">"Wyślij ponownie"</string>
|
||||
<string name="screen_room_retry_send_menu_title">"Nie udało się wysłać wiadomości"</string>
|
||||
<string name="screen_room_timeline_add_reaction">"Dodaj emoji"</string>
|
||||
<string name="screen_room_timeline_beginning_of_room">"To jest początek %1$s"</string>
|
||||
<string name="screen_room_timeline_beginning_of_room_no_name">"To jest początek tej konwersacji"</string>
|
||||
<string name="screen_room_timeline_less_reactions">"Pokaż mniej"</string>
|
||||
<string name="screen_room_timeline_message_copied">"Skopiowano wiadomość"</string>
|
||||
<string name="screen_room_timeline_no_permission_to_post">"Nie masz uprawnień, aby pisać w tym pokoju"</string>
|
||||
<string name="screen_room_timeline_reactions_show_less">"Pokaż mniej"</string>
|
||||
<string name="screen_room_timeline_reactions_show_more">"Pokaż więcej"</string>
|
||||
<string name="screen_room_timeline_read_marker_title">"Nowe"</string>
|
||||
<plurals name="screen_room_timeline_state_changes">
|
||||
<item quantity="one">"%1$d zmiana pokoju"</item>
|
||||
<item quantity="few">"%1$d zmian pokoju"</item>
|
||||
<item quantity="many">"%1$d zmiany pokoju"</item>
|
||||
</plurals>
|
||||
<plurals name="screen_room_typing_notification">
|
||||
<item quantity="one">"%1$s piszę"</item>
|
||||
<item quantity="few">"%1$s piszą"</item>
|
||||
<item quantity="many">"%1$s piszą"</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="emoji_picker_category_activity">"Atividades"</string>
|
||||
<string name="emoji_picker_category_flags">"Bandeiras"</string>
|
||||
<string name="emoji_picker_category_foods">"Comida & Bebida"</string>
|
||||
<string name="emoji_picker_category_nature">"Animais & Natureza"</string>
|
||||
<string name="emoji_picker_category_objects">"Objetos"</string>
|
||||
<string name="emoji_picker_category_people">"Sorrisos & Pessoas"</string>
|
||||
<string name="emoji_picker_category_places">"Viagens & Lugares"</string>
|
||||
<string name="emoji_picker_category_symbols">"Símbolos"</string>
|
||||
<string name="screen_report_content_block_user">"Bloquear usuário"</string>
|
||||
<string name="screen_report_content_block_user_hint">"Marque se você deseja ocultar todas as mensagens atuais e futuras desse usuário"</string>
|
||||
<string name="screen_report_content_explanation">"Essa mensagem será reportada ao administrador do seu homeserver. Eles não conseguirão ler nenhuma mensagem criptografada."</string>
|
||||
<string name="screen_report_content_hint">"Motivo para denunciar este conteúdo"</string>
|
||||
<string name="screen_room_attachment_source_camera">"Câmera"</string>
|
||||
<string name="screen_room_attachment_source_camera_photo">"Tirar foto"</string>
|
||||
<string name="screen_room_attachment_source_camera_video">"Gravar vídeo"</string>
|
||||
<string name="screen_room_attachment_source_files">"Anexo"</string>
|
||||
<string name="screen_room_attachment_source_gallery">"Biblioteca de fotos e vídeos"</string>
|
||||
<string name="screen_room_attachment_source_location">"Localização"</string>
|
||||
<string name="screen_room_attachment_source_poll">"Enquete"</string>
|
||||
<string name="screen_room_attachment_text_formatting">"Formatação de texto"</string>
|
||||
<string name="screen_room_encrypted_history_banner">"O histórico de mensagens não está disponível no momento."</string>
|
||||
<string name="screen_room_invite_again_alert_message">"Gostaria de convidá-los de volta?"</string>
|
||||
<string name="screen_room_invite_again_alert_title">"Você está sozinho neste chat"</string>
|
||||
<string name="screen_room_mentions_at_room_title">"Todos"</string>
|
||||
<string name="screen_room_retry_send_menu_send_again_action">"Enviar novamente"</string>
|
||||
<string name="screen_room_retry_send_menu_title">"Sua mensagem não foi enviada"</string>
|
||||
<string name="screen_room_timeline_add_reaction">"Adicionar emoji"</string>
|
||||
<string name="screen_room_timeline_beginning_of_room">"Este é o início do %1$s."</string>
|
||||
<string name="screen_room_timeline_beginning_of_room_no_name">"Este é o início desta conversa."</string>
|
||||
<string name="screen_room_timeline_less_reactions">"Mostrar menos"</string>
|
||||
<string name="screen_room_timeline_message_copied">"Mensagem copiada"</string>
|
||||
<string name="screen_room_timeline_no_permission_to_post">"Você não tem permissão para postar nesta sala"</string>
|
||||
<string name="screen_room_timeline_reactions_show_less">"Mostrar menos"</string>
|
||||
<string name="screen_room_timeline_reactions_show_more">"Mostrar mais"</string>
|
||||
<string name="screen_room_timeline_read_marker_title">"Novo"</string>
|
||||
<plurals name="screen_room_timeline_state_changes">
|
||||
<item quantity="one">"%1$d mudança de sala"</item>
|
||||
<item quantity="other">"%1$d mudanças de salas"</item>
|
||||
</plurals>
|
||||
</resources>
|
||||
|
|
@ -67,6 +67,7 @@ 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
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.media.MediaSource
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
|
|
@ -105,6 +106,7 @@ import io.element.android.tests.testutils.WarmUpRule
|
|||
import io.element.android.tests.testutils.consumeItemsUntilPredicate
|
||||
import io.element.android.tests.testutils.consumeItemsUntilTimeout
|
||||
import io.element.android.tests.testutils.lambda.assert
|
||||
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
|
||||
|
|
@ -138,7 +140,7 @@ class MessagesPresenterTest {
|
|||
assertThat(initialState.roomAvatar)
|
||||
.isEqualTo(AsyncData.Success(AvatarData(id = A_ROOM_ID.value, name = "", url = AN_AVATAR_URL, size = AvatarSize.TimelineRoom)))
|
||||
assertThat(initialState.userHasPermissionToSendMessage).isTrue()
|
||||
assertThat(initialState.userHasPermissionToRedactOwn).isFalse()
|
||||
assertThat(initialState.userHasPermissionToRedactOwn).isTrue()
|
||||
assertThat(initialState.hasNetworkConnection).isTrue()
|
||||
assertThat(initialState.snackbarMessage).isNull()
|
||||
assertThat(initialState.inviteProgress).isEqualTo(AsyncData.Uninitialized)
|
||||
|
|
@ -149,7 +151,13 @@ class MessagesPresenterTest {
|
|||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Test
|
||||
fun `present - check that the room's unread flag is removed`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
val room = FakeMatrixRoom(
|
||||
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||
canRedactOwnResult = { Result.success(true) },
|
||||
canRedactOtherResult = { Result.success(true) },
|
||||
canUserJoinCallResult = { Result.success(true) },
|
||||
typingNoticeResult = { Result.success(Unit) },
|
||||
)
|
||||
assertThat(room.markAsReadCalls).isEmpty()
|
||||
val presenter = createMessagesPresenter(matrixRoom = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
|
|
@ -163,8 +171,13 @@ class MessagesPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - call is disabled if user cannot join it even if there is an ongoing call`() = runTest {
|
||||
val room = FakeMatrixRoom().apply {
|
||||
givenCanUserJoinCall(Result.success(false))
|
||||
val room = FakeMatrixRoom(
|
||||
canUserJoinCallResult = { Result.success(false) },
|
||||
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||
canRedactOwnResult = { Result.success(true) },
|
||||
canRedactOtherResult = { Result.success(true) },
|
||||
typingNoticeResult = { Result.success(Unit) },
|
||||
).apply {
|
||||
givenRoomInfo(aRoomInfo(hasRoomCall = true))
|
||||
}
|
||||
val presenter = createMessagesPresenter(matrixRoom = room)
|
||||
|
|
@ -185,7 +198,14 @@ class MessagesPresenterTest {
|
|||
val timeline = FakeTimeline().apply {
|
||||
this.toggleReactionLambda = toggleReactionSuccess
|
||||
}
|
||||
val room = FakeMatrixRoom(liveTimeline = timeline)
|
||||
val room = FakeMatrixRoom(
|
||||
liveTimeline = timeline,
|
||||
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||
canRedactOwnResult = { Result.success(true) },
|
||||
canRedactOtherResult = { Result.success(true) },
|
||||
canUserJoinCallResult = { Result.success(true) },
|
||||
typingNoticeResult = { Result.success(Unit) },
|
||||
)
|
||||
val presenter = createMessagesPresenter(matrixRoom = room, coroutineDispatchers = coroutineDispatchers)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -215,7 +235,14 @@ class MessagesPresenterTest {
|
|||
val timeline = FakeTimeline().apply {
|
||||
this.toggleReactionLambda = toggleReactionSuccess
|
||||
}
|
||||
val room = FakeMatrixRoom(liveTimeline = timeline)
|
||||
val room = FakeMatrixRoom(
|
||||
liveTimeline = timeline,
|
||||
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||
canRedactOwnResult = { Result.success(true) },
|
||||
canRedactOtherResult = { Result.success(true) },
|
||||
canUserJoinCallResult = { Result.success(true) },
|
||||
typingNoticeResult = { Result.success(Unit) },
|
||||
)
|
||||
val presenter = createMessagesPresenter(matrixRoom = room, coroutineDispatchers = coroutineDispatchers)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -268,6 +295,11 @@ class MessagesPresenterTest {
|
|||
val event = aMessageEvent()
|
||||
val matrixRoom = FakeMatrixRoom(
|
||||
eventPermalinkResult = { Result.success("a link") },
|
||||
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||
canRedactOwnResult = { Result.success(true) },
|
||||
canRedactOtherResult = { Result.success(true) },
|
||||
canUserJoinCallResult = { Result.success(true) },
|
||||
typingNoticeResult = { Result.success(Unit) },
|
||||
)
|
||||
val presenter = createMessagesPresenter(
|
||||
clipboardHelper = clipboardHelper,
|
||||
|
|
@ -450,7 +482,14 @@ class MessagesPresenterTest {
|
|||
val coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true)
|
||||
|
||||
val liveTimeline = FakeTimeline()
|
||||
val matrixRoom = FakeMatrixRoom(liveTimeline = liveTimeline)
|
||||
val matrixRoom = FakeMatrixRoom(
|
||||
liveTimeline = liveTimeline,
|
||||
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||
canRedactOwnResult = { Result.success(true) },
|
||||
canRedactOtherResult = { Result.success(true) },
|
||||
canUserJoinCallResult = { Result.success(true) },
|
||||
typingNoticeResult = { Result.success(Unit) },
|
||||
)
|
||||
|
||||
val redactEventLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String? -> Result.success(true) }
|
||||
liveTimeline.redactEventLambda = redactEventLambda
|
||||
|
|
@ -515,7 +554,16 @@ class MessagesPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - shows prompt to reinvite users in DM`() = runTest {
|
||||
val room = FakeMatrixRoom(sessionId = A_SESSION_ID, isDirect = true, activeMemberCount = 1L)
|
||||
val room = FakeMatrixRoom(
|
||||
sessionId = A_SESSION_ID,
|
||||
isDirect = true,
|
||||
activeMemberCount = 1L,
|
||||
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||
canRedactOwnResult = { Result.success(true) },
|
||||
canRedactOtherResult = { Result.success(true) },
|
||||
canUserJoinCallResult = { Result.success(true) },
|
||||
typingNoticeResult = { Result.success(Unit) },
|
||||
)
|
||||
val presenter = createMessagesPresenter(matrixRoom = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -541,7 +589,16 @@ class MessagesPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - doesn't show reinvite prompt in non-direct room`() = runTest {
|
||||
val room = FakeMatrixRoom(sessionId = A_SESSION_ID, isDirect = false, activeMemberCount = 1L)
|
||||
val room = FakeMatrixRoom(
|
||||
sessionId = A_SESSION_ID,
|
||||
isDirect = false,
|
||||
activeMemberCount = 1L,
|
||||
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||
canRedactOwnResult = { Result.success(true) },
|
||||
canRedactOtherResult = { Result.success(true) },
|
||||
canUserJoinCallResult = { Result.success(true) },
|
||||
typingNoticeResult = { Result.success(Unit) },
|
||||
)
|
||||
val presenter = createMessagesPresenter(matrixRoom = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -556,7 +613,16 @@ class MessagesPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - doesn't show reinvite prompt if other party is present`() = runTest {
|
||||
val room = FakeMatrixRoom(sessionId = A_SESSION_ID, isDirect = true, activeMemberCount = 2L)
|
||||
val room = FakeMatrixRoom(
|
||||
sessionId = A_SESSION_ID,
|
||||
isDirect = true,
|
||||
activeMemberCount = 2L,
|
||||
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||
canRedactOwnResult = { Result.success(true) },
|
||||
canRedactOtherResult = { Result.success(true) },
|
||||
canUserJoinCallResult = { Result.success(true) },
|
||||
typingNoticeResult = { Result.success(Unit) },
|
||||
)
|
||||
val presenter = createMessagesPresenter(matrixRoom = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -571,7 +637,16 @@ class MessagesPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - handle reinviting other user when memberlist is ready`() = runTest {
|
||||
val room = FakeMatrixRoom(sessionId = A_SESSION_ID)
|
||||
val inviteUserResult = lambdaRecorder { _: UserId -> Result.success(Unit) }
|
||||
val room = FakeMatrixRoom(
|
||||
sessionId = A_SESSION_ID,
|
||||
inviteUserResult = inviteUserResult,
|
||||
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||
canRedactOwnResult = { Result.success(true) },
|
||||
canRedactOtherResult = { Result.success(true) },
|
||||
canUserJoinCallResult = { Result.success(true) },
|
||||
typingNoticeResult = { Result.success(Unit) },
|
||||
)
|
||||
room.givenRoomMembersState(
|
||||
MatrixRoomMembersState.Ready(
|
||||
persistentListOf(
|
||||
|
|
@ -591,13 +666,22 @@ class MessagesPresenterTest {
|
|||
assertThat(loadingState.inviteProgress.isLoading()).isTrue()
|
||||
val newState = awaitItem()
|
||||
assertThat(newState.inviteProgress.isSuccess()).isTrue()
|
||||
assertThat(room.invitedUserId).isEqualTo(A_SESSION_ID_2)
|
||||
inviteUserResult.assertions().isCalledOnce().with(value(A_SESSION_ID_2))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - handle reinviting other user when memberlist is error`() = runTest {
|
||||
val room = FakeMatrixRoom(sessionId = A_SESSION_ID)
|
||||
val inviteUserResult = lambdaRecorder { _: UserId -> Result.success(Unit) }
|
||||
val room = FakeMatrixRoom(
|
||||
sessionId = A_SESSION_ID,
|
||||
inviteUserResult = inviteUserResult,
|
||||
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||
canRedactOwnResult = { Result.success(true) },
|
||||
canRedactOtherResult = { Result.success(true) },
|
||||
canUserJoinCallResult = { Result.success(true) },
|
||||
typingNoticeResult = { Result.success(Unit) },
|
||||
)
|
||||
room.givenRoomMembersState(
|
||||
MatrixRoomMembersState.Error(
|
||||
failure = Throwable(),
|
||||
|
|
@ -620,13 +704,20 @@ class MessagesPresenterTest {
|
|||
assertThat(loadingState.inviteProgress.isLoading()).isTrue()
|
||||
val newState = awaitItem()
|
||||
assertThat(newState.inviteProgress.isSuccess()).isTrue()
|
||||
assertThat(room.invitedUserId).isEqualTo(A_SESSION_ID_2)
|
||||
inviteUserResult.assertions().isCalledOnce().with(value(A_SESSION_ID_2))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - handle reinviting other user when memberlist is not ready`() = runTest {
|
||||
val room = FakeMatrixRoom(sessionId = A_SESSION_ID)
|
||||
val room = FakeMatrixRoom(
|
||||
sessionId = A_SESSION_ID,
|
||||
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||
canRedactOwnResult = { Result.success(true) },
|
||||
canRedactOtherResult = { Result.success(true) },
|
||||
canUserJoinCallResult = { Result.success(true) },
|
||||
typingNoticeResult = { Result.success(Unit) },
|
||||
)
|
||||
room.givenRoomMembersState(MatrixRoomMembersState.Unknown)
|
||||
val presenter = createMessagesPresenter(matrixRoom = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
|
|
@ -644,7 +735,15 @@ class MessagesPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - handle reinviting other user when inviting fails`() = runTest {
|
||||
val room = FakeMatrixRoom(sessionId = A_SESSION_ID)
|
||||
val room = FakeMatrixRoom(
|
||||
sessionId = A_SESSION_ID,
|
||||
inviteUserResult = { Result.failure(Throwable("Oops!")) },
|
||||
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||
canRedactOwnResult = { Result.success(true) },
|
||||
canRedactOtherResult = { Result.success(true) },
|
||||
canUserJoinCallResult = { Result.success(true) },
|
||||
typingNoticeResult = { Result.success(Unit) },
|
||||
)
|
||||
room.givenRoomMembersState(
|
||||
MatrixRoomMembersState.Ready(
|
||||
persistentListOf(
|
||||
|
|
@ -653,7 +752,6 @@ class MessagesPresenterTest {
|
|||
)
|
||||
)
|
||||
)
|
||||
room.givenInviteUserResult(Result.failure(Throwable("Oops!")))
|
||||
val presenter = createMessagesPresenter(matrixRoom = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -673,8 +771,19 @@ class MessagesPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - permission to post`() = runTest {
|
||||
val matrixRoom = FakeMatrixRoom()
|
||||
matrixRoom.givenCanSendEventResult(MessageEventType.ROOM_MESSAGE, Result.success(true))
|
||||
val matrixRoom = FakeMatrixRoom(
|
||||
canUserSendMessageResult = { _, messageEventType ->
|
||||
when (messageEventType) {
|
||||
MessageEventType.ROOM_MESSAGE -> Result.success(true)
|
||||
MessageEventType.REACTION -> Result.success(true)
|
||||
else -> lambdaError()
|
||||
}
|
||||
},
|
||||
canRedactOwnResult = { Result.success(true) },
|
||||
canRedactOtherResult = { Result.success(true) },
|
||||
canUserJoinCallResult = { Result.success(true) },
|
||||
typingNoticeResult = { Result.success(Unit) },
|
||||
)
|
||||
val presenter = createMessagesPresenter(matrixRoom = matrixRoom)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -686,8 +795,19 @@ class MessagesPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - no permission to post`() = runTest {
|
||||
val matrixRoom = FakeMatrixRoom()
|
||||
matrixRoom.givenCanSendEventResult(MessageEventType.ROOM_MESSAGE, Result.success(false))
|
||||
val matrixRoom = FakeMatrixRoom(
|
||||
canUserSendMessageResult = { _, messageEventType ->
|
||||
when (messageEventType) {
|
||||
MessageEventType.ROOM_MESSAGE -> Result.success(false)
|
||||
MessageEventType.REACTION -> Result.success(false)
|
||||
else -> lambdaError()
|
||||
}
|
||||
},
|
||||
canRedactOwnResult = { Result.success(true) },
|
||||
canRedactOtherResult = { Result.success(true) },
|
||||
canUserJoinCallResult = { Result.success(true) },
|
||||
typingNoticeResult = { Result.success(Unit) },
|
||||
)
|
||||
val presenter = createMessagesPresenter(matrixRoom = matrixRoom)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -702,7 +822,13 @@ class MessagesPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - permission to redact own`() = runTest {
|
||||
val matrixRoom = FakeMatrixRoom(canRedactOwn = true)
|
||||
val matrixRoom = FakeMatrixRoom(
|
||||
canRedactOwnResult = { Result.success(true) },
|
||||
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||
canRedactOtherResult = { Result.success(false) },
|
||||
canUserJoinCallResult = { Result.success(true) },
|
||||
typingNoticeResult = { Result.success(Unit) },
|
||||
)
|
||||
val presenter = createMessagesPresenter(matrixRoom = matrixRoom)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -716,7 +842,13 @@ class MessagesPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - permission to redact other`() = runTest {
|
||||
val matrixRoom = FakeMatrixRoom(canRedactOther = true)
|
||||
val matrixRoom = FakeMatrixRoom(
|
||||
canRedactOtherResult = { Result.success(true) },
|
||||
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||
canRedactOwnResult = { Result.success(false) },
|
||||
canUserJoinCallResult = { Result.success(true) },
|
||||
typingNoticeResult = { Result.success(Unit) },
|
||||
)
|
||||
val presenter = createMessagesPresenter(matrixRoom = matrixRoom)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -756,7 +888,13 @@ class MessagesPresenterTest {
|
|||
|
||||
private fun TestScope.createMessagesPresenter(
|
||||
coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
|
||||
matrixRoom: MatrixRoom = FakeMatrixRoom().apply {
|
||||
matrixRoom: MatrixRoom = FakeMatrixRoom(
|
||||
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||
canRedactOwnResult = { Result.success(true) },
|
||||
canRedactOtherResult = { Result.success(true) },
|
||||
canUserJoinCallResult = { Result.success(true) },
|
||||
typingNoticeResult = { Result.success(Unit) },
|
||||
).apply {
|
||||
givenRoomInfo(aRoomInfo(id = roomId, name = ""))
|
||||
},
|
||||
navigator: FakeMessagesNavigator = FakeMessagesNavigator(),
|
||||
|
|
|
|||
|
|
@ -26,7 +26,9 @@ import com.google.common.truth.Truth.assertThat
|
|||
import io.element.android.features.messages.impl.attachments.preview.AttachmentsPreviewEvents
|
||||
import io.element.android.features.messages.impl.attachments.preview.AttachmentsPreviewPresenter
|
||||
import io.element.android.features.messages.impl.attachments.preview.SendActionState
|
||||
import io.element.android.libraries.matrix.api.core.ProgressCallback
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
|
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.mediaupload.api.MediaPreProcessor
|
||||
import io.element.android.libraries.mediaupload.api.MediaSender
|
||||
|
|
@ -34,6 +36,7 @@ import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor
|
|||
import io.element.android.libraries.mediaviewer.api.local.LocalMedia
|
||||
import io.element.android.libraries.mediaviewer.test.viewer.aLocalMedia
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
|
@ -49,13 +52,16 @@ class AttachmentsPreviewPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - send media success scenario`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
room.givenProgressCallbackValues(
|
||||
listOf(
|
||||
val sendMediaResult = lambdaRecorder<ProgressCallback?, Result<FakeMediaUploadHandler>> {
|
||||
Result.success(FakeMediaUploadHandler())
|
||||
}
|
||||
val room = FakeMatrixRoom(
|
||||
progressCallbackValues = listOf(
|
||||
Pair(0, 10),
|
||||
Pair(5, 10),
|
||||
Pair(10, 10)
|
||||
)
|
||||
),
|
||||
sendMediaResult = sendMediaResult,
|
||||
)
|
||||
val presenter = createAttachmentsPreviewPresenter(room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
|
|
@ -70,15 +76,19 @@ class AttachmentsPreviewPresenterTest {
|
|||
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Uploading(1f))
|
||||
val successState = awaitItem()
|
||||
assertThat(successState.sendActionState).isEqualTo(SendActionState.Done)
|
||||
assertThat(room.sendMediaCount).isEqualTo(1)
|
||||
sendMediaResult.assertions().isCalledOnce()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - send media failure scenario`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
val failure = MediaPreProcessor.Failure(null)
|
||||
room.givenSendMediaResult(Result.failure(failure))
|
||||
val sendMediaResult = lambdaRecorder<ProgressCallback?, Result<FakeMediaUploadHandler>> {
|
||||
Result.failure(failure)
|
||||
}
|
||||
val room = FakeMatrixRoom(
|
||||
sendMediaResult = sendMediaResult,
|
||||
)
|
||||
val presenter = createAttachmentsPreviewPresenter(room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -90,7 +100,7 @@ class AttachmentsPreviewPresenterTest {
|
|||
assertThat(loadingState.sendActionState).isEqualTo(SendActionState.Sending.Processing)
|
||||
val failureState = awaitItem()
|
||||
assertThat(failureState.sendActionState).isEqualTo(SendActionState.Failure(failure))
|
||||
assertThat(room.sendMediaCount).isEqualTo(0)
|
||||
sendMediaResult.assertions().isCalledOnce()
|
||||
failureState.eventSink(AttachmentsPreviewEvents.ClearSendState)
|
||||
val clearedState = awaitItem()
|
||||
assertThat(clearedState.sendActionState).isEqualTo(SendActionState.Idle)
|
||||
|
|
|
|||
|
|
@ -22,11 +22,14 @@ import app.cash.turbine.test
|
|||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
|
@ -81,7 +84,12 @@ class ReportMessagePresenterTest {
|
|||
|
||||
@Test
|
||||
fun `presenter - handle successful report and block user`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
val reportContentResult = lambdaRecorder<EventId, String, UserId?, Result<Unit>> { _, _, _ ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val room = FakeMatrixRoom(
|
||||
reportContentResult = reportContentResult
|
||||
)
|
||||
val presenter = createReportMessagePresenter(matrixRoom = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -92,13 +100,18 @@ class ReportMessagePresenterTest {
|
|||
initialState.eventSink(ReportMessageEvents.Report)
|
||||
assertThat(awaitItem().result).isInstanceOf(AsyncAction.Loading::class.java)
|
||||
assertThat(awaitItem().result).isInstanceOf(AsyncAction.Success::class.java)
|
||||
assertThat(room.reportedContentCount).isEqualTo(1)
|
||||
reportContentResult.assertions().isCalledOnce()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `presenter - handle successful report`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
val reportContentResult = lambdaRecorder<EventId, String, UserId?, Result<Unit>> { _, _, _ ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val room = FakeMatrixRoom(
|
||||
reportContentResult = reportContentResult
|
||||
)
|
||||
val presenter = createReportMessagePresenter(matrixRoom = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -107,15 +120,18 @@ class ReportMessagePresenterTest {
|
|||
initialState.eventSink(ReportMessageEvents.Report)
|
||||
assertThat(awaitItem().result).isInstanceOf(AsyncAction.Loading::class.java)
|
||||
assertThat(awaitItem().result).isInstanceOf(AsyncAction.Success::class.java)
|
||||
assertThat(room.reportedContentCount).isEqualTo(1)
|
||||
reportContentResult.assertions().isCalledOnce()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `presenter - handle failed report`() = runTest {
|
||||
val room = FakeMatrixRoom().apply {
|
||||
givenReportContentResult(Result.failure(Exception("Failed to report content")))
|
||||
val reportContentResult = lambdaRecorder<EventId, String, UserId?, Result<Unit>> { _, _, _ ->
|
||||
Result.failure(Exception("Failed to report content"))
|
||||
}
|
||||
val room = FakeMatrixRoom(
|
||||
reportContentResult = reportContentResult
|
||||
)
|
||||
val presenter = createReportMessagePresenter(matrixRoom = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -125,7 +141,7 @@ class ReportMessagePresenterTest {
|
|||
assertThat(awaitItem().result).isInstanceOf(AsyncAction.Loading::class.java)
|
||||
val resultState = awaitItem()
|
||||
assertThat(resultState.result).isInstanceOf(AsyncAction.Failure::class.java)
|
||||
assertThat(room.reportedContentCount).isEqualTo(1)
|
||||
reportContentResult.assertions().isCalledOnce()
|
||||
|
||||
resultState.eventSink(ReportMessageEvents.ClearError)
|
||||
assertThat(awaitItem().result).isInstanceOf(AsyncAction.Uninitialized::class.java)
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ import io.element.android.libraries.featureflag.api.FeatureFlagService
|
|||
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.ProgressCallback
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.TransactionId
|
||||
import io.element.android.libraries.matrix.api.media.ImageInfo
|
||||
|
|
@ -55,6 +56,7 @@ import io.element.android.libraries.matrix.api.room.Mention
|
|||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||
import io.element.android.libraries.matrix.api.room.draft.ComposerDraft
|
||||
import io.element.android.libraries.matrix.api.room.draft.ComposerDraftType
|
||||
import io.element.android.libraries.matrix.api.timeline.TimelineException
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo
|
||||
import io.element.android.libraries.matrix.test.ANOTHER_MESSAGE
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
|
|
@ -67,6 +69,7 @@ import io.element.android.libraries.matrix.test.A_USER_ID_2
|
|||
import io.element.android.libraries.matrix.test.A_USER_ID_3
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID_4
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
|
||||
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
|
||||
|
|
@ -297,7 +300,13 @@ class MessageComposerPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - send message with rich text enabled`() = runTest {
|
||||
val presenter = createPresenter(this)
|
||||
val presenter = createPresenter(
|
||||
coroutineScope = this,
|
||||
room = FakeMatrixRoom(
|
||||
sendMessageResult = { _, _, _ -> Result.success(Unit) },
|
||||
typingNoticeResult = { Result.success(Unit) }
|
||||
),
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
val state = presenter.present()
|
||||
remember(state, state.textEditorState.messageHtml()) { state }
|
||||
|
|
@ -324,7 +333,14 @@ class MessageComposerPresenterTest {
|
|||
@Test
|
||||
fun `present - send message with plain text enabled`() = runTest {
|
||||
val permalinkBuilder = FakePermalinkBuilder(permalinkForUserLambda = { Result.success("") })
|
||||
val presenter = createPresenter(this, isRichTextEditorEnabled = false)
|
||||
val presenter = createPresenter(
|
||||
coroutineScope = this,
|
||||
isRichTextEditorEnabled = false,
|
||||
room = FakeMatrixRoom(
|
||||
sendMessageResult = { _, _, _ -> Result.success(Unit) },
|
||||
typingNoticeResult = { Result.success(Unit) }
|
||||
),
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
val state = presenter.present()
|
||||
val messageMarkdown = state.textEditorState.messageMarkdown(permalinkBuilder)
|
||||
|
|
@ -358,7 +374,10 @@ class MessageComposerPresenterTest {
|
|||
val timeline = FakeTimeline().apply {
|
||||
this.editMessageLambda = editMessageLambda
|
||||
}
|
||||
val fakeMatrixRoom = FakeMatrixRoom(liveTimeline = timeline)
|
||||
val fakeMatrixRoom = FakeMatrixRoom(
|
||||
liveTimeline = timeline,
|
||||
typingNoticeResult = { Result.success(Unit) }
|
||||
)
|
||||
val presenter = createPresenter(
|
||||
this,
|
||||
fakeMatrixRoom,
|
||||
|
|
@ -399,6 +418,67 @@ class MessageComposerPresenterTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - edit sent message event not found`() = runTest {
|
||||
val timelineEditMessageLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String, _: String?, _: List<Mention> ->
|
||||
Result.failure<Unit>(TimelineException.EventNotFound)
|
||||
}
|
||||
val timeline = FakeTimeline().apply {
|
||||
this.editMessageLambda = timelineEditMessageLambda
|
||||
}
|
||||
val roomEditMessageLambda = lambdaRecorder { _: EventId?, _: String, _: String?, _: List<Mention> ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val fakeMatrixRoom = FakeMatrixRoom(
|
||||
liveTimeline = timeline,
|
||||
typingNoticeResult = { Result.success(Unit) }
|
||||
).apply {
|
||||
this.editMessageLambda = roomEditMessageLambda
|
||||
}
|
||||
val presenter = createPresenter(
|
||||
this,
|
||||
fakeMatrixRoom,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
val state = presenter.present()
|
||||
remember(state, state.textEditorState.messageHtml()) { state }
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
assertThat(initialState.textEditorState.messageHtml()).isEqualTo("")
|
||||
val mode = anEditMode()
|
||||
initialState.eventSink.invoke(MessageComposerEvents.SetMode(mode))
|
||||
val withMessageState = awaitItem()
|
||||
assertThat(withMessageState.mode).isEqualTo(mode)
|
||||
assertThat(withMessageState.textEditorState.messageHtml()).isEqualTo(A_MESSAGE)
|
||||
withMessageState.textEditorState.setHtml(ANOTHER_MESSAGE)
|
||||
val withEditedMessageState = awaitItem()
|
||||
assertThat(withEditedMessageState.textEditorState.messageHtml()).isEqualTo(ANOTHER_MESSAGE)
|
||||
withEditedMessageState.eventSink.invoke(MessageComposerEvents.SendMessage)
|
||||
skipItems(1)
|
||||
val messageSentState = awaitItem()
|
||||
assertThat(messageSentState.textEditorState.messageHtml()).isEqualTo("")
|
||||
|
||||
advanceUntilIdle()
|
||||
|
||||
assert(timelineEditMessageLambda)
|
||||
.isCalledOnce()
|
||||
.with(value(AN_EVENT_ID), value(null), value(ANOTHER_MESSAGE), value(ANOTHER_MESSAGE), any())
|
||||
|
||||
assert(roomEditMessageLambda)
|
||||
.isCalledOnce()
|
||||
.with(value(AN_EVENT_ID), value(ANOTHER_MESSAGE), value(ANOTHER_MESSAGE), any())
|
||||
|
||||
assertThat(analyticsService.capturedEvents).containsExactly(
|
||||
Composer(
|
||||
inThread = false,
|
||||
isEditing = true,
|
||||
isReply = false,
|
||||
messageType = Composer.MessageType.Text,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - edit not sent message`() = runTest {
|
||||
val editMessageLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String, _: String?, _: List<Mention> ->
|
||||
|
|
@ -407,7 +487,10 @@ class MessageComposerPresenterTest {
|
|||
val timeline = FakeTimeline().apply {
|
||||
this.editMessageLambda = editMessageLambda
|
||||
}
|
||||
val fakeMatrixRoom = FakeMatrixRoom(liveTimeline = timeline)
|
||||
val fakeMatrixRoom = FakeMatrixRoom(
|
||||
liveTimeline = timeline,
|
||||
typingNoticeResult = { Result.success(Unit) },
|
||||
)
|
||||
val presenter = createPresenter(
|
||||
this,
|
||||
fakeMatrixRoom,
|
||||
|
|
@ -456,7 +539,10 @@ class MessageComposerPresenterTest {
|
|||
val timeline = FakeTimeline().apply {
|
||||
this.replyMessageLambda = replyMessageLambda
|
||||
}
|
||||
val fakeMatrixRoom = FakeMatrixRoom(liveTimeline = timeline)
|
||||
val fakeMatrixRoom = FakeMatrixRoom(
|
||||
liveTimeline = timeline,
|
||||
typingNoticeResult = { Result.success(Unit) }
|
||||
)
|
||||
val presenter = createPresenter(
|
||||
this,
|
||||
fakeMatrixRoom,
|
||||
|
|
@ -524,7 +610,9 @@ class MessageComposerPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - Pick image from gallery`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
val room = FakeMatrixRoom(
|
||||
typingNoticeResult = { Result.success(Unit) }
|
||||
)
|
||||
val presenter = createPresenter(this, room = room)
|
||||
pickerProvider.givenMimeType(MimeTypes.Images)
|
||||
mediaPreProcessor.givenResult(
|
||||
|
|
@ -557,7 +645,9 @@ class MessageComposerPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - Pick video from gallery`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
val room = FakeMatrixRoom(
|
||||
typingNoticeResult = { Result.success(Unit) }
|
||||
)
|
||||
val presenter = createPresenter(this, room = room)
|
||||
pickerProvider.givenMimeType(MimeTypes.Videos)
|
||||
mediaPreProcessor.givenResult(
|
||||
|
|
@ -607,13 +697,17 @@ class MessageComposerPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - Pick file from storage`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
room.givenProgressCallbackValues(
|
||||
listOf(
|
||||
val sendMediaResult = lambdaRecorder { _: ProgressCallback? ->
|
||||
Result.success(FakeMediaUploadHandler())
|
||||
}
|
||||
val room = FakeMatrixRoom(
|
||||
progressCallbackValues = listOf(
|
||||
Pair(0, 10),
|
||||
Pair(5, 10),
|
||||
Pair(10, 10)
|
||||
)
|
||||
),
|
||||
sendMediaResult = sendMediaResult,
|
||||
typingNoticeResult = { Result.success(Unit) }
|
||||
)
|
||||
val presenter = createPresenter(this, room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
|
|
@ -629,13 +723,15 @@ class MessageComposerPresenterTest {
|
|||
assertThat(awaitItem().attachmentsState).isEqualTo(AttachmentsState.Sending.Uploading(1f))
|
||||
val sentState = awaitItem()
|
||||
assertThat(sentState.attachmentsState).isEqualTo(AttachmentsState.None)
|
||||
assertThat(room.sendMediaCount).isEqualTo(1)
|
||||
sendMediaResult.assertions().isCalledOnce()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - create poll`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
val room = FakeMatrixRoom(
|
||||
typingNoticeResult = { Result.success(Unit) }
|
||||
)
|
||||
val presenter = createPresenter(this, room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -652,7 +748,9 @@ class MessageComposerPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - share location`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
val room = FakeMatrixRoom(
|
||||
typingNoticeResult = { Result.success(Unit) }
|
||||
)
|
||||
val presenter = createPresenter(this, room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -669,7 +767,9 @@ class MessageComposerPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - Take photo`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
val room = FakeMatrixRoom(
|
||||
typingNoticeResult = { Result.success(Unit) }
|
||||
)
|
||||
val permissionPresenter = FakePermissionsPresenter().apply { setPermissionGranted() }
|
||||
val presenter = createPresenter(
|
||||
this,
|
||||
|
|
@ -689,7 +789,9 @@ class MessageComposerPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - Take photo with permission request`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
val room = FakeMatrixRoom(
|
||||
typingNoticeResult = { Result.success(Unit) }
|
||||
)
|
||||
val permissionPresenter = FakePermissionsPresenter()
|
||||
val presenter = createPresenter(
|
||||
this,
|
||||
|
|
@ -714,7 +816,9 @@ class MessageComposerPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - Record video`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
val room = FakeMatrixRoom(
|
||||
typingNoticeResult = { Result.success(Unit) }
|
||||
)
|
||||
val permissionPresenter = FakePermissionsPresenter().apply { setPermissionGranted() }
|
||||
val presenter = createPresenter(
|
||||
this,
|
||||
|
|
@ -734,7 +838,9 @@ class MessageComposerPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - Record video with permission request`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
val room = FakeMatrixRoom(
|
||||
typingNoticeResult = { Result.success(Unit) }
|
||||
)
|
||||
val permissionPresenter = FakePermissionsPresenter()
|
||||
val presenter = createPresenter(
|
||||
this,
|
||||
|
|
@ -759,9 +865,10 @@ class MessageComposerPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - Uploading media failure can be recovered from`() = runTest {
|
||||
val room = FakeMatrixRoom().apply {
|
||||
givenSendMediaResult(Result.failure(Exception()))
|
||||
}
|
||||
val room = FakeMatrixRoom(
|
||||
sendMediaResult = { Result.failure(Exception()) },
|
||||
typingNoticeResult = { Result.success(Unit) }
|
||||
)
|
||||
val presenter = createPresenter(this, room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -842,15 +949,17 @@ class MessageComposerPresenterTest {
|
|||
val invitedUser = aRoomMember(userId = A_USER_ID_3, membership = RoomMembershipState.INVITE)
|
||||
val bob = aRoomMember(userId = A_USER_ID_2, membership = RoomMembershipState.JOIN)
|
||||
val david = aRoomMember(userId = A_USER_ID_4, displayName = "Dave", membership = RoomMembershipState.JOIN)
|
||||
var canUserTriggerRoomNotificationResult = true
|
||||
val room = FakeMatrixRoom(
|
||||
isDirect = false,
|
||||
canUserTriggerRoomNotificationResult = { Result.success(canUserTriggerRoomNotificationResult) },
|
||||
typingNoticeResult = { Result.success(Unit) }
|
||||
).apply {
|
||||
givenRoomMembersState(
|
||||
MatrixRoomMembersState.Ready(
|
||||
persistentListOf(currentUser, invitedUser, bob, david),
|
||||
)
|
||||
)
|
||||
givenCanTriggerRoomNotification(Result.success(true))
|
||||
}
|
||||
val flagsService = FakeFeatureFlagService(
|
||||
mapOf(
|
||||
|
|
@ -890,13 +999,10 @@ class MessageComposerPresenterTest {
|
|||
assertThat(awaitItem().memberSuggestions).isEmpty()
|
||||
|
||||
// If user has no permission to send `@room` mentions, `RoomMemberSuggestion.Room` is not returned
|
||||
room.givenCanTriggerRoomNotification(Result.success(false))
|
||||
canUserTriggerRoomNotificationResult = false
|
||||
initialState.eventSink(MessageComposerEvents.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, "")))
|
||||
assertThat(awaitItem().memberSuggestions)
|
||||
.containsExactly(ResolvedMentionSuggestion.Member(bob), ResolvedMentionSuggestion.Member(david))
|
||||
|
||||
// If room is a DM, `RoomMemberSuggestion.Room` is not returned
|
||||
room.givenCanTriggerRoomNotification(Result.success(true))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -910,13 +1016,14 @@ class MessageComposerPresenterTest {
|
|||
isDirect = true,
|
||||
activeMemberCount = 2,
|
||||
isEncrypted = true,
|
||||
canUserTriggerRoomNotificationResult = { Result.success(true) },
|
||||
typingNoticeResult = { Result.success(Unit) }
|
||||
).apply {
|
||||
givenRoomMembersState(
|
||||
MatrixRoomMembersState.Ready(
|
||||
persistentListOf(currentUser, invitedUser, bob, david),
|
||||
)
|
||||
)
|
||||
givenCanTriggerRoomNotification(Result.success(true))
|
||||
}
|
||||
val flagsService = FakeFeatureFlagService(
|
||||
mapOf(
|
||||
|
|
@ -973,7 +1080,14 @@ class MessageComposerPresenterTest {
|
|||
this.replyMessageLambda = replyMessageLambda
|
||||
this.editMessageLambda = editMessageLambda
|
||||
}
|
||||
val room = FakeMatrixRoom(liveTimeline = timeline)
|
||||
val sendMessageResult = lambdaRecorder { _: String, _: String?, _: List<Mention> ->
|
||||
Result.success(Unit)
|
||||
}
|
||||
val room = FakeMatrixRoom(
|
||||
liveTimeline = timeline,
|
||||
sendMessageResult = sendMessageResult,
|
||||
typingNoticeResult = { Result.success(Unit) }
|
||||
)
|
||||
val presenter = createPresenter(room = room, coroutineScope = this)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -993,7 +1107,8 @@ class MessageComposerPresenterTest {
|
|||
|
||||
advanceUntilIdle()
|
||||
|
||||
assertThat(room.sendMessageMentions).isEqualTo(listOf(Mention.User(A_USER_ID)))
|
||||
sendMessageResult.assertions().isCalledOnce()
|
||||
.with(value(A_MESSAGE), any(), value(listOf(Mention.User(A_USER_ID))))
|
||||
|
||||
// Check intentional mentions on reply sent
|
||||
initialState.eventSink(MessageComposerEvents.SetMode(aReplyMode()))
|
||||
|
|
@ -1049,22 +1164,32 @@ class MessageComposerPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - handle typing notice event`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
val typingNoticeResult = lambdaRecorder<Boolean, Result<Unit>> { Result.success(Unit) }
|
||||
val room = FakeMatrixRoom(
|
||||
typingNoticeResult = typingNoticeResult
|
||||
)
|
||||
val presenter = createPresenter(room = room, coroutineScope = this)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
assertThat(room.typingRecord).isEmpty()
|
||||
typingNoticeResult.assertions().isNeverCalled()
|
||||
initialState.eventSink.invoke(MessageComposerEvents.TypingNotice(true))
|
||||
initialState.eventSink.invoke(MessageComposerEvents.TypingNotice(false))
|
||||
assertThat(room.typingRecord).isEqualTo(listOf(true, false))
|
||||
typingNoticeResult.assertions().isCalledExactly(2)
|
||||
.withSequence(
|
||||
listOf(value(true)),
|
||||
listOf(value(false)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - handle typing notice event when sending typing notice is disabled`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
val typingNoticeResult = lambdaRecorder<Boolean, Result<Unit>> { Result.success(Unit) }
|
||||
val room = FakeMatrixRoom(
|
||||
typingNoticeResult = typingNoticeResult
|
||||
)
|
||||
val store = InMemorySessionPreferencesStore(
|
||||
isSendTypingNotificationsEnabled = false
|
||||
)
|
||||
|
|
@ -1073,10 +1198,10 @@ class MessageComposerPresenterTest {
|
|||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
assertThat(room.typingRecord).isEmpty()
|
||||
typingNoticeResult.assertions().isNeverCalled()
|
||||
initialState.eventSink.invoke(MessageComposerEvents.TypingNotice(true))
|
||||
initialState.eventSink.invoke(MessageComposerEvents.TypingNotice(false))
|
||||
assertThat(room.typingRecord).isEmpty()
|
||||
typingNoticeResult.assertions().isNeverCalled()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1215,7 +1340,10 @@ class MessageComposerPresenterTest {
|
|||
val timeline = FakeTimeline().apply {
|
||||
this.loadReplyDetailsLambda = loadReplyDetailsLambda
|
||||
}
|
||||
val room = FakeMatrixRoom(liveTimeline = timeline)
|
||||
val room = FakeMatrixRoom(
|
||||
liveTimeline = timeline,
|
||||
typingNoticeResult = { Result.success(Unit) },
|
||||
)
|
||||
val permalinkBuilder = FakePermalinkBuilder()
|
||||
val presenter = createPresenter(
|
||||
room = room,
|
||||
|
|
@ -1352,7 +1480,9 @@ class MessageComposerPresenterTest {
|
|||
|
||||
private fun createPresenter(
|
||||
coroutineScope: CoroutineScope,
|
||||
room: MatrixRoom = FakeMatrixRoom(),
|
||||
room: MatrixRoom = FakeMatrixRoom(
|
||||
typingNoticeResult = { Result.success(Unit) }
|
||||
),
|
||||
pickerProvider: PickerProvider = this.pickerProvider,
|
||||
featureFlagService: FeatureFlagService = this.featureFlagService,
|
||||
sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(),
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import io.element.android.libraries.matrix.test.A_UNIQUE_ID
|
|||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
|
||||
import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
|
@ -38,9 +39,9 @@ class TimelineControllerTest {
|
|||
val liveTimeline = FakeTimeline(name = "live")
|
||||
val detachedTimeline = FakeTimeline(name = "detached")
|
||||
val matrixRoom = FakeMatrixRoom(
|
||||
liveTimeline = liveTimeline
|
||||
liveTimeline = liveTimeline,
|
||||
timelineFocusedOnEventResult = { Result.success(detachedTimeline) }
|
||||
)
|
||||
matrixRoom.givenTimelineFocusedOnEventResult(Result.success(detachedTimeline))
|
||||
val sut = TimelineController(matrixRoom)
|
||||
|
||||
sut.activeTimelineFlow().test {
|
||||
|
|
@ -68,8 +69,17 @@ class TimelineControllerTest {
|
|||
val liveTimeline = FakeTimeline(name = "live")
|
||||
val detachedTimeline1 = FakeTimeline(name = "detached 1")
|
||||
val detachedTimeline2 = FakeTimeline(name = "detached 2")
|
||||
var callNumber = 0
|
||||
val matrixRoom = FakeMatrixRoom(
|
||||
liveTimeline = liveTimeline
|
||||
liveTimeline = liveTimeline,
|
||||
timelineFocusedOnEventResult = {
|
||||
callNumber++
|
||||
when (callNumber) {
|
||||
1 -> Result.success(detachedTimeline1)
|
||||
2 -> Result.success(detachedTimeline2)
|
||||
else -> lambdaError()
|
||||
}
|
||||
}
|
||||
)
|
||||
val sut = TimelineController(matrixRoom)
|
||||
|
||||
|
|
@ -77,7 +87,6 @@ class TimelineControllerTest {
|
|||
awaitItem().also { state ->
|
||||
assertThat(state).isEqualTo(liveTimeline)
|
||||
}
|
||||
matrixRoom.givenTimelineFocusedOnEventResult(Result.success(detachedTimeline1))
|
||||
sut.focusOnEvent(AN_EVENT_ID)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state).isEqualTo(detachedTimeline1)
|
||||
|
|
@ -85,7 +94,6 @@ class TimelineControllerTest {
|
|||
assertThat(detachedTimeline1.closeCounter).isEqualTo(0)
|
||||
assertThat(detachedTimeline2.closeCounter).isEqualTo(0)
|
||||
// Focus on another event should close the previous detached timeline
|
||||
matrixRoom.givenTimelineFocusedOnEventResult(Result.success(detachedTimeline2))
|
||||
sut.focusOnEvent(AN_EVENT_ID)
|
||||
awaitItem().also { state ->
|
||||
assertThat(state).isEqualTo(detachedTimeline2)
|
||||
|
|
@ -117,11 +125,10 @@ class TimelineControllerTest {
|
|||
val liveTimeline = FakeTimeline(name = "live")
|
||||
val detachedTimeline = FakeTimeline(name = "detached")
|
||||
val matrixRoom = FakeMatrixRoom(
|
||||
liveTimeline = liveTimeline
|
||||
liveTimeline = liveTimeline,
|
||||
timelineFocusedOnEventResult = { Result.success(detachedTimeline) }
|
||||
)
|
||||
matrixRoom.givenTimelineFocusedOnEventResult(Result.success(detachedTimeline))
|
||||
val sut = TimelineController(matrixRoom)
|
||||
|
||||
sut.activeTimelineFlow().test {
|
||||
awaitItem().also { state ->
|
||||
assertThat(state).isEqualTo(liveTimeline)
|
||||
|
|
@ -168,9 +175,9 @@ class TimelineControllerTest {
|
|||
sendMessageLambda = lambdaForDetached
|
||||
}
|
||||
val matrixRoom = FakeMatrixRoom(
|
||||
liveTimeline = liveTimeline
|
||||
liveTimeline = liveTimeline,
|
||||
timelineFocusedOnEventResult = { Result.success(detachedTimeline) }
|
||||
)
|
||||
matrixRoom.givenTimelineFocusedOnEventResult(Result.success(detachedTimeline))
|
||||
val sut = TimelineController(matrixRoom)
|
||||
sut.activeTimelineFlow().test {
|
||||
sut.focusOnEvent(AN_EVENT_ID)
|
||||
|
|
@ -193,9 +200,9 @@ class TimelineControllerTest {
|
|||
val liveTimeline = FakeTimeline(name = "live")
|
||||
val detachedTimeline = FakeTimeline(name = "detached")
|
||||
val matrixRoom = FakeMatrixRoom(
|
||||
liveTimeline = liveTimeline
|
||||
liveTimeline = liveTimeline,
|
||||
timelineFocusedOnEventResult = { Result.success(detachedTimeline) }
|
||||
)
|
||||
matrixRoom.givenTimelineFocusedOnEventResult(Result.success(detachedTimeline))
|
||||
val sut = TimelineController(matrixRoom)
|
||||
|
||||
sut.activeTimelineFlow().test {
|
||||
|
|
|
|||
|
|
@ -133,7 +133,10 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
|
|||
)
|
||||
)
|
||||
)
|
||||
val room = FakeMatrixRoom(liveTimeline = timeline)
|
||||
val room = FakeMatrixRoom(
|
||||
liveTimeline = timeline,
|
||||
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||
)
|
||||
val sessionPreferencesStore = InMemorySessionPreferencesStore(isSendPublicReadReceiptsEnabled = false)
|
||||
val presenter = createTimelinePresenter(
|
||||
timeline = timeline,
|
||||
|
|
@ -482,9 +485,9 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
|
|||
)
|
||||
val room = FakeMatrixRoom(
|
||||
liveTimeline = liveTimeline,
|
||||
).apply {
|
||||
givenTimelineFocusedOnEventResult(Result.success(detachedTimeline))
|
||||
}
|
||||
timelineFocusedOnEventResult = { Result.success(detachedTimeline) },
|
||||
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||
)
|
||||
val presenter = createTimelinePresenter(
|
||||
room = room,
|
||||
)
|
||||
|
|
@ -529,6 +532,7 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
|
|||
)
|
||||
)
|
||||
),
|
||||
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||
),
|
||||
timelineItemIndexer = timelineItemIndexer,
|
||||
)
|
||||
|
|
@ -551,9 +555,9 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
|
|||
liveTimeline = FakeTimeline(
|
||||
timelineItems = flowOf(emptyList()),
|
||||
),
|
||||
).apply {
|
||||
givenTimelineFocusedOnEventResult(Result.failure(Throwable("An error")))
|
||||
},
|
||||
timelineFocusedOnEventResult = { Result.failure(Throwable("An error")) },
|
||||
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||
)
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -594,7 +598,10 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
|
|||
)
|
||||
)
|
||||
)
|
||||
val room = FakeMatrixRoom(liveTimeline = timeline).apply {
|
||||
val room = FakeMatrixRoom(
|
||||
liveTimeline = timeline,
|
||||
canUserSendMessageResult = { _, _ -> Result.success(true) },
|
||||
).apply {
|
||||
givenRoomMembersState(MatrixRoomMembersState.Unknown)
|
||||
}
|
||||
|
||||
|
|
@ -626,7 +633,10 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2"
|
|||
|
||||
private fun TestScope.createTimelinePresenter(
|
||||
timeline: Timeline = FakeTimeline(),
|
||||
room: FakeMatrixRoom = FakeMatrixRoom(liveTimeline = timeline),
|
||||
room: FakeMatrixRoom = FakeMatrixRoom(
|
||||
liveTimeline = timeline,
|
||||
canUserSendMessageResult = { _, _ -> Result.success(true) }
|
||||
),
|
||||
timelineItemsFactory: TimelineItemsFactory = aTimelineItemsFactory(),
|
||||
redactedVoiceMessageManager: RedactedVoiceMessageManager = FakeRedactedVoiceMessageManager(),
|
||||
messagesNavigator: FakeMessagesNavigator = FakeMessagesNavigator(),
|
||||
|
|
|
|||
|
|
@ -20,9 +20,11 @@ import android.net.Uri
|
|||
import android.text.SpannableString
|
||||
import android.text.SpannableStringBuilder
|
||||
import android.text.Spanned
|
||||
import android.text.SpannedString
|
||||
import android.text.style.URLSpan
|
||||
import androidx.core.text.buildSpannedString
|
||||
import androidx.core.text.inSpans
|
||||
import androidx.core.text.toSpannable
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.location.api.Location
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemAudioContent
|
||||
|
|
@ -74,6 +76,7 @@ import io.element.android.libraries.mediaviewer.api.util.FileExtensionExtractorW
|
|||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Assert.fail
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
|
|
@ -195,7 +198,7 @@ class TimelineItemContentMessageFactoryTest {
|
|||
inSpans(URLSpan("https://matrix.org")) {
|
||||
append("and manually added link")
|
||||
}
|
||||
}
|
||||
}.toSpannable()
|
||||
val sut = createTimelineItemContentMessageFactory(
|
||||
htmlConverterTransform = { expected }
|
||||
)
|
||||
|
|
@ -610,7 +613,7 @@ class TimelineItemContentMessageFactoryTest {
|
|||
senderDisambiguatedDisplayName = "Bob",
|
||||
eventId = AN_EVENT_ID,
|
||||
)
|
||||
assertThat((result as TimelineItemNoticeContent).formattedBody).isEqualTo("formatted")
|
||||
(result as TimelineItemNoticeContent).formattedBody.assertSpannedEquals(SpannedString("formatted"))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -644,7 +647,8 @@ class TimelineItemContentMessageFactoryTest {
|
|||
senderDisambiguatedDisplayName = "Bob",
|
||||
eventId = AN_EVENT_ID,
|
||||
)
|
||||
assertThat((result as TimelineItemEmoteContent).formattedBody).isEqualTo(SpannableString("* Bob formatted"))
|
||||
|
||||
(result as TimelineItemEmoteContent).formattedBody.assertSpannedEquals(SpannableString("* Bob formatted"))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -654,7 +658,7 @@ class TimelineItemContentMessageFactoryTest {
|
|||
inSpans(URLSpan("https://www.example.org")) {
|
||||
append("me@matrix.org")
|
||||
}
|
||||
}
|
||||
}.toSpannable()
|
||||
val sut = createTimelineItemContentMessageFactory(
|
||||
htmlConverterTransform = { expectedSpanned },
|
||||
permalinkParser = FakePermalinkParser { PermalinkData.FallbackLink(Uri.EMPTY) }
|
||||
|
|
@ -669,7 +673,59 @@ class TimelineItemContentMessageFactoryTest {
|
|||
senderDisambiguatedDisplayName = "Bob",
|
||||
eventId = AN_EVENT_ID,
|
||||
)
|
||||
assertThat((result as TimelineItemTextContent).formattedBody).isEqualTo(expectedSpanned)
|
||||
(result as TimelineItemTextContent).formattedBody.assertSpannedEquals(expectedSpanned)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a message with plain URL in a formatted body Spanned format gets linkified too`() = runTest {
|
||||
val expectedSpanned = buildSpannedString {
|
||||
append("Test ")
|
||||
inSpansWithFlags(URLSpan("https://www.example.org"), flags = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) {
|
||||
append("https://www.example.org")
|
||||
}
|
||||
}
|
||||
val sut = createTimelineItemContentMessageFactory(
|
||||
htmlConverterTransform = { expectedSpanned },
|
||||
permalinkParser = FakePermalinkParser { PermalinkData.FallbackLink(Uri.EMPTY) }
|
||||
)
|
||||
val result = sut.create(
|
||||
content = createMessageContent(
|
||||
type = TextMessageType(
|
||||
body = "Test [me@matrix.org](https://www.example.org)",
|
||||
formatted = FormattedBody(MessageFormat.HTML, "Test https://www.example.org")
|
||||
)
|
||||
),
|
||||
senderDisambiguatedDisplayName = "Bob",
|
||||
eventId = AN_EVENT_ID,
|
||||
)
|
||||
(result as TimelineItemTextContent).formattedBody.assertSpannedEquals(expectedSpanned)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `a message with plain URL in a formatted body with plain text format gets linkified too`() = runTest {
|
||||
val resultString = "Test https://www.example.org"
|
||||
val expectedSpanned = buildSpannedString {
|
||||
append("Test ")
|
||||
inSpansWithFlags(URLSpan("https://www.example.org"), flags = Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) {
|
||||
append("https://www.example.org")
|
||||
}
|
||||
}.toSpannable()
|
||||
val sut = createTimelineItemContentMessageFactory(
|
||||
htmlConverterTransform = { resultString },
|
||||
permalinkParser = FakePermalinkParser { PermalinkData.FallbackLink(Uri.EMPTY) }
|
||||
)
|
||||
val result = sut.create(
|
||||
content = createMessageContent(
|
||||
type = TextMessageType(
|
||||
body = "Test [me@matrix.org](https://www.example.org)",
|
||||
formatted = FormattedBody(MessageFormat.HTML, "Test https://www.example.org")
|
||||
)
|
||||
),
|
||||
senderDisambiguatedDisplayName = "Bob",
|
||||
eventId = AN_EVENT_ID,
|
||||
)
|
||||
|
||||
(result as TimelineItemTextContent).formattedBody.assertSpannedEquals(expectedSpanned)
|
||||
}
|
||||
|
||||
private fun createMessageContent(
|
||||
|
|
@ -718,3 +774,40 @@ class TimelineItemContentMessageFactoryTest {
|
|||
fileExtensionExtractor = FileExtensionExtractorWithoutValidation()
|
||||
)
|
||||
}
|
||||
|
||||
private inline fun SpannableStringBuilder.inSpansWithFlags(span: Any, flags: Int, action: SpannableStringBuilder.() -> Unit) {
|
||||
val start = this.length
|
||||
action()
|
||||
val end = this.length
|
||||
setSpan(span, start, end, flags)
|
||||
}
|
||||
|
||||
fun CharSequence?.assertSpannedEquals(other: CharSequence?) {
|
||||
if (this == null && other == null) {
|
||||
return
|
||||
} else if (this is Spanned && other is Spanned) {
|
||||
assertThat(this.toString()).isEqualTo(other.toString())
|
||||
assertThat(this.length).isEqualTo(other.length)
|
||||
val thisSpans = this.getSpans(0, this.length, Any::class.java)
|
||||
val otherSpans = other.getSpans(0, other.length, Any::class.java)
|
||||
if (thisSpans.size != otherSpans.size) {
|
||||
fail("Expected ${thisSpans.size} spans, got ${otherSpans.size}")
|
||||
}
|
||||
thisSpans.forEachIndexed { index, span ->
|
||||
val otherSpan = otherSpans[index]
|
||||
// URLSpans don't have a proper `equals` implementation, so we compare the URL instead
|
||||
if (span is URLSpan && otherSpan is URLSpan) {
|
||||
assertThat(span.url).isEqualTo(otherSpan.url)
|
||||
} else {
|
||||
assertThat(span).isEqualTo(otherSpan)
|
||||
}
|
||||
assertThat(this.getSpanStart(span)).isEqualTo(other.getSpanStart(otherSpan))
|
||||
assertThat(this.getSpanEnd(span)).isEqualTo(other.getSpanEnd(otherSpan))
|
||||
assertThat(this.getSpanFlags(span)).isEqualTo(other.getSpanFlags(otherSpan))
|
||||
}
|
||||
} else {
|
||||
val thisString = this?.toString() ?: "null"
|
||||
val otherString = other?.toString() ?: "null"
|
||||
fail("Expected Spanned, got $thisString and $otherString")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,7 +28,9 @@ import com.google.common.truth.Truth.assertThat
|
|||
import im.vector.app.features.analytics.plan.Composer
|
||||
import io.element.android.features.messages.impl.voicemessages.VoiceMessageException
|
||||
import io.element.android.features.messages.test.FakeMessageComposerContext
|
||||
import io.element.android.libraries.matrix.api.core.ProgressCallback
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
|
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
|
||||
import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer
|
||||
|
|
@ -45,6 +47,7 @@ import io.element.android.libraries.textcomposer.model.VoiceMessageState
|
|||
import io.element.android.libraries.voicerecorder.test.FakeVoiceRecorder
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
|
|
@ -63,7 +66,10 @@ class VoiceMessageComposerPresenterTest {
|
|||
recordingDuration = RECORDING_DURATION
|
||||
)
|
||||
private val analyticsService = FakeAnalyticsService()
|
||||
private val matrixRoom = FakeMatrixRoom()
|
||||
private val sendMediaResult = lambdaRecorder<ProgressCallback?, Result<FakeMediaUploadHandler>> { Result.success(FakeMediaUploadHandler()) }
|
||||
private val matrixRoom = FakeMatrixRoom(
|
||||
sendMediaResult = sendMediaResult
|
||||
)
|
||||
private val mediaPreProcessor = FakeMediaPreProcessor().apply { givenAudioResult() }
|
||||
private val mediaSender = MediaSender(mediaPreProcessor, matrixRoom)
|
||||
private val messageComposerContext = FakeMessageComposerContext()
|
||||
|
|
@ -295,7 +301,7 @@ class VoiceMessageComposerPresenterTest {
|
|||
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
|
||||
assertThat(matrixRoom.sendMediaCount).isEqualTo(1)
|
||||
sendMediaResult.assertions().isCalledOnce()
|
||||
voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 1)
|
||||
|
||||
testPauseAndDestroy(finalState)
|
||||
|
|
@ -346,7 +352,7 @@ class VoiceMessageComposerPresenterTest {
|
|||
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
|
||||
assertThat(matrixRoom.sendMediaCount).isEqualTo(1)
|
||||
sendMediaResult.assertions().isCalledOnce()
|
||||
voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 1)
|
||||
|
||||
testPauseAndDestroy(finalState)
|
||||
|
|
@ -369,7 +375,7 @@ class VoiceMessageComposerPresenterTest {
|
|||
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
|
||||
assertThat(matrixRoom.sendMediaCount).isEqualTo(1)
|
||||
sendMediaResult.assertions().isCalledOnce()
|
||||
voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 1)
|
||||
|
||||
testPauseAndDestroy(finalState)
|
||||
|
|
@ -393,7 +399,7 @@ class VoiceMessageComposerPresenterTest {
|
|||
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.voiceMessageState).isEqualTo(aPreviewState(isSending = true))
|
||||
assertThat(matrixRoom.sendMediaCount).isEqualTo(0)
|
||||
sendMediaResult.assertions().isNeverCalled()
|
||||
assertThat(analyticsService.trackedErrors).hasSize(0)
|
||||
voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 0)
|
||||
|
||||
|
|
@ -418,13 +424,13 @@ class VoiceMessageComposerPresenterTest {
|
|||
|
||||
ensureAllEventsConsumed()
|
||||
assertThat(previewState.voiceMessageState).isEqualTo(aPreviewState())
|
||||
assertThat(matrixRoom.sendMediaCount).isEqualTo(0)
|
||||
sendMediaResult.assertions().isNeverCalled()
|
||||
|
||||
mediaPreProcessor.givenAudioResult()
|
||||
previewState.eventSink(VoiceMessageComposerEvents.SendVoiceMessage)
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
|
||||
assertThat(matrixRoom.sendMediaCount).isEqualTo(1)
|
||||
sendMediaResult.assertions().isCalledOnce()
|
||||
voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 1)
|
||||
|
||||
testPauseAndDestroy(finalState)
|
||||
|
|
@ -461,7 +467,7 @@ class VoiceMessageComposerPresenterTest {
|
|||
assertThat(showSendFailureDialog).isFalse()
|
||||
}
|
||||
|
||||
assertThat(matrixRoom.sendMediaCount).isEqualTo(0)
|
||||
sendMediaResult.assertions().isNeverCalled()
|
||||
testPauseAndDestroy(finalState)
|
||||
}
|
||||
}
|
||||
|
|
@ -477,7 +483,7 @@ class VoiceMessageComposerPresenterTest {
|
|||
initialState.eventSink(VoiceMessageComposerEvents.SendVoiceMessage)
|
||||
|
||||
assertThat(initialState.voiceMessageState).isEqualTo(VoiceMessageState.Idle)
|
||||
assertThat(matrixRoom.sendMediaCount).isEqualTo(0)
|
||||
sendMediaResult.assertions().isNeverCalled()
|
||||
assertThat(analyticsService.trackedErrors).hasSize(1)
|
||||
voiceRecorder.assertCalls(started = 0)
|
||||
|
||||
|
|
@ -496,7 +502,7 @@ class VoiceMessageComposerPresenterTest {
|
|||
val initialState = awaitItem()
|
||||
initialState.eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start))
|
||||
|
||||
assertThat(matrixRoom.sendMediaCount).isEqualTo(0)
|
||||
sendMediaResult.assertions().isNeverCalled()
|
||||
assertThat(analyticsService.trackedErrors).containsExactly(
|
||||
VoiceMessageException.PermissionMissing(message = "Expected permission to record but none", cause = exception)
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue