mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-10-31 16:22:49 +00:00
Fix issues and add missing text.
This commit is contained in:
+2
@@ -32,6 +32,8 @@ class TimelineItemActionComparator : Comparator<TimelineItemAction> {
|
||||
TimelineItemAction.ViewSource,
|
||||
TimelineItemAction.ReportContent,
|
||||
TimelineItemAction.Redact,
|
||||
// Not rendered in the action list, but has to be listed here for exhaustiveness.
|
||||
TimelineItemAction.RetrySending,
|
||||
)
|
||||
|
||||
override fun compare(o1: TimelineItemAction, o2: TimelineItemAction): Int {
|
||||
|
||||
+3
-2
@@ -56,10 +56,11 @@ fun TimelineEventTimestampView(
|
||||
val isVerifiedUserSendFailure = event.localSendState is LocalEventSendState.Failed.VerifiedUser
|
||||
val onClickLabel = when {
|
||||
shield != null -> stringResource(CommonStrings.a11y_view_details)
|
||||
hasError && isVerifiedUserSendFailure -> stringResource(CommonStrings.action_open_context_menu)
|
||||
isVerifiedUserSendFailure -> stringResource(CommonStrings.action_open_context_menu)
|
||||
hasError -> stringResource(CommonStrings.a11y_view_details)
|
||||
else -> null
|
||||
}
|
||||
val clickableModifier = remember(shield, hasError) {
|
||||
val clickableModifier = remember(event) {
|
||||
when {
|
||||
shield != null -> {
|
||||
Modifier.clickable(
|
||||
|
||||
+4
-3
@@ -13,6 +13,7 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
||||
@@ -45,13 +46,13 @@ fun SendFailureDialogView(
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
fun SendFailureDialogViewPreview(
|
||||
internal fun SendFailureDialogViewPreview(
|
||||
@PreviewParameter(SendFailureDialogStateProvider::class) sendFailureDialogState: SendFailureDialogState,
|
||||
) {
|
||||
) = ElementPreview {
|
||||
SendFailureDialogView(
|
||||
sendFailureDialogState = sendFailureDialogState,
|
||||
onDismiss = {},
|
||||
onRetry = {},
|
||||
onRemoveMessage = {}
|
||||
onRemoveMessage = {},
|
||||
)
|
||||
}
|
||||
|
||||
+52
@@ -72,6 +72,7 @@ import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId
|
||||
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
@@ -83,6 +84,7 @@ import io.element.android.libraries.matrix.test.A_SESSION_ID_2
|
||||
import io.element.android.libraries.matrix.test.A_THREAD_ID
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID
|
||||
import io.element.android.libraries.matrix.test.A_USER_ID_2
|
||||
import io.element.android.libraries.matrix.test.core.FakeSendHandle
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
|
||||
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
|
||||
@@ -1043,6 +1045,56 @@ class MessagesPresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - handle action retry sending`() = runTest {
|
||||
val retryLambda = lambdaRecorder<Result<Unit>> { Result.success(Unit) }
|
||||
val messageEvent = aMessageEvent(
|
||||
sendState = LocalEventSendState.Failed.Unknown("Error"),
|
||||
sendHandleProvider = { FakeSendHandle(retryLambda = retryLambda) },
|
||||
)
|
||||
val presenter = createMessagesPresenter()
|
||||
presenter.testWithLifecycleOwner {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.RetrySending, messageEvent))
|
||||
advanceUntilIdle()
|
||||
retryLambda.assertions().isCalledOnce()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - handle action retry sending - failure is ignored`() = runTest {
|
||||
val retryLambda = lambdaRecorder<Result<Unit>> { Result.failure(AN_EXCEPTION) }
|
||||
val messageEvent = aMessageEvent(
|
||||
sendState = LocalEventSendState.Failed.Unknown("Error"),
|
||||
sendHandleProvider = { FakeSendHandle(retryLambda = retryLambda) },
|
||||
)
|
||||
val presenter = createMessagesPresenter()
|
||||
presenter.testWithLifecycleOwner {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.RetrySending, messageEvent))
|
||||
advanceUntilIdle()
|
||||
retryLambda.assertions().isCalledOnce()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - handle action retry sending - no send handle, it should have no effect`() = runTest {
|
||||
val messageEvent = aMessageEvent(
|
||||
sendState = LocalEventSendState.Failed.Unknown("Error"),
|
||||
sendHandleProvider = { null },
|
||||
)
|
||||
val presenter = createMessagesPresenter()
|
||||
presenter.testWithLifecycleOwner {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.RetrySending, messageEvent))
|
||||
advanceUntilIdle()
|
||||
// No op!
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - handle action view in timeline, it should have no effect`() = runTest {
|
||||
val messageEvent = aMessageEvent(
|
||||
|
||||
+67
@@ -55,12 +55,14 @@ import io.element.android.features.messages.impl.timeline.components.receipt.aRe
|
||||
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetEvent
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
|
||||
import io.element.android.features.messages.impl.timeline.sendfailure.SendFailureDialogState
|
||||
import io.element.android.features.roomcall.api.aStandByCallState
|
||||
import io.element.android.libraries.emoji.impl.picker.DefaultEmojiPickerRenderer
|
||||
import io.element.android.libraries.emoji.impl.picker.anEmojiPickerState
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.getAvatarUrl
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.getDisplayName
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
@@ -558,6 +560,71 @@ class MessagesViewTest : RobolectricTest() {
|
||||
eventsRecorder.assertSingle(TimelineEvent.ComputeVerifiedUserSendFailure(timelineItem))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on retry in the send failure dialog emits the expected Events`() = runAndroidComposeUiTest {
|
||||
val messagesEventsRecorder = EventsRecorder<MessagesEvent>()
|
||||
val timelineEventsRecorder = EventsRecorder<TimelineEvent>()
|
||||
val timelineItem = aTimelineItemEvent(sendState = LocalEventSendState.Failed.Unknown("Error"))
|
||||
setMessagesView(
|
||||
state = aMessagesState(
|
||||
timelineState = aTimelineState(
|
||||
sendFailureDialogState = SendFailureDialogState.Show(event = timelineItem, message = "Error"),
|
||||
eventSink = timelineEventsRecorder,
|
||||
),
|
||||
eventSink = messagesEventsRecorder,
|
||||
),
|
||||
)
|
||||
// Clear initial 'LoadMore' event emitted when setting the state
|
||||
timelineEventsRecorder.clear()
|
||||
|
||||
clickOn(CommonStrings.action_retry)
|
||||
messagesEventsRecorder.assertSingle(MessagesEvent.HandleAction(TimelineItemAction.RetrySending, timelineItem))
|
||||
timelineEventsRecorder.assertSingle(TimelineEvent.HideSendFailureDialog)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on remove message in the send failure dialog emits the expected Events`() = runAndroidComposeUiTest {
|
||||
val messagesEventsRecorder = EventsRecorder<MessagesEvent>()
|
||||
val timelineEventsRecorder = EventsRecorder<TimelineEvent>()
|
||||
val timelineItem = aTimelineItemEvent(sendState = LocalEventSendState.Failed.Unknown("Error"))
|
||||
setMessagesView(
|
||||
state = aMessagesState(
|
||||
timelineState = aTimelineState(
|
||||
sendFailureDialogState = SendFailureDialogState.Show(event = timelineItem, message = "Error"),
|
||||
eventSink = timelineEventsRecorder,
|
||||
),
|
||||
eventSink = messagesEventsRecorder,
|
||||
),
|
||||
)
|
||||
// Clear initial 'LoadMore' event emitted when setting the state
|
||||
timelineEventsRecorder.clear()
|
||||
|
||||
clickOn(CommonStrings.action_remove_message)
|
||||
messagesEventsRecorder.assertSingle(MessagesEvent.HandleAction(TimelineItemAction.Redact, timelineItem))
|
||||
timelineEventsRecorder.assertSingle(TimelineEvent.HideSendFailureDialog)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `cancelling the send failure dialog emits the expected Event`() = runAndroidComposeUiTest {
|
||||
val messagesEventsRecorder = EventsRecorder<MessagesEvent>(expectEvents = false)
|
||||
val timelineEventsRecorder = EventsRecorder<TimelineEvent>()
|
||||
val timelineItem = aTimelineItemEvent(sendState = LocalEventSendState.Failed.Unknown("Error"))
|
||||
setMessagesView(
|
||||
state = aMessagesState(
|
||||
timelineState = aTimelineState(
|
||||
sendFailureDialogState = SendFailureDialogState.Show(event = timelineItem, message = "Error"),
|
||||
eventSink = timelineEventsRecorder,
|
||||
),
|
||||
eventSink = messagesEventsRecorder,
|
||||
),
|
||||
)
|
||||
// Clear initial 'LoadMore' event emitted when setting the state
|
||||
timelineEventsRecorder.clear()
|
||||
|
||||
clickOn(CommonStrings.action_cancel)
|
||||
timelineEventsRecorder.assertSingle(TimelineEvent.HideSendFailureDialog)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on a custom emoji emits the expected Events`() = runAndroidComposeUiTest {
|
||||
val aUnicode = "🙈"
|
||||
|
||||
+109
-1
@@ -12,6 +12,8 @@ import app.cash.turbine.ReceiveTurbine
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.location.test.FakeActiveLiveLocationShareManager
|
||||
import io.element.android.features.messages.impl.FakeMessagesNavigator
|
||||
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureEvent
|
||||
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureState
|
||||
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.aResolveVerifiedUserSendFailureState
|
||||
import io.element.android.features.messages.impl.fixtures.aMessageEvent
|
||||
import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactoryCreator
|
||||
@@ -21,6 +23,7 @@ import io.element.android.features.messages.impl.timeline.model.NewEventState
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState
|
||||
import io.element.android.features.messages.impl.timeline.protection.aTimelineProtectionState
|
||||
import io.element.android.features.messages.impl.timeline.sendfailure.SendFailureDialogState
|
||||
import io.element.android.features.messages.impl.typing.aTypingNotificationState
|
||||
import io.element.android.features.messages.impl.voicemessages.timeline.FakeRedactedVoiceMessageManager
|
||||
import io.element.android.features.messages.impl.voicemessages.timeline.RedactedVoiceMessageManager
|
||||
@@ -45,6 +48,7 @@ import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.EventReaction
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.ReactionSender
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.Receipt
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.RedactedContent
|
||||
@@ -1390,6 +1394,109 @@ class TimelinePresenterTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - show and hide send failure dialog`() = runTest {
|
||||
val presenter = createTimelinePresenter()
|
||||
val event = aMessageEvent(sendState = LocalEventSendState.Failed.Unknown("An error"))
|
||||
presenter.test {
|
||||
val initialState = awaitFirstItem()
|
||||
assertThat(initialState.sendFailureDialogState).isEqualTo(SendFailureDialogState.Hidden)
|
||||
initialState.eventSink(TimelineEvent.ShowSendFailureDialog(event))
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.sendFailureDialogState).isEqualTo(
|
||||
SendFailureDialogState.Show(event = event, message = "An error")
|
||||
)
|
||||
state.eventSink(TimelineEvent.HideSendFailureDialog)
|
||||
}
|
||||
awaitItem().also { state ->
|
||||
assertThat(state.sendFailureDialogState).isEqualTo(SendFailureDialogState.Hidden)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - show send failure dialog - unknown error with a blank reason`() {
|
||||
assertSendFailureDialogMessage(
|
||||
sendState = LocalEventSendState.Failed.Unknown(error = ""),
|
||||
expectedMessage = "Unknown error",
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - show send failure dialog - invalid mime type`() {
|
||||
assertSendFailureDialogMessage(
|
||||
sendState = LocalEventSendState.Failed.InvalidMimeType(mimeType = "invalid/mimeType"),
|
||||
expectedMessage = "Invalid mime type: invalid/mimeType",
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - show send failure dialog - missing media content`() {
|
||||
assertSendFailureDialogMessage(
|
||||
sendState = LocalEventSendState.Failed.MissingMediaContent,
|
||||
expectedMessage = "Missing media content",
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - show send failure dialog - sending from unverified device`() {
|
||||
assertSendFailureDialogMessage(
|
||||
sendState = LocalEventSendState.Failed.SendingFromUnverifiedDevice,
|
||||
expectedMessage = "Sending from unverified device",
|
||||
)
|
||||
}
|
||||
|
||||
private fun assertSendFailureDialogMessage(
|
||||
sendState: LocalEventSendState.Failed,
|
||||
expectedMessage: String,
|
||||
) = runTest {
|
||||
val presenter = createTimelinePresenter()
|
||||
val event = aMessageEvent(sendState = sendState)
|
||||
presenter.test {
|
||||
val initialState = awaitFirstItem()
|
||||
initialState.eventSink(TimelineEvent.ShowSendFailureDialog(event))
|
||||
assertThat(awaitItem().sendFailureDialogState).isEqualTo(
|
||||
SendFailureDialogState.Show(event = event, message = expectedMessage)
|
||||
)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - show send failure dialog - verified user failure is delegated to the resolve presenter`() = runTest {
|
||||
val resolveEventSinkRecorder = lambdaRecorder<ResolveVerifiedUserSendFailureEvent, Unit> { }
|
||||
val presenter = createTimelinePresenter(
|
||||
resolveVerifiedUserSendFailurePresenter = {
|
||||
aResolveVerifiedUserSendFailureState(eventSink = resolveEventSinkRecorder)
|
||||
},
|
||||
)
|
||||
val event = aMessageEvent(
|
||||
sendState = LocalEventSendState.Failed.VerifiedUserChangedIdentity(users = listOf(A_USER_ID)),
|
||||
)
|
||||
presenter.test {
|
||||
val initialState = awaitFirstItem()
|
||||
initialState.eventSink(TimelineEvent.ShowSendFailureDialog(event))
|
||||
// The generic dialog is not displayed, the dedicated one is.
|
||||
assertThat(consumeItemsUntilTimeout()).isEmpty()
|
||||
assertThat(initialState.sendFailureDialogState).isEqualTo(SendFailureDialogState.Hidden)
|
||||
resolveEventSinkRecorder.assertions()
|
||||
.isCalledOnce()
|
||||
.with(value(ResolveVerifiedUserSendFailureEvent.ComputeForMessage(event)))
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - show send failure dialog - no effect if the event did not fail to send`() = runTest {
|
||||
val presenter = createTimelinePresenter()
|
||||
val event = aMessageEvent(sendState = LocalEventSendState.Sending.Event)
|
||||
presenter.test {
|
||||
val initialState = awaitFirstItem()
|
||||
initialState.eventSink(TimelineEvent.ShowSendFailureDialog(event))
|
||||
assertThat(consumeItemsUntilTimeout()).isEmpty()
|
||||
assertThat(initialState.sendFailureDialogState).isEqualTo(SendFailureDialogState.Hidden)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when room member info is loaded, read receipts info should be updated`() = runTest {
|
||||
val timeline = FakeTimeline(
|
||||
@@ -1589,6 +1696,7 @@ class TimelinePresenterTest {
|
||||
liveLocationShareManager: FakeActiveLiveLocationShareManager = FakeActiveLiveLocationShareManager(),
|
||||
markAsFullyRead: MarkAsFullyRead = FakeMarkAsFullyRead { _, _ -> },
|
||||
timelineProtectionPresenter: Presenter<TimelineProtectionState> = { aTimelineProtectionState() },
|
||||
resolveVerifiedUserSendFailurePresenter: Presenter<ResolveVerifiedUserSendFailureState> = { aResolveVerifiedUserSendFailureState() },
|
||||
): TimelinePresenter {
|
||||
return TimelinePresenter(
|
||||
timelineItemsFactoryCreator = aTimelineItemsFactoryCreator(),
|
||||
@@ -1602,7 +1710,7 @@ class TimelinePresenterTest {
|
||||
sessionPreferencesStore = sessionPreferencesStore,
|
||||
timelineItemIndexer = timelineItemIndexer,
|
||||
timelineController = TimelineController(room, timeline),
|
||||
resolveVerifiedUserSendFailurePresenter = { aResolveVerifiedUserSendFailureState() },
|
||||
resolveVerifiedUserSendFailurePresenter = resolveVerifiedUserSendFailurePresenter,
|
||||
typingNotificationPresenter = { aTypingNotificationState() },
|
||||
roomCallStatePresenter = { aStandByCallState() },
|
||||
featureFlagService = featureFlagService,
|
||||
|
||||
+41
@@ -33,6 +33,7 @@ import io.element.android.features.messages.impl.timeline.protection.aTimelinePr
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.UniqueId
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
|
||||
@@ -178,6 +179,46 @@ class TimelineViewTest : RobolectricTest() {
|
||||
eventsRecorder.assertSingle(TimelineEvent.HideShieldDialog)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on the send failure indicator emits the expected Event`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
||||
val timelineItem = aTimelineItemEvent(
|
||||
content = aTimelineItemTextContent(),
|
||||
sendState = LocalEventSendState.Failed.Unknown("Error"),
|
||||
)
|
||||
setTimelineView(
|
||||
state = aTimelineState(
|
||||
timelineItems = persistentListOf(timelineItem),
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
eventsRecorder.clear()
|
||||
|
||||
val contentDescription = activity!!.getString(CommonStrings.common_sending_failed)
|
||||
onNodeWithContentDescription(contentDescription).performClick()
|
||||
eventsRecorder.assertSingle(TimelineEvent.ShowSendFailureDialog(timelineItem))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on the send failure indicator of a verified user failure emits the expected Event`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
||||
val timelineItem = aTimelineItemEvent(
|
||||
content = aTimelineItemTextContent(),
|
||||
sendState = LocalEventSendState.Failed.VerifiedUserChangedIdentity(users = listOf(A_USER_ID)),
|
||||
)
|
||||
setTimelineView(
|
||||
state = aTimelineState(
|
||||
timelineItems = persistentListOf(timelineItem),
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
eventsRecorder.clear()
|
||||
|
||||
val contentDescription = activity!!.getString(CommonStrings.common_sending_failed)
|
||||
onNodeWithContentDescription(contentDescription).performClick()
|
||||
eventsRecorder.assertSingle(TimelineEvent.ShowSendFailureDialog(timelineItem))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on a reply to a loaded event emits the expected Event`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
||||
|
||||
+105
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Element Creations Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalTestApi::class)
|
||||
|
||||
package io.element.android.features.messages.impl.timeline.sendfailure
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||
import androidx.compose.ui.test.ExperimentalTestApi
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||
import io.element.android.tests.testutils.EnsureNeverCalledWithParam
|
||||
import io.element.android.tests.testutils.clickOn
|
||||
import io.element.android.tests.testutils.ensureCalledOnce
|
||||
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
||||
import io.element.android.tests.testutils.robolectric.RobolectricTest
|
||||
import io.element.android.tests.testutils.setSafeContent
|
||||
import org.junit.Test
|
||||
|
||||
class SendFailureDialogViewTest : RobolectricTest() {
|
||||
@Test
|
||||
fun `hidden state renders nothing`() = runAndroidComposeUiTest {
|
||||
setSendFailureDialogView(
|
||||
sendFailureDialogState = SendFailureDialogState.Hidden,
|
||||
)
|
||||
onNodeWithText(activity!!.getString(CommonStrings.common_sending_failed)).assertDoesNotExist()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `show state renders the failure reason`() = runAndroidComposeUiTest {
|
||||
setSendFailureDialogView(
|
||||
sendFailureDialogState = aSendFailureDialogStateShow(message = "A failure reason"),
|
||||
)
|
||||
onNodeWithText(activity!!.getString(CommonStrings.common_sending_failed)).assertExists()
|
||||
onNodeWithText("A failure reason").assertExists()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on retry invokes the expected callback`() = runAndroidComposeUiTest {
|
||||
val state = aSendFailureDialogStateShow()
|
||||
ensureCalledOnceWithParam(state.event) { callback ->
|
||||
setSendFailureDialogView(
|
||||
sendFailureDialogState = state,
|
||||
onRetry = callback,
|
||||
)
|
||||
clickOn(CommonStrings.action_retry)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on remove message invokes the expected callback`() = runAndroidComposeUiTest {
|
||||
val state = aSendFailureDialogStateShow()
|
||||
ensureCalledOnceWithParam(state.event) { callback ->
|
||||
setSendFailureDialogView(
|
||||
sendFailureDialogState = state,
|
||||
onRemoveMessage = callback,
|
||||
)
|
||||
clickOn(CommonStrings.action_remove_message)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on cancel invokes the expected callback`() = runAndroidComposeUiTest {
|
||||
ensureCalledOnce { callback ->
|
||||
setSendFailureDialogView(
|
||||
sendFailureDialogState = aSendFailureDialogStateShow(),
|
||||
onDismiss = callback,
|
||||
)
|
||||
clickOn(CommonStrings.action_cancel)
|
||||
}
|
||||
}
|
||||
|
||||
private fun aSendFailureDialogStateShow(
|
||||
message: String = "A failure reason",
|
||||
) = SendFailureDialogState.Show(
|
||||
event = aTimelineItemEvent(sendState = LocalEventSendState.Failed.Unknown(message)),
|
||||
message = message,
|
||||
)
|
||||
|
||||
private fun AndroidComposeUiTest<ComponentActivity>.setSendFailureDialogView(
|
||||
sendFailureDialogState: SendFailureDialogState,
|
||||
onDismiss: () -> Unit = EnsureNeverCalled(),
|
||||
onRetry: (TimelineItem.Event) -> Unit = EnsureNeverCalledWithParam(),
|
||||
onRemoveMessage: (TimelineItem.Event) -> Unit = EnsureNeverCalledWithParam(),
|
||||
) {
|
||||
setSafeContent {
|
||||
SendFailureDialogView(
|
||||
sendFailureDialogState = sendFailureDialogState,
|
||||
onDismiss = onDismiss,
|
||||
onRetry = onRetry,
|
||||
onRemoveMessage = onRemoveMessage,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user