Merge pull request #6092 from element-hq/feature/bma/endPollConfirmation

Display a confirmation dialog when ending a poll from the event bottom sheet
This commit is contained in:
Benoit Marty 2026-01-27 10:47:12 +01:00 committed by GitHub
commit cc4bf95cac
93 changed files with 646 additions and 863 deletions

View file

@ -13,13 +13,12 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.api.user.MatrixUser
sealed interface MessagesEvents { sealed interface MessagesEvent {
data class HandleAction(val action: TimelineItemAction, val event: TimelineItem.Event) : MessagesEvents data class HandleAction(val action: TimelineItemAction, val event: TimelineItem.Event) : MessagesEvent
data class ToggleReaction(val emoji: String, val eventOrTransactionId: EventOrTransactionId) : MessagesEvents data class ToggleReaction(val emoji: String, val eventOrTransactionId: EventOrTransactionId) : MessagesEvent
data class InviteDialogDismissed(val action: InviteDialogAction) : MessagesEvents data class InviteDialogDismissed(val action: InviteDialogAction) : MessagesEvent
data class OnUserClicked(val user: MatrixUser) : MessagesEvents data class OnUserClicked(val user: MatrixUser) : MessagesEvent
data object Dismiss : MessagesEvents data object MarkAsFullyReadAndExit : MessagesEvent
data object MarkAsFullyReadAndExit : MessagesEvents
} }
enum class InviteDialogAction { enum class InviteDialogAction {

View file

@ -36,7 +36,7 @@ import io.element.android.features.messages.impl.attachments.Attachment
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvent import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvent
import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter
import io.element.android.features.messages.impl.timeline.TimelineController import io.element.android.features.messages.impl.timeline.TimelineController
import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.TimelineEvent
import io.element.android.features.messages.impl.timeline.TimelinePresenter import io.element.android.features.messages.impl.timeline.TimelinePresenter
import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories
import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactories import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactories
@ -151,7 +151,7 @@ class MessagesNode(
activity: Activity, activity: Activity,
darkTheme: Boolean, darkTheme: Boolean,
url: String, url: String,
eventSink: (TimelineEvents) -> Unit, eventSink: (TimelineEvent) -> Unit,
customTab: Boolean customTab: Boolean
) { ) {
when (val permalink = permalinkParser.parse(url)) { when (val permalink = permalinkParser.parse(url)) {
@ -178,12 +178,12 @@ class MessagesNode(
private fun handleRoomLinkClick( private fun handleRoomLinkClick(
roomLink: PermalinkData.RoomLink, roomLink: PermalinkData.RoomLink,
eventSink: (TimelineEvents) -> Unit, eventSink: (TimelineEvent) -> Unit,
) { ) {
if (room.matches(roomLink.roomIdOrAlias)) { if (room.matches(roomLink.roomIdOrAlias)) {
val eventId = roomLink.eventId val eventId = roomLink.eventId
if (eventId != null) { if (eventId != null) {
eventSink(TimelineEvents.FocusOnEvent(eventId)) eventSink(TimelineEvent.FocusOnEvent(eventId))
} else { } else {
// Click on the same room, ignore // Click on the same room, ignore
displaySameRoomToast() displaySameRoomToast()
@ -242,7 +242,7 @@ class MessagesNode(
val state = presenter.present() val state = presenter.present()
BackHandler { BackHandler {
state.eventSink(MessagesEvents.MarkAsFullyReadAndExit) state.eventSink(MessagesEvent.MarkAsFullyReadAndExit)
} }
OnLifecycleEvent { _, event -> OnLifecycleEvent { _, event ->
@ -253,7 +253,7 @@ class MessagesNode(
} }
MessagesView( MessagesView(
state = state, state = state,
onBackClick = { state.eventSink(MessagesEvents.MarkAsFullyReadAndExit) }, onBackClick = { state.eventSink(MessagesEvent.MarkAsFullyReadAndExit) },
onRoomDetailsClick = callback::navigateToRoomDetails, onRoomDetailsClick = callback::navigateToRoomDetails,
onEventContentClick = { isLive, event -> onEventContentClick = { isLive, event ->
if (isLive) { if (isLive) {
@ -305,7 +305,7 @@ class MessagesNode(
} }
LaunchedEffect(focusedEventId) { LaunchedEffect(focusedEventId) {
if (focusedEventId != null) { if (focusedEventId != null) {
state.timelineState.eventSink(TimelineEvents.FocusOnEvent(focusedEventId!!)) state.timelineState.eventSink(TimelineEvent.FocusOnEvent(focusedEventId!!))
focusedEventId = null focusedEventId = null
} }
} }

View file

@ -27,7 +27,6 @@ import dev.zacsweers.metro.AssistedInject
import im.vector.app.features.analytics.plan.PinUnpinAction import im.vector.app.features.analytics.plan.PinUnpinAction
import io.element.android.appconfig.MessageComposerConfig import io.element.android.appconfig.MessageComposerConfig
import io.element.android.features.messages.api.timeline.HtmlConverterProvider import io.element.android.features.messages.api.timeline.HtmlConverterProvider
import io.element.android.features.messages.impl.actionlist.ActionListEvents
import io.element.android.features.messages.impl.actionlist.ActionListState import io.element.android.features.messages.impl.actionlist.ActionListState
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleState import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleState
@ -38,7 +37,7 @@ import io.element.android.features.messages.impl.messagecomposer.MessageComposer
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerState import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerState
import io.element.android.features.messages.impl.timeline.MarkAsFullyRead import io.element.android.features.messages.impl.timeline.MarkAsFullyRead
import io.element.android.features.messages.impl.timeline.TimelineController import io.element.android.features.messages.impl.timeline.TimelineController
import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.TimelineEvent
import io.element.android.features.messages.impl.timeline.TimelineState import io.element.android.features.messages.impl.timeline.TimelineState
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryState import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryState
@ -221,9 +220,9 @@ class MessagesPresenter(
onPauseOrDispose {} onPauseOrDispose {}
} }
fun handleEvent(event: MessagesEvents) { fun handleEvent(event: MessagesEvent) {
when (event) { when (event) {
is MessagesEvents.HandleAction -> { is MessagesEvent.HandleAction -> {
localCoroutineScope.handleTimelineAction( localCoroutineScope.handleTimelineAction(
action = event.action, action = event.action,
targetEvent = event.event, targetEvent = event.event,
@ -233,21 +232,20 @@ class MessagesPresenter(
timelineProtectionState = timelineProtectionState, timelineProtectionState = timelineProtectionState,
) )
} }
is MessagesEvents.ToggleReaction -> { is MessagesEvent.ToggleReaction -> {
localCoroutineScope.toggleReaction(event.emoji, event.eventOrTransactionId) localCoroutineScope.toggleReaction(event.emoji, event.eventOrTransactionId)
} }
is MessagesEvents.InviteDialogDismissed -> { is MessagesEvent.InviteDialogDismissed -> {
hasDismissedInviteDialog = true hasDismissedInviteDialog = true
if (event.action == InviteDialogAction.Invite) { if (event.action == InviteDialogAction.Invite) {
localCoroutineScope.reinviteOtherUser(inviteProgress) localCoroutineScope.reinviteOtherUser(inviteProgress)
} }
} }
is MessagesEvents.Dismiss -> actionListState.eventSink(ActionListEvents.Clear) is MessagesEvent.OnUserClicked -> {
is MessagesEvents.OnUserClicked -> {
roomMemberModerationState.eventSink(RoomMemberModerationEvents.ShowActionsForUser(event.user)) roomMemberModerationState.eventSink(RoomMemberModerationEvents.ShowActionsForUser(event.user))
} }
is MessagesEvents.MarkAsFullyReadAndExit -> coroutineScope.launch { is MessagesEvent.MarkAsFullyReadAndExit -> coroutineScope.launch {
if (!markingAsReadAndExiting.getAndSet(true)) { if (!markingAsReadAndExiting.getAndSet(true)) {
val latestEventId = room.liveTimeline.getLatestEventId().getOrElse { val latestEventId = room.liveTimeline.getLatestEventId().getOrElse {
Timber.w(it, "Failed to get latest event id to mark as fully read") Timber.w(it, "Failed to get latest event id to mark as fully read")
@ -529,7 +527,7 @@ class MessagesPresenter(
event: TimelineItem.Event, event: TimelineItem.Event,
timelineState: TimelineState, timelineState: TimelineState,
) { ) {
event.eventId?.let { timelineState.eventSink(TimelineEvents.EndPoll(it)) } event.eventId?.let { timelineState.eventSink(TimelineEvent.EndPoll(it)) }
} }
private suspend fun handleCopyLink(event: TimelineItem.Event) { private suspend fun handleCopyLink(event: TimelineItem.Event) {

View file

@ -57,7 +57,7 @@ data class MessagesState(
val dmUserVerificationState: IdentityState?, val dmUserVerificationState: IdentityState?,
val roomMemberModerationState: RoomMemberModerationState, val roomMemberModerationState: RoomMemberModerationState,
val successorRoom: SuccessorRoom?, val successorRoom: SuccessorRoom?,
val eventSink: (MessagesEvents) -> Unit val eventSink: (MessagesEvent) -> Unit
) { ) {
val isTombstoned = successorRoom != null val isTombstoned = successorRoom != null
} }

View file

@ -28,11 +28,11 @@ import io.element.android.features.messages.impl.pinned.banner.aLoadedPinnedMess
import io.element.android.features.messages.impl.timeline.TimelineState import io.element.android.features.messages.impl.timeline.TimelineState
import io.element.android.features.messages.impl.timeline.aTimelineItemList import io.element.android.features.messages.impl.timeline.aTimelineItemList
import io.element.android.features.messages.impl.timeline.aTimelineState import io.element.android.features.messages.impl.timeline.aTimelineState
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvents import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvent
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryEvents import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryEvent
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryState import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryState
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetEvents import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetEvent
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetState import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetState
import io.element.android.features.messages.impl.timeline.model.TimelineItem 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.model.event.aTimelineItemTextContent
@ -133,7 +133,7 @@ fun aMessagesState(
dmUserVerificationState: IdentityState? = null, dmUserVerificationState: IdentityState? = null,
roomMemberModerationState: RoomMemberModerationState = aRoomMemberModerationState(), roomMemberModerationState: RoomMemberModerationState = aRoomMemberModerationState(),
successorRoom: SuccessorRoom? = null, successorRoom: SuccessorRoom? = null,
eventSink: (MessagesEvents) -> Unit = {}, eventSink: (MessagesEvent) -> Unit = {},
) = MessagesState( ) = MessagesState(
roomId = RoomId("!id:domain"), roomId = RoomId("!id:domain"),
roomName = roomName, roomName = roomName,
@ -187,7 +187,7 @@ fun aUserEventPermissions(
fun aReactionSummaryState( fun aReactionSummaryState(
target: ReactionSummaryState.Summary? = null, target: ReactionSummaryState.Summary? = null,
eventSink: (ReactionSummaryEvents) -> Unit = {} eventSink: (ReactionSummaryEvent) -> Unit = {}
) = ReactionSummaryState( ) = ReactionSummaryState(
target = target, target = target,
eventSink = eventSink, eventSink = eventSink,
@ -196,7 +196,7 @@ fun aReactionSummaryState(
fun aCustomReactionState( fun aCustomReactionState(
target: CustomReactionState.Target = CustomReactionState.Target.None, target: CustomReactionState.Target = CustomReactionState.Target.None,
recentEmojis: ImmutableList<String> = persistentListOf(), recentEmojis: ImmutableList<String> = persistentListOf(),
eventSink: (CustomReactionEvents) -> Unit = {}, eventSink: (CustomReactionEvent) -> Unit = {},
) = CustomReactionState( ) = CustomReactionState(
target = target, target = target,
recentEmojis = recentEmojis, recentEmojis = recentEmojis,
@ -206,7 +206,7 @@ fun aCustomReactionState(
fun aReadReceiptBottomSheetState( fun aReadReceiptBottomSheetState(
selectedEvent: TimelineItem.Event? = null, selectedEvent: TimelineItem.Event? = null,
eventSink: (ReadReceiptBottomSheetEvents) -> Unit = {}, eventSink: (ReadReceiptBottomSheetEvent) -> Unit = {},
) = ReadReceiptBottomSheetState( ) = ReadReceiptBottomSheetState(
selectedEvent = selectedEvent, selectedEvent = selectedEvent,
eventSink = eventSink, eventSink = eventSink,

View file

@ -31,6 +31,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@ -51,12 +52,12 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerEvent import io.element.android.features.messages.api.timeline.voicemessages.composer.VoiceMessageComposerEvent
import io.element.android.features.messages.impl.actionlist.ActionListEvents import io.element.android.features.messages.impl.actionlist.ActionListEvent
import io.element.android.features.messages.impl.actionlist.ActionListView import io.element.android.features.messages.impl.actionlist.ActionListView
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleStateView import io.element.android.features.messages.impl.crypto.historyvisible.HistoryVisibleStateView
import io.element.android.features.messages.impl.crypto.identity.IdentityChangeStateView import io.element.android.features.messages.impl.crypto.identity.IdentityChangeStateView
import io.element.android.features.messages.impl.link.LinkEvents import io.element.android.features.messages.impl.link.LinkEvent
import io.element.android.features.messages.impl.link.LinkView import io.element.android.features.messages.impl.link.LinkView
import io.element.android.features.messages.impl.messagecomposer.AttachmentsBottomSheet import io.element.android.features.messages.impl.messagecomposer.AttachmentsBottomSheet
import io.element.android.features.messages.impl.messagecomposer.DisabledComposerView import io.element.android.features.messages.impl.messagecomposer.DisabledComposerView
@ -67,18 +68,18 @@ import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBan
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerView import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerView
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerViewDefaults import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerViewDefaults
import io.element.android.features.messages.impl.timeline.FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS import io.element.android.features.messages.impl.timeline.FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS
import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.TimelineEvent
import io.element.android.features.messages.impl.timeline.TimelineView import io.element.android.features.messages.impl.timeline.TimelineView
import io.element.android.features.messages.impl.timeline.aGroupedEvents import io.element.android.features.messages.impl.timeline.aGroupedEvents
import io.element.android.features.messages.impl.timeline.aTimelineItemDaySeparator import io.element.android.features.messages.impl.timeline.aTimelineItemDaySeparator
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
import io.element.android.features.messages.impl.timeline.aTimelineState import io.element.android.features.messages.impl.timeline.aTimelineState
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionBottomSheet import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionBottomSheet
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvents import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvent
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryEvents import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryEvent
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryView import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryView
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheet import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheet
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetEvents 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.TimelineItem
import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent
@ -168,7 +169,7 @@ fun MessagesView(
Timber.v("OnMessageLongClicked= ${event.id}") Timber.v("OnMessageLongClicked= ${event.id}")
hidingKeyboard { hidingKeyboard {
state.actionListState.eventSink( state.actionListState.eventSink(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = event, event = event,
userEventPermissions = state.userEventPermissions, userEventPermissions = state.userEventPermissions,
) )
@ -177,20 +178,20 @@ fun MessagesView(
} }
fun onActionSelected(action: TimelineItemAction, event: TimelineItem.Event) { fun onActionSelected(action: TimelineItemAction, event: TimelineItem.Event) {
state.eventSink(MessagesEvents.HandleAction(action, event)) state.eventSink(MessagesEvent.HandleAction(action, event))
} }
fun onEmojiReactionClick(emoji: String, event: TimelineItem.Event) { fun onEmojiReactionClick(emoji: String, event: TimelineItem.Event) {
state.eventSink(MessagesEvents.ToggleReaction(emoji, event.eventOrTransactionId)) state.eventSink(MessagesEvent.ToggleReaction(emoji, event.eventOrTransactionId))
} }
fun onEmojiReactionLongClick(emoji: String, event: TimelineItem.Event) { fun onEmojiReactionLongClick(emoji: String, event: TimelineItem.Event) {
if (event.eventId == null) return if (event.eventId == null) return
state.reactionSummaryState.eventSink(ReactionSummaryEvents.ShowReactionSummary(event.eventId, event.reactionsState.reactions, emoji)) state.reactionSummaryState.eventSink(ReactionSummaryEvent.ShowReactionSummary(event.eventId, event.reactionsState.reactions, emoji))
} }
fun onMoreReactionsClick(event: TimelineItem.Event) { fun onMoreReactionsClick(event: TimelineItem.Event) {
state.customReactionState.eventSink(CustomReactionEvents.ShowCustomReactionSheet(event)) state.customReactionState.eventSink(CustomReactionEvent.ShowCustomReactionSheet(event))
} }
val expandableState = rememberExpandableBottomSheetLayoutState() val expandableState = rememberExpandableBottomSheetLayoutState()
@ -243,7 +244,7 @@ fun MessagesView(
onMessageLongClick = ::onMessageLongClick, onMessageLongClick = ::onMessageLongClick,
onUserDataClick = { onUserDataClick = {
hidingKeyboard { hidingKeyboard {
state.eventSink(MessagesEvents.OnUserClicked(it)) state.eventSink(MessagesEvent.OnUserClicked(it))
} }
}, },
onLinkClick = { link, customTab -> onLinkClick = { link, customTab ->
@ -251,19 +252,19 @@ fun MessagesView(
onLinkClick(link.url, true) onLinkClick(link.url, true)
// Do not check those links, they are internal link only // Do not check those links, they are internal link only
} else { } else {
state.linkState.eventSink(LinkEvents.OnLinkClick(link)) state.linkState.eventSink(LinkEvent.OnLinkClick(link))
} }
}, },
onReactionClick = ::onEmojiReactionClick, onReactionClick = ::onEmojiReactionClick,
onReactionLongClick = ::onEmojiReactionLongClick, onReactionLongClick = ::onEmojiReactionLongClick,
onMoreReactionsClick = ::onMoreReactionsClick, onMoreReactionsClick = ::onMoreReactionsClick,
onReadReceiptClick = { event -> onReadReceiptClick = { event ->
state.readReceiptBottomSheetState.eventSink(ReadReceiptBottomSheetEvents.EventSelected(event)) state.readReceiptBottomSheetState.eventSink(ReadReceiptBottomSheetEvent.EventSelected(event))
}, },
onSendLocationClick = onSendLocationClick, onSendLocationClick = onSendLocationClick,
onCreatePollClick = onCreatePollClick, onCreatePollClick = onCreatePollClick,
onSwipeToReply = { targetEvent -> onSwipeToReply = { targetEvent ->
state.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, targetEvent)) state.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Reply, targetEvent))
}, },
forceJumpToBottomVisibility = forceJumpToBottomVisibility, forceJumpToBottomVisibility = forceJumpToBottomVisibility,
onJoinCallClick = onJoinCallClick, onJoinCallClick = onJoinCallClick,
@ -300,7 +301,7 @@ fun MessagesView(
state = state, state = state,
onLinkClick = { url, customTab -> onLinkClick(url, customTab) }, onLinkClick = { url, customTab -> onLinkClick(url, customTab) },
onRoomSuccessorClick = { roomId -> onRoomSuccessorClick = { roomId ->
state.timelineState.eventSink(TimelineEvents.NavigateToPredecessorOrSuccessorRoom(roomId = roomId)) state.timelineState.eventSink(TimelineEvent.NavigateToPredecessorOrSuccessorRoom(roomId = roomId))
}, },
) )
}, },
@ -341,22 +342,43 @@ fun MessagesView(
maxBottomSheetContentHeight = maxComposerHeightPx.toDp(), maxBottomSheetContentHeight = maxComposerHeightPx.toDp(),
) )
var endPollConfirmingEvent: TimelineItem.Event? by remember { mutableStateOf(null) }
if (endPollConfirmingEvent != null) {
ConfirmationDialog(
content = stringResource(id = CommonStrings.common_poll_end_confirmation),
onSubmitClick = {
endPollConfirmingEvent?.let { event ->
onActionSelected(TimelineItemAction.EndPoll, event)
}
endPollConfirmingEvent = null
},
onDismiss = { endPollConfirmingEvent = null },
)
}
ActionListView( ActionListView(
state = state.actionListState, state = state.actionListState,
onSelectAction = ::onActionSelected, onSelectAction = { action: TimelineItemAction, event: TimelineItem.Event ->
if (action == TimelineItemAction.EndPoll) {
endPollConfirmingEvent = event
} else {
onActionSelected(action, event)
}
},
onCustomReactionClick = { event -> onCustomReactionClick = { event ->
state.customReactionState.eventSink(CustomReactionEvents.ShowCustomReactionSheet(event)) state.customReactionState.eventSink(CustomReactionEvent.ShowCustomReactionSheet(event))
}, },
onEmojiReactionClick = ::onEmojiReactionClick, onEmojiReactionClick = ::onEmojiReactionClick,
onVerifiedUserSendFailureClick = { event -> onVerifiedUserSendFailureClick = { event ->
state.timelineState.eventSink(TimelineEvents.ComputeVerifiedUserSendFailure(event)) state.timelineState.eventSink(TimelineEvent.ComputeVerifiedUserSendFailure(event))
}, },
) )
CustomReactionBottomSheet( CustomReactionBottomSheet(
state = state.customReactionState, state = state.customReactionState,
onSelectEmoji = { uniqueId, emoji -> onSelectEmoji = { uniqueId, emoji ->
state.eventSink(MessagesEvents.ToggleReaction(emoji.unicode, uniqueId)) state.eventSink(MessagesEvent.ToggleReaction(emoji.unicode, uniqueId))
} }
) )
@ -382,8 +404,8 @@ private fun ReinviteDialog(state: MessagesState) {
content = stringResource(id = R.string.screen_room_invite_again_alert_message), content = stringResource(id = R.string.screen_room_invite_again_alert_message),
cancelText = stringResource(id = CommonStrings.action_cancel), cancelText = stringResource(id = CommonStrings.action_cancel),
submitText = stringResource(id = CommonStrings.action_invite), submitText = stringResource(id = CommonStrings.action_invite),
onSubmitClick = { state.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Invite)) }, onSubmitClick = { state.eventSink(MessagesEvent.InviteDialogDismissed(InviteDialogAction.Invite)) },
onDismiss = { state.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Cancel)) } onDismiss = { state.eventSink(MessagesEvent.InviteDialogDismissed(InviteDialogAction.Cancel)) }
) )
} }
} }
@ -467,7 +489,7 @@ private fun MessagesViewContent(
) { ) {
fun focusOnPinnedEvent(eventId: EventId) { fun focusOnPinnedEvent(eventId: EventId) {
state.timelineState.eventSink( state.timelineState.eventSink(
TimelineEvents.FocusOnEvent(eventId = eventId, debounce = FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS.milliseconds) TimelineEvent.FocusOnEvent(eventId = eventId, debounce = FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS.milliseconds)
) )
} }
PinnedMessagesBannerView( PinnedMessagesBannerView(

View file

@ -11,10 +11,10 @@ package io.element.android.features.messages.impl.actionlist
import io.element.android.features.messages.impl.UserEventPermissions import io.element.android.features.messages.impl.UserEventPermissions
import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItem
sealed interface ActionListEvents { sealed interface ActionListEvent {
data object Clear : ActionListEvents data object Clear : ActionListEvent
data class ComputeForMessage( data class ComputeForMessage(
val event: TimelineItem.Event, val event: TimelineItem.Event,
val userEventPermissions: UserEventPermissions, val userEventPermissions: UserEventPermissions,
) : ActionListEvents ) : ActionListEvent
} }

View file

@ -107,10 +107,10 @@ class DefaultActionListPresenter(
val isThreadsEnabled = featureFlagService.isFeatureEnabledFlow(FeatureFlags.Threads).collectAsState(false) val isThreadsEnabled = featureFlagService.isFeatureEnabledFlow(FeatureFlags.Threads).collectAsState(false)
fun handleEvent(event: ActionListEvents) { fun handleEvent(event: ActionListEvent) {
when (event) { when (event) {
ActionListEvents.Clear -> target.value = ActionListState.Target.None ActionListEvent.Clear -> target.value = ActionListState.Target.None
is ActionListEvents.ComputeForMessage -> localCoroutineScope.computeForMessage( is ActionListEvent.ComputeForMessage -> localCoroutineScope.computeForMessage(
timelineItem = event.event, timelineItem = event.event,
usersEventPermissions = event.userEventPermissions, usersEventPermissions = event.userEventPermissions,
isDeveloperModeEnabled = isDeveloperModeEnabled, isDeveloperModeEnabled = isDeveloperModeEnabled,

View file

@ -16,7 +16,7 @@ import kotlinx.collections.immutable.ImmutableList
data class ActionListState( data class ActionListState(
val target: Target, val target: Target,
val eventSink: (ActionListEvents) -> Unit, val eventSink: (ActionListEvent) -> Unit,
) { ) {
@Immutable @Immutable
sealed interface Target { sealed interface Target {

View file

@ -192,7 +192,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
fun anActionListState( fun anActionListState(
target: ActionListState.Target = ActionListState.Target.None, target: ActionListState.Target = ActionListState.Target.None,
eventSink: (ActionListEvents) -> Unit = {}, eventSink: (ActionListEvent) -> Unit = {},
) = ActionListState( ) = ActionListState(
target = target, target = target,
eventSink = eventSink eventSink = eventSink

View file

@ -118,7 +118,7 @@ fun ActionListView(
) { ) {
if (targetItem == null) return if (targetItem == null) return
sheetState.hide(coroutineScope) { sheetState.hide(coroutineScope) {
state.eventSink(ActionListEvents.Clear) state.eventSink(ActionListEvent.Clear)
onSelectAction(itemAction, targetItem) onSelectAction(itemAction, targetItem)
} }
} }
@ -126,7 +126,7 @@ fun ActionListView(
fun onEmojiReactionClick(emoji: String) { fun onEmojiReactionClick(emoji: String) {
if (targetItem == null) return if (targetItem == null) return
sheetState.hide(coroutineScope) { sheetState.hide(coroutineScope) {
state.eventSink(ActionListEvents.Clear) state.eventSink(ActionListEvent.Clear)
onEmojiReactionClick(emoji, targetItem) onEmojiReactionClick(emoji, targetItem)
} }
} }
@ -134,19 +134,19 @@ fun ActionListView(
fun onCustomReactionClick() { fun onCustomReactionClick() {
if (targetItem == null) return if (targetItem == null) return
sheetState.hide(coroutineScope) { sheetState.hide(coroutineScope) {
state.eventSink(ActionListEvents.Clear) state.eventSink(ActionListEvent.Clear)
onCustomReactionClick(targetItem) onCustomReactionClick(targetItem)
} }
} }
fun onDismiss() { fun onDismiss() {
state.eventSink(ActionListEvents.Clear) state.eventSink(ActionListEvent.Clear)
} }
fun onVerifiedUserSendFailureClick() { fun onVerifiedUserSendFailureClick() {
if (targetItem == null) return if (targetItem == null) return
sheetState.hide(coroutineScope) { sheetState.hide(coroutineScope) {
state.eventSink(ActionListEvents.Clear) state.eventSink(ActionListEvent.Clear)
onVerifiedUserSendFailureClick(targetItem) onVerifiedUserSendFailureClick(targetItem)
} }
} }

View file

@ -8,8 +8,8 @@
package io.element.android.features.messages.impl.attachments.preview package io.element.android.features.messages.impl.attachments.preview
sealed interface AttachmentsPreviewEvents { sealed interface AttachmentsPreviewEvent {
data object SendAttachment : AttachmentsPreviewEvents data object SendAttachment : AttachmentsPreviewEvent
data object CancelAndDismiss : AttachmentsPreviewEvents data object CancelAndDismiss : AttachmentsPreviewEvent
data object CancelAndClearSendState : AttachmentsPreviewEvents data object CancelAndClearSendState : AttachmentsPreviewEvent
} }

View file

@ -140,9 +140,9 @@ class AttachmentsPreviewPresenter(
} }
} }
fun handleEvent(event: AttachmentsPreviewEvents) { fun handleEvent(event: AttachmentsPreviewEvent) {
when (event) { when (event) {
is AttachmentsPreviewEvents.SendAttachment -> { is AttachmentsPreviewEvent.SendAttachment -> {
ongoingSendAttachmentJob.value = coroutineScope.launch { ongoingSendAttachmentJob.value = coroutineScope.launch {
// If the media optimization selector is displayed, we need to wait for the user to select the options // If the media optimization selector is displayed, we need to wait for the user to select the options
// before we can pre-process the media. // before we can pre-process the media.
@ -191,7 +191,7 @@ class AttachmentsPreviewPresenter(
} }
} }
} }
AttachmentsPreviewEvents.CancelAndDismiss -> { AttachmentsPreviewEvent.CancelAndDismiss -> {
displayFileTooLargeError = false displayFileTooLargeError = false
// Cancel media preprocessing and sending // Cancel media preprocessing and sending
@ -206,7 +206,7 @@ class AttachmentsPreviewPresenter(
sendActionState, sendActionState,
) )
} }
AttachmentsPreviewEvents.CancelAndClearSendState -> { AttachmentsPreviewEvent.CancelAndClearSendState -> {
// Cancel media sending // Cancel media sending
ongoingSendAttachmentJob.value?.let { ongoingSendAttachmentJob.value?.let {
it.cancel() it.cancel()

View file

@ -20,7 +20,7 @@ data class AttachmentsPreviewState(
val textEditorState: TextEditorState, val textEditorState: TextEditorState,
val mediaOptimizationSelectorState: MediaOptimizationSelectorState, val mediaOptimizationSelectorState: MediaOptimizationSelectorState,
val displayFileTooLargeError: Boolean, val displayFileTooLargeError: Boolean,
val eventSink: (AttachmentsPreviewEvents) -> Unit val eventSink: (AttachmentsPreviewEvent) -> Unit,
) )
@Immutable @Immutable

View file

@ -82,15 +82,15 @@ fun AttachmentsPreviewView(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
fun postSendAttachment() { fun postSendAttachment() {
state.eventSink(AttachmentsPreviewEvents.SendAttachment) state.eventSink(AttachmentsPreviewEvent.SendAttachment)
} }
fun postCancel() { fun postCancel() {
state.eventSink(AttachmentsPreviewEvents.CancelAndDismiss) state.eventSink(AttachmentsPreviewEvent.CancelAndDismiss)
} }
fun postClearSendState() { fun postClearSendState() {
state.eventSink(AttachmentsPreviewEvents.CancelAndClearSendState) state.eventSink(AttachmentsPreviewEvent.CancelAndClearSendState)
} }
BackHandler(enabled = state.sendActionState !is SendActionState.Sending.Uploading && state.sendActionState !is SendActionState.Done) { BackHandler(enabled = state.sendActionState !is SendActionState.Sending.Uploading && state.sendActionState !is SendActionState.Done) {
@ -199,7 +199,7 @@ private fun AttachmentPreviewContent(
AlertDialog( AlertDialog(
title = stringResource(CommonStrings.dialog_file_too_large_to_upload_title), title = stringResource(CommonStrings.dialog_file_too_large_to_upload_title),
content = content, content = content,
onDismiss = { state.eventSink(AttachmentsPreviewEvents.CancelAndDismiss) }, onDismiss = { state.eventSink(AttachmentsPreviewEvent.CancelAndDismiss) },
) )
} }
} }

View file

@ -10,12 +10,12 @@ package io.element.android.features.messages.impl.crypto.sendfailure.resolve
import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItem
sealed interface ResolveVerifiedUserSendFailureEvents { sealed interface ResolveVerifiedUserSendFailureEvent {
data class ComputeForMessage( data class ComputeForMessage(
val messageEvent: TimelineItem.Event, val messageEvent: TimelineItem.Event,
) : ResolveVerifiedUserSendFailureEvents ) : ResolveVerifiedUserSendFailureEvent
data object ResolveAndResend : ResolveVerifiedUserSendFailureEvents data object ResolveAndResend : ResolveVerifiedUserSendFailureEvent
data object Retry : ResolveVerifiedUserSendFailureEvents data object Retry : ResolveVerifiedUserSendFailureEvent
data object Dismiss : ResolveVerifiedUserSendFailureEvents data object Dismiss : ResolveVerifiedUserSendFailureEvent
} }

View file

@ -48,9 +48,9 @@ class ResolveVerifiedUserSendFailurePresenter(
} }
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
fun handleEvent(event: ResolveVerifiedUserSendFailureEvents) { fun handleEvent(event: ResolveVerifiedUserSendFailureEvent) {
when (event) { when (event) {
is ResolveVerifiedUserSendFailureEvents.ComputeForMessage -> { is ResolveVerifiedUserSendFailureEvent.ComputeForMessage -> {
val sendState = event.messageEvent.localSendState as? LocalEventSendState.Failed.VerifiedUser val sendState = event.messageEvent.localSendState as? LocalEventSendState.Failed.VerifiedUser
val transactionId = event.messageEvent.transactionId val transactionId = event.messageEvent.transactionId
val sendHandle = event.messageEvent.sendhandle val sendHandle = event.messageEvent.sendhandle
@ -65,10 +65,10 @@ class ResolveVerifiedUserSendFailurePresenter(
null null
} }
} }
ResolveVerifiedUserSendFailureEvents.Dismiss -> { ResolveVerifiedUserSendFailureEvent.Dismiss -> {
resolver = null resolver = null
} }
ResolveVerifiedUserSendFailureEvents.Retry -> { ResolveVerifiedUserSendFailureEvent.Retry -> {
coroutineScope.launch { coroutineScope.launch {
resolver?.run { resolver?.run {
runUpdatingState(retryAction) { runUpdatingState(retryAction) {
@ -77,7 +77,7 @@ class ResolveVerifiedUserSendFailurePresenter(
} }
} }
} }
ResolveVerifiedUserSendFailureEvents.ResolveAndResend -> { ResolveVerifiedUserSendFailureEvent.ResolveAndResend -> {
coroutineScope.launch { coroutineScope.launch {
resolver?.run { resolver?.run {
runUpdatingState(resolveAction) { runUpdatingState(resolveAction) {

View file

@ -15,5 +15,5 @@ data class ResolveVerifiedUserSendFailureState(
val verifiedUserSendFailure: VerifiedUserSendFailure, val verifiedUserSendFailure: VerifiedUserSendFailure,
val resolveAction: AsyncAction<Unit>, val resolveAction: AsyncAction<Unit>,
val retryAction: AsyncAction<Unit>, val retryAction: AsyncAction<Unit>,
val eventSink: (ResolveVerifiedUserSendFailureEvents) -> Unit val eventSink: (ResolveVerifiedUserSendFailureEvent) -> Unit
) )

View file

@ -29,7 +29,7 @@ fun aResolveVerifiedUserSendFailureState(
verifiedUserSendFailure: VerifiedUserSendFailure = VerifiedUserSendFailure.None, verifiedUserSendFailure: VerifiedUserSendFailure = VerifiedUserSendFailure.None,
resolveAction: AsyncAction<Unit> = AsyncAction.Uninitialized, resolveAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
retryAction: AsyncAction<Unit> = AsyncAction.Uninitialized, retryAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
eventSink: (ResolveVerifiedUserSendFailureEvents) -> Unit = {} eventSink: (ResolveVerifiedUserSendFailureEvent) -> Unit = {}
) = ResolveVerifiedUserSendFailureState( ) = ResolveVerifiedUserSendFailureState(
verifiedUserSendFailure = verifiedUserSendFailure, verifiedUserSendFailure = verifiedUserSendFailure,
resolveAction = resolveAction, resolveAction = resolveAction,

View file

@ -47,15 +47,15 @@ fun ResolveVerifiedUserSendFailureView(
var showSheet by remember { mutableStateOf(false) } var showSheet by remember { mutableStateOf(false) }
fun dismiss() { fun dismiss() {
state.eventSink(ResolveVerifiedUserSendFailureEvents.Dismiss) state.eventSink(ResolveVerifiedUserSendFailureEvent.Dismiss)
} }
fun onRetryClick() { fun onRetryClick() {
state.eventSink(ResolveVerifiedUserSendFailureEvents.Retry) state.eventSink(ResolveVerifiedUserSendFailureEvent.Retry)
} }
fun onResolveAndResendClick() { fun onResolveAndResendClick() {
state.eventSink(ResolveVerifiedUserSendFailureEvents.ResolveAndResend) state.eventSink(ResolveVerifiedUserSendFailureEvent.ResolveAndResend)
} }
LaunchedEffect(state.verifiedUserSendFailure) { LaunchedEffect(state.verifiedUserSendFailure) {

View file

@ -10,8 +10,8 @@ package io.element.android.features.messages.impl.link
import io.element.android.wysiwyg.link.Link import io.element.android.wysiwyg.link.Link
sealed interface LinkEvents { sealed interface LinkEvent {
data class OnLinkClick(val link: Link) : LinkEvents data class OnLinkClick(val link: Link) : LinkEvent
data object Confirm : LinkEvents data object Confirm : LinkEvent
data object Cancel : LinkEvents data object Cancel : LinkEvent
} }

View file

@ -25,9 +25,9 @@ class LinkPresenter(
override fun present(): LinkState { override fun present(): LinkState {
val linkClick: MutableState<AsyncAction<Link>> = remember { mutableStateOf(AsyncAction.Uninitialized) } val linkClick: MutableState<AsyncAction<Link>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
fun handleEvent(event: LinkEvents) { fun handleEvent(event: LinkEvent) {
when (event) { when (event) {
is LinkEvents.OnLinkClick -> { is LinkEvent.OnLinkClick -> {
linkClick.value = AsyncAction.Loading linkClick.value = AsyncAction.Loading
val result = linkChecker.isSafe(event.link) val result = linkChecker.isSafe(event.link)
if (result) { if (result) {
@ -37,12 +37,12 @@ class LinkPresenter(
linkClick.value = ConfirmingLinkClick(event.link) linkClick.value = ConfirmingLinkClick(event.link)
} }
} }
LinkEvents.Confirm -> { LinkEvent.Confirm -> {
linkClick.value = (linkClick.value as? ConfirmingLinkClick) linkClick.value = (linkClick.value as? ConfirmingLinkClick)
?.let { AsyncAction.Success(it.link) } ?.let { AsyncAction.Success(it.link) }
?: AsyncAction.Uninitialized ?: AsyncAction.Uninitialized
} }
LinkEvents.Cancel -> { LinkEvent.Cancel -> {
linkClick.value = AsyncAction.Uninitialized linkClick.value = AsyncAction.Uninitialized
} }
} }

View file

@ -13,5 +13,5 @@ import io.element.android.wysiwyg.link.Link
data class LinkState( data class LinkState(
val linkClick: AsyncAction<Link>, val linkClick: AsyncAction<Link>,
val eventSink: (LinkEvents) -> Unit, val eventSink: (LinkEvent) -> Unit,
) )

View file

@ -29,7 +29,7 @@ open class LinkStateProvider : PreviewParameterProvider<LinkState> {
fun aLinkState( fun aLinkState(
linkClick: AsyncAction<Link> = AsyncAction.Uninitialized, linkClick: AsyncAction<Link> = AsyncAction.Uninitialized,
eventSink: (LinkEvents) -> Unit = {}, eventSink: (LinkEvent) -> Unit = {},
) = LinkState( ) = LinkState(
linkClick = linkClick, linkClick = linkClick,
eventSink = eventSink, eventSink = eventSink,

View file

@ -46,10 +46,10 @@ fun LinkView(
), ),
submitText = stringResource(CommonStrings.action_continue), submitText = stringResource(CommonStrings.action_continue),
onSubmitClick = { onSubmitClick = {
state.eventSink(LinkEvents.Confirm) state.eventSink(LinkEvent.Confirm)
}, },
onDismiss = { onDismiss = {
state.eventSink(LinkEvents.Cancel) state.eventSink(LinkEvent.Cancel)
}, },
) )
} }
@ -58,7 +58,7 @@ fun LinkView(
val latestOnLinkValid by rememberUpdatedState(onLinkValid) val latestOnLinkValid by rememberUpdatedState(onLinkValid)
LaunchedEffect(state.linkClick.data) { LaunchedEffect(state.linkClick.data) {
latestOnLinkValid(state.linkClick.data) latestOnLinkValid(state.linkClick.data)
state.eventSink(LinkEvents.Cancel) state.eventSink(LinkEvent.Cancel)
} }
} }
} }

View file

@ -8,6 +8,6 @@
package io.element.android.features.messages.impl.pinned.banner package io.element.android.features.messages.impl.pinned.banner
sealed interface PinnedMessagesBannerEvents { sealed interface PinnedMessagesBannerEvent {
data object MoveToNextPinned : PinnedMessagesBannerEvents data object MoveToNextPinned : PinnedMessagesBannerEvent
} }

View file

@ -58,9 +58,9 @@ class PinnedMessagesBannerPresenter(
}, },
) )
fun handleEvent(event: PinnedMessagesBannerEvents) { fun handleEvent(event: PinnedMessagesBannerEvent) {
when (event) { when (event) {
is PinnedMessagesBannerEvents.MoveToNextPinned -> { is PinnedMessagesBannerEvent.MoveToNextPinned -> {
val loadedCount = pinnedItems.value.dataOrNull().orEmpty().size val loadedCount = pinnedItems.value.dataOrNull().orEmpty().size
currentPinnedMessageIndex = (currentPinnedMessageIndex - 1).mod(loadedCount) currentPinnedMessageIndex = (currentPinnedMessageIndex - 1).mod(loadedCount)
} }
@ -80,7 +80,7 @@ class PinnedMessagesBannerPresenter(
expectedPinnedMessagesCount: Int, expectedPinnedMessagesCount: Int,
pinnedItems: AsyncData<ImmutableList<PinnedMessagesBannerItem>>, pinnedItems: AsyncData<ImmutableList<PinnedMessagesBannerItem>>,
currentPinnedMessageIndex: Int, currentPinnedMessageIndex: Int,
eventSink: (PinnedMessagesBannerEvents) -> Unit eventSink: (PinnedMessagesBannerEvent) -> Unit
): PinnedMessagesBannerState { ): PinnedMessagesBannerState {
return when (pinnedItems) { return when (pinnedItems) {
is AsyncData.Failure, is AsyncData.Uninitialized -> PinnedMessagesBannerState.Hidden is AsyncData.Failure, is AsyncData.Uninitialized -> PinnedMessagesBannerState.Hidden

View file

@ -41,6 +41,6 @@ sealed interface PinnedMessagesBannerState {
val currentPinnedMessage: PinnedMessagesBannerItem, val currentPinnedMessage: PinnedMessagesBannerItem,
val currentPinnedMessageIndex: Int, val currentPinnedMessageIndex: Int,
val loadedPinnedMessagesCount: Int, val loadedPinnedMessagesCount: Int,
val eventSink: (PinnedMessagesBannerEvents) -> Unit val eventSink: (PinnedMessagesBannerEvent) -> Unit
) : Visible ) : Visible
} }

View file

@ -50,7 +50,7 @@ internal fun aLoadedPinnedMessagesBannerState(
eventId = EventId("\$" + Random.nextInt().toString()), eventId = EventId("\$" + Random.nextInt().toString()),
formatted = AnnotatedString(message) formatted = AnnotatedString(message)
), ),
eventSink: (PinnedMessagesBannerEvents) -> Unit = {} eventSink: (PinnedMessagesBannerEvent) -> Unit = {}
) = PinnedMessagesBannerState.Loaded( ) = PinnedMessagesBannerState.Loaded(
currentPinnedMessage = currentPinnedMessage, currentPinnedMessage = currentPinnedMessage,
currentPinnedMessageIndex = currentPinnedMessageIndex, currentPinnedMessageIndex = currentPinnedMessageIndex,

View file

@ -96,7 +96,7 @@ private fun PinnedMessagesBannerRow(
if (state is PinnedMessagesBannerState.Loaded) { if (state is PinnedMessagesBannerState.Loaded) {
analyticsService.captureInteraction(Interaction.Name.PinnedMessageBannerClick) analyticsService.captureInteraction(Interaction.Name.PinnedMessageBannerClick)
onClick(state.currentPinnedMessage.eventId) onClick(state.currentPinnedMessage.eventId)
state.eventSink(PinnedMessagesBannerEvents.MoveToNextPinned) state.eventSink(PinnedMessagesBannerEvent.MoveToNextPinned)
} }
}, },
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,

View file

@ -11,6 +11,6 @@ package io.element.android.features.messages.impl.pinned.list
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItem
sealed interface PinnedMessagesListEvents { sealed interface PinnedMessagesListEvent {
data class HandleAction(val action: TimelineItemAction, val event: TimelineItem.Event) : PinnedMessagesListEvents data class HandleAction(val action: TimelineItemAction, val event: TimelineItem.Event) : PinnedMessagesListEvent
} }

View file

@ -134,9 +134,9 @@ class PinnedMessagesListPresenter(
} }
) )
fun handleEvent(event: PinnedMessagesListEvents) { fun handleEvent(event: PinnedMessagesListEvent) {
when (event) { when (event) {
is PinnedMessagesListEvents.HandleAction -> sessionCoroutineScope.handleTimelineAction(event.action, event.event) is PinnedMessagesListEvent.HandleAction -> sessionCoroutineScope.handleTimelineAction(event.action, event.event)
} }
} }
@ -232,7 +232,7 @@ class PinnedMessagesListPresenter(
linkState: LinkState, linkState: LinkState,
userEventPermissions: UserEventPermissions, userEventPermissions: UserEventPermissions,
timelineItems: AsyncData<ImmutableList<TimelineItem>>, timelineItems: AsyncData<ImmutableList<TimelineItem>>,
eventSink: (PinnedMessagesListEvents) -> Unit eventSink: (PinnedMessagesListEvent) -> Unit
): PinnedMessagesListState { ): PinnedMessagesListState {
return when (timelineItems) { return when (timelineItems) {
AsyncData.Uninitialized, is AsyncData.Loading -> PinnedMessagesListState.Loading AsyncData.Uninitialized, is AsyncData.Loading -> PinnedMessagesListState.Loading

View file

@ -35,7 +35,7 @@ sealed interface PinnedMessagesListState {
val actionListState: ActionListState, val actionListState: ActionListState,
val linkState: LinkState, val linkState: LinkState,
val displayThreadSummaries: Boolean, val displayThreadSummaries: Boolean,
val eventSink: (PinnedMessagesListEvents) -> Unit, val eventSink: (PinnedMessagesListEvent) -> Unit,
) : PinnedMessagesListState { ) : PinnedMessagesListState {
val loadedPinnedMessagesCount = timelineItems.count { timelineItem -> timelineItem is TimelineItem.Event } val loadedPinnedMessagesCount = timelineItems.count { timelineItem -> timelineItem is TimelineItem.Event }
} }

View file

@ -94,7 +94,7 @@ fun aLoadedPinnedMessagesListState(
actionListState: ActionListState = anActionListState(), actionListState: ActionListState = anActionListState(),
aUserEventPermissions: UserEventPermissions = UserEventPermissions.DEFAULT, aUserEventPermissions: UserEventPermissions = UserEventPermissions.DEFAULT,
displayThreadSummaries: Boolean = false, displayThreadSummaries: Boolean = false,
eventSink: (PinnedMessagesListEvents) -> Unit = {} eventSink: (PinnedMessagesListEvent) -> Unit = {}
) = PinnedMessagesListState.Filled( ) = PinnedMessagesListState.Filled(
timelineRoomInfo = timelineRoomInfo, timelineRoomInfo = timelineRoomInfo,
timelineProtectionState = timelineProtectionState, timelineProtectionState = timelineProtectionState,

View file

@ -25,10 +25,10 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import im.vector.app.features.analytics.plan.Interaction import im.vector.app.features.analytics.plan.Interaction
import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.messages.impl.actionlist.ActionListEvents import io.element.android.features.messages.impl.actionlist.ActionListEvent
import io.element.android.features.messages.impl.actionlist.ActionListView import io.element.android.features.messages.impl.actionlist.ActionListView
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
import io.element.android.features.messages.impl.link.LinkEvents import io.element.android.features.messages.impl.link.LinkEvent
import io.element.android.features.messages.impl.link.LinkView import io.element.android.features.messages.impl.link.LinkView
import io.element.android.features.messages.impl.timeline.components.TimelineItemRow import io.element.android.features.messages.impl.timeline.components.TimelineItemRow
import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView
@ -175,10 +175,10 @@ private fun PinnedMessagesListLoaded(
) { ) {
fun onActionSelected(timelineItemAction: TimelineItemAction, event: TimelineItem.Event) { fun onActionSelected(timelineItemAction: TimelineItemAction, event: TimelineItem.Event) {
state.actionListState.eventSink( state.actionListState.eventSink(
ActionListEvents.Clear ActionListEvent.Clear
) )
state.eventSink( state.eventSink(
PinnedMessagesListEvents.HandleAction( PinnedMessagesListEvent.HandleAction(
action = timelineItemAction, action = timelineItemAction,
event = event, event = event,
) )
@ -187,7 +187,7 @@ private fun PinnedMessagesListLoaded(
fun onMessageLongClick(event: TimelineItem.Event) { fun onMessageLongClick(event: TimelineItem.Event) {
state.actionListState.eventSink( state.actionListState.eventSink(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = event, event = event,
userEventPermissions = state.userEventPermissions, userEventPermissions = state.userEventPermissions,
) )
@ -222,7 +222,7 @@ private fun PinnedMessagesListLoaded(
focusedEventId = null, focusedEventId = null,
onUserDataClick = onUserDataClick, onUserDataClick = onUserDataClick,
onLinkClick = { link -> onLinkClick = { link ->
state.linkState.eventSink(LinkEvents.OnLinkClick(link)) state.linkState.eventSink(LinkEvent.OnLinkClick(link))
}, },
onLinkLongClick = onLinkLongClick, onLinkLongClick = onLinkLongClick,
onContentClick = onEventClick, onContentClick = onEventClick,
@ -243,7 +243,7 @@ private fun PinnedMessagesListLoaded(
onContentClick = { onEventClick(event) }, onContentClick = { onEventClick(event) },
onLongClick = { onMessageLongClick(event) }, onLongClick = { onMessageLongClick(event) },
onLinkClick = { link -> onLinkClick = { link ->
state.linkState.eventSink(LinkEvents.OnLinkClick(link)) state.linkState.eventSink(LinkEvent.OnLinkClick(link))
}, },
onLinkLongClick = onLinkLongClick, onLinkLongClick = onLinkLongClick,
modifier = contentModifier, modifier = contentModifier,

View file

@ -8,9 +8,9 @@
package io.element.android.features.messages.impl.report package io.element.android.features.messages.impl.report
sealed interface ReportMessageEvents { sealed interface ReportMessageEvent {
data class UpdateReason(val reason: String) : ReportMessageEvents data class UpdateReason(val reason: String) : ReportMessageEvent
data object ToggleBlockUser : ReportMessageEvents data object ToggleBlockUser : ReportMessageEvent
data object Report : ReportMessageEvents data object Report : ReportMessageEvent
data object ClearError : ReportMessageEvents data object ClearError : ReportMessageEvent
} }

View file

@ -54,12 +54,12 @@ class ReportMessagePresenter(
var blockUser by rememberSaveable { mutableStateOf(false) } var blockUser by rememberSaveable { mutableStateOf(false) }
var result: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) } var result: MutableState<AsyncAction<Unit>> = remember { mutableStateOf(AsyncAction.Uninitialized) }
fun handleEvent(event: ReportMessageEvents) { fun handleEvent(event: ReportMessageEvent) {
when (event) { when (event) {
is ReportMessageEvents.UpdateReason -> reason = event.reason is ReportMessageEvent.UpdateReason -> reason = event.reason
ReportMessageEvents.ToggleBlockUser -> blockUser = !blockUser ReportMessageEvent.ToggleBlockUser -> blockUser = !blockUser
ReportMessageEvents.Report -> coroutineScope.report(inputs.eventId, inputs.senderId, reason, blockUser, result) ReportMessageEvent.Report -> coroutineScope.report(inputs.eventId, inputs.senderId, reason, blockUser, result)
ReportMessageEvents.ClearError -> result.value = AsyncAction.Uninitialized ReportMessageEvent.ClearError -> result.value = AsyncAction.Uninitialized
} }
} }

View file

@ -14,5 +14,5 @@ data class ReportMessageState(
val reason: String, val reason: String,
val blockUser: Boolean, val blockUser: Boolean,
val result: AsyncAction<Unit>, val result: AsyncAction<Unit>,
val eventSink: (ReportMessageEvents) -> Unit val eventSink: (ReportMessageEvent) -> Unit
) )

View file

@ -57,7 +57,7 @@ fun ReportMessageView(
progressDialog = {}, progressDialog = {},
onSuccess = { onBackClick() }, onSuccess = { onBackClick() },
errorMessage = { stringResource(CommonStrings.error_unknown) }, errorMessage = { stringResource(CommonStrings.error_unknown) },
onErrorDismiss = { state.eventSink(ReportMessageEvents.ClearError) } onErrorDismiss = { state.eventSink(ReportMessageEvent.ClearError) }
) )
Scaffold( Scaffold(
@ -84,7 +84,7 @@ fun ReportMessageView(
TextField( TextField(
value = state.reason, value = state.reason,
onValueChange = { state.eventSink(ReportMessageEvents.UpdateReason(it)) }, onValueChange = { state.eventSink(ReportMessageEvent.UpdateReason(it)) },
placeholder = stringResource(R.string.screen_report_content_hint), placeholder = stringResource(R.string.screen_report_content_hint),
minLines = 3, minLines = 3,
enabled = !isSending, enabled = !isSending,
@ -113,7 +113,7 @@ fun ReportMessageView(
Switch( Switch(
enabled = !isSending, enabled = !isSending,
checked = state.blockUser, checked = state.blockUser,
onCheckedChange = { state.eventSink(ReportMessageEvents.ToggleBlockUser) }, onCheckedChange = { state.eventSink(ReportMessageEvent.ToggleBlockUser) },
) )
} }
@ -125,7 +125,7 @@ fun ReportMessageView(
showProgress = isSending, showProgress = isSending,
onClick = { onClick = {
focusManager.clearFocus(force = true) focusManager.clearFocus(force = true)
state.eventSink(ReportMessageEvents.Report) state.eventSink(ReportMessageEvent.Report)
}, },
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()

View file

@ -36,7 +36,7 @@ import io.element.android.features.messages.impl.attachments.Attachment
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvent import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvent
import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter
import io.element.android.features.messages.impl.timeline.TimelineController import io.element.android.features.messages.impl.timeline.TimelineController
import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.TimelineEvent
import io.element.android.features.messages.impl.timeline.TimelinePresenter import io.element.android.features.messages.impl.timeline.TimelinePresenter
import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories
import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactories import io.element.android.features.messages.impl.timeline.di.TimelineItemPresenterFactories
@ -148,7 +148,7 @@ class ThreadedMessagesNode(
activity: Activity, activity: Activity,
darkTheme: Boolean, darkTheme: Boolean,
url: String, url: String,
eventSink: (TimelineEvents) -> Unit, eventSink: (TimelineEvent) -> Unit,
customTab: Boolean customTab: Boolean
) { ) {
when (val permalink = permalinkParser.parse(url)) { when (val permalink = permalinkParser.parse(url)) {
@ -175,12 +175,12 @@ class ThreadedMessagesNode(
private fun handleRoomLinkClick( private fun handleRoomLinkClick(
roomLink: PermalinkData.RoomLink, roomLink: PermalinkData.RoomLink,
eventSink: (TimelineEvents) -> Unit, eventSink: (TimelineEvent) -> Unit,
) { ) {
if (room.matches(roomLink.roomIdOrAlias)) { if (room.matches(roomLink.roomIdOrAlias)) {
val eventId = roomLink.eventId val eventId = roomLink.eventId
if (eventId != null) { if (eventId != null) {
eventSink(TimelineEvents.FocusOnEvent(eventId)) eventSink(TimelineEvent.FocusOnEvent(eventId))
} else { } else {
// Click on the same room, navigate up // Click on the same room, navigate up
// Note that it can not be enough to go back to the room if the thread has been opened // Note that it can not be enough to go back to the room if the thread has been opened
@ -277,7 +277,7 @@ class ThreadedMessagesNode(
} }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
focusedEventId?.also { eventId -> focusedEventId?.also { eventId ->
state.timelineState.eventSink(TimelineEvents.FocusOnEvent(eventId)) state.timelineState.eventSink(TimelineEvent.FocusOnEvent(eventId))
} }
// Reset the focused event id to null to avoid refocusing when restoring node. // Reset the focused event id to null to avoid refocusing when restoring node.
focusedEventId = null focusedEventId = null

View file

@ -16,45 +16,45 @@ import io.element.android.libraries.matrix.api.core.ThreadId
import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.Timeline
import kotlin.time.Duration import kotlin.time.Duration
sealed interface TimelineEvents { sealed interface TimelineEvent {
data class OnScrollFinished(val firstIndex: Int) : TimelineEvents data class OnScrollFinished(val firstIndex: Int) : TimelineEvent
data class FocusOnEvent(val eventId: EventId, val debounce: Duration = Duration.ZERO) : TimelineEvents data class FocusOnEvent(val eventId: EventId, val debounce: Duration = Duration.ZERO) : TimelineEvent
data object ClearFocusRequestState : TimelineEvents data object ClearFocusRequestState : TimelineEvent
data object OnFocusEventRender : TimelineEvents data object OnFocusEventRender : TimelineEvent
data object JumpToLive : TimelineEvents data object JumpToLive : TimelineEvent
data object HideShieldDialog : TimelineEvents data object HideShieldDialog : TimelineEvent
/** /**
* Events coming from a timeline item. * Events coming from a timeline item.
*/ */
sealed interface EventFromTimelineItem : TimelineEvents sealed interface TimelineItemEvent : TimelineEvent
data class ComputeVerifiedUserSendFailure(val event: TimelineItem.Event) : EventFromTimelineItem data class ComputeVerifiedUserSendFailure(val event: TimelineItem.Event) : TimelineItemEvent
data class ShowShieldDialog(val messageShieldData: MessageShieldData) : EventFromTimelineItem data class ShowShieldDialog(val messageShieldData: MessageShieldData) : TimelineItemEvent
data class LoadMore(val direction: Timeline.PaginationDirection) : EventFromTimelineItem data class LoadMore(val direction: Timeline.PaginationDirection) : TimelineItemEvent
data class OpenThread(val threadRootEventId: ThreadId, val focusedEvent: EventId?) : EventFromTimelineItem data class OpenThread(val threadRootEventId: ThreadId, val focusedEvent: EventId?) : TimelineItemEvent
/** /**
* Navigate to the predecessor or successor room of the current room. * Navigate to the predecessor or successor room of the current room.
*/ */
data class NavigateToPredecessorOrSuccessorRoom(val roomId: RoomId) : EventFromTimelineItem data class NavigateToPredecessorOrSuccessorRoom(val roomId: RoomId) : TimelineItemEvent
/** /**
* Events coming from a poll item. * Events coming from a poll item.
*/ */
sealed interface TimelineItemPollEvents : EventFromTimelineItem sealed interface TimelineItemPollEvent : TimelineItemEvent
data class SelectPollAnswer( data class SelectPollAnswer(
val pollStartId: EventId, val pollStartId: EventId,
val answerId: String val answerId: String
) : TimelineItemPollEvents ) : TimelineItemPollEvent
data class EndPoll( data class EndPoll(
val pollStartId: EventId, val pollStartId: EventId,
) : TimelineItemPollEvents ) : TimelineItemPollEvent
data class EditPoll( data class EditPoll(
val pollStartId: EventId, val pollStartId: EventId,
) : TimelineItemPollEvents ) : TimelineItemPollEvent
} }

View file

@ -25,7 +25,7 @@ import dev.zacsweers.metro.AssistedFactory
import dev.zacsweers.metro.AssistedInject import dev.zacsweers.metro.AssistedInject
import io.element.android.features.messages.impl.MessagesNavigator import io.element.android.features.messages.impl.MessagesNavigator
import io.element.android.features.messages.impl.UserEventPermissions import io.element.android.features.messages.impl.UserEventPermissions
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureEvents 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.ResolveVerifiedUserSendFailureState
import io.element.android.features.messages.impl.timeline.components.MessageShieldData import io.element.android.features.messages.impl.timeline.components.MessageShieldData
import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory
@ -150,9 +150,9 @@ class TimelinePresenter(
value = featureFlagService.isFeatureEnabled(FeatureFlags.Threads) value = featureFlagService.isFeatureEnabled(FeatureFlags.Threads)
} }
fun handleEvent(event: TimelineEvents) { fun handleEvent(event: TimelineEvent) {
when (event) { when (event) {
is TimelineEvents.LoadMore -> { is TimelineEvent.LoadMore -> {
if (event.direction == Timeline.PaginationDirection.FORWARDS && timelineMode is Timeline.Mode.Thread) { if (event.direction == Timeline.PaginationDirection.FORWARDS && timelineMode is Timeline.Mode.Thread) {
// Do not paginate forwards in thread mode, as it's not supported // Do not paginate forwards in thread mode, as it's not supported
return return
@ -161,7 +161,7 @@ class TimelinePresenter(
timelineController.paginate(direction = event.direction) timelineController.paginate(direction = event.direction)
} }
} }
is TimelineEvents.OnScrollFinished -> { is TimelineEvent.OnScrollFinished -> {
if (isLive) { if (isLive) {
if (event.firstIndex == 0) { if (event.firstIndex == 0) {
newEventState.value = NewEventState.None newEventState.value = NewEventState.None
@ -177,7 +177,7 @@ class TimelinePresenter(
newEventState.value = NewEventState.None newEventState.value = NewEventState.None
} }
} }
is TimelineEvents.SelectPollAnswer -> sessionCoroutineScope.launch { is TimelineEvent.SelectPollAnswer -> sessionCoroutineScope.launch {
timelineController.invokeOnCurrentTimeline { timelineController.invokeOnCurrentTimeline {
sendPollResponseAction.execute( sendPollResponseAction.execute(
timeline = this, timeline = this,
@ -186,7 +186,7 @@ class TimelinePresenter(
) )
} }
} }
is TimelineEvents.EndPoll -> sessionCoroutineScope.launch { is TimelineEvent.EndPoll -> sessionCoroutineScope.launch {
timelineController.invokeOnCurrentTimeline { timelineController.invokeOnCurrentTimeline {
endPollAction.execute( endPollAction.execute(
timeline = this, timeline = this,
@ -194,38 +194,38 @@ class TimelinePresenter(
) )
} }
} }
is TimelineEvents.EditPoll -> { is TimelineEvent.EditPoll -> {
navigator.navigateToEditPoll(event.pollStartId) navigator.navigateToEditPoll(event.pollStartId)
} }
is TimelineEvents.FocusOnEvent -> sessionCoroutineScope.launch { is TimelineEvent.FocusOnEvent -> sessionCoroutineScope.launch {
focusRequestState.value = FocusRequestState.Requested(event.eventId, event.debounce) focusRequestState.value = FocusRequestState.Requested(event.eventId, event.debounce)
delay(event.debounce) delay(event.debounce)
Timber.tag(tag).d("Started focus on ${event.eventId}") Timber.tag(tag).d("Started focus on ${event.eventId}")
focusOnEvent(event.eventId, focusRequestState) focusOnEvent(event.eventId, focusRequestState)
}.start() }.start()
is TimelineEvents.OnFocusEventRender -> { is TimelineEvent.OnFocusEventRender -> {
// If there was a pending 'notification tap opens timeline' transaction, finish it now we're focused in the required event // If there was a pending 'notification tap opens timeline' transaction, finish it now we're focused in the required event
analyticsService.finishLongRunningTransaction(NotificationToMessage) analyticsService.finishLongRunningTransaction(NotificationToMessage)
focusRequestState.value = focusRequestState.value.onFocusEventRender() focusRequestState.value = focusRequestState.value.onFocusEventRender()
} }
is TimelineEvents.ClearFocusRequestState -> { is TimelineEvent.ClearFocusRequestState -> {
focusRequestState.value = FocusRequestState.None focusRequestState.value = FocusRequestState.None
} }
is TimelineEvents.JumpToLive -> { is TimelineEvent.JumpToLive -> {
timelineController.focusOnLive() timelineController.focusOnLive()
} }
TimelineEvents.HideShieldDialog -> messageShieldDialogData.value = null TimelineEvent.HideShieldDialog -> messageShieldDialogData.value = null
is TimelineEvents.ShowShieldDialog -> messageShieldDialogData.value = event.messageShieldData is TimelineEvent.ShowShieldDialog -> messageShieldDialogData.value = event.messageShieldData
is TimelineEvents.ComputeVerifiedUserSendFailure -> { is TimelineEvent.ComputeVerifiedUserSendFailure -> {
resolveVerifiedUserSendFailureState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(event.event)) resolveVerifiedUserSendFailureState.eventSink(ResolveVerifiedUserSendFailureEvent.ComputeForMessage(event.event))
} }
is TimelineEvents.NavigateToPredecessorOrSuccessorRoom -> { is TimelineEvent.NavigateToPredecessorOrSuccessorRoom -> {
// Navigate to the predecessor or successor room // Navigate to the predecessor or successor room
val serverNames = calculateServerNamesForRoom(room) val serverNames = calculateServerNamesForRoom(room)
navigator.navigateToRoom(event.roomId, null, serverNames) navigator.navigateToRoom(event.roomId, null, serverNames)
} }
is TimelineEvents.OpenThread -> { is TimelineEvent.OpenThread -> {
navigator.navigateToThread( navigator.navigateToThread(
threadRootId = event.threadRootEventId, threadRootId = event.threadRootEventId,
focusedEventId = event.focusedEvent, focusedEventId = event.focusedEvent,

View file

@ -34,7 +34,7 @@ data class TimelineState(
val messageShieldDialogData: MessageShieldData?, val messageShieldDialogData: MessageShieldData?,
val resolveVerifiedUserSendFailureState: ResolveVerifiedUserSendFailureState, val resolveVerifiedUserSendFailureState: ResolveVerifiedUserSendFailureState,
val displayThreadSummaries: Boolean, val displayThreadSummaries: Boolean,
val eventSink: (TimelineEvents) -> Unit, val eventSink: (TimelineEvent) -> Unit,
) { ) {
private val lastTimelineEvent = timelineItems.firstOrNull { it is TimelineItem.Event } as? TimelineItem.Event private val lastTimelineEvent = timelineItems.firstOrNull { it is TimelineItem.Event } as? TimelineItem.Event
val hasAnyEvent = lastTimelineEvent != null val hasAnyEvent = lastTimelineEvent != null

View file

@ -56,7 +56,7 @@ fun aTimelineState(
messageShield: MessageShield? = null, messageShield: MessageShield? = null,
resolveVerifiedUserSendFailureState: ResolveVerifiedUserSendFailureState = aResolveVerifiedUserSendFailureState(), resolveVerifiedUserSendFailureState: ResolveVerifiedUserSendFailureState = aResolveVerifiedUserSendFailureState(),
displayThreadSummaries: Boolean = false, displayThreadSummaries: Boolean = false,
eventSink: (TimelineEvents) -> Unit = {}, eventSink: (TimelineEvent) -> Unit = {},
): TimelineState { ): TimelineState {
val focusedEventId = timelineItems.filterIsInstance<TimelineItem.Event>().getOrNull(focusedEventIndex)?.eventId val focusedEventId = timelineItems.filterIsInstance<TimelineItem.Event>().getOrNull(focusedEventIndex)?.eventId
val focusRequestState = if (focusedEventId != null) { val focusRequestState = if (focusedEventId != null) {

View file

@ -107,19 +107,19 @@ fun TimelineView(
nestedScrollConnection: NestedScrollConnection = rememberNestedScrollInteropConnection(), nestedScrollConnection: NestedScrollConnection = rememberNestedScrollInteropConnection(),
) { ) {
fun clearFocusRequestState() { fun clearFocusRequestState() {
state.eventSink(TimelineEvents.ClearFocusRequestState) state.eventSink(TimelineEvent.ClearFocusRequestState)
} }
fun onScrollFinishAt(firstVisibleIndex: Int) { fun onScrollFinishAt(firstVisibleIndex: Int) {
state.eventSink(TimelineEvents.OnScrollFinished(firstVisibleIndex)) state.eventSink(TimelineEvent.OnScrollFinished(firstVisibleIndex))
} }
fun onFocusEventRender() { fun onFocusEventRender() {
state.eventSink(TimelineEvents.OnFocusEventRender) state.eventSink(TimelineEvent.OnFocusEventRender)
} }
fun onJumpToLive() { fun onJumpToLive() {
state.eventSink(TimelineEvents.JumpToLive) state.eventSink(TimelineEvent.JumpToLive)
} }
val context = LocalContext.current val context = LocalContext.current
@ -129,7 +129,7 @@ fun TimelineView(
val useReverseLayout = !isTalkbackActive() val useReverseLayout = !isTalkbackActive()
fun inReplyToClick(eventId: EventId) { fun inReplyToClick(eventId: EventId) {
state.eventSink(TimelineEvents.FocusOnEvent(eventId)) state.eventSink(TimelineEvent.FocusOnEvent(eventId))
} }
fun onLinkLongClick(link: Link) { fun onLinkLongClick(link: Link) {
@ -143,7 +143,7 @@ fun TimelineView(
} }
fun prefetchMoreItems() { fun prefetchMoreItems() {
state.eventSink(TimelineEvents.LoadMore(Timeline.PaginationDirection.BACKWARDS)) state.eventSink(TimelineEvent.LoadMore(Timeline.PaginationDirection.BACKWARDS))
} }
// Animate alpha when timeline is first displayed, to avoid flashes or glitching when viewing rooms // Animate alpha when timeline is first displayed, to avoid flashes or glitching when viewing rooms
@ -223,7 +223,7 @@ private fun MessageShieldDialog(state: TimelineState) {
val messageShield = state.messageShieldDialogData ?: return val messageShield = state.messageShieldDialogData ?: return
AlertDialog( AlertDialog(
content = messageShield.toText(), content = messageShield.toText(),
onDismiss = { state.eventSink.invoke(TimelineEvents.HideShieldDialog) }, onDismiss = { state.eventSink.invoke(TimelineEvent.HideShieldDialog) },
) )
} }

View file

@ -23,7 +23,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.TimelineEvent
import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.event.isEdited import io.element.android.features.messages.impl.timeline.model.event.isEdited
import io.element.android.features.messages.impl.timeline.model.event.isRedacted import io.element.android.features.messages.impl.timeline.model.event.isRedacted
@ -33,13 +33,12 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.Icon
import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.isCritical
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
@Composable @Composable
fun TimelineEventTimestampView( fun TimelineEventTimestampView(
event: TimelineItem.Event, event: TimelineItem.Event,
eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit, eventSink: (TimelineEvent.TimelineItemEvent) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val formattedTime = event.sentTime val formattedTime = event.sentTime
@ -80,7 +79,7 @@ fun TimelineEventTimestampView(
enabled = isVerifiedUserSendFailure, enabled = isVerifiedUserSendFailure,
onClickLabel = stringResource(CommonStrings.action_open_context_menu), onClickLabel = stringResource(CommonStrings.action_open_context_menu),
) { ) {
eventSink(TimelineEvents.ComputeVerifiedUserSendFailure(event)) eventSink(TimelineEvent.ComputeVerifiedUserSendFailure(event))
} }
) )
} }
@ -96,7 +95,7 @@ fun TimelineEventTimestampView(
.clickable( .clickable(
onClickLabel = stringResource(CommonStrings.a11y_view_details), onClickLabel = stringResource(CommonStrings.a11y_view_details),
) { ) {
eventSink(TimelineEvents.ShowShieldDialog(shield)) eventSink(TimelineEvent.ShowShieldDialog(shield))
}, },
tint = shield.toIconColor(), tint = shield.toIconColor(),
) )

View file

@ -57,7 +57,7 @@ import androidx.constraintlayout.compose.ConstrainScope
import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.ConstraintLayout
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.TimelineEvent
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView
@ -153,7 +153,7 @@ fun TimelineItemEventRow(
onMoreReactionsClick: (eventId: TimelineItem.Event) -> Unit, onMoreReactionsClick: (eventId: TimelineItem.Event) -> Unit,
onReadReceiptClick: (event: TimelineItem.Event) -> Unit, onReadReceiptClick: (event: TimelineItem.Event) -> Unit,
onSwipeToReply: () -> Unit, onSwipeToReply: () -> Unit,
eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit, eventSink: (TimelineEvent.TimelineItemEvent) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
eventContentView: @Composable (Modifier, (ContentAvoidingLayoutData) -> Unit) -> Unit = { contentModifier, onContentLayoutChange -> eventContentView: @Composable (Modifier, (ContentAvoidingLayoutData) -> Unit) -> Unit = { contentModifier, onContentLayoutChange ->
// Only pass down a custom clickable lambda if the content can be clicked separately // Only pass down a custom clickable lambda if the content can be clicked separately
@ -278,7 +278,7 @@ fun TimelineItemEventRow(
isOutgoing = event.isMine, isOutgoing = event.isMine,
onClick = { onClick = {
event.eventId?.let { event.eventId?.let {
eventSink(TimelineEvents.OpenThread(it.toThreadId(), null)) eventSink(TimelineEvent.OpenThread(it.toThreadId(), null))
} }
} }
) )
@ -410,7 +410,7 @@ private fun TimelineItemEventRowContent(
onReactionClick: (emoji: String) -> Unit, onReactionClick: (emoji: String) -> Unit,
onReactionLongClick: (emoji: String) -> Unit, onReactionLongClick: (emoji: String) -> Unit,
onMoreReactionsClick: (event: TimelineItem.Event) -> Unit, onMoreReactionsClick: (event: TimelineItem.Event) -> Unit,
eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit, eventSink: (TimelineEvent.TimelineItemEvent) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
eventContentView: @Composable (Modifier, (ContentAvoidingLayoutData) -> Unit) -> Unit, eventContentView: @Composable (Modifier, (ContentAvoidingLayoutData) -> Unit) -> Unit,
) { ) {
@ -585,7 +585,7 @@ private fun MessageEventBubbleContent(
timelineProtectionState: TimelineProtectionState, timelineProtectionState: TimelineProtectionState,
onMessageLongClick: () -> Unit, onMessageLongClick: () -> Unit,
inReplyToClick: () -> Unit, inReplyToClick: () -> Unit,
eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit, eventSink: (TimelineEvent.TimelineItemEvent) -> Unit,
@SuppressLint("ModifierParameter") @SuppressLint("ModifierParameter")
// need to rename this modifier to prevent linter false positives // need to rename this modifier to prevent linter false positives
@Suppress("ModifierNaming") @Suppress("ModifierNaming")
@ -623,7 +623,7 @@ private fun MessageEventBubbleContent(
@Composable @Composable
fun WithTimestampLayout( fun WithTimestampLayout(
timestampPosition: TimestampPosition, timestampPosition: TimestampPosition,
eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit, eventSink: (TimelineEvent.TimelineItemEvent) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
canShrinkContent: Boolean = false, canShrinkContent: Boolean = false,
content: @Composable (onContentLayoutChange: (ContentAvoidingLayoutData) -> Unit) -> Unit, content: @Composable (onContentLayoutChange: (ContentAvoidingLayoutData) -> Unit) -> Unit,

View file

@ -16,7 +16,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.pluralStringResource
import io.element.android.features.messages.impl.R import io.element.android.features.messages.impl.R
import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.TimelineEvent
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
import io.element.android.features.messages.impl.timeline.aGroupedEvents import io.element.android.features.messages.impl.timeline.aGroupedEvents
import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo
@ -57,7 +57,7 @@ fun TimelineItemGroupedEventsRow(
onReactionLongClick: (key: String, TimelineItem.Event) -> Unit, onReactionLongClick: (key: String, TimelineItem.Event) -> Unit,
onMoreReactionsClick: (TimelineItem.Event) -> Unit, onMoreReactionsClick: (TimelineItem.Event) -> Unit,
onReadReceiptClick: (TimelineItem.Event) -> Unit, onReadReceiptClick: (TimelineItem.Event) -> Unit,
eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit, eventSink: (TimelineEvent.TimelineItemEvent) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
eventContentView: @Composable (TimelineItem.Event, Modifier, (ContentAvoidingLayoutData) -> Unit) -> Unit = eventContentView: @Composable (TimelineItem.Event, Modifier, (ContentAvoidingLayoutData) -> Unit) -> Unit =
{ event, contentModifier, onContentLayoutChange -> { event, contentModifier, onContentLayoutChange ->
@ -130,7 +130,7 @@ private fun TimelineItemGroupedEventsRowContent(
onReactionLongClick: (key: String, TimelineItem.Event) -> Unit, onReactionLongClick: (key: String, TimelineItem.Event) -> Unit,
onMoreReactionsClick: (TimelineItem.Event) -> Unit, onMoreReactionsClick: (TimelineItem.Event) -> Unit,
onReadReceiptClick: (TimelineItem.Event) -> Unit, onReadReceiptClick: (TimelineItem.Event) -> Unit,
eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit, eventSink: (TimelineEvent.TimelineItemEvent) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
eventContentView: @Composable (TimelineItem.Event, Modifier, (ContentAvoidingLayoutData) -> Unit) -> Unit = eventContentView: @Composable (TimelineItem.Event, Modifier, (ContentAvoidingLayoutData) -> Unit) -> Unit =
{ event, contentModifier, onContentLayoutChange -> { event, contentModifier, onContentLayoutChange ->

View file

@ -26,7 +26,7 @@ import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme import io.element.android.compound.theme.ElementTheme
import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.TimelineEvent
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
@ -73,7 +73,7 @@ internal fun TimelineItemRow(
onReadReceiptClick: (TimelineItem.Event) -> Unit, onReadReceiptClick: (TimelineItem.Event) -> Unit,
onSwipeToReply: (TimelineItem.Event) -> Unit, onSwipeToReply: (TimelineItem.Event) -> Unit,
onJoinCallClick: () -> Unit, onJoinCallClick: () -> Unit,
eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit, eventSink: (TimelineEvent.TimelineItemEvent) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
eventContentView: @Composable (TimelineItem.Event, Modifier, (ContentAvoidingLayoutData) -> Unit) -> Unit = eventContentView: @Composable (TimelineItem.Event, Modifier, (ContentAvoidingLayoutData) -> Unit) -> Unit =
{ event, contentModifier, onContentLayoutChange -> { event, contentModifier, onContentLayoutChange ->

View file

@ -21,7 +21,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.zIndex import androidx.compose.ui.zIndex
import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.TimelineEvent
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView
import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewState import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewState
@ -44,7 +44,7 @@ fun TimelineItemStateEventRow(
onClick: () -> Unit, onClick: () -> Unit,
onLongClick: () -> Unit, onLongClick: () -> Unit,
onReadReceiptsClick: (event: TimelineItem.Event) -> Unit, onReadReceiptsClick: (event: TimelineItem.Event) -> Unit,
eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit, eventSink: (TimelineEvent.TimelineItemEvent) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() }

View file

@ -15,7 +15,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.TimelineEvent
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
import io.element.android.features.messages.impl.timeline.components.virtual.TimelineItemDaySeparatorView import io.element.android.features.messages.impl.timeline.components.virtual.TimelineItemDaySeparatorView
import io.element.android.features.messages.impl.timeline.components.virtual.TimelineItemReadMarkerView import io.element.android.features.messages.impl.timeline.components.virtual.TimelineItemReadMarkerView
@ -35,7 +35,7 @@ import timber.log.Timber
fun TimelineItemVirtualRow( fun TimelineItemVirtualRow(
virtual: TimelineItem.Virtual, virtual: TimelineItem.Virtual,
timelineRoomInfo: TimelineRoomInfo, timelineRoomInfo: TimelineRoomInfo,
eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit, eventSink: (TimelineEvent.TimelineItemEvent) -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
Box(modifier = modifier) { Box(modifier = modifier) {
@ -48,7 +48,7 @@ fun TimelineItemVirtualRow(
roomName = timelineRoomInfo.name, roomName = timelineRoomInfo.name,
isDm = timelineRoomInfo.isDm, isDm = timelineRoomInfo.isDm,
onPredecessorRoomClick = { roomId -> onPredecessorRoomClick = { roomId ->
eventSink(TimelineEvents.NavigateToPredecessorOrSuccessorRoom(roomId)) eventSink(TimelineEvent.NavigateToPredecessorOrSuccessorRoom(roomId))
}, },
) )
} }
@ -57,7 +57,7 @@ fun TimelineItemVirtualRow(
val latestEventSink by rememberUpdatedState(eventSink) val latestEventSink by rememberUpdatedState(eventSink)
LaunchedEffect(virtual.model.timestamp) { LaunchedEffect(virtual.model.timestamp) {
Timber.d("Pagination triggered by load more indicator") Timber.d("Pagination triggered by load more indicator")
latestEventSink(TimelineEvents.LoadMore(virtual.model.direction)) latestEventSink(TimelineEvent.LoadMore(virtual.model.direction))
} }
} }
// Empty model trick to avoid timeline jumping during forward pagination. // Empty model trick to avoid timeline jumping during forward pagination.

View file

@ -35,13 +35,13 @@ fun CustomReactionBottomSheet(
val target = state.target as? CustomReactionState.Target.Success val target = state.target as? CustomReactionState.Target.Success
fun onDismiss() { fun onDismiss() {
state.eventSink(CustomReactionEvents.DismissCustomReactionSheet) state.eventSink(CustomReactionEvent.DismissCustomReactionSheet)
} }
fun onEmojiSelectedDismiss(emoji: Emoji) { fun onEmojiSelectedDismiss(emoji: Emoji) {
if (target?.event == null) return if (target?.event == null) return
sheetState.hide(coroutineScope) { sheetState.hide(coroutineScope) {
state.eventSink(CustomReactionEvents.DismissCustomReactionSheet) state.eventSink(CustomReactionEvent.DismissCustomReactionSheet)
onSelectEmoji(target.event.eventOrTransactionId, emoji) onSelectEmoji(target.event.eventOrTransactionId, emoji)
} }
} }

View file

@ -10,7 +10,7 @@ package io.element.android.features.messages.impl.timeline.components.customreac
import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItem
sealed interface CustomReactionEvents { sealed interface CustomReactionEvent {
data class ShowCustomReactionSheet(val event: TimelineItem.Event) : CustomReactionEvents data class ShowCustomReactionSheet(val event: TimelineItem.Event) : CustomReactionEvent
data object DismissCustomReactionSheet : CustomReactionEvents data object DismissCustomReactionSheet : CustomReactionEvent
} }

View file

@ -54,10 +54,10 @@ class CustomReactionPresenter(
target.value = CustomReactionState.Target.None target.value = CustomReactionState.Target.None
} }
fun handleEvent(event: CustomReactionEvents) { fun handleEvent(event: CustomReactionEvent) {
when (event) { when (event) {
is CustomReactionEvents.ShowCustomReactionSheet -> handleShowCustomReactionSheet(event.event) is CustomReactionEvent.ShowCustomReactionSheet -> handleShowCustomReactionSheet(event.event)
is CustomReactionEvents.DismissCustomReactionSheet -> handleDismissCustomReactionSheet() is CustomReactionEvent.DismissCustomReactionSheet -> handleDismissCustomReactionSheet()
} }
} }
val event = (target.value as? CustomReactionState.Target.Success)?.event val event = (target.value as? CustomReactionState.Target.Success)?.event

View file

@ -17,7 +17,7 @@ data class CustomReactionState(
val target: Target, val target: Target,
val selectedEmoji: ImmutableSet<String>, val selectedEmoji: ImmutableSet<String>,
val recentEmojis: ImmutableList<String>, val recentEmojis: ImmutableList<String>,
val eventSink: (CustomReactionEvents) -> Unit, val eventSink: (CustomReactionEvent) -> Unit,
) { ) {
sealed interface Target { sealed interface Target {
data object None : Target data object None : Target

View file

@ -60,7 +60,7 @@ fun EmojiPicker(
queryState = state.searchQuery, queryState = state.searchQuery,
resultState = state.searchResults, resultState = state.searchResults,
active = state.isSearchActive, active = state.isSearchActive,
onActiveChange = { state.eventSink(EmojiPickerEvents.ToggleSearchActive(it)) }, onActiveChange = { state.eventSink(EmojiPickerEvent.ToggleSearchActive(it)) },
windowInsets = WindowInsets(0, 0, 0, 0), windowInsets = WindowInsets(0, 0, 0, 0),
placeHolderTitle = stringResource(CommonStrings.emoji_picker_search_placeholder), placeHolderTitle = stringResource(CommonStrings.emoji_picker_search_placeholder),
) { emojis -> ) { emojis ->

View file

@ -8,6 +8,6 @@
package io.element.android.features.messages.impl.timeline.components.customreaction.picker package io.element.android.features.messages.impl.timeline.components.customreaction.picker
sealed interface EmojiPickerEvents { sealed interface EmojiPickerEvent {
data class ToggleSearchActive(val isActive: Boolean) : EmojiPickerEvents data class ToggleSearchActive(val isActive: Boolean) : EmojiPickerEvent
} }

View file

@ -93,10 +93,10 @@ class EmojiPickerPresenter(
} }
val isInPreview = LocalInspectionMode.current val isInPreview = LocalInspectionMode.current
fun handleEvent(event: EmojiPickerEvents) { fun handleEvent(event: EmojiPickerEvent) {
when (event) { when (event) {
// For some reason, in preview mode the SearchBar emits this event with an `isActive = true` value automatically // For some reason, in preview mode the SearchBar emits this event with an `isActive = true` value automatically
is EmojiPickerEvents.ToggleSearchActive -> if (!isInPreview) { is EmojiPickerEvent.ToggleSearchActive -> if (!isInPreview) {
isSearchActive = event.isActive isSearchActive = event.isActive
} }
} }

View file

@ -24,7 +24,7 @@ data class EmojiPickerState(
val searchQuery: TextFieldState, val searchQuery: TextFieldState,
val isSearchActive: Boolean, val isSearchActive: Boolean,
val searchResults: SearchBarResultState<ImmutableList<Emoji>>, val searchResults: SearchBarResultState<ImmutableList<Emoji>>,
val eventSink: (EmojiPickerEvents) -> Unit, val eventSink: (EmojiPickerEvent) -> Unit,
) )
/** /**

View file

@ -73,7 +73,7 @@ internal fun anEmojiPickerState(
searchQuery: String = "", searchQuery: String = "",
isSearchActive: Boolean = false, isSearchActive: Boolean = false,
searchResults: SearchBarResultState<ImmutableList<Emoji>> = SearchBarResultState.Initial(), searchResults: SearchBarResultState<ImmutableList<Emoji>> = SearchBarResultState.Initial(),
eventSink: (EmojiPickerEvents) -> Unit = {}, eventSink: (EmojiPickerEvent) -> Unit = {},
) = EmojiPickerState( ) = EmojiPickerState(
categories = categories, categories = categories,
allEmojis = allEmojis, allEmojis = allEmojis,

View file

@ -10,7 +10,7 @@ package io.element.android.features.messages.impl.timeline.components.event
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.TimelineEvent
import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData import io.element.android.features.messages.impl.timeline.components.layout.ContentAvoidingLayoutData
import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories
import io.element.android.features.messages.impl.timeline.di.rememberPresenter import io.element.android.features.messages.impl.timeline.di.rememberPresenter
@ -43,7 +43,7 @@ fun TimelineItemEventContentView(
onShowContentClick: () -> Unit, onShowContentClick: () -> Unit,
onLinkClick: (Link) -> Unit, onLinkClick: (Link) -> Unit,
onLinkLongClick: (Link) -> Unit, onLinkLongClick: (Link) -> Unit,
eventSink: (TimelineEvents.EventFromTimelineItem) -> Unit, eventSink: (TimelineEvent.TimelineItemEvent) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onContentLayoutChange: (ContentAvoidingLayoutData) -> Unit = {}, onContentLayoutChange: (ContentAvoidingLayoutData) -> Unit = {},
) { ) {

View file

@ -11,7 +11,7 @@ package io.element.android.features.messages.impl.timeline.components.event
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameter
import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.TimelineEvent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContentProvider import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContentProvider
import io.element.android.features.poll.api.pollcontent.PollContentView import io.element.android.features.poll.api.pollcontent.PollContentView
@ -23,19 +23,19 @@ import kotlinx.collections.immutable.toImmutableList
@Composable @Composable
fun TimelineItemPollView( fun TimelineItemPollView(
content: TimelineItemPollContent, content: TimelineItemPollContent,
eventSink: (TimelineEvents.TimelineItemPollEvents) -> Unit, eventSink: (TimelineEvent.TimelineItemPollEvent) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
fun onSelectAnswer(pollStartId: EventId, answerId: String) { fun onSelectAnswer(pollStartId: EventId, answerId: String) {
eventSink(TimelineEvents.SelectPollAnswer(pollStartId, answerId)) eventSink(TimelineEvent.SelectPollAnswer(pollStartId, answerId))
} }
fun onEndPoll(pollStartId: EventId) { fun onEndPoll(pollStartId: EventId) {
eventSink(TimelineEvents.EndPoll(pollStartId)) eventSink(TimelineEvent.EndPoll(pollStartId))
} }
fun onEditPoll(pollStartId: EventId) { fun onEditPoll(pollStartId: EventId) {
eventSink(TimelineEvents.EditPoll(pollStartId)) eventSink(TimelineEvent.EditPoll(pollStartId))
} }
PollContentView( PollContentView(

View file

@ -11,7 +11,7 @@ package io.element.android.features.messages.impl.timeline.components.reactionsu
import io.element.android.features.messages.impl.timeline.model.AggregatedReaction import io.element.android.features.messages.impl.timeline.model.AggregatedReaction
import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.EventId
sealed interface ReactionSummaryEvents { sealed interface ReactionSummaryEvent {
data object Clear : ReactionSummaryEvents data object Clear : ReactionSummaryEvent
data class ShowReactionSummary(val eventId: EventId, val reactions: List<AggregatedReaction>, val selectedKey: String) : ReactionSummaryEvents data class ShowReactionSummary(val eventId: EventId, val reactions: List<AggregatedReaction>, val selectedKey: String) : ReactionSummaryEvent
} }

View file

@ -37,14 +37,14 @@ class ReactionSummaryPresenter(
} }
val targetWithAvatars = populateSenderAvatars(members = membersState.roomMembers().orEmpty().toImmutableList(), summary = target.value) val targetWithAvatars = populateSenderAvatars(members = membersState.roomMembers().orEmpty().toImmutableList(), summary = target.value)
fun handleEvent(event: ReactionSummaryEvents) { fun handleEvent(event: ReactionSummaryEvent) {
when (event) { when (event) {
is ReactionSummaryEvents.ShowReactionSummary -> target.value = ReactionSummaryState.Summary( is ReactionSummaryEvent.ShowReactionSummary -> target.value = ReactionSummaryState.Summary(
reactions = event.reactions.toImmutableList(), reactions = event.reactions.toImmutableList(),
selectedKey = event.selectedKey, selectedKey = event.selectedKey,
selectedEventId = event.eventId selectedEventId = event.eventId
) )
ReactionSummaryEvents.Clear -> target.value = null ReactionSummaryEvent.Clear -> target.value = null
} }
} }
return ReactionSummaryState( return ReactionSummaryState(

View file

@ -14,7 +14,7 @@ import kotlinx.collections.immutable.ImmutableList
data class ReactionSummaryState( data class ReactionSummaryState(
val target: Summary?, val target: Summary?,
val eventSink: (ReactionSummaryEvents) -> Unit val eventSink: (ReactionSummaryEvent) -> Unit
) { ) {
data class Summary( data class Summary(
val reactions: ImmutableList<AggregatedReaction>, val reactions: ImmutableList<AggregatedReaction>,

View file

@ -84,7 +84,7 @@ fun ReactionSummaryView(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
fun onDismiss() { fun onDismiss() {
state.eventSink(ReactionSummaryEvents.Clear) state.eventSink(ReactionSummaryEvent.Clear)
} }
if (state.target != null) { if (state.target != null) {

View file

@ -55,7 +55,7 @@ internal fun ReadReceiptBottomSheet(
onDismissRequest = { onDismissRequest = {
coroutineScope.launch { coroutineScope.launch {
sheetState.hide() sheetState.hide()
state.eventSink(ReadReceiptBottomSheetEvents.Dismiss) state.eventSink(ReadReceiptBottomSheetEvent.Dismiss)
} }
} }
) { ) {
@ -64,7 +64,7 @@ internal fun ReadReceiptBottomSheet(
onUserDataClick = { onUserDataClick = {
coroutineScope.launch { coroutineScope.launch {
sheetState.hide() sheetState.hide()
state.eventSink(ReadReceiptBottomSheetEvents.Dismiss) state.eventSink(ReadReceiptBottomSheetEvent.Dismiss)
onUserDataClick.invoke(it) onUserDataClick.invoke(it)
} }
}, },

View file

@ -10,7 +10,7 @@ package io.element.android.features.messages.impl.timeline.components.receipt.bo
import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItem
sealed interface ReadReceiptBottomSheetEvents { sealed interface ReadReceiptBottomSheetEvent {
data class EventSelected(val event: TimelineItem.Event) : ReadReceiptBottomSheetEvents data class EventSelected(val event: TimelineItem.Event) : ReadReceiptBottomSheetEvent
data object Dismiss : ReadReceiptBottomSheetEvents data object Dismiss : ReadReceiptBottomSheetEvent
} }

View file

@ -23,13 +23,13 @@ class ReadReceiptBottomSheetPresenter : Presenter<ReadReceiptBottomSheetState> {
override fun present(): ReadReceiptBottomSheetState { override fun present(): ReadReceiptBottomSheetState {
var selectedEvent: TimelineItem.Event? by remember { mutableStateOf(null) } var selectedEvent: TimelineItem.Event? by remember { mutableStateOf(null) }
fun handleEvent(event: ReadReceiptBottomSheetEvents) { fun handleEvent(event: ReadReceiptBottomSheetEvent) {
@Suppress("LiftReturnOrAssignment") @Suppress("LiftReturnOrAssignment")
when (event) { when (event) {
is ReadReceiptBottomSheetEvents.EventSelected -> { is ReadReceiptBottomSheetEvent.EventSelected -> {
selectedEvent = event.event selectedEvent = event.event
} }
ReadReceiptBottomSheetEvents.Dismiss -> { ReadReceiptBottomSheetEvent.Dismiss -> {
selectedEvent = null selectedEvent = null
} }
} }

View file

@ -12,5 +12,5 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem
data class ReadReceiptBottomSheetState( data class ReadReceiptBottomSheetState(
val selectedEvent: TimelineItem.Event?, val selectedEvent: TimelineItem.Event?,
val eventSink: (ReadReceiptBottomSheetEvents) -> Unit, val eventSink: (ReadReceiptBottomSheetEvent) -> Unit,
) )

View file

@ -13,7 +13,7 @@ package io.element.android.features.messages.impl
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import im.vector.app.features.analytics.plan.PinUnpinAction import im.vector.app.features.analytics.plan.PinUnpinAction
import io.element.android.features.messages.impl.actionlist.ActionListEvents import io.element.android.features.messages.impl.actionlist.ActionListEvent
import io.element.android.features.messages.impl.actionlist.ActionListState import io.element.android.features.messages.impl.actionlist.ActionListState
import io.element.android.features.messages.impl.actionlist.anActionListState import io.element.android.features.messages.impl.actionlist.anActionListState
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
@ -28,7 +28,7 @@ import io.element.android.features.messages.impl.pinned.banner.aLoadedPinnedMess
import io.element.android.features.messages.impl.timeline.FakeMarkAsFullyRead import io.element.android.features.messages.impl.timeline.FakeMarkAsFullyRead
import io.element.android.features.messages.impl.timeline.MarkAsFullyRead import io.element.android.features.messages.impl.timeline.MarkAsFullyRead
import io.element.android.features.messages.impl.timeline.TimelineController import io.element.android.features.messages.impl.timeline.TimelineController
import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.TimelineEvent
import io.element.android.features.messages.impl.timeline.aTimelineState import io.element.android.features.messages.impl.timeline.aTimelineState
import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo import io.element.android.features.messages.impl.timeline.model.TimelineItemThreadInfo
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
@ -185,14 +185,14 @@ class MessagesPresenterTest {
presenter.testWithLifecycleOwner { presenter.testWithLifecycleOwner {
skipItems(1) skipItems(1)
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId())) initialState.eventSink(MessagesEvent.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId()))
advanceUntilIdle() advanceUntilIdle()
assert(toggleReactionSuccess) assert(toggleReactionSuccess)
.isCalledOnce() .isCalledOnce()
.with(value("👍"), value(AN_EVENT_ID.toEventOrTransactionId())) .with(value("👍"), value(AN_EVENT_ID.toEventOrTransactionId()))
// No crashes when sending a reaction failed // No crashes when sending a reaction failed
timeline.toggleReactionLambda = toggleReactionFailure timeline.toggleReactionLambda = toggleReactionFailure
initialState.eventSink(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId())) initialState.eventSink(MessagesEvent.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId()))
advanceUntilIdle() advanceUntilIdle()
assert(toggleReactionFailure) assert(toggleReactionFailure)
.isCalledOnce() .isCalledOnce()
@ -230,8 +230,8 @@ class MessagesPresenterTest {
) )
presenter.testWithLifecycleOwner { presenter.testWithLifecycleOwner {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId())) initialState.eventSink(MessagesEvent.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId()))
initialState.eventSink(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId())) initialState.eventSink(MessagesEvent.ToggleReaction("👍", AN_EVENT_ID.toEventOrTransactionId()))
advanceUntilIdle() advanceUntilIdle()
assert(toggleReactionSuccess) assert(toggleReactionSuccess)
.isCalledExactly(2) .isCalledExactly(2)
@ -253,7 +253,7 @@ class MessagesPresenterTest {
val presenter = createMessagesPresenter(navigator = navigator) val presenter = createMessagesPresenter(navigator = navigator)
presenter.testWithLifecycleOwner { presenter.testWithLifecycleOwner {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Forward, aMessageEvent())) initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Forward, aMessageEvent()))
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None) assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
onForwardEventClickLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID)) onForwardEventClickLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID))
} }
@ -266,7 +266,7 @@ class MessagesPresenterTest {
val presenter = createMessagesPresenter(clipboardHelper = clipboardHelper) val presenter = createMessagesPresenter(clipboardHelper = clipboardHelper)
presenter.testWithLifecycleOwner { presenter.testWithLifecycleOwner {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.CopyText, event)) initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.CopyText, event))
skipItems(2) skipItems(2)
assertThat(clipboardHelper.clipboardContents).isEqualTo((event.content as TimelineItemTextContent).body) assertThat(clipboardHelper.clipboardContents).isEqualTo((event.content as TimelineItemTextContent).body)
} }
@ -289,7 +289,7 @@ class MessagesPresenterTest {
) )
presenter.testWithLifecycleOwner { presenter.testWithLifecycleOwner {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.CopyLink, event)) initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.CopyLink, event))
skipItems(2) skipItems(2)
assertThat(clipboardHelper.clipboardContents).isEqualTo("a link") assertThat(clipboardHelper.clipboardContents).isEqualTo("a link")
} }
@ -303,7 +303,7 @@ class MessagesPresenterTest {
) )
presenter.testWithLifecycleOwner { presenter.testWithLifecycleOwner {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, aMessageEvent())) initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Reply, aMessageEvent()))
awaitItem() awaitItem()
composerRecorder.assertSingle( composerRecorder.assertSingle(
MessageComposerEvent.SetMode( MessageComposerEvent.SetMode(
@ -321,7 +321,7 @@ class MessagesPresenterTest {
val presenter = createMessagesPresenter() val presenter = createMessagesPresenter()
presenter.testWithLifecycleOwner { presenter.testWithLifecycleOwner {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, aMessageEvent(eventId = null))) initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Reply, aMessageEvent(eventId = null)))
skipItems(1) skipItems(1)
} }
} }
@ -354,7 +354,7 @@ class MessagesPresenterTest {
formattedFileSize = "4MB" formattedFileSize = "4MB"
) )
) )
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, mediaMessage)) initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Reply, mediaMessage))
awaitItem() awaitItem()
composerRecorder.assertSingle( composerRecorder.assertSingle(
MessageComposerEvent.SetMode( MessageComposerEvent.SetMode(
@ -396,7 +396,7 @@ class MessagesPresenterTest {
formattedFileSize = "50MB" formattedFileSize = "50MB"
) )
) )
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, mediaMessage)) initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Reply, mediaMessage))
awaitItem() awaitItem()
composerRecorder.assertSingle( composerRecorder.assertSingle(
MessageComposerEvent.SetMode( MessageComposerEvent.SetMode(
@ -431,7 +431,7 @@ class MessagesPresenterTest {
fileExtension = "pdf", fileExtension = "pdf",
) )
) )
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, mediaMessage)) initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Reply, mediaMessage))
awaitItem() awaitItem()
composerRecorder.assertSingle( composerRecorder.assertSingle(
MessageComposerEvent.SetMode( MessageComposerEvent.SetMode(
@ -452,7 +452,7 @@ class MessagesPresenterTest {
) )
presenter.testWithLifecycleOwner { presenter.testWithLifecycleOwner {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Edit, aMessageEvent())) initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Edit, aMessageEvent()))
awaitItem() awaitItem()
composerRecorder.assertSingle( composerRecorder.assertSingle(
MessageComposerEvent.SetMode( MessageComposerEvent.SetMode(
@ -474,7 +474,7 @@ class MessagesPresenterTest {
val presenter = createMessagesPresenter(navigator = navigator) val presenter = createMessagesPresenter(navigator = navigator)
presenter.testWithLifecycleOwner { presenter.testWithLifecycleOwner {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.EditPoll, aMessageEvent(content = aTimelineItemPollContent()))) initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.EditPoll, aMessageEvent(content = aTimelineItemPollContent())))
awaitItem() awaitItem()
onEditPollClickLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID)) onEditPollClickLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID))
} }
@ -482,13 +482,13 @@ class MessagesPresenterTest {
@Test @Test
fun `present - handle action end poll`() = runTest { fun `present - handle action end poll`() = runTest {
val timelineEventSink = EventsRecorder<TimelineEvents>() val timelineEventSink = EventsRecorder<TimelineEvent>()
val presenter = createMessagesPresenter(timelineEventSink = timelineEventSink) val presenter = createMessagesPresenter(timelineEventSink = timelineEventSink)
presenter.testWithLifecycleOwner { presenter.testWithLifecycleOwner {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.EndPoll, aMessageEvent(content = aTimelineItemPollContent()))) initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.EndPoll, aMessageEvent(content = aTimelineItemPollContent())))
delay(1) delay(1)
timelineEventSink.assertSingle(TimelineEvents.EndPoll(AN_EVENT_ID)) timelineEventSink.assertSingle(TimelineEvent.EndPoll(AN_EVENT_ID))
cancelAndIgnoreRemainingEvents() cancelAndIgnoreRemainingEvents()
} }
} }
@ -516,7 +516,7 @@ class MessagesPresenterTest {
presenter.testWithLifecycleOwner { presenter.testWithLifecycleOwner {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent() val messageEvent = aMessageEvent()
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Redact, messageEvent)) initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Redact, messageEvent))
awaitItem() awaitItem()
assert(redactEventLambda) assert(redactEventLambda)
.isCalledOnce() .isCalledOnce()
@ -533,22 +533,12 @@ class MessagesPresenterTest {
val presenter = createMessagesPresenter(navigator = navigator) val presenter = createMessagesPresenter(navigator = navigator)
presenter.testWithLifecycleOwner { presenter.testWithLifecycleOwner {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.ReportContent, aMessageEvent())) initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.ReportContent, aMessageEvent()))
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None) assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
onReportContentClickLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID), value(A_USER_ID)) onReportContentClickLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID), value(A_USER_ID))
} }
} }
@Test
fun `present - handle dismiss action`() = runTest {
val presenter = createMessagesPresenter()
presenter.testWithLifecycleOwner {
val initialState = awaitItem()
initialState.eventSink(MessagesEvents.Dismiss)
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
}
}
@Test @Test
fun `present - handle action show developer info`() = runTest { fun `present - handle action show developer info`() = runTest {
val onShowEventDebugInfoClickLambda = lambdaRecorder { _: EventId?, _: TimelineItemDebugInfo -> } val onShowEventDebugInfoClickLambda = lambdaRecorder { _: EventId?, _: TimelineItemDebugInfo -> }
@ -558,7 +548,7 @@ class MessagesPresenterTest {
val presenter = createMessagesPresenter(navigator = navigator) val presenter = createMessagesPresenter(navigator = navigator)
presenter.testWithLifecycleOwner { presenter.testWithLifecycleOwner {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.ViewSource, aMessageEvent())) initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.ViewSource, aMessageEvent()))
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None) assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
onShowEventDebugInfoClickLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID), value(aTimelineItemDebugInfo())) onShowEventDebugInfoClickLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID), value(aTimelineItemDebugInfo()))
} }
@ -586,7 +576,7 @@ class MessagesPresenterTest {
val focusedState = awaitItem() val focusedState = awaitItem()
assertThat(focusedState.showReinvitePrompt).isTrue() assertThat(focusedState.showReinvitePrompt).isTrue()
// If it's dismissed then we stop showing the alert // If it's dismissed then we stop showing the alert
initialState.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Cancel)) initialState.eventSink(MessagesEvent.InviteDialogDismissed(InviteDialogAction.Cancel))
skipItems(1) skipItems(1)
val dismissedState = awaitItem() val dismissedState = awaitItem()
assertThat(dismissedState.showReinvitePrompt).isFalse() assertThat(dismissedState.showReinvitePrompt).isFalse()
@ -658,7 +648,7 @@ class MessagesPresenterTest {
val presenter = createMessagesPresenter(joinedRoom = room) val presenter = createMessagesPresenter(joinedRoom = room)
presenter.testWithLifecycleOwner { presenter.testWithLifecycleOwner {
val initialState = consumeItemsUntilTimeout().last() val initialState = consumeItemsUntilTimeout().last()
initialState.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Invite)) initialState.eventSink(MessagesEvent.InviteDialogDismissed(InviteDialogAction.Invite))
skipItems(1) skipItems(1)
val loadingState = awaitItem() val loadingState = awaitItem()
assertThat(loadingState.inviteProgress.isLoading()).isTrue() assertThat(loadingState.inviteProgress.isLoading()).isTrue()
@ -690,7 +680,7 @@ class MessagesPresenterTest {
val presenter = createMessagesPresenter(joinedRoom = room) val presenter = createMessagesPresenter(joinedRoom = room)
presenter.testWithLifecycleOwner { presenter.testWithLifecycleOwner {
val initialState = consumeItemsUntilTimeout().last() val initialState = consumeItemsUntilTimeout().last()
initialState.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Invite)) initialState.eventSink(MessagesEvent.InviteDialogDismissed(InviteDialogAction.Invite))
skipItems(1) skipItems(1)
val loadingState = consumeItemsUntilPredicate { state -> val loadingState = consumeItemsUntilPredicate { state ->
state.inviteProgress.isLoading() state.inviteProgress.isLoading()
@ -714,7 +704,7 @@ class MessagesPresenterTest {
val presenter = createMessagesPresenter(joinedRoom = room) val presenter = createMessagesPresenter(joinedRoom = room)
presenter.testWithLifecycleOwner { presenter.testWithLifecycleOwner {
val initialState = consumeItemsUntilTimeout().last() val initialState = consumeItemsUntilTimeout().last()
initialState.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Invite)) initialState.eventSink(MessagesEvent.InviteDialogDismissed(InviteDialogAction.Invite))
skipItems(1) skipItems(1)
val loadingState = awaitItem() val loadingState = awaitItem()
assertThat(loadingState.inviteProgress.isLoading()).isTrue() assertThat(loadingState.inviteProgress.isLoading()).isTrue()
@ -743,7 +733,7 @@ class MessagesPresenterTest {
val presenter = createMessagesPresenter(joinedRoom = room) val presenter = createMessagesPresenter(joinedRoom = room)
presenter.testWithLifecycleOwner { presenter.testWithLifecycleOwner {
val initialState = consumeItemsUntilTimeout().last() val initialState = consumeItemsUntilTimeout().last()
initialState.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Invite)) initialState.eventSink(MessagesEvent.InviteDialogDismissed(InviteDialogAction.Invite))
val loadingState = consumeItemsUntilPredicate { state -> val loadingState = consumeItemsUntilPredicate { state ->
state.inviteProgress.isLoading() state.inviteProgress.isLoading()
@ -840,7 +830,7 @@ class MessagesPresenterTest {
val poll = aMessageEvent( val poll = aMessageEvent(
content = aTimelineItemPollContent() content = aTimelineItemPollContent()
) )
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, poll)) initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Reply, poll))
skipItems(1) skipItems(1)
composerRecorder.assertSingle( composerRecorder.assertSingle(
MessageComposerEvent.SetMode( MessageComposerEvent.SetMode(
@ -878,11 +868,11 @@ class MessagesPresenterTest {
val initialState = awaitItem() val initialState = awaitItem()
timeline.pinEventLambda = successPinEventLambda timeline.pinEventLambda = successPinEventLambda
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Pin, messageEvent)) initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Pin, messageEvent))
assert(successPinEventLambda).isCalledOnce().with(value(messageEvent.eventId)) assert(successPinEventLambda).isCalledOnce().with(value(messageEvent.eventId))
timeline.pinEventLambda = failurePinEventLambda timeline.pinEventLambda = failurePinEventLambda
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Pin, messageEvent)) initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Pin, messageEvent))
assert(failurePinEventLambda).isCalledOnce().with(value(messageEvent.eventId)) assert(failurePinEventLambda).isCalledOnce().with(value(messageEvent.eventId))
skipItems(1) skipItems(1)
assertThat(awaitItem().snackbarMessage).isNotNull() assertThat(awaitItem().snackbarMessage).isNotNull()
@ -918,11 +908,11 @@ class MessagesPresenterTest {
val initialState = awaitItem() val initialState = awaitItem()
timeline.unpinEventLambda = successUnpinEventLambda timeline.unpinEventLambda = successUnpinEventLambda
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Unpin, messageEvent)) initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Unpin, messageEvent))
assert(successUnpinEventLambda).isCalledOnce().with(value(messageEvent.eventId)) assert(successUnpinEventLambda).isCalledOnce().with(value(messageEvent.eventId))
timeline.unpinEventLambda = failureUnpinEventLambda timeline.unpinEventLambda = failureUnpinEventLambda
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Unpin, messageEvent)) initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.Unpin, messageEvent))
assert(failureUnpinEventLambda).isCalledOnce().with(value(messageEvent.eventId)) assert(failureUnpinEventLambda).isCalledOnce().with(value(messageEvent.eventId))
skipItems(1) skipItems(1)
assertThat(awaitItem().snackbarMessage).isNotNull() assertThat(awaitItem().snackbarMessage).isNotNull()
@ -946,7 +936,7 @@ class MessagesPresenterTest {
) )
presenter.testWithLifecycleOwner { presenter.testWithLifecycleOwner {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.EditCaption, messageEvent)) initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.EditCaption, messageEvent))
awaitItem() awaitItem()
composerRecorder.assertSingle( composerRecorder.assertSingle(
MessageComposerEvent.SetMode( MessageComposerEvent.SetMode(
@ -972,7 +962,7 @@ class MessagesPresenterTest {
) )
presenter.testWithLifecycleOwner { presenter.testWithLifecycleOwner {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.AddCaption, messageEvent)) initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.AddCaption, messageEvent))
awaitItem() awaitItem()
composerRecorder.assertSingle( composerRecorder.assertSingle(
MessageComposerEvent.SetMode( MessageComposerEvent.SetMode(
@ -1010,7 +1000,7 @@ class MessagesPresenterTest {
presenter.testWithLifecycleOwner { presenter.testWithLifecycleOwner {
skipItems(1) skipItems(1)
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.RemoveCaption, messageEvent)) initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.RemoveCaption, messageEvent))
editCaptionLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID.toEventOrTransactionId()), value(null), value(null)) editCaptionLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID.toEventOrTransactionId()), value(null), value(null))
} }
} }
@ -1024,7 +1014,7 @@ class MessagesPresenterTest {
presenter.testWithLifecycleOwner { presenter.testWithLifecycleOwner {
skipItems(1) skipItems(1)
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.ViewInTimeline, messageEvent)) initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.ViewInTimeline, messageEvent))
// No op! // No op!
} }
} }
@ -1118,7 +1108,7 @@ class MessagesPresenterTest {
presenter.testWithLifecycleOwner { presenter.testWithLifecycleOwner {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink( initialState.eventSink(
MessagesEvents.HandleAction( MessagesEvent.HandleAction(
action = TimelineItemAction.ReplyInThread, action = TimelineItemAction.ReplyInThread,
event = aMessageEvent(threadInfo = TimelineItemThreadInfo.ThreadResponse(A_THREAD_ID)) event = aMessageEvent(threadInfo = TimelineItemThreadInfo.ThreadResponse(A_THREAD_ID))
) )
@ -1140,7 +1130,7 @@ class MessagesPresenterTest {
presenter.testWithLifecycleOwner { presenter.testWithLifecycleOwner {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink( initialState.eventSink(
MessagesEvents.HandleAction( MessagesEvent.HandleAction(
action = TimelineItemAction.ReplyInThread, action = TimelineItemAction.ReplyInThread,
event = aMessageEvent( event = aMessageEvent(
// The event id will be used as the thread id instead // The event id will be used as the thread id instead
@ -1165,7 +1155,7 @@ class MessagesPresenterTest {
) )
presenter.testWithLifecycleOwner { presenter.testWithLifecycleOwner {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.ReplyInThread, aMessageEvent())) initialState.eventSink(MessagesEvent.HandleAction(TimelineItemAction.ReplyInThread, aMessageEvent()))
awaitItem() awaitItem()
composerRecorder.assertSingle( composerRecorder.assertSingle(
MessageComposerEvent.SetMode( MessageComposerEvent.SetMode(
@ -1192,7 +1182,7 @@ class MessagesPresenterTest {
) )
presenter.testWithLifecycleOwner { presenter.testWithLifecycleOwner {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(MessagesEvents.MarkAsFullyReadAndExit) initialState.eventSink(MessagesEvent.MarkAsFullyReadAndExit)
runCurrent() runCurrent()
@ -1216,7 +1206,7 @@ class MessagesPresenterTest {
) )
presenter.testWithLifecycleOwner { presenter.testWithLifecycleOwner {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(MessagesEvents.MarkAsFullyReadAndExit) initialState.eventSink(MessagesEvent.MarkAsFullyReadAndExit)
runCurrent() runCurrent()
@ -1273,7 +1263,7 @@ class MessagesPresenterTest {
navigator: FakeMessagesNavigator = FakeMessagesNavigator(), navigator: FakeMessagesNavigator = FakeMessagesNavigator(),
clipboardHelper: FakeClipboardHelper = FakeClipboardHelper(), clipboardHelper: FakeClipboardHelper = FakeClipboardHelper(),
analyticsService: FakeAnalyticsService = FakeAnalyticsService(), analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
timelineEventSink: (TimelineEvents) -> Unit = {}, timelineEventSink: (TimelineEvent) -> Unit = {},
permalinkParser: PermalinkParser = FakePermalinkParser(), permalinkParser: PermalinkParser = FakePermalinkParser(),
messageComposerPresenter: Presenter<MessageComposerState> = Presenter { messageComposerPresenter: Presenter<MessageComposerState> = Presenter {
aMessageComposerState( aMessageComposerState(
@ -1286,7 +1276,7 @@ class MessagesPresenterTest {
}, },
encryptionService: FakeEncryptionService = FakeEncryptionService(), encryptionService: FakeEncryptionService = FakeEncryptionService(),
featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(), featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(),
actionListEventSink: (ActionListEvents) -> Unit = {}, actionListEventSink: (ActionListEvent) -> Unit = {},
addRecentEmoji: AddRecentEmoji = AddRecentEmoji { _ -> lambdaError() }, addRecentEmoji: AddRecentEmoji = AddRecentEmoji { _ -> lambdaError() },
markAsFullyRead: MarkAsFullyRead = FakeMarkAsFullyRead(), markAsFullyRead: MarkAsFullyRead = FakeMarkAsFullyRead(),
): MessagesPresenter { ): MessagesPresenter {

View file

@ -30,7 +30,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.emojibasebindings.Emoji import io.element.android.emojibasebindings.Emoji
import io.element.android.emojibasebindings.EmojibaseCategory import io.element.android.emojibasebindings.EmojibaseCategory
import io.element.android.emojibasebindings.EmojibaseStore import io.element.android.emojibasebindings.EmojibaseStore
import io.element.android.features.messages.impl.actionlist.ActionListEvents import io.element.android.features.messages.impl.actionlist.ActionListEvent
import io.element.android.features.messages.impl.actionlist.ActionListState import io.element.android.features.messages.impl.actionlist.ActionListState
import io.element.android.features.messages.impl.actionlist.anActionListState import io.element.android.features.messages.impl.actionlist.anActionListState
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
@ -40,17 +40,17 @@ import io.element.android.features.messages.impl.messagecomposer.aMessageCompose
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerItem import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerItem
import io.element.android.features.messages.impl.pinned.banner.aLoadedPinnedMessagesBannerState import io.element.android.features.messages.impl.pinned.banner.aLoadedPinnedMessagesBannerState
import io.element.android.features.messages.impl.timeline.FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS import io.element.android.features.messages.impl.timeline.FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS
import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.TimelineEvent
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
import io.element.android.features.messages.impl.timeline.aTimelineItemList import io.element.android.features.messages.impl.timeline.aTimelineItemList
import io.element.android.features.messages.impl.timeline.aTimelineItemReadReceipts import io.element.android.features.messages.impl.timeline.aTimelineItemReadReceipts
import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo
import io.element.android.features.messages.impl.timeline.aTimelineState import io.element.android.features.messages.impl.timeline.aTimelineState
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvents import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionEvent
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryEvents import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryEvent
import io.element.android.features.messages.impl.timeline.components.receipt.aReadReceiptData import io.element.android.features.messages.impl.timeline.components.receipt.aReadReceiptData
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetEvents 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.TimelineItem
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
@ -88,7 +88,7 @@ class MessagesViewTest {
@Test @Test
fun `clicking on back invoke expected callback`() { fun `clicking on back invoke expected callback`() {
val eventsRecorder = EventsRecorder<MessagesEvents>(expectEvents = false) val eventsRecorder = EventsRecorder<MessagesEvent>(expectEvents = false)
val state = aMessagesState( val state = aMessagesState(
eventSink = eventsRecorder eventSink = eventsRecorder
) )
@ -103,7 +103,7 @@ class MessagesViewTest {
@Test @Test
fun `clicking on room name invoke expected callback`() { fun `clicking on room name invoke expected callback`() {
val eventsRecorder = EventsRecorder<MessagesEvents>(expectEvents = false) val eventsRecorder = EventsRecorder<MessagesEvent>(expectEvents = false)
val state = aMessagesState( val state = aMessagesState(
eventSink = eventsRecorder eventSink = eventsRecorder
) )
@ -118,7 +118,7 @@ class MessagesViewTest {
@Test @Test
fun `clicking on join call invoke expected callback`() { fun `clicking on join call invoke expected callback`() {
val eventsRecorder = EventsRecorder<MessagesEvents>(expectEvents = false) val eventsRecorder = EventsRecorder<MessagesEvent>(expectEvents = false)
val state = aMessagesState( val state = aMessagesState(
eventSink = eventsRecorder eventSink = eventsRecorder
) )
@ -134,7 +134,7 @@ class MessagesViewTest {
@Test @Test
fun `clicking on an Event invoke expected callback`() { fun `clicking on an Event invoke expected callback`() {
val eventsRecorder = EventsRecorder<MessagesEvents>(expectEvents = false) val eventsRecorder = EventsRecorder<MessagesEvent>(expectEvents = false)
val state = aMessagesState( val state = aMessagesState(
timelineState = aTimelineState( timelineState = aTimelineState(
timelineItems = aTimelineItemList(aTimelineItemTextContent()), timelineItems = aTimelineItemList(aTimelineItemTextContent()),
@ -183,7 +183,7 @@ class MessagesViewTest {
userHasPermissionToSendReaction: Boolean = false, userHasPermissionToSendReaction: Boolean = false,
userCanPinEvent: Boolean = false, userCanPinEvent: Boolean = false,
) { ) {
val eventsRecorder = EventsRecorder<ActionListEvents>() val eventsRecorder = EventsRecorder<ActionListEvent>()
val state = aMessagesState( val state = aMessagesState(
actionListState = anActionListState( actionListState = anActionListState(
eventSink = eventsRecorder eventSink = eventsRecorder
@ -206,7 +206,7 @@ class MessagesViewTest {
// Cannot perform click on "Text", it's not detected. Use tag instead // Cannot perform click on "Text", it's not detected. Use tag instead
rule.onAllNodesWithTag(TestTags.messageBubble.value).onFirst().performTouchInput { longClick() } rule.onAllNodesWithTag(TestTags.messageBubble.value).onFirst().performTouchInput { longClick() }
eventsRecorder.assertSingle( eventsRecorder.assertSingle(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = timelineItem, event = timelineItem,
userEventPermissions = state.userEventPermissions, userEventPermissions = state.userEventPermissions,
) )
@ -216,7 +216,7 @@ class MessagesViewTest {
@Test @Test
@Config(qualifiers = "h1024dp") @Config(qualifiers = "h1024dp")
fun `clicking on a read receipt list emits the expected Event`() { fun `clicking on a read receipt list emits the expected Event`() {
val eventsRecorder = EventsRecorder<ReadReceiptBottomSheetEvents>() val eventsRecorder = EventsRecorder<ReadReceiptBottomSheetEvent>()
val state = aMessagesState( val state = aMessagesState(
timelineState = aTimelineState( timelineState = aTimelineState(
renderReadReceipts = true, renderReadReceipts = true,
@ -239,7 +239,7 @@ class MessagesViewTest {
state = state, state = state,
) )
rule.onNodeWithTag(TestTags.messageReadReceipts.value, useUnmergedTree = true).performClick() rule.onNodeWithTag(TestTags.messageReadReceipts.value, useUnmergedTree = true).performClick()
eventsRecorder.assertSingle(ReadReceiptBottomSheetEvents.EventSelected(timelineItem)) eventsRecorder.assertSingle(ReadReceiptBottomSheetEvent.EventSelected(timelineItem))
} }
@Test @Test
@ -253,7 +253,7 @@ class MessagesViewTest {
} }
private fun swipeTest(userHasPermissionToSendMessage: Boolean) { private fun swipeTest(userHasPermissionToSendMessage: Boolean) {
val eventsRecorder = EventsRecorder<MessagesEvents>() val eventsRecorder = EventsRecorder<MessagesEvent>()
val canBeRepliedEvent = aTimelineItemEvent(canBeRepliedTo = true) val canBeRepliedEvent = aTimelineItemEvent(canBeRepliedTo = true)
val cannotBeRepliedEvent = aTimelineItemEvent(canBeRepliedTo = false) val cannotBeRepliedEvent = aTimelineItemEvent(canBeRepliedTo = false)
val state = aMessagesState( val state = aMessagesState(
@ -273,7 +273,7 @@ class MessagesViewTest {
onLast().performTouchInput { swipeRight(endX = 200f) } onLast().performTouchInput { swipeRight(endX = 200f) }
} }
if (userHasPermissionToSendMessage) { if (userHasPermissionToSendMessage) {
eventsRecorder.assertSingle(MessagesEvents.HandleAction(TimelineItemAction.Reply, canBeRepliedEvent)) eventsRecorder.assertSingle(MessagesEvent.HandleAction(TimelineItemAction.Reply, canBeRepliedEvent))
} else { } else {
eventsRecorder.assertEmpty() eventsRecorder.assertEmpty()
} }
@ -281,7 +281,7 @@ class MessagesViewTest {
@Test @Test
fun `clicking on send location invoke expected callback`() { fun `clicking on send location invoke expected callback`() {
val eventsRecorder = EventsRecorder<MessagesEvents>(expectEvents = false) val eventsRecorder = EventsRecorder<MessagesEvent>(expectEvents = false)
val state = aMessagesState( val state = aMessagesState(
composerState = aMessageComposerState( composerState = aMessageComposerState(
showAttachmentSourcePicker = true showAttachmentSourcePicker = true
@ -299,7 +299,7 @@ class MessagesViewTest {
@Test @Test
fun `clicking on create poll invoke expected callback`() { fun `clicking on create poll invoke expected callback`() {
val eventsRecorder = EventsRecorder<MessagesEvents>(expectEvents = false) val eventsRecorder = EventsRecorder<MessagesEvent>(expectEvents = false)
val state = aMessagesState( val state = aMessagesState(
composerState = aMessageComposerState( composerState = aMessageComposerState(
showAttachmentSourcePicker = true showAttachmentSourcePicker = true
@ -319,7 +319,7 @@ class MessagesViewTest {
@Test @Test
@Config(qualifiers = "h1024dp") @Config(qualifiers = "h1024dp")
fun `clicking on the avatar of the sender of an Event emits the expected event`() { fun `clicking on the avatar of the sender of an Event emits the expected event`() {
val eventsRecorder = EventsRecorder<MessagesEvents>() val eventsRecorder = EventsRecorder<MessagesEvent>()
val state = aMessagesState( val state = aMessagesState(
eventSink = eventsRecorder eventSink = eventsRecorder
) )
@ -327,7 +327,7 @@ class MessagesViewTest {
rule.setMessagesView(state = state) rule.setMessagesView(state = state)
rule.onNodeWithTag(TestTags.timelineItemSenderAvatar.value, useUnmergedTree = true).performClick() rule.onNodeWithTag(TestTags.timelineItemSenderAvatar.value, useUnmergedTree = true).performClick()
eventsRecorder.assertSingle( eventsRecorder.assertSingle(
MessagesEvents.OnUserClicked( MessagesEvent.OnUserClicked(
MatrixUser( MatrixUser(
userId = timelineEvent.senderId, userId = timelineEvent.senderId,
displayName = timelineEvent.senderProfile.getDisplayName(), displayName = timelineEvent.senderProfile.getDisplayName(),
@ -340,13 +340,13 @@ class MessagesViewTest {
@Test @Test
@Config(qualifiers = "h1024dp") @Config(qualifiers = "h1024dp")
fun `clicking on the display name of the sender of an Event emits expected event`() { fun `clicking on the display name of the sender of an Event emits expected event`() {
val eventsRecorder = EventsRecorder<MessagesEvents>() val eventsRecorder = EventsRecorder<MessagesEvent>()
val state = aMessagesState(eventSink = eventsRecorder) val state = aMessagesState(eventSink = eventsRecorder)
val timelineEvent = state.timelineState.timelineItems.filterIsInstance<TimelineItem.Event>().first() val timelineEvent = state.timelineState.timelineItems.filterIsInstance<TimelineItem.Event>().first()
rule.setMessagesView(state = state) rule.setMessagesView(state = state)
rule.onNodeWithTag(TestTags.timelineItemSenderAvatar.value, useUnmergedTree = true).performClick() rule.onNodeWithTag(TestTags.timelineItemSenderAvatar.value, useUnmergedTree = true).performClick()
eventsRecorder.assertSingle( eventsRecorder.assertSingle(
MessagesEvents.OnUserClicked( MessagesEvent.OnUserClicked(
MatrixUser( MatrixUser(
userId = timelineEvent.senderId, userId = timelineEvent.senderId,
displayName = timelineEvent.senderProfile.getDisplayName(), displayName = timelineEvent.senderProfile.getDisplayName(),
@ -358,7 +358,7 @@ class MessagesViewTest {
@Test @Test
fun `selecting a action on a message emits the expected Event`() { fun `selecting a action on a message emits the expected Event`() {
val eventsRecorder = EventsRecorder<MessagesEvents>() val eventsRecorder = EventsRecorder<MessagesEvent>()
val state = aMessagesState( val state = aMessagesState(
eventSink = eventsRecorder eventSink = eventsRecorder
) )
@ -381,12 +381,12 @@ class MessagesViewTest {
rule.clickOn(CommonStrings.action_edit) rule.clickOn(CommonStrings.action_edit)
// Give time for the close animation to complete // Give time for the close animation to complete
rule.mainClock.advanceTimeBy(milliseconds = 1_000) rule.mainClock.advanceTimeBy(milliseconds = 1_000)
eventsRecorder.assertSingle(MessagesEvents.HandleAction(TimelineItemAction.Edit, timelineItem)) eventsRecorder.assertSingle(MessagesEvent.HandleAction(TimelineItemAction.Edit, timelineItem))
} }
@Test @Test
fun `clicking on a reaction emits the expected Event`() { fun `clicking on a reaction emits the expected Event`() {
val eventsRecorder = EventsRecorder<MessagesEvents>() val eventsRecorder = EventsRecorder<MessagesEvent>()
val state = aMessagesState( val state = aMessagesState(
timelineState = aTimelineState( timelineState = aTimelineState(
timelineItems = aTimelineItemList(aTimelineItemTextContent()), timelineItems = aTimelineItemList(aTimelineItemTextContent()),
@ -401,12 +401,12 @@ class MessagesViewTest {
text = "👍️", text = "👍️",
useUnmergedTree = true, useUnmergedTree = true,
).onFirst().performClick() ).onFirst().performClick()
eventsRecorder.assertSingle(MessagesEvents.ToggleReaction("👍️", timelineItem.eventOrTransactionId)) eventsRecorder.assertSingle(MessagesEvent.ToggleReaction("👍️", timelineItem.eventOrTransactionId))
} }
@Test @Test
fun `long clicking on a reaction emits the expected Event`() { fun `long clicking on a reaction emits the expected Event`() {
val eventsRecorder = EventsRecorder<ReactionSummaryEvents>() val eventsRecorder = EventsRecorder<ReactionSummaryEvent>()
val state = aMessagesState( val state = aMessagesState(
timelineState = aTimelineState( timelineState = aTimelineState(
timelineItems = aTimelineItemList(aTimelineItemTextContent()), timelineItems = aTimelineItemList(aTimelineItemTextContent()),
@ -424,12 +424,12 @@ class MessagesViewTest {
text = "👍️", text = "👍️",
useUnmergedTree = true, useUnmergedTree = true,
).onFirst().performTouchInput { longClick() } ).onFirst().performTouchInput { longClick() }
eventsRecorder.assertSingle(ReactionSummaryEvents.ShowReactionSummary(timelineItem.eventId!!, timelineItem.reactionsState.reactions, "👍️")) eventsRecorder.assertSingle(ReactionSummaryEvent.ShowReactionSummary(timelineItem.eventId!!, timelineItem.reactionsState.reactions, "👍️"))
} }
@Test @Test
fun `clicking on more reaction emits the expected Event`() { fun `clicking on more reaction emits the expected Event`() {
val eventsRecorder = EventsRecorder<CustomReactionEvents>() val eventsRecorder = EventsRecorder<CustomReactionEvent>()
val state = aMessagesState( val state = aMessagesState(
timelineState = aTimelineState( timelineState = aTimelineState(
timelineItems = aTimelineItemList(aTimelineItemTextContent()), timelineItems = aTimelineItemList(aTimelineItemTextContent()),
@ -444,12 +444,12 @@ class MessagesViewTest {
) )
val moreReactionContentDescription = rule.activity.getString(R.string.screen_room_timeline_add_reaction) val moreReactionContentDescription = rule.activity.getString(R.string.screen_room_timeline_add_reaction)
rule.onAllNodesWithContentDescription(moreReactionContentDescription).onFirst().performClick() rule.onAllNodesWithContentDescription(moreReactionContentDescription).onFirst().performClick()
eventsRecorder.assertSingle(CustomReactionEvents.ShowCustomReactionSheet(timelineItem)) eventsRecorder.assertSingle(CustomReactionEvent.ShowCustomReactionSheet(timelineItem))
} }
@Test @Test
fun `clicking on more reaction from action list emits the expected Event`() { fun `clicking on more reaction from action list emits the expected Event`() {
val eventsRecorder = EventsRecorder<CustomReactionEvents>() val eventsRecorder = EventsRecorder<CustomReactionEvent>()
val state = aMessagesState( val state = aMessagesState(
timelineState = aTimelineState( timelineState = aTimelineState(
timelineItems = aTimelineItemList(aTimelineItemTextContent()), timelineItems = aTimelineItemList(aTimelineItemTextContent()),
@ -478,12 +478,12 @@ class MessagesViewTest {
rule.onNodeWithContentDescription(moreReactionContentDescription).performClick() rule.onNodeWithContentDescription(moreReactionContentDescription).performClick()
// Give time for the close animation to complete // Give time for the close animation to complete
rule.mainClock.advanceTimeBy(milliseconds = 1_000) rule.mainClock.advanceTimeBy(milliseconds = 1_000)
eventsRecorder.assertSingle(CustomReactionEvents.ShowCustomReactionSheet(timelineItem)) eventsRecorder.assertSingle(CustomReactionEvent.ShowCustomReactionSheet(timelineItem))
} }
@Test @Test
fun `clicking on verified user send failure from action list emits the expected Event`() { fun `clicking on verified user send failure from action list emits the expected Event`() {
val eventsRecorder = EventsRecorder<TimelineEvents>() val eventsRecorder = EventsRecorder<TimelineEvent>()
val state = aMessagesState() val state = aMessagesState()
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
val stateWithActionListState = state.copy( val stateWithActionListState = state.copy(
@ -506,14 +506,14 @@ class MessagesViewTest {
rule.onNodeWithText(verifiedUserSendFailure).performClick() rule.onNodeWithText(verifiedUserSendFailure).performClick()
// Give time for the close animation to complete // Give time for the close animation to complete
rule.mainClock.advanceTimeBy(milliseconds = 1_000) rule.mainClock.advanceTimeBy(milliseconds = 1_000)
eventsRecorder.assertSingle(TimelineEvents.ComputeVerifiedUserSendFailure(timelineItem)) eventsRecorder.assertSingle(TimelineEvent.ComputeVerifiedUserSendFailure(timelineItem))
} }
@Test @Test
fun `clicking on a custom emoji emits the expected Events`() { fun `clicking on a custom emoji emits the expected Events`() {
val aUnicode = "🙈" val aUnicode = "🙈"
val customReactionStateEventsRecorder = EventsRecorder<CustomReactionEvents>() val customReactionStateEventsRecorder = EventsRecorder<CustomReactionEvent>()
val eventsRecorder = EventsRecorder<MessagesEvents>() val eventsRecorder = EventsRecorder<MessagesEvent>()
val state = aMessagesState( val state = aMessagesState(
eventSink = eventsRecorder, eventSink = eventsRecorder,
) )
@ -546,13 +546,13 @@ class MessagesViewTest {
rule.onNodeWithText(aUnicode, useUnmergedTree = true).performClick() rule.onNodeWithText(aUnicode, useUnmergedTree = true).performClick()
// Give time for the close animation to complete // Give time for the close animation to complete
rule.mainClock.advanceTimeBy(milliseconds = 1_000) rule.mainClock.advanceTimeBy(milliseconds = 1_000)
customReactionStateEventsRecorder.assertSingle(CustomReactionEvents.DismissCustomReactionSheet) customReactionStateEventsRecorder.assertSingle(CustomReactionEvent.DismissCustomReactionSheet)
eventsRecorder.assertSingle(MessagesEvents.ToggleReaction(aUnicode, timelineItem.eventOrTransactionId)) eventsRecorder.assertSingle(MessagesEvent.ToggleReaction(aUnicode, timelineItem.eventOrTransactionId))
} }
@Test @Test
fun `clicking on pinned messages banner emits the expected Event`() { fun `clicking on pinned messages banner emits the expected Event`() {
val eventsRecorder = EventsRecorder<TimelineEvents>() val eventsRecorder = EventsRecorder<TimelineEvent>()
val state = aMessagesState( val state = aMessagesState(
timelineState = aTimelineState(eventSink = eventsRecorder), timelineState = aTimelineState(eventSink = eventsRecorder),
pinnedMessagesBannerState = aLoadedPinnedMessagesBannerState( pinnedMessagesBannerState = aLoadedPinnedMessagesBannerState(
@ -566,12 +566,12 @@ class MessagesViewTest {
) )
rule.setMessagesView(state = state) rule.setMessagesView(state = state)
rule.onNodeWithText("This is a pinned message").performClick() rule.onNodeWithText("This is a pinned message").performClick()
eventsRecorder.assertSingle(TimelineEvents.FocusOnEvent(AN_EVENT_ID, debounce = FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS.milliseconds)) eventsRecorder.assertSingle(TimelineEvent.FocusOnEvent(AN_EVENT_ID, debounce = FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS.milliseconds))
} }
@Test @Test
fun `clicking on successor room button emits expected event`() { fun `clicking on successor room button emits expected event`() {
val eventsRecorder = EventsRecorder<TimelineEvents>() val eventsRecorder = EventsRecorder<TimelineEvent>()
val successorRoomId = RoomId("!successor:server.org") val successorRoomId = RoomId("!successor:server.org")
val state = aMessagesState( val state = aMessagesState(
successorRoom = SuccessorRoom( successorRoom = SuccessorRoom(
@ -584,12 +584,12 @@ class MessagesViewTest {
val text = rule.activity.getString(R.string.screen_room_timeline_tombstoned_room_action) val text = rule.activity.getString(R.string.screen_room_timeline_tombstoned_room_action)
// The bottomsheet subcompose seems to make the node to appear twice // The bottomsheet subcompose seems to make the node to appear twice
rule.onAllNodesWithText(text).onFirst().performClick() rule.onAllNodesWithText(text).onFirst().performClick()
eventsRecorder.assertSingle(TimelineEvents.NavigateToPredecessorOrSuccessorRoom(successorRoomId)) eventsRecorder.assertSingle(TimelineEvent.NavigateToPredecessorOrSuccessorRoom(successorRoomId))
} }
@Test @Test
fun `no banner shown when there is no successor room`() { fun `no banner shown when there is no successor room`() {
val eventsRecorder = EventsRecorder<MessagesEvents>(expectEvents = false) val eventsRecorder = EventsRecorder<MessagesEvent>(expectEvents = false)
val state = aMessagesState( val state = aMessagesState(
successorRoom = null, successorRoom = null,
eventSink = eventsRecorder eventSink = eventsRecorder

View file

@ -8,9 +8,6 @@
package io.element.android.features.messages.impl.actionlist package io.element.android.features.messages.impl.actionlist
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.messages.impl.aUserEventPermissions import io.element.android.features.messages.impl.aUserEventPermissions
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
@ -62,9 +59,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - initial state`() = runTest { fun `present - initial state`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.target).isEqualTo(ActionListState.Target.None) assertThat(initialState.target).isEqualTo(ActionListState.Target.None)
} }
@ -73,13 +68,11 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for message from me redacted`() = runTest { fun `present - compute for message from me redacted`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent(isMine = true, isEditable = false, content = TimelineItemRedactedContent) val messageEvent = aMessageEvent(isMine = true, isEditable = false, content = TimelineItemRedactedContent)
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = messageEvent, event = messageEvent,
userEventPermissions = aUserEventPermissions( userEventPermissions = aUserEventPermissions(
canRedactOwn = false, canRedactOwn = false,
@ -103,7 +96,7 @@ class ActionListPresenterTest {
recentEmojis = suggestedEmojis, recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvent.Clear)
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
} }
} }
@ -111,9 +104,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for message from others redacted`() = runTest { fun `present - compute for message from others redacted`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent( val messageEvent = aMessageEvent(
isMine = false, isMine = false,
@ -121,7 +112,7 @@ class ActionListPresenterTest {
content = TimelineItemRedactedContent content = TimelineItemRedactedContent
) )
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = messageEvent, event = messageEvent,
userEventPermissions = aUserEventPermissions( userEventPermissions = aUserEventPermissions(
canRedactOwn = false, canRedactOwn = false,
@ -145,7 +136,7 @@ class ActionListPresenterTest {
recentEmojis = suggestedEmojis, recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvent.Clear)
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
} }
} }
@ -153,9 +144,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for others message`() = runTest { fun `present - compute for others message`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent( val messageEvent = aMessageEvent(
isMine = false, isMine = false,
@ -163,7 +152,7 @@ class ActionListPresenterTest {
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE)
) )
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = messageEvent, event = messageEvent,
userEventPermissions = aUserEventPermissions( userEventPermissions = aUserEventPermissions(
canRedactOwn = false, canRedactOwn = false,
@ -193,7 +182,7 @@ class ActionListPresenterTest {
recentEmojis = suggestedEmojis, recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvent.Clear)
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
} }
} }
@ -210,7 +199,7 @@ class ActionListPresenterTest {
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE)
) )
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = messageEvent, event = messageEvent,
userEventPermissions = aUserEventPermissions( userEventPermissions = aUserEventPermissions(
canRedactOwn = false, canRedactOwn = false,
@ -240,7 +229,7 @@ class ActionListPresenterTest {
recentEmojis = suggestedEmojis, recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvent.Clear)
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
} }
} }
@ -248,9 +237,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for others message cannot sent message`() = runTest { fun `present - compute for others message cannot sent message`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent( val messageEvent = aMessageEvent(
isMine = false, isMine = false,
@ -258,7 +245,7 @@ class ActionListPresenterTest {
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE)
) )
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = messageEvent, event = messageEvent,
userEventPermissions = aUserEventPermissions( userEventPermissions = aUserEventPermissions(
canRedactOwn = true, canRedactOwn = true,
@ -287,7 +274,7 @@ class ActionListPresenterTest {
recentEmojis = suggestedEmojis, recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvent.Clear)
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
} }
} }
@ -295,9 +282,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for others message and can redact`() = runTest { fun `present - compute for others message and can redact`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent( val messageEvent = aMessageEvent(
isMine = false, isMine = false,
@ -305,7 +290,7 @@ class ActionListPresenterTest {
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE)
) )
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = messageEvent, event = messageEvent,
userEventPermissions = aUserEventPermissions( userEventPermissions = aUserEventPermissions(
canRedactOwn = false, canRedactOwn = false,
@ -336,7 +321,7 @@ class ActionListPresenterTest {
recentEmojis = suggestedEmojis, recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvent.Clear)
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
} }
} }
@ -344,9 +329,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for others message and cannot send reaction`() = runTest { fun `present - compute for others message and cannot send reaction`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent( val messageEvent = aMessageEvent(
isMine = false, isMine = false,
@ -354,7 +337,7 @@ class ActionListPresenterTest {
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE)
) )
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = messageEvent, event = messageEvent,
userEventPermissions = aUserEventPermissions( userEventPermissions = aUserEventPermissions(
canRedactOwn = false, canRedactOwn = false,
@ -385,7 +368,7 @@ class ActionListPresenterTest {
recentEmojis = suggestedEmojis, recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvent.Clear)
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
} }
} }
@ -393,16 +376,14 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for my message`() = runTest { fun `present - compute for my message`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent( val messageEvent = aMessageEvent(
isMine = true, isMine = true,
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE)
) )
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = messageEvent, event = messageEvent,
userEventPermissions = aUserEventPermissions( userEventPermissions = aUserEventPermissions(
canRedactOwn = true, canRedactOwn = true,
@ -433,7 +414,7 @@ class ActionListPresenterTest {
recentEmojis = suggestedEmojis, recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvent.Clear)
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
} }
} }
@ -449,7 +430,7 @@ class ActionListPresenterTest {
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE)
) )
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = messageEvent, event = messageEvent,
userEventPermissions = aUserEventPermissions( userEventPermissions = aUserEventPermissions(
canRedactOwn = true, canRedactOwn = true,
@ -480,7 +461,7 @@ class ActionListPresenterTest {
recentEmojis = suggestedEmojis, recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvent.Clear)
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
} }
} }
@ -488,16 +469,14 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for my message cannot redact`() = runTest { fun `present - compute for my message cannot redact`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent( val messageEvent = aMessageEvent(
isMine = true, isMine = true,
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE)
) )
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = messageEvent, event = messageEvent,
userEventPermissions = aUserEventPermissions( userEventPermissions = aUserEventPermissions(
canRedactOwn = false, canRedactOwn = false,
@ -527,7 +506,7 @@ class ActionListPresenterTest {
recentEmojis = suggestedEmojis, recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvent.Clear)
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
} }
} }
@ -535,16 +514,14 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for my message no permission`() = runTest { fun `present - compute for my message no permission`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent( val messageEvent = aMessageEvent(
isMine = true, isMine = true,
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE)
) )
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = messageEvent, event = messageEvent,
userEventPermissions = aUserEventPermissions( userEventPermissions = aUserEventPermissions(
canRedactOwn = false, canRedactOwn = false,
@ -571,7 +548,7 @@ class ActionListPresenterTest {
recentEmojis = suggestedEmojis, recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvent.Clear)
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
} }
} }
@ -579,9 +556,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for a media item`() = runTest { fun `present - compute for a media item`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent( val messageEvent = aMessageEvent(
isMine = true, isMine = true,
@ -589,7 +564,7 @@ class ActionListPresenterTest {
content = aTimelineItemImageContent(), content = aTimelineItemImageContent(),
) )
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = messageEvent, event = messageEvent,
userEventPermissions = aUserEventPermissions( userEventPermissions = aUserEventPermissions(
canRedactOwn = true, canRedactOwn = true,
@ -619,7 +594,7 @@ class ActionListPresenterTest {
recentEmojis = suggestedEmojis, recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvent.Clear)
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
} }
} }
@ -627,9 +602,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for a media with caption item`() = runTest { fun `present - compute for a media with caption item`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent( val messageEvent = aMessageEvent(
isMine = true, isMine = true,
@ -639,7 +612,7 @@ class ActionListPresenterTest {
), ),
) )
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = messageEvent, event = messageEvent,
userEventPermissions = aUserEventPermissions( userEventPermissions = aUserEventPermissions(
canRedactOwn = true, canRedactOwn = true,
@ -671,7 +644,7 @@ class ActionListPresenterTest {
recentEmojis = suggestedEmojis, recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvent.Clear)
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
} }
} }
@ -679,9 +652,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for a media with caption item - other user event`() = runTest { fun `present - compute for a media with caption item - other user event`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent( val messageEvent = aMessageEvent(
isMine = false, isMine = false,
@ -691,7 +662,7 @@ class ActionListPresenterTest {
), ),
) )
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = messageEvent, event = messageEvent,
userEventPermissions = aUserEventPermissions( userEventPermissions = aUserEventPermissions(
canRedactOwn = true, canRedactOwn = true,
@ -721,7 +692,7 @@ class ActionListPresenterTest {
recentEmojis = suggestedEmojis, recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvent.Clear)
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
} }
} }
@ -729,16 +700,14 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for a state item in debug build`() = runTest { fun `present - compute for a state item in debug build`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val stateEvent = aTimelineItemEvent( val stateEvent = aTimelineItemEvent(
isMine = true, isMine = true,
content = aTimelineItemStateEventContent(), content = aTimelineItemStateEventContent(),
) )
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = stateEvent, event = stateEvent,
userEventPermissions = aUserEventPermissions( userEventPermissions = aUserEventPermissions(
canRedactOwn = false, canRedactOwn = false,
@ -762,7 +731,7 @@ class ActionListPresenterTest {
recentEmojis = suggestedEmojis, recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvent.Clear)
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
} }
} }
@ -770,16 +739,14 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for a state item in non-debuggable build`() = runTest { fun `present - compute for a state item in non-debuggable build`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = false) val presenter = createActionListPresenter(isDeveloperModeEnabled = false)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val stateEvent = aTimelineItemEvent( val stateEvent = aTimelineItemEvent(
isMine = true, isMine = true,
content = aTimelineItemStateEventContent(), content = aTimelineItemStateEventContent(),
) )
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = stateEvent, event = stateEvent,
userEventPermissions = aUserEventPermissions( userEventPermissions = aUserEventPermissions(
canRedactOwn = false, canRedactOwn = false,
@ -797,16 +764,14 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute message in non-debuggable build`() = runTest { fun `present - compute message in non-debuggable build`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = false) val presenter = createActionListPresenter(isDeveloperModeEnabled = false)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent( val messageEvent = aMessageEvent(
isMine = true, isMine = true,
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE)
) )
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = messageEvent, event = messageEvent,
userEventPermissions = aUserEventPermissions( userEventPermissions = aUserEventPermissions(
canRedactOwn = true, canRedactOwn = true,
@ -836,7 +801,7 @@ class ActionListPresenterTest {
recentEmojis = suggestedEmojis, recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvent.Clear)
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
} }
} }
@ -844,16 +809,14 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute message when user can't pin`() = runTest { fun `present - compute message when user can't pin`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent( val messageEvent = aMessageEvent(
isMine = true, isMine = true,
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE)
) )
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = messageEvent, event = messageEvent,
userEventPermissions = aUserEventPermissions( userEventPermissions = aUserEventPermissions(
canRedactOwn = true, canRedactOwn = true,
@ -883,7 +846,7 @@ class ActionListPresenterTest {
recentEmojis = suggestedEmojis, recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvent.Clear)
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
} }
} }
@ -897,16 +860,14 @@ class ActionListPresenterTest {
isDeveloperModeEnabled = true, isDeveloperModeEnabled = true,
room = room room = room
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent( val messageEvent = aMessageEvent(
isMine = true, isMine = true,
content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE) content = TimelineItemTextContent(body = A_MESSAGE, htmlDocument = null, isEdited = false, formattedBody = A_MESSAGE)
) )
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = messageEvent, event = messageEvent,
userEventPermissions = aUserEventPermissions( userEventPermissions = aUserEventPermissions(
canRedactOwn = true, canRedactOwn = true,
@ -937,7 +898,7 @@ class ActionListPresenterTest {
recentEmojis = suggestedEmojis, recentEmojis = suggestedEmojis,
) )
) )
initialState.eventSink.invoke(ActionListEvents.Clear) initialState.eventSink.invoke(ActionListEvent.Clear)
assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None) assertThat(awaitItem().target).isEqualTo(ActionListState.Target.None)
} }
} }
@ -945,9 +906,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute message with no actions`() = runTest { fun `present - compute message with no actions`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = false) val presenter = createActionListPresenter(isDeveloperModeEnabled = false)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent( val messageEvent = aMessageEvent(
isMine = true, isMine = true,
@ -959,7 +918,7 @@ class ActionListPresenterTest {
) )
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = messageEvent, event = messageEvent,
userEventPermissions = aUserEventPermissions( userEventPermissions = aUserEventPermissions(
canRedactOwn = false, canRedactOwn = false,
@ -973,7 +932,7 @@ class ActionListPresenterTest {
assertThat(awaitItem().target).isInstanceOf(ActionListState.Target.Success::class.java) assertThat(awaitItem().target).isInstanceOf(ActionListState.Target.Success::class.java)
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = redactedEvent, event = redactedEvent,
userEventPermissions = aUserEventPermissions( userEventPermissions = aUserEventPermissions(
canRedactOwn = false, canRedactOwn = false,
@ -992,9 +951,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute not sent message`() = runTest { fun `present - compute not sent message`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = false) val presenter = createActionListPresenter(isDeveloperModeEnabled = false)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent( val messageEvent = aMessageEvent(
// No event id, so it's not sent yet // No event id, so it's not sent yet
@ -1005,7 +962,7 @@ class ActionListPresenterTest {
) )
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = messageEvent, event = messageEvent,
userEventPermissions = aUserEventPermissions( userEventPermissions = aUserEventPermissions(
canRedactOwn = true, canRedactOwn = true,
@ -1037,9 +994,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for editable poll message`() = runTest { fun `present - compute for editable poll message`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = false) val presenter = createActionListPresenter(isDeveloperModeEnabled = false)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent( val messageEvent = aMessageEvent(
isMine = true, isMine = true,
@ -1047,7 +1002,7 @@ class ActionListPresenterTest {
content = aTimelineItemPollContent(answerItems = aPollAnswerItemList(hasVotes = false)), content = aTimelineItemPollContent(answerItems = aPollAnswerItemList(hasVotes = false)),
) )
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = messageEvent, event = messageEvent,
userEventPermissions = aUserEventPermissions( userEventPermissions = aUserEventPermissions(
canRedactOwn = true, canRedactOwn = true,
@ -1082,9 +1037,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for non-editable poll message`() = runTest { fun `present - compute for non-editable poll message`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = false) val presenter = createActionListPresenter(isDeveloperModeEnabled = false)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent( val messageEvent = aMessageEvent(
isMine = true, isMine = true,
@ -1092,7 +1045,7 @@ class ActionListPresenterTest {
content = aTimelineItemPollContent(answerItems = aPollAnswerItemList(hasVotes = true)), content = aTimelineItemPollContent(answerItems = aPollAnswerItemList(hasVotes = true)),
) )
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = messageEvent, event = messageEvent,
userEventPermissions = aUserEventPermissions( userEventPermissions = aUserEventPermissions(
canRedactOwn = true, canRedactOwn = true,
@ -1126,9 +1079,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for ended poll message`() = runTest { fun `present - compute for ended poll message`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = false) val presenter = createActionListPresenter(isDeveloperModeEnabled = false)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent( val messageEvent = aMessageEvent(
isMine = true, isMine = true,
@ -1136,7 +1087,7 @@ class ActionListPresenterTest {
content = aTimelineItemPollContent(isEnded = true), content = aTimelineItemPollContent(isEnded = true),
) )
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = messageEvent, event = messageEvent,
userEventPermissions = aUserEventPermissions( userEventPermissions = aUserEventPermissions(
canRedactOwn = true, canRedactOwn = true,
@ -1169,9 +1120,7 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for voice message`() = runTest { fun `present - compute for voice message`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = false) val presenter = createActionListPresenter(isDeveloperModeEnabled = false)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent( val messageEvent = aMessageEvent(
isMine = true, isMine = true,
@ -1181,7 +1130,7 @@ class ActionListPresenterTest {
), ),
) )
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = messageEvent, event = messageEvent,
userEventPermissions = aUserEventPermissions( userEventPermissions = aUserEventPermissions(
canRedactOwn = true, canRedactOwn = true,
@ -1215,16 +1164,14 @@ class ActionListPresenterTest {
@Test @Test
fun `present - compute for call notify`() = runTest { fun `present - compute for call notify`() = runTest {
val presenter = createActionListPresenter(isDeveloperModeEnabled = true) val presenter = createActionListPresenter(isDeveloperModeEnabled = true)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent( val messageEvent = aMessageEvent(
isMine = true, isMine = true,
content = TimelineItemRtcNotificationContent(), content = TimelineItemRtcNotificationContent(),
) )
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = messageEvent, event = messageEvent,
userEventPermissions = aUserEventPermissions( userEventPermissions = aUserEventPermissions(
canRedactOwn = true, canRedactOwn = true,
@ -1256,15 +1203,13 @@ class ActionListPresenterTest {
userDisplayNameResult = { Result.success("Alice") } userDisplayNameResult = { Result.success("Alice") }
) )
val presenter = createActionListPresenter(isDeveloperModeEnabled = false, room = room) val presenter = createActionListPresenter(isDeveloperModeEnabled = false, room = room)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent( val messageEvent = aMessageEvent(
sendState = LocalEventSendState.Failed.VerifiedUserChangedIdentity(users = listOf(A_USER_ID)), sendState = LocalEventSendState.Failed.VerifiedUserChangedIdentity(users = listOf(A_USER_ID)),
) )
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = messageEvent, event = messageEvent,
userEventPermissions = aUserEventPermissions(), userEventPermissions = aUserEventPermissions(),
) )
@ -1283,9 +1228,7 @@ class ActionListPresenterTest {
timelineMode = Timeline.Mode.Thread(A_THREAD_ID), timelineMode = Timeline.Mode.Thread(A_THREAD_ID),
featureFlagService = FakeFeatureFlagService(initialState = mapOf(FeatureFlags.Threads.key to true)), featureFlagService = FakeFeatureFlagService(initialState = mapOf(FeatureFlags.Threads.key to true)),
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent( val messageEvent = aMessageEvent(
isMine = true, isMine = true,
@ -1296,7 +1239,7 @@ class ActionListPresenterTest {
threadInfo = TimelineItemThreadInfo.ThreadResponse(threadRootId = A_THREAD_ID) threadInfo = TimelineItemThreadInfo.ThreadResponse(threadRootId = A_THREAD_ID)
) )
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = messageEvent, event = messageEvent,
userEventPermissions = aUserEventPermissions( userEventPermissions = aUserEventPermissions(
canRedactOwn = true, canRedactOwn = true,
@ -1334,9 +1277,7 @@ class ActionListPresenterTest {
isDeveloperModeEnabled = false, isDeveloperModeEnabled = false,
featureFlagService = FakeFeatureFlagService(initialState = mapOf(FeatureFlags.Threads.key to true)), featureFlagService = FakeFeatureFlagService(initialState = mapOf(FeatureFlags.Threads.key to true)),
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent( val messageEvent = aMessageEvent(
eventId = AN_EVENT_ID, eventId = AN_EVENT_ID,
@ -1350,7 +1291,7 @@ class ActionListPresenterTest {
assertThat(messageEvent.isRemote).isTrue() assertThat(messageEvent.isRemote).isTrue()
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = messageEvent, event = messageEvent,
userEventPermissions = aUserEventPermissions( userEventPermissions = aUserEventPermissions(
canRedactOwn = true, canRedactOwn = true,
@ -1388,9 +1329,7 @@ class ActionListPresenterTest {
isDeveloperModeEnabled = false, isDeveloperModeEnabled = false,
featureFlagService = FakeFeatureFlagService(initialState = mapOf(FeatureFlags.Threads.key to true)), featureFlagService = FakeFeatureFlagService(initialState = mapOf(FeatureFlags.Threads.key to true)),
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent( val messageEvent = aMessageEvent(
eventId = AN_EVENT_ID, eventId = AN_EVENT_ID,
@ -1405,7 +1344,7 @@ class ActionListPresenterTest {
assertThat(messageEvent.isRemote).isTrue() assertThat(messageEvent.isRemote).isTrue()
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = messageEvent, event = messageEvent,
userEventPermissions = aUserEventPermissions( userEventPermissions = aUserEventPermissions(
canRedactOwn = true, canRedactOwn = true,
@ -1443,9 +1382,7 @@ class ActionListPresenterTest {
isDeveloperModeEnabled = false, isDeveloperModeEnabled = false,
featureFlagService = FakeFeatureFlagService(initialState = mapOf(FeatureFlags.Threads.key to true)), featureFlagService = FakeFeatureFlagService(initialState = mapOf(FeatureFlags.Threads.key to true)),
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent( val messageEvent = aMessageEvent(
eventId = null, eventId = null,
@ -1460,7 +1397,7 @@ class ActionListPresenterTest {
assertThat(messageEvent.isRemote).isFalse() assertThat(messageEvent.isRemote).isFalse()
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = messageEvent, event = messageEvent,
userEventPermissions = aUserEventPermissions( userEventPermissions = aUserEventPermissions(
canRedactOwn = true, canRedactOwn = true,
@ -1498,9 +1435,7 @@ class ActionListPresenterTest {
isDeveloperModeEnabled = false, isDeveloperModeEnabled = false,
recentEmojis = GetRecentEmojis { Result.success((listOf("👍️", ":)", "❤️") + otherEmojis).toImmutableList()) }, recentEmojis = GetRecentEmojis { Result.success((listOf("👍️", ":)", "❤️") + otherEmojis).toImmutableList()) },
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val messageEvent = aMessageEvent( val messageEvent = aMessageEvent(
eventId = null, eventId = null,
@ -1513,7 +1448,7 @@ class ActionListPresenterTest {
) )
initialState.eventSink.invoke( initialState.eventSink.invoke(
ActionListEvents.ComputeForMessage( ActionListEvent.ComputeForMessage(
event = messageEvent, event = messageEvent,
userEventPermissions = aUserEventPermissions( userEventPermissions = aUserEventPermissions(
canRedactOwn = true, canRedactOwn = true,

View file

@ -11,11 +11,8 @@
package io.element.android.features.messages.impl.attachments package io.element.android.features.messages.impl.attachments
import android.net.Uri import android.net.Uri
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat 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.AttachmentsPreviewEvent
import io.element.android.features.messages.impl.attachments.preview.AttachmentsPreviewPresenter import io.element.android.features.messages.impl.attachments.preview.AttachmentsPreviewPresenter
import io.element.android.features.messages.impl.attachments.preview.OnDoneListener import io.element.android.features.messages.impl.attachments.preview.OnDoneListener
import io.element.android.features.messages.impl.attachments.preview.SendActionState import io.element.android.features.messages.impl.attachments.preview.SendActionState
@ -111,13 +108,11 @@ class AttachmentsPreviewPresenterTest {
mediaPreProcessor = mediaPreProcessor, mediaPreProcessor = mediaPreProcessor,
onDoneListener = { onDoneListener() }, onDoneListener = { onDoneListener() },
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvent.SendAttachment)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = true)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = true))
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo))
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Uploading(mediaUploadInfo)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Uploading(mediaUploadInfo))
@ -147,16 +142,14 @@ class AttachmentsPreviewPresenterTest {
mediaPreProcessor = mediaPreProcessor, mediaPreProcessor = mediaPreProcessor,
onDoneListener = { onDoneListener() }, onDoneListener = { onDoneListener() },
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
// Pre-processing finishes // Pre-processing finishes
processLatch.complete(Unit) processLatch.complete(Unit)
advanceUntilIdle() advanceUntilIdle()
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvent.SendAttachment)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo))
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Uploading(mediaUploadInfo)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Uploading(mediaUploadInfo))
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done)
@ -185,13 +178,11 @@ class AttachmentsPreviewPresenterTest {
mediaPreProcessor = mediaPreProcessor, mediaPreProcessor = mediaPreProcessor,
onDoneListener = { onDoneListener() }, onDoneListener = { onDoneListener() },
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvent.SendAttachment)
// Pre-processing finishes // Pre-processing finishes
processLatch.complete(Unit) processLatch.complete(Unit)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = true)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = true))
@ -216,12 +207,10 @@ class AttachmentsPreviewPresenterTest {
}, },
onDoneListener = { onDoneListener() }, onDoneListener = { onDoneListener() },
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvent.SendAttachment)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
// Pre-processing finishes // Pre-processing finishes
processLatch.complete(Unit) processLatch.complete(Unit)
@ -241,15 +230,13 @@ class AttachmentsPreviewPresenterTest {
}, },
onDoneListener = { onDoneListener() }, onDoneListener = { onDoneListener() },
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
// Pre-processing finishes // Pre-processing finishes
processLatch.complete(Unit) processLatch.complete(Unit)
advanceUntilIdle() advanceUntilIdle()
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvent.SendAttachment)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Failure::class.java) assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Failure::class.java)
} }
@ -265,12 +252,10 @@ class AttachmentsPreviewPresenterTest {
temporaryUriDeleter = FakeTemporaryUriDeleter(deleteCallback), temporaryUriDeleter = FakeTemporaryUriDeleter(deleteCallback),
onDoneListener = { onDoneListener() }, onDoneListener = { onDoneListener() },
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.eventSink(AttachmentsPreviewEvents.CancelAndDismiss) initialState.eventSink(AttachmentsPreviewEvent.CancelAndDismiss)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Done)
deleteCallback.assertions().isCalledOnce() deleteCallback.assertions().isCalledOnce()
onDoneListener.assertions().isCalledOnce() onDoneListener.assertions().isCalledOnce()
@ -298,13 +283,11 @@ class AttachmentsPreviewPresenterTest {
mediaPreProcessor = mediaPreProcessor, mediaPreProcessor = mediaPreProcessor,
onDoneListener = { onDoneListener() }, onDoneListener = { onDoneListener() },
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.textEditorState.setMarkdown(A_CAPTION) initialState.textEditorState.setMarkdown(A_CAPTION)
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvent.SendAttachment)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Sending.ReadyToUpload::class.java) assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Sending.ReadyToUpload::class.java)
assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Sending.Uploading::class.java) assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Sending.Uploading::class.java)
@ -341,13 +324,11 @@ class AttachmentsPreviewPresenterTest {
mediaPreProcessor = mediaPreProcessor, mediaPreProcessor = mediaPreProcessor,
onDoneListener = { onDoneListener() }, onDoneListener = { onDoneListener() },
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.textEditorState.setMarkdown(A_CAPTION) initialState.textEditorState.setMarkdown(A_CAPTION)
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvent.SendAttachment)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Sending.ReadyToUpload::class.java) assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Sending.ReadyToUpload::class.java)
assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Sending.Uploading::class.java) assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Sending.Uploading::class.java)
@ -388,7 +369,7 @@ class AttachmentsPreviewPresenterTest {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.textEditorState.setMarkdown(A_CAPTION) initialState.textEditorState.setMarkdown(A_CAPTION)
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvent.SendAttachment)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Sending.ReadyToUpload::class.java) assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Sending.ReadyToUpload::class.java)
assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Sending.Uploading::class.java) assertThat(awaitItem().sendActionState).isInstanceOf(SendActionState.Sending.Uploading::class.java)
@ -418,12 +399,10 @@ class AttachmentsPreviewPresenterTest {
}, },
) )
val presenter = createAttachmentsPreviewPresenter(room = room, onDoneListener = onDoneListenerResult) val presenter = createAttachmentsPreviewPresenter(room = room, onDoneListener = onDoneListenerResult)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvent.SendAttachment)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo))
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Uploading(mediaUploadInfo)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Uploading(mediaUploadInfo))
@ -434,7 +413,7 @@ class AttachmentsPreviewPresenterTest {
val failureState = awaitItem() val failureState = awaitItem()
assertThat(failureState.sendActionState).isEqualTo(SendActionState.Failure(failure, mediaUploadInfo)) assertThat(failureState.sendActionState).isEqualTo(SendActionState.Failure(failure, mediaUploadInfo))
sendFileResult.assertions().isCalledOnce() sendFileResult.assertions().isCalledOnce()
failureState.eventSink(AttachmentsPreviewEvents.CancelAndClearSendState) failureState.eventSink(AttachmentsPreviewEvent.CancelAndClearSendState)
val clearedState = awaitLastSequentialItem() val clearedState = awaitLastSequentialItem()
assertThat(clearedState.sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo)) assertThat(clearedState.sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo))
} }
@ -453,16 +432,14 @@ class AttachmentsPreviewPresenterTest {
), ),
onDoneListener = onDoneListenerResult, onDoneListener = onDoneListenerResult,
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle) assertThat(initialState.sendActionState).isEqualTo(SendActionState.Idle)
initialState.eventSink(AttachmentsPreviewEvents.SendAttachment) initialState.eventSink(AttachmentsPreviewEvent.SendAttachment)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Processing(displayProgress = false))
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo))
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Uploading(mediaUploadInfo)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Uploading(mediaUploadInfo))
initialState.eventSink(AttachmentsPreviewEvents.CancelAndClearSendState) initialState.eventSink(AttachmentsPreviewEvent.CancelAndClearSendState)
assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo)) assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.ReadyToUpload(mediaUploadInfo))
// The sending is cancelled and the state is kept at ReadyToUpload // The sending is cancelled and the state is kept at ReadyToUpload
ensureAllEventsConsumed() ensureAllEventsConsumed()
@ -503,9 +480,7 @@ class AttachmentsPreviewPresenterTest {
} }
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
assertThat(localMedia.info.fileSize).isGreaterThan(maxUploadSize) assertThat(localMedia.info.fileSize).isGreaterThan(maxUploadSize)
consumeItemsUntilPredicate { it.mediaOptimizationSelectorState.maxUploadSize.isSuccess() } consumeItemsUntilPredicate { it.mediaOptimizationSelectorState.maxUploadSize.isSuccess() }
@ -563,9 +538,7 @@ class AttachmentsPreviewPresenterTest {
} }
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
consumeItemsUntilPredicate { consumeItemsUntilPredicate {
it.mediaOptimizationSelectorState.maxUploadSize.isSuccess() && it.mediaOptimizationSelectorState.maxUploadSize.isSuccess() &&
it.mediaOptimizationSelectorState.videoSizeEstimations.dataOrNull()?.isNotEmpty() == true it.mediaOptimizationSelectorState.videoSizeEstimations.dataOrNull()?.isNotEmpty() == true

View file

@ -11,9 +11,6 @@ package io.element.android.features.messages.impl.attachments.video
import android.net.Uri import android.net.Uri
import android.util.Size import android.util.Size
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.messages.test.attachments.video.FakeVideoMetadataExtractor import io.element.android.features.messages.test.attachments.video.FakeVideoMetadataExtractor
import io.element.android.features.messages.test.attachments.video.FakeVideoMetadataExtractorFactory import io.element.android.features.messages.test.attachments.video.FakeVideoMetadataExtractorFactory
@ -29,6 +26,7 @@ import io.element.android.libraries.mediaviewer.api.local.LocalMedia
import io.element.android.libraries.mediaviewer.test.viewer.aLocalMedia import io.element.android.libraries.mediaviewer.test.viewer.aLocalMedia
import io.element.android.libraries.preferences.api.store.VideoCompressionPreset import io.element.android.libraries.preferences.api.store.VideoCompressionPreset
import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.test
import io.mockk.mockk import io.mockk.mockk
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Rule import org.junit.Rule
@ -46,9 +44,7 @@ class DefaultMediaOptimizationSelectorPresenterTest {
@Test @Test
fun `present - initial state`() = runTest { fun `present - initial state`() = runTest {
val presenter = createDefaultMediaOptimizationSelectorPresenter() val presenter = createDefaultMediaOptimizationSelectorPresenter()
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
awaitItem().run { awaitItem().run {
// Loading // Loading
assertThat(videoSizeEstimations).isInstanceOf(AsyncData.Loading::class.java) assertThat(videoSizeEstimations).isInstanceOf(AsyncData.Loading::class.java)
@ -77,9 +73,7 @@ class DefaultMediaOptimizationSelectorPresenterTest {
val presenter = createDefaultMediaOptimizationSelectorPresenter( val presenter = createDefaultMediaOptimizationSelectorPresenter(
localMedia = aLocalMedia(mockMediaUrl, anImageMediaInfo()) localMedia = aLocalMedia(mockMediaUrl, anImageMediaInfo())
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
// Skip loading state // Skip loading state
skipItems(1) skipItems(1)
@ -94,9 +88,7 @@ class DefaultMediaOptimizationSelectorPresenterTest {
@Test @Test
fun `present - OpenVideoPresetSelectorDialog displays it, DismissVideoPresetSelectorDialog hides it`() = runTest { fun `present - OpenVideoPresetSelectorDialog displays it, DismissVideoPresetSelectorDialog hides it`() = runTest {
val presenter = createDefaultMediaOptimizationSelectorPresenter() val presenter = createDefaultMediaOptimizationSelectorPresenter()
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
// Skip loading state // Skip loading state
val eventSink = awaitItem().eventSink val eventSink = awaitItem().eventSink
@ -115,9 +107,7 @@ class DefaultMediaOptimizationSelectorPresenterTest {
@Test @Test
fun `present - SelectVideoPreset sets it and dismisses the dialog`() = runTest { fun `present - SelectVideoPreset sets it and dismisses the dialog`() = runTest {
val presenter = createDefaultMediaOptimizationSelectorPresenter() val presenter = createDefaultMediaOptimizationSelectorPresenter()
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
// Skip loading state // Skip loading state
val eventSink = awaitItem().eventSink val eventSink = awaitItem().eventSink
@ -139,9 +129,7 @@ class DefaultMediaOptimizationSelectorPresenterTest {
val presenter = createDefaultMediaOptimizationSelectorPresenter( val presenter = createDefaultMediaOptimizationSelectorPresenter(
mediaExtractorFactory = FakeVideoMetadataExtractorFactory(FakeVideoMetadataExtractor(sizeResult = Result.failure(AN_EXCEPTION))), mediaExtractorFactory = FakeVideoMetadataExtractorFactory(FakeVideoMetadataExtractor(sizeResult = Result.failure(AN_EXCEPTION))),
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
// Skip loading state // Skip loading state
val eventSink = awaitItem().eventSink val eventSink = awaitItem().eventSink
@ -163,9 +151,7 @@ class DefaultMediaOptimizationSelectorPresenterTest {
) )
), ),
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
// Skip loading and loaded states // Skip loading and loaded states
val eventSink = awaitItem().eventSink val eventSink = awaitItem().eventSink
skipItems(1) skipItems(1)
@ -188,9 +174,7 @@ class DefaultMediaOptimizationSelectorPresenterTest {
val presenter = createDefaultMediaOptimizationSelectorPresenter( val presenter = createDefaultMediaOptimizationSelectorPresenter(
localMedia = aLocalMedia(mockMediaUrl, anImageMediaInfo()), localMedia = aLocalMedia(mockMediaUrl, anImageMediaInfo()),
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
// Skip loading state // Skip loading state
val eventSink = awaitItem().eventSink val eventSink = awaitItem().eventSink
@ -207,9 +191,7 @@ class DefaultMediaOptimizationSelectorPresenterTest {
val presenter = createDefaultMediaOptimizationSelectorPresenter( val presenter = createDefaultMediaOptimizationSelectorPresenter(
maxUploadSizeProvider = MaxUploadSizeProvider { Result.failure(AN_EXCEPTION) } maxUploadSizeProvider = MaxUploadSizeProvider { Result.failure(AN_EXCEPTION) }
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
// Skip loading and loaded state // Skip loading and loaded state
skipItems(1) skipItems(1)
assertThat(awaitItem().maxUploadSize.dataOrNull()).isEqualTo(1024 * 1024 * 100) assertThat(awaitItem().maxUploadSize.dataOrNull()).isEqualTo(1024 * 1024 * 100)
@ -221,9 +203,7 @@ class DefaultMediaOptimizationSelectorPresenterTest {
val presenter = createDefaultMediaOptimizationSelectorPresenter( val presenter = createDefaultMediaOptimizationSelectorPresenter(
featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.SelectableMediaQuality.key to false)), featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.SelectableMediaQuality.key to false)),
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
// Skip loading and loaded state // Skip loading and loaded state
skipItems(1) skipItems(1)
assertThat(awaitItem().displayMediaSelectorViews).isFalse() assertThat(awaitItem().displayMediaSelectorViews).isFalse()

View file

@ -47,7 +47,7 @@ class ResolveVerifiedUserSendFailurePresenterTest {
val sentMessage = aMessageEvent() val sentMessage = aMessageEvent()
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.None) assertThat(initialState.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.None)
initialState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(sentMessage)) initialState.eventSink(ResolveVerifiedUserSendFailureEvent.ComputeForMessage(sentMessage))
ensureAllEventsConsumed() ensureAllEventsConsumed()
} }
} }
@ -61,7 +61,7 @@ class ResolveVerifiedUserSendFailurePresenterTest {
) )
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.None) assertThat(initialState.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.None)
initialState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(sentMessage)) initialState.eventSink(ResolveVerifiedUserSendFailureEvent.ComputeForMessage(sentMessage))
ensureAllEventsConsumed() ensureAllEventsConsumed()
} }
} }
@ -75,7 +75,7 @@ class ResolveVerifiedUserSendFailurePresenterTest {
) )
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.None) assertThat(initialState.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.None)
initialState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(failedMessage)) initialState.eventSink(ResolveVerifiedUserSendFailureEvent.ComputeForMessage(failedMessage))
ensureAllEventsConsumed() ensureAllEventsConsumed()
} }
} }
@ -94,11 +94,11 @@ class ResolveVerifiedUserSendFailurePresenterTest {
val failedMessage = aVerifiedUserHasUnsignedDeviceFailedMessage() val failedMessage = aVerifiedUserHasUnsignedDeviceFailedMessage()
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.None) assertThat(initialState.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.None)
initialState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(failedMessage)) initialState.eventSink(ResolveVerifiedUserSendFailureEvent.ComputeForMessage(failedMessage))
skipItems(1) skipItems(1)
awaitItem().also { state -> awaitItem().also { state ->
assertThat(state.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.UnsignedDevice.FromYou) assertThat(state.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.UnsignedDevice.FromYou)
state.eventSink(ResolveVerifiedUserSendFailureEvents.Dismiss) state.eventSink(ResolveVerifiedUserSendFailureEvent.Dismiss)
} }
skipItems(1) skipItems(1)
awaitItem().also { state -> awaitItem().also { state ->
@ -122,12 +122,12 @@ class ResolveVerifiedUserSendFailurePresenterTest {
val failedMessage = aVerifiedUserHasUnsignedDeviceFailedMessage() val failedMessage = aVerifiedUserHasUnsignedDeviceFailedMessage()
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.None) assertThat(initialState.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.None)
initialState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(failedMessage)) initialState.eventSink(ResolveVerifiedUserSendFailureEvent.ComputeForMessage(failedMessage))
skipItems(1) skipItems(1)
awaitItem().also { state -> awaitItem().also { state ->
assertThat(state.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.UnsignedDevice.FromYou) assertThat(state.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.UnsignedDevice.FromYou)
state.eventSink(ResolveVerifiedUserSendFailureEvents.Retry) state.eventSink(ResolveVerifiedUserSendFailureEvent.Retry)
} }
awaitItem().also { state -> awaitItem().also { state ->
assertThat(state.retryAction).isEqualTo(AsyncAction.Loading) assertThat(state.retryAction).isEqualTo(AsyncAction.Loading)
@ -158,12 +158,12 @@ class ResolveVerifiedUserSendFailurePresenterTest {
val failedMessage = aVerifiedUserHasUnsignedDeviceFailedMessage() val failedMessage = aVerifiedUserHasUnsignedDeviceFailedMessage()
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.None) assertThat(initialState.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.None)
initialState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(failedMessage)) initialState.eventSink(ResolveVerifiedUserSendFailureEvent.ComputeForMessage(failedMessage))
skipItems(1) skipItems(1)
awaitItem().also { state -> awaitItem().also { state ->
assertThat(state.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.UnsignedDevice.FromYou) assertThat(state.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.UnsignedDevice.FromYou)
state.eventSink(ResolveVerifiedUserSendFailureEvents.ResolveAndResend) state.eventSink(ResolveVerifiedUserSendFailureEvent.ResolveAndResend)
} }
awaitItem().also { state -> awaitItem().also { state ->
assertThat(state.resolveAction).isEqualTo(AsyncAction.Loading) assertThat(state.resolveAction).isEqualTo(AsyncAction.Loading)
@ -173,7 +173,7 @@ class ResolveVerifiedUserSendFailurePresenterTest {
awaitItem().also { state -> awaitItem().also { state ->
assertThat(state.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.UnsignedDevice.FromOther(A_USER_ID_2.value)) assertThat(state.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.UnsignedDevice.FromOther(A_USER_ID_2.value))
assertThat(state.resolveAction).isEqualTo(AsyncAction.Success(Unit)) assertThat(state.resolveAction).isEqualTo(AsyncAction.Success(Unit))
state.eventSink(ResolveVerifiedUserSendFailureEvents.ResolveAndResend) state.eventSink(ResolveVerifiedUserSendFailureEvent.ResolveAndResend)
} }
skipItems(3) skipItems(3)
@ -201,12 +201,12 @@ class ResolveVerifiedUserSendFailurePresenterTest {
val failedMessage = aVerifiedUserHasUnsignedDeviceFailedMessage() val failedMessage = aVerifiedUserHasUnsignedDeviceFailedMessage()
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.None) assertThat(initialState.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.None)
initialState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(failedMessage)) initialState.eventSink(ResolveVerifiedUserSendFailureEvent.ComputeForMessage(failedMessage))
skipItems(1) skipItems(1)
awaitItem().also { state -> awaitItem().also { state ->
assertThat(state.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.UnsignedDevice.FromYou) assertThat(state.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.UnsignedDevice.FromYou)
state.eventSink(ResolveVerifiedUserSendFailureEvents.ResolveAndResend) state.eventSink(ResolveVerifiedUserSendFailureEvent.ResolveAndResend)
} }
awaitItem().also { state -> awaitItem().also { state ->
assertThat(state.resolveAction).isEqualTo(AsyncAction.Loading) assertThat(state.resolveAction).isEqualTo(AsyncAction.Loading)
@ -233,12 +233,12 @@ class ResolveVerifiedUserSendFailurePresenterTest {
val failedMessage = aVerifiedUserChangedIdentityMessage() val failedMessage = aVerifiedUserChangedIdentityMessage()
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.None) assertThat(initialState.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.None)
initialState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(failedMessage)) initialState.eventSink(ResolveVerifiedUserSendFailureEvent.ComputeForMessage(failedMessage))
skipItems(1) skipItems(1)
awaitItem().also { state -> awaitItem().also { state ->
assertThat(state.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.ChangedIdentity(A_USER_ID.value)) assertThat(state.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.ChangedIdentity(A_USER_ID.value))
state.eventSink(ResolveVerifiedUserSendFailureEvents.Retry) state.eventSink(ResolveVerifiedUserSendFailureEvent.Retry)
} }
awaitItem().also { state -> awaitItem().also { state ->
assertThat(state.retryAction).isEqualTo(AsyncAction.Loading) assertThat(state.retryAction).isEqualTo(AsyncAction.Loading)
@ -269,12 +269,12 @@ class ResolveVerifiedUserSendFailurePresenterTest {
val failedMessage = aVerifiedUserChangedIdentityMessage() val failedMessage = aVerifiedUserChangedIdentityMessage()
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.None) assertThat(initialState.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.None)
initialState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(failedMessage)) initialState.eventSink(ResolveVerifiedUserSendFailureEvent.ComputeForMessage(failedMessage))
skipItems(1) skipItems(1)
awaitItem().also { state -> awaitItem().also { state ->
assertThat(state.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.ChangedIdentity(A_USER_ID.value)) assertThat(state.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.ChangedIdentity(A_USER_ID.value))
state.eventSink(ResolveVerifiedUserSendFailureEvents.ResolveAndResend) state.eventSink(ResolveVerifiedUserSendFailureEvent.ResolveAndResend)
} }
awaitItem().also { state -> awaitItem().also { state ->
assertThat(state.resolveAction).isEqualTo(AsyncAction.Loading) assertThat(state.resolveAction).isEqualTo(AsyncAction.Loading)
@ -284,7 +284,7 @@ class ResolveVerifiedUserSendFailurePresenterTest {
awaitItem().also { state -> awaitItem().also { state ->
assertThat(state.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.ChangedIdentity(A_USER_ID_2.value)) assertThat(state.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.ChangedIdentity(A_USER_ID_2.value))
assertThat(state.resolveAction).isEqualTo(AsyncAction.Success(Unit)) assertThat(state.resolveAction).isEqualTo(AsyncAction.Success(Unit))
state.eventSink(ResolveVerifiedUserSendFailureEvents.ResolveAndResend) state.eventSink(ResolveVerifiedUserSendFailureEvent.ResolveAndResend)
} }
skipItems(3) skipItems(3)
@ -312,12 +312,12 @@ class ResolveVerifiedUserSendFailurePresenterTest {
val failedMessage = aVerifiedUserChangedIdentityMessage() val failedMessage = aVerifiedUserChangedIdentityMessage()
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.None) assertThat(initialState.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.None)
initialState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(failedMessage)) initialState.eventSink(ResolveVerifiedUserSendFailureEvent.ComputeForMessage(failedMessage))
skipItems(1) skipItems(1)
awaitItem().also { state -> awaitItem().also { state ->
assertThat(state.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.ChangedIdentity(A_USER_ID.value)) assertThat(state.verifiedUserSendFailure).isEqualTo(VerifiedUserSendFailure.ChangedIdentity(A_USER_ID.value))
state.eventSink(ResolveVerifiedUserSendFailureEvents.ResolveAndResend) state.eventSink(ResolveVerifiedUserSendFailureEvent.ResolveAndResend)
} }
awaitItem().also { state -> awaitItem().also { state ->
assertThat(state.resolveAction).isEqualTo(AsyncAction.Loading) assertThat(state.resolveAction).isEqualTo(AsyncAction.Loading)

View file

@ -27,7 +27,7 @@ class ResolveVerifiedUserSendFailureViewTest {
@Test @Test
fun `clicking on resolve and resend emit the expected event`() { fun `clicking on resolve and resend emit the expected event`() {
val eventsRecorder = EventsRecorder<ResolveVerifiedUserSendFailureEvents>() val eventsRecorder = EventsRecorder<ResolveVerifiedUserSendFailureEvent>()
rule.setResolveVerifiedUserSendFailureView( rule.setResolveVerifiedUserSendFailureView(
state = aResolveVerifiedUserSendFailureState( state = aResolveVerifiedUserSendFailureState(
verifiedUserSendFailure = aChangedIdentitySendFailure(), verifiedUserSendFailure = aChangedIdentitySendFailure(),
@ -36,12 +36,12 @@ class ResolveVerifiedUserSendFailureViewTest {
) )
rule.clickOn(res = CommonStrings.screen_resolve_send_failure_changed_identity_primary_button_title) rule.clickOn(res = CommonStrings.screen_resolve_send_failure_changed_identity_primary_button_title)
eventsRecorder.assertSingle(ResolveVerifiedUserSendFailureEvents.ResolveAndResend) eventsRecorder.assertSingle(ResolveVerifiedUserSendFailureEvent.ResolveAndResend)
} }
@Test @Test
fun `clicking on retry emit the expected event`() { fun `clicking on retry emit the expected event`() {
val eventsRecorder = EventsRecorder<ResolveVerifiedUserSendFailureEvents>() val eventsRecorder = EventsRecorder<ResolveVerifiedUserSendFailureEvent>()
rule.setResolveVerifiedUserSendFailureView( rule.setResolveVerifiedUserSendFailureView(
state = aResolveVerifiedUserSendFailureState( state = aResolveVerifiedUserSendFailureState(
verifiedUserSendFailure = aChangedIdentitySendFailure(), verifiedUserSendFailure = aChangedIdentitySendFailure(),
@ -50,7 +50,7 @@ class ResolveVerifiedUserSendFailureViewTest {
) )
rule.clickOn(res = CommonStrings.action_retry) rule.clickOn(res = CommonStrings.action_retry)
eventsRecorder.assertSingle(ResolveVerifiedUserSendFailureEvents.Retry) eventsRecorder.assertSingle(ResolveVerifiedUserSendFailureEvent.Retry)
} }
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setResolveVerifiedUserSendFailureView( private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setResolveVerifiedUserSendFailureView(

View file

@ -45,7 +45,7 @@ class LinkPresenterTest {
presenter.test { presenter.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.linkClick).isEqualTo(AsyncAction.Uninitialized) assertThat(initialState.linkClick).isEqualTo(AsyncAction.Uninitialized)
initialState.eventSink(LinkEvents.OnLinkClick(aLink)) initialState.eventSink(LinkEvent.OnLinkClick(aLink))
assertThat(awaitItem().linkClick).isEqualTo(AsyncAction.Loading) assertThat(awaitItem().linkClick).isEqualTo(AsyncAction.Loading)
val state = awaitItem() val state = awaitItem()
assertThat(state.linkClick).isEqualTo(AsyncAction.Success(aLink)) assertThat(state.linkClick).isEqualTo(AsyncAction.Success(aLink))
@ -61,11 +61,11 @@ class LinkPresenterTest {
presenter.test { presenter.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.linkClick).isEqualTo(AsyncAction.Uninitialized) assertThat(initialState.linkClick).isEqualTo(AsyncAction.Uninitialized)
initialState.eventSink(LinkEvents.OnLinkClick(aLink)) initialState.eventSink(LinkEvent.OnLinkClick(aLink))
assertThat(awaitItem().linkClick).isEqualTo(AsyncAction.Loading) assertThat(awaitItem().linkClick).isEqualTo(AsyncAction.Loading)
val state = awaitItem() val state = awaitItem()
assertThat(state.linkClick).isEqualTo(ConfirmingLinkClick(aLink)) assertThat(state.linkClick).isEqualTo(ConfirmingLinkClick(aLink))
state.eventSink(LinkEvents.Cancel) state.eventSink(LinkEvent.Cancel)
val finalState = awaitItem() val finalState = awaitItem()
assertThat(finalState.linkClick).isEqualTo(AsyncAction.Uninitialized) assertThat(finalState.linkClick).isEqualTo(AsyncAction.Uninitialized)
} }
@ -79,11 +79,11 @@ class LinkPresenterTest {
presenter.test { presenter.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.linkClick).isEqualTo(AsyncAction.Uninitialized) assertThat(initialState.linkClick).isEqualTo(AsyncAction.Uninitialized)
initialState.eventSink(LinkEvents.OnLinkClick(aLink)) initialState.eventSink(LinkEvent.OnLinkClick(aLink))
assertThat(awaitItem().linkClick).isEqualTo(AsyncAction.Loading) assertThat(awaitItem().linkClick).isEqualTo(AsyncAction.Loading)
val state = awaitItem() val state = awaitItem()
assertThat(state.linkClick).isEqualTo(ConfirmingLinkClick(aLink)) assertThat(state.linkClick).isEqualTo(ConfirmingLinkClick(aLink))
state.eventSink(LinkEvents.Confirm) state.eventSink(LinkEvent.Confirm)
val finalState = awaitItem() val finalState = awaitItem()
assertThat(finalState.linkClick).isEqualTo(AsyncAction.Success(aLink)) assertThat(finalState.linkClick).isEqualTo(AsyncAction.Success(aLink))
} }

View file

@ -31,7 +31,7 @@ class LinkViewTest {
@Test @Test
fun `clicking on cancel emits the expected event`() { fun `clicking on cancel emits the expected event`() {
val eventsRecorder = EventsRecorder<LinkEvents>() val eventsRecorder = EventsRecorder<LinkEvent>()
rule.setLinkView( rule.setLinkView(
aLinkState( aLinkState(
linkClick = ConfirmingLinkClick(aLink), linkClick = ConfirmingLinkClick(aLink),
@ -40,13 +40,13 @@ class LinkViewTest {
) )
rule.clickOn(CommonStrings.action_cancel) rule.clickOn(CommonStrings.action_cancel)
eventsRecorder.assertSingle( eventsRecorder.assertSingle(
LinkEvents.Cancel LinkEvent.Cancel
) )
} }
@Test @Test
fun `clicking on continue emits the expected event`() { fun `clicking on continue emits the expected event`() {
val eventsRecorder = EventsRecorder<LinkEvents>() val eventsRecorder = EventsRecorder<LinkEvent>()
rule.setLinkView( rule.setLinkView(
aLinkState( aLinkState(
linkClick = ConfirmingLinkClick(aLink), linkClick = ConfirmingLinkClick(aLink),
@ -55,13 +55,13 @@ class LinkViewTest {
) )
rule.clickOn(CommonStrings.action_continue) rule.clickOn(CommonStrings.action_continue)
eventsRecorder.assertSingle( eventsRecorder.assertSingle(
LinkEvents.Confirm LinkEvent.Confirm
) )
} }
@Test @Test
fun `success state invokes the callback and emits the expected event`() { fun `success state invokes the callback and emits the expected event`() {
val eventsRecorder = EventsRecorder<LinkEvents>() val eventsRecorder = EventsRecorder<LinkEvent>()
ensureCalledOnceWithParam(aLink) { callback -> ensureCalledOnceWithParam(aLink) { callback ->
rule.setLinkView( rule.setLinkView(
aLinkState( aLinkState(
@ -72,7 +72,7 @@ class LinkViewTest {
) )
} }
eventsRecorder.assertSingle( eventsRecorder.assertSingle(
LinkEvents.Cancel LinkEvent.Cancel
) )
} }
} }

View file

@ -137,9 +137,7 @@ class MessageComposerPresenterTest {
@Test @Test
fun `present - initial state`() = runTest { fun `present - initial state`() = runTest {
val presenter = createPresenter() val presenter = createPresenter()
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
assertThat(initialState.isFullScreen).isFalse() assertThat(initialState.isFullScreen).isFalse()
assertThat(initialState.textEditorState.messageHtml()).isEqualTo("") assertThat(initialState.textEditorState.messageHtml()).isEqualTo("")
@ -152,9 +150,7 @@ class MessageComposerPresenterTest {
@Test @Test
fun `present - toggle fullscreen`() = runTest { fun `present - toggle fullscreen`() = runTest {
val presenter = createPresenter() val presenter = createPresenter()
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.eventSink.invoke(MessageComposerEvent.ToggleFullScreenState) initialState.eventSink.invoke(MessageComposerEvent.ToggleFullScreenState)
val fullscreenState = awaitItem() val fullscreenState = awaitItem()
@ -168,9 +164,7 @@ class MessageComposerPresenterTest {
@Test @Test
fun `present - change message`() = runTest { fun `present - change message`() = runTest {
val presenter = createPresenter() val presenter = createPresenter()
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.textEditorState.setHtml(A_MESSAGE) initialState.textEditorState.setHtml(A_MESSAGE)
assertThat(initialState.textEditorState.messageHtml()).isEqualTo(A_MESSAGE) assertThat(initialState.textEditorState.messageHtml()).isEqualTo(A_MESSAGE)
@ -347,9 +341,7 @@ class MessageComposerPresenterTest {
@Test @Test
fun `present - change mode to reply`() = runTest { fun `present - change mode to reply`() = runTest {
val presenter = createPresenter() val presenter = createPresenter()
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
var state = awaitFirstItem() var state = awaitFirstItem()
val mode = aReplyMode() val mode = aReplyMode()
state.eventSink.invoke(MessageComposerEvent.SetMode(mode)) state.eventSink.invoke(MessageComposerEvent.SetMode(mode))
@ -363,9 +355,7 @@ class MessageComposerPresenterTest {
@Test @Test
fun `present - cancel reply`() = runTest { fun `present - cancel reply`() = runTest {
val presenter = createPresenter() val presenter = createPresenter()
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
var state = awaitFirstItem() var state = awaitFirstItem()
val mode = aReplyMode() val mode = aReplyMode()
state.eventSink.invoke(MessageComposerEvent.SetMode(mode)) state.eventSink.invoke(MessageComposerEvent.SetMode(mode))
@ -625,9 +615,7 @@ class MessageComposerPresenterTest {
val presenter = createPresenter( val presenter = createPresenter(
joinedRoom, joinedRoom,
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
assertThat(initialState.textEditorState.messageHtml()).isEqualTo("") assertThat(initialState.textEditorState.messageHtml()).isEqualTo("")
val mode = aReplyMode() val mode = aReplyMode()
@ -661,9 +649,7 @@ class MessageComposerPresenterTest {
@Test @Test
fun `present - Open attachments menu`() = runTest { fun `present - Open attachments menu`() = runTest {
val presenter = createPresenter() val presenter = createPresenter()
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
assertThat(initialState.showAttachmentSourcePicker).isFalse() assertThat(initialState.showAttachmentSourcePicker).isFalse()
initialState.eventSink(MessageComposerEvent.AddAttachment) initialState.eventSink(MessageComposerEvent.AddAttachment)
@ -674,9 +660,7 @@ class MessageComposerPresenterTest {
@Test @Test
fun `present - Dismiss attachments menu`() = runTest { fun `present - Dismiss attachments menu`() = runTest {
val presenter = createPresenter() val presenter = createPresenter()
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.eventSink(MessageComposerEvent.AddAttachment) initialState.eventSink(MessageComposerEvent.AddAttachment)
skipItems(1) skipItems(1)
@ -717,9 +701,7 @@ class MessageComposerPresenterTest {
) )
) )
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.eventSink(MessageComposerEvent.PickAttachmentSource.FromGallery) initialState.eventSink(MessageComposerEvent.PickAttachmentSource.FromGallery)
onPreviewAttachmentLambda.assertions().isCalledOnce() onPreviewAttachmentLambda.assertions().isCalledOnce()
@ -758,9 +740,7 @@ class MessageComposerPresenterTest {
) )
) )
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.eventSink(MessageComposerEvent.PickAttachmentSource.FromGallery) initialState.eventSink(MessageComposerEvent.PickAttachmentSource.FromGallery)
onPreviewAttachmentLambda.assertions().isCalledOnce() onPreviewAttachmentLambda.assertions().isCalledOnce()
@ -774,9 +754,7 @@ class MessageComposerPresenterTest {
givenResult(null) // Simulate a user canceling the flow givenResult(null) // Simulate a user canceling the flow
givenMimeType(MimeTypes.Images) givenMimeType(MimeTypes.Images)
} }
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.eventSink(MessageComposerEvent.PickAttachmentSource.FromGallery) initialState.eventSink(MessageComposerEvent.PickAttachmentSource.FromGallery)
// No crashes here, otherwise it fails // No crashes here, otherwise it fails
@ -796,9 +774,7 @@ class MessageComposerPresenterTest {
room = room, room = room,
navigator = navigator, navigator = navigator,
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.eventSink(MessageComposerEvent.PickAttachmentSource.FromFiles) initialState.eventSink(MessageComposerEvent.PickAttachmentSource.FromFiles)
onPreviewAttachmentLambda.assertions().isCalledOnce() onPreviewAttachmentLambda.assertions().isCalledOnce()
@ -811,9 +787,7 @@ class MessageComposerPresenterTest {
typingNoticeResult = { Result.success(Unit) } typingNoticeResult = { Result.success(Unit) }
) )
val presenter = createPresenter(room = room) val presenter = createPresenter(room = room)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.eventSink(MessageComposerEvent.AddAttachment) initialState.eventSink(MessageComposerEvent.AddAttachment)
val attachmentOpenState = awaitItem() val attachmentOpenState = awaitItem()
@ -830,9 +804,7 @@ class MessageComposerPresenterTest {
typingNoticeResult = { Result.success(Unit) } typingNoticeResult = { Result.success(Unit) }
) )
val presenter = createPresenter(room = room) val presenter = createPresenter(room = room)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.eventSink(MessageComposerEvent.AddAttachment) initialState.eventSink(MessageComposerEvent.AddAttachment)
val attachmentOpenState = awaitItem() val attachmentOpenState = awaitItem()
@ -858,9 +830,7 @@ class MessageComposerPresenterTest {
permissionPresenter = permissionPresenter, permissionPresenter = permissionPresenter,
navigator = navigator, navigator = navigator,
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.eventSink(MessageComposerEvent.PickAttachmentSource.PhotoFromCamera) initialState.eventSink(MessageComposerEvent.PickAttachmentSource.PhotoFromCamera)
onPreviewAttachmentLambda.assertions().isCalledOnce() onPreviewAttachmentLambda.assertions().isCalledOnce()
@ -882,9 +852,7 @@ class MessageComposerPresenterTest {
permissionPresenter = permissionPresenter, permissionPresenter = permissionPresenter,
navigator = navigator, navigator = navigator,
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.eventSink(MessageComposerEvent.PickAttachmentSource.PhotoFromCamera) initialState.eventSink(MessageComposerEvent.PickAttachmentSource.PhotoFromCamera)
permissionPresenter.setPermissionGranted() permissionPresenter.setPermissionGranted()
@ -908,9 +876,7 @@ class MessageComposerPresenterTest {
permissionPresenter = permissionPresenter, permissionPresenter = permissionPresenter,
navigator = navigator, navigator = navigator,
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.eventSink(MessageComposerEvent.PickAttachmentSource.VideoFromCamera) initialState.eventSink(MessageComposerEvent.PickAttachmentSource.VideoFromCamera)
onPreviewAttachmentLambda.assertions().isCalledOnce() onPreviewAttachmentLambda.assertions().isCalledOnce()
@ -932,9 +898,7 @@ class MessageComposerPresenterTest {
permissionPresenter = permissionPresenter, permissionPresenter = permissionPresenter,
navigator = navigator, navigator = navigator,
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.eventSink(MessageComposerEvent.PickAttachmentSource.VideoFromCamera) initialState.eventSink(MessageComposerEvent.PickAttachmentSource.VideoFromCamera)
val permissionState = awaitItem() val permissionState = awaitItem()
@ -949,9 +913,7 @@ class MessageComposerPresenterTest {
fun `present - errors are tracked`() = runTest { fun `present - errors are tracked`() = runTest {
val testException = Exception("Test error") val testException = Exception("Test error")
val presenter = createPresenter() val presenter = createPresenter()
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.eventSink(MessageComposerEvent.Error(testException)) initialState.eventSink(MessageComposerEvent.Error(testException))
assertThat(analyticsService.trackedErrors).containsExactly(testException) assertThat(analyticsService.trackedErrors).containsExactly(testException)
@ -961,9 +923,7 @@ class MessageComposerPresenterTest {
@Test @Test
fun `present - ToggleTextFormatting toggles text formatting`() = runTest { fun `present - ToggleTextFormatting toggles text formatting`() = runTest {
val presenter = createPresenter(isRichTextEditorEnabled = false) val presenter = createPresenter(isRichTextEditorEnabled = false)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
assertThat(initialState.showTextFormatting).isFalse() assertThat(initialState.showTextFormatting).isFalse()
initialState.eventSink(MessageComposerEvent.AddAttachment) initialState.eventSink(MessageComposerEvent.AddAttachment)
@ -1008,9 +968,7 @@ class MessageComposerPresenterTest {
givenRoomInfo(aRoomInfo(isDirect = false)) givenRoomInfo(aRoomInfo(isDirect = false))
} }
val presenter = createPresenter(room) val presenter = createPresenter(room)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
// A null suggestion (no suggestion was received) returns nothing // A null suggestion (no suggestion was received) returns nothing
@ -1062,9 +1020,7 @@ class MessageComposerPresenterTest {
givenRoomInfo(aRoomInfo(isDirect = false)) givenRoomInfo(aRoomInfo(isDirect = false))
} }
val presenter = createPresenter(room) val presenter = createPresenter(room)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
// An empty suggestion returns the joined members that are not the current user, but not the room // An empty suggestion returns the joined members that are not the current user, but not the room
initialState.eventSink(MessageComposerEvent.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, ""))) initialState.eventSink(MessageComposerEvent.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, "")))
@ -1099,9 +1055,7 @@ class MessageComposerPresenterTest {
) )
} }
val presenter = createPresenter(room) val presenter = createPresenter(room)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
// An empty suggestion returns the joined members that are not the current user, but not the room // An empty suggestion returns the joined members that are not the current user, but not the room
initialState.eventSink(MessageComposerEvent.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, ""))) initialState.eventSink(MessageComposerEvent.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, "")))
@ -1119,9 +1073,7 @@ class MessageComposerPresenterTest {
} }
) )
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.textEditorState.setHtml("Hey @bo") initialState.textEditorState.setHtml("Hey @bo")
initialState.eventSink(MessageComposerEvent.InsertSuggestion(ResolvedSuggestion.Member(aRoomMember(userId = A_USER_ID_2)))) initialState.eventSink(MessageComposerEvent.InsertSuggestion(ResolvedSuggestion.Member(aRoomMember(userId = A_USER_ID_2))))
@ -1153,9 +1105,7 @@ class MessageComposerPresenterTest {
typingNoticeResult = { Result.success(Unit) } typingNoticeResult = { Result.success(Unit) }
) )
val presenter = createPresenter(room = room) val presenter = createPresenter(room = room)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
// Check intentional mentions on message sent // Check intentional mentions on message sent
@ -1242,9 +1192,7 @@ class MessageComposerPresenterTest {
typingNoticeResult = typingNoticeResult, typingNoticeResult = typingNoticeResult,
) )
val presenter = createPresenter(room = room) val presenter = createPresenter(room = room)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
typingNoticeResult.assertions().isNeverCalled() typingNoticeResult.assertions().isNeverCalled()
initialState.eventSink.invoke(MessageComposerEvent.TypingNotice(true)) initialState.eventSink.invoke(MessageComposerEvent.TypingNotice(true))
@ -1268,9 +1216,7 @@ class MessageComposerPresenterTest {
isSendTypingNotificationsEnabled = false isSendTypingNotificationsEnabled = false
) )
val presenter = createPresenter(room = room, sessionPreferencesStore = store) val presenter = createPresenter(room = room, sessionPreferencesStore = store)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
typingNoticeResult.assertions().isNeverCalled() typingNoticeResult.assertions().isNeverCalled()
initialState.eventSink.invoke(MessageComposerEvent.TypingNotice(true)) initialState.eventSink.invoke(MessageComposerEvent.TypingNotice(true))
@ -1286,9 +1232,7 @@ class MessageComposerPresenterTest {
this.loadDraftLambda = loadDraftLambda this.loadDraftLambda = loadDraftLambda
} }
val presenter = createPresenter(draftService = composerDraftService) val presenter = createPresenter(draftService = composerDraftService)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
awaitFirstItem() awaitFirstItem()
assert(loadDraftLambda) assert(loadDraftLambda)
.isCalledOnce() .isCalledOnce()
@ -1311,9 +1255,7 @@ class MessageComposerPresenterTest {
draftService = composerDraftService, draftService = composerDraftService,
permalinkBuilder = permalinkBuilder, permalinkBuilder = permalinkBuilder,
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
skipItems(2) skipItems(2)
awaitItem().also { state -> awaitItem().also { state ->
assertThat(state.textEditorState.messageMarkdown(permalinkBuilder)).isEqualTo(A_MESSAGE) assertThat(state.textEditorState.messageMarkdown(permalinkBuilder)).isEqualTo(A_MESSAGE)
@ -1344,9 +1286,7 @@ class MessageComposerPresenterTest {
draftService = composerDraftService, draftService = composerDraftService,
permalinkBuilder = permalinkBuilder, permalinkBuilder = permalinkBuilder,
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
skipItems(1) skipItems(1)
awaitItem().also { state -> awaitItem().also { state ->
assertThat(state.showTextFormatting).isTrue() assertThat(state.showTextFormatting).isTrue()
@ -1377,9 +1317,7 @@ class MessageComposerPresenterTest {
draftService = composerDraftService, draftService = composerDraftService,
permalinkBuilder = permalinkBuilder, permalinkBuilder = permalinkBuilder,
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
skipItems(2) skipItems(2)
awaitItem().also { state -> awaitItem().also { state ->
assertThat(state.showTextFormatting).isFalse() assertThat(state.showTextFormatting).isFalse()
@ -1423,9 +1361,7 @@ class MessageComposerPresenterTest {
draftService = composerDraftService, draftService = composerDraftService,
permalinkBuilder = permalinkBuilder, permalinkBuilder = permalinkBuilder,
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
skipItems(2) skipItems(2)
awaitItem().also { state -> awaitItem().also { state ->
assertThat(state.showTextFormatting).isFalse() assertThat(state.showTextFormatting).isFalse()
@ -1452,9 +1388,7 @@ class MessageComposerPresenterTest {
this.saveDraftLambda = saveDraftLambda this.saveDraftLambda = saveDraftLambda
} }
val presenter = createPresenter(draftService = composerDraftService) val presenter = createPresenter(draftService = composerDraftService)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.eventSink.invoke(MessageComposerEvent.SaveDraft) initialState.eventSink.invoke(MessageComposerEvent.SaveDraft)
advanceUntilIdle() advanceUntilIdle()

View file

@ -127,7 +127,7 @@ class PinnedMessagesBannerPresenterTest {
assertThat(loadedState.currentPinnedMessageIndex).isEqualTo(1) assertThat(loadedState.currentPinnedMessageIndex).isEqualTo(1)
assertThat(loadedState.loadedPinnedMessagesCount).isEqualTo(2) assertThat(loadedState.loadedPinnedMessagesCount).isEqualTo(2)
assertThat(loadedState.currentPinnedMessage.formatted.text).isEqualTo(messageContent2.toString()) assertThat(loadedState.currentPinnedMessage.formatted.text).isEqualTo(messageContent2.toString())
loadedState.eventSink(PinnedMessagesBannerEvents.MoveToNextPinned) loadedState.eventSink(PinnedMessagesBannerEvent.MoveToNextPinned)
} }
awaitItem().also { loadedState -> awaitItem().also { loadedState ->
@ -135,7 +135,7 @@ class PinnedMessagesBannerPresenterTest {
assertThat(loadedState.currentPinnedMessageIndex).isEqualTo(0) assertThat(loadedState.currentPinnedMessageIndex).isEqualTo(0)
assertThat(loadedState.loadedPinnedMessagesCount).isEqualTo(2) assertThat(loadedState.loadedPinnedMessagesCount).isEqualTo(2)
assertThat(loadedState.currentPinnedMessage.formatted.text).isEqualTo(messageContent1.toString()) assertThat(loadedState.currentPinnedMessage.formatted.text).isEqualTo(messageContent1.toString())
loadedState.eventSink(PinnedMessagesBannerEvents.MoveToNextPinned) loadedState.eventSink(PinnedMessagesBannerEvent.MoveToNextPinned)
} }
awaitItem().also { loadedState -> awaitItem().also { loadedState ->

View file

@ -33,7 +33,7 @@ class PinnedMessagesBannerViewTest {
@Test @Test
fun `clicking on the banner invoke expected callback`() { fun `clicking on the banner invoke expected callback`() {
val eventsRecorder = EventsRecorder<PinnedMessagesBannerEvents>() val eventsRecorder = EventsRecorder<PinnedMessagesBannerEvent>()
val state = aLoadedPinnedMessagesBannerState( val state = aLoadedPinnedMessagesBannerState(
eventSink = eventsRecorder eventSink = eventsRecorder
) )
@ -44,13 +44,13 @@ class PinnedMessagesBannerViewTest {
onClick = callback onClick = callback
) )
rule.onRoot().performClick() rule.onRoot().performClick()
eventsRecorder.assertSingle(PinnedMessagesBannerEvents.MoveToNextPinned) eventsRecorder.assertSingle(PinnedMessagesBannerEvent.MoveToNextPinned)
} }
} }
@Test @Test
fun `clicking on view all emit the expected event`() { fun `clicking on view all emit the expected event`() {
val eventsRecorder = EventsRecorder<PinnedMessagesBannerEvents>(expectEvents = true) val eventsRecorder = EventsRecorder<PinnedMessagesBannerEvent>(expectEvents = true)
val state = aLoadedPinnedMessagesBannerState( val state = aLoadedPinnedMessagesBannerState(
eventSink = eventsRecorder eventSink = eventsRecorder
) )

View file

@ -152,11 +152,11 @@ class PinnedMessagesListPresenterTest {
val eventItem = filledState.timelineItems.first() as TimelineItem.Event val eventItem = filledState.timelineItems.first() as TimelineItem.Event
pinnedEventsTimeline.unpinEventLambda = successUnpinEventLambda pinnedEventsTimeline.unpinEventLambda = successUnpinEventLambda
filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.Unpin, eventItem)) filledState.eventSink(PinnedMessagesListEvent.HandleAction(TimelineItemAction.Unpin, eventItem))
advanceUntilIdle() advanceUntilIdle()
pinnedEventsTimeline.unpinEventLambda = failureUnpinEventLambda pinnedEventsTimeline.unpinEventLambda = failureUnpinEventLambda
filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.Unpin, eventItem)) filledState.eventSink(PinnedMessagesListEvent.HandleAction(TimelineItemAction.Unpin, eventItem))
advanceUntilIdle() advanceUntilIdle()
cancelAndIgnoreRemainingEvents() cancelAndIgnoreRemainingEvents()
@ -196,7 +196,7 @@ class PinnedMessagesListPresenterTest {
skipItems(3) skipItems(3)
val filledState = awaitItem() as PinnedMessagesListState.Filled val filledState = awaitItem() as PinnedMessagesListState.Filled
val eventItem = filledState.timelineItems.first() as TimelineItem.Event val eventItem = filledState.timelineItems.first() as TimelineItem.Event
filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.ViewInTimeline, eventItem)) filledState.eventSink(PinnedMessagesListEvent.HandleAction(TimelineItemAction.ViewInTimeline, eventItem))
advanceUntilIdle() advanceUntilIdle()
cancelAndIgnoreRemainingEvents() cancelAndIgnoreRemainingEvents()
assert(onViewInTimelineClickLambda) assert(onViewInTimelineClickLambda)
@ -225,7 +225,7 @@ class PinnedMessagesListPresenterTest {
skipItems(3) skipItems(3)
val filledState = awaitItem() as PinnedMessagesListState.Filled val filledState = awaitItem() as PinnedMessagesListState.Filled
val eventItem = filledState.timelineItems.first() as TimelineItem.Event val eventItem = filledState.timelineItems.first() as TimelineItem.Event
filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.ViewSource, eventItem)) filledState.eventSink(PinnedMessagesListEvent.HandleAction(TimelineItemAction.ViewSource, eventItem))
advanceUntilIdle() advanceUntilIdle()
cancelAndIgnoreRemainingEvents() cancelAndIgnoreRemainingEvents()
assert(onShowEventDebugInfoClickLambda) assert(onShowEventDebugInfoClickLambda)
@ -254,7 +254,7 @@ class PinnedMessagesListPresenterTest {
skipItems(3) skipItems(3)
val filledState = awaitItem() as PinnedMessagesListState.Filled val filledState = awaitItem() as PinnedMessagesListState.Filled
val eventItem = filledState.timelineItems.first() as TimelineItem.Event val eventItem = filledState.timelineItems.first() as TimelineItem.Event
filledState.eventSink(PinnedMessagesListEvents.HandleAction(TimelineItemAction.Forward, eventItem)) filledState.eventSink(PinnedMessagesListEvent.HandleAction(TimelineItemAction.Forward, eventItem))
advanceUntilIdle() advanceUntilIdle()
cancelAndIgnoreRemainingEvents() cancelAndIgnoreRemainingEvents()
assert(onForwardEventClickLambda) assert(onForwardEventClickLambda)

View file

@ -17,7 +17,7 @@ import androidx.compose.ui.test.onFirst
import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTouchInput import androidx.compose.ui.test.performTouchInput
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.messages.impl.actionlist.ActionListEvents import io.element.android.features.messages.impl.actionlist.ActionListEvent
import io.element.android.features.messages.impl.actionlist.anActionListState import io.element.android.features.messages.impl.actionlist.anActionListState
import io.element.android.features.messages.impl.timeline.aTimelineItemList import io.element.android.features.messages.impl.timeline.aTimelineItemList
import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItem
@ -43,7 +43,7 @@ class PinnedMessagesListViewTest {
@Test @Test
fun `clicking on back calls the expected callback`() { fun `clicking on back calls the expected callback`() {
val eventsRecorder = EventsRecorder<PinnedMessagesListEvents>(expectEvents = false) val eventsRecorder = EventsRecorder<PinnedMessagesListEvent>(expectEvents = false)
val state = aLoadedPinnedMessagesListState( val state = aLoadedPinnedMessagesListState(
eventSink = eventsRecorder eventSink = eventsRecorder
) )
@ -58,7 +58,7 @@ class PinnedMessagesListViewTest {
@Test @Test
fun `click on an event calls the expected callback`() { fun `click on an event calls the expected callback`() {
val eventsRecorder = EventsRecorder<PinnedMessagesListEvents>(expectEvents = false) val eventsRecorder = EventsRecorder<PinnedMessagesListEvent>(expectEvents = false)
val content = aTimelineItemFileContent() val content = aTimelineItemFileContent()
val state = aLoadedPinnedMessagesListState( val state = aLoadedPinnedMessagesListState(
timelineItems = aTimelineItemList(content), timelineItems = aTimelineItemList(content),
@ -77,7 +77,7 @@ class PinnedMessagesListViewTest {
@Test @Test
fun `long click on an event emits the expected event`() { fun `long click on an event emits the expected event`() {
val eventsRecorder = EventsRecorder<ActionListEvents>(expectEvents = true) val eventsRecorder = EventsRecorder<ActionListEvent>(expectEvents = true)
val content = aTimelineItemFileContent() val content = aTimelineItemFileContent()
val state = aLoadedPinnedMessagesListState( val state = aLoadedPinnedMessagesListState(
timelineItems = aTimelineItemList(content), timelineItems = aTimelineItemList(content),
@ -92,7 +92,7 @@ class PinnedMessagesListViewTest {
longClick() longClick()
} }
val event = state.timelineItems.first() as TimelineItem.Event val event = state.timelineItems.first() as TimelineItem.Event
eventsRecorder.assertSingle(ActionListEvents.ComputeForMessage(event, state.userEventPermissions)) eventsRecorder.assertSingle(ActionListEvent.ComputeForMessage(event, state.userEventPermissions))
} }
} }

View file

@ -8,9 +8,6 @@
package io.element.android.features.messages.impl.report package io.element.android.features.messages.impl.report
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
@ -22,6 +19,7 @@ import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.test
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
@ -33,9 +31,7 @@ class ReportMessagePresenterTest {
@Test @Test
fun `presenter - initial state`() = runTest { fun `presenter - initial state`() = runTest {
val presenter = createReportMessagePresenter() val presenter = createReportMessagePresenter()
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.reason).isEmpty() assertThat(initialState.reason).isEmpty()
assertThat(initialState.blockUser).isFalse() assertThat(initialState.blockUser).isFalse()
@ -46,12 +42,10 @@ class ReportMessagePresenterTest {
@Test @Test
fun `presenter - update reason`() = runTest { fun `presenter - update reason`() = runTest {
val presenter = createReportMessagePresenter() val presenter = createReportMessagePresenter()
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val reason = "This user is making the chat very toxic." val reason = "This user is making the chat very toxic."
initialState.eventSink(ReportMessageEvents.UpdateReason(reason)) initialState.eventSink(ReportMessageEvent.UpdateReason(reason))
assertThat(awaitItem().reason).isEqualTo(reason) assertThat(awaitItem().reason).isEqualTo(reason)
} }
@ -60,15 +54,13 @@ class ReportMessagePresenterTest {
@Test @Test
fun `presenter - toggle block user`() = runTest { fun `presenter - toggle block user`() = runTest {
val presenter = createReportMessagePresenter() val presenter = createReportMessagePresenter()
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(ReportMessageEvents.ToggleBlockUser) initialState.eventSink(ReportMessageEvent.ToggleBlockUser)
assertThat(awaitItem().blockUser).isTrue() assertThat(awaitItem().blockUser).isTrue()
initialState.eventSink(ReportMessageEvents.ToggleBlockUser) initialState.eventSink(ReportMessageEvent.ToggleBlockUser)
assertThat(awaitItem().blockUser).isFalse() assertThat(awaitItem().blockUser).isFalse()
} }
@ -83,13 +75,11 @@ class ReportMessagePresenterTest {
reportContentResult = reportContentResult reportContentResult = reportContentResult
) )
val presenter = createReportMessagePresenter(joinedRoom = room) val presenter = createReportMessagePresenter(joinedRoom = room)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(ReportMessageEvents.ToggleBlockUser) initialState.eventSink(ReportMessageEvent.ToggleBlockUser)
skipItems(1) skipItems(1)
initialState.eventSink(ReportMessageEvents.Report) initialState.eventSink(ReportMessageEvent.Report)
assertThat(awaitItem().result).isInstanceOf(AsyncAction.Loading::class.java) assertThat(awaitItem().result).isInstanceOf(AsyncAction.Loading::class.java)
assertThat(awaitItem().result).isInstanceOf(AsyncAction.Success::class.java) assertThat(awaitItem().result).isInstanceOf(AsyncAction.Success::class.java)
reportContentResult.assertions().isCalledOnce() reportContentResult.assertions().isCalledOnce()
@ -105,11 +95,9 @@ class ReportMessagePresenterTest {
reportContentResult = reportContentResult reportContentResult = reportContentResult
) )
val presenter = createReportMessagePresenter(joinedRoom = room) val presenter = createReportMessagePresenter(joinedRoom = room)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(ReportMessageEvents.Report) initialState.eventSink(ReportMessageEvent.Report)
assertThat(awaitItem().result).isInstanceOf(AsyncAction.Loading::class.java) assertThat(awaitItem().result).isInstanceOf(AsyncAction.Loading::class.java)
assertThat(awaitItem().result).isInstanceOf(AsyncAction.Success::class.java) assertThat(awaitItem().result).isInstanceOf(AsyncAction.Success::class.java)
reportContentResult.assertions().isCalledOnce() reportContentResult.assertions().isCalledOnce()
@ -125,17 +113,15 @@ class ReportMessagePresenterTest {
reportContentResult = reportContentResult reportContentResult = reportContentResult
) )
val presenter = createReportMessagePresenter(joinedRoom = room) val presenter = createReportMessagePresenter(joinedRoom = room)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(ReportMessageEvents.Report) initialState.eventSink(ReportMessageEvent.Report)
assertThat(awaitItem().result).isInstanceOf(AsyncAction.Loading::class.java) assertThat(awaitItem().result).isInstanceOf(AsyncAction.Loading::class.java)
val resultState = awaitItem() val resultState = awaitItem()
assertThat(resultState.result).isInstanceOf(AsyncAction.Failure::class.java) assertThat(resultState.result).isInstanceOf(AsyncAction.Failure::class.java)
reportContentResult.assertions().isCalledOnce() reportContentResult.assertions().isCalledOnce()
resultState.eventSink(ReportMessageEvents.ClearError) resultState.eventSink(ReportMessageEvent.ClearError)
assertThat(awaitItem().result).isInstanceOf(AsyncAction.Uninitialized::class.java) assertThat(awaitItem().result).isInstanceOf(AsyncAction.Uninitialized::class.java)
} }
} }

View file

@ -9,7 +9,6 @@
package io.element.android.features.messages.impl.timeline package io.element.android.features.messages.impl.timeline
import app.cash.turbine.ReceiveTurbine import app.cash.turbine.ReceiveTurbine
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.messages.impl.FakeMessagesNavigator import io.element.android.features.messages.impl.FakeMessagesNavigator
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.aResolveVerifiedUserSendFailureState import io.element.android.features.messages.impl.crypto.sendfailure.resolve.aResolveVerifiedUserSendFailureState
@ -120,8 +119,8 @@ class TimelinePresenterTest {
val presenter = createTimelinePresenter(timeline = timeline) val presenter = createTimelinePresenter(timeline = timeline)
presenter.test { presenter.test {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink.invoke(TimelineEvents.LoadMore(Timeline.PaginationDirection.BACKWARDS)) initialState.eventSink.invoke(TimelineEvent.LoadMore(Timeline.PaginationDirection.BACKWARDS))
initialState.eventSink.invoke(TimelineEvents.LoadMore(Timeline.PaginationDirection.FORWARDS)) initialState.eventSink.invoke(TimelineEvent.LoadMore(Timeline.PaginationDirection.FORWARDS))
assert(paginateLambda) assert(paginateLambda)
.isCalledExactly(2) .isCalledExactly(2)
.withSequence( .withSequence(
@ -173,7 +172,7 @@ class TimelinePresenterTest {
) )
presenter.test { presenter.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(0)) initialState.eventSink.invoke(TimelineEvent.OnScrollFinished(0))
runCurrent() runCurrent()
assert(markAsReadResult) assert(markAsReadResult)
.isCalledOnce() .isCalledOnce()
@ -207,7 +206,7 @@ class TimelinePresenterTest {
presenter.test { presenter.test {
skipItems(1) skipItems(1)
awaitItem().run { awaitItem().run {
eventSink.invoke(TimelineEvents.OnScrollFinished(1)) eventSink.invoke(TimelineEvent.OnScrollFinished(1))
} }
advanceUntilIdle() advanceUntilIdle()
assert(sendReadReceiptsLambda) assert(sendReadReceiptsLambda)
@ -246,8 +245,8 @@ class TimelinePresenterTest {
presenter.test { presenter.test {
skipItems(1) skipItems(1)
awaitItem().run { awaitItem().run {
eventSink.invoke(TimelineEvents.OnScrollFinished(0)) eventSink.invoke(TimelineEvent.OnScrollFinished(0))
eventSink.invoke(TimelineEvents.OnScrollFinished(1)) eventSink.invoke(TimelineEvent.OnScrollFinished(1))
} }
advanceUntilIdle() advanceUntilIdle()
assert(sendReadReceiptsLambda) assert(sendReadReceiptsLambda)
@ -282,8 +281,8 @@ class TimelinePresenterTest {
presenter.test { presenter.test {
skipItems(1) skipItems(1)
awaitItem().run { awaitItem().run {
eventSink.invoke(TimelineEvents.OnScrollFinished(1)) eventSink.invoke(TimelineEvent.OnScrollFinished(1))
eventSink.invoke(TimelineEvents.OnScrollFinished(1)) eventSink.invoke(TimelineEvent.OnScrollFinished(1))
} }
advanceUntilIdle() advanceUntilIdle()
cancelAndIgnoreRemainingEvents() cancelAndIgnoreRemainingEvents()
@ -310,7 +309,7 @@ class TimelinePresenterTest {
presenter.test { presenter.test {
skipItems(1) skipItems(1)
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(1)) initialState.eventSink.invoke(TimelineEvent.OnScrollFinished(1))
cancelAndIgnoreRemainingEvents() cancelAndIgnoreRemainingEvents()
assert(sendReadReceiptsLambda).isNeverCalled() assert(sendReadReceiptsLambda).isNeverCalled()
} }
@ -349,7 +348,7 @@ class TimelinePresenterTest {
consumeItemsUntilPredicate { it.timelineItems.size == 3 } consumeItemsUntilPredicate { it.timelineItems.size == 3 }
// Scroll to bottom to clear previous FromMe // Scroll to bottom to clear previous FromMe
initialState.eventSink.invoke(TimelineEvents.OnScrollFinished(0)) initialState.eventSink.invoke(TimelineEvent.OnScrollFinished(0))
awaitLastSequentialItem().also { state -> awaitLastSequentialItem().also { state ->
assertThat(state.newEventState).isEqualTo(NewEventState.None) assertThat(state.newEventState).isEqualTo(NewEventState.None)
} }
@ -429,7 +428,7 @@ class TimelinePresenterTest {
) )
presenter.test { presenter.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.eventSink.invoke(TimelineEvents.SelectPollAnswer(AN_EVENT_ID, "anAnswerId")) initialState.eventSink.invoke(TimelineEvent.SelectPollAnswer(AN_EVENT_ID, "anAnswerId"))
} }
delay(1) delay(1)
sendPollResponseAction.verifyExecutionCount(1) sendPollResponseAction.verifyExecutionCount(1)
@ -443,7 +442,7 @@ class TimelinePresenterTest {
) )
presenter.test { presenter.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.eventSink.invoke(TimelineEvents.EndPoll(AN_EVENT_ID)) initialState.eventSink.invoke(TimelineEvent.EndPoll(AN_EVENT_ID))
} }
delay(1) delay(1)
endPollAction.verifyExecutionCount(1) endPollAction.verifyExecutionCount(1)
@ -459,7 +458,7 @@ class TimelinePresenterTest {
messagesNavigator = navigator, messagesNavigator = navigator,
) )
presenter.test { presenter.test {
awaitFirstItem().eventSink(TimelineEvents.EditPoll(AN_EVENT_ID)) awaitFirstItem().eventSink(TimelineEvent.EditPoll(AN_EVENT_ID))
onEditPollClickLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID)) onEditPollClickLambda.assertions().isCalledOnce().with(value(AN_EVENT_ID))
} }
} }
@ -510,7 +509,7 @@ class TimelinePresenterTest {
) )
presenter.test { presenter.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.eventSink.invoke(TimelineEvents.FocusOnEvent(AN_EVENT_ID)) initialState.eventSink.invoke(TimelineEvent.FocusOnEvent(AN_EVENT_ID))
awaitItem().also { state -> awaitItem().also { state ->
assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID) assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID)
assertThat(state.focusRequestState).isEqualTo(FocusRequestState.Requested(AN_EVENT_ID, Duration.ZERO)) assertThat(state.focusRequestState).isEqualTo(FocusRequestState.Requested(AN_EVENT_ID, Duration.ZERO))
@ -524,7 +523,7 @@ class TimelinePresenterTest {
assertThat(state.focusRequestState).isEqualTo(FocusRequestState.Success(AN_EVENT_ID)) assertThat(state.focusRequestState).isEqualTo(FocusRequestState.Success(AN_EVENT_ID))
assertThat(state.timelineItems).isNotEmpty() assertThat(state.timelineItems).isNotEmpty()
} }
initialState.eventSink.invoke(TimelineEvents.JumpToLive) initialState.eventSink.invoke(TimelineEvent.JumpToLive)
skipItems(2) skipItems(2)
awaitItem().also { state -> awaitItem().also { state ->
// Event stays focused // Event stays focused
@ -564,7 +563,7 @@ class TimelinePresenterTest {
// Pre-populate the indexer after the first items have been retrieved // Pre-populate the indexer after the first items have been retrieved
timelineItemIndexer.process(listOf(aMessageEvent(eventId = AN_EVENT_ID))) timelineItemIndexer.process(listOf(aMessageEvent(eventId = AN_EVENT_ID)))
initialState.eventSink.invoke(TimelineEvents.FocusOnEvent(AN_EVENT_ID)) initialState.eventSink.invoke(TimelineEvent.FocusOnEvent(AN_EVENT_ID))
advanceUntilIdle() advanceUntilIdle()
@ -595,7 +594,7 @@ class TimelinePresenterTest {
) )
presenter.test { presenter.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.eventSink(TimelineEvents.FocusOnEvent(AN_EVENT_ID)) initialState.eventSink(TimelineEvent.FocusOnEvent(AN_EVENT_ID))
awaitItem().also { state -> awaitItem().also { state ->
assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID) assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID)
assertThat(state.focusRequestState).isEqualTo(FocusRequestState.Requested(AN_EVENT_ID, Duration.ZERO)) assertThat(state.focusRequestState).isEqualTo(FocusRequestState.Requested(AN_EVENT_ID, Duration.ZERO))
@ -606,7 +605,7 @@ class TimelinePresenterTest {
} }
awaitItem().also { state -> awaitItem().also { state ->
assertThat(state.focusRequestState).isInstanceOf(FocusRequestState.Failure::class.java) assertThat(state.focusRequestState).isInstanceOf(FocusRequestState.Failure::class.java)
state.eventSink(TimelineEvents.ClearFocusRequestState) state.eventSink(TimelineEvent.ClearFocusRequestState)
} }
awaitItem().also { state -> awaitItem().also { state ->
assertThat(state.focusRequestState).isEqualTo(FocusRequestState.None) assertThat(state.focusRequestState).isEqualTo(FocusRequestState.None)
@ -648,7 +647,7 @@ class TimelinePresenterTest {
) )
presenter.test { presenter.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.eventSink.invoke(TimelineEvents.FocusOnEvent(AN_EVENT_ID)) initialState.eventSink.invoke(TimelineEvent.FocusOnEvent(AN_EVENT_ID))
awaitItem().also { state -> awaitItem().also { state ->
assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID) assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID)
@ -707,7 +706,7 @@ class TimelinePresenterTest {
) )
presenter.test { presenter.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.eventSink.invoke(TimelineEvents.FocusOnEvent(AN_EVENT_ID)) initialState.eventSink.invoke(TimelineEvent.FocusOnEvent(AN_EVENT_ID))
awaitItem().also { state -> awaitItem().also { state ->
assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID) assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID)
@ -762,7 +761,7 @@ class TimelinePresenterTest {
) )
presenter.test { presenter.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.eventSink.invoke(TimelineEvents.FocusOnEvent(AN_EVENT_ID)) initialState.eventSink.invoke(TimelineEvent.FocusOnEvent(AN_EVENT_ID))
awaitItem().also { state -> awaitItem().also { state ->
assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID) assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID)
@ -821,7 +820,7 @@ class TimelinePresenterTest {
) )
presenter.test { presenter.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.eventSink.invoke(TimelineEvents.FocusOnEvent(AN_EVENT_ID)) initialState.eventSink.invoke(TimelineEvent.FocusOnEvent(AN_EVENT_ID))
awaitItem().also { state -> awaitItem().also { state ->
assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID) assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID)
@ -854,10 +853,10 @@ class TimelinePresenterTest {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
assertThat(initialState.messageShieldDialogData).isNull() assertThat(initialState.messageShieldDialogData).isNull()
val shieldData = MessageShieldData(shield, null, null) val shieldData = MessageShieldData(shield, null, null)
initialState.eventSink(TimelineEvents.ShowShieldDialog(shieldData)) initialState.eventSink(TimelineEvent.ShowShieldDialog(shieldData))
awaitItem().also { state -> awaitItem().also { state ->
assertThat(state.messageShieldDialogData).isEqualTo(shieldData) assertThat(state.messageShieldDialogData).isEqualTo(shieldData)
state.eventSink(TimelineEvents.HideShieldDialog) state.eventSink(TimelineEvent.HideShieldDialog)
} }
awaitItem().also { state -> awaitItem().also { state ->
assertThat(state.messageShieldDialogData).isNull() assertThat(state.messageShieldDialogData).isNull()
@ -963,7 +962,7 @@ class TimelinePresenterTest {
val presenter = createTimelinePresenter(room = room, messagesNavigator = navigator) val presenter = createTimelinePresenter(room = room, messagesNavigator = navigator)
presenter.test { presenter.test {
val initialState = awaitFirstItem() val initialState = awaitFirstItem()
initialState.eventSink(TimelineEvents.NavigateToPredecessorOrSuccessorRoom(A_ROOM_ID)) initialState.eventSink(TimelineEvent.NavigateToPredecessorOrSuccessorRoom(A_ROOM_ID))
assert(onNavigateToRoomLambda) assert(onNavigateToRoomLambda)
.isCalledOnce() .isCalledOnce()
.with( .with(

View file

@ -50,7 +50,7 @@ class TimelineViewTest {
@Test @Test
fun `reaching the end of the timeline with more events to load emits a LoadMore event`() { fun `reaching the end of the timeline with more events to load emits a LoadMore event`() {
val eventsRecorder = EventsRecorder<TimelineEvents>() val eventsRecorder = EventsRecorder<TimelineEvent>()
rule.setTimelineView( rule.setTimelineView(
state = aTimelineState( state = aTimelineState(
timelineItems = persistentListOf<TimelineItem>( timelineItems = persistentListOf<TimelineItem>(
@ -62,12 +62,12 @@ class TimelineViewTest {
eventSink = eventsRecorder, eventSink = eventsRecorder,
), ),
) )
eventsRecorder.assertSingle(TimelineEvents.LoadMore(Timeline.PaginationDirection.BACKWARDS)) eventsRecorder.assertSingle(TimelineEvent.LoadMore(Timeline.PaginationDirection.BACKWARDS))
} }
@Test @Test
fun `reaching the end of the timeline does not send a LoadMore event`() { fun `reaching the end of the timeline does not send a LoadMore event`() {
val eventsRecorder = EventsRecorder<TimelineEvents>(expectEvents = false) val eventsRecorder = EventsRecorder<TimelineEvent>(expectEvents = false)
rule.setTimelineView( rule.setTimelineView(
state = aTimelineState( state = aTimelineState(
eventSink = eventsRecorder, eventSink = eventsRecorder,
@ -77,7 +77,7 @@ class TimelineViewTest {
@Test @Test
fun `scroll to bottom on live timeline does not emit the Event`() { fun `scroll to bottom on live timeline does not emit the Event`() {
val eventsRecorder = EventsRecorder<TimelineEvents>(expectEvents = false) val eventsRecorder = EventsRecorder<TimelineEvent>(expectEvents = false)
rule.setTimelineView( rule.setTimelineView(
state = aTimelineState( state = aTimelineState(
isLive = true, isLive = true,
@ -91,7 +91,7 @@ class TimelineViewTest {
@Test @Test
fun `scroll to bottom on detached timeline emits the expected Event`() { fun `scroll to bottom on detached timeline emits the expected Event`() {
val eventsRecorder = EventsRecorder<TimelineEvents>() val eventsRecorder = EventsRecorder<TimelineEvent>()
rule.setTimelineView( rule.setTimelineView(
state = aTimelineState( state = aTimelineState(
isLive = false, isLive = false,
@ -100,12 +100,12 @@ class TimelineViewTest {
) )
val contentDescription = rule.activity.getString(CommonStrings.a11y_jump_to_bottom) val contentDescription = rule.activity.getString(CommonStrings.a11y_jump_to_bottom)
rule.onNodeWithContentDescription(contentDescription).performClick() rule.onNodeWithContentDescription(contentDescription).performClick()
eventsRecorder.assertSingle(TimelineEvents.JumpToLive) eventsRecorder.assertSingle(TimelineEvent.JumpToLive)
} }
@Test @Test
fun `show shield dialog`() { fun `show shield dialog`() {
val eventsRecorder = EventsRecorder<TimelineEvents>() val eventsRecorder = EventsRecorder<TimelineEvent>()
rule.setTimelineView( rule.setTimelineView(
state = aTimelineState( state = aTimelineState(
timelineItems = persistentListOf<TimelineItem>( timelineItems = persistentListOf<TimelineItem>(
@ -122,15 +122,15 @@ class TimelineViewTest {
rule.onNodeWithContentDescription(contentDescription).performClick() rule.onNodeWithContentDescription(contentDescription).performClick()
eventsRecorder.assertList( eventsRecorder.assertList(
listOf( listOf(
TimelineEvents.OnScrollFinished(0), TimelineEvent.OnScrollFinished(0),
TimelineEvents.ShowShieldDialog(MessageShieldData(MessageShield.UnverifiedIdentity(true))), TimelineEvent.ShowShieldDialog(MessageShieldData(MessageShield.UnverifiedIdentity(true))),
) )
) )
} }
@Test @Test
fun `hide shield dialog`() { fun `hide shield dialog`() {
val eventsRecorder = EventsRecorder<TimelineEvents>() val eventsRecorder = EventsRecorder<TimelineEvent>()
rule.setTimelineView( rule.setTimelineView(
state = aTimelineState( state = aTimelineState(
isLive = false, isLive = false,
@ -139,12 +139,12 @@ class TimelineViewTest {
), ),
) )
rule.clickOn(CommonStrings.action_ok) rule.clickOn(CommonStrings.action_ok)
eventsRecorder.assertSingle(TimelineEvents.HideShieldDialog) eventsRecorder.assertSingle(TimelineEvent.HideShieldDialog)
} }
@Test @Test
fun `scrolling near to the start of the loaded items triggers a pre-fetch`() { fun `scrolling near to the start of the loaded items triggers a pre-fetch`() {
val eventsRecorder = EventsRecorder<TimelineEvents>() val eventsRecorder = EventsRecorder<TimelineEvent>()
val items = List<TimelineItem>(200) { val items = List<TimelineItem>(200) {
aTimelineItemEvent( aTimelineItemEvent(
eventId = EventId("\$event_$it"), eventId = EventId("\$event_$it"),
@ -167,8 +167,8 @@ class TimelineViewTest {
eventsRecorder.assertList( eventsRecorder.assertList(
listOf( listOf(
TimelineEvents.OnScrollFinished(firstIndex = 0), TimelineEvent.OnScrollFinished(firstIndex = 0),
TimelineEvents.LoadMore(Timeline.PaginationDirection.BACKWARDS), TimelineEvent.LoadMore(Timeline.PaginationDirection.BACKWARDS),
) )
) )
} }

View file

@ -8,15 +8,13 @@
package io.element.android.features.messages.impl.timeline.components.customreaction package io.element.android.features.messages.impl.timeline.components.customreaction
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
import io.element.android.features.messages.impl.timeline.aTimelineItemReactions import io.element.android.features.messages.impl.timeline.aTimelineItemReactions
import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.recentemojis.test.FakeEmojibaseProvider import io.element.android.libraries.recentemojis.test.FakeEmojibaseProvider
import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.test
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Rule import org.junit.Rule
@ -33,37 +31,33 @@ class CustomReactionPresenterTest {
@Test @Test
fun `present - handle selecting and de-selecting an event`() = runTest { fun `present - handle selecting and de-selecting an event`() = runTest {
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val event = aTimelineItemEvent(eventId = AN_EVENT_ID) val event = aTimelineItemEvent(eventId = AN_EVENT_ID)
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.target).isEqualTo(CustomReactionState.Target.None) assertThat(initialState.target).isEqualTo(CustomReactionState.Target.None)
initialState.eventSink(CustomReactionEvents.ShowCustomReactionSheet(event)) initialState.eventSink(CustomReactionEvent.ShowCustomReactionSheet(event))
assertThat(awaitItem().target).isEqualTo(CustomReactionState.Target.Loading(event)) assertThat(awaitItem().target).isEqualTo(CustomReactionState.Target.Loading(event))
val eventId = (awaitItem().target as? CustomReactionState.Target.Success)?.event?.eventId val eventId = (awaitItem().target as? CustomReactionState.Target.Success)?.event?.eventId
assertThat(eventId).isEqualTo(AN_EVENT_ID) assertThat(eventId).isEqualTo(AN_EVENT_ID)
initialState.eventSink(CustomReactionEvents.DismissCustomReactionSheet) initialState.eventSink(CustomReactionEvent.DismissCustomReactionSheet)
assertThat(awaitItem().target).isEqualTo(CustomReactionState.Target.None) assertThat(awaitItem().target).isEqualTo(CustomReactionState.Target.None)
} }
} }
@Test @Test
fun `present - handle selected emojis`() = runTest { fun `present - handle selected emojis`() = runTest {
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val reactions = aTimelineItemReactions(count = 1, isHighlighted = true) val reactions = aTimelineItemReactions(count = 1, isHighlighted = true)
val event = aTimelineItemEvent(eventId = AN_EVENT_ID, timelineItemReactions = reactions) val event = aTimelineItemEvent(eventId = AN_EVENT_ID, timelineItemReactions = reactions)
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.target).isEqualTo(CustomReactionState.Target.None) assertThat(initialState.target).isEqualTo(CustomReactionState.Target.None)
val key = reactions.reactions.first().key val key = reactions.reactions.first().key
initialState.eventSink(CustomReactionEvents.ShowCustomReactionSheet(event)) initialState.eventSink(CustomReactionEvent.ShowCustomReactionSheet(event))
assertThat(awaitItem().target).isEqualTo(CustomReactionState.Target.Loading(event)) assertThat(awaitItem().target).isEqualTo(CustomReactionState.Target.Loading(event))

View file

@ -66,10 +66,10 @@ class EmojiPickerPresenterTest {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.isSearchActive).isFalse() assertThat(initialState.isSearchActive).isFalse()
initialState.eventSink(EmojiPickerEvents.ToggleSearchActive(true)) initialState.eventSink(EmojiPickerEvent.ToggleSearchActive(true))
assertThat(awaitItem().isSearchActive).isTrue() assertThat(awaitItem().isSearchActive).isTrue()
initialState.eventSink(EmojiPickerEvents.ToggleSearchActive(false)) initialState.eventSink(EmojiPickerEvent.ToggleSearchActive(false))
assertThat(awaitItem().isSearchActive).isFalse() assertThat(awaitItem().isSearchActive).isFalse()
} }
} }

View file

@ -13,7 +13,7 @@ import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.createAndroidComposeRule import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performClick
import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.junit.runners.AndroidJUnit4
import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.TimelineEvent
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent
import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.testtags.TestTags
import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.libraries.ui.strings.CommonStrings
@ -39,7 +39,7 @@ class TimelineItemPollViewTest {
} }
private fun testAnswer(answerIndex: Int) { private fun testAnswer(answerIndex: Int) {
val eventsRecorder = EventsRecorder<TimelineEvents.TimelineItemPollEvents>() val eventsRecorder = EventsRecorder<TimelineEvent.TimelineItemPollEvent>()
val content = aTimelineItemPollContent() val content = aTimelineItemPollContent()
rule.setContent { rule.setContent {
TimelineItemPollView( TimelineItemPollView(
@ -52,12 +52,12 @@ class TimelineItemPollViewTest {
matcher = hasText(answer.text), matcher = hasText(answer.text),
useUnmergedTree = true, useUnmergedTree = true,
).performClick() ).performClick()
eventsRecorder.assertSingle(TimelineEvents.SelectPollAnswer(content.eventId!!, answer.id)) eventsRecorder.assertSingle(TimelineEvent.SelectPollAnswer(content.eventId!!, answer.id))
} }
@Test @Test
fun `editing a poll should emit a PollEditClicked event`() { fun `editing a poll should emit a PollEditClicked event`() {
val eventsRecorder = EventsRecorder<TimelineEvents.TimelineItemPollEvents>() val eventsRecorder = EventsRecorder<TimelineEvent.TimelineItemPollEvent>()
val content = aTimelineItemPollContent( val content = aTimelineItemPollContent(
isMine = true, isMine = true,
isEditable = true, isEditable = true,
@ -69,12 +69,12 @@ class TimelineItemPollViewTest {
) )
} }
rule.clickOn(CommonStrings.action_edit_poll) rule.clickOn(CommonStrings.action_edit_poll)
eventsRecorder.assertSingle(TimelineEvents.EditPoll(content.eventId!!)) eventsRecorder.assertSingle(TimelineEvent.EditPoll(content.eventId!!))
} }
@Test @Test
fun `closing a poll should emit a PollEndClicked event`() { fun `closing a poll should emit a PollEndClicked event`() {
val eventsRecorder = EventsRecorder<TimelineEvents.TimelineItemPollEvents>() val eventsRecorder = EventsRecorder<TimelineEvent.TimelineItemPollEvent>()
val content = aTimelineItemPollContent( val content = aTimelineItemPollContent(
isMine = true, isMine = true,
) )
@ -88,6 +88,6 @@ class TimelineItemPollViewTest {
// A confirmation dialog should be shown // A confirmation dialog should be shown
eventsRecorder.assertEmpty() eventsRecorder.assertEmpty()
rule.pressTag(TestTags.dialogPositive.value) rule.pressTag(TestTags.dialogPositive.value)
eventsRecorder.assertSingle(TimelineEvents.EndPoll(content.eventId!!)) eventsRecorder.assertSingle(TimelineEvent.EndPoll(content.eventId!!))
} }
} }

View file

@ -8,9 +8,6 @@
package io.element.android.features.messages.impl.timeline.components.reactionsummary package io.element.android.features.messages.impl.timeline.components.reactionsummary
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.messages.impl.timeline.model.anAggregatedReaction import io.element.android.features.messages.impl.timeline.model.anAggregatedReaction
import io.element.android.libraries.matrix.api.room.RoomMembersState import io.element.android.libraries.matrix.api.room.RoomMembersState
@ -21,6 +18,7 @@ import io.element.android.libraries.matrix.test.A_USER_NAME
import io.element.android.libraries.matrix.test.room.FakeBaseRoom import io.element.android.libraries.matrix.test.room.FakeBaseRoom
import io.element.android.libraries.matrix.test.room.aRoomMember import io.element.android.libraries.matrix.test.room.aRoomMember
import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.test
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Rule import org.junit.Rule
@ -32,7 +30,7 @@ class ReactionSummaryPresenterTest {
private val aggregatedReaction = anAggregatedReaction(userId = A_USER_ID, key = "👍", isHighlighted = true) private val aggregatedReaction = anAggregatedReaction(userId = A_USER_ID, key = "👍", isHighlighted = true)
private val roomMember = aRoomMember(userId = A_USER_ID, avatarUrl = AN_AVATAR_URL, displayName = A_USER_NAME) private val roomMember = aRoomMember(userId = A_USER_ID, avatarUrl = AN_AVATAR_URL, displayName = A_USER_NAME)
private val summaryEvent = ReactionSummaryEvents.ShowReactionSummary(AN_EVENT_ID, listOf(aggregatedReaction), aggregatedReaction.key) private val summaryEvent = ReactionSummaryEvent.ShowReactionSummary(AN_EVENT_ID, listOf(aggregatedReaction), aggregatedReaction.key)
private val room = FakeBaseRoom().apply { private val room = FakeBaseRoom().apply {
givenRoomMembersState(RoomMembersState.Ready(persistentListOf(roomMember))) givenRoomMembersState(RoomMembersState.Ready(persistentListOf(roomMember)))
} }
@ -40,25 +38,21 @@ class ReactionSummaryPresenterTest {
@Test @Test
fun `present - handle showing and hiding the reaction summary`() = runTest { fun `present - handle showing and hiding the reaction summary`() = runTest {
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.target).isNull() assertThat(initialState.target).isNull()
initialState.eventSink(summaryEvent) initialState.eventSink(summaryEvent)
assertThat(awaitItem().target).isNotNull() assertThat(awaitItem().target).isNotNull()
initialState.eventSink(ReactionSummaryEvents.Clear) initialState.eventSink(ReactionSummaryEvent.Clear)
assertThat(awaitItem().target).isNull() assertThat(awaitItem().target).isNull()
} }
} }
@Test @Test
fun `present - handle reaction summary content and avatars populated`() = runTest { fun `present - handle reaction summary content and avatars populated`() = runTest {
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.target).isNull() assertThat(initialState.target).isNull()

View file

@ -8,12 +8,10 @@
package io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet package io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.test
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
@ -25,12 +23,10 @@ class ReadReceiptBottomSheetPresenterTest {
@Test @Test
fun `present - handle event selected`() = runTest { fun `present - handle event selected`() = runTest {
val presenter = createPresenter() val presenter = createPresenter()
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val selectedEvent = aTimelineItemEvent() val selectedEvent = aTimelineItemEvent()
initialState.eventSink(ReadReceiptBottomSheetEvents.EventSelected(selectedEvent)) initialState.eventSink(ReadReceiptBottomSheetEvent.EventSelected(selectedEvent))
assertThat(awaitItem().selectedEvent).isSameInstanceAs(selectedEvent) assertThat(awaitItem().selectedEvent).isSameInstanceAs(selectedEvent)
} }
} }
@ -38,14 +34,12 @@ class ReadReceiptBottomSheetPresenterTest {
@Test @Test
fun `present - handle dismiss`() = runTest { fun `present - handle dismiss`() = runTest {
val presenter = createPresenter() val presenter = createPresenter()
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val selectedEvent = aTimelineItemEvent() val selectedEvent = aTimelineItemEvent()
initialState.eventSink(ReadReceiptBottomSheetEvents.EventSelected(selectedEvent)) initialState.eventSink(ReadReceiptBottomSheetEvent.EventSelected(selectedEvent))
skipItems(1) skipItems(1)
initialState.eventSink(ReadReceiptBottomSheetEvents.Dismiss) initialState.eventSink(ReadReceiptBottomSheetEvent.Dismiss)
assertThat(awaitItem().selectedEvent).isNull() assertThat(awaitItem().selectedEvent).isNull()
} }
} }

View file

@ -8,10 +8,7 @@
package io.element.android.features.messages.impl.typing package io.element.android.features.messages.impl.typing
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.Event import app.cash.turbine.Event
import app.cash.turbine.test
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.JoinedRoom import io.element.android.libraries.matrix.api.room.JoinedRoom
@ -26,6 +23,7 @@ import io.element.android.libraries.matrix.test.room.aRoomMember
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.test
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
@ -40,9 +38,7 @@ class TypingNotificationPresenterTest {
@Test @Test
fun `present - initial state`() = runTest { fun `present - initial state`() = runTest {
val presenter = createPresenter() val presenter = createPresenter()
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.renderTypingNotifications).isTrue() assertThat(initialState.renderTypingNotifications).isTrue()
assertThat(initialState.typingMembers).isEmpty() assertThat(initialState.typingMembers).isEmpty()
@ -61,9 +57,7 @@ class TypingNotificationPresenterTest {
joinedRoom = room, joinedRoom = room,
sessionPreferencesStore = sessionPreferencesStore, sessionPreferencesStore = sessionPreferencesStore,
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
skipItems(1) skipItems(1)
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.renderTypingNotifications).isFalse() assertThat(initialState.renderTypingNotifications).isFalse()
@ -95,9 +89,7 @@ class TypingNotificationPresenterTest {
val typingMembersFlow = MutableStateFlow<List<UserId>>(emptyList()) val typingMembersFlow = MutableStateFlow<List<UserId>>(emptyList())
val room = FakeJoinedRoom(roomTypingMembersFlow = typingMembersFlow) val room = FakeJoinedRoom(roomTypingMembersFlow = typingMembersFlow)
val presenter = createPresenter(joinedRoom = room) val presenter = createPresenter(joinedRoom = room)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.typingMembers).isEmpty() assertThat(initialState.typingMembers).isEmpty()
typingMembersFlow.emit(listOf(A_USER_ID_2)) typingMembersFlow.emit(listOf(A_USER_ID_2))
@ -133,9 +125,7 @@ class TypingNotificationPresenterTest {
) )
} }
val presenter = createPresenter(joinedRoom = room) val presenter = createPresenter(joinedRoom = room)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.typingMembers).isEmpty() assertThat(initialState.typingMembers).isEmpty()
typingMembersFlow.emit(listOf(A_USER_ID_2)) typingMembersFlow.emit(listOf(A_USER_ID_2))
@ -160,9 +150,7 @@ class TypingNotificationPresenterTest {
val typingMembersFlow = MutableStateFlow<List<UserId>>(emptyList()) val typingMembersFlow = MutableStateFlow<List<UserId>>(emptyList())
val room = FakeJoinedRoom(roomTypingMembersFlow = typingMembersFlow) val room = FakeJoinedRoom(roomTypingMembersFlow = typingMembersFlow)
val presenter = createPresenter(joinedRoom = room) val presenter = createPresenter(joinedRoom = room)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.typingMembers).isEmpty() assertThat(initialState.typingMembers).isEmpty()
typingMembersFlow.emit(listOf(A_USER_ID_2)) typingMembersFlow.emit(listOf(A_USER_ID_2))
@ -194,9 +182,7 @@ class TypingNotificationPresenterTest {
val typingMembersFlow = MutableStateFlow<List<UserId>>(emptyList()) val typingMembersFlow = MutableStateFlow<List<UserId>>(emptyList())
val room = FakeJoinedRoom(roomTypingMembersFlow = typingMembersFlow) val room = FakeJoinedRoom(roomTypingMembersFlow = typingMembersFlow)
val presenter = createPresenter(joinedRoom = room) val presenter = createPresenter(joinedRoom = room)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.typingMembers).isEmpty() assertThat(initialState.typingMembers).isEmpty()
typingMembersFlow.emit(listOf(A_USER_ID_2)) typingMembersFlow.emit(listOf(A_USER_ID_2))